Merge branch 'MDL-68217-master' of git://github.com/lucaboesch/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Wed, 6 May 2020 04:50:15 +0000 (12:50 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Wed, 6 May 2020 04:50:15 +0000 (12:50 +0800)
84 files changed:
contentbank/classes/content.php
contentbank/classes/contentbank.php
contentbank/classes/contenttype.php
contentbank/contenttype/h5p/classes/contenttype.php
contentbank/tests/behat/events.feature [new file with mode: 0644]
contentbank/tests/contentbank_test.php
contentbank/upload.php
course/format/topics/styles.css [deleted file]
course/format/weeks/styles.css [deleted file]
enrol/database/settings.php
enrol/database/settingslib.php [deleted file]
enrol/database/upgrade.txt
grade/grading/form/guide/lang/en/gradingform_guide.php
grade/grading/form/guide/templates/grades/grader/gradingpanel.mustache
lang/en/contentbank.php
lib/classes/event/contentbank_content_created.php [new file with mode: 0644]
lib/classes/event/contentbank_content_deleted.php [new file with mode: 0644]
lib/classes/event/contentbank_content_updated.php [new file with mode: 0644]
lib/classes/event/contentbank_content_uploaded.php [new file with mode: 0644]
lib/classes/event/contentbank_content_viewed.php [new file with mode: 0644]
lib/editor/atto/plugins/accessibilitychecker/tests/behat/accessibilitychecker.feature
lib/editor/atto/plugins/image/lang/en/atto_image.php
lib/editor/atto/plugins/image/tests/behat/image.feature
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js
lib/editor/atto/plugins/image/yui/src/button/js/button.js
lib/editor/atto/plugins/italic/tests/behat/italic.feature
lib/editor/atto/plugins/italic/yui/build/moodle-atto_italic-button/moodle-atto_italic-button-debug.js
lib/editor/atto/plugins/italic/yui/build/moodle-atto_italic-button/moodle-atto_italic-button-min.js
lib/editor/atto/plugins/italic/yui/build/moodle-atto_italic-button/moodle-atto_italic-button.js
lib/editor/atto/plugins/italic/yui/src/button/js/button.js
lib/editor/atto/tests/behat/customtoolbar.feature
lib/editor/atto/tests/behat/disablecontrol.feature
lib/questionlib.php
lib/table/amd/build/dynamic.min.js
lib/table/amd/build/dynamic.min.js.map
lib/table/amd/build/local/dynamic/repository.min.js
lib/table/amd/build/local/dynamic/repository.min.js.map
lib/table/amd/src/dynamic.js
lib/table/amd/src/local/dynamic/repository.js
lib/table/classes/external/dynamic/fetch.php
lib/tablelib.php
lib/tests/event/contentbank_content_created_test.php [new file with mode: 0644]
lib/tests/event/contentbank_content_deleted_test.php [new file with mode: 0644]
lib/tests/event/contentbank_content_updated_test.php [new file with mode: 0644]
lib/tests/event/contentbank_content_uploaded_test.php [new file with mode: 0644]
lib/tests/event/contentbank_content_viewed_test.php [new file with mode: 0644]
lib/tests/questionlib_test.php
media/player/videojs/amd/build/Youtube-lazy.min.js
media/player/videojs/amd/build/Youtube-lazy.min.js.map
media/player/videojs/amd/build/video-lazy.min.js
media/player/videojs/amd/build/video-lazy.min.js.map
media/player/videojs/amd/build/videojs-flash-lazy.min.js
media/player/videojs/amd/build/videojs-flash-lazy.min.js.map
media/player/videojs/amd/src/Youtube-lazy.js
media/player/videojs/amd/src/video-lazy.js
media/player/videojs/amd/src/videojs-flash-lazy.js
media/player/videojs/classes/plugin.php
media/player/videojs/fonts/VideoJS.svg [changed mode: 0644->0755]
media/player/videojs/fonts/VideoJS.ttf [changed mode: 0644->0755]
media/player/videojs/fonts/VideoJS.woff [changed mode: 0644->0755]
media/player/videojs/readme_moodle.txt
media/player/videojs/tests/player_test.php
media/player/videojs/thirdpartylibs.xml
media/player/videojs/videojs/lang/ar.js
media/player/videojs/videojs/lang/ar.json
media/player/videojs/videojs/lang/de.js
media/player/videojs/videojs/lang/de.json
media/player/videojs/videojs/lang/en.js
media/player/videojs/videojs/lang/en.json
media/player/videojs/videojs/lang/fa.js
media/player/videojs/videojs/lang/fa.json
media/player/videojs/videojs/lang/gd.json [changed mode: 0644->0755]
media/player/videojs/videojs/lang/nb.js
media/player/videojs/videojs/lang/nb.json
media/player/videojs/videojs/lang/nn.js
media/player/videojs/videojs/lang/nn.json
question/format/gift/format.php
question/format/gift/tests/giftformat_test.php
theme/boost/scss/moodle/course.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css
user/tests/behat/view_participants.feature

index 27975cb..25bc2dd 100644 (file)
@@ -29,6 +29,7 @@ use stored_file;
 use stdClass;
 use coding_exception;
 use moodle_url;
+use core\event\contentbank_content_updated;
 
 /**
  * Content manager class
@@ -100,7 +101,13 @@ abstract class content {
         }
         $this->content->usermodified = $USER->id;
         $this->content->timemodified = time();
-        return $DB->update_record('contentbank_content', $this->content);
+        $result = $DB->update_record('contentbank_content', $this->content);
+        if ($result) {
+            // Trigger an event for updating this content.
+            $event = contentbank_content_updated::create_from_record($this->content);
+            $event->trigger();
+        }
+        return $result;
     }
 
     /**
index 0a21353..35499ce 100644 (file)
@@ -24,6 +24,8 @@
 
 namespace core_contentbank;
 
+use stored_file;
+
 /**
  * Content bank class
  *
@@ -202,4 +204,32 @@ class contentbank {
 
         return $contents;
     }
+
+    /**
+     * Create content from a file information.
+     *
+     * @param \context $context Context where to upload the file and content.
+     * @param int $userid Id of the user uploading the file.
+     * @param stored_file $file The file to get information from
+     * @return content
+     */
+    public function create_content_from_file(\context $context, int $userid, stored_file $file): ?content {
+        global $USER;
+        if (empty($userid)) {
+            $userid = $USER->id;
+        }
+        // Get the contenttype to manage given file's extension.
+        $filename = $file->get_filename();
+        $extension = $this->get_extension($filename);
+        $plugin = $this->get_extension_supporter($extension, $context);
+        $classname = '\\contenttype_'.$plugin.'\\contenttype';
+        $record = new \stdClass();
+        $record->name = $filename;
+        $record->usercreated = $userid;
+        $contentype = new $classname($context);
+        $content = $contentype->create_content($record);
+        $event = \core\event\contentbank_content_uploaded::create_from_record($content->get_content());
+        $event->trigger();
+        return $content;
+    }
 }
index 05922b7..7f7fbf5 100644 (file)
@@ -24,6 +24,9 @@
 
 namespace core_contentbank;
 
+use core\event\contentbank_content_created;
+use core\event\contentbank_content_deleted;
+use core\event\contentbank_content_viewed;
 use moodle_url;
 
 /**
@@ -71,10 +74,15 @@ abstract class contenttype {
         $entry->usermodified = $entry->usercreated;
         $entry->timemodified = $entry->timecreated;
         $entry->configdata = $record->configdata ?? '';
+        $entry->instanceid = $record->instanceid ?? 0;
         $entry->id = $DB->insert_record('contentbank_content', $entry);
         if ($entry->id) {
             $classname = '\\'.$entry->contenttype.'\\content';
-            return new $classname($entry);
+            $content = new $classname($entry);
+            // Trigger an event for creating the content.
+            $event = contentbank_content_created::create_from_record($content->get_content());
+            $event->trigger();
+            return $content;
         }
         return null;
     }
@@ -95,7 +103,23 @@ abstract class contenttype {
         }
 
         // Delete the contentbank DB entry.
-        return $DB->delete_records('contentbank_content', ['id' => $content->get_id()]);
+        $result = $DB->delete_records('contentbank_content', ['id' => $content->get_id()]);
+        if ($result) {
+            // Trigger an event for deleting this content.
+            $record = $content->get_content();
+            $event = contentbank_content_deleted::create([
+                'objectid' => $content->get_id(),
+                'relateduserid' => $record->usercreated,
+                'context' => \context::instance_by_id($record->contextid),
+                'other' => [
+                    'contenttype' => $content->get_content_type(),
+                    'name' => $content->get_name()
+                ]
+            ]);
+            $event->add_record_snapshot('contentbank_content', $record);
+            $event->trigger();
+        }
+        return $result;
     }
 
     /**
@@ -149,6 +173,10 @@ abstract class contenttype {
      * @return string           HTML code to include in view.php.
      */
     public function get_view_content(\stdClass $record): string {
+        // Trigger an event for viewing this content.
+        $event = contentbank_content_viewed::create_from_record($record);
+        $event->trigger();
+
         // Main contenttype class can visualize the content, but plugins could overwrite visualization.
         return '';
     }
index f28bee8..806205d 100644 (file)
@@ -24,6 +24,7 @@
 
 namespace contenttype_h5p;
 
+use core\event\contentbank_content_viewed;
 use stdClass;
 use html_writer;
 
@@ -45,7 +46,9 @@ class contenttype extends \core_contentbank\contenttype {
     public function delete_content(\core_contentbank\content $content): bool {
         // Delete the H5P content.
         $factory = new \core_h5p\factory();
-        \core_h5p\api::delete_content_from_pluginfile_url($content->get_file_url(), $factory);
+        if (!empty($content->get_file_url())) {
+            \core_h5p\api::delete_content_from_pluginfile_url($content->get_file_url(), $factory);
+        }
 
         // Delete the content from the content_bank.
         return parent::delete_content($content);
@@ -58,6 +61,10 @@ class contenttype extends \core_contentbank\contenttype {
      * @return string            HTML code to include in view.php.
      */
     public function get_view_content(\stdClass $record): string {
+        // Trigger an event for viewing this content.
+        $event = contentbank_content_viewed::create_from_record($record);
+        $event->trigger();
+
         $content = new content($record);
         $fileurl = $content->get_file_url();
         $html = html_writer::tag('h2', $content->get_name());
diff --git a/contentbank/tests/behat/events.feature b/contentbank/tests/behat/events.feature
new file mode 100644 (file)
index 0000000..c520199
--- /dev/null
@@ -0,0 +1,73 @@
+@core @core_contentbank @_file_upload @_switch_iframe @javascript
+Feature: Confirm content bank events are triggered
+  In order to log content bank actions
+  As an admin
+  I need to be able to check triggered events
+
+  Background:
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1        | 0        |
+    And the following "contentbank content" exist:
+      | course | contenttype     | user  | contentname |
+      | C1     | contenttype_h5p | admin | Existing    |
+    And I log in as "admin"
+    And I follow "Manage private files..."
+    And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
+    And I click on "Save changes" "button"
+    And I am on "Course 1" course homepage with editing mode on
+    And I add the "Navigation" block if not present
+
+  Scenario: Content created and uploaded events when uploading a content file
+    Given I navigate to "Reports > Live logs" in site administration
+    And I should not see "Content uploaded"
+    And I should not see "Content created"
+    And I am on "Course 1" course homepage
+    And I expand "Site pages" node
+    And I click on "Content bank" "link"
+    When I click on "Upload" "link"
+    And I click on "Choose a file..." "button"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "filltheblanks.h5p" "link"
+    And I click on "Select this file" "button"
+    And I click on "Save changes" "button"
+    And I navigate to "Reports > Live logs" in site administration
+    Then I should see "Content uploaded"
+    And I should see "Content created"
+
+  Scenario: Content viewed event
+    Given I navigate to "Reports > Live logs" in site administration
+    And I should not see "Content viewed"
+    And I am on "Course 1" course homepage
+    And I expand "Site pages" node
+    And I click on "Content bank" "link"
+    When I click on "Existing" "link"
+    And I navigate to "Reports > Live logs" in site administration
+    Then I should see "Content viewed"
+
+  Scenario: Content deleted event
+    Given I navigate to "Reports > Live logs" in site administration
+    And I should not see "Content deleted"
+    And I am on "Course 1" course homepage
+    And I expand "Site pages" node
+    And I click on "Content bank" "link"
+    And I click on "Existing" "link"
+    And I open the action menu in "region-main-settings-menu" "region"
+    When I choose "Delete" in the open action menu
+    And I click on "Delete" "button" in the "Delete content" "dialogue"
+    And I navigate to "Reports > Live logs" in site administration
+    Then I should see "Content deleted"
+
+  Scenario: Content updated event when renaming
+    Given I navigate to "Reports > Live logs" in site administration
+    And I should not see "Content updated"
+    And I am on "Course 1" course homepage
+    And I expand "Site pages" node
+    And I click on "Content bank" "link"
+    And I click on "Existing" "link"
+    And I open the action menu in "region-main-settings-menu" "region"
+    When I choose "Rename" in the open action menu
+    And I set the field "Content name" to "New name"
+    And I click on "Rename" "button"
+    And I navigate to "Reports > Live logs" in site administration
+    Then I should see "Content updated"
\ No newline at end of file
index 824f7a5..e420456 100644 (file)
@@ -181,11 +181,11 @@ class core_contentbank_testcase extends advanced_testcase {
      *
      * @dataProvider search_contents_provider
      * @param  string $search String to search.
-     * @param  int $contextid Contextid to search.
+     * @param  string $where Context where to search.
      * @param  int $expectedresult Expected result.
      * @param  array $contexts List of contexts where to create content.
      */
-    public function test_search_contents(?string $search, int $contextid, int $expectedresult, array $contexts = []): void {
+    public function test_search_contents(?string $search, string $where, int $expectedresult, array $contexts = []): void {
         global $DB;
 
         $this->resetAfterTest();
@@ -195,11 +195,26 @@ class core_contentbank_testcase extends advanced_testcase {
         $manager = $this->getDataGenerator()->create_user();
         $this->getDataGenerator()->role_assign($managerroleid, $manager->id);
 
+        // Create a category and a course.
+        $coursecat = $this->getDataGenerator()->create_category();
+        $course = $this->getDataGenerator()->create_course();
+        $existingcontexts = [];
+        $existingcontexts['system'] = \context_system::instance();
+        $existingcontexts['category'] = \context_coursecat::instance($coursecat->id);
+        $existingcontexts['course'] = \context_course::instance($course->id);
+
+        if (empty($where)) {
+            $contextid = 0;
+        } else {
+            $contextid = $existingcontexts[$where]->id;
+        }
+
         // Add some content to the content bank.
         $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
         foreach ($contexts as $context) {
+            $contextinstance = $existingcontexts[$context];
             $records = $generator->generate_contentbank_data('contenttype_h5p', 3,
-                $manager->id, $context, false);
+                $manager->id, $contextinstance, false);
         }
 
         // Search for some content.
@@ -220,98 +235,126 @@ class core_contentbank_testcase extends advanced_testcase {
      * @return array
      */
     public function search_contents_provider(): array {
-        // Create a category and a course.
-        $systemcontext = \context_system::instance();
-        $coursecat = $this->getDataGenerator()->create_category();
-        $course = $this->getDataGenerator()->create_course();
-        $coursecatcontext = \context_coursecat::instance($coursecat->id);
-        $coursecontext = \context_course::instance($course->id);
 
         return [
             'Search all content in all contexts' => [
                 null,
-                0,
+                '',
                 9,
-                [$systemcontext, $coursecatcontext, $coursecontext]
+                ['system', 'category', 'course']
             ],
             'Search in all contexts for existing string in all contents' => [
                 'content',
-                0,
+                '',
                 9,
-                [$systemcontext, $coursecatcontext, $coursecontext]
+                ['system', 'category', 'course']
             ],
             'Search in all contexts for unexisting string in all contents' => [
                 'chocolate',
+                '',
                 0,
-                0,
-                [$systemcontext, $coursecatcontext, $coursecontext]
+                ['system', 'category', 'course']
             ],
             'Search in all contexts for existing string in some contents' => [
                 '1',
-                0,
+                '',
                 3,
-                [$systemcontext, $coursecatcontext, $coursecontext]
+                ['system', 'category', 'course']
             ],
             'Search in all contexts for existing string in some contents (create only 1 context)' => [
                 '1',
-                0,
+                '',
                 1,
-                [$systemcontext]
+                ['system']
             ],
             'Search in system context for existing string in all contents' => [
                 'content',
-                $systemcontext->id,
+                'system',
                 3,
-                [$systemcontext, $coursecatcontext, $coursecontext]
+                ['system', 'category', 'course']
             ],
             'Search in category context for unexisting string in all contents' => [
                 'chocolate',
-                $coursecatcontext->id,
+                'category',
                 0,
-                [$systemcontext, $coursecatcontext, $coursecontext]
+                ['system', 'category', 'course']
             ],
             'Search in course context for existing string in some contents' => [
                 '1',
-                $coursecontext->id,
+                'course',
                 1,
-                [$systemcontext, $coursecatcontext, $coursecontext]
+                ['system', 'category', 'course']
             ],
             'Search in system context' => [
                 null,
-                $systemcontext->id,
+                'system',
                 3,
-                [$systemcontext, $coursecatcontext, $coursecontext]
+                ['system', 'category', 'course']
             ],
             'Search in course context with existing content' => [
                 null,
-                $coursecontext->id,
+                'course',
                 3,
-                [$systemcontext, $coursecatcontext, $coursecontext]
+                ['system', 'category', 'course']
             ],
             'Search in course context without existing content' => [
                 null,
-                $coursecontext->id,
+                'course',
                 0,
-                [$systemcontext, $coursecatcontext]
+                ['system', 'category']
             ],
             'Search in an empty contentbank' => [
                 null,
-                0,
+                '',
                 0,
                 []
             ],
             'Search in a context in an empty contentbank' => [
                 null,
-                $systemcontext->id,
+                'system',
                 0,
                 []
             ],
             'Search for a string in an empty contentbank' => [
                 'content',
-                0,
+                '',
                 0,
                 []
             ],
         ];
     }
+
+    /**
+     * Test create_content_from_file function.
+     *
+     * @covers ::create_content_from_file
+     */
+    public function test_create_content_from_file() {
+        global $USER;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $systemcontext = \context_system::instance();
+        $name = 'dummy_h5p.h5p';
+
+        // Create a dummy H5P file.
+        $dummyh5p = array(
+            'contextid' => $systemcontext->id,
+            'component' => 'contentbank',
+            'filearea' => 'public',
+            'itemid' => 1,
+            'filepath' => '/',
+            'filename' => $name,
+            'userid' => $USER->id
+        );
+        $fs = get_file_storage();
+        $dummyh5pfile = $fs->create_file_from_string($dummyh5p, 'Dummy H5Pcontent');
+
+        $cb = new \core_contentbank\contentbank();
+        $content = $cb->create_content_from_file($systemcontext, $USER->id, $dummyh5pfile);
+
+        $this->assertEquals('contenttype_h5p', $content->get_content_type());
+        $this->assertInstanceOf('\\contenttype_h5p\\content', $content);
+        $this->assertEquals($name, $content->get_name());
+    }
 }
index 48b651e..0a88a69 100644 (file)
@@ -72,25 +72,14 @@ if ($mform->is_cancelled()) {
     redirect($returnurl);
 } else if ($formdata = $mform->get_data()) {
     require_sesskey();
-
-    // Get the file and the contenttype to manage given file's extension.
-    $usercontext = context_user::instance($USER->id);
+    // Get the file and create the content based on it.
+    $usercontext = \context_user::instance($USER->id);
     $fs = get_file_storage();
     $files = $fs->get_area_files($usercontext->id, 'user', 'draft', $formdata->file, 'itemid, filepath, filename', false);
-
     if (!empty($files)) {
         $file = reset($files);
-        $filename = $file->get_filename();
-        $extension = $cb->get_extension($filename);
-        $plugin = $cb->get_extension_supporter($extension, $context);
-        $classname = '\\contenttype_'.$plugin.'\\contenttype';
-        $record = new stdClass();
-        $record->name = $filename;
-        if (class_exists($classname)) {
-            $contentype = new $classname($context);
-            $content = $contentype->create_content($record);
-            file_save_draft_area_files($formdata->file, $contextid, 'contentbank', 'public', $content->get_id());
-        }
+        $content = $cb->create_content_from_file($context, $USER->id, $file);
+        file_save_draft_area_files($formdata->file, $contextid, 'contentbank', 'public', $content->get_id());
     }
     redirect($returnurl);
 }
diff --git a/course/format/topics/styles.css b/course/format/topics/styles.css
deleted file mode 100644 (file)
index 319e883..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-.course-content ul.topics {
-    margin: 0;
-    list-style: none;
-}
-
-.course-content ul.topics li.section .content {
-    margin: 0 40px;
-}
-
-.course-content ul.topics li.section .left,
-.course-content ul.topics li.section .right {
-    width: 40px;
-    padding: 0 6px;
-}
-
-.course-content ul.topics li.section .left {
-    padding-top: 22px;
-    text-align: right;
-}
-
-.jsenabled .course-content ul.topics li.section .left,
-.jsenabled .course-content ul.topics li.section .right {
-    width: auto;
-}
-
-.course-content ul.topics li.section .left .section-handle img.icon {
-    padding: 0;
-    vertical-align: baseline;
-}
-
-.course-content ul.topics li.section .section_action_menu .textmenu,
-.course-content ul.topics li.section .section_action_menu .menu-action-text {
-    white-space: nowrap;
-}
\ No newline at end of file
diff --git a/course/format/weeks/styles.css b/course/format/weeks/styles.css
deleted file mode 100644 (file)
index eee188f..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-.course-content ul.weeks {
-    margin: 0;
-    list-style: none;
-}
-
-.course-content ul.weeks li.section .content {
-    margin: 0 40px;
-}
-
-.course-content ul.weeks li.section .left,
-.course-content ul.weeks li.section .right {
-    width: 40px;
-    padding: 0 6px;
-}
-
-.course-content ul.weeks li.section .left {
-    padding-top: 22px;
-    text-align: right;
-}
-
-.jsenabled .course-content ul.weeks li.section .left,
-.jsenabled .course-content ul.weeks li.section .right {
-    width: auto;
-}
-
-.course-content ul.weeks li.section .left .section-handle img.icon {
-    padding: 0;
-    vertical-align: baseline;
-}
-
-.course-content ul.weeks li.section .section_action_menu .textmenu,
-.course-content ul.weeks li.section .section_action_menu .menu-action-text {
-    white-space: nowrap;
-}
\ No newline at end of file
index f5dde91..28c7504 100644 (file)
@@ -111,9 +111,9 @@ if ($ADMIN->fulltree) {
 
     $settings->add(new admin_setting_configtext('enrol_database/newcoursecategory', get_string('newcoursecategory', 'enrol_database'), '', ''));
 
-    require_once($CFG->dirroot.'/enrol/database/settingslib.php');
-
-    $settings->add(new enrol_database_admin_setting_category('enrol_database/defaultcategory', get_string('defaultcategory', 'enrol_database'), get_string('defaultcategory_desc', 'enrol_database')));
+    $settings->add(new admin_settings_coursecat_select('enrol_database/defaultcategory',
+        get_string('defaultcategory', 'enrol_database'),
+        get_string('defaultcategory_desc', 'enrol_database'), 1));
 
     $settings->add(new admin_setting_configtext('enrol_database/templatecourse', get_string('templatecourse', 'enrol_database'), get_string('templatecourse_desc', 'enrol_database'), ''));
 }
diff --git a/enrol/database/settingslib.php b/enrol/database/settingslib.php
deleted file mode 100644 (file)
index a1bace8..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Database enrolment plugin custom settings.
- *
- * @package    enrol_database
- * @copyright  2013 Darko Miletic
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Class implements new specialized setting for course categories that are loaded
- * only when required
- * @author Darko Miletic
- *
- */
-class enrol_database_admin_setting_category extends admin_setting_configselect {
-    public function __construct($name, $visiblename, $description) {
-        parent::__construct($name, $visiblename, $description, 1, null);
-    }
-
-    public function load_choices() {
-        if (is_array($this->choices)) {
-            return true;
-        }
-
-        $this->choices = make_categories_options();
-        return true;
-    }
-}
index 8fccd35..53c0931 100644 (file)
@@ -1,4 +1,8 @@
 This files describes API changes in the enrol_database code.
 
+=== 3.9 ===
+* Class enrol_database_admin_setting_category has been removed. This class was only used by the database
+  enrolment plugin settings and it was replaced by admin_settings_coursecat_select.
+
 === 3.7 ===
 * enrol/database/cli/sync.php script has been deprecated in favour of enrol_database\task\sync_enrolments task.
index 319ef65..dc70dfd 100644 (file)
@@ -26,6 +26,7 @@ defined('MOODLE_INTERNAL') || die();
 
 $string['addcomment'] = 'Add frequently used comment';
 $string['additionalcomments'] = 'Additional comments';
+$string['additionalcommentsforcriterion'] = 'Additional comments for criterion, {$a}';
 $string['addcriterion'] = 'Add criterion';
 $string['alwaysshowdefinition'] = 'Show guide definition to students';
 $string['backtoediting'] = 'Back to editing';
@@ -33,6 +34,7 @@ $string['clicktocopy'] = 'Click to copy this text into the criteria feedback';
 $string['clicktoedit'] = 'Click to edit';
 $string['clicktoeditname'] = 'Click to edit criterion name';
 $string['comment'] = 'Comment';
+$string['commentpickerforcriterion'] = 'Frequently used comments picker for criterion, {$a}';
 $string['comments'] = 'Frequently used comments';
 $string['commentsdelete'] = 'Delete comment';
 $string['commentsempty'] = 'Click to edit comment';
@@ -70,6 +72,7 @@ $string['guideoptions'] = 'Marking guide options';
 $string['guidestatus'] = 'Current marking guide status';
 $string['hidemarkerdesc'] = 'Hide marker criterion descriptions';
 $string['hidestudentdesc'] = 'Hide student criterion descriptions';
+$string['informationforcriterion'] = 'Information for criterion, {$a}';
 $string['insertcomment'] = 'Insert frequently used comment';
 $string['maxscore'] = 'Maximum score';
 $string['name'] = 'Name';
@@ -88,11 +91,14 @@ $string['regrademessage1'] = 'You are about to save changes to a marking guide t
 $string['regrademessage5'] = 'You are about to save significant changes to a marking guide that has already been used for grading. The gradebook value will be unchanged, but the marking guide will be hidden from students until their item is regraded.';
 $string['regradeoption0'] = 'Do not mark for regrade';
 $string['regradeoption1'] = 'Mark for regrade';
+$string['remark_help'] = 'Enter any additional comments that you would like to provide related to the grade for this criterion. You may also pick from the list of frequently used comments using the frequently used comments picker button.';
 $string['restoredfromdraft'] = 'NOTE: The last attempt to grade this person was not saved properly so draft grades have been restored. If you want to cancel these changes use the \'Cancel\' button below.';
 $string['save'] = 'Save';
 $string['saveguide'] = 'Save marking guide and make it ready';
 $string['saveguidedraft'] = 'Save as draft';
 $string['score'] = 'score';
+$string['scoreforcriterion'] = 'Score for criterion, {$a}';
+$string['score_help'] = 'Enter a score for the criterion, {$a->criterion}. Ensure that this is a number between 0 and {$a->maxscore}.';
 $string['showmarkerdesc'] = 'Show marker criterion descriptions';
 $string['showmarkspercriterionstudents'] = 'Show marks per criterion to students';
 $string['showstudentdesc'] = 'Show student criterion descriptions';
index 8b5fc97..965c805 100644 (file)
@@ -73,6 +73,7 @@
             type="button"
           >
             {{# pix }} info, gradingform_guide {{/ pix }}
+            <span class="sr-only">{{#str}}informationforcriterion, gradingform_guide, {{name}}{{/str}}</span>
         </button>
         <button class="criterion-toggle btn btn-icon icon-no-margin text-reset p-0 font-weight-bold mb-0 ml-auto"
                 type="button"
         <div class="form-group">
           <label for="gradingform_guide-{{uniqid}}-criteria-{{id}}-score">{{#str}}outof, gradingform_guide, {{maxscore}}{{/str}}</label>
           <input class="form-control" type="number" name="advancedgrading[criteria][{{id}}][score]" value="{{score}}"
-              id="gradingform_guide-{{uniqid}}-criteria-{{id}}-score"
-              aria-describedby="gradingform_guide-{{uniqid}}-help-{{id}}-score">
-          <small id="gradingform_guide-{{uniqid}}-help-{{id}}-score" class="sr-only">{{#str}}grade_help, gradingform_guide{{/str}}</small>
+            id="gradingform_guide-{{uniqid}}-criteria-{{id}}-score"
+            aria-describedby="gradingform_guide-{{uniqid}}-help-{{id}}-score"
+            min="0" max="{{maxscore}}"
+            aria-label="{{#str}}scoreforcriterion, gradingform_guide, {{name}}{{/str}}">
+          <span id="gradingform_guide-{{uniqid}}-help-{{id}}-score" aria-hidden="true" class="sr-only">{{!
+            }}{{#str}}score_help, gradingform_guide, { "criterion":  {{# quote }}{{ name }}{{/ quote }}, "maxscore": {{# quote }}{{ maxscore }}{{/ quote }} }{{/str}}
+          </span>
         </div>
         <div class="form-group ">
-          <label class="text-muted" for="gradingform_guide-{{uniqid}}-criteria-{{id}}-remark">{{#str}}additionalcomments, gradingform_guide{{/str}}</label>
+          <label for="gradingform_guide-{{uniqid}}-criteria-{{id}}-remark">{{#str}}additionalcomments, gradingform_guide{{/str}}</label>
           <div class="input-group mb-3 form-inset form-inset-right">
             <textarea class="form-control" type="text" name="advancedgrading[criteria][{{id}}][remark]"
                 id="gradingform_guide-{{uniqid}}-criteria-{{id}}-remark"
                 aria-describedby="gradingform_guide-{{uniqid}}-help-{{id}}-remark"
+                aria-label="{{#str}}additionalcommentsforcriterion, gradingform_guide, {{name}}{{/str}}"
                 data-gradingform-guide-role="remark"
                 rows="2"
                 data-max-rows="5"
                 type="button"
               >
                   {{#pix}}plus, gradingform_guide{{/pix}}
+                  <span class="sr-only">{{#str}}commentpickerforcriterion, gradingform_guide, {{name}}{{/str}}</span>
               </button>
             {{/hascomments}}
           </div>
               </div>
             </div>
           {{/hascomments}}
-          <small id="gradingform_guide-{{uniqid}}-help-{{id}}-remark" class="sr-only">{{#str}}grade_help, gradingform_guide{{/str}}</small>
+          <span id="gradingform_guide-{{uniqid}}-help-{{id}}-remark" aria-hidden="true" class="sr-only">{{#str}}remark_help, gradingform_guide{{/str}}</span>
         </div>
       </div>
     </div>
index 1d3b885..06958d6 100644 (file)
@@ -28,6 +28,11 @@ $string['contentname'] = 'Content name';
 $string['contentnotdeleted'] = 'An error was encountered while trying to delete the content.';
 $string['contentnotrenamed'] = 'An error was encountered while trying to rename the content.';
 $string['contentrenamed'] = 'The content has been renamed.';
+$string['eventcontentcreated'] = 'Content created';
+$string['eventcontentdeleted'] = 'Content deleted';
+$string['eventcontentupdated'] = 'Content updated';
+$string['eventcontentuploaded'] = 'Content uploaded';
+$string['eventcontentviewed'] = 'Content viewed';
 $string['deletecontent'] = 'Delete content';
 $string['deletecontentconfirm'] = 'Are you sure you want to delete the content <em>\'{$a->name}\'</em> and all associated files? This action cannot be undone.';
 $string['file'] = 'Upload content';
diff --git a/lib/classes/event/contentbank_content_created.php b/lib/classes/event/contentbank_content_created.php
new file mode 100644 (file)
index 0000000..bf651c7
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Contentbank content created event.
+ *
+ * @package    core
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+/**
+ * Content bank content created class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *      - string contenttype: the contenttype of the content.
+ *      - string name: the name of the content.
+ * }
+ *
+ * @package    core
+ * @since      Moodle 3.9
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class contentbank_content_created extends base {
+
+    /**
+     * Initialise the event data.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'contentbank_content';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Creates an event from content bank content object
+     *
+     * @since Moodle 3.9
+     * @param \stdClass $record Data to create the event
+     * @return contentbank_content_created
+     */
+    public static function create_from_record(\stdClass $record) {
+        $event = self::create([
+            'objectid' => $record->id,
+            'relateduserid' => $record->usercreated,
+            'context' => \context::instance_by_id($record->contextid),
+            'other' => [
+                'contenttype' => $record->contenttype,
+                'name' => $record->name
+            ]
+        ]);
+        return $event;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcontentcreated', 'core_contentbank');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' created the content with id '$this->objectid'.";
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['contenttype'])) {
+            throw new \coding_exception('The \'contenttype\' value must be set in other.');
+        }
+
+        if (!isset($this->other['name'])) {
+            throw new \coding_exception('The \'name\' value must be set in other.');
+        }
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        $url = new \moodle_url('/contentbank/view.php');
+        $url->param('id', $this->objectid);
+        return $url;
+    }
+
+    /**
+     * Used for mapping events on restore
+     *
+     * @return array
+     */
+    public static function get_objectid_mapping() {
+        return array('db' => 'contentbank_content', 'restore' => 'contentbank_content');
+    }
+
+    /**
+     * Used for mapping events on restore
+     *
+     * @return bool
+     */
+    public static function get_other_mapping() {
+        // No mapping required.
+        return false;
+    }
+}
diff --git a/lib/classes/event/contentbank_content_deleted.php b/lib/classes/event/contentbank_content_deleted.php
new file mode 100644 (file)
index 0000000..6021d5a
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Contentbank content deleted event.
+ *
+ * @package    core
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+/**
+ * Content bank content deleted class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *      - string contenttype: the contenttype of the content.
+ *      - string name: the name of the content.
+ * }
+ *
+ * @package    core
+ * @since      Moodle 3.9
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class contentbank_content_deleted extends base {
+
+    /**
+     * Initialise the event data.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'contentbank_content';
+        $this->data['crud'] = 'd';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Creates an event from content bank content object
+     *
+     * @since Moodle 3.9
+     * @param \stdClass $record Data to create the event
+     * @return contentbank_content_deleted
+     */
+    public static function create_from_record(\stdClass $record) {
+        $event = self::create([
+            'objectid' => $record->id,
+            'relateduserid' => $record->usercreated,
+            'context' => \context::instance_by_id($record->contextid),
+            'other' => [
+                'contenttype' => $record->contenttype,
+                'name' => $record->name
+            ]
+        ]);
+        return $event;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcontentdeleted', 'core_contentbank');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' deleted the content with id '$this->objectid'.";
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['contenttype'])) {
+            throw new \coding_exception('The \'contenttype\' value must be set in other.');
+        }
+
+        if (!isset($this->other['name'])) {
+            throw new \coding_exception('The \'name\' value must be set in other.');
+        }
+    }
+
+    /**
+     * Used for mapping events on restore
+     *
+     * @return array
+     */
+    public static function get_objectid_mapping() {
+        return array('db' => 'contentbank_content', 'restore' => 'contentbank_content');
+    }
+
+    /**
+     * Used for mapping events on restore
+     *
+     * @return bool
+     */
+    public static function get_other_mapping() {
+        // No mapping required.
+        return false;
+    }
+}
diff --git a/lib/classes/event/contentbank_content_updated.php b/lib/classes/event/contentbank_content_updated.php
new file mode 100644 (file)
index 0000000..74a3420
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Contentbank content uploaded event.
+ *
+ * @package    core
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+/**
+ * Content bank content updated class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *      - string contenttype: the contenttype of the content.
+ *      - string name: the name of the content.
+ * }
+ *
+ * @package    core
+ * @since      Moodle 3.9
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class contentbank_content_updated extends base {
+
+    /**
+     * Initialise the event data.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'contentbank_content';
+        $this->data['crud'] = 'u';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Creates an event from content bank content object
+     *
+     * @since Moodle 3.9
+     * @param \stdClass $record Data to create the event
+     * @return contentbank_content_updated
+     */
+    public static function create_from_record(\stdClass $record) {
+        $event = self::create([
+            'objectid' => $record->id,
+            'relateduserid' => $record->usercreated,
+            'context' => \context::instance_by_id($record->contextid),
+            'other' => [
+                'contenttype' => $record->contenttype,
+                'name' => $record->name
+            ]
+        ]);
+        return $event;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcontentupdated', 'core_contentbank');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' updated the content with id '$this->objectid'.";
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['contenttype'])) {
+            throw new \coding_exception('The \'contenttype\' value must be set in other.');
+        }
+
+        if (!isset($this->other['name'])) {
+            throw new \coding_exception('The \'name\' value must be set in other.');
+        }
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        $url = new \moodle_url('/contentbank/view.php');
+        $url->param('id', $this->objectid);
+        return $url;
+    }
+
+    /**
+     * Used for mapping events on restore
+     *
+     * @return array
+     */
+    public static function get_objectid_mapping() {
+        return array('db' => 'contentbank_content', 'restore' => 'contentbank_content');
+    }
+
+    /**
+     * Used for mapping events on restore
+     *
+     * @return bool
+     */
+    public static function get_other_mapping() {
+        // No mapping required.
+        return false;
+    }
+}
diff --git a/lib/classes/event/contentbank_content_uploaded.php b/lib/classes/event/contentbank_content_uploaded.php
new file mode 100644 (file)
index 0000000..1080407
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Contentbank content uploaded event.
+ *
+ * @package    core
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+/**
+ * Content bank content uploaded class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *      - string contenttype: the contenttype of the content.
+ *      - string name: the name of the content.
+ * }
+ *
+ * @package    core
+ * @since      Moodle 3.9
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class contentbank_content_uploaded extends base {
+
+    /**
+     * Initialise the event data.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'contentbank_content';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Creates an event from content bank content object
+     *
+     * @since Moodle 3.9
+     * @param \stdClass $record Data to create the event
+     * @return contentbank_content_uploaded
+     */
+    public static function create_from_record(\stdClass $record) {
+        $event = self::create([
+            'objectid' => $record->id,
+            'relateduserid' => $record->usercreated,
+            'context' => \context::instance_by_id($record->contextid),
+            'other' => [
+                'contenttype' => $record->contenttype,
+                'name' => $record->name
+            ]
+        ]);
+        return $event;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcontentuploaded', 'core_contentbank');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' uploaded the content with id '$this->objectid'.";
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['contenttype'])) {
+            throw new \coding_exception('The \'contenttype\' value must be set in other.');
+        }
+
+        if (!isset($this->other['name'])) {
+            throw new \coding_exception('The \'name\' value must be set in other.');
+        }
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        $url = new \moodle_url('/contentbank/view.php');
+        $url->param('id', $this->objectid);
+        return $url;
+    }
+
+    /**
+     * Used for mapping events on restore
+     *
+     * @return array
+     */
+    public static function get_objectid_mapping() {
+        return array('db' => 'contentbank_content', 'restore' => 'contentbank_content');
+    }
+
+    /**
+     * Used for mapping events on restore
+     *
+     * @return bool
+     */
+    public static function get_other_mapping() {
+        // No mapping required.
+        return false;
+    }
+}
diff --git a/lib/classes/event/contentbank_content_viewed.php b/lib/classes/event/contentbank_content_viewed.php
new file mode 100644 (file)
index 0000000..cd8b4d2
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Contentbank content viewed event.
+ *
+ * @package    core
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+/**
+ * Content bank content updated class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *      - string contenttype: the contenttype of the content.
+ *      - string name: the name of the content.
+ * }
+ *
+ * @package    core
+ * @since      Moodle 3.9
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class contentbank_content_viewed extends base {
+
+    /**
+     * Initialise the event data.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'contentbank_content';
+        $this->data['crud'] = 'r';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Creates an event from content bank content object
+     *
+     * @since Moodle 3.9
+     * @param \stdClass $record Data to create the event
+     * @return contentbank_content_viewed
+     */
+    public static function create_from_record(\stdClass $record) {
+        $event = self::create([
+            'objectid' => $record->id,
+            'relateduserid' => $record->usercreated,
+            'context' => \context::instance_by_id($record->contextid),
+            'other' => [
+                'contenttype' => $record->contenttype,
+                'name' => $record->name
+            ]
+        ]);
+        return $event;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcontentviewed', 'core_contentbank');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' viewed the content with id '$this->objectid'.";
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['contenttype'])) {
+            throw new \coding_exception('The \'contenttype\' value must be set in other.');
+        }
+
+        if (!isset($this->other['name'])) {
+            throw new \coding_exception('The \'name\' value must be set in other.');
+        }
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        $url = new \moodle_url('/contentbank/view.php');
+        $url->param('id', $this->objectid);
+        return $url;
+    }
+
+    /**
+     * Used for mapping events on restore
+     *
+     * @return array
+     */
+    public static function get_objectid_mapping() {
+        return array('db' => 'contentbank_content', 'restore' => 'contentbank_content');
+    }
+
+    /**
+     * Used for mapping events on restore
+     *
+     * @return bool
+     */
+    public static function get_other_mapping() {
+        // No mapping required.
+        return false;
+    }
+}
index e2b5c78..be495fe 100644 (file)
@@ -22,7 +22,7 @@ Feature: Atto accessibility checker
     And I select the text in the "Description" Atto editor
     And I click on "Insert or edit image" "button"
     And I set the field "Describe this image for someone who cannot see it" to ""
-    And I set the field "Description not necessary" to "1"
+    And I set the field "This image is decorative only" to "1"
     And I press "Save image"
     And I press "Accessibility checker"
     And I should see "Congratulations, no accessibility problems found!"
index e9bceb9..624247e 100644 (file)
@@ -36,7 +36,7 @@ $string['enteralt'] = 'Describe this image for someone who cannot see it';
 $string['enterurl'] = 'Enter URL';
 $string['height'] = 'Height';
 $string['imageproperties'] = 'Image properties';
-$string['presentation'] = 'Description not necessary';
+$string['presentation'] = 'This image is decorative only';
 $string['pluginname'] = 'Insert or edit image';
 $string['presentationoraltrequired'] = 'Images must have a description, except if the description is marked as not necessary.';
 $string['preview'] = 'Preview';
index 941acdf..c43debe 100644 (file)
@@ -39,8 +39,9 @@ Feature: Add images to Atto
     And I take focus off "Height" "field"
     And the field "Width" matches value "123"
     And the field "Height" matches value "456"
-    And I click on "Save image" "button"
-    And I click on "Update profile" "button"
+    And I change window size to "large"
+    And I press "Save image"
+    And I press "Update profile"
     And I click on "Edit profile" "link" in the "region-main" "region"
     And I select the text in the "Description" Atto editor
     And I click on "Insert or edit image" "button"
index bed9c39..75f44a5 100644 (file)
Binary files a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js and b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js differ
index fea59fe..d6b125e 100644 (file)
Binary files a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js and b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js differ
index 5c65d96..4bb3907 100644 (file)
Binary files a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js and b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js differ
index 7aa557a..774d8b1 100644 (file)
@@ -122,8 +122,14 @@ var CSS = {
                 '</div>' +
                 '<div class="mb-1">' +
                 '<label for="{{elementid}}_{{CSS.INPUTALT}}">{{get_string "enteralt" component}}</label>' +
-                '<input class="form-control fullwidth {{CSS.INPUTALT}}" type="text" value="" ' +
-                'id="{{elementid}}_{{CSS.INPUTALT}}" size="32"/>' +
+                '<textarea class="form-control fullwidth {{CSS.INPUTALT}}" ' +
+                'id="{{elementid}}_{{CSS.INPUTALT}}" maxlength="125"></textarea>' +
+
+                // Add the character count.
+                '<div id="the-count" class="d-flex justify-content-end small">' +
+                '<span id="currentcount">0</span>' +
+                '<span id="maximumcount"> / 125</span>' +
+                '</div>' +
 
                 // Add the presentation select box.
                 '<div class="form-check">' +
@@ -603,6 +609,9 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
             }, this);
         }
 
+        // Character count.
+        this._form.one('.' + CSS.INPUTALT).on('keyup', this._handleKeyup, this);
+
         return content;
     },
 
@@ -1043,5 +1052,16 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
         }
         this.getDialogue().centerDialogue();
         return state;
+    },
+
+    /**
+     * Handle the keyup to update the character count.
+     */
+    _handleKeyup: function() {
+        var form = this._form,
+            alt = form.one('.' + CSS.INPUTALT).get('value'),
+            characterCount = alt.length,
+            current = form.one('#currentcount');
+        current.setHTML(characterCount);
     }
 });
index 6449ef5..b62b377 100644 (file)
@@ -15,7 +15,7 @@ Feature: Atto italic button
     And I set the field "Text editor" to "Plain text area"
     And I press "Save changes"
     And I click on "Edit profile" "link" in the "region-main" "region"
-    Then I should see "<i>Tower of Pisa</i>"
+    Then I should see "<em>Tower of Pisa</em>"
 
   @javascript
   Scenario: Toggle italics in some text
@@ -31,5 +31,5 @@ Feature: Atto italic button
     And I set the field "Text editor" to "Plain text area"
     And I press "Save changes"
     And I click on "Edit profile" "link" in the "region-main" "region"
-    Then I should not see "<i>GHD - for hair</i>"
+    Then I should not see "<em>GHD - for hair</em>"
     And I should see "GHD - for hair"
index dcba9e9..d316663 100644 (file)
Binary files a/lib/editor/atto/plugins/italic/yui/build/moodle-atto_italic-button/moodle-atto_italic-button-debug.js and b/lib/editor/atto/plugins/italic/yui/build/moodle-atto_italic-button/moodle-atto_italic-button-debug.js differ
index 6a64c70..52208d1 100644 (file)
Binary files a/lib/editor/atto/plugins/italic/yui/build/moodle-atto_italic-button/moodle-atto_italic-button-min.js and b/lib/editor/atto/plugins/italic/yui/build/moodle-atto_italic-button/moodle-atto_italic-button-min.js differ
index dcba9e9..d316663 100644 (file)
Binary files a/lib/editor/atto/plugins/italic/yui/build/moodle-atto_italic-button/moodle-atto_italic-button.js and b/lib/editor/atto/plugins/italic/yui/build/moodle-atto_italic-button/moodle-atto_italic-button.js differ
index 8823c30..fadc5e5 100644 (file)
 
 Y.namespace('M.atto_italic').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
     initializer: function() {
-        this.addBasicButton({
-            exec: 'italic',
+        var italic;
+
+        this.addButton({
+            callback: this._toggleItalic,
+            icon: 'e/italic',
+            buttonName: italic,
+            inlineFormat: true,
 
             // Key code for the keyboard shortcut which triggers this button:
             keys: '73',
 
             // Watch the following tags and add/remove highlighting as appropriate:
-            tags: 'i'
+            tags: 'em, i'
         });
+    },
+    /**
+     * Toggle the italic setting.
+     *
+     * @method _toggleItalic
+     * @param {EventFacade} e
+     */
+    _toggleItalic: function() {
+        var host = this.get('host');
+
+        // Use the "italic" command for simplicity. This will toggle <em> tags off as well.
+        document.execCommand('italic', false, null);
+
+        // Then change all <i> tags to <em> tags. This will change any existing <i> tags as well.
+        host.changeToCSS('i', 'bf-editor-italic-emphasis');
+        host.changeToTags('bf-editor-italic-emphasis', 'em');
     }
 });
index 02f5665..ec282c2 100644 (file)
@@ -21,6 +21,6 @@ Feature: Atto editor with customised toolbar
     Then ".atto_link_button" "css_element" should exist in the ".normaldiv" "css_element"
     And ".atto_link_button" "css_element" should not exist in the ".specialdiv" "css_element"
     And ".atto_bold_button" "css_element" should exist in the ".normaldiv" "css_element"
-    And ".atto_italic_button_italic" "css_element" should exist in the ".normaldiv" "css_element"
+    And ".atto_italic_button" "css_element" should exist in the ".normaldiv" "css_element"
     And ".atto_bold_button" "css_element" should exist in the ".specialdiv" "css_element"
-    And ".atto_italic_button_italic" "css_element" should exist in the ".specialdiv" "css_element"
+    And ".atto_italic_button" "css_element" should exist in the ".specialdiv" "css_element"
index a3a6590..4e1d16a 100644 (file)
@@ -22,7 +22,7 @@ Feature: Atto with enable/disable function.
     Then the "disabled" attribute of "button.atto_collapse_button" "css_element" should contain "disabled"
     And the "disabled" attribute of "button.atto_title_button" "css_element" should contain "disabled"
     And the "disabled" attribute of "button.atto_bold_button" "css_element" should contain "disabled"
-    And the "disabled" attribute of "button.atto_italic_button_italic" "css_element" should contain "disabled"
+    And the "disabled" attribute of "button.atto_italic_button" "css_element" should contain "disabled"
     And the "disabled" attribute of "button.atto_unorderedlist_button_insertUnorderedList" "css_element" should contain "disabled"
     And the "disabled" attribute of "button.atto_orderedlist_button_insertOrderedList" "css_element" should contain "disabled"
     And the "disabled" attribute of "button.atto_link_button" "css_element" should contain "disabled"
@@ -36,7 +36,7 @@ Feature: Atto with enable/disable function.
     Then "button.atto_collapse_button[disabled]" "css_element" should not exist
     And "button.atto_title_button[disabled]" "css_element" should not exist
     And "button.atto_bold_button[disabled]" "css_element" should not exist
-    And "button.atto_italic_button_italic[disabled]" "css_element" should not exist
+    And "button.atto_italic_button[disabled]" "css_element" should not exist
     And "button.atto_unorderedlist_button_insertUnorderedList[disabled]" "css_element" should not exist
     And "button.atto_orderedlist_button_insertOrderedList[disabled]" "css_element" should not exist
     And "button.atto_link_button[disabled]" "css_element" should not exist
index 8b95027..8f2c834 100644 (file)
@@ -2407,13 +2407,16 @@ function core_question_find_next_unused_idnumber(?string $oldidnumber, int $cate
             [$categoryid], '', 'idnumber, 1');
 
     // Find the next unused idnumber.
-    $newidnumber = $oldidnumber;
+    $numberbit = 'X' . $matches[0]; // Need a string here so PHP does not do '0001' + 1 = 2.
+    $stem = substr($oldidnumber, 0, -strlen($matches[0]));
     do {
+
         // If we have got to something9999, insert an extra digit before incrementing.
-        if (preg_match('~^(.*[^0-9])(9+)$~', $newidnumber, $matches)) {
-            $newidnumber = $matches[1] . '0' . $matches[2];
+        if (preg_match('~^(.*[^0-9])(9+)$~', $numberbit, $matches)) {
+            $numberbit = $matches[1] . '0' . $matches[2];
         }
-        $newidnumber++;
+        $numberbit++;
+        $newidnumber = $stem . substr($numberbit, 1);
     } while (isset($usedidnumbers[$newidnumber]));
 
     return (string) $newidnumber;
index 69210d8..127cd83 100644 (file)
Binary files a/lib/table/amd/build/dynamic.min.js and b/lib/table/amd/build/dynamic.min.js differ
index 9f3aae9..fd3cbd2 100644 (file)
Binary files a/lib/table/amd/build/dynamic.min.js.map and b/lib/table/amd/build/dynamic.min.js.map differ
index a377b54..ec438f1 100644 (file)
Binary files a/lib/table/amd/build/local/dynamic/repository.min.js and b/lib/table/amd/build/local/dynamic/repository.min.js differ
index 1f58475..af07af3 100644 (file)
Binary files a/lib/table/amd/build/local/dynamic/repository.min.js.map and b/lib/table/amd/build/local/dynamic/repository.min.js.map differ
index aa1d030..18e35fb 100644 (file)
@@ -61,9 +61,10 @@ const getFiltersetFromTable = tableRoot => {
  * Update the specified table based on its current values.
  *
  * @param {HTMLElement} tableRoot
+ * @param {Bool} resetContent
  * @returns {Promise}
  */
-export const refreshTableContent = tableRoot => {
+export const refreshTableContent = (tableRoot, resetContent = false) => {
     const filterset = getFiltersetFromTable(tableRoot);
 
     return fetchTableData(
@@ -80,7 +81,8 @@ export const refreshTableContent = tableRoot => {
             pageNumber: tableRoot.dataset.tablePageNumber,
             pageSize: tableRoot.dataset.tablePageSize,
             hiddenColumns: JSON.parse(tableRoot.dataset.tableHiddenColumns),
-        }
+        },
+        resetContent,
     )
     .then(data => {
         const placeholder = document.createElement('div');
@@ -246,6 +248,14 @@ export const showColumn = (tableRoot, columnToShow, refreshContent = true) => {
     updateTable(tableRoot, {hiddenColumns}, refreshContent);
 };
 
+/**
+ * Reset table preferences.
+ *
+ * @param {HTMLElement} tableRoot
+ * @returns {Promise}
+ */
+const resetTablePreferences = tableRoot => refreshTableContent(tableRoot, true);
+
 /**
  * Set up listeners to handle table updates.
  */
@@ -305,6 +315,12 @@ export const init = () => {
             showColumn(tableRoot, show.dataset.column);
         }
 
+        const resetTablePreferencesLink = e.target.closest('.resettable a');
+        if (resetTablePreferencesLink) {
+            e.preventDefault();
+
+            resetTablePreferences(tableRoot);
+        }
     });
 };
 
index 344de98..a8e6141 100644 (file)
@@ -37,6 +37,7 @@ import {call as fetchMany} from 'core/ajax';
  * @param {String} pageNumber The page number
  * @param {Number} pageSize The page size
  * @param {Number} params parameters to request table
+ * @param {Bool} resetPreferences
  * @return {Promise} Resolved with requested table view
  */
 export const fetch = (component, handler, uniqueid, {
@@ -49,8 +50,7 @@ export const fetch = (component, handler, uniqueid, {
         pageNumber = null,
         pageSize = null,
         hiddenColumns = {}
-    } = {}
-) => {
+    } = {}, resetPreferences = false) => {
     return fetchMany([{
         methodname: `core_table_dynamic_fetch`,
         args: {
@@ -66,6 +66,7 @@ export const fetch = (component, handler, uniqueid, {
             pagenumber: pageNumber,
             pagesize: pageSize,
             hiddencolumns: hiddenColumns,
+            resetpreferences: resetPreferences
         },
     }])[0];
 };
index 890efb5..15b9583 100644 (file)
@@ -123,6 +123,12 @@ class fetch extends external_api {
                     null
                 )
             ),
+            'resetpreferences' => new external_value(
+                PARAM_BOOL,
+                'Whether the table preferences should be reset',
+                VALUE_REQUIRED,
+                null
+            ),
         ]);
     }
 
@@ -140,6 +146,8 @@ class fetch extends external_api {
      * @param string $lastinitial The last name initial to filter on
      * @param int $pagenumber The page number.
      * @param int $pagesize The number of records.
+     * @param string $jointype The join type.
+     * @param bool $resetpreferences Whether it is resetting table preferences or not.
      *
      * @return array
      */
@@ -155,7 +163,8 @@ class fetch extends external_api {
         ?string $lastinitial = null,
         ?int $pagenumber = null,
         ?int $pagesize = null,
-        ?array $hiddencolumns = null
+        ?array $hiddencolumns = null,
+        ?bool $resetpreferences = null
     ) {
 
         global $PAGE;
@@ -173,6 +182,7 @@ class fetch extends external_api {
             'pagenumber' => $pagenumber,
             'pagesize' => $pagesize,
             'hiddencolumns' => $hiddencolumns,
+            'resetpreferences' => $resetpreferences,
         ] = self::validate_parameters(self::execute_parameters(), [
             'component' => $component,
             'handler' => $handler,
@@ -186,6 +196,7 @@ class fetch extends external_api {
             'pagenumber' => $pagenumber,
             'pagesize' => $pagesize,
             'hiddencolumns' => $hiddencolumns,
+            'resetpreferences' => $resetpreferences,
         ]);
 
         $tableclass = "\\{$component}\\table\\{$handler}";
@@ -238,6 +249,12 @@ class fetch extends external_api {
             $instance->set_hidden_columns($hiddencolumns);
         }
 
+        if ($resetpreferences === true) {
+            $instance->mark_table_to_reset();
+        }
+
+        $PAGE->set_url($instance->baseurl);
+
         ob_start();
         $instance->out($pagesize, true);
         $tablehtml = ob_get_contents();
index 55a83c2..8a13164 100644 (file)
@@ -158,6 +158,9 @@ class flexible_table {
     /** @var array $hiddencolumns List of hidden columns. */
     protected $hiddencolumns;
 
+    /** @var $resetting bool Whether the table preferences is resetting. */
+    protected $resetting;
+
     /**
      * @var filterset The currently applied filerset
      * This is required for dynamic tables, but can be used by other tables too if desired.
@@ -485,6 +488,26 @@ class flexible_table {
         $this->helpforheaders = $helpicons;
     }
 
+    /**
+     * Mark the table preferences to be reset.
+     */
+    public function mark_table_to_reset(): void {
+        $this->resetting = true;
+    }
+
+    /**
+     * Is the table marked for reset preferences?
+     *
+     * @return bool True if the table is marked to reset, false otherwise.
+     */
+    protected function is_resetting_preferences(): bool {
+        if ($this->resetting === null) {
+            $this->resetting = optional_param($this->request[TABLE_VAR_RESET], false, PARAM_BOOL);
+        }
+
+        return $this->resetting;
+}
+
     /**
      * Must be called after table is defined. Use methods above first. Cannot
      * use functions below till after calling this method.
@@ -497,43 +520,7 @@ class flexible_table {
             return false;
         }
 
-        // Load any existing user preferences.
-        if ($this->persistent) {
-            $this->prefs = json_decode(get_user_preferences('flextable_' . $this->uniqueid), true);
-            $oldprefs = $this->prefs;
-        } else if (isset($SESSION->flextable[$this->uniqueid])) {
-            $this->prefs = $SESSION->flextable[$this->uniqueid];
-            $oldprefs = $this->prefs;
-        }
-
-        // Set up default preferences if needed.
-        if (!$this->prefs or optional_param($this->request[TABLE_VAR_RESET], false, PARAM_BOOL)) {
-            $this->prefs = array(
-                'collapse' => array(),
-                'sortby'   => array(),
-                'i_first'  => '',
-                'i_last'   => '',
-                'textsort' => $this->column_textsort,
-            );
-        }
-
-        if (!isset($oldprefs)) {
-            $oldprefs = $this->prefs;
-        }
-
-        $this->set_hide_show_preferences();
-        $this->set_sorting_preferences();
-        $this->set_initials_preferences();
-
-        // Save user preferences if they have changed.
-        if ($this->prefs != $oldprefs) {
-            if ($this->persistent) {
-                set_user_preference('flextable_' . $this->uniqueid, json_encode($this->prefs));
-            } else {
-                $SESSION->flextable[$this->uniqueid] = $this->prefs;
-            }
-        }
-        unset($oldprefs);
+        $this->initialise_table_preferences();
 
         if (empty($this->baseurl)) {
             debugging('You should set baseurl when using flexible_table.');
@@ -1413,6 +1400,100 @@ class flexible_table {
         $this->hiddencolumns = $columns;
     }
 
+    /**
+     * Initialise table preferences.
+     */
+    protected function initialise_table_preferences(): void {
+
+        // Load any existing user preferences.
+        if ($this->persistent) {
+            $this->prefs = json_decode(get_user_preferences('flextable_' . $this->uniqueid), true);
+            $oldprefs = $this->prefs;
+        } else if (isset($SESSION->flextable[$this->uniqueid])) {
+            $this->prefs = $SESSION->flextable[$this->uniqueid];
+            $oldprefs = $this->prefs;
+        }
+
+        // Set up default preferences if needed.
+        if (!$this->prefs || $this->is_resetting_preferences()) {
+            $this->prefs = [
+                'collapse' => [],
+                'sortby'   => [],
+                'i_first'  => '',
+                'i_last'   => '',
+                'textsort' => $this->column_textsort,
+            ];
+        }
+
+        if (!isset($oldprefs)) {
+            $oldprefs = $this->prefs;
+        }
+
+        // Save user preferences if they have changed.
+        if ($this->is_resetting_preferences()) {
+            $this->sortorder = null;
+            $this->sortby = null;
+            $this->ifirst = null;
+            $this->ilast = null;
+        }
+
+        if (($showcol = optional_param($this->request[TABLE_VAR_SHOW], '', PARAM_ALPHANUMEXT)) &&
+            isset($this->columns[$showcol])) {
+            $this->prefs['collapse'][$showcol] = false;
+        } else if (($hidecol = optional_param($this->request[TABLE_VAR_HIDE], '', PARAM_ALPHANUMEXT)) &&
+            isset($this->columns[$hidecol])) {
+            $this->prefs['collapse'][$hidecol] = true;
+            if (array_key_exists($hidecol, $this->prefs['sortby'])) {
+                unset($this->prefs['sortby'][$hidecol]);
+            }
+        }
+
+        // Now, update the column attributes for collapsed columns
+        foreach (array_keys($this->columns) as $column) {
+            if (!empty($this->prefs['collapse'][$column])) {
+                $this->column_style[$column]['width'] = '10px';
+            }
+        }
+
+        // Now, update the column attributes for collapsed columns
+        foreach (array_keys($this->columns) as $column) {
+            if (!empty($this->prefs['collapse'][$column])) {
+                $this->column_style[$column]['width'] = '10px';
+            }
+        }
+
+        $this->set_sorting_preferences();
+        $this->set_initials_preferences();
+
+        if (empty($this->baseurl)) {
+            debugging('You should set baseurl when using flexible_table.');
+            global $PAGE;
+            $this->baseurl = $PAGE->url;
+        }
+
+        if ($this->currpage == null) {
+            $this->currpage = optional_param($this->request[TABLE_VAR_PAGE], 0, PARAM_INT);
+        }
+
+        $this->save_preferences($oldprefs);
+    }
+
+    /**
+     * Save preferences.
+     *
+     * @param array $oldprefs Old preferences to compare against.
+     */
+    protected function save_preferences($oldprefs): void {
+        if ($this->prefs != $oldprefs) {
+            if ($this->persistent) {
+                set_user_preference('flextable_' . $this->uniqueid, json_encode($this->prefs));
+            } else {
+                $SESSION->flextable[$this->uniqueid] = $this->prefs;
+            }
+        }
+        unset($oldprefs);
+    }
+
     /**
      * Set the preferred table sorting attributes.
      *
diff --git a/lib/tests/event/contentbank_content_created_test.php b/lib/tests/event/contentbank_content_created_test.php
new file mode 100644 (file)
index 0000000..0e381b9
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Content bank created event tests.
+ *
+ * @package core
+ * @category test
+ * @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+/**
+ * Test for content bank created event.
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @coversDefaultClass \core\event\contentbank_content_created
+ */
+class contentbank_content_created_testcase extends \advanced_testcase {
+
+    /**
+     * Setup to ensure that fixtures are loaded.
+     */
+    public static function setUpBeforeClass() {
+        global $CFG;
+
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
+    }
+
+    /**
+     * Test the content created event.
+     *
+     * @covers ::create_from_record
+     */
+    public function test_content_created() {
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        // Save the system context.
+        $systemcontext = \context_system::instance();
+
+        // Trigger and capture the event when creating a content.
+        $sink = $this->redirectEvents();
+
+        // Create a content bank content.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        $contents = $generator->generate_contentbank_data('contenttype_testable', 1);
+        $content = array_shift($contents);
+
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\contentbank_content_created', $event);
+        $this->assertEquals($systemcontext, $event->get_context());
+    }
+}
diff --git a/lib/tests/event/contentbank_content_deleted_test.php b/lib/tests/event/contentbank_content_deleted_test.php
new file mode 100644 (file)
index 0000000..20c7517
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Content bank deleted event tests.
+ *
+ * @package core
+ * @category test
+ * @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+/**
+ * Test for content bank deleted event.
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @coversDefaultClass \core\event\contentbank_content_deleted
+ */
+class contentbank_content_deleted_testcase extends \advanced_testcase {
+
+    /**
+     * Setup to ensure that fixtures are loaded.
+     */
+    public static function setUpBeforeClass() {
+        global $CFG;
+
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
+    }
+
+    /**
+     * Test the content deleted event
+     *
+     * @covers ::create_from_record
+     */
+    public function test_content_deleted() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        // Save the system context.
+        $systemcontext = \context_system::instance();
+
+        // Create a content bank content.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        $contents = $generator->generate_contentbank_data('contenttype_testable', 3);
+        $content = array_shift($contents);
+        $this->assertEquals(3, $DB->count_records('contentbank_content'));
+
+        $classname = '\\contenttype_testable\\contenttype';
+        $contentype = new $classname($systemcontext);
+
+        // Trigger and capture the event for deleting a content.
+        $sink = $this->redirectEvents();
+        $contentype->delete_content($content);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the content was deleted and the event data is valid.
+        $this->assertEquals(2, $DB->count_records('contentbank_content'));
+        $this->assertInstanceOf('\core\event\contentbank_content_deleted', $event);
+        $this->assertEquals(\context_system::instance(), $event->get_context());
+    }
+}
diff --git a/lib/tests/event/contentbank_content_updated_test.php b/lib/tests/event/contentbank_content_updated_test.php
new file mode 100644 (file)
index 0000000..b6aeae7
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Content bank updated event tests.
+ *
+ * @package core
+ * @category test
+ * @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+/**
+ * Test for content bank updated event.
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @coversDefaultClass \core\event\contentbank_content_updated
+ */
+class contentbank_content_updated_testcase extends \advanced_testcase {
+
+    /**
+     * Setup to ensure that fixtures are loaded.
+     */
+    public static function setUpBeforeClass() {
+        global $CFG;
+
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
+    }
+
+    /**
+     * Test the content updated event.
+     *
+     * @covers ::create_from_record
+     */
+    public function test_content_updated() {
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        // Save the system context.
+        $systemcontext = \context_system::instance();
+
+        // Create a content bank content.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        $contents = $generator->generate_contentbank_data('contenttype_testable', 1);
+        $content = array_shift($contents);
+
+        // Store the name before we change it.
+        $oldname = $content->get_name();
+
+        // Trigger and capture the event when renaming a content.
+        $sink = $this->redirectEvents();
+
+        $newname = "New name";
+        $content->set_name($newname);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\contentbank_content_updated', $event);
+        $this->assertEquals($systemcontext, $event->get_context());
+    }
+}
diff --git a/lib/tests/event/contentbank_content_uploaded_test.php b/lib/tests/event/contentbank_content_uploaded_test.php
new file mode 100644 (file)
index 0000000..93e6d7d
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Content bank uploaded event tests.
+ *
+ * @package core
+ * @category test
+ * @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+use core_contentbank\contentbank;
+
+/**
+ * Test for content bank uploaded event.
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @coversDefaultClass \core\event\contentbank_content_uploaded
+ */
+class contentbank_content_uploaded_testcase extends \advanced_testcase {
+
+    /**
+     * Setup to ensure that fixtures are loaded.
+     */
+    public static function setUpBeforeClass() {
+        global $CFG;
+
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
+    }
+
+    /**
+     * Test the content created event.
+     *
+     * @covers ::create_from_record
+     */
+    public function test_content_created() {
+        global $USER;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $systemcontext = \context_system::instance();
+
+        // Create a dummy H5P file.
+        $dummyh5p = array(
+            'contextid' => $systemcontext->id,
+            'component' => 'contentbank',
+            'filearea' => 'public',
+            'itemid' => 1,
+            'filepath' => '/',
+            'filename' => 'dummy_h5p.h5p'
+        );
+        $fs = get_file_storage();
+        $dummyh5pfile = $fs->create_file_from_string($dummyh5p, 'Dummy H5Pcontent');
+
+        // Trigger and capture the event when creating content from a file.
+        $sink = $this->redirectEvents();
+        $cb = new contentbank();
+        $cb->create_content_from_file($systemcontext, $USER->id, $dummyh5pfile);
+
+        // Both uploaded and created events are raised.
+        $events = $sink->get_events();
+        $this->assertCount(2, $events);
+
+        // First the created content event has been raised.
+        $event = array_shift($events);
+        $this->assertInstanceOf('\core\event\contentbank_content_created', $event);
+        $this->assertEquals($systemcontext, $event->get_context());
+
+        // Second the uploaded content event has been raised.
+        $event = array_pop($events);
+        $this->assertInstanceOf('\core\event\contentbank_content_uploaded', $event);
+        $this->assertEquals($systemcontext, $event->get_context());
+    }
+}
diff --git a/lib/tests/event/contentbank_content_viewed_test.php b/lib/tests/event/contentbank_content_viewed_test.php
new file mode 100644 (file)
index 0000000..acc7e53
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Content bank viewed event tests.
+ *
+ * @package core
+ * @category test
+ * @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+/**
+ * Test for content bank viewed event.
+ *
+ * @package    core
+ * @category   test
+ * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @coversDefaultClass \core\event\contentbank_content_viewed
+ */
+class contentbank_content_viewed_testcase extends \advanced_testcase {
+
+    /**
+     * Setup to ensure that fixtures are loaded.
+     */
+    public static function setUpBeforeClass() {
+        global $CFG;
+
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
+    }
+
+    /**
+     * Test the content updated event.
+     *
+     * @covers ::create_from_record
+     */
+    public function test_content_updated() {
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        // Save the system context.
+        $systemcontext = \context_system::instance();
+
+        // Create a content bank content.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+        $contents = $generator->generate_contentbank_data('contenttype_testable', 1);
+        $content = array_shift($contents);
+
+        // Trigger and capture the content viewed event.
+        $sink = $this->redirectEvents();
+        $eventtotrigger = \core\event\contentbank_content_viewed::create_from_record($content->get_content());
+        $eventtotrigger->trigger();
+
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\contentbank_content_viewed', $event);
+        $this->assertEquals($systemcontext, $event->get_context());
+    }
+}
index 3142b26..2347fb6 100644 (file)
@@ -2155,6 +2155,10 @@ class core_questionlib_testcase extends advanced_testcase {
             ['id9', 'id10'],
             ['id009', 'id010'],
             ['id999', 'id1000'],
+            ['0', '1'],
+            ['-1', '-2'],
+            ['1.0E+29', '1.0E+30'], // Idnumbers are strings, not floats.
+            ['1.0E-29', '1.0E-30'], // By the way, this is not a sensible idnumber!
         ];
     }
 
index 71c16be..9fc42ac 100644 (file)
Binary files a/media/player/videojs/amd/build/Youtube-lazy.min.js and b/media/player/videojs/amd/build/Youtube-lazy.min.js differ
index 4553449..b4d5671 100644 (file)
Binary files a/media/player/videojs/amd/build/Youtube-lazy.min.js.map and b/media/player/videojs/amd/build/Youtube-lazy.min.js.map differ
index 38921dd..aec8187 100644 (file)
Binary files a/media/player/videojs/amd/build/video-lazy.min.js and b/media/player/videojs/amd/build/video-lazy.min.js differ
index 25772f1..4581b63 100644 (file)
Binary files a/media/player/videojs/amd/build/video-lazy.min.js.map and b/media/player/videojs/amd/build/video-lazy.min.js.map differ
index 79f882e..f28a0d1 100644 (file)
Binary files a/media/player/videojs/amd/build/videojs-flash-lazy.min.js and b/media/player/videojs/amd/build/videojs-flash-lazy.min.js differ
index 0802abc..c61c73c 100644 (file)
Binary files a/media/player/videojs/amd/build/videojs-flash-lazy.min.js.map and b/media/player/videojs/amd/build/videojs-flash-lazy.min.js.map differ
index 1c8dba4..de221fe 100644 (file)
@@ -1,13 +1,17 @@
 /* The MIT License (MIT)
+
 Copyright (c) 2014-2015 Benoit Tremblay <trembl.ben@gmail.com>
+
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
+
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
+
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -363,7 +367,7 @@ THE SOFTWARE. */
         options.startSeconds = this.options_.start;
       }
       if (this.options_.end) {
-        options.endEnd = this.options_.end;
+        options.endSeconds = this.options_.end;
       }
       this.ytPlayer.loadVideoById(options);
     },
@@ -376,7 +380,7 @@ THE SOFTWARE. */
         options.startSeconds = this.options_.start;
       }
       if (this.options_.end) {
-        options.endEnd = this.options_.end;
+        options.endSeconds = this.options_.end;
       }
       this.ytPlayer.cueVideoById(options);
     },
@@ -736,6 +740,11 @@ THE SOFTWARE. */
     var loaded = false;
     var tag = document.createElement('script');
     var firstScriptTag = document.getElementsByTagName('script')[0];
+    if (!firstScriptTag) {
+      // when loaded in jest without jsdom setup it doesn't get any element.
+      // In jest it doesn't really make sense to do anything, because no one is watching youtube in jest
+      return;
+    }
     firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
     tag.onload = function () {
       if (!loaded) {
@@ -786,4 +795,4 @@ THE SOFTWARE. */
   } else {
     videojs.registerComponent('Youtube', Youtube);
   }
-}));
\ No newline at end of file
+}));
index b32a05d..9e57c6c 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * @license
- * Video.js 7.6.5 <http://videojs.com/>
+ * Video.js 7.7.6 <http://videojs.com/>
  * Copyright Brightcove, Inc. <https://www.brightcove.com/>
  * Available under Apache License Version 2.0
  * <https://github.com/videojs/video.js/blob/master/LICENSE>
   typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('global/window'), require('global/document')) :
   typeof define === 'function' && define.amd ? define(['./window', './document'], factory) :
   (global = global || self, global.videojs = factory(global.window, global.document));
-}(this, function (window$1, document) {
-  window$1 = window$1 && window$1.hasOwnProperty('default') ? window$1['default'] : window$1;
+}(this, function (window$3, document) { 'use strict';
+
+  window$3 = window$3 && window$3.hasOwnProperty('default') ? window$3['default'] : window$3;
   document = document && document.hasOwnProperty('default') ? document['default'] : document;
 
-  var version = "7.6.5";
+  var version = "7.7.6";
 
   /**
    * @file create-logger.js
       args.unshift(name + ':'); // Add a clone of the args at this point to history.
 
       if (history) {
-        history.push([].concat(args));
+        history.push([].concat(args)); // only store 1000 history entries
+
+        var splice = history.length - 1000;
+        history.splice(0, splice > 0 ? splice : 0);
       } // If there's no console then don't try to output messages, but they will
       // still be stored in history.
 
 
-      if (!window$1.console) {
+      if (!window$3.console) {
         return;
       } // Was setting these once outside of this function, but containing them
       // in the function makes it easier to test cases where console doesn't exist
       // when the module is executed.
 
 
-      var fn = window$1.console[type];
+      var fn = window$3.console[type];
 
       if (!fn && type === 'debug') {
         // Certain browsers don't have support for console.debug. For those, we
         // should default to the closest comparable log.
-        fn = window$1.console.info || window$1.console.log;
+        fn = window$3.console.info || window$3.console.log;
       } // Bail out if there's no console or if this type is not allowed by the
       // current logging level.
 
@@ -77,7 +81,7 @@
         return;
       }
 
-      fn[Array.isArray(args) ? 'apply' : 'call'](window$1.console, args);
+      fn[Array.isArray(args) ? 'apply' : 'call'](window$3.console, args);
     };
   };
 
   var log = createLogger('VIDEOJS');
   var createLogger$1 = log.createLogger;
 
+  function createCommonjsModule(fn, module) {
+       return module = { exports: {} }, fn(module, module.exports), module.exports;
+  }
+
+  var _extends_1 = createCommonjsModule(function (module) {
+    function _extends() {
+      module.exports = _extends = Object.assign || function (target) {
+        for (var i = 1; i < arguments.length; i++) {
+          var source = arguments[i];
+
+          for (var key in source) {
+            if (Object.prototype.hasOwnProperty.call(source, key)) {
+              target[key] = source[key];
+            }
+          }
+        }
+
+        return target;
+      };
+
+      return _extends.apply(this, arguments);
+    }
+
+    module.exports = _extends;
+  });
+
   /**
    * @file obj.js
    * @module obj
     }
 
     if (Object.assign) {
-      return Object.assign.apply(Object, [target].concat(sources));
+      return _extends_1.apply(void 0, [target].concat(sources));
     }
 
     sources.forEach(function (source) {
       return '';
     }
 
-    if (typeof window$1.getComputedStyle === 'function') {
-      var computedStyleValue = window$1.getComputedStyle(el);
+    if (typeof window$3.getComputedStyle === 'function') {
+      var computedStyleValue = window$3.getComputedStyle(el);
       return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : '';
     }
 
    */
 
   function isNonBlankString(str) {
-    return typeof str === 'string' && /\S/.test(str);
+    // we use str.trim as it will trim any whitespace characters
+    // from the front or back of non-whitespace characters. aka
+    // Any string that contains non-whitespace characters will
+    // still contain them after `trim` but whitespace only strings
+    // will have a length of 0, failing this check.
+    return typeof str === 'string' && Boolean(str.trim());
   }
   /**
    * Throws an error if the passed string has whitespace. This is used by
 
 
   function throwIfWhitespace(str) {
-    if (/\s/.test(str)) {
+    // str.indexOf instead of regex because str.indexOf is faster performance wise.
+    if (str.indexOf(' ') >= 0) {
       throw new Error('class has illegal whitespace characters');
     }
   }
 
   function isReal() {
     // Both document and window will never be undefined thanks to `global`.
-    return document === window$1.document;
+    return document === window$3.document;
   }
   /**
    * Determines, via duck typing, whether or not a value is a DOM element.
     // We need a try/catch here because Safari will throw errors when attempting
     // to get either `parent` or `self`
     try {
-      return window$1.parent !== window$1.self;
+      return window$3.parent !== window$3.self;
     } catch (x) {
       return true;
     }
         // method for it.
       } else if (propName === 'textContent') {
         textContent(el, val);
-      } else {
+      } else if (el[propName] !== val) {
         el[propName] = val;
       }
     });
     var docEl = document.documentElement;
     var body = document.body;
     var clientLeft = docEl.clientLeft || body.clientLeft || 0;
-    var scrollLeft = window$1.pageXOffset || body.scrollLeft;
+    var scrollLeft = window$3.pageXOffset || body.scrollLeft;
     var left = box.left + scrollLeft - clientLeft;
     var clientTop = docEl.clientTop || body.clientTop || 0;
-    var scrollTop = window$1.pageYOffset || body.scrollTop;
+    var scrollTop = window$3.pageYOffset || body.scrollTop;
     var top = box.top + scrollTop - clientTop; // Android sometimes returns slightly off decimal values, so need to round
 
     return {
       videojs = vjs;
     }
 
-    window$1.setTimeout(autoSetup, wait);
+    window$3.setTimeout(autoSetup, wait);
   }
   /**
    * Used to set the internal tracking of window loaded state to true.
 
   function setWindowLoaded() {
     _windowLoaded = true;
-    window$1.removeEventListener('load', setWindowLoaded);
+    window$3.removeEventListener('load', setWindowLoaded);
   }
 
   if (isReal()) {
        *
        * @listens load
        */
-      window$1.addEventListener('load', setWindowLoaded);
+      window$3.addEventListener('load', setWindowLoaded);
     }
   }
 
     }
   };
 
-  /**
-   * @file dom-data.js
-   * @module dom-data
-   */
-
-  /**
-   * Element Data Store.
-   *
-   * Allows for binding data to an element without putting it directly on the
-   * element. Ex. Event listeners are stored here.
-   * (also from jsninja.com, slightly modified and updated for closure compiler)
-   *
-   * @type {Object}
-   * @private
-   */
-  var DomData = new WeakMap();
-
   /**
    * @file guid.js
    * @module guid
     return _guid++;
   }
 
+  /**
+   * @file dom-data.js
+   * @module dom-data
+   */
+  var FakeWeakMap;
+
+  if (!window$3.WeakMap) {
+    FakeWeakMap = /*#__PURE__*/function () {
+      function FakeWeakMap() {
+        this.vdata = 'vdata' + Math.floor(window$3.performance && window$3.performance.now() || Date.now());
+        this.data = {};
+      }
+
+      var _proto = FakeWeakMap.prototype;
+
+      _proto.set = function set(key, value) {
+        var access = key[this.vdata] || newGUID();
+
+        if (!key[this.vdata]) {
+          key[this.vdata] = access;
+        }
+
+        this.data[access] = value;
+        return this;
+      };
+
+      _proto.get = function get(key) {
+        var access = key[this.vdata]; // we have data, return it
+
+        if (access) {
+          return this.data[access];
+        } // we don't have data, return nothing.
+        // return undefined explicitly as that's the contract for this method
+
+
+        log('We have no data for this element', key);
+        return undefined;
+      };
+
+      _proto.has = function has(key) {
+        var access = key[this.vdata];
+        return access in this.data;
+      };
+
+      _proto["delete"] = function _delete(key) {
+        var access = key[this.vdata];
+
+        if (access) {
+          delete this.data[access];
+          delete key[this.vdata];
+        }
+      };
+
+      return FakeWeakMap;
+    }();
+  }
+  /**
+   * Element Data Store.
+   *
+   * Allows for binding data to an element without putting it directly on the
+   * element. Ex. Event listeners are stored here.
+   * (also from jsninja.com, slightly modified and updated for closure compiler)
+   *
+   * @type {Object}
+   * @private
+   */
+
+
+  var DomData = window$3.WeakMap ? new WeakMap() : new FakeWeakMap();
+
   /**
    * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
    * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
 
 
   function fixEvent(event) {
+    if (event.fixed_) {
+      return event;
+    }
+
     function returnTrue() {
       return true;
     }
 
 
     if (!event || !event.isPropagationStopped) {
-      var old = event || window$1.event;
+      var old = event || window$3.event;
       event = {}; // Clone the old object so that we can modify the values event = {};
       // IE8 Doesn't like when you mess with native event properties
       // Firefox returns false for event.hasOwnProperty('type') and other props
         event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
         /* eslint-enable */
       }
-    } // Returns fixed-up instance
+    }
 
+    event.fixed_ = true; // Returns fixed-up instance
 
     return event;
   }
    * Whether passive event listeners are supported
    */
 
-  var _supportsPassive = false;
+  var _supportsPassive;
 
-  (function () {
-    try {
-      var opts = Object.defineProperty({}, 'passive', {
-        get: function get() {
-          _supportsPassive = true;
-        }
-      });
-      window$1.addEventListener('test', null, opts);
-      window$1.removeEventListener('test', null, opts);
-    } catch (e) {// disregard
+  var supportsPassive = function supportsPassive() {
+    if (typeof _supportsPassive !== 'boolean') {
+      _supportsPassive = false;
+
+      try {
+        var opts = Object.defineProperty({}, 'passive', {
+          get: function get() {
+            _supportsPassive = true;
+          }
+        });
+        window$3.addEventListener('test', null, opts);
+        window$3.removeEventListener('test', null, opts);
+      } catch (e) {// disregard
+      }
     }
-  })();
+
+    return _supportsPassive;
+  };
   /**
    * Touch events Chrome expects to be passive
    */
       if (elem.addEventListener) {
         var options = false;
 
-        if (_supportsPassive && passiveEvents.indexOf(type) > -1) {
+        if (supportsPassive() && passiveEvents.indexOf(type) > -1) {
           options = {
             passive: true
           };
    */
 
   var throttle = function throttle(fn, wait) {
-    var last = window$1.performance.now();
+    var last = window$3.performance.now();
 
     var throttled = function throttled() {
-      var now = window$1.performance.now();
+      var now = window$3.performance.now();
 
       if (now - last >= wait) {
         fn.apply(void 0, arguments);
 
   var debounce = function debounce(func, wait, immediate, context) {
     if (context === void 0) {
-      context = window$1;
+      context = window$3;
     }
 
     var timeout;
 
     var oldTimeout = map.get(type);
     map["delete"](type);
-    window$1.clearTimeout(oldTimeout);
-    var timeout = window$1.setTimeout(function () {
+    window$3.clearTimeout(oldTimeout);
+    var timeout = window$3.setTimeout(function () {
       // if we cleared out all timeouts for the current target, delete its map
       if (map.size === 0) {
         map = null;
 
     target.on('dispose', function () {
       target.off();
-      window$1.setTimeout(function () {
+      window$3.setTimeout(function () {
         target.eventBusEl_ = null;
       }, 0);
     });
    * Components can also use methods from {@link EventTarget}
    */
 
-  var Component =
-  /*#__PURE__*/
-  function () {
+  var Component = /*#__PURE__*/function () {
     /**
      * A callback that is called when a component is ready. Does not have any
      * paramters and any callback value will be ignored.
         this.player_ = player = this; // eslint-disable-line
       } else {
         this.player_ = player;
-      } // Hold the reference to the parent component via `addChild` method
+      }
 
+      this.isDisposed_ = false; // Hold the reference to the parent component via `addChild` method
 
       this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults
 
       this.children_ = [];
       this.childIndex_ = {};
       this.childNameIndex_ = {};
-      this.setTimeoutIds_ = new Set();
-      this.setIntervalIds_ = new Set();
-      this.rafIds_ = new Set();
+      var SetSham;
+
+      if (!window$3.Set) {
+        SetSham = /*#__PURE__*/function () {
+          function SetSham() {
+            this.set_ = {};
+          }
+
+          var _proto2 = SetSham.prototype;
+
+          _proto2.has = function has(key) {
+            return key in this.set_;
+          };
+
+          _proto2["delete"] = function _delete(key) {
+            var has = this.has(key);
+            delete this.set_[key];
+            return has;
+          };
+
+          _proto2.add = function add(key) {
+            this.set_[key] = 1;
+            return this;
+          };
+
+          _proto2.forEach = function forEach(callback, thisArg) {
+            for (var key in this.set_) {
+              callback.call(thisArg, key, key, this);
+            }
+          };
+
+          return SetSham;
+        }();
+      }
+
+      this.setTimeoutIds_ = window$3.Set ? new Set() : new SetSham();
+      this.setIntervalIds_ = window$3.Set ? new Set() : new SetSham();
+      this.rafIds_ = window$3.Set ? new Set() : new SetSham();
       this.clearingTimersOnDispose_ = false; // Add any child components in options
 
       if (options.initChildren !== false) {
     var _proto = Component.prototype;
 
     _proto.dispose = function dispose() {
+      // Bail out if the component has already been disposed.
+      if (this.isDisposed_) {
+        return;
+      }
       /**
        * Triggered when a `Component` is disposed.
        *
        * @type {EventTarget~Event}
        *
        * @property {boolean} [bubbles=false]
-       *           set to false so that the close event does not
+       *           set to false so that the dispose event does not
        *           bubble up
        */
+
+
       this.trigger({
         type: 'dispose',
         bubbles: false
-      }); // Dispose all children.
+      });
+      this.isDisposed_ = true; // Dispose all children.
 
       if (this.children_) {
         for (var i = this.children_.length - 1; i >= 0; i--) {
 
       this.player_ = null;
     }
+    /**
+     * Determine whether or not this component has been disposed.
+     *
+     * @return {boolean}
+     *         If the component has been disposed, will be `true`. Otherwise, `false`.
+     */
+    ;
+
+    _proto.isDisposed = function isDisposed() {
+      return Boolean(this.isDisposed_);
+    }
     /**
      * Return the {@link Player} that the `Component` has attached to.
      *
 
 
       if (typeof component.el === 'function' && component.el()) {
-        var childNodes = this.contentEl().children;
-        var refNode = childNodes[index] || null;
+        // If inserting before a component, insert before that component's element
+        var refNode = null;
+
+        if (this.children_[index + 1] && this.children_[index + 1].el_) {
+          refNode = this.children_[index + 1].el_;
+        }
+
         this.contentEl().insertBefore(component.el(), refNode);
       } // Return so it can stored on parent object if desired.
 
             pageY: event.touches[0].pageY
           }; // Record start time so we can detect a tap vs. "touch and hold"
 
-          touchStart = window$1.performance.now(); // Reset couldBeTap tracking
+          touchStart = window$3.performance.now(); // Reset couldBeTap tracking
 
           couldBeTap = true;
         }
 
         if (couldBeTap === true) {
           // Measure how long the touch lasted
-          var touchTime = window$1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap
+          var touchTime = window$3.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap
 
           if (touchTime < touchTimeThreshold) {
             // Don't let browser turn this into a click
       var timeoutId;
       fn = bind(this, fn);
       this.clearTimersOnDispose_();
-      timeoutId = window$1.setTimeout(function () {
+      timeoutId = window$3.setTimeout(function () {
         if (_this2.setTimeoutIds_.has(timeoutId)) {
           _this2.setTimeoutIds_["delete"](timeoutId);
         }
     _proto.clearTimeout = function clearTimeout(timeoutId) {
       if (this.setTimeoutIds_.has(timeoutId)) {
         this.setTimeoutIds_["delete"](timeoutId);
-        window$1.clearTimeout(timeoutId);
+        window$3.clearTimeout(timeoutId);
       }
 
       return timeoutId;
     _proto.setInterval = function setInterval(fn, interval) {
       fn = bind(this, fn);
       this.clearTimersOnDispose_();
-      var intervalId = window$1.setInterval(fn, interval);
+      var intervalId = window$3.setInterval(fn, interval);
       this.setIntervalIds_.add(intervalId);
       return intervalId;
     }
     _proto.clearInterval = function clearInterval(intervalId) {
       if (this.setIntervalIds_.has(intervalId)) {
         this.setIntervalIds_["delete"](intervalId);
-        window$1.clearInterval(intervalId);
+        window$3.clearInterval(intervalId);
       }
 
       return intervalId;
 
       var id;
       fn = bind(this, fn);
-      id = window$1.requestAnimationFrame(function () {
+      id = window$3.requestAnimationFrame(function () {
         if (_this3.rafIds_.has(id)) {
           _this3.rafIds_["delete"](id);
         }
 
       if (this.rafIds_.has(id)) {
         this.rafIds_["delete"](id);
-        window$1.cancelAnimationFrame(id);
+        window$3.cancelAnimationFrame(id);
       }
 
       return id;
    */
 
 
-  Component.prototype.supportsRaf_ = typeof window$1.requestAnimationFrame === 'function' && typeof window$1.cancelAnimationFrame === 'function';
+  Component.prototype.supportsRaf_ = typeof window$3.requestAnimationFrame === 'function' && typeof window$3.cancelAnimationFrame === 'function';
   Component.registerComponent('Component', Component);
 
-  function _inheritsLoose(subClass, superClass) {
-    subClass.prototype = Object.create(superClass.prototype);
-    subClass.prototype.constructor = subClass;
-    subClass.__proto__ = superClass;
+  function _assertThisInitialized(self) {
+    if (self === void 0) {
+      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+    }
+
+    return self;
   }
 
-  function _setPrototypeOf(o, p) {
-    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
-      o.__proto__ = p;
-      return o;
-    };
+  var assertThisInitialized = _assertThisInitialized;
 
-    return _setPrototypeOf(o, p);
-  }
+  var _typeof_1 = createCommonjsModule(function (module) {
+    function _typeof(obj) {
+      "@babel/helpers - typeof";
 
-  function isNativeReflectConstruct() {
-    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
-    if (Reflect.construct.sham) return false;
-    if (typeof Proxy === "function") return true;
+      if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
+        module.exports = _typeof = function _typeof(obj) {
+          return typeof obj;
+        };
+      } else {
+        module.exports = _typeof = function _typeof(obj) {
+          return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
+        };
+      }
 
-    try {
-      Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
-      return true;
-    } catch (e) {
-      return false;
+      return _typeof(obj);
     }
-  }
 
-  function _construct(Parent, args, Class) {
-    if (isNativeReflectConstruct()) {
-      _construct = Reflect.construct;
-    } else {
-      _construct = function _construct(Parent, args, Class) {
-        var a = [null];
-        a.push.apply(a, args);
-        var Constructor = Function.bind.apply(Parent, a);
-        var instance = new Constructor();
-        if (Class) _setPrototypeOf(instance, Class.prototype);
-        return instance;
+    module.exports = _typeof;
+  });
+
+  var getPrototypeOf = createCommonjsModule(function (module) {
+    function _getPrototypeOf(o) {
+      module.exports = _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
+        return o.__proto__ || Object.getPrototypeOf(o);
       };
+      return _getPrototypeOf(o);
     }
 
-    return _construct.apply(null, arguments);
-  }
-
-  function _assertThisInitialized(self) {
-    if (self === void 0) {
-      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
-    }
+    module.exports = _getPrototypeOf;
+  });
 
-    return self;
+  function _inheritsLoose(subClass, superClass) {
+    subClass.prototype = Object.create(superClass.prototype);
+    subClass.prototype.constructor = subClass;
+    subClass.__proto__ = superClass;
   }
 
+  var inheritsLoose = _inheritsLoose;
+
   /**
    * @file browser.js
    * @module browser
    */
-  var USER_AGENT = window$1.navigator && window$1.navigator.userAgent || '';
+  var USER_AGENT = window$3.navigator && window$3.navigator.userAgent || '';
   var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
   var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
-  /**
-   * Whether or not this device is an iPad.
-   *
-   * @static
-   * @const
-   * @type {Boolean}
-   */
-
-  var IS_IPAD = /iPad/i.test(USER_AGENT);
-  /**
-   * Whether or not this device is an iPhone.
-   *
-   * @static
-   * @const
-   * @type {Boolean}
-   */
-  // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
-  // to identify iPhones, we need to exclude iPads.
-  // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
-
-  var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
   /**
    * Whether or not this device is an iPod.
    *
    */
 
   var IS_IPOD = /iPod/i.test(USER_AGENT);
-  /**
-   * Whether or not this is an iOS device.
-   *
-   * @static
-   * @const
-   * @type {Boolean}
-   */
-
-  var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
   /**
    * The detected iOS version - or `null`.
    *
    * @type {Boolean}
    */
 
-  var IS_EDGE = /Edge/i.test(USER_AGENT);
+  var IS_EDGE = /Edg/i.test(USER_AGENT);
   /**
    * Whether or not this is Google Chrome.
    *
 
   var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
   /**
-   * Whether or not this is any flavor of Safari - including iOS.
+   * Whether or not this is a Windows machine.
    *
    * @static
    * @const
    * @type {Boolean}
    */
 
-  var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
+  var IS_WINDOWS = /Windows/i.test(USER_AGENT);
   /**
-   * Whether or not this is a Windows machine.
+   * Whether or not this device is touch-enabled.
    *
    * @static
    * @const
    * @type {Boolean}
    */
 
-  var IS_WINDOWS = /Windows/i.test(USER_AGENT);
+  var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$3 || window$3.navigator.maxTouchPoints || window$3.DocumentTouch && window$3.document instanceof window$3.DocumentTouch);
   /**
-   * Whether or not this device is touch-enabled.
+   * Whether or not this device is an iPad.
+   *
+   * @static
+   * @const
+   * @type {Boolean}
+   */
+
+  var IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT);
+  /**
+   * Whether or not this device is an iPhone.
+   *
+   * @static
+   * @const
+   * @type {Boolean}
+   */
+  // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
+  // to identify iPhones, we need to exclude iPads.
+  // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
+
+  var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
+  /**
+   * Whether or not this is an iOS device.
+   *
+   * @static
+   * @const
+   * @type {Boolean}
+   */
+
+  var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
+  /**
+   * Whether or not this is any flavor of Safari - including iOS.
    *
    * @static
    * @const
    * @type {Boolean}
    */
 
-  var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch);
+  var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
 
   var browser = /*#__PURE__*/Object.freeze({
-    IS_IPAD: IS_IPAD,
-    IS_IPHONE: IS_IPHONE,
     IS_IPOD: IS_IPOD,
-    IS_IOS: IS_IOS,
     IOS_VERSION: IOS_VERSION,
     IS_ANDROID: IS_ANDROID,
     ANDROID_VERSION: ANDROID_VERSION,
     CHROME_VERSION: CHROME_VERSION,
     IE_VERSION: IE_VERSION,
     IS_SAFARI: IS_SAFARI,
-    IS_ANY_SAFARI: IS_ANY_SAFARI,
     IS_WINDOWS: IS_WINDOWS,
-    TOUCH_ENABLED: TOUCH_ENABLED
+    TOUCH_ENABLED: TOUCH_ENABLED,
+    IS_IPAD: IS_IPAD,
+    IS_IPHONE: IS_IPHONE,
+    IS_IOS: IS_IOS,
+    IS_ANY_SAFARI: IS_ANY_SAFARI
   });
 
   /**
     trackToJson_: trackToJson_
   };
 
-  function createCommonjsModule(fn, module) {
-       return module = { exports: {} }, fn(module, module.exports), module.exports;
-  }
-
   var keycode = createCommonjsModule(function (module, exports) {
     // Source: http://jsfiddle.net/vWx8V/
     // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
       '[': 219,
       '\\': 220,
       ']': 221,
-      "'": 222 // Helper aliases
+      "'": 222
+    }; // Helper aliases
 
-    };
     var aliases = exports.aliases = {
       'windows': 91,
       '⇧': 16,
       'ins': 45,
       'del': 46,
       'cmd': 91
-      /*!
-       * Programatically add the following
-       */
-      // lower case chars
-
     };
+    /*!
+     * Programatically add the following
+     */
+    // lower case chars
 
     for (i = 97; i < 123; i++) {
       codes[String.fromCharCode(i)] = i - 32;
    * @extends Component
    */
 
-  var ModalDialog =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(ModalDialog, _Component);
+  var ModalDialog = /*#__PURE__*/function (_Component) {
+    inheritsLoose(ModalDialog, _Component);
 
     /**
      * Create an instance of this class.
     _proto.focusableEls_ = function focusableEls_() {
       var allChildren = this.el_.querySelectorAll('*');
       return Array.prototype.filter.call(allChildren, function (child) {
-        return (child instanceof window$1.HTMLAnchorElement || child instanceof window$1.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window$1.HTMLInputElement || child instanceof window$1.HTMLSelectElement || child instanceof window$1.HTMLTextAreaElement || child instanceof window$1.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window$1.HTMLIFrameElement || child instanceof window$1.HTMLObjectElement || child instanceof window$1.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable');
+        return (child instanceof window$3.HTMLAnchorElement || child instanceof window$3.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window$3.HTMLInputElement || child instanceof window$3.HTMLSelectElement || child instanceof window$3.HTMLTextAreaElement || child instanceof window$3.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window$3.HTMLIFrameElement || child instanceof window$3.HTMLObjectElement || child instanceof window$3.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable');
       });
     };
 
    * @extends EventTarget
    */
 
-  var TrackList =
-  /*#__PURE__*/
-  function (_EventTarget) {
-    _inheritsLoose(TrackList, _EventTarget);
+  var TrackList = /*#__PURE__*/function (_EventTarget) {
+    inheritsLoose(TrackList, _EventTarget);
 
     /**
      * Create an instance of this class
        * @instance
        */
 
-      Object.defineProperty(_assertThisInitialized(_this), 'length', {
+      Object.defineProperty(assertThisInitialized(_this), 'length', {
         get: function get() {
           return this.tracks_.length;
         }
    */
 
 
-  var AudioTrackList =
-  /*#__PURE__*/
-  function (_TrackList) {
-    _inheritsLoose(AudioTrackList, _TrackList);
+  var AudioTrackList = /*#__PURE__*/function (_TrackList) {
+    inheritsLoose(AudioTrackList, _TrackList);
 
     /**
      * Create an instance of this class.
    */
 
 
-  var VideoTrackList =
-  /*#__PURE__*/
-  function (_TrackList) {
-    _inheritsLoose(VideoTrackList, _TrackList);
+  var VideoTrackList = /*#__PURE__*/function (_TrackList) {
+    inheritsLoose(VideoTrackList, _TrackList);
 
     /**
      * Create an instance of this class.
        *         The current index of the selected {@link VideoTrack`}.
        */
 
-      Object.defineProperty(_assertThisInitialized(_this), 'selectedIndex', {
+      Object.defineProperty(assertThisInitialized(_this), 'selectedIndex', {
         get: function get() {
           for (var _i = 0; _i < this.length; _i++) {
             if (this[_i].selected) {
    * @extends TrackList
    */
 
-  var TextTrackList =
-  /*#__PURE__*/
-  function (_TrackList) {
-    _inheritsLoose(TextTrackList, _TrackList);
+  var TextTrackList = /*#__PURE__*/function (_TrackList) {
+    inheritsLoose(TextTrackList, _TrackList);
 
     function TextTrackList() {
       return _TrackList.apply(this, arguments) || this;
   /**
    * The current list of {@link HtmlTrackElement}s.
    */
-  var HtmlTrackElementList =
-  /*#__PURE__*/
-  function () {
+  var HtmlTrackElementList = /*#__PURE__*/function () {
     /**
      * Create an instance of this class.
      *
    *
    * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
    */
-  var TextTrackCueList =
-  /*#__PURE__*/
-  function () {
+  var TextTrackCueList = /*#__PURE__*/function () {
     /**
      * Create an instance of this class..
      *
    * @abstract
    */
 
-  var Track =
-  /*#__PURE__*/
-  function (_EventTarget) {
-    _inheritsLoose(Track, _EventTarget);
+  var Track = /*#__PURE__*/function (_EventTarget) {
+    inheritsLoose(Track, _EventTarget);
 
     /**
      * Create an instance of this class.
        */
 
       var _loop = function _loop(key) {
-        Object.defineProperty(_assertThisInitialized(_this), key, {
+        Object.defineProperty(assertThisInitialized(_this), key, {
           get: function get() {
             return trackProps[key];
           },
     }
 
     if (!details.protocol) {
-      details.protocol = window$1.location.protocol;
+      details.protocol = window$3.location.protocol;
     }
 
     if (addToBody) {
    * @param    {string} url
    *           The url to check.
    *
+   * @param    {Object} [winLoc]
+   *           the domain to check the url against, defaults to window.location
+   *
+   * @param    {string} [winLoc.protocol]
+   *           The window location protocol defaults to window.location.protocol
+   *
+   * @param    {string} [winLoc.host]
+   *           The window location host defaults to window.location.host
+   *
    * @return   {boolean}
    *           Whether it is a cross domain request or not.
    */
 
-  var isCrossOrigin = function isCrossOrigin(url) {
-    var winLoc = window$1.location;
+  var isCrossOrigin = function isCrossOrigin(url, winLoc) {
+    if (winLoc === void 0) {
+      winLoc = window$3.location;
+    }
+
     var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol
 
     var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin
     fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
   }
 
-  /* eslint no-invalid-this: 1 */
+  /**
+   * @license
+   * slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/>
+   * Copyright (c) 2014 David Björklund
+   * Available under the MIT license
+   * <https://github.com/kesla/parse-headers/blob/master/LICENCE>
+   */
 
-  var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible ';
-  var slice = Array.prototype.slice;
-  var toStr = Object.prototype.toString;
-  var funcType = '[object Function]';
 
-  var implementation = function bind(that) {
-    var target = this;
+  var parseHeaders = function parseHeaders(headers) {
+    var result = {};
 
-    if (typeof target !== 'function' || toStr.call(target) !== funcType) {
-      throw new TypeError(ERROR_MESSAGE + target);
+    if (!headers) {
+      return result;
     }
 
-    var args = slice.call(arguments, 1);
-    var bound;
-
-    var binder = function binder() {
-      if (this instanceof bound) {
-        var result = target.apply(this, args.concat(slice.call(arguments)));
-
-        if (Object(result) === result) {
-          return result;
-        }
+    headers.trim().split('\n').forEach(function (row) {
+      var index = row.indexOf(':');
+      var key = row.slice(0, index).trim().toLowerCase();
+      var value = row.slice(index + 1).trim();
 
-        return this;
+      if (typeof result[key] === 'undefined') {
+        result[key] = value;
+      } else if (Array.isArray(result[key])) {
+        result[key].push(value);
       } else {
-        return target.apply(that, args.concat(slice.call(arguments)));
+        result[key] = [result[key], value];
       }
-    };
-
-    var boundLength = Math.max(0, target.length - args.length);
-    var boundArgs = [];
-
-    for (var i = 0; i < boundLength; i++) {
-      boundArgs.push('$' + i);
-    }
+    });
+    return result;
+  };
 
-    bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder);
+  var xhr = createXHR; // Allow use of default import syntax in TypeScript
 
-    if (target.prototype) {
-      var Empty = function Empty() {};
+  var default_1 = createXHR;
+  createXHR.XMLHttpRequest = window$3.XMLHttpRequest || noop;
+  createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$3.XDomainRequest;
+  forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) {
+    createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
+      options = initParams(uri, options, callback);
+      options.method = method.toUpperCase();
+      return _createXHR(options);
+    };
+  });
 
-      Empty.prototype = target.prototype;
-      bound.prototype = new Empty();
-      Empty.prototype = null;
+  function forEachArray(array, iterator) {
+    for (var i = 0; i < array.length; i++) {
+      iterator(array[i]);
     }
+  }
 
-    return bound;
-  };
+  function isEmpty(obj) {
+    for (var i in obj) {
+      if (obj.hasOwnProperty(i)) return false;
+    }
 
-  var functionBind = Function.prototype.bind || implementation;
+    return true;
+  }
 
-  var toStr$1 = Object.prototype.toString;
+  function initParams(uri, options, callback) {
+    var params = uri;
 
-  var isArguments = function isArguments(value) {
-    var str = toStr$1.call(value);
-    var isArgs = str === '[object Arguments]';
+    if (isFunction_1(options)) {
+      callback = options;
 
-    if (!isArgs) {
-      isArgs = str !== '[object Array]' && value !== null && typeof value === 'object' && typeof value.length === 'number' && value.length >= 0 && toStr$1.call(value.callee) === '[object Function]';
+      if (typeof uri === "string") {
+        params = {
+          uri: uri
+        };
+      }
+    } else {
+      params = _extends_1({}, options, {
+        uri: uri
+      });
     }
 
-    return isArgs;
-  };
+    params.callback = callback;
+    return params;
+  }
 
-  var keysShim;
+  function createXHR(uri, options, callback) {
+    options = initParams(uri, options, callback);
+    return _createXHR(options);
+  }
 
-  if (!Object.keys) {
-    // modified from https://github.com/es-shims/es5-shim
-    var has = Object.prototype.hasOwnProperty;
-    var toStr$2 = Object.prototype.toString;
-    var isArgs = isArguments; // eslint-disable-line global-require
+  function _createXHR(options) {
+    if (typeof options.callback === "undefined") {
+      throw new Error("callback argument missing");
+    }
 
-    var isEnumerable = Object.prototype.propertyIsEnumerable;
-    var hasDontEnumBug = !isEnumerable.call({
-      toString: null
-    }, 'toString');
-    var hasProtoEnumBug = isEnumerable.call(function () {}, 'prototype');
-    var dontEnums = ['toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor'];
+    var called = false;
 
-    var equalsConstructorPrototype = function equalsConstructorPrototype(o) {
-      var ctor = o.constructor;
-      return ctor && ctor.prototype === o;
+    var callback = function cbOnce(err, response, body) {
+      if (!called) {
+        called = true;
+        options.callback(err, response, body);
+      }
     };
 
-    var excludedKeys = {
-      $applicationCache: true,
-      $console: true,
-      $external: true,
-      $frame: true,
-      $frameElement: true,
-      $frames: true,
-      $innerHeight: true,
-      $innerWidth: true,
-      $onmozfullscreenchange: true,
-      $onmozfullscreenerror: true,
-      $outerHeight: true,
-      $outerWidth: true,
-      $pageXOffset: true,
-      $pageYOffset: true,
-      $parent: true,
-      $scrollLeft: true,
-      $scrollTop: true,
-      $scrollX: true,
-      $scrollY: true,
-      $self: true,
-      $webkitIndexedDB: true,
-      $webkitStorageInfo: true,
-      $window: true
-    };
+    function readystatechange() {
+      if (xhr.readyState === 4) {
+        setTimeout(loadFunc, 0);
+      }
+    }
 
-    var hasAutomationEqualityBug = function () {
-      /* global window */
-      if (typeof window === 'undefined') {
-        return false;
+    function getBody() {
+      // Chrome with requestType=blob throws errors arround when even testing access to responseText
+      var body = undefined;
+
+      if (xhr.response) {
+        body = xhr.response;
+      } else {
+        body = xhr.responseText || getXml(xhr);
       }
 
-      for (var k in window) {
+      if (isJson) {
         try {
-          if (!excludedKeys['$' + k] && has.call(window, k) && window[k] !== null && typeof window[k] === 'object') {
-            try {
-              equalsConstructorPrototype(window[k]);
-            } catch (e) {
-              return true;
-            }
-          }
-        } catch (e) {
-          return true;
-        }
+          body = JSON.parse(body);
+        } catch (e) {}
       }
 
-      return false;
-    }();
+      return body;
+    }
 
-    var equalsConstructorPrototypeIfNotBuggy = function equalsConstructorPrototypeIfNotBuggy(o) {
-      /* global window */
-      if (typeof window === 'undefined' || !hasAutomationEqualityBug) {
-        return equalsConstructorPrototype(o);
-      }
+    function errorFunc(evt) {
+      clearTimeout(timeoutTimer);
 
-      try {
-        return equalsConstructorPrototype(o);
-      } catch (e) {
-        return false;
+      if (!(evt instanceof Error)) {
+        evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
       }
-    };
 
-    keysShim = function keys(object) {
-      var isObject = object !== null && typeof object === 'object';
-      var isFunction = toStr$2.call(object) === '[object Function]';
-      var isArguments = isArgs(object);
-      var isString = isObject && toStr$2.call(object) === '[object String]';
-      var theKeys = [];
+      evt.statusCode = 0;
+      return callback(evt, failureResponse);
+    } // will load the data & process the response in a special response object
 
-      if (!isObject && !isFunction && !isArguments) {
-        throw new TypeError('Object.keys called on a non-object');
-      }
 
-      var skipProto = hasProtoEnumBug && isFunction;
+    function loadFunc() {
+      if (aborted) return;
+      var status;
+      clearTimeout(timeoutTimer);
 
-      if (isString && object.length > 0 && !has.call(object, 0)) {
-        for (var i = 0; i < object.length; ++i) {
-          theKeys.push(String(i));
-        }
+      if (options.useXDR && xhr.status === undefined) {
+        //IE8 CORS GET successful response doesn't have a status field, but body is fine
+        status = 200;
+      } else {
+        status = xhr.status === 1223 ? 204 : xhr.status;
       }
 
-      if (isArguments && object.length > 0) {
-        for (var j = 0; j < object.length; ++j) {
-          theKeys.push(String(j));
+      var response = failureResponse;
+      var err = null;
+
+      if (status !== 0) {
+        response = {
+          body: getBody(),
+          statusCode: status,
+          method: method,
+          headers: {},
+          url: uri,
+          rawRequest: xhr
+        };
+
+        if (xhr.getAllResponseHeaders) {
+          //remember xhr can in fact be XDR for CORS in IE
+          response.headers = parseHeaders(xhr.getAllResponseHeaders());
         }
       } else {
-        for (var name in object) {
-          if (!(skipProto && name === 'prototype') && has.call(object, name)) {
-            theKeys.push(String(name));
-          }
-        }
+        err = new Error("Internal XMLHttpRequest Error");
       }
 
-      if (hasDontEnumBug) {
-        var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object);
+      return callback(err, response, response.body);
+    }
+
+    var xhr = options.xhr || null;
 
-        for (var k = 0; k < dontEnums.length; ++k) {
-          if (!(skipConstructor && dontEnums[k] === 'constructor') && has.call(object, dontEnums[k])) {
-            theKeys.push(dontEnums[k]);
-          }
-        }
+    if (!xhr) {
+      if (options.cors || options.useXDR) {
+        xhr = new createXHR.XDomainRequest();
+      } else {
+        xhr = new createXHR.XMLHttpRequest();
       }
+    }
 
-      return theKeys;
+    var key;
+    var aborted;
+    var uri = xhr.url = options.uri || options.url;
+    var method = xhr.method = options.method || "GET";
+    var body = options.body || options.data;
+    var headers = xhr.headers = options.headers || {};
+    var sync = !!options.sync;
+    var isJson = false;
+    var timeoutTimer;
+    var failureResponse = {
+      body: undefined,
+      headers: {},
+      statusCode: 0,
+      method: method,
+      url: uri,
+      rawRequest: xhr
     };
-  }
 
-  var implementation$1 = keysShim;
-
-  var slice$1 = Array.prototype.slice;
-  var origKeys = Object.keys;
-  var keysShim$1 = origKeys ? function keys(o) {
-    return origKeys(o);
-  } : implementation$1;
-  var originalKeys = Object.keys;
-
-  keysShim$1.shim = function shimObjectKeys() {
-    if (Object.keys) {
-      var keysWorksWithArguments = function () {
-        // Safari 5.0 bug
-        var args = Object.keys(arguments);
-        return args && args.length === arguments.length;
-      }(1, 2);
+    if ("json" in options && options.json !== false) {
+      isJson = true;
+      headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
 
-      if (!keysWorksWithArguments) {
-        Object.keys = function keys(object) {
-          // eslint-disable-line func-name-matching
-          if (isArguments(object)) {
-            return originalKeys(slice$1.call(object));
-          }
+      if (method !== "GET" && method !== "HEAD") {
+        headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
 
-          return originalKeys(object);
-        };
+        body = JSON.stringify(options.json === true ? body : options.json);
       }
-    } else {
-      Object.keys = keysShim$1;
     }
 
-    return Object.keys || keysShim$1;
-  };
-
-  var objectKeys = keysShim$1;
-
-  var hasSymbols = typeof Symbol === 'function' && typeof Symbol('foo') === 'symbol';
-  var toStr$3 = Object.prototype.toString;
-  var concat = Array.prototype.concat;
-  var origDefineProperty = Object.defineProperty;
-
-  var isFunction$1 = function isFunction(fn) {
-    return typeof fn === 'function' && toStr$3.call(fn) === '[object Function]';
-  };
+    xhr.onreadystatechange = readystatechange;
+    xhr.onload = loadFunc;
+    xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
 
-  var arePropertyDescriptorsSupported = function arePropertyDescriptorsSupported() {
-    var obj = {};
+    xhr.onprogress = function () {// IE must die
+    };
 
-    try {
-      origDefineProperty(obj, 'x', {
-        enumerable: false,
-        value: obj
-      }); // eslint-disable-next-line no-unused-vars, no-restricted-syntax
+    xhr.onabort = function () {
+      aborted = true;
+    };
 
-      for (var _ in obj) {
-        // jscs:ignore disallowUnusedVariables
-        return false;
-      }
+    xhr.ontimeout = errorFunc;
+    xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
 
-      return obj.x === obj;
-    } catch (e) {
-      /* this is IE 8. */
-      return false;
-    }
-  };
+    if (!sync) {
+      xhr.withCredentials = !!options.withCredentials;
+    } // Cannot set timeout with sync request
+    // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
+    // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
 
-  var supportsDescriptors = origDefineProperty && arePropertyDescriptorsSupported();
 
-  var defineProperty = function defineProperty(object, name, value, predicate) {
-    if (name in object && (!isFunction$1(predicate) || !predicate())) {
-      return;
-    }
+    if (!sync && options.timeout > 0) {
+      timeoutTimer = setTimeout(function () {
+        if (aborted) return;
+        aborted = true; //IE9 may still call readystatechange
 
-    if (supportsDescriptors) {
-      origDefineProperty(object, name, {
-        configurable: true,
-        enumerable: false,
-        value: value,
-        writable: true
-      });
-    } else {
-      object[name] = value;
+        xhr.abort("timeout");
+        var e = new Error("XMLHttpRequest timeout");
+        e.code = "ETIMEDOUT";
+        errorFunc(e);
+      }, options.timeout);
     }
-  };
 
-  var defineProperties = function defineProperties(object, map) {
-    var predicates = arguments.length > 2 ? arguments[2] : {};
-    var props = objectKeys(map);
-
-    if (hasSymbols) {
-      props = concat.call(props, Object.getOwnPropertySymbols(map));
+    if (xhr.setRequestHeader) {
+      for (key in headers) {
+        if (headers.hasOwnProperty(key)) {
+          xhr.setRequestHeader(key, headers[key]);
+        }
+      }
+    } else if (options.headers && !isEmpty(options.headers)) {
+      throw new Error("Headers cannot be set on an XDomainRequest object");
     }
 
-    for (var i = 0; i < props.length; i += 1) {
-      defineProperty(object, props[i], map[props[i]], predicates[props[i]]);
+    if ("responseType" in options) {
+      xhr.responseType = options.responseType;
     }
-  };
 
-  defineProperties.supportsDescriptors = !!supportsDescriptors;
-  var defineProperties_1 = defineProperties;
-
-  /* globals
-       Set,
-       Map,
-       WeakSet,
-       WeakMap,
-
-       Promise,
-
-       Symbol,
-       Proxy,
+    if ("beforeSend" in options && typeof options.beforeSend === "function") {
+      options.beforeSend(xhr);
+    } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
+    // XMLHttpRequest spec says to pass null as body to indicate no body
+    // See https://github.com/naugtur/xhr/issues/100.
 
-       Atomics,
-       SharedArrayBuffer,
 
-       ArrayBuffer,
-       DataView,
-       Uint8Array,
-       Float32Array,
-       Float64Array,
-       Int8Array,
-       Int16Array,
-       Int32Array,
-       Uint8ClampedArray,
-       Uint16Array,
-       Uint32Array,
-  */
+    xhr.send(body || null);
+    return xhr;
+  }
 
-  var undefined$1; // eslint-disable-line no-shadow-restricted-names
-
-  var ThrowTypeError = Object.getOwnPropertyDescriptor ? function () {
-    return Object.getOwnPropertyDescriptor(arguments, 'callee').get;
-  }() : function () {
-    throw new TypeError();
-  };
-  var hasSymbols$1 = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol';
-
-  var getProto = Object.getPrototypeOf || function (x) {
-    return x.__proto__;
-  }; // eslint-disable-line no-proto
-
-  var generatorFunction =  undefined$1;
-
-  var asyncFunction =  undefined$1;
-
-  var asyncGenFunction =  undefined$1;
-  var TypedArray = typeof Uint8Array === 'undefined' ? undefined$1 : getProto(Uint8Array);
-  var INTRINSICS = {
-    '$ %Array%': Array,
-    '$ %ArrayBuffer%': typeof ArrayBuffer === 'undefined' ? undefined$1 : ArrayBuffer,
-    '$ %ArrayBufferPrototype%': typeof ArrayBuffer === 'undefined' ? undefined$1 : ArrayBuffer.prototype,
-    '$ %ArrayIteratorPrototype%': hasSymbols$1 ? getProto([][Symbol.iterator]()) : undefined$1,
-    '$ %ArrayPrototype%': Array.prototype,
-    '$ %ArrayProto_entries%': Array.prototype.entries,
-    '$ %ArrayProto_forEach%': Array.prototype.forEach,
-    '$ %ArrayProto_keys%': Array.prototype.keys,
-    '$ %ArrayProto_values%': Array.prototype.values,
-    '$ %AsyncFromSyncIteratorPrototype%': undefined$1,
-    '$ %AsyncFunction%': asyncFunction,
-    '$ %AsyncFunctionPrototype%':  undefined$1,
-    '$ %AsyncGenerator%':  undefined$1,
-    '$ %AsyncGeneratorFunction%': asyncGenFunction,
-    '$ %AsyncGeneratorPrototype%':  undefined$1,
-    '$ %AsyncIteratorPrototype%':  undefined$1,
-    '$ %Atomics%': typeof Atomics === 'undefined' ? undefined$1 : Atomics,
-    '$ %Boolean%': Boolean,
-    '$ %BooleanPrototype%': Boolean.prototype,
-    '$ %DataView%': typeof DataView === 'undefined' ? undefined$1 : DataView,
-    '$ %DataViewPrototype%': typeof DataView === 'undefined' ? undefined$1 : DataView.prototype,
-    '$ %Date%': Date,
-    '$ %DatePrototype%': Date.prototype,
-    '$ %decodeURI%': decodeURI,
-    '$ %decodeURIComponent%': decodeURIComponent,
-    '$ %encodeURI%': encodeURI,
-    '$ %encodeURIComponent%': encodeURIComponent,
-    '$ %Error%': Error,
-    '$ %ErrorPrototype%': Error.prototype,
-    '$ %eval%': eval,
-    // eslint-disable-line no-eval
-    '$ %EvalError%': EvalError,
-    '$ %EvalErrorPrototype%': EvalError.prototype,
-    '$ %Float32Array%': typeof Float32Array === 'undefined' ? undefined$1 : Float32Array,
-    '$ %Float32ArrayPrototype%': typeof Float32Array === 'undefined' ? undefined$1 : Float32Array.prototype,
-    '$ %Float64Array%': typeof Float64Array === 'undefined' ? undefined$1 : Float64Array,
-    '$ %Float64ArrayPrototype%': typeof Float64Array === 'undefined' ? undefined$1 : Float64Array.prototype,
-    '$ %Function%': Function,
-    '$ %FunctionPrototype%': Function.prototype,
-    '$ %Generator%':  undefined$1,
-    '$ %GeneratorFunction%': generatorFunction,
-    '$ %GeneratorPrototype%':  undefined$1,
-    '$ %Int8Array%': typeof Int8Array === 'undefined' ? undefined$1 : Int8Array,
-    '$ %Int8ArrayPrototype%': typeof Int8Array === 'undefined' ? undefined$1 : Int8Array.prototype,
-    '$ %Int16Array%': typeof Int16Array === 'undefined' ? undefined$1 : Int16Array,
-    '$ %Int16ArrayPrototype%': typeof Int16Array === 'undefined' ? undefined$1 : Int8Array.prototype,
-    '$ %Int32Array%': typeof Int32Array === 'undefined' ? undefined$1 : Int32Array,
-    '$ %Int32ArrayPrototype%': typeof Int32Array === 'undefined' ? undefined$1 : Int32Array.prototype,
-    '$ %isFinite%': isFinite,
-    '$ %isNaN%': isNaN,
-    '$ %IteratorPrototype%': hasSymbols$1 ? getProto(getProto([][Symbol.iterator]())) : undefined$1,
-    '$ %JSON%': JSON,
-    '$ %JSONParse%': JSON.parse,
-    '$ %Map%': typeof Map === 'undefined' ? undefined$1 : Map,
-    '$ %MapIteratorPrototype%': typeof Map === 'undefined' || !hasSymbols$1 ? undefined$1 : getProto(new Map()[Symbol.iterator]()),
-    '$ %MapPrototype%': typeof Map === 'undefined' ? undefined$1 : Map.prototype,
-    '$ %Math%': Math,
-    '$ %Number%': Number,
-    '$ %NumberPrototype%': Number.prototype,
-    '$ %Object%': Object,
-    '$ %ObjectPrototype%': Object.prototype,
-    '$ %ObjProto_toString%': Object.prototype.toString,
-    '$ %ObjProto_valueOf%': Object.prototype.valueOf,
-    '$ %parseFloat%': parseFloat,
-    '$ %parseInt%': parseInt,
-    '$ %Promise%': typeof Promise === 'undefined' ? undefined$1 : Promise,
-    '$ %PromisePrototype%': typeof Promise === 'undefined' ? undefined$1 : Promise.prototype,
-    '$ %PromiseProto_then%': typeof Promise === 'undefined' ? undefined$1 : Promise.prototype.then,
-    '$ %Promise_all%': typeof Promise === 'undefined' ? undefined$1 : Promise.all,
-    '$ %Promise_reject%': typeof Promise === 'undefined' ? undefined$1 : Promise.reject,
-    '$ %Promise_resolve%': typeof Promise === 'undefined' ? undefined$1 : Promise.resolve,
-    '$ %Proxy%': typeof Proxy === 'undefined' ? undefined$1 : Proxy,
-    '$ %RangeError%': RangeError,
-    '$ %RangeErrorPrototype%': RangeError.prototype,
-    '$ %ReferenceError%': ReferenceError,
-    '$ %ReferenceErrorPrototype%': ReferenceError.prototype,
-    '$ %Reflect%': typeof Reflect === 'undefined' ? undefined$1 : Reflect,
-    '$ %RegExp%': RegExp,
-    '$ %RegExpPrototype%': RegExp.prototype,
-    '$ %Set%': typeof Set === 'undefined' ? undefined$1 : Set,
-    '$ %SetIteratorPrototype%': typeof Set === 'undefined' || !hasSymbols$1 ? undefined$1 : getProto(new Set()[Symbol.iterator]()),
-    '$ %SetPrototype%': typeof Set === 'undefined' ? undefined$1 : Set.prototype,
-    '$ %SharedArrayBuffer%': typeof SharedArrayBuffer === 'undefined' ? undefined$1 : SharedArrayBuffer,
-    '$ %SharedArrayBufferPrototype%': typeof SharedArrayBuffer === 'undefined' ? undefined$1 : SharedArrayBuffer.prototype,
-    '$ %String%': String,
-    '$ %StringIteratorPrototype%': hasSymbols$1 ? getProto(''[Symbol.iterator]()) : undefined$1,
-    '$ %StringPrototype%': String.prototype,
-    '$ %Symbol%': hasSymbols$1 ? Symbol : undefined$1,
-    '$ %SymbolPrototype%': hasSymbols$1 ? Symbol.prototype : undefined$1,
-    '$ %SyntaxError%': SyntaxError,
-    '$ %SyntaxErrorPrototype%': SyntaxError.prototype,
-    '$ %ThrowTypeError%': ThrowTypeError,
-    '$ %TypedArray%': TypedArray,
-    '$ %TypedArrayPrototype%': TypedArray ? TypedArray.prototype : undefined$1,
-    '$ %TypeError%': TypeError,
-    '$ %TypeErrorPrototype%': TypeError.prototype,
-    '$ %Uint8Array%': typeof Uint8Array === 'undefined' ? undefined$1 : Uint8Array,
-    '$ %Uint8ArrayPrototype%': typeof Uint8Array === 'undefined' ? undefined$1 : Uint8Array.prototype,
-    '$ %Uint8ClampedArray%': typeof Uint8ClampedArray === 'undefined' ? undefined$1 : Uint8ClampedArray,
-    '$ %Uint8ClampedArrayPrototype%': typeof Uint8ClampedArray === 'undefined' ? undefined$1 : Uint8ClampedArray.prototype,
-    '$ %Uint16Array%': typeof Uint16Array === 'undefined' ? undefined$1 : Uint16Array,
-    '$ %Uint16ArrayPrototype%': typeof Uint16Array === 'undefined' ? undefined$1 : Uint16Array.prototype,
-    '$ %Uint32Array%': typeof Uint32Array === 'undefined' ? undefined$1 : Uint32Array,
-    '$ %Uint32ArrayPrototype%': typeof Uint32Array === 'undefined' ? undefined$1 : Uint32Array.prototype,
-    '$ %URIError%': URIError,
-    '$ %URIErrorPrototype%': URIError.prototype,
-    '$ %WeakMap%': typeof WeakMap === 'undefined' ? undefined$1 : WeakMap,
-    '$ %WeakMapPrototype%': typeof WeakMap === 'undefined' ? undefined$1 : WeakMap.prototype,
-    '$ %WeakSet%': typeof WeakSet === 'undefined' ? undefined$1 : WeakSet,
-    '$ %WeakSetPrototype%': typeof WeakSet === 'undefined' ? undefined$1 : WeakSet.prototype
-  };
-
-  var GetIntrinsic = function GetIntrinsic(name, allowMissing) {
-    if (arguments.length > 1 && typeof allowMissing !== 'boolean') {
-      throw new TypeError('"allowMissing" argument must be a boolean');
-    }
-
-    var key = '$ ' + name;
-
-    if (!(key in INTRINSICS)) {
-      throw new SyntaxError('intrinsic ' + name + ' does not exist!');
-    } // istanbul ignore if // hopefully this is impossible to test :-)
-
-
-    if (typeof INTRINSICS[key] === 'undefined' && !allowMissing) {
-      throw new TypeError('intrinsic ' + name + ' exists, but is not available. Please file an issue!');
-    }
-
-    return INTRINSICS[key];
-  };
-
-  var src = functionBind.call(Function.call, Object.prototype.hasOwnProperty);
-
-  var $TypeError = GetIntrinsic('%TypeError%');
-  var $SyntaxError = GetIntrinsic('%SyntaxError%');
-  var predicates = {
-    // https://ecma-international.org/ecma-262/6.0/#sec-property-descriptor-specification-type
-    'Property Descriptor': function isPropertyDescriptor(ES, Desc) {
-      if (ES.Type(Desc) !== 'Object') {
-        return false;
+  function getXml(xhr) {
+    // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException"
+    // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML.
+    try {
+      if (xhr.responseType === "document") {
+        return xhr.responseXML;
       }
 
-      var allowed = {
-        '[[Configurable]]': true,
-        '[[Enumerable]]': true,
-        '[[Get]]': true,
-        '[[Set]]': true,
-        '[[Value]]': true,
-        '[[Writable]]': true
-      };
+      var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
 
-      for (var key in Desc) {
-        // eslint-disable-line
-        if (src(Desc, key) && !allowed[key]) {
-          return false;
-        }
+      if (xhr.responseType === "" && !firefoxBugTakenEffect) {
+        return xhr.responseXML;
       }
+    } catch (e) {}
 
-      var isData = src(Desc, '[[Value]]');
-      var IsAccessor = src(Desc, '[[Get]]') || src(Desc, '[[Set]]');
+    return null;
+  }
 
-      if (isData && IsAccessor) {
-        throw new $TypeError('Property Descriptors may not be both accessor and data descriptors');
-      }
-
-      return true;
-    }
-  };
-
-  var assertRecord = function assertRecord(ES, recordType, argumentName, value) {
-    var predicate = predicates[recordType];
-
-    if (typeof predicate !== 'function') {
-      throw new $SyntaxError('unknown record type: ' + recordType);
-    }
-
-    if (!predicate(ES, value)) {
-      throw new $TypeError(argumentName + ' must be a ' + recordType);
-    }
-
-    console.log(predicate(ES, value), value);
-  };
-
-  var _isNaN = Number.isNaN || function isNaN(a) {
-    return a !== a;
-  };
-
-  var $isNaN = Number.isNaN || function (a) {
-    return a !== a;
-  };
-
-  var _isFinite = Number.isFinite || function (x) {
-    return typeof x === 'number' && !$isNaN(x) && x !== Infinity && x !== -Infinity;
-  };
-
-  var sign = function sign(number) {
-    return number >= 0 ? 1 : -1;
-  };
-
-  var mod = function mod(number, modulo) {
-    var remain = number % modulo;
-    return Math.floor(remain >= 0 ? remain : remain + modulo);
-  };
-
-  var fnToStr = Function.prototype.toString;
-  var constructorRegex = /^\s*class\b/;
-
-  var isES6ClassFn = function isES6ClassFunction(value) {
-    try {
-      var fnStr = fnToStr.call(value);
-      return constructorRegex.test(fnStr);
-    } catch (e) {
-      return false; // not a function
-    }
-  };
-
-  var tryFunctionObject = function tryFunctionToStr(value) {
-    try {
-      if (isES6ClassFn(value)) {
-        return false;
-      }
-
-      fnToStr.call(value);
-      return true;
-    } catch (e) {
-      return false;
-    }
-  };
-
-  var toStr$4 = Object.prototype.toString;
-  var fnClass = '[object Function]';
-  var genClass = '[object GeneratorFunction]';
-  var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
-
-  var isCallable = function isCallable(value) {
-    if (!value) {
-      return false;
-    }
-
-    if (typeof value !== 'function' && typeof value !== 'object') {
-      return false;
-    }
-
-    if (typeof value === 'function' && !value.prototype) {
-      return true;
-    }
-
-    if (hasToStringTag) {
-      return tryFunctionObject(value);
-    }
-
-    if (isES6ClassFn(value)) {
-      return false;
-    }
-
-    var strClass = toStr$4.call(value);
-    return strClass === fnClass || strClass === genClass;
-  };
-
-  var isPrimitive = function isPrimitive(value) {
-    return value === null || typeof value !== 'function' && typeof value !== 'object';
-  };
-
-  var toStr$5 = Object.prototype.toString; // http://ecma-international.org/ecma-262/5.1/#sec-8.12.8
-
-  var ES5internalSlots = {
-    '[[DefaultValue]]': function DefaultValue(O) {
-      var actualHint;
-
-      if (arguments.length > 1) {
-        actualHint = arguments[1];
-      } else {
-        actualHint = toStr$5.call(O) === '[object Date]' ? String : Number;
-      }
-
-      if (actualHint === String || actualHint === Number) {
-        var methods = actualHint === String ? ['toString', 'valueOf'] : ['valueOf', 'toString'];
-        var value, i;
-
-        for (i = 0; i < methods.length; ++i) {
-          if (isCallable(O[methods[i]])) {
-            value = O[methods[i]]();
-
-            if (isPrimitive(value)) {
-              return value;
-            }
-          }
-        }
-
-        throw new TypeError('No default value');
-      }
-
-      throw new TypeError('invalid [[DefaultValue]] hint supplied');
-    }
-  }; // http://ecma-international.org/ecma-262/5.1/#sec-9.1
-
-  var es5 = function ToPrimitive(input) {
-    if (isPrimitive(input)) {
-      return input;
-    }
-
-    if (arguments.length > 1) {
-      return ES5internalSlots['[[DefaultValue]]'](input, arguments[1]);
-    }
-
-    return ES5internalSlots['[[DefaultValue]]'](input);
-  };
-
-  var $Object = GetIntrinsic('%Object%');
-  var $TypeError$1 = GetIntrinsic('%TypeError%');
-  var $String = GetIntrinsic('%String%'); // https://es5.github.io/#x9
-
-  var ES5 = {
-    ToPrimitive: es5,
-    ToBoolean: function ToBoolean(value) {
-      return !!value;
-    },
-    ToNumber: function ToNumber(value) {
-      return +value; // eslint-disable-line no-implicit-coercion
-    },
-    ToInteger: function ToInteger(value) {
-      var number = this.ToNumber(value);
-
-      if (_isNaN(number)) {
-        return 0;
-      }
-
-      if (number === 0 || !_isFinite(number)) {
-        return number;
-      }
-
-      return sign(number) * Math.floor(Math.abs(number));
-    },
-    ToInt32: function ToInt32(x) {
-      return this.ToNumber(x) >> 0;
-    },
-    ToUint32: function ToUint32(x) {
-      return this.ToNumber(x) >>> 0;
-    },
-    ToUint16: function ToUint16(value) {
-      var number = this.ToNumber(value);
-
-      if (_isNaN(number) || number === 0 || !_isFinite(number)) {
-        return 0;
-      }
-
-      var posInt = sign(number) * Math.floor(Math.abs(number));
-      return mod(posInt, 0x10000);
-    },
-    ToString: function ToString(value) {
-      return $String(value);
-    },
-    ToObject: function ToObject(value) {
-      this.CheckObjectCoercible(value);
-      return $Object(value);
-    },
-    CheckObjectCoercible: function CheckObjectCoercible(value, optMessage) {
-      /* jshint eqnull:true */
-      if (value == null) {
-        throw new $TypeError$1(optMessage || 'Cannot call method on ' + value);
-      }
-
-      return value;
-    },
-    IsCallable: isCallable,
-    SameValue: function SameValue(x, y) {
-      if (x === y) {
-        // 0 === -0, but they are not identical.
-        if (x === 0) {
-          return 1 / x === 1 / y;
-        }
-
-        return true;
-      }
-
-      return _isNaN(x) && _isNaN(y);
-    },
-    // https://www.ecma-international.org/ecma-262/5.1/#sec-8
-    Type: function Type(x) {
-      if (x === null) {
-        return 'Null';
-      }
-
-      if (typeof x === 'undefined') {
-        return 'Undefined';
-      }
-
-      if (typeof x === 'function' || typeof x === 'object') {
-        return 'Object';
-      }
-
-      if (typeof x === 'number') {
-        return 'Number';
-      }
-
-      if (typeof x === 'boolean') {
-        return 'Boolean';
-      }
-
-      if (typeof x === 'string') {
-        return 'String';
-      }
-    },
-    // https://ecma-international.org/ecma-262/6.0/#sec-property-descriptor-specification-type
-    IsPropertyDescriptor: function IsPropertyDescriptor(Desc) {
-      if (this.Type(Desc) !== 'Object') {
-        return false;
-      }
-
-      var allowed = {
-        '[[Configurable]]': true,
-        '[[Enumerable]]': true,
-        '[[Get]]': true,
-        '[[Set]]': true,
-        '[[Value]]': true,
-        '[[Writable]]': true
-      };
-
-      for (var key in Desc) {
-        // eslint-disable-line
-        if (src(Desc, key) && !allowed[key]) {
-          return false;
-        }
-      }
-
-      var isData = src(Desc, '[[Value]]');
-      var IsAccessor = src(Desc, '[[Get]]') || src(Desc, '[[Set]]');
-
-      if (isData && IsAccessor) {
-        throw new $TypeError$1('Property Descriptors may not be both accessor and data descriptors');
-      }
-
-      return true;
-    },
-    // https://ecma-international.org/ecma-262/5.1/#sec-8.10.1
-    IsAccessorDescriptor: function IsAccessorDescriptor(Desc) {
-      if (typeof Desc === 'undefined') {
-        return false;
-      }
-
-      assertRecord(this, 'Property Descriptor', 'Desc', Desc);
-
-      if (!src(Desc, '[[Get]]') && !src(Desc, '[[Set]]')) {
-        return false;
-      }
-
-      return true;
-    },
-    // https://ecma-international.org/ecma-262/5.1/#sec-8.10.2
-    IsDataDescriptor: function IsDataDescriptor(Desc) {
-      if (typeof Desc === 'undefined') {
-        return false;
-      }
-
-      assertRecord(this, 'Property Descriptor', 'Desc', Desc);
-
-      if (!src(Desc, '[[Value]]') && !src(Desc, '[[Writable]]')) {
-        return false;
-      }
-
-      return true;
-    },
-    // https://ecma-international.org/ecma-262/5.1/#sec-8.10.3
-    IsGenericDescriptor: function IsGenericDescriptor(Desc) {
-      if (typeof Desc === 'undefined') {
-        return false;
-      }
-
-      assertRecord(this, 'Property Descriptor', 'Desc', Desc);
-
-      if (!this.IsAccessorDescriptor(Desc) && !this.IsDataDescriptor(Desc)) {
-        return true;
-      }
-
-      return false;
-    },
-    // https://ecma-international.org/ecma-262/5.1/#sec-8.10.4
-    FromPropertyDescriptor: function FromPropertyDescriptor(Desc) {
-      if (typeof Desc === 'undefined') {
-        return Desc;
-      }
-
-      assertRecord(this, 'Property Descriptor', 'Desc', Desc);
-
-      if (this.IsDataDescriptor(Desc)) {
-        return {
-          value: Desc['[[Value]]'],
-          writable: !!Desc['[[Writable]]'],
-          enumerable: !!Desc['[[Enumerable]]'],
-          configurable: !!Desc['[[Configurable]]']
-        };
-      } else if (this.IsAccessorDescriptor(Desc)) {
-        return {
-          get: Desc['[[Get]]'],
-          set: Desc['[[Set]]'],
-          enumerable: !!Desc['[[Enumerable]]'],
-          configurable: !!Desc['[[Configurable]]']
-        };
-      } else {
-        throw new $TypeError$1('FromPropertyDescriptor must be called with a fully populated Property Descriptor');
-      }
-    },
-    // https://ecma-international.org/ecma-262/5.1/#sec-8.10.5
-    ToPropertyDescriptor: function ToPropertyDescriptor(Obj) {
-      if (this.Type(Obj) !== 'Object') {
-        throw new $TypeError$1('ToPropertyDescriptor requires an object');
-      }
-
-      var desc = {};
-
-      if (src(Obj, 'enumerable')) {
-        desc['[[Enumerable]]'] = this.ToBoolean(Obj.enumerable);
-      }
-
-      if (src(Obj, 'configurable')) {
-        desc['[[Configurable]]'] = this.ToBoolean(Obj.configurable);
-      }
-
-      if (src(Obj, 'value')) {
-        desc['[[Value]]'] = Obj.value;
-      }
-
-      if (src(Obj, 'writable')) {
-        desc['[[Writable]]'] = this.ToBoolean(Obj.writable);
-      }
-
-      if (src(Obj, 'get')) {
-        var getter = Obj.get;
-
-        if (typeof getter !== 'undefined' && !this.IsCallable(getter)) {
-          throw new TypeError('getter must be a function');
-        }
-
-        desc['[[Get]]'] = getter;
-      }
-
-      if (src(Obj, 'set')) {
-        var setter = Obj.set;
-
-        if (typeof setter !== 'undefined' && !this.IsCallable(setter)) {
-          throw new $TypeError$1('setter must be a function');
-        }
-
-        desc['[[Set]]'] = setter;
-      }
-
-      if ((src(desc, '[[Get]]') || src(desc, '[[Set]]')) && (src(desc, '[[Value]]') || src(desc, '[[Writable]]'))) {
-        throw new $TypeError$1('Invalid property descriptor. Cannot both specify accessors and a value or writable attribute');
-      }
-
-      return desc;
-    }
-  };
-  var es5$1 = ES5;
-
-  var replace = functionBind.call(Function.call, String.prototype.replace);
-  /* eslint-disable no-control-regex */
-
-  var leftWhitespace = /^[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]+/;
-  var rightWhitespace = /[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]+$/;
-  /* eslint-enable no-control-regex */
-
-  var implementation$2 = function trim() {
-    var S = es5$1.ToString(es5$1.CheckObjectCoercible(this));
-    return replace(replace(S, leftWhitespace, ''), rightWhitespace, '');
-  };
-
-  var zeroWidthSpace = "\u200B";
-
-  var polyfill = function getPolyfill() {
-    if (String.prototype.trim && zeroWidthSpace.trim() === zeroWidthSpace) {
-      return String.prototype.trim;
-    }
-
-    return implementation$2;
-  };
-
-  var shim = function shimStringTrim() {
-    var polyfill$1 = polyfill();
-    defineProperties_1(String.prototype, {
-      trim: polyfill$1
-    }, {
-      trim: function testTrim() {
-        return String.prototype.trim !== polyfill$1;
-      }
-    });
-    return polyfill$1;
-  };
-
-  var boundTrim = functionBind.call(Function.call, polyfill());
-  defineProperties_1(boundTrim, {
-    getPolyfill: polyfill,
-    implementation: implementation$2,
-    shim: shim
-  });
-  var string_prototype_trim = boundTrim;
-
-  var toStr$6 = Object.prototype.toString;
-  var hasOwnProperty = Object.prototype.hasOwnProperty;
-
-  var forEachArray = function forEachArray(array, iterator, receiver) {
-    for (var i = 0, len = array.length; i < len; i++) {
-      if (hasOwnProperty.call(array, i)) {
-        if (receiver == null) {
-          iterator(array[i], i, array);
-        } else {
-          iterator.call(receiver, array[i], i, array);
-        }
-      }
-    }
-  };
-
-  var forEachString = function forEachString(string, iterator, receiver) {
-    for (var i = 0, len = string.length; i < len; i++) {
-      // no such thing as a sparse string.
-      if (receiver == null) {
-        iterator(string.charAt(i), i, string);
-      } else {
-        iterator.call(receiver, string.charAt(i), i, string);
-      }
-    }
-  };
-
-  var forEachObject = function forEachObject(object, iterator, receiver) {
-    for (var k in object) {
-      if (hasOwnProperty.call(object, k)) {
-        if (receiver == null) {
-          iterator(object[k], k, object);
-        } else {
-          iterator.call(receiver, object[k], k, object);
-        }
-      }
-    }
-  };
-
-  var forEach = function forEach(list, iterator, thisArg) {
-    if (!isCallable(iterator)) {
-      throw new TypeError('iterator must be a function');
-    }
-
-    var receiver;
-
-    if (arguments.length >= 3) {
-      receiver = thisArg;
-    }
-
-    if (toStr$6.call(list) === '[object Array]') {
-      forEachArray(list, iterator, receiver);
-    } else if (typeof list === 'string') {
-      forEachString(list, iterator, receiver);
-    } else {
-      forEachObject(list, iterator, receiver);
-    }
-  };
-
-  var forEach_1 = forEach;
-
-  var isArray = function isArray(arg) {
-    return Object.prototype.toString.call(arg) === '[object Array]';
-  };
-
-  var parseHeaders = function parseHeaders(headers) {
-    if (!headers) return {};
-    var result = {};
-    forEach_1(string_prototype_trim(headers).split('\n'), function (row) {
-      var index = row.indexOf(':'),
-          key = string_prototype_trim(row.slice(0, index)).toLowerCase(),
-          value = string_prototype_trim(row.slice(index + 1));
-
-      if (typeof result[key] === 'undefined') {
-        result[key] = value;
-      } else if (isArray(result[key])) {
-        result[key].push(value);
-      } else {
-        result[key] = [result[key], value];
-      }
-    });
-    return result;
-  };
-
-  var immutable = extend;
-  var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
-
-  function extend() {
-    var target = {};
-
-    for (var i = 0; i < arguments.length; i++) {
-      var source = arguments[i];
-
-      for (var key in source) {
-        if (hasOwnProperty$1.call(source, key)) {
-          target[key] = source[key];
-        }
-      }
-    }
-
-    return target;
-  }
-
-  var xhr = createXHR;
-  createXHR.XMLHttpRequest = window$1.XMLHttpRequest || noop;
-  createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$1.XDomainRequest;
-  forEachArray$1(["get", "put", "post", "patch", "head", "delete"], function (method) {
-    createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
-      options = initParams(uri, options, callback);
-      options.method = method.toUpperCase();
-      return _createXHR(options);
-    };
-  });
-
-  function forEachArray$1(array, iterator) {
-    for (var i = 0; i < array.length; i++) {
-      iterator(array[i]);
-    }
-  }
-
-  function isEmpty(obj) {
-    for (var i in obj) {
-      if (obj.hasOwnProperty(i)) return false;
-    }
-
-    return true;
-  }
-
-  function initParams(uri, options, callback) {
-    var params = uri;
-
-    if (isFunction_1(options)) {
-      callback = options;
-
-      if (typeof uri === "string") {
-        params = {
-          uri: uri
-        };
-      }
-    } else {
-      params = immutable(options, {
-        uri: uri
-      });
-    }
-
-    params.callback = callback;
-    return params;
-  }
-
-  function createXHR(uri, options, callback) {
-    options = initParams(uri, options, callback);
-    return _createXHR(options);
-  }
-
-  function _createXHR(options) {
-    if (typeof options.callback === "undefined") {
-      throw new Error("callback argument missing");
-    }
-
-    var called = false;
-
-    var callback = function cbOnce(err, response, body) {
-      if (!called) {
-        called = true;
-        options.callback(err, response, body);
-      }
-    };
-
-    function readystatechange() {
-      if (xhr.readyState === 4) {
-        setTimeout(loadFunc, 0);
-      }
-    }
-
-    function getBody() {
-      // Chrome with requestType=blob throws errors arround when even testing access to responseText
-      var body = undefined;
-
-      if (xhr.response) {
-        body = xhr.response;
-      } else {
-        body = xhr.responseText || getXml(xhr);
-      }
-
-      if (isJson) {
-        try {
-          body = JSON.parse(body);
-        } catch (e) {}
-      }
-
-      return body;
-    }
-
-    function errorFunc(evt) {
-      clearTimeout(timeoutTimer);
-
-      if (!(evt instanceof Error)) {
-        evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
-      }
-
-      evt.statusCode = 0;
-      return callback(evt, failureResponse);
-    } // will load the data & process the response in a special response object
-
-
-    function loadFunc() {
-      if (aborted) return;
-      var status;
-      clearTimeout(timeoutTimer);
-
-      if (options.useXDR && xhr.status === undefined) {
-        //IE8 CORS GET successful response doesn't have a status field, but body is fine
-        status = 200;
-      } else {
-        status = xhr.status === 1223 ? 204 : xhr.status;
-      }
-
-      var response = failureResponse;
-      var err = null;
-
-      if (status !== 0) {
-        response = {
-          body: getBody(),
-          statusCode: status,
-          method: method,
-          headers: {},
-          url: uri,
-          rawRequest: xhr
-        };
-
-        if (xhr.getAllResponseHeaders) {
-          //remember xhr can in fact be XDR for CORS in IE
-          response.headers = parseHeaders(xhr.getAllResponseHeaders());
-        }
-      } else {
-        err = new Error("Internal XMLHttpRequest Error");
-      }
-
-      return callback(err, response, response.body);
-    }
-
-    var xhr = options.xhr || null;
-
-    if (!xhr) {
-      if (options.cors || options.useXDR) {
-        xhr = new createXHR.XDomainRequest();
-      } else {
-        xhr = new createXHR.XMLHttpRequest();
-      }
-    }
-
-    var key;
-    var aborted;
-    var uri = xhr.url = options.uri || options.url;
-    var method = xhr.method = options.method || "GET";
-    var body = options.body || options.data;
-    var headers = xhr.headers = options.headers || {};
-    var sync = !!options.sync;
-    var isJson = false;
-    var timeoutTimer;
-    var failureResponse = {
-      body: undefined,
-      headers: {},
-      statusCode: 0,
-      method: method,
-      url: uri,
-      rawRequest: xhr
-    };
-
-    if ("json" in options && options.json !== false) {
-      isJson = true;
-      headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
-
-      if (method !== "GET" && method !== "HEAD") {
-        headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
-
-        body = JSON.stringify(options.json === true ? body : options.json);
-      }
-    }
-
-    xhr.onreadystatechange = readystatechange;
-    xhr.onload = loadFunc;
-    xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function.
-
-    xhr.onprogress = function () {// IE must die
-    };
-
-    xhr.onabort = function () {
-      aborted = true;
-    };
-
-    xhr.ontimeout = errorFunc;
-    xhr.open(method, uri, !sync, options.username, options.password); //has to be after open
-
-    if (!sync) {
-      xhr.withCredentials = !!options.withCredentials;
-    } // Cannot set timeout with sync request
-    // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
-    // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
-
-
-    if (!sync && options.timeout > 0) {
-      timeoutTimer = setTimeout(function () {
-        if (aborted) return;
-        aborted = true; //IE9 may still call readystatechange
-
-        xhr.abort("timeout");
-        var e = new Error("XMLHttpRequest timeout");
-        e.code = "ETIMEDOUT";
-        errorFunc(e);
-      }, options.timeout);
-    }
-
-    if (xhr.setRequestHeader) {
-      for (key in headers) {
-        if (headers.hasOwnProperty(key)) {
-          xhr.setRequestHeader(key, headers[key]);
-        }
-      }
-    } else if (options.headers && !isEmpty(options.headers)) {
-      throw new Error("Headers cannot be set on an XDomainRequest object");
-    }
-
-    if ("responseType" in options) {
-      xhr.responseType = options.responseType;
-    }
-
-    if ("beforeSend" in options && typeof options.beforeSend === "function") {
-      options.beforeSend(xhr);
-    } // Microsoft Edge browser sends "undefined" when send is called with undefined value.
-    // XMLHttpRequest spec says to pass null as body to indicate no body
-    // See https://github.com/naugtur/xhr/issues/100.
-
-
-    xhr.send(body || null);
-    return xhr;
-  }
-
-  function getXml(xhr) {
-    if (xhr.responseType === "document") {
-      return xhr.responseXML;
-    }
-
-    var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
-
-    if (xhr.responseType === "" && !firefoxBugTakenEffect) {
-      return xhr.responseXML;
-    }
-
-    return null;
-  }
-
-  function noop() {}
+  function noop() {}
+  xhr["default"] = default_1;
 
   /**
    * Takes a webvtt file contents and parses it into cues
    */
 
   var parseCues = function parseCues(srcContent, track) {
-    var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder());
+    var parser = new window$3.WebVTT.Parser(window$3, window$3.vttjs, window$3.WebVTT.StringDecoder());
     var errors = [];
 
     parser.oncue = function (cue) {
     parser.parse(srcContent);
 
     if (errors.length > 0) {
-      if (window$1.console && window$1.console.groupCollapsed) {
-        window$1.console.groupCollapsed("Text Track parsing errors for " + track.src);
+      if (window$3.console && window$3.console.groupCollapsed) {
+        window$3.console.groupCollapsed("Text Track parsing errors for " + track.src);
       }
 
       errors.forEach(function (error) {
         return log.error(error);
       });
 
-      if (window$1.console && window$1.console.groupEnd) {
-        window$1.console.groupEnd();
+      if (window$3.console && window$3.console.groupEnd) {
+        window$3.console.groupEnd();
       }
     }
 
       track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading
       // NOTE: this is only used for the alt/video.novtt.js build
 
-      if (typeof window$1.WebVTT !== 'function') {
+      if (typeof window$3.WebVTT !== 'function') {
         if (track.tech_) {
           // to prevent use before define eslint error, we define loadHandler
           // as a let here
    */
 
 
-  var TextTrack =
-  /*#__PURE__*/
-  function (_Track) {
-    _inheritsLoose(TextTrack, _Track);
+  var TextTrack = /*#__PURE__*/function (_Track) {
+    inheritsLoose(TextTrack, _Track);
 
     /**
      * Create an instance of this class.
       _this.tech_ = settings.tech;
       _this.cues_ = [];
       _this.activeCues_ = [];
+      _this.preload_ = _this.tech_.preloadTextTracks !== false;
       var cues = new TextTrackCueList(_this.cues_);
       var activeCues = new TextTrackCueList(_this.activeCues_);
       var changed = false;
-      var timeupdateHandler = bind(_assertThisInitialized(_this), function () {
+      var timeupdateHandler = bind(assertThisInitialized(_this), function () {
         // Accessing this.activeCues for the side-effects of updating itself
         // due to its nature as a getter function. Do not remove or cues will
         // stop updating!
         }, true);
       }
 
-      Object.defineProperties(_assertThisInitialized(_this), {
+      Object.defineProperties(assertThisInitialized(_this), {
         /**
          * @memberof TextTrack
          * @member {boolean} default
 
             mode = newMode;
 
+            if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) {
+              // On-demand load.
+              loadTrack(this.src, this);
+            }
+
             if (mode !== 'disabled') {
               this.tech_.ready(function () {
                 _this2.tech_.on('timeupdate', timeupdateHandler);
 
       if (settings.src) {
         _this.src = settings.src;
-        loadTrack(settings.src, _assertThisInitialized(_this));
+
+        if (!_this.preload_) {
+          // Tracks will load on-demand.
+          // Act like we're loaded for other purposes.
+          _this.loaded_ = true;
+        }
+
+        if (_this.preload_ || default_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') {
+          loadTrack(_this.src, assertThisInitialized(_this));
+        }
       } else {
         _this.loaded_ = true;
       }
     _proto.addCue = function addCue(originalCue) {
       var cue = originalCue;
 
-      if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) {
-        cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
+      if (window$3.vttjs && !(originalCue instanceof window$3.vttjs.VTTCue)) {
+        cue = new window$3.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
 
         for (var prop in originalCue) {
           if (!(prop in cue)) {
    * @extends Track
    */
 
-  var AudioTrack =
-  /*#__PURE__*/
-  function (_Track) {
-    _inheritsLoose(AudioTrack, _Track);
+  var AudioTrack = /*#__PURE__*/function (_Track) {
+    inheritsLoose(AudioTrack, _Track);
 
     /**
      * Create an instance of this class.
        * @fires VideoTrack#selectedchange
        */
 
-      Object.defineProperty(_assertThisInitialized(_this), 'enabled', {
+      Object.defineProperty(assertThisInitialized(_this), 'enabled', {
         get: function get() {
           return enabled;
         },
    * @extends Track
    */
 
-  var VideoTrack =
-  /*#__PURE__*/
-  function (_Track) {
-    _inheritsLoose(VideoTrack, _Track);
+  var VideoTrack = /*#__PURE__*/function (_Track) {
+    inheritsLoose(VideoTrack, _Track);
 
     /**
      * Create an instance of this class.
        * @fires VideoTrack#selectedchange
        */
 
-      Object.defineProperty(_assertThisInitialized(_this), 'selected', {
+      Object.defineProperty(assertThisInitialized(_this), 'selected', {
         get: function get() {
           return selected;
         },
    * @extends EventTarget
    */
 
-  var HTMLTrackElement =
-  /*#__PURE__*/
-  function (_EventTarget) {
-    _inheritsLoose(HTMLTrackElement, _EventTarget);
+  var HTMLTrackElement = /*#__PURE__*/function (_EventTarget) {
+    inheritsLoose(HTMLTrackElement, _EventTarget);
 
     /**
      * Create an instance of this class.
       _this.srclang = track.language;
       _this.label = track.label;
       _this["default"] = track["default"];
-      Object.defineProperties(_assertThisInitialized(_this), {
+      Object.defineProperties(assertThisInitialized(_this), {
         /**
          * @memberof HTMLTrackElement
          * @member {HTMLTrackElement~ReadyState} readyState
 
         _this.trigger({
           type: 'load',
-          target: _assertThisInitialized(_this)
+          target: assertThisInitialized(_this)
         });
       });
       return _this;
       privateName: 'remoteTextTrackEls_'
     }
   };
-  var ALL = mergeOptions(NORMAL, REMOTE);
-  REMOTE.names = Object.keys(REMOTE);
+
+  var ALL = _extends_1({}, NORMAL, REMOTE);
+
+  REMOTE.names = Object.keys(REMOTE);
   NORMAL.names = Object.keys(NORMAL);
   ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
 
   /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 
   /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
   var _objCreate = Object.create || function () {
     function F() {}
 
       return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
     }
 
-    var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/);
+    var m = input.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/);
 
     if (!m) {
       return null;
             settings.alt(k, vals0, ["auto"]);
 
             if (vals.length === 2) {
-              settings.alt("lineAlign", vals[1], ["start", "middle", "end"]);
+              settings.alt("lineAlign", vals[1], ["start", "center", "end"]);
             }
 
             break;
             settings.percent(k, vals[0]);
 
             if (vals.length === 2) {
-              settings.alt("positionAlign", vals[1], ["start", "middle", "end"]);
+              settings.alt("positionAlign", vals[1], ["start", "center", "end"]);
             }
 
             break;
             break;
 
           case "align":
-            settings.alt(k, v, ["start", "middle", "end", "left", "right"]);
+            settings.alt(k, v, ["start", "center", "end", "left", "right"]);
             break;
         }
       }, /:/, /\s/); // Apply default values for any missing fields.
 
       cue.region = settings.get("region", null);
       cue.vertical = settings.get("vertical", "");
-      cue.line = settings.get("line", "auto");
+
+      try {
+        cue.line = settings.get("line", "auto");
+      } catch (e) {}
+
       cue.lineAlign = settings.get("lineAlign", "start");
       cue.snapToLines = settings.get("snapToLines", true);
-      cue.size = settings.get("size", 100);
-      cue.align = settings.get("align", "middle");
-      cue.position = settings.get("position", {
-        start: 0,
-        left: 0,
-        middle: 50,
-        end: 100,
-        right: 100
-      }, cue.align);
+      cue.size = settings.get("size", 100); // Safari still uses the old middle value and won't accept center
+
+      try {
+        cue.align = settings.get("align", "center");
+      } catch (e) {
+        cue.align = settings.get("align", "middle");
+      }
+
+      try {
+        cue.position = settings.get("position", "auto");
+      } catch (e) {
+        cue.position = settings.get("position", {
+          start: 0,
+          left: 0,
+          center: 50,
+          middle: 50,
+          end: 100,
+          right: 100
+        }, cue.align);
+      }
+
       cue.positionAlign = settings.get("positionAlign", {
         start: "start",
         left: "start",
-        middle: "middle",
+        center: "center",
+        middle: "center",
         end: "end",
         right: "end"
       }, cue.align);
     consumeCueSettings(input, cue);
   }
 
-  var ESCAPE = {
-    "&amp;": "&",
-    "&lt;": "<",
-    "&gt;": ">",
-    "&lrm;": "\u200E",
-    "&rlm;": "\u200F",
-    "&nbsp;": "\xA0"
-  };
+  var TEXTAREA_ELEMENT = document.createElement("textarea");
   var TAG_NAME = {
     c: "span",
     i: "i",
     rt: "rt",
     v: "span",
     lang: "span"
+  }; // 5.1 default text color
+  // 5.2 default text background color is equivalent to text color with bg_ prefix
+
+  var DEFAULT_COLOR_CLASS = {
+    white: 'rgba(255,255,255,1)',
+    lime: 'rgba(0,255,0,1)',
+    cyan: 'rgba(0,255,255,1)',
+    red: 'rgba(255,0,0,1)',
+    yellow: 'rgba(255,255,0,1)',
+    magenta: 'rgba(255,0,255,1)',
+    blue: 'rgba(0,0,255,1)',
+    black: 'rgba(0,0,0,1)'
   };
   var TAG_ANNOTATION = {
     v: "title",
       // the tag.
 
       return consume(m[1] ? m[1] : m[2]);
-    } // Unescape a string 's'.
-
-
-    function unescape1(e) {
-      return ESCAPE[e];
     }
 
     function unescape(s) {
-      while (m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/)) {
-        s = s.replace(m[0], unescape1);
-      }
-
+      TEXTAREA_ELEMENT.innerHTML = s;
+      s = TEXTAREA_ELEMENT.textContent;
+      TEXTAREA_ELEMENT.textContent = "";
       return s;
     }
 
       }
 
       var element = window.document.createElement(tagName);
-      element.localName = tagName;
       var name = TAG_ANNOTATION[type];
 
       if (name && annotation) {
 
 
         if (m[2]) {
-          node.className = m[2].substr(1).replace('.', ' ');
+          var classes = m[2].split('.');
+          classes.forEach(function (cl) {
+            var bgColor = /^bg_/.test(cl); // slice out `bg_` if it's a background color
+
+            var colorName = bgColor ? cl.slice(3) : cl;
+
+            if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) {
+              var propName = bgColor ? 'background-color' : 'color';
+              var propValue = DEFAULT_COLOR_CLASS[colorName];
+              node.style[propName] = propValue;
+            }
+          });
+          node.className = classes.join(' ');
         } // Append the node to the current node, and enter the scope of the new
         // node.
 
     };
     this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue
     // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
-    // mirrors of them except "middle" which is "center" in CSS.
+    // mirrors of them except middle instead of center on Safari.
 
     this.div = window.document.createElement("div");
     styles = {
         textPos = cue.position;
         break;
 
-      case "middle":
+      case "center":
         textPos = cue.position - cue.size / 2;
         break;
 
       var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
 
       switch (cue.lineAlign) {
-        case "middle":
+        case "center":
           linePos -= calculatedPercentage / 2;
           break;
 
                 continue;
               }
 
-              self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
+              self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); // Safari still uses the old middle value and won't accept center
+
+              try {
+                self.cue.align = "center";
+              } catch (e) {
+                self.cue.align = "middle";
+              }
+
               self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data.
 
               if (line.indexOf("-->") === -1) {
                 self.cue.text += "\n";
               }
 
-              self.cue.text += line;
+              self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n');
               continue;
 
             case "BADCUE":
   };
   var alignSetting = {
     "start": 1,
-    "middle": 1,
+    "center": 1,
     "end": 1,
     "left": 1,
-    "right": 1
+    "right": 1,
+    "auto": 1,
+    "line-left": 1,
+    "line-right": 1
   };
 
   function findDirectionSetting(value) {
     var _snapToLines = true;
     var _line = "auto";
     var _lineAlign = "start";
-    var _position = 50;
-    var _positionAlign = "middle";
-    var _size = 50;
-    var _align = "middle";
+    var _position = "auto";
+    var _positionAlign = "auto";
+    var _size = 100;
+    var _align = "center";
     Object.defineProperties(this, {
       "id": {
         enumerable: true,
           var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string.
 
           if (setting === false) {
-            throw new SyntaxError("An invalid or illegal string was specified.");
+            throw new SyntaxError("Vertical: an invalid or illegal direction string was specified.");
           }
 
           _vertical = setting;
         },
         set: function set(value) {
           if (typeof value !== "number" && value !== autoKeyword) {
-            throw new SyntaxError("An invalid number or illegal string was specified.");
+            throw new SyntaxError("Line: an invalid number or illegal string was specified.");
           }
 
           _line = value;
           var setting = findAlignSetting(value);
 
           if (!setting) {
-            throw new SyntaxError("An invalid or illegal string was specified.");
+            console.warn("lineAlign: an invalid or illegal string was specified.");
+          } else {
+            _lineAlign = setting;
+            this.hasBeenReset = true;
           }
-
-          _lineAlign = setting;
-          this.hasBeenReset = true;
         }
       },
       "position": {
           var setting = findAlignSetting(value);
 
           if (!setting) {
-            throw new SyntaxError("An invalid or illegal string was specified.");
+            console.warn("positionAlign: an invalid or illegal string was specified.");
+          } else {
+            _positionAlign = setting;
+            this.hasBeenReset = true;
           }
-
-          _positionAlign = setting;
-          this.hasBeenReset = true;
         }
       },
       "size": {
           var setting = findAlignSetting(value);
 
           if (!setting) {
-            throw new SyntaxError("An invalid or illegal string was specified.");
+            throw new SyntaxError("align: an invalid or illegal alignment string was specified.");
           }
 
           _align = setting;
           var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value.
 
           if (setting === false) {
-            throw new SyntaxError("An invalid or illegal string was specified.");
+            console.warn("Scroll: an invalid or illegal string was specified.");
+          } else {
+            _scroll = setting;
           }
-
-          _scroll = setting;
         }
       }
     });
       VTTCue: vttcue,
       VTTRegion: vttregion
     };
-    window$1.vttjs = vttjs;
-    window$1.WebVTT = vttjs.WebVTT;
+    window$3.vttjs = vttjs;
+    window$3.WebVTT = vttjs.WebVTT;
     var cueShim = vttjs.VTTCue;
     var regionShim = vttjs.VTTRegion;
-    var nativeVTTCue = window$1.VTTCue;
-    var nativeVTTRegion = window$1.VTTRegion;
+    var nativeVTTCue = window$3.VTTCue;
+    var nativeVTTRegion = window$3.VTTRegion;
 
     vttjs.shim = function () {
-      window$1.VTTCue = cueShim;
-      window$1.VTTRegion = regionShim;
+      window$3.VTTCue = cueShim;
+      window$3.VTTRegion = regionShim;
     };
 
     vttjs.restore = function () {
-      window$1.VTTCue = nativeVTTCue;
-      window$1.VTTRegion = nativeVTTRegion;
+      window$3.VTTCue = nativeVTTCue;
+      window$3.VTTRegion = nativeVTTRegion;
     };
 
-    if (!window$1.VTTCue) {
+    if (!window$3.VTTCue) {
       vttjs.shim();
     }
   });
    */
 
 
-  var Tech =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(Tech, _Component);
+  var Tech = /*#__PURE__*/function (_Component) {
+    inheritsLoose(Tech, _Component);
 
     /**
     * Create an instance of this Tech.
         _this.emulateTextTracks();
       }
 
+      _this.preloadTextTracks = options.preloadTextTracks !== false;
       _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
 
       _this.initTrackListeners(); // Turn on component tap events only if not using native controls
     _proto.addWebVttScript_ = function addWebVttScript_() {
       var _this5 = this;
 
-      if (window$1.WebVTT) {
+      if (window$3.WebVTT) {
         return;
       } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
       // signals that the Tech is ready at which point Tech.el_ is part of the DOM
         }); // but have not loaded yet and we set it to true before the inject so that
         // we don't overwrite the injected window.WebVTT if it loads right away
 
-        window$1.WebVTT = true;
+        window$3.WebVTT = true;
         this.el().parentNode.appendChild(script);
       } else {
         this.ready(this.addWebVttScript_);
     ;
 
     _proto.requestPictureInPicture = function requestPictureInPicture() {
-      var PromiseClass = this.options_.Promise || window$1.Promise;
+      var PromiseClass = this.options_.Promise || window$3.Promise;
 
       if (PromiseClass) {
         return PromiseClass.reject();
 
       name = toTitleCase(name);
 
-      if (window$1 && window$1.videojs && window$1.videojs[name]) {
+      if (window$3 && window$3.videojs && window$3.videojs[name]) {
         log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)");
-        return window$1.videojs[name];
+        return window$3.videojs[name];
       }
     };
 
     buffered: 1,
     currentTime: 1,
     duration: 1,
-    seekable: 1,
+    muted: 1,
     played: 1,
     paused: 1,
+    seekable: 1,
     volume: 1
   };
   /**
 
   var allowedSetters = {
     setCurrentTime: 1,
+    setMuted: 1,
     setVolume: 1
   };
   /**
    * @extends Component
    */
 
-  var MediaLoader =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(MediaLoader, _Component);
+  var MediaLoader = /*#__PURE__*/function (_Component) {
+    inheritsLoose(MediaLoader, _Component);
 
     /**
      * Create an instance of this class.
    * @extends Component
    */
 
-  var ClickableComponent =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(ClickableComponent, _Component);
+  var ClickableComponent = /*#__PURE__*/function (_Component) {
+    inheritsLoose(ClickableComponent, _Component);
 
     /**
      * Creates an instance of this class.
      *
      * @param  {Object} [options]
      *         The key/value store of player options.
+     *
+     * @param  {function} [options.clickHandler]
+     *         The function to call when the button is clicked / activated
      */
     function ClickableComponent(player, options) {
       var _this;
      */
     ;
 
-    _proto.handleClick = function handleClick(event) {}
+    _proto.handleClick = function handleClick(event) {
+      if (this.options_.clickHandler) {
+        this.options_.clickHandler.call(this, arguments);
+      }
+    }
     /**
      * Event handler that is called when a `ClickableComponent` receives a
      * `keydown` event.
    * @extends ClickableComponent
    */
 
-  var PosterImage =
-  /*#__PURE__*/
-  function (_ClickableComponent) {
-    _inheritsLoose(PosterImage, _ClickableComponent);
+  var PosterImage = /*#__PURE__*/function (_ClickableComponent) {
+    inheritsLoose(PosterImage, _ClickableComponent);
 
     /**
      * Create an instance of this class.
 
       _this.update();
 
-      player.on('posterchange', bind(_assertThisInitialized(_this), _this.update));
+      player.on('posterchange', bind(assertThisInitialized(_this), _this.update));
       return _this;
     }
     /**
         return;
       }
 
-      if (this.player_.tech(true)) {
+      var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0;
+
+      if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where
+      // calling .focus() on the video element causes the video to go black,
+      // so we avoid it in that specific case
+      !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) {
         this.player_.tech(true).focus();
       }
 
    */
 
 
-  var TextTrackDisplay =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(TextTrackDisplay, _Component);
+  var TextTrackDisplay = /*#__PURE__*/function (_Component) {
+    inheritsLoose(TextTrackDisplay, _Component);
 
     /**
      * Creates an instance of this class.
       var _this;
 
       _this = _Component.call(this, player, options, ready) || this;
-      var updateDisplayHandler = bind(_assertThisInitialized(_this), _this.updateDisplay);
-      player.on('loadstart', bind(_assertThisInitialized(_this), _this.toggleDisplay));
+      var updateDisplayHandler = bind(assertThisInitialized(_this), _this.updateDisplay);
+      player.on('loadstart', bind(assertThisInitialized(_this), _this.toggleDisplay));
       player.on('texttrackchange', updateDisplayHandler);
-      player.on('loadedmetadata', bind(_assertThisInitialized(_this), _this.preselectTrack)); // This used to be called during player init, but was causing an error
+      player.on('loadedmetadata', bind(assertThisInitialized(_this), _this.preselectTrack)); // This used to be called during player init, but was causing an error
       // if a track should show by default and the display hadn't loaded yet.
       // Should probably be moved to an external track loader when we support
       // tracks that don't need a display.
 
-      player.ready(bind(_assertThisInitialized(_this), function () {
+      player.ready(bind(assertThisInitialized(_this), function () {
         if (player.tech_ && player.tech_.featuresNativeTextTracks) {
           this.hide();
           return;
 
         player.on('fullscreenchange', updateDisplayHandler);
         player.on('playerresize', updateDisplayHandler);
-        window$1.addEventListener('orientationchange', updateDisplayHandler);
+        window$3.addEventListener('orientationchange', updateDisplayHandler);
         player.on('dispose', function () {
-          return window$1.removeEventListener('orientationchange', updateDisplayHandler);
+          return window$3.removeEventListener('orientationchange', updateDisplayHandler);
         });
         var tracks = this.options_.playerOptions.tracks || [];
 
     ;
 
     _proto.clearDisplay = function clearDisplay() {
-      if (typeof window$1.WebVTT === 'function') {
-        window$1.WebVTT.processCues(window$1, [], this.el_);
+      if (typeof window$3.WebVTT === 'function') {
+        window$3.WebVTT.processCues(window$3, [], this.el_);
       }
     }
     /**
         }
 
         if (overrides.fontPercent && overrides.fontPercent !== 1) {
-          var fontSize = window$1.parseFloat(cueDiv.style.fontSize);
+          var fontSize = window$3.parseFloat(cueDiv.style.fontSize);
           cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
           cueDiv.style.height = 'auto';
           cueDiv.style.top = 'auto';
         tracks = [tracks];
       }
 
-      if (typeof window$1.WebVTT !== 'function' || tracks.every(function (track) {
+      if (typeof window$3.WebVTT !== 'function' || tracks.every(function (track) {
         return !track.activeCues;
       })) {
         return;
       } // removes all cues before it processes new ones
 
 
-      window$1.WebVTT.processCues(window$1, cues, this.el_); // add unique class to each language text track & add settings styling if necessary
+      window$3.WebVTT.processCues(window$3, cues, this.el_); // add unique class to each language text track & add settings styling if necessary
 
       for (var _i2 = 0; _i2 < tracks.length; ++_i2) {
         var _track2 = tracks[_i2];
    * @extends Component
    */
 
-  var LoadingSpinner =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(LoadingSpinner, _Component);
+  var LoadingSpinner = /*#__PURE__*/function (_Component) {
+    inheritsLoose(LoadingSpinner, _Component);
 
     function LoadingSpinner() {
       return _Component.apply(this, arguments) || this;
    * @extends ClickableComponent
    */
 
-  var Button =
-  /*#__PURE__*/
-  function (_ClickableComponent) {
-    _inheritsLoose(Button, _ClickableComponent);
+  var Button = /*#__PURE__*/function (_ClickableComponent) {
+    inheritsLoose(Button, _ClickableComponent);
 
     function Button() {
       return _ClickableComponent.apply(this, arguments) || this;
    * @extends Button
    */
 
-  var BigPlayButton =
-  /*#__PURE__*/
-  function (_Button) {
-    _inheritsLoose(BigPlayButton, _Button);
+  var BigPlayButton = /*#__PURE__*/function (_Button) {
+    inheritsLoose(BigPlayButton, _Button);
 
     function BigPlayButton(player, options) {
       var _this;
       var playPromise = this.player_.play(); // exit early if clicked via the mouse
 
       if (this.mouseused_ && event.clientX && event.clientY) {
+        var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0;
         silencePromise(playPromise);
 
-        if (this.player_.tech(true)) {
+        if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where
+        // calling .focus() on the video element causes the video to go black,
+        // so we avoid it in that specific case
+        !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) {
           this.player_.tech(true).focus();
         }
 
    * @extends Button
    */
 
-  var CloseButton =
-  /*#__PURE__*/
-  function (_Button) {
-    _inheritsLoose(CloseButton, _Button);
+  var CloseButton = /*#__PURE__*/function (_Button) {
+    inheritsLoose(CloseButton, _Button);
 
     /**
     * Creates an instance of the this class.
    * @extends Button
    */
 
-  var PlayToggle =
-  /*#__PURE__*/
-  function (_Button) {
-    _inheritsLoose(PlayToggle, _Button);
+  var PlayToggle = /*#__PURE__*/function (_Button) {
+    inheritsLoose(PlayToggle, _Button);
 
     /**
      * Creates an instance of this class.
   }; // Internal pointer to the current implementation.
 
 
-  var implementation$3 = defaultImplementation;
+  var implementation = defaultImplementation;
   /**
    * Replaces the default formatTime implementation with a custom implementation.
    *
    */
 
   function setFormatTime(customImplementation) {
-    implementation$3 = customImplementation;
+    implementation = customImplementation;
   }
   /**
    * Resets formatTime to the default implementation.
    */
 
   function resetFormatTime() {
-    implementation$3 = defaultImplementation;
+    implementation = defaultImplementation;
   }
   /**
    * Delegates to either the default time formatting function or a custom
       guide = seconds;
     }
 
-    return implementation$3(seconds, guide);
+    return implementation(seconds, guide);
   }
 
   /**
    * @extends Component
    */
 
-  var TimeDisplay =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(TimeDisplay, _Component);
+  var TimeDisplay = /*#__PURE__*/function (_Component) {
+    inheritsLoose(TimeDisplay, _Component);
 
     /**
      * Creates an instance of this class.
       var _this;
 
       _this = _Component.call(this, player, options) || this;
-      _this.throttledUpdateContent = throttle(bind(_assertThisInitialized(_this), _this.updateContent), UPDATE_REFRESH_INTERVAL);
 
-      _this.on(player, 'timeupdate', _this.throttledUpdateContent);
+      _this.on(player, ['timeupdate', 'ended'], _this.updateContent);
+
+      _this.updateTextNode_();
 
       return _this;
     }
         // role='presentation' causes VoiceOver to NOT treat this span as a break.
         'role': 'presentation'
       });
-      this.updateTextNode_();
       el.appendChild(this.contentEl_);
       return el;
     };
       _Component.prototype.dispose.call(this);
     }
     /**
-     * Updates the "remaining time" text node with new content using the
-     * contents of the `formattedTime_` property.
+     * Updates the time display text node with a new time
+     *
+     * @param {number} [time=0] the time to update to
      *
      * @private
      */
     ;
 
-    _proto.updateTextNode_ = function updateTextNode_() {
-      if (!this.contentEl_) {
-        return;
-      }
+    _proto.updateTextNode_ = function updateTextNode_(time) {
+      var _this2 = this;
 
-      while (this.contentEl_.firstChild) {
-        this.contentEl_.removeChild(this.contentEl_.firstChild);
+      if (time === void 0) {
+        time = 0;
       }
 
-      this.textNode_ = document.createTextNode(this.formattedTime_ || this.formatTime_(0));
-      this.contentEl_.appendChild(this.textNode_);
-    }
-    /**
-     * Generates a formatted time for this component to use in display.
-     *
-     * @param  {number} time
-     *         A numeric time, in seconds.
-     *
-     * @return {string}
-     *         A formatted time
-     *
-     * @private
-     */
-    ;
-
-    _proto.formatTime_ = function formatTime_(time) {
-      return formatTime(time);
-    }
-    /**
-     * Updates the time display text node if it has what was passed in changed
-     * the formatted time.
-     *
-     * @param {number} time
-     *        The time to update to
-     *
-     * @private
-     */
-    ;
-
-    _proto.updateFormattedTime_ = function updateFormattedTime_(time) {
-      var formattedTime = this.formatTime_(time);
+      time = formatTime(time);
 
-      if (formattedTime === this.formattedTime_) {
+      if (this.formattedTime_ === time) {
         return;
       }
 
-      this.formattedTime_ = formattedTime;
-      this.requestAnimationFrame(this.updateTextNode_);
+      this.formattedTime_ = time;
+      this.requestAnimationFrame(function () {
+        if (!_this2.contentEl_) {
+          return;
+        }
+
+        var oldNode = _this2.textNode_;
+        _this2.textNode_ = document.createTextNode(_this2.formattedTime_);
+
+        if (!_this2.textNode_) {
+          return;
+        }
+
+        if (oldNode) {
+          _this2.contentEl_.replaceChild(_this2.textNode_, oldNode);
+        } else {
+          _this2.contentEl_.appendChild(_this2.textNode_);
+        }
+      });
     }
     /**
      * To be filled out in the child class, should update the displayed time
    * @extends Component
    */
 
-  var CurrentTimeDisplay =
-  /*#__PURE__*/
-  function (_TimeDisplay) {
-    _inheritsLoose(CurrentTimeDisplay, _TimeDisplay);
+  var CurrentTimeDisplay = /*#__PURE__*/function (_TimeDisplay) {
+    inheritsLoose(CurrentTimeDisplay, _TimeDisplay);
 
-    /**
-     * Creates an instance of this class.
-     *
-     * @param {Player} player
-     *        The `Player` that this class should be attached to.
-     *
-     * @param {Object} [options]
-     *        The key/value store of player options.
-     */
-    function CurrentTimeDisplay(player, options) {
-      var _this;
-
-      _this = _TimeDisplay.call(this, player, options) || this;
+    function CurrentTimeDisplay() {
+      return _TimeDisplay.apply(this, arguments) || this;
+    }
 
-      _this.on(player, 'ended', _this.handleEnded);
+    var _proto = CurrentTimeDisplay.prototype;
 
-      return _this;
-    }
     /**
      * Builds the default DOM `className`.
      *
      * @return {string}
      *         The DOM `className` for this object.
      */
-
-
-    var _proto = CurrentTimeDisplay.prototype;
-
     _proto.buildCSSClass = function buildCSSClass() {
       return 'vjs-current-time';
     }
 
     _proto.updateContent = function updateContent(event) {
       // Allows for smooth scrubbing, when player can't keep up.
-      var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
-      this.updateFormattedTime_(time);
-    }
-    /**
-     * When the player fires ended there should be no time left. Sadly
-     * this is not always the case, lets make it seem like that is the case
-     * for users.
-     *
-     * @param {EventTarget~Event} [event]
-     *        The `ended` event that caused this to run.
-     *
-     * @listens Player#ended
-     */
-    ;
+      var time;
 
-    _proto.handleEnded = function handleEnded(event) {
-      if (!this.player_.duration()) {
-        return;
+      if (this.player_.ended()) {
+        time = this.player_.duration();
+      } else {
+        time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
       }
 
-      this.updateFormattedTime_(this.player_.duration());
+      this.updateTextNode_(time);
     };
 
     return CurrentTimeDisplay;
    * @extends Component
    */
 
-  var DurationDisplay =
-  /*#__PURE__*/
-  function (_TimeDisplay) {
-    _inheritsLoose(DurationDisplay, _TimeDisplay);
+  var DurationDisplay = /*#__PURE__*/function (_TimeDisplay) {
+    inheritsLoose(DurationDisplay, _TimeDisplay);
 
     /**
      * Creates an instance of this class.
       // can likely be removed for 7.0.
 
 
-      _this.on(player, 'loadedmetadata', _this.throttledUpdateContent);
+      _this.on(player, 'loadedmetadata', _this.updateContent);
 
       return _this;
     }
 
     _proto.updateContent = function updateContent(event) {
       var duration = this.player_.duration();
-
-      if (this.duration_ !== duration) {
-        this.duration_ = duration;
-        this.updateFormattedTime_(duration);
-      }
+      this.updateTextNode_(duration);
     };
 
     return DurationDisplay;
    * @extends Component
    */
 
-  var TimeDivider =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(TimeDivider, _Component);
+  var TimeDivider = /*#__PURE__*/function (_Component) {
+    inheritsLoose(TimeDivider, _Component);
 
     function TimeDivider() {
       return _Component.apply(this, arguments) || this;
    * @extends Component
    */
 
-  var RemainingTimeDisplay =
-  /*#__PURE__*/
-  function (_TimeDisplay) {
-    _inheritsLoose(RemainingTimeDisplay, _TimeDisplay);
+  var RemainingTimeDisplay = /*#__PURE__*/function (_TimeDisplay) {
+    inheritsLoose(RemainingTimeDisplay, _TimeDisplay);
 
     /**
      * Creates an instance of this class.
 
       _this = _TimeDisplay.call(this, player, options) || this;
 
-      _this.on(player, 'durationchange', _this.throttledUpdateContent);
-
-      _this.on(player, 'ended', _this.handleEnded);
+      _this.on(player, 'durationchange', _this.updateContent);
 
       return _this;
     }
     _proto.updateContent = function updateContent(event) {
       if (typeof this.player_.duration() !== 'number') {
         return;
-      } // @deprecated We should only use remainingTimeDisplay
-      // as of video.js 7
+      }
 
+      var time; // @deprecated We should only use remainingTimeDisplay
+      // as of video.js 7
 
-      if (this.player_.remainingTimeDisplay) {
-        this.updateFormattedTime_(this.player_.remainingTimeDisplay());
+      if (this.player_.ended()) {
+        time = 0;
+      } else if (this.player_.remainingTimeDisplay) {
+        time = this.player_.remainingTimeDisplay();
       } else {
-        this.updateFormattedTime_(this.player_.remainingTime());
-      }
-    }
-    /**
-     * When the player fires ended there should be no time left. Sadly
-     * this is not always the case, lets make it seem like that is the case
-     * for users.
-     *
-     * @param {EventTarget~Event} [event]
-     *        The `ended` event that caused this to run.
-     *
-     * @listens Player#ended
-     */
-    ;
-
-    _proto.handleEnded = function handleEnded(event) {
-      if (!this.player_.duration()) {
-        return;
+        time = this.player_.remainingTime();
       }
 
-      this.updateFormattedTime_(0);
+      this.updateTextNode_(time);
     };
 
     return RemainingTimeDisplay;
    * @extends Component
    */
 
-  var LiveDisplay =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(LiveDisplay, _Component);
+  var LiveDisplay = /*#__PURE__*/function (_Component) {
+    inheritsLoose(LiveDisplay, _Component);
 
     /**
      * Creates an instance of this class.
    * @extends Component
    */
 
-  var SeekToLive =
-  /*#__PURE__*/
-  function (_Button) {
-    _inheritsLoose(SeekToLive, _Button);
+  var SeekToLive = /*#__PURE__*/function (_Button) {
+    inheritsLoose(SeekToLive, _Button);
 
     /**
      * Creates an instance of this class.
   SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live';
   Component.registerComponent('SeekToLive', SeekToLive);
 
+  /**
+   * Keep a number between a min and a max value
+   *
+   * @param {number} number
+   *        The number to clamp
+   *
+   * @param {number} min
+   *        The minimum value
+   * @param {number} max
+   *        The maximum value
+   *
+   * @return {number}
+   *         the clamped number
+   */
+  var clamp = function clamp(number, min, max) {
+    number = Number(number);
+    return Math.min(max, Math.max(min, isNaN(number) ? min : number));
+  };
+
   /**
    * The base functionality for a slider. Can be vertical or horizontal.
    * For instance the volume bar or the seek bar on a video is a slider.
    * @extends Component
    */
 
-  var Slider =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(Slider, _Component);
+  var Slider = /*#__PURE__*/function (_Component) {
+    inheritsLoose(Slider, _Component);
 
     /**
     * Create an instance of this class
     ;
 
     _proto.update = function update() {
+      var _this2 = this;
+
       // In VolumeBar init we have a setTimeout for update that pops and update
       // to the end of the execution stack. The player is destroyed before then
       // update will cause an error
-      if (!this.el_) {
-        return;
-      } // If scrubbing, we could use a cached value to make the handle keep up
-      // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but
-      // some flash players are slow, so we might want to utilize this later.
-      // var progress =  (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
-
-
-      var progress = this.getPercent();
-      var bar = this.bar; // If there's no bar...
-
-      if (!bar) {
+      // If there's no bar...
+      if (!this.el_ || !this.bar) {
         return;
-      } // Protect against no duration and other division issues
-
+      } // clamp progress between 0 and 1
+      // and only round to four decimal places, as we round to two below
 
-      if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) {
-        progress = 0;
-      } // Convert to a percentage for setting
 
+      var progress = this.getProgress();
 
-      var percentage = (progress * 100).toFixed(2) + '%';
-      var style = bar.el().style; // Set the new bar width or height
-
-      var sizeKey = this.vertical() ? 'height' : 'width';
-
-      if (style[sizeKey] !== percentage) {
-        style[sizeKey] = percentage;
+      if (progress === this.progress_) {
+        return progress;
       }
 
+      this.progress_ = progress;
+      this.requestAnimationFrame(function () {
+        // Set the new bar width or height
+        var sizeKey = _this2.vertical() ? 'height' : 'width'; // Convert to a percentage for css value
+
+        _this2.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%';
+      });
       return progress;
     }
+    /**
+     * Get the percentage of the bar that should be filled
+     * but clamped and rounded.
+     *
+     * @return {number}
+     *         percentage filled that the slider is
+     */
+    ;
+
+    _proto.getProgress = function getProgress() {
+      return Number(clamp(this.getPercent(), 0, 1).toFixed(4));
+    }
     /**
      * Calculate distance for slider
      *
 
   Component.registerComponent('Slider', Slider);
 
+  var percentify = function percentify(time, end) {
+    return clamp(time / end * 100, 0, 100).toFixed(2) + '%';
+  };
   /**
    * Shows loading progress
    *
    * @extends Component
    */
 
-  var LoadProgressBar =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(LoadProgressBar, _Component);
+
+  var LoadProgressBar = /*#__PURE__*/function (_Component) {
+    inheritsLoose(LoadProgressBar, _Component);
 
     /**
      * Creates an instance of this class.
 
     var _proto = LoadProgressBar.prototype;
 
-    _proto.createEl = function createEl() {
-      return _Component.prototype.createEl.call(this, 'div', {
-        className: 'vjs-load-progress',
-        innerHTML: "<span class=\"vjs-control-text\"><span>" + this.localize('Loaded') + "</span>: <span class=\"vjs-control-text-loaded-percentage\">0%</span></span>"
+    _proto.createEl = function createEl$1() {
+      var el = _Component.prototype.createEl.call(this, 'div', {
+        className: 'vjs-load-progress'
+      });
+
+      var wrapper = createEl('span', {
+        className: 'vjs-control-text'
+      });
+      var loadedText = createEl('span', {
+        textContent: this.localize('Loaded')
       });
+      var separator = document.createTextNode(': ');
+      this.percentageEl_ = createEl('span', {
+        className: 'vjs-control-text-loaded-percentage',
+        textContent: '0%'
+      });
+      el.appendChild(wrapper);
+      wrapper.appendChild(loadedText);
+      wrapper.appendChild(separator);
+      wrapper.appendChild(this.percentageEl_);
+      return el;
     };
 
     _proto.dispose = function dispose() {
       this.partEls_ = null;
+      this.percentageEl_ = null;
 
       _Component.prototype.dispose.call(this);
     }
     ;
 
     _proto.update = function update(event) {
-      var liveTracker = this.player_.liveTracker;
-      var buffered = this.player_.buffered();
-      var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration();
-      var bufferedEnd = this.player_.bufferedEnd();
-      var children = this.partEls_;
-      var controlTextPercentage = this.$('.vjs-control-text-loaded-percentage'); // get the percent width of a time compared to the total end
+      var _this2 = this;
 
-      var percentify = function percentify(time, end, rounded) {
-        // no NaN
-        var percent = time / end || 0;
-        percent = (percent >= 1 ? 1 : percent) * 100;
+      this.requestAnimationFrame(function () {
+        var liveTracker = _this2.player_.liveTracker;
 
-        if (rounded) {
-          percent = percent.toFixed(2);
-        }
+        var buffered = _this2.player_.buffered();
 
-        return percent + '%';
-      }; // update the width of the progress bar
+        var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : _this2.player_.duration();
 
+        var bufferedEnd = _this2.player_.bufferedEnd();
 
-      this.el_.style.width = percentify(bufferedEnd, duration); // update the control-text
+        var children = _this2.partEls_;
+        var percent = percentify(bufferedEnd, duration);
 
-      textContent(controlTextPercentage, percentify(bufferedEnd, duration, true)); // add child elements to represent the individual buffered time ranges
+        if (_this2.percent_ !== percent) {
+          // update the width of the progress bar
+          _this2.el_.style.width = percent; // update the control-text
 
-      for (var i = 0; i < buffered.length; i++) {
-        var start = buffered.start(i);
-        var end = buffered.end(i);
-        var part = children[i];
+          textContent(_this2.percentageEl_, percent);
+          _this2.percent_ = percent;
+        } // add child elements to represent the individual buffered time ranges
 
-        if (!part) {
-          part = this.el_.appendChild(createEl());
-          children[i] = part;
-        } // set the percent based on the width of the progress bar (bufferedEnd)
 
+        for (var i = 0; i < buffered.length; i++) {
+          var start = buffered.start(i);
+          var end = buffered.end(i);
+          var part = children[i];
 
-        part.style.left = percentify(start, bufferedEnd);
-        part.style.width = percentify(end - start, bufferedEnd);
-      } // remove unused buffered range elements
+          if (!part) {
+            part = _this2.el_.appendChild(createEl());
+            children[i] = part;
+          } //  only update if changed
 
 
-      for (var _i = children.length; _i > buffered.length; _i--) {
-        this.el_.removeChild(children[_i - 1]);
-      }
+          if (part.dataset.start === start && part.dataset.end === end) {
+            continue;
+          }
 
-      children.length = buffered.length;
+          part.dataset.start = start;
+          part.dataset.end = end; // set the percent based on the width of the progress bar (bufferedEnd)
+
+          part.style.left = percentify(start, bufferedEnd);
+          part.style.width = percentify(end - start, bufferedEnd);
+        } // remove unused buffered range elements
+
+
+        for (var _i = children.length; _i > buffered.length; _i--) {
+          _this2.el_.removeChild(children[_i - 1]);
+        }
+
+        children.length = buffered.length;
+      });
     };
 
     return LoadProgressBar;
    * @extends Component
    */
 
-  var TimeTooltip =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(TimeTooltip, _Component);
+  var TimeTooltip = /*#__PURE__*/function (_Component) {
+    inheritsLoose(TimeTooltip, _Component);
 
     /**
      * Creates an instance of this class.
       var _this;
 
       _this = _Component.call(this, player, options) || this;
-      _this.update = throttle(bind(_assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
+      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
       return _this;
     }
     /**
    * @extends Component
    */
 
-  var PlayProgressBar =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(PlayProgressBar, _Component);
+  var PlayProgressBar = /*#__PURE__*/function (_Component) {
+    inheritsLoose(PlayProgressBar, _Component);
 
     /**
      * Creates an instance of this class.
       var _this;
 
       _this = _Component.call(this, player, options) || this;
-      _this.update = throttle(bind(_assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
+      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
       return _this;
     }
     /**
    * @extends Component
    */
 
-  var MouseTimeDisplay =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(MouseTimeDisplay, _Component);
+  var MouseTimeDisplay = /*#__PURE__*/function (_Component) {
+    inheritsLoose(MouseTimeDisplay, _Component);
 
     /**
      * Creates an instance of this class.
       var _this;
 
       _this = _Component.call(this, player, options) || this;
-      _this.update = throttle(bind(_assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
+      _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL);
       return _this;
     }
     /**
 
   var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline.
 
-  var PAGE_KEY_MULTIPLIER = 12; // The interval at which the bar should update as it progresses.
-
-  var UPDATE_REFRESH_INTERVAL$1 = 30;
+  var PAGE_KEY_MULTIPLIER = 12;
   /**
    * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
    * as its `bar`.
    * @extends Slider
    */
 
-  var SeekBar =
-  /*#__PURE__*/
-  function (_Slider) {
-    _inheritsLoose(SeekBar, _Slider);
+  var SeekBar = /*#__PURE__*/function (_Slider) {
+    inheritsLoose(SeekBar, _Slider);
 
     /**
      * Creates an instance of this class.
     var _proto = SeekBar.prototype;
 
     _proto.setEventHandlers_ = function setEventHandlers_() {
-      this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL$1);
-      this.on(this.player_, 'timeupdate', this.update);
-      this.on(this.player_, 'ended', this.handleEnded);
-      this.on(this.player_, 'durationchange', this.update);
+      this.update_ = bind(this, this.update);
+      this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL);
+      this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
 
       if (this.player_.liveTracker) {
         this.on(this.player_.liveTracker, 'liveedgechange', this.update);
       } else {
         this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP
 
-        this.requestAnimationFrame(this.update);
+        this.update();
       }
     };
 
     _proto.enableInterval_ = function enableInterval_() {
-      var _this2 = this;
+      if (this.updateInterval) {
+        return;
+      }
 
-      this.clearInterval(this.updateInterval);
-      this.updateInterval = this.setInterval(function () {
-        _this2.requestAnimationFrame(_this2.update);
-      }, UPDATE_REFRESH_INTERVAL$1);
+      this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL);
     };
 
     _proto.disableInterval_ = function disableInterval_(e) {
         return;
       }
 
+      if (!this.updateInterval) {
+        return;
+      }
+
       this.clearInterval(this.updateInterval);
+      this.updateInterval = null;
     }
     /**
      * Create the `Component`'s DOM element
      * This function updates the play progress bar and accessibility
      * attributes to whatever is passed in.
      *
-     * @param {number} currentTime
-     *        The currentTime value that should be used for accessibility
-     *
-     * @param {number} percent
-     *        The percentage as a decimal that the bar should be filled from 0-1.
-     *
-     * @private
-     */
-    ;
-
-    _proto.update_ = function update_(currentTime, percent) {
-      var liveTracker = this.player_.liveTracker;
-      var duration = this.player_.duration();
-
-      if (liveTracker && liveTracker.isLive()) {
-        duration = this.player_.liveTracker.liveCurrentTime();
-      } // machine readable value of progress bar (percentage complete)
-
-
-      this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); // human readable value of progress bar (time complete)
-
-      this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}')); // Update the `PlayProgressBar`.
-
-      if (this.bar) {
-        this.bar.update(getBoundingClientRect(this.el_), percent);
-      }
-    }
-    /**
-     * Update the seek bar's UI.
-     *
      * @param {EventTarget~Event} [event]
      *        The `timeupdate` or `ended` event that caused this to run.
      *
     ;
 
     _proto.update = function update(event) {
-      // if the offsetParent is null, then this element is hidden, in which case
-      // we don't need to update it.
-      if (this.el().offsetParent === null) {
-        return;
-      }
+      var _this2 = this;
 
       var percent = _Slider.prototype.update.call(this);
 
-      this.update_(this.getCurrentTime_(), percent);
+      this.requestAnimationFrame(function () {
+        var currentTime = _this2.player_.ended() ? _this2.player_.duration() : _this2.getCurrentTime_();
+        var liveTracker = _this2.player_.liveTracker;
+
+        var duration = _this2.player_.duration();
+
+        if (liveTracker && liveTracker.isLive()) {
+          duration = _this2.player_.liveTracker.liveCurrentTime();
+        }
+
+        if (_this2.percent_ !== percent) {
+          // machine readable value of progress bar (percentage complete)
+          _this2.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
+
+          _this2.percent_ = percent;
+        }
+
+        if (_this2.currentTime_ !== currentTime || _this2.duration_ !== duration) {
+          // human readable value of progress bar (time complete)
+          _this2.el_.setAttribute('aria-valuetext', _this2.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}'));
+
+          _this2.currentTime_ = currentTime;
+          _this2.duration_ = duration;
+        } // update the progress bar time tooltip with the current time
+
+
+        if (_this2.bar) {
+          _this2.bar.update(getBoundingClientRect(_this2.el()), _this2.getProgress());
+        }
+      });
       return percent;
     }
     /**
     _proto.getCurrentTime_ = function getCurrentTime_() {
       return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
     }
-    /**
-     * We want the seek bar to be full on ended
-     * no matter what the actual internal values are. so we force it.
-     *
-     * @param {EventTarget~Event} [event]
-     *        The `timeupdate` or `ended` event that caused this to run.
-     *
-     * @listens Player#ended
-     */
-    ;
-
-    _proto.handleEnded = function handleEnded(event) {
-      this.update_(this.player_.duration(), 1);
-    }
     /**
      * Get the percentage of media played so far.
      *
         percent = currentTime / this.player_.duration();
       }
 
-      return percent >= 1 ? 1 : percent || 0;
+      return percent;
     }
     /**
      * Handle mouse down on seek bar
 
       if (this.videoWasPlaying) {
         silencePromise(this.player_.play());
+      } else {
+        // We're done seeking and the time has changed.
+        // If the player is paused, make sure we display the correct time on the seek bar.
+        this.update_();
       }
     }
     /**
    * @extends Component
    */
 
-  var ProgressControl =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(ProgressControl, _Component);
+  var ProgressControl = /*#__PURE__*/function (_Component) {
+    inheritsLoose(ProgressControl, _Component);
 
     /**
      * Creates an instance of this class.
       var _this;
 
       _this = _Component.call(this, player, options) || this;
-      _this.handleMouseMove = throttle(bind(_assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
-      _this.throttledHandleMouseSeek = throttle(bind(_assertThisInitialized(_this), _this.handleMouseSeek), UPDATE_REFRESH_INTERVAL);
+      _this.handleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
+      _this.throttledHandleMouseSeek = throttle(bind(assertThisInitialized(_this), _this.handleMouseSeek), UPDATE_REFRESH_INTERVAL);
 
       _this.enable();
 
     _proto.handleMouseMove = function handleMouseMove(event) {
       var seekBar = this.getChild('seekBar');
 
-      if (seekBar) {
-        var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
-        var seekBarEl = seekBar.el();
-        var seekBarRect = getBoundingClientRect(seekBarEl);
-        var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means
-        // that it's possible to trigger this behavior outside the boundaries of
-        // the `SeekBar`. This ensures we stay within it at all times.
+      if (!seekBar) {
+        return;
+      }
 
-        if (seekBarPoint > 1) {
-          seekBarPoint = 1;
-        } else if (seekBarPoint < 0) {
-          seekBarPoint = 0;
-        }
+      var playProgressBar = seekBar.getChild('playProgressBar');
+      var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
 
-        if (mouseTimeDisplay) {
-          mouseTimeDisplay.update(seekBarRect, seekBarPoint);
-        }
+      if (!playProgressBar && !mouseTimeDisplay) {
+        return;
+      }
+
+      var seekBarEl = seekBar.el();
+      var seekBarRect = getBoundingClientRect(seekBarEl);
+      var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means
+      // that it's possible to trigger this behavior outside the boundaries of
+      // the `SeekBar`. This ensures we stay within it at all times.
+
+      seekBarPoint = clamp(0, 1, seekBarPoint);
+
+      if (mouseTimeDisplay) {
+        mouseTimeDisplay.update(seekBarRect, seekBarPoint);
+      }
+
+      if (playProgressBar) {
+        playProgressBar.update(seekBarRect, seekBar.getProgress());
       }
     }
     /**
    * @extends Button
    */
 
-  var PictureInPictureToggle =
-  /*#__PURE__*/
-  function (_Button) {
-    _inheritsLoose(PictureInPictureToggle, _Button);
+  var PictureInPictureToggle = /*#__PURE__*/function (_Button) {
+    inheritsLoose(PictureInPictureToggle, _Button);
 
     /**
      * Creates an instance of this class.
    * @extends Button
    */
 
-  var FullscreenToggle =
-  /*#__PURE__*/
-  function (_Button) {
-    _inheritsLoose(FullscreenToggle, _Button);
+  var FullscreenToggle = /*#__PURE__*/function (_Button) {
+    inheritsLoose(FullscreenToggle, _Button);
 
     /**
      * Creates an instance of this class.
    * @extends Component
    */
 
-  var VolumeLevel =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(VolumeLevel, _Component);
+  var VolumeLevel = /*#__PURE__*/function (_Component) {
+    inheritsLoose(VolumeLevel, _Component);
 
     function VolumeLevel() {
       return _Component.apply(this, arguments) || this;
    * @extends Slider
    */
 
-  var VolumeBar =
-  /*#__PURE__*/
-  function (_Slider) {
-    _inheritsLoose(VolumeBar, _Slider);
+  var VolumeBar = /*#__PURE__*/function (_Slider) {
+    inheritsLoose(VolumeBar, _Slider);
 
     /**
      * Creates an instance of this class.
    * @extends Component
    */
 
-  var VolumeControl =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(VolumeControl, _Component);
+  var VolumeControl = /*#__PURE__*/function (_Component) {
+    inheritsLoose(VolumeControl, _Component);
 
     /**
      * Creates an instance of this class.
 
       _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing
 
-      checkVolumeSupport(_assertThisInitialized(_this), player);
-      _this.throttledHandleMouseMove = throttle(bind(_assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
+      checkVolumeSupport(assertThisInitialized(_this), player);
+      _this.throttledHandleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL);
 
       _this.on('mousedown', _this.handleMouseDown);
 
    * @extends Button
    */
 
-  var MuteToggle =
-  /*#__PURE__*/
-  function (_Button) {
-    _inheritsLoose(MuteToggle, _Button);
+  var MuteToggle = /*#__PURE__*/function (_Button) {
+    inheritsLoose(MuteToggle, _Button);
 
     /**
      * Creates an instance of this class.
 
       _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing
 
-      checkMuteSupport(_assertThisInitialized(_this), player);
+      checkMuteSupport(assertThisInitialized(_this), player);
 
       _this.on(player, ['loadstart', 'volumechange'], _this.update);
 
    * @extends Component
    */
 
-  var VolumePanel =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(VolumePanel, _Component);
+  var VolumePanel = /*#__PURE__*/function (_Component) {
+    inheritsLoose(VolumePanel, _Component);
 
     /**
      * Creates an instance of this class.
    * @extends Component
    */
 
-  var Menu =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(Menu, _Component);
+  var Menu = /*#__PURE__*/function (_Component) {
+    inheritsLoose(Menu, _Component);
 
     /**
      * Create an instance of this class.
       _this.on('keydown', _this.handleKeyDown); // All the menu item instances share the same blur handler provided by the menu container.
 
 
-      _this.boundHandleBlur_ = bind(_assertThisInitialized(_this), _this.handleBlur);
-      _this.boundHandleTapClick_ = bind(_assertThisInitialized(_this), _this.handleTapClick);
+      _this.boundHandleBlur_ = bind(assertThisInitialized(_this), _this.handleBlur);
+      _this.boundHandleTapClick_ = bind(assertThisInitialized(_this), _this.handleTapClick);
       return _this;
     }
     /**
    * @extends Component
    */
 
-  var MenuButton =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(MenuButton, _Component);
+  var MenuButton = /*#__PURE__*/function (_Component) {
+    inheritsLoose(MenuButton, _Component);
 
     /**
      * Creates an instance of this class.
 
         _this.menu.show();
 
-        on(document, 'keyup', bind(_assertThisInitialized(_this), _this.handleMenuKeyUp));
+        on(document, 'keyup', bind(assertThisInitialized(_this), _this.handleMenuKeyUp));
       });
 
       _this.on('mouseleave', _this.handleMouseLeave);
    * @extends MenuButton
    */
 
-  var TrackButton =
-  /*#__PURE__*/
-  function (_MenuButton) {
-    _inheritsLoose(TrackButton, _MenuButton);
+  var TrackButton = /*#__PURE__*/function (_MenuButton) {
+    inheritsLoose(TrackButton, _MenuButton);
 
     /**
      * Creates an instance of this class.
       }
 
       if (!tracks) {
-        return _assertThisInitialized(_this);
+        return assertThisInitialized(_this);
       }
 
-      var updateHandler = bind(_assertThisInitialized(_this), _this.update);
+      var updateHandler = bind(assertThisInitialized(_this), _this.update);
       tracks.addEventListener('removetrack', updateHandler);
       tracks.addEventListener('addtrack', updateHandler);
 
    * @extends ClickableComponent
    */
 
-  var MenuItem =
-  /*#__PURE__*/
-  function (_ClickableComponent) {
-    _inheritsLoose(MenuItem, _ClickableComponent);
+  var MenuItem = /*#__PURE__*/function (_ClickableComponent) {
+    inheritsLoose(MenuItem, _ClickableComponent);
 
     /**
      * Creates an instance of the this class.
    * @extends MenuItem
    */
 
-  var TextTrackMenuItem =
-  /*#__PURE__*/
-  function (_MenuItem) {
-    _inheritsLoose(TextTrackMenuItem, _MenuItem);
+  var TextTrackMenuItem = /*#__PURE__*/function (_MenuItem) {
+    inheritsLoose(TextTrackMenuItem, _MenuItem);
 
     /**
      * Creates an instance of this class.
           args[_key] = arguments[_key];
         }
 
-        _this.handleTracksChange.apply(_assertThisInitialized(_this), args);
+        _this.handleTracksChange.apply(assertThisInitialized(_this), args);
       };
 
       var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
           args[_key2] = arguments[_key2];
         }
 
-        _this.handleSelectedLanguageChange.apply(_assertThisInitialized(_this), args);
+        _this.handleSelectedLanguageChange.apply(assertThisInitialized(_this), args);
       };
 
       player.on(['loadstart', 'texttrackchange'], changeHandler);
         var event;
 
         _this.on(['tap', 'click'], function () {
-          if (typeof window$1.Event !== 'object') {
+          if (typeof window$3.Event !== 'object') {
             // Android 2.3 throws an Illegal Constructor error for window.Event
             try {
-              event = new window$1.Event('change');
+              event = new window$3.Event('change');
             } catch (err) {// continue regardless of error
             }
           }
    * @extends TextTrackMenuItem
    */
 
-  var OffTextTrackMenuItem =
-  /*#__PURE__*/
-  function (_TextTrackMenuItem) {
-    _inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem);
+  var OffTextTrackMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) {
+    inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem);
 
     /**
      * Creates an instance of this class.
    * @extends MenuButton
    */
 
-  var TextTrackButton =
-  /*#__PURE__*/
-  function (_TrackButton) {
-    _inheritsLoose(TextTrackButton, _TrackButton);
+  var TextTrackButton = /*#__PURE__*/function (_TrackButton) {
+    inheritsLoose(TextTrackButton, _TrackButton);
 
     /**
      * Creates an instance of this class.
    * @extends MenuItem
    */
 
-  var ChaptersTrackMenuItem =
-  /*#__PURE__*/
-  function (_MenuItem) {
-    _inheritsLoose(ChaptersTrackMenuItem, _MenuItem);
+  var ChaptersTrackMenuItem = /*#__PURE__*/function (_MenuItem) {
+    inheritsLoose(ChaptersTrackMenuItem, _MenuItem);
 
     /**
      * Creates an instance of this class.
       _this = _MenuItem.call(this, player, options) || this;
       _this.track = track;
       _this.cue = cue;
-      track.addEventListener('cuechange', bind(_assertThisInitialized(_this), _this.update));
+      track.addEventListener('cuechange', bind(assertThisInitialized(_this), _this.update));
       return _this;
     }
     /**
    * @extends TextTrackButton
    */
 
-  var ChaptersButton =
-  /*#__PURE__*/
-  function (_TextTrackButton) {
-    _inheritsLoose(ChaptersButton, _TextTrackButton);
+  var ChaptersButton = /*#__PURE__*/function (_TextTrackButton) {
+    inheritsLoose(ChaptersButton, _TextTrackButton);
 
     /**
      * Creates an instance of this class.
    * @extends TextTrackButton
    */
 
-  var DescriptionsButton =
-  /*#__PURE__*/
-  function (_TextTrackButton) {
-    _inheritsLoose(DescriptionsButton, _TextTrackButton);
+  var DescriptionsButton = /*#__PURE__*/function (_TextTrackButton) {
+    inheritsLoose(DescriptionsButton, _TextTrackButton);
 
     /**
      * Creates an instance of this class.
 
       _this = _TextTrackButton.call(this, player, options, ready) || this;
       var tracks = player.textTracks();
-      var changeHandler = bind(_assertThisInitialized(_this), _this.handleTracksChange);
+      var changeHandler = bind(assertThisInitialized(_this), _this.handleTracksChange);
       tracks.addEventListener('change', changeHandler);
 
       _this.on('dispose', function () {
    * @extends TextTrackButton
    */
 
-  var SubtitlesButton =
-  /*#__PURE__*/
-  function (_TextTrackButton) {
-    _inheritsLoose(SubtitlesButton, _TextTrackButton);
+  var SubtitlesButton = /*#__PURE__*/function (_TextTrackButton) {
+    inheritsLoose(SubtitlesButton, _TextTrackButton);
 
     /**
      * Creates an instance of this class.
    * @extends TextTrackMenuItem
    */
 
-  var CaptionSettingsMenuItem =
-  /*#__PURE__*/
-  function (_TextTrackMenuItem) {
-    _inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem);
+  var CaptionSettingsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) {
+    inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem);
 
     /**
      * Creates an instance of this class.
    * @extends TextTrackButton
    */
 
-  var CaptionsButton =
-  /*#__PURE__*/
-  function (_TextTrackButton) {
-    _inheritsLoose(CaptionsButton, _TextTrackButton);
+  var CaptionsButton = /*#__PURE__*/function (_TextTrackButton) {
+    inheritsLoose(CaptionsButton, _TextTrackButton);
 
     /**
      * Creates an instance of this class.
    * @extends TextTrackMenuItem
    */
 
-  var SubsCapsMenuItem =
-  /*#__PURE__*/
-  function (_TextTrackMenuItem) {
-    _inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem);
+  var SubsCapsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) {
+    inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem);
 
     function SubsCapsMenuItem() {
       return _TextTrackMenuItem.apply(this, arguments) || this;
    * @extends TextTrackButton
    */
 
-  var SubsCapsButton =
-  /*#__PURE__*/
-  function (_TextTrackButton) {
-    _inheritsLoose(SubsCapsButton, _TextTrackButton);
+  var SubsCapsButton = /*#__PURE__*/function (_TextTrackButton) {
+    inheritsLoose(SubsCapsButton, _TextTrackButton);
 
     function SubsCapsButton(player, options) {
       var _this;
    * @extends MenuItem
    */
 
-  var AudioTrackMenuItem =
-  /*#__PURE__*/
-  function (_MenuItem) {
-    _inheritsLoose(AudioTrackMenuItem, _MenuItem);
+  var AudioTrackMenuItem = /*#__PURE__*/function (_MenuItem) {
+    inheritsLoose(AudioTrackMenuItem, _MenuItem);
 
     /**
      * Creates an instance of this class.
           args[_key] = arguments[_key];
         }
 
-        _this.handleTracksChange.apply(_assertThisInitialized(_this), args);
+        _this.handleTracksChange.apply(assertThisInitialized(_this), args);
       };
 
       tracks.addEventListener('change', changeHandler);
    * @extends TrackButton
    */
 
-  var AudioTrackButton =
-  /*#__PURE__*/
-  function (_TrackButton) {
-    _inheritsLoose(AudioTrackButton, _TrackButton);
+  var AudioTrackButton = /*#__PURE__*/function (_TrackButton) {
+    inheritsLoose(AudioTrackButton, _TrackButton);
 
     /**
      * Creates an instance of this class.
    * @extends MenuItem
    */
 
-  var PlaybackRateMenuItem =
-  /*#__PURE__*/
-  function (_MenuItem) {
-    _inheritsLoose(PlaybackRateMenuItem, _MenuItem);
+  var PlaybackRateMenuItem = /*#__PURE__*/function (_MenuItem) {
+    inheritsLoose(PlaybackRateMenuItem, _MenuItem);
 
     /**
      * Creates an instance of this class.
    * @extends MenuButton
    */
 
-  var PlaybackRateMenuButton =
-  /*#__PURE__*/
-  function (_MenuButton) {
-    _inheritsLoose(PlaybackRateMenuButton, _MenuButton);
+  var PlaybackRateMenuButton = /*#__PURE__*/function (_MenuButton) {
+    inheritsLoose(PlaybackRateMenuButton, _MenuButton);
 
     /**
      * Creates an instance of this class.
    * @extends Component
    */
 
-  var Spacer =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(Spacer, _Component);
+  var Spacer = /*#__PURE__*/function (_Component) {
+    inheritsLoose(Spacer, _Component);
 
     function Spacer() {
       return _Component.apply(this, arguments) || this;
    * @extends Spacer
    */
 
-  var CustomControlSpacer =
-  /*#__PURE__*/
-  function (_Spacer) {
-    _inheritsLoose(CustomControlSpacer, _Spacer);
+  var CustomControlSpacer = /*#__PURE__*/function (_Spacer) {
+    inheritsLoose(CustomControlSpacer, _Spacer);
 
     function CustomControlSpacer() {
       return _Spacer.apply(this, arguments) || this;
    * @extends Component
    */
 
-  var ControlBar =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(ControlBar, _Component);
+  var ControlBar = /*#__PURE__*/function (_Component) {
+    inheritsLoose(ControlBar, _Component);
 
     function ControlBar() {
       return _Component.apply(this, arguments) || this;
    * @extends ModalDialog
    */
 
-  var ErrorDisplay =
-  /*#__PURE__*/
-  function (_ModalDialog) {
-    _inheritsLoose(ErrorDisplay, _ModalDialog);
+  var ErrorDisplay = /*#__PURE__*/function (_ModalDialog) {
+    inheritsLoose(ErrorDisplay, _ModalDialog);
 
     /**
      * Creates an instance of this class.
    */
 
 
-  ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, {
+  ErrorDisplay.prototype.options_ = _extends_1({}, ModalDialog.prototype.options_, {
     pauseOnOpen: false,
     fillAlways: true,
     temporary: false,
    */
 
 
-  var TextTrackSettings =
-  /*#__PURE__*/
-  function (_ModalDialog) {
-    _inheritsLoose(TextTrackSettings, _ModalDialog);
+  var TextTrackSettings = /*#__PURE__*/function (_ModalDialog) {
+    inheritsLoose(TextTrackSettings, _ModalDialog);
 
     /**
      * Creates an instance of this class.
 
       options.temporary = false;
       _this = _ModalDialog.call(this, player, options) || this;
-      _this.updateDisplay = bind(_assertThisInitialized(_this), _this.updateDisplay); // fill the modal and pretend we have opened it
+      _this.updateDisplay = bind(assertThisInitialized(_this), _this.updateDisplay); // fill the modal and pretend we have opened it
 
       _this.fill();
 
       var values;
 
       try {
-        values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY));
+        values = JSON.parse(window$3.localStorage.getItem(LOCAL_STORAGE_KEY));
       } catch (err) {
         log.warn(err);
       }
 
       try {
         if (Object.keys(values).length) {
-          window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
+          window$3.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
         } else {
-          window$1.localStorage.removeItem(LOCAL_STORAGE_KEY);
+          window$3.localStorage.removeItem(LOCAL_STORAGE_KEY);
         }
       } catch (err) {
         log.warn(err);
    * @extends Component
    */
 
-  var ResizeManager =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(ResizeManager, _Component);
+  var ResizeManager = /*#__PURE__*/function (_Component) {
+    inheritsLoose(ResizeManager, _Component);
 
     /**
      * Create the ResizeManager.
     function ResizeManager(player, options) {
       var _this;
 
-      var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver
+      var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$3.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver
 
       if (options.ResizeObserver === null) {
         RESIZE_OBSERVER_AVAILABLE = false;
         reportTouchActivity: false
       }, options);
       _this = _Component.call(this, player, options_) || this;
-      _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver;
+      _this.ResizeObserver = options.ResizeObserver || window$3.ResizeObserver;
       _this.loadListener_ = null;
       _this.resizeObserver_ = null;
       _this.debouncedHandler_ = debounce(function () {
         _this.resizeHandler();
-      }, 100, false, _assertThisInitialized(_this));
+      }, 100, false, assertThisInitialized(_this));
 
       if (RESIZE_OBSERVER_AVAILABLE) {
         _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
     return arr.length % 2 !== 0 ? sortedList[mid] : (sortedList[mid - 1] + sortedList[mid]) / 2;
   };
 
+  var defaults = {
+    // Number of seconds of live window (seekableEnd - seekableStart) that
+    // a video needs to have before the liveui will be shown.
+    trackingThreshold: 30
+  };
   /* track when we are at the live edge, and other helpers for live playback */
 
-  var LiveTracker =
-  /*#__PURE__*/
-  function (_Component) {
-    _inheritsLoose(LiveTracker, _Component);
+  var LiveTracker = /*#__PURE__*/function (_Component) {
+    inheritsLoose(LiveTracker, _Component);
 
     function LiveTracker(player, options) {
       var _this;
 
       // LiveTracker does not need an element
-      var options_ = mergeOptions({
+      var options_ = mergeOptions(defaults, options, {
         createEl: false
-      }, options);
+      });
       _this = _Component.call(this, player, options_) || this;
 
       _this.re