From 20b692ea468a280e3edb4a9e7f97543b5499025d Mon Sep 17 00:00:00 2001 From: adityacp Date: Tue, 26 Jun 2018 13:13:22 +0530 Subject: Changes in views, models, forms, urls, file_utils - Add new view function to download course content - Add new attribute to Lesson model - Add new model methods to add course, module and lesson content - Add validation in forms to check for lesson video file format - Add functions in file_utils to add static files and templates to the zip file --- yaksh/file_utils.py | 26 ++++++++++++++++ yaksh/forms.py | 14 ++++++++- yaksh/models.py | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++-- yaksh/urls.py | 6 +++- yaksh/views.py | 37 ++++++++++++++++++++++- 5 files changed, 165 insertions(+), 5 deletions(-) diff --git a/yaksh/file_utils.py b/yaksh/file_utils.py index 6c3fd5d..6b0340f 100644 --- a/yaksh/file_utils.py +++ b/yaksh/file_utils.py @@ -3,6 +3,7 @@ import os import zipfile import tempfile import csv +from django.template import Context, Template def copy_files(file_paths): @@ -66,3 +67,28 @@ def is_csv(document): except (csv.Error, UnicodeDecodeError): return False, None return True, dialect + + +def write_static_files_to_zip(zipfile, course_name, current_dir): + relative_folders = ["css", "js", "images"] + static_files = {"js": ["bootstrap.js", "bootstrap.min.js", + "jquery-1.9.1.min.js", "video.js"], + "css": ["bootstrap.css", "bootstrap.min.css", + "video-js.css"], + "images": ["yaksh_banner.png"]} + for folder in relative_folders: + folder_path = os.sep.join((current_dir, "static", "yaksh", folder)) + for file in static_files[folder]: + file_path = os.sep.join((folder_path, file)) + zipfile.write(file_path, os.sep.join((course_name, "static", + folder, file))) + + +def write_templates_to_zip(zipfile, template_path, data, filename, filepath): + with open(template_path) as f: + template_data = f.read() + template = Template(template_data) + context = Context(data) + render = template.render(context) + zipfile.writestr(os.sep.join((filepath, "{0}.html".format(filename))), + render) diff --git a/yaksh/forms.py b/yaksh/forms.py index 41c9176..66076b1 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -324,7 +324,19 @@ class LessonForm(forms.ModelForm): class Meta: model = Lesson - exclude = ['completed_lessons', 'creator', 'html_data'] + exclude = ['creator', 'html_data'] + + def clean_video_file(self): + file = self.cleaned_data.get("video_file") + if file: + extension = file.name.split(".")[-1] + actual_extension = ["mp4", "ogv"] + if extension not in actual_extension: + raise forms.ValidationError( + "Please upload video files in {0} format".format( + ", ".join(actual_extension)) + ) + return file class LessonFileForm(forms.Form): diff --git a/yaksh/models.py b/yaksh/models.py index 5d17dba..b97859d 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -26,7 +26,10 @@ import zipfile import tempfile from textwrap import dedent from ast import literal_eval -from .file_utils import extract_files, delete_files +from .file_utils import ( + extract_files, delete_files, write_templates_to_zip, + write_static_files_to_zip +) from yaksh.code_server import ( submit, get_result as get_result_from_code_server ) @@ -124,7 +127,10 @@ def dict_to_yaml(dictionary): def get_file_dir(instance, filename): - upload_dir = instance.lesson.name.replace(" ", "_") + if isinstance(instance, LessonFile): + upload_dir = instance.lesson.name.replace(" ", "_") + else: + upload_dir = instance.name.replace(" ", "_") return os.sep.join((upload_dir, filename)) @@ -159,6 +165,11 @@ class Lesson(models.Model): # Activate/Deactivate Lesson active = models.BooleanField(default=True) + # A video file + video_file = models.FileField(upload_to=get_file_dir, default=None, + null=True, blank=True + ) + def __str__(self): return "{0}".format(self.name) @@ -182,6 +193,37 @@ class Lesson(models.Model): lesson_file_obj.file.save(file_name, django_file, save=True) return new_lesson + def remove_file(self): + if self.video_file: + file_path = self.video_file.path + if os.path.exists(file_path): + os.remove(file_path) + + def _add_lesson_to_zip(self, module, course, zip_file, path): + lesson_name = self.name.replace(" ", "_") + course_name = course.name.replace(" ", "_") + module_name = module.name.replace(" ", "_") + sub_folder_name = os.sep.join(( + course_name, module_name, lesson_name + )) + lesson_files = self.get_files() + if self.video_file: + video_file = os.sep.join((sub_folder_name, os.path.basename( + self.video_file.name))) + zip_file.write(self.video_file.path, video_file) + for lesson_file in lesson_files: + if os.path.exists(lesson_file.file.path): + filename = os.sep.join((sub_folder_name, os.path.basename( + lesson_file.file.name))) + zip_file.write(lesson_file.file.path, filename) + unit_file_path = os.sep.join(( + path, "templates", "yaksh", "unit.html" + )) + lesson_data = {"course": course, "module": module, + "lesson": self, "lesson_files": lesson_files} + write_templates_to_zip(zip_file, unit_file_path, lesson_data, + lesson_name, sub_folder_name) + ############################################################################# class LessonFile(models.Model): @@ -491,6 +533,10 @@ class LearningModule(models.Model): return [unit.quiz for unit in self.learning_unit.filter( type="quiz")] + def get_lesson_units(self): + return [unit.lesson for unit in self.learning_unit.filter( + type="lesson").order_by("order")] + def get_learning_units(self): return self.learning_unit.order_by("order") @@ -579,6 +625,20 @@ class LearningModule(models.Model): new_module.learning_unit.add(new_unit) return new_module + def _add_module_to_zip(self, course, zip_file, path): + module_name = self.name.replace(" ", "_") + course_name = course.name.replace(" ", "_") + folder_name = os.sep.join((course_name, module_name)) + lessons = self.get_lesson_units() + for lesson in lessons: + lesson._add_lesson_to_zip(self, course, zip_file, path) + module_file_path = os.sep.join(( + path, "templates", "yaksh", "module.html" + )) + module_data = {"course": course, "module": self, "lessons": lessons} + write_templates_to_zip(zip_file, module_file_path, module_data, + module_name, folder_name) + def __str__(self): return self.name @@ -819,6 +879,29 @@ class Course(models.Model): percentage = 0 return percentage + def is_student(self, user): + return user in self.students.all() + + def create_zip(self, zip_file, path): + course_name = self.name.replace(" ", "_") + modules = self.get_learning_modules() + file_path = os.sep.join((path, "templates", "yaksh", "index.html")) + write_static_files_to_zip(zip_file, course_name, path) + course_data = {"course": self, "modules": modules} + write_templates_to_zip(zip_file, file_path, course_data, + "index", course_name) + for module in modules: + module._add_module_to_zip(self, zip_file, path) + + def has_lessons(self): + modules = self.get_learning_modules() + status = False + for module in modules: + if module.get_lesson_units(): + status = True + break + return status + def __str__(self): return self.name diff --git a/yaksh/urls.py b/yaksh/urls.py index 1e1def6..6e6b8c1 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -200,5 +200,9 @@ urlpatterns = [ url(r'^manage/preview_questionpaper/(?P\d+)/$', views.preview_questionpaper, name="preview_questionpaper"), url(r'^manage/get_user_status/(?P\d+)/(?P\d+)/$', - views.get_user_data, name="get_user_data") + views.get_user_data, name="get_user_data"), + url(r'^manage/courses/download_course/(?P\d+)/$', + views.download_course, name="download_course"), + url(r'^download_course/(?P\d+)/$', + views.download_course, name="download_course"), ] diff --git a/yaksh/views.py b/yaksh/views.py index 3341aca..c5c5be1 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -2297,9 +2297,16 @@ def edit_lesson(request, lesson_id=None, course_id=None): context = {} if request.method == "POST": if "Save" in request.POST: - lesson_form = LessonForm(request.POST, instance=lesson) + lesson_form = LessonForm(request.POST, request.FILES, + instance=lesson) lesson_file_form = LessonFileForm(request.POST, request.FILES) lessonfiles = request.FILES.getlist('Lesson_files') + clear = request.POST.get("video_file-clear") + video_file = request.FILES.get("video_file") + if (clear or video_file) and lesson: + # Remove previous video file if new file is uploaded or + # if clear is selected + lesson.remove_file() if lesson_form.is_valid(): if lesson is None: lesson_form.instance.creator = user @@ -2314,6 +2321,7 @@ def edit_lesson(request, lesson_id=None, course_id=None): return my_redirect(redirect_url) else: context['lesson_form'] = lesson_form + context['error'] = lesson_form["video_file"].errors context['lesson_file_form'] = lesson_file_form if 'Delete' in request.POST: @@ -2808,3 +2816,30 @@ def get_user_data(request, course_id, student_id): context = Context(data) data = template.render(context) return HttpResponse(json.dumps({"user_data": data}), **response_kwargs) + + +@login_required +@email_verified +def download_course(request, course_id): + user = request.user + course = get_object_or_404(Course, pk=course_id) + if (not course.is_creator(user) and not course.is_teacher(user) and not + course.is_student(user)): + raise Http404("You are not allowed to download {0} course".format( + course.name)) + if not course.has_lessons(): + raise Http404("{0} course does not have any lessons".format( + course.name)) + file_name = string_io() + current_dir = os.path.dirname(__file__) + course_name = course.name.replace(" ", "_") + zip_file = zipfile.ZipFile(file_name, "w") + course.create_zip(zip_file, current_dir) + zip_file.close() + file_name.seek(0) + response = HttpResponse(content_type='application/zip') + response['Content-Disposition'] = 'attachment; filename={0}.zip'.format( + course_name + ) + response.write(file_name.read()) + return response -- cgit From 6ceea60ad3681aa2e6136b1294982438e6649932 Mon Sep 17 00:00:00 2001 From: adityacp Date: Tue, 26 Jun 2018 13:25:19 +0530 Subject: Changes in template, templatetags and static files - Add new templates index.html, module.html, unit.html for course download - Add links in student and moderator templates for course download - Add video js plugin for rendering uploaded lesson video file - Remove jquery-1.4.2 - Add template tag to get formatted names - Change student and moderator lesson template to view lessons files properly --- yaksh/static/yaksh/css/video-js.css | 1391 + yaksh/static/yaksh/js/jquery-1.4.2.min.js | 154 - yaksh/static/yaksh/js/video.js | 47810 ++++++++++++++++++++++++++++ yaksh/templates/manage.html | 2 +- yaksh/templates/yaksh/add_lesson.html | 28 +- yaksh/templates/yaksh/courses.html | 10 + yaksh/templates/yaksh/index.html | 69 + yaksh/templates/yaksh/module.html | 74 + yaksh/templates/yaksh/quizzes_user.html | 6 +- yaksh/templates/yaksh/show_video.html | 18 +- yaksh/templates/yaksh/unit.html | 82 + yaksh/templatetags/custom_filters.py | 5 + 12 files changed, 49474 insertions(+), 175 deletions(-) create mode 100644 yaksh/static/yaksh/css/video-js.css delete mode 100644 yaksh/static/yaksh/js/jquery-1.4.2.min.js create mode 100644 yaksh/static/yaksh/js/video.js create mode 100644 yaksh/templates/yaksh/index.html create mode 100644 yaksh/templates/yaksh/module.html create mode 100644 yaksh/templates/yaksh/unit.html diff --git a/yaksh/static/yaksh/css/video-js.css b/yaksh/static/yaksh/css/video-js.css new file mode 100644 index 0000000..e9d3c6d --- /dev/null +++ b/yaksh/static/yaksh/css/video-js.css @@ -0,0 +1,1391 @@ +.video-js .vjs-big-play-button .vjs-icon-placeholder:before, .vjs-button > .vjs-icon-placeholder:before, .video-js .vjs-modal-dialog, .vjs-modal-dialog .vjs-modal-dialog-content { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; } + +.video-js .vjs-big-play-button .vjs-icon-placeholder:before, .vjs-button > .vjs-icon-placeholder:before { + text-align: center; } + +@font-face { + font-family: VideoJS; + src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAABBIAAsAAAAAGoQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADsAAABUIIslek9TLzIAAAFEAAAAPgAAAFZRiV3RY21hcAAAAYQAAADQAAADIjn098ZnbHlmAAACVAAACv4AABEIAwnSw2hlYWQAAA1UAAAAKgAAADYUHzoRaGhlYQAADYAAAAAbAAAAJA4DByFobXR4AAANnAAAAA8AAACE4AAAAGxvY2EAAA2sAAAARAAAAEQ9NEHGbWF4cAAADfAAAAAfAAAAIAEyAIFuYW1lAAAOEAAAASUAAAIK1cf1oHBvc3QAAA84AAABDwAAAZ5AAl/0eJxjYGRgYOBiMGCwY2BycfMJYeDLSSzJY5BiYGGAAJA8MpsxJzM9kYEDxgPKsYBpDiBmg4gCACY7BUgAeJxjYGQ7xTiBgZWBgaWQ5RkDA8MvCM0cwxDOeI6BgYmBlZkBKwhIc01hcPjI+FGBHcRdyA4RZgQRAC4HCwEAAHic7dFprsIgAEXhg8U61XmeWcBb1FuQP4w7ZQXK5boMm3yclFDSANAHmuKviBBeBPQ8ymyo8w3jOh/5r2ui5nN6v8sYNJb3WMdeWRvLji0DhozKdxM6psyYs2DJijUbtuzYc+DIiTMXrty4k8oGLb+n0xCe37ekM7Z66j1DbUy3l6PpHnLfdLO5NdSBoQ4NdWSoY9ON54mhdqa/y1NDnRnq3FAXhro01JWhrg11Y6hbQ90Z6t5QD4Z6NNSToZ4N9WKoV0O9GerdUJORPqkhTd54nJ1YDXBU1RV+576/JBs2bPYPkrDZt5vsJrv53V/I5mclhGDCTwgGBQQSTEji4hCkYIAGd4TGIWFAhV0RQTpWmQp1xv6hA4OTOlNr2zFANbHUYbq2OtNCpViRqsk+e+7bTQAhzti8vPfuPffcc88959zznbcMMPjHD/KDDGEY0ABpYX384NhlomIYlo4JISGEY9mMh2FSidYiqkEUphtNYDSY/dXg9023l4DdxlqUl0chuZRhncJKrsCQHIwcGuwfnhMIzBnuH4Sym+1D2zaGjheXlhYfD238z80mKYMmvJ5XeOTzd8z9eujbMxJNhu4C9xPE/bCMiDuSNIWgkTQwBE55hLSAE7ZwhrHLnAHZOGV/kmBGTiNjZxzI77Hb7Hqjz68TjT6vh+5JT/cCIkqS0D6CqPf5jX4Qjdx5j6vlDfZM4aZFdbVXIxtOlJaP/WottMnH6CJQ3bTiue3PrY23HjnChtuamxwvvzFjxkPrNj3z0tG9T561HDYf6OgmRWvlY3JQHoQb8ltV2Yet7YfWctEjR1AtxS/cSX6U4alf6NJEBQ7YKg9wrXQKd0IeZCb2ux75Uhh1Un+Nz+9LTOE7PK777nN5xqdTneTBhCbx446mZrhnUkrCz2YhA9dSMxaG0SYmT8hi9ZPu1E94PJYQSH6LRmhxec7Q7ZeXntgQuVpbh+a4qWNsckVyTdn0P7o7DpgPW84+uRcq0BITflBikGdUjAZ9wYBVI3mtrNvr9kpg1UsaK6t3690aoorC1lg0GpMH2HAMtkZjsSi5Ig9ESVosOh7GQfLjKNLvKpMKkLSKNFAka710GdgSi8oDMSoNhqjkKBXTgn3swtaxyzGkUzIzae9RtLdWkSlZ1KDX6EzgllzV4NV4SoDFSOGD4+HCeQUF8wrZ5Hs8zIb5EaVxy8DYFTbMCJPnLIWZxugZE2NlivC0gc1qEQUR8jEKgZcAXeH18BiCgl5nlHh0CrjB4Hb5fX4gb0J7c9PuHVsfgkx2n/vTY/JV8kn8PGxf7faOZ8qX8JVByuIf4whk9sqXli2hvPJV9hrp0hY7l8r2x37ydaVsb4xvXv/47v2NjfCl8m5oRDJclFMoE1yk0Uh1Te4/m8lFXe9qBZD0EkheicebXvzI2PLCuoKCukLuhPIeKwaHPEouxw3kMqaIUXDQ1p0mip+MyCORSCQaoUsnY1VZ38nUTrG21WvVo4f1OsEJFhvSfAFwGfT8VHRMeAVUpwLOoLzjT/REIj3O3FhuURE+nERF+0pTId5Fyxv5sfwGyg4O+my4vZv0sZm7oeQlFZORiB+tG0MweVNraeitl7yxiPIHTk4/diVxs94o5lEYishB2iAtkchEnsActoEpx44Fo8XnsQMaA22BlqC20RmhBKzYojZyYaxg+JggMc4HHY2m+L9EkWSYljirOisrO7d3VorxzyZ6Vc4lJqITAu1b2wOBdrLElAP+bFc2eGaZFVbkmJktv5uT6Jlz5D/MnBFor6ig/JPnRViBsV3LNKGGqB1ChJ0tgQywlVLFJIuQgTFttwkiKxhyQdAZMdMYtSaoAewqfvXVYPAbDT6/1mez85YS8FSDywQ6NfAnef6FNEGMilnppyvn5rB6tTyq1pOceRWnp2WJEZFXHeX5oyoem1nTTgdqc4heDY7bOeKz63vnz+/dRx+s31Ht2JGanQ5seirfWJL9tjozU/12TnEjn5oux9OzU3ckGbBzBwNOyk69JykKH0n/0LM9A72tuwM3zQpIRu4AxiToseEpgPOmbROyFe9/X2yeUvoUsCyEvjcgs7fpWP3/aKlFN0+6HFUe6D9HFz/XPwBlN9tTqNyZjFJ8UO2RUT5/h4CptCctEyeisnOyXjALEp7dXKaQKf6O7IMnGjNNACRMLxqdYJX8eMLvmmd68D+ayBLyKKYZwYxDt/GNhzETDJ05Qxlyi3pi3/Z93ndYVSumgj0V/KkIFlO6+1K3fF2+3g0q+YtuSIf0bvmLqV09nnobI6hwcjIP8aPCKayjsF5JBY3LaKAeRLSyYB1h81oTwe9SlPMkXB7G0mfL9q71gaqqwPqu67QRKS1+ObTx+sbQy9QV2OQHEScGkdFBeT7v7qisqqrs6N52i78/R+6S0qQONVj26agOVoswCyQWIV5D86vH53bxNUeXV0K+XZaHv/nm/KsHhOvylwsWnJX/HE8l/4WCv5x+l5n08z6UU8bUMa3MBpSmM7F63AxntdC9eBCKEZW9Hr+ABNqtxgAQrSbMtmrW7lKQuoSgBhSrTazWVU2QAKWY8wiiuhqFmQgWJBgoXiuWIm42N7hqZbBsgXz52O5P5uSvaNgFGnOuvsRw8I8Laha91wMvDuxqWFheN7/8GVtTltdS83DQsXRmqc5ZtcJXEVrlV2doTWk5+Yunm71dG5f55m/qY0MjI93vv9/NfpxXV9sUXrxy2fbNy1or65cOlDRnOoKFeeXcbw42H/bNDT5Qs3flgs31gWC1lD1nfUV/X7NdCnSUdHY2e8afzfKsqZ5ZljfDqjLOmk3UebNXB+aHArPYDRs+/HDDxeT5DiP+sFg7OpRaVQMGBV89PpeBdj22hCE0Uub0UqwLrNWsG0cuyadgLXTeR5rbO4+3c/vl15cur2nRq+TXCQDcS3SO+s6ak+e5/eMS+1dw3btu3YG2tvFL8XdIZvdjdW6TO/4B7IdrZWVPmctm5/59AgsPItTSbCiIBr2OqIGzmu20SMKAS7yqwGBUfGfgjDYlLLDeF0SfcLB2LSx8flT+08/kzz6yOj96rft4rpTjdPQcmLd47uKibbDq7ZSz/XtbH2nN717Nd62rU+c8Icevvv7I09wA6WvjVcafb+FsbNG+ZQ80Rn6ZZsvrP7teP2dzTdoETvNhjCmsr8FID2sJ69VYvdUcxk4AzYRlKcaE38eXNRlfW9H1as9i6acLHp1XpuNB5K7DIvkX08y1ZYvh3KfWaiCzH+ztrSDmD7LuX73x/mJelB8Yj39t8nhNQJJ2CAthpoFGLsGgtSOCJooCGoaJAMTjSWHVZ08YAa1Fg9lPI5U6DOsGVjDasJeZZ+YyhfCwfOzCxlBA69M9XLXtza7H/rav+9Tjq5xNi0wpKQIRNO4Lrzz7yp5QVYM6Jd/oc1Uvn/mQhhuWh6ENXoS2YTZ8QT42bF5d/559zp5r0Uff2VnR2tdf2/WCOd2cO0Mw6qpWPnvxpV0nrt5fZd2yItc199GWe8vlNfNDq+CH/7yAAnB9hn7T4QO4c1g9ScxsZgmzntnE/IDGndtHMw69lFwoCnYsMGx+rBp8JSBqdLzBr9QRPq/PbhWMWFtQZp1xguy/haw3TEHm3TWAnxFWQQWgt7M5OV0lCz1VRYucpWliy7z6Zd4urwPIyeZQqli2Lgg7szJV09PysATbOQtYIrB2YzbkJYkGgJ0m4AjPUap1pvYu1K9qr97z0Yl3p332b2LYB78ncYIlRkau/8GObSsOlZancACE5d5ily+c2+7h5Yj4lqhVmXXB+iXLfvdqSgqfKtQvfHDV0OnvQR1qhw42XS/vkvsh/hXcrDFP0a+SJNIomEfD1nsrYGO+1bgTOJhM8Hv6ek+7vVglxuSRwoKn17S937bm6YJCeSSG0Op1n+7tE37tcZ/p7dsTv4EUrGpDbWueKigsLHhqTVsoEj+JU0kaSjnj9tz8/gryQWwJ9BcJXBC/7smO+I/IFURJetFPrdt5WcoL6DbEJaygI8CTHfQTjf40ofD+DwalTqIAAHicY2BkYGAA4uByr8R4fpuvDNzsDCBw7f/3LmSanREszsHABKIAKi0J7gAAeJxjYGRgYGcAARD5/z87IwMjAypQBAAtgwI4AHicY2BgYGAfYAwAOkQA4QAAAAAAAA4AaAB+AMwA4AECAUIBbAGYAcICGAJYArQC4AMwA7AD3gQwBJYE3AUkBWYFigYgBmYGtAbqB1gIEghYCG4IhHicY2BkYGBQZChlYGcAASYg5gJCBob/YD4DABfTAbQAeJxdkE1qg0AYhl8Tk9AIoVDaVSmzahcF87PMARLIMoFAl0ZHY1BHdBJIT9AT9AQ9RQ9Qeqy+yteNMzDzfM+88w0K4BY/cNAMB6N2bUaPPBLukybCLvleeAAPj8JD+hfhMV7hC3u4wxs7OO4NzQSZcI/8Ltwnfwi75E/hAR7wJTyk/xYeY49fYQ/PztM+jbTZ7LY6OWdBJdX/pqs6NYWa+zMxa13oKrA6Uoerqi/JwtpYxZXJ1coUVmeZUWVlTjq0/tHacjmdxuL90OR8O0UEDYMNdtiSEpz5XQGqzlm30kzUdAYFFOb8R7NOZk0q2lwAyz1i7oAr1xoXvrOgtYhZx8wY5KRV269JZ5yGpmzPTjQhvY9je6vEElPOuJP3mWKnP5M3V+YAAAB4nG2PyXLCMBBE3YCNDWEL2ffk7o8S8oCnkCVHC5C/jzBQlUP6IHVPzYyekl5y0iL5X5/ooY8BUmQYIkeBEca4wgRTzDDHAtdY4ga3uMM9HvCIJzzjBa94wzs+8ImvZNAq8TM+HqVkKxWlrQiOxjujQkNlEzyNzl6Z/cU2XF06at7U83VQyklLpEvSnuzsb+HAPnPfQVgaupa1Jlu4sPLsFblcitaz0dHU0ZF1qatjZ1+aTXYCmp6u0gSvWNPyHLtFZ+ZeXWVSaEkqs3T8S74WklbGbNNNq4LL4+CWKtZDv2cfX8l8aFbKFhEnJnJ+IULFpqwoQnNHlHaVQtPBl+ypmbSWdmyC61KS/AKZC3Y+AA==) format("woff"); + font-weight: normal; + font-style: normal; } + +.vjs-icon-play, .video-js .vjs-big-play-button .vjs-icon-placeholder:before, .video-js .vjs-play-control .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-play:before, .video-js .vjs-big-play-button .vjs-icon-placeholder:before, .video-js .vjs-play-control .vjs-icon-placeholder:before { + content: "\f101"; } + +.vjs-icon-play-circle { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-play-circle:before { + content: "\f102"; } + +.vjs-icon-pause, .video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-pause:before, .video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder:before { + content: "\f103"; } + +.vjs-icon-volume-mute, .video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-volume-mute:before, .video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder:before { + content: "\f104"; } + +.vjs-icon-volume-low, .video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-volume-low:before, .video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder:before { + content: "\f105"; } + +.vjs-icon-volume-mid, .video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-volume-mid:before, .video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder:before { + content: "\f106"; } + +.vjs-icon-volume-high, .video-js .vjs-mute-control .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-volume-high:before, .video-js .vjs-mute-control .vjs-icon-placeholder:before { + content: "\f107"; } + +.vjs-icon-fullscreen-enter, .video-js .vjs-fullscreen-control .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-fullscreen-enter:before, .video-js .vjs-fullscreen-control .vjs-icon-placeholder:before { + content: "\f108"; } + +.vjs-icon-fullscreen-exit, .video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-fullscreen-exit:before, .video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder:before { + content: "\f109"; } + +.vjs-icon-square { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-square:before { + content: "\f10a"; } + +.vjs-icon-spinner { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-spinner:before { + content: "\f10b"; } + +.vjs-icon-subtitles, .video-js .vjs-subtitles-button .vjs-icon-placeholder, .video-js .vjs-subs-caps-button .vjs-icon-placeholder, +.video-js.video-js:lang(en-GB) .vjs-subs-caps-button .vjs-icon-placeholder, +.video-js.video-js:lang(en-IE) .vjs-subs-caps-button .vjs-icon-placeholder, +.video-js.video-js:lang(en-AU) .vjs-subs-caps-button .vjs-icon-placeholder, +.video-js.video-js:lang(en-NZ) .vjs-subs-caps-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-subtitles:before, .video-js .vjs-subtitles-button .vjs-icon-placeholder:before, .video-js .vjs-subs-caps-button .vjs-icon-placeholder:before, + .video-js.video-js:lang(en-GB) .vjs-subs-caps-button .vjs-icon-placeholder:before, + .video-js.video-js:lang(en-IE) .vjs-subs-caps-button .vjs-icon-placeholder:before, + .video-js.video-js:lang(en-AU) .vjs-subs-caps-button .vjs-icon-placeholder:before, + .video-js.video-js:lang(en-NZ) .vjs-subs-caps-button .vjs-icon-placeholder:before { + content: "\f10c"; } + +.vjs-icon-captions, .video-js .vjs-captions-button .vjs-icon-placeholder, .video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder, +.video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-captions:before, .video-js .vjs-captions-button .vjs-icon-placeholder:before, .video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder:before, + .video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder:before { + content: "\f10d"; } + +.vjs-icon-chapters, .video-js .vjs-chapters-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-chapters:before, .video-js .vjs-chapters-button .vjs-icon-placeholder:before { + content: "\f10e"; } + +.vjs-icon-share { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-share:before { + content: "\f10f"; } + +.vjs-icon-cog { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-cog:before { + content: "\f110"; } + +.vjs-icon-circle, .video-js .vjs-play-progress, .video-js .vjs-volume-level { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-circle:before, .video-js .vjs-play-progress:before, .video-js .vjs-volume-level:before { + content: "\f111"; } + +.vjs-icon-circle-outline { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-circle-outline:before { + content: "\f112"; } + +.vjs-icon-circle-inner-circle { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-circle-inner-circle:before { + content: "\f113"; } + +.vjs-icon-hd { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-hd:before { + content: "\f114"; } + +.vjs-icon-cancel, .video-js .vjs-control.vjs-close-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-cancel:before, .video-js .vjs-control.vjs-close-button .vjs-icon-placeholder:before { + content: "\f115"; } + +.vjs-icon-replay, .video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-replay:before, .video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder:before { + content: "\f116"; } + +.vjs-icon-facebook { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-facebook:before { + content: "\f117"; } + +.vjs-icon-gplus { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-gplus:before { + content: "\f118"; } + +.vjs-icon-linkedin { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-linkedin:before { + content: "\f119"; } + +.vjs-icon-twitter { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-twitter:before { + content: "\f11a"; } + +.vjs-icon-tumblr { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-tumblr:before { + content: "\f11b"; } + +.vjs-icon-pinterest { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-pinterest:before { + content: "\f11c"; } + +.vjs-icon-audio-description, .video-js .vjs-descriptions-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-audio-description:before, .video-js .vjs-descriptions-button .vjs-icon-placeholder:before { + content: "\f11d"; } + +.vjs-icon-audio, .video-js .vjs-audio-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-audio:before, .video-js .vjs-audio-button .vjs-icon-placeholder:before { + content: "\f11e"; } + +.vjs-icon-next-item { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-next-item:before { + content: "\f11f"; } + +.vjs-icon-previous-item { + font-family: VideoJS; + font-weight: normal; + font-style: normal; } + .vjs-icon-previous-item:before { + content: "\f120"; } + +.video-js { + display: block; + vertical-align: top; + box-sizing: border-box; + color: #fff; + background-color: #000; + position: relative; + padding: 0; + font-size: 10px; + line-height: 1; + font-weight: normal; + font-style: normal; + font-family: Arial, Helvetica, sans-serif; + word-break: initial; } + .video-js:-moz-full-screen { + position: absolute; } + .video-js:-webkit-full-screen { + width: 100% !important; + height: 100% !important; } + +.video-js[tabindex="-1"] { + outline: none; } + +.video-js *, +.video-js *:before, +.video-js *:after { + box-sizing: inherit; } + +.video-js ul { + font-family: inherit; + font-size: inherit; + line-height: inherit; + list-style-position: outside; + margin-left: 0; + margin-right: 0; + margin-top: 0; + margin-bottom: 0; } + +.video-js.vjs-fluid, +.video-js.vjs-16-9, +.video-js.vjs-4-3 { + width: 100%; + max-width: 100%; + height: 0; } + +.video-js.vjs-16-9 { + padding-top: 56.25%; } + +.video-js.vjs-4-3 { + padding-top: 75%; } + +.video-js.vjs-fill { + width: 100%; + height: 100%; } + +.video-js .vjs-tech { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; } + +body.vjs-full-window { + padding: 0; + margin: 0; + height: 100%; } + +.vjs-full-window .video-js.vjs-fullscreen { + position: fixed; + overflow: hidden; + z-index: 1000; + left: 0; + top: 0; + bottom: 0; + right: 0; } + +.video-js.vjs-fullscreen { + width: 100% !important; + height: 100% !important; + padding-top: 0 !important; } + +.video-js.vjs-fullscreen.vjs-user-inactive { + cursor: none; } + +.vjs-hidden { + display: none !important; } + +.vjs-disabled { + opacity: 0.5; + cursor: default; } + +.video-js .vjs-offscreen { + height: 1px; + left: -9999px; + position: absolute; + top: 0; + width: 1px; } + +.vjs-lock-showing { + display: block !important; + opacity: 1; + visibility: visible; } + +.vjs-no-js { + padding: 20px; + color: #fff; + background-color: #000; + font-size: 18px; + font-family: Arial, Helvetica, sans-serif; + text-align: center; + width: 300px; + height: 150px; + margin: 0px auto; } + +.vjs-no-js a, +.vjs-no-js a:visited { + color: #66A8CC; } + +.video-js .vjs-big-play-button { + font-size: 3em; + line-height: 1.5em; + height: 1.5em; + width: 3em; + display: block; + position: absolute; + top: 10px; + left: 10px; + padding: 0; + cursor: pointer; + opacity: 1; + border: 0.06666em solid #fff; + background-color: #2B333F; + background-color: rgba(43, 51, 63, 0.7); + -webkit-border-radius: 0.3em; + -moz-border-radius: 0.3em; + border-radius: 0.3em; + -webkit-transition: all 0.4s; + -moz-transition: all 0.4s; + -ms-transition: all 0.4s; + -o-transition: all 0.4s; + transition: all 0.4s; } + +.vjs-big-play-centered .vjs-big-play-button { + top: 50%; + left: 50%; + margin-top: -0.75em; + margin-left: -1.5em; } + +.video-js:hover .vjs-big-play-button, +.video-js .vjs-big-play-button:focus { + border-color: #fff; + background-color: #73859f; + background-color: rgba(115, 133, 159, 0.5); + -webkit-transition: all 0s; + -moz-transition: all 0s; + -ms-transition: all 0s; + -o-transition: all 0s; + transition: all 0s; } + +.vjs-controls-disabled .vjs-big-play-button, +.vjs-has-started .vjs-big-play-button, +.vjs-using-native-controls .vjs-big-play-button, +.vjs-error .vjs-big-play-button { + display: none; } + +.vjs-has-started.vjs-paused.vjs-show-big-play-button-on-pause .vjs-big-play-button { + display: block; } + +.video-js button { + background: none; + border: none; + color: inherit; + display: inline-block; + font-size: inherit; + line-height: inherit; + text-transform: none; + text-decoration: none; + transition: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } + +.vjs-control .vjs-button { + width: 100%; + height: 100%; } + +.video-js .vjs-control.vjs-close-button { + cursor: pointer; + height: 3em; + position: absolute; + right: 0; + top: 0.5em; + z-index: 2; } + +.video-js .vjs-modal-dialog { + background: rgba(0, 0, 0, 0.8); + background: -webkit-linear-gradient(-90deg, rgba(0, 0, 0, 0.8), rgba(255, 255, 255, 0)); + background: linear-gradient(180deg, rgba(0, 0, 0, 0.8), rgba(255, 255, 255, 0)); + overflow: auto; } + +.video-js .vjs-modal-dialog > * { + box-sizing: border-box; } + +.vjs-modal-dialog .vjs-modal-dialog-content { + font-size: 1.2em; + line-height: 1.5; + padding: 20px 24px; + z-index: 1; } + +.vjs-menu-button { + cursor: pointer; } + +.vjs-menu-button.vjs-disabled { + cursor: default; } + +.vjs-workinghover .vjs-menu-button.vjs-disabled:hover .vjs-menu { + display: none; } + +.vjs-menu .vjs-menu-content { + display: block; + padding: 0; + margin: 0; + font-family: Arial, Helvetica, sans-serif; + overflow: auto; } + +.vjs-menu .vjs-menu-content > * { + box-sizing: border-box; } + +.vjs-scrubbing .vjs-control.vjs-menu-button:hover .vjs-menu { + display: none; } + +.vjs-menu li { + list-style: none; + margin: 0; + padding: 0.2em 0; + line-height: 1.4em; + font-size: 1.2em; + text-align: center; + text-transform: lowercase; } + +.vjs-menu li.vjs-menu-item:focus, +.vjs-menu li.vjs-menu-item:hover { + background-color: #73859f; + background-color: rgba(115, 133, 159, 0.5); } + +.vjs-menu li.vjs-selected, +.vjs-menu li.vjs-selected:focus, +.vjs-menu li.vjs-selected:hover { + background-color: #fff; + color: #2B333F; } + +.vjs-menu li.vjs-menu-title { + text-align: center; + text-transform: uppercase; + font-size: 1em; + line-height: 2em; + padding: 0; + margin: 0 0 0.3em 0; + font-weight: bold; + cursor: default; } + +.vjs-menu-button-popup .vjs-menu { + display: none; + position: absolute; + bottom: 0; + width: 10em; + left: -3em; + height: 0em; + margin-bottom: 1.5em; + border-top-color: rgba(43, 51, 63, 0.7); } + +.vjs-menu-button-popup .vjs-menu .vjs-menu-content { + background-color: #2B333F; + background-color: rgba(43, 51, 63, 0.7); + position: absolute; + width: 100%; + bottom: 1.5em; + max-height: 15em; } + +.vjs-workinghover .vjs-menu-button-popup:hover .vjs-menu, +.vjs-menu-button-popup .vjs-menu.vjs-lock-showing { + display: block; } + +.video-js .vjs-menu-button-inline { + -webkit-transition: all 0.4s; + -moz-transition: all 0.4s; + -ms-transition: all 0.4s; + -o-transition: all 0.4s; + transition: all 0.4s; + overflow: hidden; } + +.video-js .vjs-menu-button-inline:before { + width: 2.222222222em; } + +.video-js .vjs-menu-button-inline:hover, +.video-js .vjs-menu-button-inline:focus, +.video-js .vjs-menu-button-inline.vjs-slider-active, +.video-js.vjs-no-flex .vjs-menu-button-inline { + width: 12em; } + +.vjs-menu-button-inline .vjs-menu { + opacity: 0; + height: 100%; + width: auto; + position: absolute; + left: 4em; + top: 0; + padding: 0; + margin: 0; + -webkit-transition: all 0.4s; + -moz-transition: all 0.4s; + -ms-transition: all 0.4s; + -o-transition: all 0.4s; + transition: all 0.4s; } + +.vjs-menu-button-inline:hover .vjs-menu, +.vjs-menu-button-inline:focus .vjs-menu, +.vjs-menu-button-inline.vjs-slider-active .vjs-menu { + display: block; + opacity: 1; } + +.vjs-no-flex .vjs-menu-button-inline .vjs-menu { + display: block; + opacity: 1; + position: relative; + width: auto; } + +.vjs-no-flex .vjs-menu-button-inline:hover .vjs-menu, +.vjs-no-flex .vjs-menu-button-inline:focus .vjs-menu, +.vjs-no-flex .vjs-menu-button-inline.vjs-slider-active .vjs-menu { + width: auto; } + +.vjs-menu-button-inline .vjs-menu-content { + width: auto; + height: 100%; + margin: 0; + overflow: hidden; } + +.video-js .vjs-control-bar { + display: none; + width: 100%; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 3.0em; + background-color: #2B333F; + background-color: rgba(43, 51, 63, 0.7); } + +.vjs-has-started .vjs-control-bar { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + visibility: visible; + opacity: 1; + -webkit-transition: visibility 0.1s, opacity 0.1s; + -moz-transition: visibility 0.1s, opacity 0.1s; + -ms-transition: visibility 0.1s, opacity 0.1s; + -o-transition: visibility 0.1s, opacity 0.1s; + transition: visibility 0.1s, opacity 0.1s; } + +.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar { + visibility: visible; + opacity: 0; + -webkit-transition: visibility 1s, opacity 1s; + -moz-transition: visibility 1s, opacity 1s; + -ms-transition: visibility 1s, opacity 1s; + -o-transition: visibility 1s, opacity 1s; + transition: visibility 1s, opacity 1s; } + +.vjs-controls-disabled .vjs-control-bar, +.vjs-using-native-controls .vjs-control-bar, +.vjs-error .vjs-control-bar { + display: none !important; } + +.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar { + opacity: 1; + visibility: visible; } + +.vjs-has-started.vjs-no-flex .vjs-control-bar { + display: table; } + +.video-js .vjs-control { + position: relative; + text-align: center; + margin: 0; + padding: 0; + height: 100%; + width: 4em; + -webkit-box-flex: none; + -moz-box-flex: none; + -webkit-flex: none; + -ms-flex: none; + flex: none; } + +.vjs-button > .vjs-icon-placeholder:before { + font-size: 1.8em; + line-height: 1.67; } + +.video-js .vjs-control:focus:before, +.video-js .vjs-control:hover:before, +.video-js .vjs-control:focus { + text-shadow: 0em 0em 1em white; } + +.video-js .vjs-control-text { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; } + +.vjs-no-flex .vjs-control { + display: table-cell; + vertical-align: middle; } + +.video-js .vjs-custom-control-spacer { + display: none; } + +.video-js .vjs-progress-control { + cursor: pointer; + -webkit-box-flex: auto; + -moz-box-flex: auto; + -webkit-flex: auto; + -ms-flex: auto; + flex: auto; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + min-width: 4em; + touch-action: none; } + +.video-js .vjs-progress-control.disabled { + cursor: default; } + +.vjs-live .vjs-progress-control { + display: none; } + +.vjs-no-flex .vjs-progress-control { + width: auto; } + +.video-js .vjs-progress-holder { + -webkit-box-flex: auto; + -moz-box-flex: auto; + -webkit-flex: auto; + -ms-flex: auto; + flex: auto; + -webkit-transition: all 0.2s; + -moz-transition: all 0.2s; + -ms-transition: all 0.2s; + -o-transition: all 0.2s; + transition: all 0.2s; + height: 0.3em; } + +.video-js .vjs-progress-control .vjs-progress-holder { + margin: 0 10px; } + +.video-js .vjs-progress-control:hover .vjs-progress-holder { + font-size: 1.666666666666666666em; } + +.video-js .vjs-progress-control:hover .vjs-progress-holder.disabled { + font-size: 1em; } + +.video-js .vjs-progress-holder .vjs-play-progress, +.video-js .vjs-progress-holder .vjs-load-progress, +.video-js .vjs-progress-holder .vjs-load-progress div { + position: absolute; + display: block; + height: 100%; + margin: 0; + padding: 0; + width: 0; } + +.video-js .vjs-play-progress { + background-color: #fff; } + .video-js .vjs-play-progress:before { + font-size: 0.9em; + position: absolute; + right: -0.5em; + top: -0.333333333333333em; + z-index: 1; } + +.video-js .vjs-load-progress { + background: rgba(115, 133, 159, 0.5); } + +.video-js .vjs-load-progress div { + background: rgba(115, 133, 159, 0.75); } + +.video-js .vjs-time-tooltip { + background-color: #fff; + background-color: rgba(255, 255, 255, 0.8); + -webkit-border-radius: 0.3em; + -moz-border-radius: 0.3em; + border-radius: 0.3em; + color: #000; + float: right; + font-family: Arial, Helvetica, sans-serif; + font-size: 1em; + padding: 6px 8px 8px 8px; + pointer-events: none; + position: relative; + top: -3.4em; + visibility: hidden; + z-index: 1; } + +.video-js .vjs-progress-holder:focus .vjs-time-tooltip { + display: none; } + +.video-js .vjs-progress-control:hover .vjs-time-tooltip, +.video-js .vjs-progress-control:hover .vjs-progress-holder:focus .vjs-time-tooltip { + display: block; + font-size: 0.6em; + visibility: visible; } + +.video-js .vjs-progress-control.disabled:hover .vjs-time-tooltip { + font-size: 1em; } + +.video-js .vjs-progress-control .vjs-mouse-display { + display: none; + position: absolute; + width: 1px; + height: 100%; + background-color: #000; + z-index: 1; } + +.vjs-no-flex .vjs-progress-control .vjs-mouse-display { + z-index: 0; } + +.video-js .vjs-progress-control:hover .vjs-mouse-display { + display: block; } + +.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display { + visibility: hidden; + opacity: 0; + -webkit-transition: visibility 1s, opacity 1s; + -moz-transition: visibility 1s, opacity 1s; + -ms-transition: visibility 1s, opacity 1s; + -o-transition: visibility 1s, opacity 1s; + transition: visibility 1s, opacity 1s; } + +.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display { + display: none; } + +.vjs-mouse-display .vjs-time-tooltip { + color: #fff; + background-color: #000; + background-color: rgba(0, 0, 0, 0.8); } + +.video-js .vjs-slider { + position: relative; + cursor: pointer; + padding: 0; + margin: 0 0.45em 0 0.45em; + /* iOS Safari */ + -webkit-touch-callout: none; + /* Safari */ + -webkit-user-select: none; + /* Konqueror HTML */ + -khtml-user-select: none; + /* Firefox */ + -moz-user-select: none; + /* Internet Explorer/Edge */ + -ms-user-select: none; + /* Non-prefixed version, currently supported by Chrome and Opera */ + user-select: none; + background-color: #73859f; + background-color: rgba(115, 133, 159, 0.5); } + +.video-js .vjs-slider.disabled { + cursor: default; } + +.video-js .vjs-slider:focus { + text-shadow: 0em 0em 1em white; + -webkit-box-shadow: 0 0 1em #fff; + -moz-box-shadow: 0 0 1em #fff; + box-shadow: 0 0 1em #fff; } + +.video-js .vjs-mute-control { + cursor: pointer; + -webkit-box-flex: none; + -moz-box-flex: none; + -webkit-flex: none; + -ms-flex: none; + flex: none; } + +.video-js .vjs-volume-control { + cursor: pointer; + margin-right: 1em; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; } + +.video-js .vjs-volume-control.vjs-volume-horizontal { + width: 5em; } + +.video-js .vjs-volume-panel .vjs-volume-control { + visibility: visible; + opacity: 0; + width: 1px; + height: 1px; + margin-left: -1px; } + +.video-js .vjs-volume-panel { + -webkit-transition: width 1s; + -moz-transition: width 1s; + -ms-transition: width 1s; + -o-transition: width 1s; + transition: width 1s; } + .video-js .vjs-volume-panel:hover .vjs-volume-control, + .video-js .vjs-volume-panel:active .vjs-volume-control, + .video-js .vjs-volume-panel:focus .vjs-volume-control, + .video-js .vjs-volume-panel .vjs-volume-control:hover, + .video-js .vjs-volume-panel .vjs-volume-control:active, + .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control, + .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active { + visibility: visible; + opacity: 1; + position: relative; + -webkit-transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s; + -moz-transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s; + -ms-transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s; + -o-transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s; + transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s; } + .video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-horizontal, + .video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-horizontal, + .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-horizontal, + .video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-horizontal, + .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-horizontal, + .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-horizontal, + .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-horizontal { + width: 5em; + height: 3em; } + .video-js .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active { + width: 9em; + -webkit-transition: width 0.1s; + -moz-transition: width 0.1s; + -ms-transition: width 0.1s; + -o-transition: width 0.1s; + transition: width 0.1s; } + +.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical { + height: 8em; + width: 3em; + left: -3.5em; + -webkit-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s 1s, left 1s 1s, top 1s 1s; + -moz-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s 1s, left 1s 1s, top 1s 1s; + -ms-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s 1s, left 1s 1s, top 1s 1s; + -o-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s 1s, left 1s 1s, top 1s 1s; + transition: visibility 1s, opacity 1s, height 1s 1s, width 1s 1s, left 1s 1s, top 1s 1s; } + +.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal { + -webkit-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, left 1s 1s, top 1s 1s; + -moz-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, left 1s 1s, top 1s 1s; + -ms-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, left 1s 1s, top 1s 1s; + -o-transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, left 1s 1s, top 1s 1s; + transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, left 1s 1s, top 1s 1s; } + +.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal { + width: 5em; + height: 3em; + visibility: visible; + opacity: 1; + position: relative; + -webkit-transition: none; + -moz-transition: none; + -ms-transition: none; + -o-transition: none; + transition: none; } + +.video-js.vjs-no-flex .vjs-volume-control.vjs-volume-vertical, +.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical { + position: absolute; + bottom: 3em; + left: 0.5em; } + +.video-js .vjs-volume-panel { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; } + +.video-js .vjs-volume-bar { + margin: 1.35em 0.45em; } + +.vjs-volume-bar.vjs-slider-horizontal { + width: 5em; + height: 0.3em; } + +.vjs-volume-bar.vjs-slider-vertical { + width: 0.3em; + height: 5em; + margin: 1.35em auto; } + +.video-js .vjs-volume-level { + position: absolute; + bottom: 0; + left: 0; + background-color: #fff; } + .video-js .vjs-volume-level:before { + position: absolute; + font-size: 0.9em; } + +.vjs-slider-vertical .vjs-volume-level { + width: 0.3em; } + .vjs-slider-vertical .vjs-volume-level:before { + top: -0.5em; + left: -0.3em; } + +.vjs-slider-horizontal .vjs-volume-level { + height: 0.3em; } + .vjs-slider-horizontal .vjs-volume-level:before { + top: -0.3em; + right: -0.5em; } + +.video-js .vjs-volume-panel.vjs-volume-panel-vertical { + width: 4em; } + +.vjs-volume-bar.vjs-slider-vertical .vjs-volume-level { + height: 100%; } + +.vjs-volume-bar.vjs-slider-horizontal .vjs-volume-level { + width: 100%; } + +.video-js .vjs-volume-vertical { + width: 3em; + height: 8em; + bottom: 8em; + background-color: #2B333F; + background-color: rgba(43, 51, 63, 0.7); } + +.video-js .vjs-volume-horizontal .vjs-menu { + left: -2em; } + +.vjs-poster { + display: inline-block; + vertical-align: middle; + background-repeat: no-repeat; + background-position: 50% 50%; + background-size: contain; + background-color: #000000; + cursor: pointer; + margin: 0; + padding: 0; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + height: 100%; } + +.vjs-has-started .vjs-poster { + display: none; } + +.vjs-audio.vjs-has-started .vjs-poster { + display: block; } + +.vjs-using-native-controls .vjs-poster { + display: none; } + +.video-js .vjs-live-control { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: flex-start; + -webkit-align-items: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + -webkit-box-flex: auto; + -moz-box-flex: auto; + -webkit-flex: auto; + -ms-flex: auto; + flex: auto; + font-size: 1em; + line-height: 3em; } + +.vjs-no-flex .vjs-live-control { + display: table-cell; + width: auto; + text-align: left; } + +.video-js .vjs-time-control { + -webkit-box-flex: none; + -moz-box-flex: none; + -webkit-flex: none; + -ms-flex: none; + flex: none; + font-size: 1em; + line-height: 3em; + min-width: 2em; + width: auto; + padding-left: 1em; + padding-right: 1em; } + +.vjs-live .vjs-time-control { + display: none; } + +.video-js .vjs-current-time, +.vjs-no-flex .vjs-current-time { + display: none; } + +.video-js .vjs-duration, +.vjs-no-flex .vjs-duration { + display: none; } + +.vjs-time-divider { + display: none; + line-height: 3em; } + +.vjs-live .vjs-time-divider { + display: none; } + +.video-js .vjs-play-control .vjs-icon-placeholder { + cursor: pointer; + -webkit-box-flex: none; + -moz-box-flex: none; + -webkit-flex: none; + -ms-flex: none; + flex: none; } + +.vjs-text-track-display { + position: absolute; + bottom: 3em; + left: 0; + right: 0; + top: 0; + pointer-events: none; } + +.video-js.vjs-user-inactive.vjs-playing .vjs-text-track-display { + bottom: 1em; } + +.video-js .vjs-text-track { + font-size: 1.4em; + text-align: center; + margin-bottom: 0.1em; } + +.vjs-subtitles { + color: #fff; } + +.vjs-captions { + color: #fc6; } + +.vjs-tt-cue { + display: block; } + +video::-webkit-media-text-track-display { + -moz-transform: translateY(-3em); + -ms-transform: translateY(-3em); + -o-transform: translateY(-3em); + -webkit-transform: translateY(-3em); + transform: translateY(-3em); } + +.video-js.vjs-user-inactive.vjs-playing video::-webkit-media-text-track-display { + -moz-transform: translateY(-1.5em); + -ms-transform: translateY(-1.5em); + -o-transform: translateY(-1.5em); + -webkit-transform: translateY(-1.5em); + transform: translateY(-1.5em); } + +.video-js .vjs-fullscreen-control { + cursor: pointer; + -webkit-box-flex: none; + -moz-box-flex: none; + -webkit-flex: none; + -ms-flex: none; + flex: none; } + +.vjs-playback-rate > .vjs-menu-button, +.vjs-playback-rate .vjs-playback-rate-value { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; } + +.vjs-playback-rate .vjs-playback-rate-value { + pointer-events: none; + font-size: 1.5em; + line-height: 2; + text-align: center; } + +.vjs-playback-rate .vjs-menu { + width: 4em; + left: 0em; } + +.vjs-error .vjs-error-display .vjs-modal-dialog-content { + font-size: 1.4em; + text-align: center; } + +.vjs-error .vjs-error-display:before { + color: #fff; + content: 'X'; + font-family: Arial, Helvetica, sans-serif; + font-size: 4em; + left: 0; + line-height: 1; + margin-top: -0.5em; + position: absolute; + text-shadow: 0.05em 0.05em 0.1em #000; + text-align: center; + top: 50%; + vertical-align: middle; + width: 100%; } + +.vjs-loading-spinner { + display: none; + position: absolute; + top: 50%; + left: 50%; + margin: -25px 0 0 -25px; + opacity: 0.85; + text-align: left; + border: 6px solid rgba(43, 51, 63, 0.7); + box-sizing: border-box; + background-clip: padding-box; + width: 50px; + height: 50px; + border-radius: 25px; + visibility: hidden; } + +.vjs-seeking .vjs-loading-spinner, +.vjs-waiting .vjs-loading-spinner { + display: block; + animation: 0s linear 0.3s forwards vjs-spinner-show; } + +.vjs-loading-spinner:before, +.vjs-loading-spinner:after { + content: ""; + position: absolute; + margin: -6px; + box-sizing: inherit; + width: inherit; + height: inherit; + border-radius: inherit; + opacity: 1; + border: inherit; + border-color: transparent; + border-top-color: white; } + +.vjs-seeking .vjs-loading-spinner:before, +.vjs-seeking .vjs-loading-spinner:after, +.vjs-waiting .vjs-loading-spinner:before, +.vjs-waiting .vjs-loading-spinner:after { + -webkit-animation: vjs-spinner-spin 1.1s cubic-bezier(0.6, 0.2, 0, 0.8) infinite, vjs-spinner-fade 1.1s linear infinite; + animation: vjs-spinner-spin 1.1s cubic-bezier(0.6, 0.2, 0, 0.8) infinite, vjs-spinner-fade 1.1s linear infinite; } + +.vjs-seeking .vjs-loading-spinner:before, +.vjs-waiting .vjs-loading-spinner:before { + border-top-color: white; } + +.vjs-seeking .vjs-loading-spinner:after, +.vjs-waiting .vjs-loading-spinner:after { + border-top-color: white; + -webkit-animation-delay: 0.44s; + animation-delay: 0.44s; } + +@keyframes vjs-spinner-show { + to { + visibility: visible; } } + +@-webkit-keyframes vjs-spinner-show { + to { + visibility: visible; } } + +@keyframes vjs-spinner-spin { + 100% { + transform: rotate(360deg); } } + +@-webkit-keyframes vjs-spinner-spin { + 100% { + -webkit-transform: rotate(360deg); } } + +@keyframes vjs-spinner-fade { + 0% { + border-top-color: #73859f; } + 20% { + border-top-color: #73859f; } + 35% { + border-top-color: white; } + 60% { + border-top-color: #73859f; } + 100% { + border-top-color: #73859f; } } + +@-webkit-keyframes vjs-spinner-fade { + 0% { + border-top-color: #73859f; } + 20% { + border-top-color: #73859f; } + 35% { + border-top-color: white; } + 60% { + border-top-color: #73859f; } + 100% { + border-top-color: #73859f; } } + +.vjs-chapters-button .vjs-menu ul { + width: 24em; } + +.video-js .vjs-subs-caps-button + .vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder { + vertical-align: middle; + display: inline-block; + margin-bottom: -0.1em; } + +.video-js .vjs-subs-caps-button + .vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before { + font-family: VideoJS; + content: "\f10d"; + font-size: 1.5em; + line-height: inherit; } + +.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-custom-control-spacer { + -webkit-box-flex: auto; + -moz-box-flex: auto; + -webkit-flex: auto; + -ms-flex: auto; + flex: auto; } + +.video-js.vjs-layout-tiny:not(.vjs-fullscreen).vjs-no-flex .vjs-custom-control-spacer { + width: auto; } + +.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-current-time, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-time-divider, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-duration, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-remaining-time, +.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-playback-rate, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-progress-control, +.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-mute-control, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-volume-control, +.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-chapters-button, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-descriptions-button, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-captions-button, +.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-subtitles-button, .video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-audio-button { + display: none; } + +.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-current-time, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-time-divider, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-duration, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-remaining-time, +.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-playback-rate, +.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-mute-control, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-volume-control, +.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-chapters-button, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-descriptions-button, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-captions-button, +.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-subtitles-button, .video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-audio-button { + display: none; } + +.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-current-time, .video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-time-divider, .video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-duration, .video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-remaining-time, +.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-playback-rate, +.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-mute-control, .video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-volume-control, +.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-chapters-button, .video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-descriptions-button, .video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-captions-button, +.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-subtitles-button .vjs-audio-button { + display: none; } + +.vjs-modal-dialog.vjs-text-track-settings { + background-color: #2B333F; + background-color: rgba(43, 51, 63, 0.75); + color: #fff; + height: 70%; } + +.vjs-text-track-settings .vjs-modal-dialog-content { + display: table; } + +.vjs-text-track-settings .vjs-track-settings-colors, +.vjs-text-track-settings .vjs-track-settings-font, +.vjs-text-track-settings .vjs-track-settings-controls { + display: table-cell; } + +.vjs-text-track-settings .vjs-track-settings-controls { + text-align: right; + vertical-align: bottom; } + +@supports (display: grid) { + .vjs-text-track-settings .vjs-modal-dialog-content { + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr auto; } + .vjs-text-track-settings .vjs-track-settings-colors { + display: block; + grid-column: 1; + grid-row: 1; } + .vjs-text-track-settings .vjs-track-settings-font { + grid-column: 2; + grid-row: 1; } + .vjs-text-track-settings .vjs-track-settings-controls { + grid-column: 2; + grid-row: 2; } } + +.vjs-track-setting > select { + margin-right: 5px; } + +.vjs-text-track-settings fieldset { + margin: 5px; + padding: 3px; + border: none; } + +.vjs-text-track-settings fieldset span { + display: inline-block; } + +.vjs-text-track-settings legend { + color: #fff; + margin: 0 0 5px 0; } + +.vjs-text-track-settings .vjs-label { + position: absolute; + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + display: block; + margin: 0 0 5px 0; + padding: 0; + border: 0; + height: 1px; + width: 1px; + overflow: hidden; } + +.vjs-track-settings-controls button:focus, +.vjs-track-settings-controls button:active { + outline-style: solid; + outline-width: medium; + background-image: linear-gradient(0deg, #fff 88%, #73859f 100%); } + +.vjs-track-settings-controls button:hover { + color: rgba(43, 51, 63, 0.75); } + +.vjs-track-settings-controls button { + background-color: #fff; + background-image: linear-gradient(-180deg, #fff 88%, #73859f 100%); + color: #2B333F; + cursor: pointer; + border-radius: 2px; } + +.vjs-track-settings-controls .vjs-default-button { + margin-right: 1em; } + +@media print { + .video-js > *:not(.vjs-tech):not(.vjs-poster) { + visibility: hidden; } } + +.vjs-resize-manager { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: none; + visibility: hidden; } diff --git a/yaksh/static/yaksh/js/jquery-1.4.2.min.js b/yaksh/static/yaksh/js/jquery-1.4.2.min.js deleted file mode 100644 index 48a88b8..0000000 --- a/yaksh/static/yaksh/js/jquery-1.4.2.min.js +++ /dev/null @@ -1,154 +0,0 @@ -/*! - * jQuery JavaScript Library v1.4.2 - * http://jquery.com/ - * - * Copyright 2010, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2010, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Sat Feb 13 22:33:48 2010 -0500 - */ -(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, -Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& -(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, -a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== -"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, -function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a"; -var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, -parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= -false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= -s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, -applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; -else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, -a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== -w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, -cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= -c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); -a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, -function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); -k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), -C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= -e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& -f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; -if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", -e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, -"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, -d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, -e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); -t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| -g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, -CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, -g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, -text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, -setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= -h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== -"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, -h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& -q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; -if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); -(function(){var g=s.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: -function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= -{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== -"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", -d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? -a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== -1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= -c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, -wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, -prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, -this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); -return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, -""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); -return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", -""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= -c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? -c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= -function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= -Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, -"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= -a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= -a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== -"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, -serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), -function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, -global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& -e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? -"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== -false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= -false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", -c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| -d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); -g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== -1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== -"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; -if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== -"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| -c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; -this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= -this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, -e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
"; -a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); -c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, -d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- -f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": -"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in -e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); \ No newline at end of file diff --git a/yaksh/static/yaksh/js/video.js b/yaksh/static/yaksh/js/video.js new file mode 100644 index 0000000..2b3ef8a --- /dev/null +++ b/yaksh/static/yaksh/js/video.js @@ -0,0 +1,47810 @@ +/** + * @license + * Video.js 7.0.5 + * Copyright Brightcove, Inc. + * Available under Apache License Version 2.0 + * + * + * Includes vtt.js + * Available under Apache License Version 2.0 + * + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.videojs = factory()); +}(this, (function () { + var version = "7.0.5"; + + var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + + function createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; + } + + var win; + + if (typeof window !== "undefined") { + win = window; + } else if (typeof commonjsGlobal !== "undefined") { + win = commonjsGlobal; + } else if (typeof self !== "undefined") { + win = self; + } else { + win = {}; + } + + var window_1 = win; + + var empty = {}; + + var empty$1 = /*#__PURE__*/Object.freeze({ + default: empty + }); + + var minDoc = ( empty$1 && empty ) || empty$1; + + var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof window !== 'undefined' ? window : {}; + + var doccy; + + if (typeof document !== 'undefined') { + doccy = document; + } else { + doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4']; + + if (!doccy) { + doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc; + } + } + + var document_1 = doccy; + + /** + * @file log.js + * @module log + */ + + var log = void 0; + + // This is the private tracking variable for logging level. + var level = 'info'; + + // This is the private tracking variable for the logging history. + var history = []; + + /** + * Log messages to the console and history based on the type of message + * + * @private + * @param {string} type + * The name of the console method to use. + * + * @param {Array} args + * The arguments to be passed to the matching console method. + */ + var logByType = function logByType(type, args) { + var lvl = log.levels[level]; + var lvlRegExp = new RegExp('^(' + lvl + ')$'); + + if (type !== 'log') { + + // Add the type to the front of the message when it's not "log". + args.unshift(type.toUpperCase() + ':'); + } + + // Add a clone of the args at this point to history. + if (history) { + history.push([].concat(args)); + } + + // Add console prefix after adding to history. + args.unshift('VIDEOJS:'); + + // If there's no console then don't try to output messages, but they will + // still be stored in history. + if (!window_1.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]; + + 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; + } + + // Bail out if there's no console or if this type is not allowed by the + // current logging level. + if (!fn || !lvl || !lvlRegExp.test(type)) { + return; + } + + fn[Array.isArray(args) ? 'apply' : 'call'](window_1.console, args); + }; + + /** + * Logs plain debug messages. Similar to `console.log`. + * + * @class + * @param {Mixed[]} args + * One or more messages or objects that should be logged. + */ + log = function log() { + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + logByType('log', args); + }; + + /** + * Enumeration of available logging levels, where the keys are the level names + * and the values are `|`-separated strings containing logging methods allowed + * in that logging level. These strings are used to create a regular expression + * matching the function name being called. + * + * Levels provided by video.js are: + * + * - `off`: Matches no calls. Any value that can be cast to `false` will have + * this effect. The most restrictive. + * - `all`: Matches only Video.js-provided functions (`debug`, `log`, + * `log.warn`, and `log.error`). + * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls. + * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls. + * - `warn`: Matches `log.warn` and `log.error` calls. + * - `error`: Matches only `log.error` calls. + * + * @type {Object} + */ + log.levels = { + all: 'debug|log|warn|error', + off: '', + debug: 'debug|log|warn|error', + info: 'log|warn|error', + warn: 'warn|error', + error: 'error', + DEFAULT: level + }; + + /** + * Get or set the current logging level. If a string matching a key from + * {@link log.levels} is provided, acts as a setter. Regardless of argument, + * returns the current logging level. + * + * @param {string} [lvl] + * Pass to set a new logging level. + * + * @return {string} + * The current logging level. + */ + log.level = function (lvl) { + if (typeof lvl === 'string') { + if (!log.levels.hasOwnProperty(lvl)) { + throw new Error('"' + lvl + '" in not a valid log level'); + } + level = lvl; + } + return level; + }; + + /** + * Returns an array containing everything that has been logged to the history. + * + * This array is a shallow clone of the internal history record. However, its + * contents are _not_ cloned; so, mutating objects inside this array will + * mutate them in history. + * + * @return {Array} + */ + log.history = function () { + return history ? [].concat(history) : []; + }; + + /** + * Clears the internal history tracking, but does not prevent further history + * tracking. + */ + log.history.clear = function () { + if (history) { + history.length = 0; + } + }; + + /** + * Disable history tracking if it is currently enabled. + */ + log.history.disable = function () { + if (history !== null) { + history.length = 0; + history = null; + } + }; + + /** + * Enable history tracking if it is currently disabled. + */ + log.history.enable = function () { + if (history === null) { + history = []; + } + }; + + /** + * Logs error messages. Similar to `console.error`. + * + * @param {Mixed[]} args + * One or more messages or objects that should be logged as an error + */ + log.error = function () { + for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + return logByType('error', args); + }; + + /** + * Logs warning messages. Similar to `console.warn`. + * + * @param {Mixed[]} args + * One or more messages or objects that should be logged as a warning. + */ + log.warn = function () { + for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + args[_key3] = arguments[_key3]; + } + + return logByType('warn', args); + }; + + /** + * Logs debug messages. Similar to `console.debug`, but may also act as a comparable + * log if `console.debug` is not available + * + * @param {Mixed[]} args + * One or more messages or objects that should be logged as debug. + */ + log.debug = function () { + for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { + args[_key4] = arguments[_key4]; + } + + return logByType('debug', args); + }; + + var log$1 = log; + + function clean(s) { + return s.replace(/\n\r?\s*/g, ''); + } + + var tsml = function tsml(sa) { + var s = '', + i = 0; + + for (; i < arguments.length; i++) { + s += clean(sa[i]) + (arguments[i + 1] || ''); + }return s; + }; + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + + var classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + }; + + var inherits = function (subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + }; + + var possibleConstructorReturn = function (self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; + }; + + var taggedTemplateLiteralLoose = function (strings, raw) { + strings.raw = raw; + return strings; + }; + + /** + * @file obj.js + * @module obj + */ + + /** + * @callback obj:EachCallback + * + * @param {Mixed} value + * The current key for the object that is being iterated over. + * + * @param {string} key + * The current key-value for object that is being iterated over + */ + + /** + * @callback obj:ReduceCallback + * + * @param {Mixed} accum + * The value that is accumulating over the reduce loop. + * + * @param {Mixed} value + * The current key for the object that is being iterated over. + * + * @param {string} key + * The current key-value for object that is being iterated over + * + * @return {Mixed} + * The new accumulated value. + */ + var toString = Object.prototype.toString; + + /** + * Get the keys of an Object + * + * @param {Object} + * The Object to get the keys from + * + * @return {string[]} + * An array of the keys from the object. Returns an empty array if the + * object passed in was invalid or had no keys. + * + * @private + */ + var keys = function keys(object) { + return isObject(object) ? Object.keys(object) : []; + }; + + /** + * Array-like iteration for objects. + * + * @param {Object} object + * The object to iterate over + * + * @param {obj:EachCallback} fn + * The callback function which is called for each key in the object. + */ + function each(object, fn) { + keys(object).forEach(function (key) { + return fn(object[key], key); + }); + } + + /** + * Array-like reduce for objects. + * + * @param {Object} object + * The Object that you want to reduce. + * + * @param {Function} fn + * A callback function which is called for each key in the object. It + * receives the accumulated value and the per-iteration value and key + * as arguments. + * + * @param {Mixed} [initial = 0] + * Starting value + * + * @return {Mixed} + * The final accumulated value. + */ + function reduce(object, fn) { + var initial = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; + + return keys(object).reduce(function (accum, key) { + return fn(accum, object[key], key); + }, initial); + } + + /** + * Object.assign-style object shallow merge/extend. + * + * @param {Object} target + * @param {Object} ...sources + * @return {Object} + */ + function assign(target) { + for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + sources[_key - 1] = arguments[_key]; + } + + if (Object.assign) { + return Object.assign.apply(Object, [target].concat(sources)); + } + + sources.forEach(function (source) { + if (!source) { + return; + } + + each(source, function (value, key) { + target[key] = value; + }); + }); + + return target; + } + + /** + * Returns whether a value is an object of any kind - including DOM nodes, + * arrays, regular expressions, etc. Not functions, though. + * + * This avoids the gotcha where using `typeof` on a `null` value + * results in `'object'`. + * + * @param {Object} value + * @return {Boolean} + */ + function isObject(value) { + return !!value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object'; + } + + /** + * Returns whether an object appears to be a "plain" object - that is, a + * direct instance of `Object`. + * + * @param {Object} value + * @return {Boolean} + */ + function isPlain(value) { + return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object; + } + + /** + * @file computed-style.js + * @module computed-style + */ + + /** + * A safe getComputedStyle. + * + * This is needed because in Firefox, if the player is loaded in an iframe with + * `display:none`, then `getComputedStyle` returns `null`, so, we do a null-check to + * make sure that the player doesn't break in these cases. + * + * @param {Element} el + * The element you want the computed style of + * + * @param {string} prop + * The property name you want + * + * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 + * + * @static + * @const + */ + function computedStyle(el, prop) { + if (!el || !prop) { + return ''; + } + + if (typeof window_1.getComputedStyle === 'function') { + var cs = window_1.getComputedStyle(el); + + return cs ? cs[prop] : ''; + } + + return ''; + } + + var _templateObject = taggedTemplateLiteralLoose(['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.'], ['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.']); + + /** + * Detect if a value is a string with any non-whitespace characters. + * + * @param {string} str + * The string to check + * + * @return {boolean} + * - True if the string is non-blank + * - False otherwise + * + */ + function isNonBlankString(str) { + return typeof str === 'string' && /\S/.test(str); + } + + /** + * Throws an error if the passed string has whitespace. This is used by + * class methods to be relatively consistent with the classList API. + * + * @param {string} str + * The string to check for whitespace. + * + * @throws {Error} + * Throws an error if there is whitespace in the string. + * + */ + function throwIfWhitespace(str) { + if (/\s/.test(str)) { + throw new Error('class has illegal whitespace characters'); + } + } + + /** + * Produce a regular expression for matching a className within an elements className. + * + * @param {string} className + * The className to generate the RegExp for. + * + * @return {RegExp} + * The RegExp that will check for a specific `className` in an elements + * className. + */ + function classRegExp(className) { + return new RegExp('(^|\\s)' + className + '($|\\s)'); + } + + /** + * Whether the current DOM interface appears to be real. + * + * @return {Boolean} + */ + function isReal() { + // Both document and window will never be undefined thanks to `global`. + return document_1 === window_1.document; + } + + /** + * Determines, via duck typing, whether or not a value is a DOM element. + * + * @param {Mixed} value + * The thing to check + * + * @return {boolean} + * - True if it is a DOM element + * - False otherwise + */ + function isEl(value) { + return isObject(value) && value.nodeType === 1; + } + + /** + * Determines if the current DOM is embedded in an iframe. + * + * @return {boolean} + * + */ + function isInFrame() { + + // 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; + } catch (x) { + return true; + } + } + + /** + * Creates functions to query the DOM using a given method. + * + * @param {string} method + * The method to create the query with. + * + * @return {Function} + * The query method + */ + function createQuerier(method) { + return function (selector, context) { + if (!isNonBlankString(selector)) { + return document_1[method](null); + } + if (isNonBlankString(context)) { + context = document_1.querySelector(context); + } + + var ctx = isEl(context) ? context : document_1; + + return ctx[method] && ctx[method](selector); + }; + } + + /** + * Creates an element and applies properties. + * + * @param {string} [tagName='div'] + * Name of tag to be created. + * + * @param {Object} [properties={}] + * Element properties to be applied. + * + * @param {Object} [attributes={}] + * Element attributes to be applied. + * + * @param {String|Element|TextNode|Array|Function} [content] + * Contents for the element (see: {@link dom:normalizeContent}) + * + * @return {Element} + * The element that was created. + */ + function createEl() { + var tagName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div'; + var properties = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + var content = arguments[3]; + + var el = document_1.createElement(tagName); + + Object.getOwnPropertyNames(properties).forEach(function (propName) { + var val = properties[propName]; + + // See #2176 + // We originally were accepting both properties and attributes in the + // same object, but that doesn't work so well. + if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { + log$1.warn(tsml(_templateObject, propName, val)); + el.setAttribute(propName, val); + + // Handle textContent since it's not supported everywhere and we have a + // method for it. + } else if (propName === 'textContent') { + textContent(el, val); + } else { + el[propName] = val; + } + }); + + Object.getOwnPropertyNames(attributes).forEach(function (attrName) { + el.setAttribute(attrName, attributes[attrName]); + }); + + if (content) { + appendContent(el, content); + } + + return el; + } + + /** + * Injects text into an element, replacing any existing contents entirely. + * + * @param {Element} el + * The element to add text content into + * + * @param {string} text + * The text content to add. + * + * @return {Element} + * The element with added text content. + */ + function textContent(el, text) { + if (typeof el.textContent === 'undefined') { + el.innerText = text; + } else { + el.textContent = text; + } + return el; + } + + /** + * Insert an element as the first child node of another + * + * @param {Element} child + * Element to insert + * + * @param {Element} parent + * Element to insert child into + */ + function prependTo(child, parent) { + if (parent.firstChild) { + parent.insertBefore(child, parent.firstChild); + } else { + parent.appendChild(child); + } + } + + /** + * Check if an element has a CSS class + * + * @param {Element} element + * Element to check + * + * @param {string} classToCheck + * Class name to check for + * + * @return {boolean} + * - True if the element had the class + * - False otherwise. + * + * @throws {Error} + * Throws an error if `classToCheck` has white space. + */ + function hasClass(element, classToCheck) { + throwIfWhitespace(classToCheck); + if (element.classList) { + return element.classList.contains(classToCheck); + } + return classRegExp(classToCheck).test(element.className); + } + + /** + * Add a CSS class name to an element + * + * @param {Element} element + * Element to add class name to. + * + * @param {string} classToAdd + * Class name to add. + * + * @return {Element} + * The dom element with the added class name. + */ + function addClass(element, classToAdd) { + if (element.classList) { + element.classList.add(classToAdd); + + // Don't need to `throwIfWhitespace` here because `hasElClass` will do it + // in the case of classList not being supported. + } else if (!hasClass(element, classToAdd)) { + element.className = (element.className + ' ' + classToAdd).trim(); + } + + return element; + } + + /** + * Remove a CSS class name from an element + * + * @param {Element} element + * Element to remove a class name from. + * + * @param {string} classToRemove + * Class name to remove + * + * @return {Element} + * The dom element with class name removed. + */ + function removeClass(element, classToRemove) { + if (element.classList) { + element.classList.remove(classToRemove); + } else { + throwIfWhitespace(classToRemove); + element.className = element.className.split(/\s+/).filter(function (c) { + return c !== classToRemove; + }).join(' '); + } + + return element; + } + + /** + * The callback definition for toggleElClass. + * + * @callback Dom~PredicateCallback + * @param {Element} element + * The DOM element of the Component. + * + * @param {string} classToToggle + * The `className` that wants to be toggled + * + * @return {boolean|undefined} + * - If true the `classToToggle` will get added to `element`. + * - If false the `classToToggle` will get removed from `element`. + * - If undefined this callback will be ignored + */ + + /** + * Adds or removes a CSS class name on an element depending on an optional + * condition or the presence/absence of the class name. + * + * @param {Element} element + * The element to toggle a class name on. + * + * @param {string} classToToggle + * The class that should be toggled + * + * @param {boolean|PredicateCallback} [predicate] + * See the return value for {@link Dom~PredicateCallback} + * + * @return {Element} + * The element with a class that has been toggled. + */ + function toggleClass(element, classToToggle, predicate) { + + // This CANNOT use `classList` internally because IE11 does not support the + // second parameter to the `classList.toggle()` method! Which is fine because + // `classList` will be used by the add/remove functions. + var has = hasClass(element, classToToggle); + + if (typeof predicate === 'function') { + predicate = predicate(element, classToToggle); + } + + if (typeof predicate !== 'boolean') { + predicate = !has; + } + + // If the necessary class operation matches the current state of the + // element, no action is required. + if (predicate === has) { + return; + } + + if (predicate) { + addClass(element, classToToggle); + } else { + removeClass(element, classToToggle); + } + + return element; + } + + /** + * Apply attributes to an HTML element. + * + * @param {Element} el + * Element to add attributes to. + * + * @param {Object} [attributes] + * Attributes to be applied. + */ + function setAttributes(el, attributes) { + Object.getOwnPropertyNames(attributes).forEach(function (attrName) { + var attrValue = attributes[attrName]; + + if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { + el.removeAttribute(attrName); + } else { + el.setAttribute(attrName, attrValue === true ? '' : attrValue); + } + }); + } + + /** + * Get an element's attribute values, as defined on the HTML tag + * Attributes are not the same as properties. They're defined on the tag + * or with setAttribute (which shouldn't be used with HTML) + * This will return true or false for boolean attributes. + * + * @param {Element} tag + * Element from which to get tag attributes. + * + * @return {Object} + * All attributes of the element. + */ + function getAttributes(tag) { + var obj = {}; + + // known boolean attributes + // we can check for matching boolean properties, but not all browsers + // and not all tags know about these attributes, so, we still want to check them manually + var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ','; + + if (tag && tag.attributes && tag.attributes.length > 0) { + var attrs = tag.attributes; + + for (var i = attrs.length - 1; i >= 0; i--) { + var attrName = attrs[i].name; + var attrVal = attrs[i].value; + + // check for known booleans + // the matching element property will return a value for typeof + if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) { + // the value of an included boolean attribute is typically an empty + // string ('') which would equal false if we just check for a false value. + // we also don't want support bad code like autoplay='false' + attrVal = attrVal !== null ? true : false; + } + + obj[attrName] = attrVal; + } + } + + return obj; + } + + /** + * Get the value of an element's attribute + * + * @param {Element} el + * A DOM element + * + * @param {string} attribute + * Attribute to get the value of + * + * @return {string} + * value of the attribute + */ + function getAttribute(el, attribute) { + return el.getAttribute(attribute); + } + + /** + * Set the value of an element's attribute + * + * @param {Element} el + * A DOM element + * + * @param {string} attribute + * Attribute to set + * + * @param {string} value + * Value to set the attribute to + */ + function setAttribute(el, attribute, value) { + el.setAttribute(attribute, value); + } + + /** + * Remove an element's attribute + * + * @param {Element} el + * A DOM element + * + * @param {string} attribute + * Attribute to remove + */ + function removeAttribute(el, attribute) { + el.removeAttribute(attribute); + } + + /** + * Attempt to block the ability to select text while dragging controls + */ + function blockTextSelection() { + document_1.body.focus(); + document_1.onselectstart = function () { + return false; + }; + } + + /** + * Turn off text selection blocking + */ + function unblockTextSelection() { + document_1.onselectstart = function () { + return true; + }; + } + + /** + * Identical to the native `getBoundingClientRect` function, but ensures that + * the method is supported at all (it is in all browsers we claim to support) + * and that the element is in the DOM before continuing. + * + * This wrapper function also shims properties which are not provided by some + * older browsers (namely, IE8). + * + * Additionally, some browsers do not support adding properties to a + * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard + * properties (except `x` and `y` which are not widely supported). This helps + * avoid implementations where keys are non-enumerable. + * + * @param {Element} el + * Element whose `ClientRect` we want to calculate. + * + * @return {Object|undefined} + * Always returns a plain + */ + function getBoundingClientRect(el) { + if (el && el.getBoundingClientRect && el.parentNode) { + var rect = el.getBoundingClientRect(); + var result = {}; + + ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) { + if (rect[k] !== undefined) { + result[k] = rect[k]; + } + }); + + if (!result.height) { + result.height = parseFloat(computedStyle(el, 'height')); + } + + if (!result.width) { + result.width = parseFloat(computedStyle(el, 'width')); + } + + return result; + } + } + + /** + * The postion of a DOM element on the page. + * + * @typedef {Object} module:dom~Position + * + * @property {number} left + * Pixels to the left + * + * @property {number} top + * Pixels on top + */ + + /** + * Offset Left. + * getBoundingClientRect technique from + * John Resig + * + * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/ + * + * @param {Element} el + * Element from which to get offset + * + * @return {module:dom~Position} + * The position of the element that was passed in. + */ + function findPosition(el) { + var box = void 0; + + if (el.getBoundingClientRect && el.parentNode) { + box = el.getBoundingClientRect(); + } + + if (!box) { + return { + left: 0, + top: 0 + }; + } + + var docEl = document_1.documentElement; + var body = document_1.body; + + var clientLeft = docEl.clientLeft || body.clientLeft || 0; + var scrollLeft = window_1.pageXOffset || body.scrollLeft; + var left = box.left + scrollLeft - clientLeft; + + var clientTop = docEl.clientTop || body.clientTop || 0; + var scrollTop = window_1.pageYOffset || body.scrollTop; + var top = box.top + scrollTop - clientTop; + + // Android sometimes returns slightly off decimal values, so need to round + return { + left: Math.round(left), + top: Math.round(top) + }; + } + + /** + * x and y coordinates for a dom element or mouse pointer + * + * @typedef {Object} Dom~Coordinates + * + * @property {number} x + * x coordinate in pixels + * + * @property {number} y + * y coordinate in pixels + */ + + /** + * Get pointer position in element + * Returns an object with x and y coordinates. + * The base on the coordinates are the bottom left of the element. + * + * @param {Element} el + * Element on which to get the pointer position on + * + * @param {EventTarget~Event} event + * Event object + * + * @return {Dom~Coordinates} + * A Coordinates object corresponding to the mouse position. + * + */ + function getPointerPosition(el, event) { + var position = {}; + var box = findPosition(el); + var boxW = el.offsetWidth; + var boxH = el.offsetHeight; + + var boxY = box.top; + var boxX = box.left; + var pageY = event.pageY; + var pageX = event.pageX; + + if (event.changedTouches) { + pageX = event.changedTouches[0].pageX; + pageY = event.changedTouches[0].pageY; + } + + position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH)); + position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW)); + + return position; + } + + /** + * Determines, via duck typing, whether or not a value is a text node. + * + * @param {Mixed} value + * Check if this value is a text node. + * + * @return {boolean} + * - True if it is a text node + * - False otherwise + */ + function isTextNode(value) { + return isObject(value) && value.nodeType === 3; + } + + /** + * Empties the contents of an element. + * + * @param {Element} el + * The element to empty children from + * + * @return {Element} + * The element with no children + */ + function emptyEl(el) { + while (el.firstChild) { + el.removeChild(el.firstChild); + } + return el; + } + + /** + * Normalizes content for eventual insertion into the DOM. + * + * This allows a wide range of content definition methods, but protects + * from falling into the trap of simply writing to `innerHTML`, which is + * an XSS concern. + * + * The content for an element can be passed in multiple types and + * combinations, whose behavior is as follows: + * + * @param {String|Element|TextNode|Array|Function} content + * - String: Normalized into a text node. + * - Element/TextNode: Passed through. + * - Array: A one-dimensional array of strings, elements, nodes, or functions + * (which return single strings, elements, or nodes). + * - Function: If the sole argument, is expected to produce a string, element, + * node, or array as defined above. + * + * @return {Array} + * All of the content that was passed in normalized. + */ + function normalizeContent(content) { + + // First, invoke content if it is a function. If it produces an array, + // that needs to happen before normalization. + if (typeof content === 'function') { + content = content(); + } + + // Next up, normalize to an array, so one or many items can be normalized, + // filtered, and returned. + return (Array.isArray(content) ? content : [content]).map(function (value) { + + // First, invoke value if it is a function to produce a new value, + // which will be subsequently normalized to a Node of some kind. + if (typeof value === 'function') { + value = value(); + } + + if (isEl(value) || isTextNode(value)) { + return value; + } + + if (typeof value === 'string' && /\S/.test(value)) { + return document_1.createTextNode(value); + } + }).filter(function (value) { + return value; + }); + } + + /** + * Normalizes and appends content to an element. + * + * @param {Element} el + * Element to append normalized content to. + * + * + * @param {String|Element|TextNode|Array|Function} content + * See the `content` argument of {@link dom:normalizeContent} + * + * @return {Element} + * The element with appended normalized content. + */ + function appendContent(el, content) { + normalizeContent(content).forEach(function (node) { + return el.appendChild(node); + }); + return el; + } + + /** + * Normalizes and inserts content into an element; this is identical to + * `appendContent()`, except it empties the element first. + * + * @param {Element} el + * Element to insert normalized content into. + * + * @param {String|Element|TextNode|Array|Function} content + * See the `content` argument of {@link dom:normalizeContent} + * + * @return {Element} + * The element with inserted normalized content. + * + */ + function insertContent(el, content) { + return appendContent(emptyEl(el), content); + } + + /** + * Check if event was a single left click + * + * @param {EventTarget~Event} event + * Event object + * + * @return {boolean} + * - True if a left click + * - False if not a left click + */ + function isSingleLeftClick(event) { + // Note: if you create something draggable, be sure to + // call it on both `mousedown` and `mousemove` event, + // otherwise `mousedown` should be enough for a button + + if (event.button === undefined && event.buttons === undefined) { + // Why do we need `buttons` ? + // Because, middle mouse sometimes have this: + // e.button === 0 and e.buttons === 4 + // Furthermore, we want to prevent combination click, something like + // HOLD middlemouse then left click, that would be + // e.button === 0, e.buttons === 5 + // just `button` is not gonna work + + // Alright, then what this block does ? + // this is for chrome `simulate mobile devices` + // I want to support this as well + + return true; + } + + if (event.button === 0 && event.buttons === undefined) { + // Touch screen, sometimes on some specific device, `buttons` + // doesn't have anything (safari on ios, blackberry...) + + return true; + } + + if (event.button !== 0 || event.buttons !== 1) { + // This is the reason we have those if else block above + // if any special case we can catch and let it slide + // we do it above, when get to here, this definitely + // is-not-left-click + + return false; + } + + return true; + } + + /** + * Finds a single DOM element matching `selector` within the optional + * `context` of another DOM element (defaulting to `document`). + * + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelector`. + * + * @param {Element|String} [context=document] + * A DOM element within which to query. Can also be a selector + * string in which case the first matching element will be used + * as context. If missing (or no element matches selector), falls + * back to `document`. + * + * @return {Element|null} + * The element that was found or null. + */ + var $ = createQuerier('querySelector'); + + /** + * Finds a all DOM elements matching `selector` within the optional + * `context` of another DOM element (defaulting to `document`). + * + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelectorAll`. + * + * @param {Element|String} [context=document] + * A DOM element within which to query. Can also be a selector + * string in which case the first matching element will be used + * as context. If missing (or no element matches selector), falls + * back to `document`. + * + * @return {NodeList} + * A element list of elements that were found. Will be empty if none were found. + * + */ + var $$ = createQuerier('querySelectorAll'); + + var Dom = /*#__PURE__*/Object.freeze({ + isReal: isReal, + isEl: isEl, + isInFrame: isInFrame, + createEl: createEl, + textContent: textContent, + prependTo: prependTo, + hasClass: hasClass, + addClass: addClass, + removeClass: removeClass, + toggleClass: toggleClass, + setAttributes: setAttributes, + getAttributes: getAttributes, + getAttribute: getAttribute, + setAttribute: setAttribute, + removeAttribute: removeAttribute, + blockTextSelection: blockTextSelection, + unblockTextSelection: unblockTextSelection, + getBoundingClientRect: getBoundingClientRect, + findPosition: findPosition, + getPointerPosition: getPointerPosition, + isTextNode: isTextNode, + emptyEl: emptyEl, + normalizeContent: normalizeContent, + appendContent: appendContent, + insertContent: insertContent, + isSingleLeftClick: isSingleLeftClick, + $: $, + $$: $$ + }); + + /** + * @file guid.js + * @module guid + */ + + /** + * Unique ID for an element or function + * @type {Number} + */ + var _guid = 1; + + /** + * Get a unique auto-incrementing ID by number that has not been returned before. + * + * @return {number} + * A new unique ID. + */ + function newGUID() { + return _guid++; + } + + /** + * @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 elData = {}; + + /* + * Unique attribute name to store an element's guid in + * + * @type {String} + * @constant + * @private + */ + var elIdAttr = 'vdata' + new Date().getTime(); + + /** + * Returns the cache object where data for an element is stored + * + * @param {Element} el + * Element to store data for. + * + * @return {Object} + * The cache object for that el that was passed in. + */ + function getData(el) { + var id = el[elIdAttr]; + + if (!id) { + id = el[elIdAttr] = newGUID(); + } + + if (!elData[id]) { + elData[id] = {}; + } + + return elData[id]; + } + + /** + * Returns whether or not an element has cached data + * + * @param {Element} el + * Check if this element has cached data. + * + * @return {boolean} + * - True if the DOM element has cached data. + * - False otherwise. + */ + function hasData(el) { + var id = el[elIdAttr]; + + if (!id) { + return false; + } + + return !!Object.getOwnPropertyNames(elData[id]).length; + } + + /** + * Delete data for the element from the cache and the guid attr from getElementById + * + * @param {Element} el + * Remove cached data for this element. + */ + function removeData(el) { + var id = el[elIdAttr]; + + if (!id) { + return; + } + + // Remove all stored data + delete elData[id]; + + // Remove the elIdAttr property from the DOM node + try { + delete el[elIdAttr]; + } catch (e) { + if (el.removeAttribute) { + el.removeAttribute(elIdAttr); + } else { + // IE doesn't appear to support removeAttribute on the document element + el[elIdAttr] = null; + } + } + } + + /** + * @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) + * This should work very similarly to jQuery's events, however it's based off the book version which isn't as + * robust as jquery's, so there's probably some differences. + * + * @module events + */ + + /** + * Clean up the listener cache and dispatchers + * + * @param {Element|Object} elem + * Element to clean up + * + * @param {string} type + * Type of event to clean up + */ + function _cleanUpEvents(elem, type) { + var data = getData(elem); + + // Remove the events of a particular type if there are none left + if (data.handlers[type].length === 0) { + delete data.handlers[type]; + // data.handlers[type] = null; + // Setting to null was causing an error with data.handlers + + // Remove the meta-handler from the element + if (elem.removeEventListener) { + elem.removeEventListener(type, data.dispatcher, false); + } else if (elem.detachEvent) { + elem.detachEvent('on' + type, data.dispatcher); + } + } + + // Remove the events object if there are no types left + if (Object.getOwnPropertyNames(data.handlers).length <= 0) { + delete data.handlers; + delete data.dispatcher; + delete data.disabled; + } + + // Finally remove the element data if there is no data left + if (Object.getOwnPropertyNames(data).length === 0) { + removeData(elem); + } + } + + /** + * Loops through an array of event types and calls the requested method for each type. + * + * @param {Function} fn + * The event method we want to use. + * + * @param {Element|Object} elem + * Element or object to bind listeners to + * + * @param {string} type + * Type of event to bind to. + * + * @param {EventTarget~EventListener} callback + * Event listener. + */ + function _handleMultipleEvents(fn, elem, types, callback) { + types.forEach(function (type) { + // Call the event method for each one of the types + fn(elem, type, callback); + }); + } + + /** + * Fix a native event to have standard property values + * + * @param {Object} event + * Event object to fix. + * + * @return {Object} + * Fixed event object. + */ + function fixEvent(event) { + + function returnTrue() { + return true; + } + + function returnFalse() { + return false; + } + + // Test if fixing up is needed + // Used to check if !event.stopPropagation instead of isPropagationStopped + // But native events return true for stopPropagation, but don't have + // other expected methods like isPropagationStopped. Seems to be a problem + // with the Javascript Ninja code. So we're just overriding all events now. + if (!event || !event.isPropagationStopped) { + var old = event || window_1.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 + // which makes copying more difficult. + // TODO: Probably best to create a whitelist of event props + for (var key in old) { + // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y + // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation + // and webkitMovementX/Y + if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') { + // Chrome 32+ warns if you try to copy deprecated returnValue, but + // we still want to if preventDefault isn't supported (IE8). + if (!(key === 'returnValue' && old.preventDefault)) { + event[key] = old[key]; + } + } + } + + // The event occurred on this element + if (!event.target) { + event.target = event.srcElement || document_1; + } + + // Handle which other element the event is related to + if (!event.relatedTarget) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Stop the default browser action + event.preventDefault = function () { + if (old.preventDefault) { + old.preventDefault(); + } + event.returnValue = false; + old.returnValue = false; + event.defaultPrevented = true; + }; + + event.defaultPrevented = false; + + // Stop the event from bubbling + event.stopPropagation = function () { + if (old.stopPropagation) { + old.stopPropagation(); + } + event.cancelBubble = true; + old.cancelBubble = true; + event.isPropagationStopped = returnTrue; + }; + + event.isPropagationStopped = returnFalse; + + // Stop the event from bubbling and executing other handlers + event.stopImmediatePropagation = function () { + if (old.stopImmediatePropagation) { + old.stopImmediatePropagation(); + } + event.isImmediatePropagationStopped = returnTrue; + event.stopPropagation(); + }; + + event.isImmediatePropagationStopped = returnFalse; + + // Handle mouse position + if (event.clientX !== null && event.clientX !== undefined) { + var doc = document_1.documentElement; + var body = document_1.body; + + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Handle key presses + event.which = event.charCode || event.keyCode; + + // Fix button for mouse clicks: + // 0 == left; 1 == middle; 2 == right + if (event.button !== null && event.button !== undefined) { + + // The following is disabled because it does not pass videojs-standard + // and... yikes. + /* eslint-disable */ + event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0; + /* eslint-enable */ + } + } + + // Returns fixed-up instance + return event; + } + + /** + * Whether passive event listeners are supported + */ + var _supportsPassive = false; + + (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 + } + })(); + + /** + * Touch events Chrome expects to be passive + */ + var passiveEvents = ['touchstart', 'touchmove']; + + /** + * Add an event listener to element + * It stores the handler function in a separate cache object + * and adds a generic handler to the element's event, + * along with a unique id (guid) to the element. + * + * @param {Element|Object} elem + * Element or object to bind listeners to + * + * @param {string|string[]} type + * Type of event to bind to. + * + * @param {EventTarget~EventListener} fn + * Event listener. + */ + function on(elem, type, fn) { + if (Array.isArray(type)) { + return _handleMultipleEvents(on, elem, type, fn); + } + + var data = getData(elem); + + // We need a place to store all our handler data + if (!data.handlers) { + data.handlers = {}; + } + + if (!data.handlers[type]) { + data.handlers[type] = []; + } + + if (!fn.guid) { + fn.guid = newGUID(); + } + + data.handlers[type].push(fn); + + if (!data.dispatcher) { + data.disabled = false; + + data.dispatcher = function (event, hash) { + + if (data.disabled) { + return; + } + + event = fixEvent(event); + + var handlers = data.handlers[event.type]; + + if (handlers) { + // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. + var handlersCopy = handlers.slice(0); + + for (var m = 0, n = handlersCopy.length; m < n; m++) { + if (event.isImmediatePropagationStopped()) { + break; + } else { + try { + handlersCopy[m].call(elem, event, hash); + } catch (e) { + log$1.error(e); + } + } + } + } + }; + } + + if (data.handlers[type].length === 1) { + if (elem.addEventListener) { + var options = false; + + if (_supportsPassive && passiveEvents.indexOf(type) > -1) { + options = { passive: true }; + } + elem.addEventListener(type, data.dispatcher, options); + } else if (elem.attachEvent) { + elem.attachEvent('on' + type, data.dispatcher); + } + } + } + + /** + * Removes event listeners from an element + * + * @param {Element|Object} elem + * Object to remove listeners from. + * + * @param {string|string[]} [type] + * Type of listener to remove. Don't include to remove all events from element. + * + * @param {EventTarget~EventListener} [fn] + * Specific listener to remove. Don't include to remove listeners for an event + * type. + */ + function off(elem, type, fn) { + // Don't want to add a cache object through getElData if not needed + if (!hasData(elem)) { + return; + } + + var data = getData(elem); + + // If no events exist, nothing to unbind + if (!data.handlers) { + return; + } + + if (Array.isArray(type)) { + return _handleMultipleEvents(off, elem, type, fn); + } + + // Utility function + var removeType = function removeType(el, t) { + data.handlers[t] = []; + _cleanUpEvents(el, t); + }; + + // Are we removing all bound events? + if (type === undefined) { + for (var t in data.handlers) { + if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) { + removeType(elem, t); + } + } + return; + } + + var handlers = data.handlers[type]; + + // If no handlers exist, nothing to unbind + if (!handlers) { + return; + } + + // If no listener was provided, remove all listeners for type + if (!fn) { + removeType(elem, type); + return; + } + + // We're only removing a single handler + if (fn.guid) { + for (var n = 0; n < handlers.length; n++) { + if (handlers[n].guid === fn.guid) { + handlers.splice(n--, 1); + } + } + } + + _cleanUpEvents(elem, type); + } + + /** + * Trigger an event for an element + * + * @param {Element|Object} elem + * Element to trigger an event on + * + * @param {EventTarget~Event|string} event + * A string (the type) or an event object with a type attribute + * + * @param {Object} [hash] + * data hash to pass along with the event + * + * @return {boolean|undefined} + * - Returns the opposite of `defaultPrevented` if default was prevented + * - Otherwise returns undefined + */ + function trigger(elem, event, hash) { + // Fetches element data and a reference to the parent (for bubbling). + // Don't want to add a data object to cache for every parent, + // so checking hasElData first. + var elemData = hasData(elem) ? getData(elem) : {}; + var parent = elem.parentNode || elem.ownerDocument; + // type = event.type || event, + // handler; + + // If an event name was passed as a string, creates an event out of it + if (typeof event === 'string') { + event = { type: event, target: elem }; + } else if (!event.target) { + event.target = elem; + } + + // Normalizes the event properties. + event = fixEvent(event); + + // If the passed element has a dispatcher, executes the established handlers. + if (elemData.dispatcher) { + elemData.dispatcher.call(elem, event, hash); + } + + // Unless explicitly stopped or the event does not bubble (e.g. media events) + // recursively calls this function to bubble the event up the DOM. + if (parent && !event.isPropagationStopped() && event.bubbles === true) { + trigger.call(null, parent, event, hash); + + // If at the top of the DOM, triggers the default action unless disabled. + } else if (!parent && !event.defaultPrevented) { + var targetData = getData(event.target); + + // Checks if the target has a default action for this event. + if (event.target[event.type]) { + // Temporarily disables event dispatching on the target as we have already executed the handler. + targetData.disabled = true; + // Executes the default action. + if (typeof event.target[event.type] === 'function') { + event.target[event.type](); + } + // Re-enables event dispatching. + targetData.disabled = false; + } + } + + // Inform the triggerer if the default was prevented by returning false + return !event.defaultPrevented; + } + + /** + * Trigger a listener only once for an event + * + * @param {Element|Object} elem + * Element or object to bind to. + * + * @param {string|string[]} type + * Name/type of event + * + * @param {Event~EventListener} fn + * Event Listener function + */ + function one(elem, type, fn) { + if (Array.isArray(type)) { + return _handleMultipleEvents(one, elem, type, fn); + } + var func = function func() { + off(elem, type, func); + fn.apply(this, arguments); + }; + + // copy the guid to the new function so it can removed using the original function's ID + func.guid = fn.guid = fn.guid || newGUID(); + on(elem, type, func); + } + + var Events = /*#__PURE__*/Object.freeze({ + fixEvent: fixEvent, + on: on, + off: off, + trigger: trigger, + one: one + }); + + /** + * @file setup.js - Functions for setting up a player without + * user interaction based on the data-setup `attribute` of the video tag. + * + * @module setup + */ + + var _windowLoaded = false; + var videojs = void 0; + + /** + * Set up any tags that have a data-setup `attribute` when the player is started. + */ + var autoSetup = function autoSetup() { + + // Protect against breakage in non-browser environments and check global autoSetup option. + if (!isReal() || videojs.options.autoSetup === false) { + return; + } + + var vids = Array.prototype.slice.call(document_1.getElementsByTagName('video')); + var audios = Array.prototype.slice.call(document_1.getElementsByTagName('audio')); + var divs = Array.prototype.slice.call(document_1.getElementsByTagName('video-js')); + var mediaEls = vids.concat(audios, divs); + + // Check if any media elements exist + if (mediaEls && mediaEls.length > 0) { + + for (var i = 0, e = mediaEls.length; i < e; i++) { + var mediaEl = mediaEls[i]; + + // Check if element exists, has getAttribute func. + if (mediaEl && mediaEl.getAttribute) { + + // Make sure this player hasn't already been set up. + if (mediaEl.player === undefined) { + var options = mediaEl.getAttribute('data-setup'); + + // Check if data-setup attr exists. + // We only auto-setup if they've added the data-setup attr. + if (options !== null) { + // Create new video.js instance. + videojs(mediaEl); + } + } + + // If getAttribute isn't defined, we need to wait for the DOM. + } else { + autoSetupTimeout(1); + break; + } + } + + // No videos were found, so keep looping unless page is finished loading. + } else if (!_windowLoaded) { + autoSetupTimeout(1); + } + }; + + /** + * Wait until the page is loaded before running autoSetup. This will be called in + * autoSetup if `hasLoaded` returns false. + * + * @param {number} wait + * How long to wait in ms + * + * @param {module:videojs} [vjs] + * The videojs library function + */ + function autoSetupTimeout(wait, vjs) { + if (vjs) { + videojs = vjs; + } + + window_1.setTimeout(autoSetup, wait); + } + + if (isReal() && document_1.readyState === 'complete') { + _windowLoaded = true; + } else { + /** + * Listen for the load event on window, and set _windowLoaded to true. + * + * @listens load + */ + one(window_1, 'load', function () { + _windowLoaded = true; + }); + } + + /** + * @file stylesheet.js + * @module stylesheet + */ + + /** + * Create a DOM syle element given a className for it. + * + * @param {string} className + * The className to add to the created style element. + * + * @return {Element} + * The element that was created. + */ + var createStyleElement = function createStyleElement(className) { + var style = document_1.createElement('style'); + + style.className = className; + + return style; + }; + + /** + * Add text to a DOM element. + * + * @param {Element} el + * The Element to add text content to. + * + * @param {string} content + * The text to add to the element. + */ + var setTextContent = function setTextContent(el, content) { + if (el.styleSheet) { + el.styleSheet.cssText = content; + } else { + el.textContent = content; + } + }; + + /** + * @file fn.js + * @module fn + */ + + /** + * Bind (a.k.a proxy or Context). A simple method for changing the context of a function + * It also stores a unique id on the function so it can be easily removed from events. + * + * @param {Mixed} context + * The object to bind as scope. + * + * @param {Function} fn + * The function to be bound to a scope. + * + * @param {number} [uid] + * An optional unique ID for the function to be set + * + * @return {Function} + * The new function that will be bound into the context given + */ + var bind = function bind(context, fn, uid) { + // Make sure the function has a unique ID + if (!fn.guid) { + fn.guid = newGUID(); + } + + // Create the new function that changes the context + var bound = function bound() { + return fn.apply(context, arguments); + }; + + // Allow for the ability to individualize this function + // Needed in the case where multiple objects might share the same prototype + // IF both items add an event listener with the same function, then you try to remove just one + // it will remove both because they both have the same guid. + // when using this, you need to use the bind method when you remove the listener as well. + // currently used in text tracks + bound.guid = uid ? uid + '_' + fn.guid : fn.guid; + + return bound; + }; + + /** + * Wraps the given function, `fn`, with a new function that only invokes `fn` + * at most once per every `wait` milliseconds. + * + * @param {Function} fn + * The function to be throttled. + * + * @param {Number} wait + * The number of milliseconds by which to throttle. + * + * @return {Function} + */ + var throttle = function throttle(fn, wait) { + var last = Date.now(); + + var throttled = function throttled() { + var now = Date.now(); + + if (now - last >= wait) { + fn.apply(undefined, arguments); + last = now; + } + }; + + return throttled; + }; + + /** + * Creates a debounced function that delays invoking `func` until after `wait` + * milliseconds have elapsed since the last time the debounced function was + * invoked. + * + * Inspired by lodash and underscore implementations. + * + * @param {Function} func + * The function to wrap with debounce behavior. + * + * @param {number} wait + * The number of milliseconds to wait after the last invocation. + * + * @param {boolean} [immediate] + * Whether or not to invoke the function immediately upon creation. + * + * @param {Object} [context=window] + * The "context" in which the debounced function should debounce. For + * example, if this function should be tied to a Video.js player, + * the player can be passed here. Alternatively, defaults to the + * global `window` object. + * + * @return {Function} + * A debounced function. + */ + var debounce = function debounce(func, wait, immediate) { + var context = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : window_1; + + var timeout = void 0; + + /* eslint-disable consistent-this */ + return function () { + var self = this; + var args = arguments; + + var _later = function later() { + timeout = null; + _later = null; + if (!immediate) { + func.apply(self, args); + } + }; + + if (!timeout && immediate) { + func.apply(self, args); + } + + context.clearTimeout(timeout); + timeout = context.setTimeout(_later, wait); + }; + /* eslint-enable consistent-this */ + }; + + /** + * @file src/js/event-target.js + */ + + /** + * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It + * adds shorthand functions that wrap around lengthy functions. For example: + * the `on` function is a wrapper around `addEventListener`. + * + * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} + * @class EventTarget + */ + var EventTarget = function EventTarget() {}; + + /** + * A Custom DOM event. + * + * @typedef {Object} EventTarget~Event + * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} + */ + + /** + * All event listeners should follow the following format. + * + * @callback EventTarget~EventListener + * @this {EventTarget} + * + * @param {EventTarget~Event} event + * the event that triggered this function + * + * @param {Object} [hash] + * hash of data sent during the event + */ + + /** + * An object containing event names as keys and booleans as values. + * + * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} + * will have extra functionality. See that function for more information. + * + * @property EventTarget.prototype.allowedEvents_ + * @private + */ + EventTarget.prototype.allowedEvents_ = {}; + + /** + * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a + * function that will get called when an event with a certain name gets triggered. + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to call with `EventTarget`s + */ + EventTarget.prototype.on = function (type, fn) { + // Remove the addEventListener alias before calling Events.on + // so we don't get into an infinite type loop + var ael = this.addEventListener; + + this.addEventListener = function () {}; + on(this, type, fn); + this.addEventListener = ael; + }; + + /** + * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#on} + */ + EventTarget.prototype.addEventListener = EventTarget.prototype.on; + + /** + * Removes an `event listener` for a specific event from an instance of `EventTarget`. + * This makes it so that the `event listener` will no longer get called when the + * named event happens. + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to remove. + */ + EventTarget.prototype.off = function (type, fn) { + off(this, type, fn); + }; + + /** + * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#off} + */ + EventTarget.prototype.removeEventListener = EventTarget.prototype.off; + + /** + * This function will add an `event listener` that gets triggered only once. After the + * first trigger it will get removed. This is like adding an `event listener` + * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to be called once for each event name. + */ + EventTarget.prototype.one = function (type, fn) { + // Remove the addEventListener alialing Events.on + // so we don't get into an infinite type loop + var ael = this.addEventListener; + + this.addEventListener = function () {}; + one(this, type, fn); + this.addEventListener = ael; + }; + + /** + * This function causes an event to happen. This will then cause any `event listeners` + * that are waiting for that event, to get called. If there are no `event listeners` + * for an event then nothing will happen. + * + * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. + * Trigger will also call the `on` + `uppercaseEventName` function. + * + * Example: + * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call + * `onClick` if it exists. + * + * @param {string|EventTarget~Event|Object} event + * The name of the event, an `Event`, or an object with a key of type set to + * an event name. + */ + EventTarget.prototype.trigger = function (event) { + var type = event.type || event; + + if (typeof event === 'string') { + event = { type: type }; + } + event = fixEvent(event); + + if (this.allowedEvents_[type] && this['on' + type]) { + this['on' + type](event); + } + + trigger(this, event); + }; + + /** + * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#trigger} + */ + EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger; + + /** + * @file mixins/evented.js + * @module evented + */ + + /** + * Returns whether or not an object has had the evented mixin applied. + * + * @param {Object} object + * An object to test. + * + * @return {boolean} + * Whether or not the object appears to be evented. + */ + var isEvented = function isEvented(object) { + return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) { + return typeof object[k] === 'function'; + }); + }; + + /** + * Whether a value is a valid event type - non-empty string or array. + * + * @private + * @param {string|Array} type + * The type value to test. + * + * @return {boolean} + * Whether or not the type is a valid event type. + */ + var isValidEventType = function isValidEventType(type) { + return ( + // The regex here verifies that the `type` contains at least one non- + // whitespace character. + typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length + ); + }; + + /** + * Validates a value to determine if it is a valid event target. Throws if not. + * + * @private + * @throws {Error} + * If the target does not appear to be a valid event target. + * + * @param {Object} target + * The object to test. + */ + var validateTarget = function validateTarget(target) { + if (!target.nodeName && !isEvented(target)) { + throw new Error('Invalid target; must be a DOM node or evented object.'); + } + }; + + /** + * Validates a value to determine if it is a valid event target. Throws if not. + * + * @private + * @throws {Error} + * If the type does not appear to be a valid event type. + * + * @param {string|Array} type + * The type to test. + */ + var validateEventType = function validateEventType(type) { + if (!isValidEventType(type)) { + throw new Error('Invalid event type; must be a non-empty string or array.'); + } + }; + + /** + * Validates a value to determine if it is a valid listener. Throws if not. + * + * @private + * @throws {Error} + * If the listener is not a function. + * + * @param {Function} listener + * The listener to test. + */ + var validateListener = function validateListener(listener) { + if (typeof listener !== 'function') { + throw new Error('Invalid listener; must be a function.'); + } + }; + + /** + * Takes an array of arguments given to `on()` or `one()`, validates them, and + * normalizes them into an object. + * + * @private + * @param {Object} self + * The evented object on which `on()` or `one()` was called. This + * object will be bound as the `this` value for the listener. + * + * @param {Array} args + * An array of arguments passed to `on()` or `one()`. + * + * @return {Object} + * An object containing useful values for `on()` or `one()` calls. + */ + var normalizeListenArgs = function normalizeListenArgs(self, args) { + + // If the number of arguments is less than 3, the target is always the + // evented object itself. + var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; + var target = void 0; + var type = void 0; + var listener = void 0; + + if (isTargetingSelf) { + target = self.eventBusEl_; + + // Deal with cases where we got 3 arguments, but we are still listening to + // the evented object itself. + if (args.length >= 3) { + args.shift(); + } + + type = args[0]; + listener = args[1]; + } else { + target = args[0]; + type = args[1]; + listener = args[2]; + } + + validateTarget(target); + validateEventType(type); + validateListener(listener); + + listener = bind(self, listener); + + return { isTargetingSelf: isTargetingSelf, target: target, type: type, listener: listener }; + }; + + /** + * Adds the listener to the event type(s) on the target, normalizing for + * the type of target. + * + * @private + * @param {Element|Object} target + * A DOM node or evented object. + * + * @param {string} method + * The event binding method to use ("on" or "one"). + * + * @param {string|Array} type + * One or more event type(s). + * + * @param {Function} listener + * A listener function. + */ + var listen = function listen(target, method, type, listener) { + validateTarget(target); + + if (target.nodeName) { + Events[method](target, type, listener); + } else { + target[method](type, listener); + } + }; + + /** + * Contains methods that provide event capabilities to an object which is passed + * to {@link module:evented|evented}. + * + * @mixin EventedMixin + */ + var EventedMixin = { + + /** + * Add a listener to an event (or events) on this object or another evented + * object. + * + * @param {string|Array|Element|Object} targetOrType + * If this is a string or array, it represents the event type(s) + * that will trigger the listener. + * + * Another evented object can be passed here instead, which will + * cause the listener to listen for events on _that_ object. + * + * In either case, the listener's `this` value will be bound to + * this object. + * + * @param {string|Array|Function} typeOrListener + * If the first argument was a string or array, this should be the + * listener function. Otherwise, this is a string or array of event + * type(s). + * + * @param {Function} [listener] + * If the first argument was another evented object, this will be + * the listener function. + */ + on: function on$$1() { + var _this = this; + + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + var _normalizeListenArgs = normalizeListenArgs(this, args), + isTargetingSelf = _normalizeListenArgs.isTargetingSelf, + target = _normalizeListenArgs.target, + type = _normalizeListenArgs.type, + listener = _normalizeListenArgs.listener; + + listen(target, 'on', type, listener); + + // If this object is listening to another evented object. + if (!isTargetingSelf) { + + // If this object is disposed, remove the listener. + var removeListenerOnDispose = function removeListenerOnDispose() { + return _this.off(target, type, listener); + }; + + // Use the same function ID as the listener so we can remove it later it + // using the ID of the original listener. + removeListenerOnDispose.guid = listener.guid; + + // Add a listener to the target's dispose event as well. This ensures + // that if the target is disposed BEFORE this object, we remove the + // removal listener that was just added. Otherwise, we create a memory leak. + var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() { + return _this.off('dispose', removeListenerOnDispose); + }; + + // Use the same function ID as the listener so we can remove it later + // it using the ID of the original listener. + removeRemoverOnTargetDispose.guid = listener.guid; + + listen(this, 'on', 'dispose', removeListenerOnDispose); + listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); + } + }, + + + /** + * Add a listener to an event (or events) on this object or another evented + * object. The listener will only be called once and then removed. + * + * @param {string|Array|Element|Object} targetOrType + * If this is a string or array, it represents the event type(s) + * that will trigger the listener. + * + * Another evented object can be passed here instead, which will + * cause the listener to listen for events on _that_ object. + * + * In either case, the listener's `this` value will be bound to + * this object. + * + * @param {string|Array|Function} typeOrListener + * If the first argument was a string or array, this should be the + * listener function. Otherwise, this is a string or array of event + * type(s). + * + * @param {Function} [listener] + * If the first argument was another evented object, this will be + * the listener function. + */ + one: function one$$1() { + var _this2 = this; + + for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + var _normalizeListenArgs2 = normalizeListenArgs(this, args), + isTargetingSelf = _normalizeListenArgs2.isTargetingSelf, + target = _normalizeListenArgs2.target, + type = _normalizeListenArgs2.type, + listener = _normalizeListenArgs2.listener; + + // Targeting this evented object. + + + if (isTargetingSelf) { + listen(target, 'one', type, listener); + + // Targeting another evented object. + } else { + var wrapper = function wrapper() { + for (var _len3 = arguments.length, largs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + largs[_key3] = arguments[_key3]; + } + + _this2.off(target, type, wrapper); + listener.apply(null, largs); + }; + + // Use the same function ID as the listener so we can remove it later + // it using the ID of the original listener. + wrapper.guid = listener.guid; + listen(target, 'one', type, wrapper); + } + }, + + + /** + * Removes listener(s) from event(s) on an evented object. + * + * @param {string|Array|Element|Object} [targetOrType] + * If this is a string or array, it represents the event type(s). + * + * Another evented object can be passed here instead, in which case + * ALL 3 arguments are _required_. + * + * @param {string|Array|Function} [typeOrListener] + * If the first argument was a string or array, this may be the + * listener function. Otherwise, this is a string or array of event + * type(s). + * + * @param {Function} [listener] + * If the first argument was another evented object, this will be + * the listener function; otherwise, _all_ listeners bound to the + * event type(s) will be removed. + */ + off: function off$$1(targetOrType, typeOrListener, listener) { + + // Targeting this evented object. + if (!targetOrType || isValidEventType(targetOrType)) { + off(this.eventBusEl_, targetOrType, typeOrListener); + + // Targeting another evented object. + } else { + var target = targetOrType; + var type = typeOrListener; + + // Fail fast and in a meaningful way! + validateTarget(target); + validateEventType(type); + validateListener(listener); + + // Ensure there's at least a guid, even if the function hasn't been used + listener = bind(this, listener); + + // Remove the dispose listener on this evented object, which was given + // the same guid as the event listener in on(). + this.off('dispose', listener); + + if (target.nodeName) { + off(target, type, listener); + off(target, 'dispose', listener); + } else if (isEvented(target)) { + target.off(type, listener); + target.off('dispose', listener); + } + } + }, + + + /** + * Fire an event on this evented object, causing its listeners to be called. + * + * @param {string|Object} event + * An event type or an object with a type property. + * + * @param {Object} [hash] + * An additional object to pass along to listeners. + * + * @returns {boolean} + * Whether or not the default behavior was prevented. + */ + trigger: function trigger$$1(event, hash) { + return trigger(this.eventBusEl_, event, hash); + } + }; + + /** + * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. + * + * @param {Object} target + * The object to which to add event methods. + * + * @param {Object} [options={}] + * Options for customizing the mixin behavior. + * + * @param {String} [options.eventBusKey] + * By default, adds a `eventBusEl_` DOM element to the target object, + * which is used as an event bus. If the target object already has a + * DOM element that should be used, pass its key here. + * + * @return {Object} + * The target object. + */ + function evented(target) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var eventBusKey = options.eventBusKey; + + // Set or create the eventBusEl_. + + if (eventBusKey) { + if (!target[eventBusKey].nodeName) { + throw new Error('The eventBusKey "' + eventBusKey + '" does not refer to an element.'); + } + target.eventBusEl_ = target[eventBusKey]; + } else { + target.eventBusEl_ = createEl('span', { className: 'vjs-event-bus' }); + } + + assign(target, EventedMixin); + + // When any evented object is disposed, it removes all its listeners. + target.on('dispose', function () { + target.off(); + window_1.setTimeout(function () { + target.eventBusEl_ = null; + }, 0); + }); + + return target; + } + + /** + * @file mixins/stateful.js + * @module stateful + */ + + /** + * Contains methods that provide statefulness to an object which is passed + * to {@link module:stateful}. + * + * @mixin StatefulMixin + */ + var StatefulMixin = { + + /** + * A hash containing arbitrary keys and values representing the state of + * the object. + * + * @type {Object} + */ + state: {}, + + /** + * Set the state of an object by mutating its + * {@link module:stateful~StatefulMixin.state|state} object in place. + * + * @fires module:stateful~StatefulMixin#statechanged + * @param {Object|Function} stateUpdates + * A new set of properties to shallow-merge into the plugin state. + * Can be a plain object or a function returning a plain object. + * + * @returns {Object|undefined} + * An object containing changes that occurred. If no changes + * occurred, returns `undefined`. + */ + setState: function setState(stateUpdates) { + var _this = this; + + // Support providing the `stateUpdates` state as a function. + if (typeof stateUpdates === 'function') { + stateUpdates = stateUpdates(); + } + + var changes = void 0; + + each(stateUpdates, function (value, key) { + + // Record the change if the value is different from what's in the + // current state. + if (_this.state[key] !== value) { + changes = changes || {}; + changes[key] = { + from: _this.state[key], + to: value + }; + } + + _this.state[key] = value; + }); + + // Only trigger "statechange" if there were changes AND we have a trigger + // function. This allows us to not require that the target object be an + // evented object. + if (changes && isEvented(this)) { + + /** + * An event triggered on an object that is both + * {@link module:stateful|stateful} and {@link module:evented|evented} + * indicating that its state has changed. + * + * @event module:stateful~StatefulMixin#statechanged + * @type {Object} + * @property {Object} changes + * A hash containing the properties that were changed and + * the values they were changed `from` and `to`. + */ + this.trigger({ + changes: changes, + type: 'statechanged' + }); + } + + return changes; + } + }; + + /** + * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target + * object. + * + * If the target object is {@link module:evented|evented} and has a + * `handleStateChanged` method, that method will be automatically bound to the + * `statechanged` event on itself. + * + * @param {Object} target + * The object to be made stateful. + * + * @param {Object} [defaultState] + * A default set of properties to populate the newly-stateful object's + * `state` property. + * + * @returns {Object} + * Returns the `target`. + */ + function stateful(target, defaultState) { + assign(target, StatefulMixin); + + // This happens after the mixing-in because we need to replace the `state` + // added in that step. + target.state = assign({}, target.state, defaultState); + + // Auto-bind the `handleStateChanged` method of the target object if it exists. + if (typeof target.handleStateChanged === 'function' && isEvented(target)) { + target.on('statechanged', target.handleStateChanged); + } + + return target; + } + + /** + * @file to-title-case.js + * @module to-title-case + */ + + /** + * Uppercase the first letter of a string. + * + * @param {string} string + * String to be uppercased + * + * @return {string} + * The string with an uppercased first letter + */ + function toTitleCase(string) { + if (typeof string !== 'string') { + return string; + } + + return string.charAt(0).toUpperCase() + string.slice(1); + } + + /** + * Compares the TitleCase versions of the two strings for equality. + * + * @param {string} str1 + * The first string to compare + * + * @param {string} str2 + * The second string to compare + * + * @return {boolean} + * Whether the TitleCase versions of the strings are equal + */ + function titleCaseEquals(str1, str2) { + return toTitleCase(str1) === toTitleCase(str2); + } + + /** + * @file merge-options.js + * @module merge-options + */ + + /** + * Deep-merge one or more options objects, recursively merging **only** plain + * object properties. + * + * @param {Object[]} sources + * One or more objects to merge into a new object. + * + * @returns {Object} + * A new object that is the merged result of all sources. + */ + function mergeOptions() { + var result = {}; + + for (var _len = arguments.length, sources = Array(_len), _key = 0; _key < _len; _key++) { + sources[_key] = arguments[_key]; + } + + sources.forEach(function (source) { + if (!source) { + return; + } + + each(source, function (value, key) { + if (!isPlain(value)) { + result[key] = value; + return; + } + + if (!isPlain(result[key])) { + result[key] = {}; + } + + result[key] = mergeOptions(result[key], value); + }); + }); + + return result; + } + + /** + * Player Component - Base class for all UI objects + * + * @file component.js + */ + + /** + * Base class for all UI Components. + * Components are UI objects which represent both a javascript object and an element + * in the DOM. They can be children of other components, and can have + * children themselves. + * + * Components can also use methods from {@link EventTarget} + */ + + var Component = function () { + + /** + * A callback that is called when a component is ready. Does not have any + * paramters and any callback value will be ignored. + * + * @callback Component~ReadyCallback + * @this Component + */ + + /** + * 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. + * + * @param {Object[]} [options.children] + * An array of children objects to intialize this component with. Children objects have + * a name property that will be used if more than one component of the same type needs to be + * added. + * + * @param {Component~ReadyCallback} [ready] + * Function that gets called when the `Component` is ready. + */ + function Component(player, options, ready) { + classCallCheck(this, Component); + + + // The component might be the player itself and we can't pass `this` to super + if (!player && this.play) { + this.player_ = player = this; // eslint-disable-line + } else { + this.player_ = player; + } + + // Make a copy of prototype.options_ to protect against overriding defaults + this.options_ = mergeOptions({}, this.options_); + + // Updated options with supplied options + options = this.options_ = mergeOptions(this.options_, options); + + // Get ID from options or options element if one is supplied + this.id_ = options.id || options.el && options.el.id; + + // If there was no ID from the options, generate one + if (!this.id_) { + // Don't require the player ID function in the case of mock players + var id = player && player.id && player.id() || 'no_player'; + + this.id_ = id + '_component_' + newGUID(); + } + + this.name_ = options.name || null; + + // Create element if one wasn't provided in options + if (options.el) { + this.el_ = options.el; + } else if (options.createEl !== false) { + this.el_ = this.createEl(); + } + + // if evented is anything except false, we want to mixin in evented + if (options.evented !== false) { + // Make this an evented object and use `el_`, if available, as its event bus + evented(this, { eventBusKey: this.el_ ? 'el_' : null }); + } + stateful(this, this.constructor.defaultState); + + this.children_ = []; + this.childIndex_ = {}; + this.childNameIndex_ = {}; + + // Add any child components in options + if (options.initChildren !== false) { + this.initChildren(); + } + + this.ready(ready); + // Don't want to trigger ready here or it will before init is actually + // finished for all children that run this constructor + + if (options.reportTouchActivity !== false) { + this.enableTouchActivity(); + } + } + + /** + * Dispose of the `Component` and all child components. + * + * @fires Component#dispose + */ + + + Component.prototype.dispose = function dispose() { + + /** + * Triggered when a `Component` is disposed. + * + * @event Component#dispose + * @type {EventTarget~Event} + * + * @property {boolean} [bubbles=false] + * set to false so that the close event does not + * bubble up + */ + this.trigger({ type: 'dispose', bubbles: false }); + + // Dispose all children. + if (this.children_) { + for (var i = this.children_.length - 1; i >= 0; i--) { + if (this.children_[i].dispose) { + this.children_[i].dispose(); + } + } + } + + // Delete child references + this.children_ = null; + this.childIndex_ = null; + this.childNameIndex_ = null; + + if (this.el_) { + // Remove element from DOM + if (this.el_.parentNode) { + this.el_.parentNode.removeChild(this.el_); + } + + removeData(this.el_); + this.el_ = null; + } + + // remove reference to the player after disposing of the element + this.player_ = null; + }; + + /** + * Return the {@link Player} that the `Component` has attached to. + * + * @return {Player} + * The player that this `Component` has attached to. + */ + + + Component.prototype.player = function player() { + return this.player_; + }; + + /** + * Deep merge of options objects with new options. + * > Note: When both `obj` and `options` contain properties whose values are objects. + * The two properties get merged using {@link module:mergeOptions} + * + * @param {Object} obj + * The object that contains new options. + * + * @return {Object} + * A new object of `this.options_` and `obj` merged together. + * + * @deprecated since version 5 + */ + + + Component.prototype.options = function options(obj) { + log$1.warn('this.options() has been deprecated and will be moved to the constructor in 6.0'); + + if (!obj) { + return this.options_; + } + + this.options_ = mergeOptions(this.options_, obj); + return this.options_; + }; + + /** + * Get the `Component`s DOM element + * + * @return {Element} + * The DOM element for this `Component`. + */ + + + Component.prototype.el = function el() { + return this.el_; + }; + + /** + * Create the `Component`s DOM element. + * + * @param {string} [tagName] + * Element's DOM node type. e.g. 'div' + * + * @param {Object} [properties] + * An object of properties that should be set. + * + * @param {Object} [attributes] + * An object of attributes that should be set. + * + * @return {Element} + * The element that gets created. + */ + + + Component.prototype.createEl = function createEl$$1(tagName, properties, attributes) { + return createEl(tagName, properties, attributes); + }; + + /** + * Localize a string given the string in english. + * + * If tokens are provided, it'll try and run a simple token replacement on the provided string. + * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array. + * + * If a `defaultValue` is provided, it'll use that over `string`, + * if a value isn't found in provided language files. + * This is useful if you want to have a descriptive key for token replacement + * but have a succinct localized string and not require `en.json` to be included. + * + * Currently, it is used for the progress bar timing. + * ```js + * { + * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" + * } + * ``` + * It is then used like so: + * ```js + * this.localize('progress bar timing: currentTime={1} duration{2}', + * [this.player_.currentTime(), this.player_.duration()], + * '{1} of {2}'); + * ``` + * + * Which outputs something like: `01:23 of 24:56`. + * + * + * @param {string} string + * The string to localize and the key to lookup in the language files. + * @param {string[]} [tokens] + * If the current item has token replacements, provide the tokens here. + * @param {string} [defaultValue] + * Defaults to `string`. Can be a default value to use for token replacement + * if the lookup key is needed to be separate. + * + * @return {string} + * The localized string or if no localization exists the english string. + */ + + + Component.prototype.localize = function localize(string, tokens) { + var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : string; + + var code = this.player_.language && this.player_.language(); + var languages = this.player_.languages && this.player_.languages(); + var language = languages && languages[code]; + var primaryCode = code && code.split('-')[0]; + var primaryLang = languages && languages[primaryCode]; + + var localizedString = defaultValue; + + if (language && language[string]) { + localizedString = language[string]; + } else if (primaryLang && primaryLang[string]) { + localizedString = primaryLang[string]; + } + + if (tokens) { + localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { + var value = tokens[index - 1]; + var ret = value; + + if (typeof value === 'undefined') { + ret = match; + } + + return ret; + }); + } + + return localizedString; + }; + + /** + * Return the `Component`s DOM element. This is where children get inserted. + * This will usually be the the same as the element returned in {@link Component#el}. + * + * @return {Element} + * The content element for this `Component`. + */ + + + Component.prototype.contentEl = function contentEl() { + return this.contentEl_ || this.el_; + }; + + /** + * Get this `Component`s ID + * + * @return {string} + * The id of this `Component` + */ + + + Component.prototype.id = function id() { + return this.id_; + }; + + /** + * Get the `Component`s name. The name gets used to reference the `Component` + * and is set during registration. + * + * @return {string} + * The name of this `Component`. + */ + + + Component.prototype.name = function name() { + return this.name_; + }; + + /** + * Get an array of all child components + * + * @return {Array} + * The children + */ + + + Component.prototype.children = function children() { + return this.children_; + }; + + /** + * Returns the child `Component` with the given `id`. + * + * @param {string} id + * The id of the child `Component` to get. + * + * @return {Component|undefined} + * The child `Component` with the given `id` or undefined. + */ + + + Component.prototype.getChildById = function getChildById(id) { + return this.childIndex_[id]; + }; + + /** + * Returns the child `Component` with the given `name`. + * + * @param {string} name + * The name of the child `Component` to get. + * + * @return {Component|undefined} + * The child `Component` with the given `name` or undefined. + */ + + + Component.prototype.getChild = function getChild(name) { + if (!name) { + return; + } + + name = toTitleCase(name); + + return this.childNameIndex_[name]; + }; + + /** + * Add a child `Component` inside the current `Component`. + * + * + * @param {string|Component} child + * The name or instance of a child to add. + * + * @param {Object} [options={}] + * The key/value store of options that will get passed to children of + * the child. + * + * @param {number} [index=this.children_.length] + * The index to attempt to add a child into. + * + * @return {Component} + * The `Component` that gets added as a child. When using a string the + * `Component` will get created by this process. + */ + + + Component.prototype.addChild = function addChild(child) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.children_.length; + + var component = void 0; + var componentName = void 0; + + // If child is a string, create component with options + if (typeof child === 'string') { + componentName = toTitleCase(child); + + var componentClassName = options.componentClass || componentName; + + // Set name through options + options.name = componentName; + + // Create a new object & element for this controls set + // If there's no .player_, this is a player + var ComponentClass = Component.getComponent(componentClassName); + + if (!ComponentClass) { + throw new Error('Component ' + componentClassName + ' does not exist'); + } + + // data stored directly on the videojs object may be + // misidentified as a component to retain + // backwards-compatibility with 4.x. check to make sure the + // component class can be instantiated. + if (typeof ComponentClass !== 'function') { + return null; + } + + component = new ComponentClass(this.player_ || this, options); + + // child is a component instance + } else { + component = child; + } + + this.children_.splice(index, 0, component); + + if (typeof component.id === 'function') { + this.childIndex_[component.id()] = component; + } + + // If a name wasn't used to create the component, check if we can use the + // name function of the component + componentName = componentName || component.name && toTitleCase(component.name()); + + if (componentName) { + this.childNameIndex_[componentName] = component; + } + + // Add the UI object's element to the container div (box) + // Having an element is not required + if (typeof component.el === 'function' && component.el()) { + var childNodes = this.contentEl().children; + var refNode = childNodes[index] || null; + + this.contentEl().insertBefore(component.el(), refNode); + } + + // Return so it can stored on parent object if desired. + return component; + }; + + /** + * Remove a child `Component` from this `Component`s list of children. Also removes + * the child `Component`s element from this `Component`s element. + * + * @param {Component} component + * The child `Component` to remove. + */ + + + Component.prototype.removeChild = function removeChild(component) { + if (typeof component === 'string') { + component = this.getChild(component); + } + + if (!component || !this.children_) { + return; + } + + var childFound = false; + + for (var i = this.children_.length - 1; i >= 0; i--) { + if (this.children_[i] === component) { + childFound = true; + this.children_.splice(i, 1); + break; + } + } + + if (!childFound) { + return; + } + + this.childIndex_[component.id()] = null; + this.childNameIndex_[component.name()] = null; + + var compEl = component.el(); + + if (compEl && compEl.parentNode === this.contentEl()) { + this.contentEl().removeChild(component.el()); + } + }; + + /** + * Add and initialize default child `Component`s based upon options. + */ + + + Component.prototype.initChildren = function initChildren() { + var _this = this; + + var children = this.options_.children; + + if (children) { + // `this` is `parent` + var parentOptions = this.options_; + + var handleAdd = function handleAdd(child) { + var name = child.name; + var opts = child.opts; + + // Allow options for children to be set at the parent options + // e.g. videojs(id, { controlBar: false }); + // instead of videojs(id, { children: { controlBar: false }); + if (parentOptions[name] !== undefined) { + opts = parentOptions[name]; + } + + // Allow for disabling default components + // e.g. options['children']['posterImage'] = false + if (opts === false) { + return; + } + + // Allow options to be passed as a simple boolean if no configuration + // is necessary. + if (opts === true) { + opts = {}; + } + + // We also want to pass the original player options + // to each component as well so they don't need to + // reach back into the player for options later. + opts.playerOptions = _this.options_.playerOptions; + + // Create and add the child component. + // Add a direct reference to the child by name on the parent instance. + // If two of the same component are used, different names should be supplied + // for each + var newChild = _this.addChild(name, opts); + + if (newChild) { + _this[name] = newChild; + } + }; + + // Allow for an array of children details to passed in the options + var workingChildren = void 0; + var Tech = Component.getComponent('Tech'); + + if (Array.isArray(children)) { + workingChildren = children; + } else { + workingChildren = Object.keys(children); + } + + workingChildren + // children that are in this.options_ but also in workingChildren would + // give us extra children we do not want. So, we want to filter them out. + .concat(Object.keys(this.options_).filter(function (child) { + return !workingChildren.some(function (wchild) { + if (typeof wchild === 'string') { + return child === wchild; + } + return child === wchild.name; + }); + })).map(function (child) { + var name = void 0; + var opts = void 0; + + if (typeof child === 'string') { + name = child; + opts = children[name] || _this.options_[name] || {}; + } else { + name = child.name; + opts = child; + } + + return { name: name, opts: opts }; + }).filter(function (child) { + // we have to make sure that child.name isn't in the techOrder since + // techs are registerd as Components but can't aren't compatible + // See https://github.com/videojs/video.js/issues/2772 + var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name)); + + return c && !Tech.isTech(c); + }).forEach(handleAdd); + } + }; + + /** + * Builds the default DOM class name. Should be overriden by sub-components. + * + * @return {string} + * The DOM class name for this object. + * + * @abstract + */ + + + Component.prototype.buildCSSClass = function buildCSSClass() { + // Child classes can include a function that does: + // return 'CLASS NAME' + this._super(); + return ''; + }; + + /** + * Bind a listener to the component's ready state. + * Different from event listeners in that if the ready event has already happened + * it will trigger the function immediately. + * + * @return {Component} + * Returns itself; method can be chained. + */ + + + Component.prototype.ready = function ready(fn) { + var sync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + if (!fn) { + return; + } + + if (!this.isReady_) { + this.readyQueue_ = this.readyQueue_ || []; + this.readyQueue_.push(fn); + return; + } + + if (sync) { + fn.call(this); + } else { + // Call the function asynchronously by default for consistency + this.setTimeout(fn, 1); + } + }; + + /** + * Trigger all the ready listeners for this `Component`. + * + * @fires Component#ready + */ + + + Component.prototype.triggerReady = function triggerReady() { + this.isReady_ = true; + + // Ensure ready is triggered asynchronously + this.setTimeout(function () { + var readyQueue = this.readyQueue_; + + // Reset Ready Queue + this.readyQueue_ = []; + + if (readyQueue && readyQueue.length > 0) { + readyQueue.forEach(function (fn) { + fn.call(this); + }, this); + } + + // Allow for using event listeners also + /** + * Triggered when a `Component` is ready. + * + * @event Component#ready + * @type {EventTarget~Event} + */ + this.trigger('ready'); + }, 1); + }; + + /** + * Find a single DOM element matching a `selector`. This can be within the `Component`s + * `contentEl()` or another custom context. + * + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelector`. + * + * @param {Element|string} [context=this.contentEl()] + * A DOM element within which to query. Can also be a selector string in + * which case the first matching element will get used as context. If + * missing `this.contentEl()` gets used. If `this.contentEl()` returns + * nothing it falls back to `document`. + * + * @return {Element|null} + * the dom element that was found, or null + * + * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) + */ + + + Component.prototype.$ = function $$$1(selector, context) { + return $(selector, context || this.contentEl()); + }; + + /** + * Finds all DOM element matching a `selector`. This can be within the `Component`s + * `contentEl()` or another custom context. + * + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelectorAll`. + * + * @param {Element|string} [context=this.contentEl()] + * A DOM element within which to query. Can also be a selector string in + * which case the first matching element will get used as context. If + * missing `this.contentEl()` gets used. If `this.contentEl()` returns + * nothing it falls back to `document`. + * + * @return {NodeList} + * a list of dom elements that were found + * + * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) + */ + + + Component.prototype.$$ = function $$$$1(selector, context) { + return $$(selector, context || this.contentEl()); + }; + + /** + * Check if a component's element has a CSS class name. + * + * @param {string} classToCheck + * CSS class name to check. + * + * @return {boolean} + * - True if the `Component` has the class. + * - False if the `Component` does not have the class` + */ + + + Component.prototype.hasClass = function hasClass$$1(classToCheck) { + return hasClass(this.el_, classToCheck); + }; + + /** + * Add a CSS class name to the `Component`s element. + * + * @param {string} classToAdd + * CSS class name to add + */ + + + Component.prototype.addClass = function addClass$$1(classToAdd) { + addClass(this.el_, classToAdd); + }; + + /** + * Remove a CSS class name from the `Component`s element. + * + * @param {string} classToRemove + * CSS class name to remove + */ + + + Component.prototype.removeClass = function removeClass$$1(classToRemove) { + removeClass(this.el_, classToRemove); + }; + + /** + * Add or remove a CSS class name from the component's element. + * - `classToToggle` gets added when {@link Component#hasClass} would return false. + * - `classToToggle` gets removed when {@link Component#hasClass} would return true. + * + * @param {string} classToToggle + * The class to add or remove based on (@link Component#hasClass} + * + * @param {boolean|Dom~predicate} [predicate] + * An {@link Dom~predicate} function or a boolean + */ + + + Component.prototype.toggleClass = function toggleClass$$1(classToToggle, predicate) { + toggleClass(this.el_, classToToggle, predicate); + }; + + /** + * Show the `Component`s element if it is hidden by removing the + * 'vjs-hidden' class name from it. + */ + + + Component.prototype.show = function show() { + this.removeClass('vjs-hidden'); + }; + + /** + * Hide the `Component`s element if it is currently showing by adding the + * 'vjs-hidden` class name to it. + */ + + + Component.prototype.hide = function hide() { + this.addClass('vjs-hidden'); + }; + + /** + * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' + * class name to it. Used during fadeIn/fadeOut. + * + * @private + */ + + + Component.prototype.lockShowing = function lockShowing() { + this.addClass('vjs-lock-showing'); + }; + + /** + * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' + * class name from it. Used during fadeIn/fadeOut. + * + * @private + */ + + + Component.prototype.unlockShowing = function unlockShowing() { + this.removeClass('vjs-lock-showing'); + }; + + /** + * Get the value of an attribute on the `Component`s element. + * + * @param {string} attribute + * Name of the attribute to get the value from. + * + * @return {string|null} + * - The value of the attribute that was asked for. + * - Can be an empty string on some browsers if the attribute does not exist + * or has no value + * - Most browsers will return null if the attibute does not exist or has + * no value. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} + */ + + + Component.prototype.getAttribute = function getAttribute$$1(attribute) { + return getAttribute(this.el_, attribute); + }; + + /** + * Set the value of an attribute on the `Component`'s element + * + * @param {string} attribute + * Name of the attribute to set. + * + * @param {string} value + * Value to set the attribute to. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} + */ + + + Component.prototype.setAttribute = function setAttribute$$1(attribute, value) { + setAttribute(this.el_, attribute, value); + }; + + /** + * Remove an attribute from the `Component`s element. + * + * @param {string} attribute + * Name of the attribute to remove. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} + */ + + + Component.prototype.removeAttribute = function removeAttribute$$1(attribute) { + removeAttribute(this.el_, attribute); + }; + + /** + * Get or set the width of the component based upon the CSS styles. + * See {@link Component#dimension} for more detailed information. + * + * @param {number|string} [num] + * The width that you want to set postfixed with '%', 'px' or nothing. + * + * @param {boolean} [skipListeners] + * Skip the componentresize event trigger + * + * @return {number|string} + * The width when getting, zero if there is no width. Can be a string + * postpixed with '%' or 'px'. + */ + + + Component.prototype.width = function width(num, skipListeners) { + return this.dimension('width', num, skipListeners); + }; + + /** + * Get or set the height of the component based upon the CSS styles. + * See {@link Component#dimension} for more detailed information. + * + * @param {number|string} [num] + * The height that you want to set postfixed with '%', 'px' or nothing. + * + * @param {boolean} [skipListeners] + * Skip the componentresize event trigger + * + * @return {number|string} + * The width when getting, zero if there is no width. Can be a string + * postpixed with '%' or 'px'. + */ + + + Component.prototype.height = function height(num, skipListeners) { + return this.dimension('height', num, skipListeners); + }; + + /** + * Set both the width and height of the `Component` element at the same time. + * + * @param {number|string} width + * Width to set the `Component`s element to. + * + * @param {number|string} height + * Height to set the `Component`s element to. + */ + + + Component.prototype.dimensions = function dimensions(width, height) { + // Skip componentresize listeners on width for optimization + this.width(width, true); + this.height(height); + }; + + /** + * Get or set width or height of the `Component` element. This is the shared code + * for the {@link Component#width} and {@link Component#height}. + * + * Things to know: + * - If the width or height in an number this will return the number postfixed with 'px'. + * - If the width/height is a percent this will return the percent postfixed with '%' + * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function + * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. + * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/} + * for more information + * - If you want the computed style of the component, use {@link Component#currentWidth} + * and {@link {Component#currentHeight} + * + * @fires Component#componentresize + * + * @param {string} widthOrHeight + 8 'width' or 'height' + * + * @param {number|string} [num] + 8 New dimension + * + * @param {boolean} [skipListeners] + * Skip componentresize event trigger + * + * @return {number} + * The dimension when getting or 0 if unset + */ + + + Component.prototype.dimension = function dimension(widthOrHeight, num, skipListeners) { + if (num !== undefined) { + // Set to zero if null or literally NaN (NaN !== NaN) + if (num === null || num !== num) { + num = 0; + } + + // Check if using css width/height (% or px) and adjust + if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) { + this.el_.style[widthOrHeight] = num; + } else if (num === 'auto') { + this.el_.style[widthOrHeight] = ''; + } else { + this.el_.style[widthOrHeight] = num + 'px'; + } + + // skipListeners allows us to avoid triggering the resize event when setting both width and height + if (!skipListeners) { + /** + * Triggered when a component is resized. + * + * @event Component#componentresize + * @type {EventTarget~Event} + */ + this.trigger('componentresize'); + } + + return; + } + + // Not setting a value, so getting it + // Make sure element exists + if (!this.el_) { + return 0; + } + + // Get dimension value from style + var val = this.el_.style[widthOrHeight]; + var pxIndex = val.indexOf('px'); + + if (pxIndex !== -1) { + // Return the pixel value with no 'px' + return parseInt(val.slice(0, pxIndex), 10); + } + + // No px so using % or no style was set, so falling back to offsetWidth/height + // If component has display:none, offset will return 0 + // TODO: handle display:none and no dimension style using px + return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10); + }; + + /** + * Get the width or the height of the `Component` elements computed style. Uses + * `window.getComputedStyle`. + * + * @param {string} widthOrHeight + * A string containing 'width' or 'height'. Whichever one you want to get. + * + * @return {number} + * The dimension that gets asked for or 0 if nothing was set + * for that dimension. + */ + + + Component.prototype.currentDimension = function currentDimension(widthOrHeight) { + var computedWidthOrHeight = 0; + + if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { + throw new Error('currentDimension only accepts width or height value'); + } + + if (typeof window_1.getComputedStyle === 'function') { + var computedStyle = window_1.getComputedStyle(this.el_); + + computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight]; + } + + // remove 'px' from variable and parse as integer + computedWidthOrHeight = parseFloat(computedWidthOrHeight); + + // if the computed value is still 0, it's possible that the browser is lying + // and we want to check the offset values. + // This code also runs wherever getComputedStyle doesn't exist. + if (computedWidthOrHeight === 0) { + var rule = 'offset' + toTitleCase(widthOrHeight); + + computedWidthOrHeight = this.el_[rule]; + } + + return computedWidthOrHeight; + }; + + /** + * An object that contains width and height values of the `Component`s + * computed style. Uses `window.getComputedStyle`. + * + * @typedef {Object} Component~DimensionObject + * + * @property {number} width + * The width of the `Component`s computed style. + * + * @property {number} height + * The height of the `Component`s computed style. + */ + + /** + * Get an object that contains width and height values of the `Component`s + * computed style. + * + * @return {Component~DimensionObject} + * The dimensions of the components element + */ + + + Component.prototype.currentDimensions = function currentDimensions() { + return { + width: this.currentDimension('width'), + height: this.currentDimension('height') + }; + }; + + /** + * Get the width of the `Component`s computed style. Uses `window.getComputedStyle`. + * + * @return {number} width + * The width of the `Component`s computed style. + */ + + + Component.prototype.currentWidth = function currentWidth() { + return this.currentDimension('width'); + }; + + /** + * Get the height of the `Component`s computed style. Uses `window.getComputedStyle`. + * + * @return {number} height + * The height of the `Component`s computed style. + */ + + + Component.prototype.currentHeight = function currentHeight() { + return this.currentDimension('height'); + }; + + /** + * Set the focus to this component + */ + + + Component.prototype.focus = function focus() { + this.el_.focus(); + }; + + /** + * Remove the focus from this component + */ + + + Component.prototype.blur = function blur() { + this.el_.blur(); + }; + + /** + * Emit a 'tap' events when touch event support gets detected. This gets used to + * support toggling the controls through a tap on the video. They get enabled + * because every sub-component would have extra overhead otherwise. + * + * @private + * @fires Component#tap + * @listens Component#touchstart + * @listens Component#touchmove + * @listens Component#touchleave + * @listens Component#touchcancel + * @listens Component#touchend + */ + + + Component.prototype.emitTapEvents = function emitTapEvents() { + // Track the start time so we can determine how long the touch lasted + var touchStart = 0; + var firstTouch = null; + + // Maximum movement allowed during a touch event to still be considered a tap + // Other popular libs use anywhere from 2 (hammer.js) to 15, + // so 10 seems like a nice, round number. + var tapMovementThreshold = 10; + + // The maximum length a touch can be while still being considered a tap + var touchTimeThreshold = 200; + + var couldBeTap = void 0; + + this.on('touchstart', function (event) { + // If more than one finger, don't consider treating this as a click + if (event.touches.length === 1) { + // Copy pageX/pageY from the object + firstTouch = { + pageX: event.touches[0].pageX, + pageY: event.touches[0].pageY + }; + // Record start time so we can detect a tap vs. "touch and hold" + touchStart = new Date().getTime(); + // Reset couldBeTap tracking + couldBeTap = true; + } + }); + + this.on('touchmove', function (event) { + // If more than one finger, don't consider treating this as a click + if (event.touches.length > 1) { + couldBeTap = false; + } else if (firstTouch) { + // Some devices will throw touchmoves for all but the slightest of taps. + // So, if we moved only a small distance, this could still be a tap + var xdiff = event.touches[0].pageX - firstTouch.pageX; + var ydiff = event.touches[0].pageY - firstTouch.pageY; + var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); + + if (touchDistance > tapMovementThreshold) { + couldBeTap = false; + } + } + }); + + var noTap = function noTap() { + couldBeTap = false; + }; + + // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s + this.on('touchleave', noTap); + this.on('touchcancel', noTap); + + // When the touch ends, measure how long it took and trigger the appropriate + // event + this.on('touchend', function (event) { + firstTouch = null; + // Proceed only if the touchmove/leave/cancel event didn't happen + if (couldBeTap === true) { + // Measure how long the touch lasted + var touchTime = new Date().getTime() - 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 + event.preventDefault(); + /** + * Triggered when a `Component` is tapped. + * + * @event Component#tap + * @type {EventTarget~Event} + */ + this.trigger('tap'); + // It may be good to copy the touchend event object and change the + // type to tap, if the other event properties aren't exact after + // Events.fixEvent runs (e.g. event.target) + } + } + }); + }; + + /** + * This function reports user activity whenever touch events happen. This can get + * turned off by any sub-components that wants touch events to act another way. + * + * Report user touch activity when touch events occur. User activity gets used to + * determine when controls should show/hide. It is simple when it comes to mouse + * events, because any mouse event should show the controls. So we capture mouse + * events that bubble up to the player and report activity when that happens. + * With touch events it isn't as easy as `touchstart` and `touchend` toggle player + * controls. So touch events can't help us at the player level either. + * + * User activity gets checked asynchronously. So what could happen is a tap event + * on the video turns the controls off. Then the `touchend` event bubbles up to + * the player. Which, if it reported user activity, would turn the controls right + * back on. We also don't want to completely block touch events from bubbling up. + * Furthermore a `touchmove` event and anything other than a tap, should not turn + * controls back on. + * + * @listens Component#touchstart + * @listens Component#touchmove + * @listens Component#touchend + * @listens Component#touchcancel + */ + + + Component.prototype.enableTouchActivity = function enableTouchActivity() { + // Don't continue if the root player doesn't support reporting user activity + if (!this.player() || !this.player().reportUserActivity) { + return; + } + + // listener for reporting that the user is active + var report = bind(this.player(), this.player().reportUserActivity); + + var touchHolding = void 0; + + this.on('touchstart', function () { + report(); + // For as long as the they are touching the device or have their mouse down, + // we consider them active even if they're not moving their finger or mouse. + // So we want to continue to update that they are active + this.clearInterval(touchHolding); + // report at the same interval as activityCheck + touchHolding = this.setInterval(report, 250); + }); + + var touchEnd = function touchEnd(event) { + report(); + // stop the interval that maintains activity if the touch is holding + this.clearInterval(touchHolding); + }; + + this.on('touchmove', report); + this.on('touchend', touchEnd); + this.on('touchcancel', touchEnd); + }; + + /** + * A callback that has no parameters and is bound into `Component`s context. + * + * @callback Component~GenericCallback + * @this Component + */ + + /** + * Creates a function that runs after an `x` millisecond timeout. This function is a + * wrapper around `window.setTimeout`. There are a few reasons to use this one + * instead though: + * 1. It gets cleared via {@link Component#clearTimeout} when + * {@link Component#dispose} gets called. + * 2. The function callback will gets turned into a {@link Component~GenericCallback} + * + * > Note: You can't use `window.clearTimeout` on the id returned by this function. This + * will cause its dispose listener not to get cleaned up! Please use + * {@link Component#clearTimeout} or {@link Component#dispose} instead. + * + * @param {Component~GenericCallback} fn + * The function that will be run after `timeout`. + * + * @param {number} timeout + * Timeout in milliseconds to delay before executing the specified function. + * + * @return {number} + * Returns a timeout ID that gets used to identify the timeout. It can also + * get used in {@link Component#clearTimeout} to clear the timeout that + * was set. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} + */ + + + Component.prototype.setTimeout = function setTimeout(fn, timeout) { + var _this2 = this; + + fn = bind(this, fn); + + var timeoutId = window_1.setTimeout(fn, timeout); + var disposeFn = function disposeFn() { + return _this2.clearTimeout(timeoutId); + }; + + disposeFn.guid = 'vjs-timeout-' + timeoutId; + + this.on('dispose', disposeFn); + + return timeoutId; + }; + + /** + * Clears a timeout that gets created via `window.setTimeout` or + * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} + * use this function instead of `window.clearTimout`. If you don't your dispose + * listener will not get cleaned up until {@link Component#dispose}! + * + * @param {number} timeoutId + * The id of the timeout to clear. The return value of + * {@link Component#setTimeout} or `window.setTimeout`. + * + * @return {number} + * Returns the timeout id that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} + */ + + + Component.prototype.clearTimeout = function clearTimeout(timeoutId) { + window_1.clearTimeout(timeoutId); + + var disposeFn = function disposeFn() {}; + + disposeFn.guid = 'vjs-timeout-' + timeoutId; + + this.off('dispose', disposeFn); + + return timeoutId; + }; + + /** + * Creates a function that gets run every `x` milliseconds. This function is a wrapper + * around `window.setInterval`. There are a few reasons to use this one instead though. + * 1. It gets cleared via {@link Component#clearInterval} when + * {@link Component#dispose} gets called. + * 2. The function callback will be a {@link Component~GenericCallback} + * + * @param {Component~GenericCallback} fn + * The function to run every `x` seconds. + * + * @param {number} interval + * Execute the specified function every `x` milliseconds. + * + * @return {number} + * Returns an id that can be used to identify the interval. It can also be be used in + * {@link Component#clearInterval} to clear the interval. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} + */ + + + Component.prototype.setInterval = function setInterval(fn, interval) { + var _this3 = this; + + fn = bind(this, fn); + + var intervalId = window_1.setInterval(fn, interval); + + var disposeFn = function disposeFn() { + return _this3.clearInterval(intervalId); + }; + + disposeFn.guid = 'vjs-interval-' + intervalId; + + this.on('dispose', disposeFn); + + return intervalId; + }; + + /** + * Clears an interval that gets created via `window.setInterval` or + * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} + * use this function instead of `window.clearInterval`. If you don't your dispose + * listener will not get cleaned up until {@link Component#dispose}! + * + * @param {number} intervalId + * The id of the interval to clear. The return value of + * {@link Component#setInterval} or `window.setInterval`. + * + * @return {number} + * Returns the interval id that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} + */ + + + Component.prototype.clearInterval = function clearInterval(intervalId) { + window_1.clearInterval(intervalId); + + var disposeFn = function disposeFn() {}; + + disposeFn.guid = 'vjs-interval-' + intervalId; + + this.off('dispose', disposeFn); + + return intervalId; + }; + + /** + * Queues up a callback to be passed to requestAnimationFrame (rAF), but + * with a few extra bonuses: + * + * - Supports browsers that do not support rAF by falling back to + * {@link Component#setTimeout}. + * + * - The callback is turned into a {@link Component~GenericCallback} (i.e. + * bound to the component). + * + * - Automatic cancellation of the rAF callback is handled if the component + * is disposed before it is called. + * + * @param {Component~GenericCallback} fn + * A function that will be bound to this component and executed just + * before the browser's next repaint. + * + * @return {number} + * Returns an rAF ID that gets used to identify the timeout. It can + * also be used in {@link Component#cancelAnimationFrame} to cancel + * the animation frame callback. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} + */ + + + Component.prototype.requestAnimationFrame = function requestAnimationFrame(fn) { + var _this4 = this; + + if (this.supportsRaf_) { + fn = bind(this, fn); + + var id = window_1.requestAnimationFrame(fn); + var disposeFn = function disposeFn() { + return _this4.cancelAnimationFrame(id); + }; + + disposeFn.guid = 'vjs-raf-' + id; + this.on('dispose', disposeFn); + + return id; + } + + // Fall back to using a timer. + return this.setTimeout(fn, 1000 / 60); + }; + + /** + * Cancels a queued callback passed to {@link Component#requestAnimationFrame} + * (rAF). + * + * If you queue an rAF callback via {@link Component#requestAnimationFrame}, + * use this function instead of `window.cancelAnimationFrame`. If you don't, + * your dispose listener will not get cleaned up until {@link Component#dispose}! + * + * @param {number} id + * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. + * + * @return {number} + * Returns the rAF ID that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} + */ + + + Component.prototype.cancelAnimationFrame = function cancelAnimationFrame(id) { + if (this.supportsRaf_) { + window_1.cancelAnimationFrame(id); + + var disposeFn = function disposeFn() {}; + + disposeFn.guid = 'vjs-raf-' + id; + + this.off('dispose', disposeFn); + + return id; + } + + // Fall back to using a timer. + return this.clearTimeout(id); + }; + + /** + * Register a `Component` with `videojs` given the name and the component. + * + * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s + * should be registered using {@link Tech.registerTech} or + * {@link videojs:videojs.registerTech}. + * + * > NOTE: This function can also be seen on videojs as + * {@link videojs:videojs.registerComponent}. + * + * @param {string} name + * The name of the `Component` to register. + * + * @param {Component} ComponentToRegister + * The `Component` class to register. + * + * @return {Component} + * The `Component` that was registered. + */ + + + Component.registerComponent = function registerComponent(name, ComponentToRegister) { + if (typeof name !== 'string' || !name) { + throw new Error('Illegal component name, "' + name + '"; must be a non-empty string.'); + } + + var Tech = Component.getComponent('Tech'); + + // We need to make sure this check is only done if Tech has been registered. + var isTech = Tech && Tech.isTech(ComponentToRegister); + var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype); + + if (isTech || !isComp) { + var reason = void 0; + + if (isTech) { + reason = 'techs must be registered using Tech.registerTech()'; + } else { + reason = 'must be a Component subclass'; + } + + throw new Error('Illegal component, "' + name + '"; ' + reason + '.'); + } + + name = toTitleCase(name); + + if (!Component.components_) { + Component.components_ = {}; + } + + var Player = Component.getComponent('Player'); + + if (name === 'Player' && Player && Player.players) { + var players = Player.players; + var playerNames = Object.keys(players); + + // If we have players that were disposed, then their name will still be + // in Players.players. So, we must loop through and verify that the value + // for each item is not null. This allows registration of the Player component + // after all players have been disposed or before any were created. + if (players && playerNames.length > 0 && playerNames.map(function (pname) { + return players[pname]; + }).every(Boolean)) { + throw new Error('Can not register Player component after player has been created.'); + } + } + + Component.components_[name] = ComponentToRegister; + + return ComponentToRegister; + }; + + /** + * Get a `Component` based on the name it was registered with. + * + * @param {string} name + * The Name of the component to get. + * + * @return {Component} + * The `Component` that got registered under the given name. + * + * @deprecated In `videojs` 6 this will not return `Component`s that were not + * registered using {@link Component.registerComponent}. Currently we + * check the global `videojs` object for a `Component` name and + * return that if it exists. + */ + + + Component.getComponent = function getComponent(name) { + if (!name) { + return; + } + + name = toTitleCase(name); + + if (Component.components_ && Component.components_[name]) { + return Component.components_[name]; + } + }; + + return Component; + }(); + + /** + * Whether or not this component supports `requestAnimationFrame`. + * + * This is exposed primarily for testing purposes. + * + * @private + * @type {Boolean} + */ + + + Component.prototype.supportsRaf_ = typeof window_1.requestAnimationFrame === 'function' && typeof window_1.cancelAnimationFrame === 'function'; + + Component.registerComponent('Component', Component); + + /** + * @file browser.js + * @module browser + */ + + var USER_AGENT = window_1.navigator && window_1.navigator.userAgent || ''; + var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT); + var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null; + + /* + * Device is an iPhone + * + * @type {Boolean} + * @constant + * @private + */ + var IS_IPAD = /iPad/i.test(USER_AGENT); + + // 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; + var IS_IPOD = /iPod/i.test(USER_AGENT); + var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD; + + var IOS_VERSION = function () { + var match = USER_AGENT.match(/OS (\d+)_/i); + + if (match && match[1]) { + return match[1]; + } + return null; + }(); + + var IS_ANDROID = /Android/i.test(USER_AGENT); + var ANDROID_VERSION = function () { + // This matches Android Major.Minor.Patch versions + // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned + var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i); + + if (!match) { + return null; + } + + var major = match[1] && parseFloat(match[1]); + var minor = match[2] && parseFloat(match[2]); + + if (major && minor) { + return parseFloat(match[1] + '.' + match[2]); + } else if (major) { + return major; + } + return null; + }(); + + var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537; + + var IS_FIREFOX = /Firefox/i.test(USER_AGENT); + var IS_EDGE = /Edge/i.test(USER_AGENT); + var IS_CHROME = !IS_EDGE && /Chrome/i.test(USER_AGENT); + var CHROME_VERSION = function () { + var match = USER_AGENT.match(/Chrome\/(\d+)/); + + if (match && match[1]) { + return parseFloat(match[1]); + } + return null; + }(); + var IE_VERSION = function () { + var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT); + var version = result && parseFloat(result[1]); + + if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) { + // IE 11 has a different user agent string than other IE versions + version = 11.0; + } + + return version; + }(); + + var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE; + var IS_ANY_SAFARI = IS_SAFARI || IS_IOS; + + var TOUCH_ENABLED = isReal() && ('ontouchstart' in window_1 || window_1.DocumentTouch && window_1.document instanceof window_1.DocumentTouch); + + 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, + IS_NATIVE_ANDROID: IS_NATIVE_ANDROID, + IS_FIREFOX: IS_FIREFOX, + IS_EDGE: IS_EDGE, + IS_CHROME: IS_CHROME, + CHROME_VERSION: CHROME_VERSION, + IE_VERSION: IE_VERSION, + IS_SAFARI: IS_SAFARI, + IS_ANY_SAFARI: IS_ANY_SAFARI, + TOUCH_ENABLED: TOUCH_ENABLED + }); + + /** + * @file time-ranges.js + * @module time-ranges + */ + + /** + * Returns the time for the specified index at the start or end + * of a TimeRange object. + * + * @function time-ranges:indexFunction + * + * @param {number} [index=0] + * The range number to return the time for. + * + * @return {number} + * The time that offset at the specified index. + * + * @depricated index must be set to a value, in the future this will throw an error. + */ + + /** + * An object that contains ranges of time for various reasons. + * + * @typedef {Object} TimeRange + * + * @property {number} length + * The number of time ranges represented by this Object + * + * @property {time-ranges:indexFunction} start + * Returns the time offset at which a specified time range begins. + * + * @property {time-ranges:indexFunction} end + * Returns the time offset at which a specified time range ends. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges + */ + + /** + * Check if any of the time ranges are over the maximum index. + * + * @param {string} fnName + * The function name to use for logging + * + * @param {number} index + * The index to check + * + * @param {number} maxIndex + * The maximum possible index + * + * @throws {Error} if the timeRanges provided are over the maxIndex + */ + function rangeCheck(fnName, index, maxIndex) { + if (typeof index !== 'number' || index < 0 || index > maxIndex) { + throw new Error('Failed to execute \'' + fnName + '\' on \'TimeRanges\': The index provided (' + index + ') is non-numeric or out of bounds (0-' + maxIndex + ').'); + } + } + + /** + * Get the time for the specified index at the start or end + * of a TimeRange object. + * + * @param {string} fnName + * The function name to use for logging + * + * @param {string} valueIndex + * The property that should be used to get the time. should be 'start' or 'end' + * + * @param {Array} ranges + * An array of time ranges + * + * @param {Array} [rangeIndex=0] + * The index to start the search at + * + * @return {number} + * The time that offset at the specified index. + * + * + * @depricated rangeIndex must be set to a value, in the future this will throw an error. + * @throws {Error} if rangeIndex is more than the length of ranges + */ + function getRange(fnName, valueIndex, ranges, rangeIndex) { + rangeCheck(fnName, rangeIndex, ranges.length - 1); + return ranges[rangeIndex][valueIndex]; + } + + /** + * Create a time range object given ranges of time. + * + * @param {Array} [ranges] + * An array of time ranges. + */ + function createTimeRangesObj(ranges) { + if (ranges === undefined || ranges.length === 0) { + return { + length: 0, + start: function start() { + throw new Error('This TimeRanges object is empty'); + }, + end: function end() { + throw new Error('This TimeRanges object is empty'); + } + }; + } + return { + length: ranges.length, + start: getRange.bind(null, 'start', 0, ranges), + end: getRange.bind(null, 'end', 1, ranges) + }; + } + + /** + * Should create a fake `TimeRange` object which mimics an HTML5 time range instance. + * + * @param {number|Array} start + * The start of a single range or an array of ranges + * + * @param {number} end + * The end of a single range. + * + * @private + */ + function createTimeRanges(start, end) { + if (Array.isArray(start)) { + return createTimeRangesObj(start); + } else if (start === undefined || end === undefined) { + return createTimeRangesObj(); + } + return createTimeRangesObj([[start, end]]); + } + + /** + * @file buffer.js + * @module buffer + */ + + /** + * Compute the percentage of the media that has been buffered. + * + * @param {TimeRange} buffered + * The current `TimeRange` object representing buffered time ranges + * + * @param {number} duration + * Total duration of the media + * + * @return {number} + * Percent buffered of the total duration in decimal form. + */ + function bufferedPercent(buffered, duration) { + var bufferedDuration = 0; + var start = void 0; + var end = void 0; + + if (!duration) { + return 0; + } + + if (!buffered || !buffered.length) { + buffered = createTimeRanges(0, 0); + } + + for (var i = 0; i < buffered.length; i++) { + start = buffered.start(i); + end = buffered.end(i); + + // buffered end can be bigger than duration by a very small fraction + if (end > duration) { + end = duration; + } + + bufferedDuration += end - start; + } + + return bufferedDuration / duration; + } + + /** + * @file fullscreen-api.js + * @module fullscreen-api + * @private + */ + + /** + * Store the browser-specific methods for the fullscreen API. + * + * @type {Object} + * @see [Specification]{@link https://fullscreen.spec.whatwg.org} + * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} + */ + var FullscreenApi = {}; + + // browser API methods + var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror'], + // WebKit + ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror'], + // Old WebKit (Safari 5.1) + ['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror'], + // Mozilla + ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror'], + // Microsoft + ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError']]; + + var specApi = apiMap[0]; + var browserApi = void 0; + + // determine the supported set of functions + for (var i = 0; i < apiMap.length; i++) { + // check for exitFullscreen function + if (apiMap[i][1] in document_1) { + browserApi = apiMap[i]; + break; + } + } + + // map the browser API names to the spec API names + if (browserApi) { + for (var _i = 0; _i < browserApi.length; _i++) { + FullscreenApi[specApi[_i]] = browserApi[_i]; + } + } + + /** + * @file media-error.js + */ + + /** + * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. + * + * @param {number|string|Object|MediaError} value + * This can be of multiple types: + * - number: should be a standard error code + * - string: an error message (the code will be 0) + * - Object: arbitrary properties + * - `MediaError` (native): used to populate a video.js `MediaError` object + * - `MediaError` (video.js): will return itself if it's already a + * video.js `MediaError` object. + * + * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} + * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} + * + * @class MediaError + */ + function MediaError(value) { + + // Allow redundant calls to this constructor to avoid having `instanceof` + // checks peppered around the code. + if (value instanceof MediaError) { + return value; + } + + if (typeof value === 'number') { + this.code = value; + } else if (typeof value === 'string') { + // default code is zero, so this is a custom error + this.message = value; + } else if (isObject(value)) { + + // We assign the `code` property manually because native `MediaError` objects + // do not expose it as an own/enumerable property of the object. + if (typeof value.code === 'number') { + this.code = value.code; + } + + assign(this, value); + } + + if (!this.message) { + this.message = MediaError.defaultMessages[this.code] || ''; + } + } + + /** + * The error code that refers two one of the defined `MediaError` types + * + * @type {Number} + */ + MediaError.prototype.code = 0; + + /** + * An optional message that to show with the error. Message is not part of the HTML5 + * video spec but allows for more informative custom errors. + * + * @type {String} + */ + MediaError.prototype.message = ''; + + /** + * An optional status code that can be set by plugins to allow even more detail about + * the error. For example a plugin might provide a specific HTTP status code and an + * error message for that code. Then when the plugin gets that error this class will + * know how to display an error message for it. This allows a custom message to show + * up on the `Player` error overlay. + * + * @type {Array} + */ + MediaError.prototype.status = null; + + /** + * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the + * specification listed under {@link MediaError} for more information. + * + * @enum {array} + * @readonly + * @property {string} 0 - MEDIA_ERR_CUSTOM + * @property {string} 1 - MEDIA_ERR_CUSTOM + * @property {string} 2 - MEDIA_ERR_ABORTED + * @property {string} 3 - MEDIA_ERR_NETWORK + * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED + * @property {string} 5 - MEDIA_ERR_ENCRYPTED + */ + MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; + + /** + * The default `MediaError` messages based on the {@link MediaError.errorTypes}. + * + * @type {Array} + * @constant + */ + MediaError.defaultMessages = { + 1: 'You aborted the media playback', + 2: 'A network error caused the media download to fail part-way.', + 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', + 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', + 5: 'The media is encrypted and we do not have the keys to decrypt it.' + }; + + // Add types as properties on MediaError + // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; + for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { + MediaError[MediaError.errorTypes[errNum]] = errNum; + // values should be accessible on both the class and instance + MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; + } + + var tuple = SafeParseTuple; + + function SafeParseTuple(obj, reviver) { + var json; + var error = null; + + try { + json = JSON.parse(obj, reviver); + } catch (err) { + error = err; + } + + return [error, json]; + } + + /** + * Returns whether an object is `Promise`-like (i.e. has a `then` method). + * + * @param {Object} value + * An object that may or may not be `Promise`-like. + * + * @return {Boolean} + * Whether or not the object is `Promise`-like. + */ + function isPromise(value) { + return value !== undefined && value !== null && typeof value.then === 'function'; + } + + /** + * Silence a Promise-like object. + * + * This is useful for avoiding non-harmful, but potentially confusing "uncaught + * play promise" rejection error messages. + * + * @param {Object} value + * An object that may or may not be `Promise`-like. + */ + function silencePromise(value) { + if (isPromise(value)) { + value.then(null, function (e) {}); + } + } + + /** + * @file text-track-list-converter.js Utilities for capturing text track state and + * re-creating tracks based on a capture. + * + * @module text-track-list-converter + */ + + /** + * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that + * represents the {@link TextTrack}'s state. + * + * @param {TextTrack} track + * The text track to query. + * + * @return {Object} + * A serializable javascript representation of the TextTrack. + * @private + */ + var trackToJson_ = function trackToJson_(track) { + var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) { + + if (track[prop]) { + acc[prop] = track[prop]; + } + + return acc; + }, { + cues: track.cues && Array.prototype.map.call(track.cues, function (cue) { + return { + startTime: cue.startTime, + endTime: cue.endTime, + text: cue.text, + id: cue.id + }; + }) + }); + + return ret; + }; + + /** + * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the + * state of all {@link TextTrack}s currently configured. The return array is compatible with + * {@link text-track-list-converter:jsonToTextTracks}. + * + * @param {Tech} tech + * The tech object to query + * + * @return {Array} + * A serializable javascript representation of the {@link Tech}s + * {@link TextTrackList}. + */ + var textTracksToJson = function textTracksToJson(tech) { + + var trackEls = tech.$$('track'); + + var trackObjs = Array.prototype.map.call(trackEls, function (t) { + return t.track; + }); + var tracks = Array.prototype.map.call(trackEls, function (trackEl) { + var json = trackToJson_(trackEl.track); + + if (trackEl.src) { + json.src = trackEl.src; + } + return json; + }); + + return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) { + return trackObjs.indexOf(track) === -1; + }).map(trackToJson_)); + }; + + /** + * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript + * object {@link TextTrack} representations. + * + * @param {Array} json + * An array of `TextTrack` representation objects, like those that would be + * produced by `textTracksToJson`. + * + * @param {Tech} tech + * The `Tech` to create the `TextTrack`s on. + */ + var jsonToTextTracks = function jsonToTextTracks(json, tech) { + json.forEach(function (track) { + var addedTrack = tech.addRemoteTextTrack(track).track; + + if (!track.src && track.cues) { + track.cues.forEach(function (cue) { + return addedTrack.addCue(cue); + }); + } + }); + + return tech.textTracks(); + }; + + var textTrackConverter = { textTracksToJson: textTracksToJson, jsonToTextTracks: jsonToTextTracks, trackToJson_: trackToJson_ }; + + /** + * @file modal-dialog.js + */ + + var MODAL_CLASS_NAME = 'vjs-modal-dialog'; + var ESC = 27; + + /** + * The `ModalDialog` displays over the video and its controls, which blocks + * interaction with the player until it is closed. + * + * Modal dialogs include a "Close" button and will close when that button + * is activated - or when ESC is pressed anywhere. + * + * @extends Component + */ + + var ModalDialog = function (_Component) { + inherits(ModalDialog, _Component); + + /** + * Create 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. + * + * @param {Mixed} [options.content=undefined] + * Provide customized content for this modal. + * + * @param {string} [options.description] + * A text description for the modal, primarily for accessibility. + * + * @param {boolean} [options.fillAlways=false] + * Normally, modals are automatically filled only the first time + * they open. This tells the modal to refresh its content + * every time it opens. + * + * @param {string} [options.label] + * A text label for the modal, primarily for accessibility. + * + * @param {boolean} [options.temporary=true] + * If `true`, the modal can only be opened once; it will be + * disposed as soon as it's closed. + * + * @param {boolean} [options.uncloseable=false] + * If `true`, the user will not be able to close the modal + * through the UI in the normal ways. Programmatic closing is + * still possible. + */ + function ModalDialog(player, options) { + classCallCheck(this, ModalDialog); + + var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); + + _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false; + + _this.closeable(!_this.options_.uncloseable); + _this.content(_this.options_.content); + + // Make sure the contentEl is defined AFTER any children are initialized + // because we only want the contents of the modal in the contentEl + // (not the UI elements like the close button). + _this.contentEl_ = createEl('div', { + className: MODAL_CLASS_NAME + '-content' + }, { + role: 'document' + }); + + _this.descEl_ = createEl('p', { + className: MODAL_CLASS_NAME + '-description vjs-control-text', + id: _this.el().getAttribute('aria-describedby') + }); + + textContent(_this.descEl_, _this.description()); + _this.el_.appendChild(_this.descEl_); + _this.el_.appendChild(_this.contentEl_); + return _this; + } + + /** + * Create the `ModalDialog`'s DOM element + * + * @return {Element} + * The DOM element that gets created. + */ + + + ModalDialog.prototype.createEl = function createEl$$1() { + return _Component.prototype.createEl.call(this, 'div', { + className: this.buildCSSClass(), + tabIndex: -1 + }, { + 'aria-describedby': this.id() + '_description', + 'aria-hidden': 'true', + 'aria-label': this.label(), + 'role': 'dialog' + }); + }; + + ModalDialog.prototype.dispose = function dispose() { + this.contentEl_ = null; + this.descEl_ = null; + this.previouslyActiveEl_ = null; + + _Component.prototype.dispose.call(this); + }; + + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ + + + ModalDialog.prototype.buildCSSClass = function buildCSSClass() { + return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this); + }; + + /** + * Handles `keydown` events on the document, looking for ESC, which closes + * the modal. + * + * @param {EventTarget~Event} e + * The keypress that triggered this event. + * + * @listens keydown + */ + + + ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) { + if (e.which === ESC && this.closeable()) { + this.close(); + } + }; + + /** + * Returns the label string for this modal. Primarily used for accessibility. + * + * @return {string} + * the localized or raw label of this modal. + */ + + + ModalDialog.prototype.label = function label() { + return this.localize(this.options_.label || 'Modal Window'); + }; + + /** + * Returns the description string for this modal. Primarily used for + * accessibility. + * + * @return {string} + * The localized or raw description of this modal. + */ + + + ModalDialog.prototype.description = function description() { + var desc = this.options_.description || this.localize('This is a modal window.'); + + // Append a universal closeability message if the modal is closeable. + if (this.closeable()) { + desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); + } + + return desc; + }; + + /** + * Opens the modal. + * + * @fires ModalDialog#beforemodalopen + * @fires ModalDialog#modalopen + */ + + + ModalDialog.prototype.open = function open() { + if (!this.opened_) { + var player = this.player(); + + /** + * Fired just before a `ModalDialog` is opened. + * + * @event ModalDialog#beforemodalopen + * @type {EventTarget~Event} + */ + this.trigger('beforemodalopen'); + this.opened_ = true; + + // Fill content if the modal has never opened before and + // never been filled. + if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { + this.fill(); + } + + // If the player was playing, pause it and take note of its previously + // playing state. + this.wasPlaying_ = !player.paused(); + + if (this.options_.pauseOnOpen && this.wasPlaying_) { + player.pause(); + } + + if (this.closeable()) { + this.on(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress)); + } + + // Hide controls and note if they were enabled. + this.hadControls_ = player.controls(); + player.controls(false); + + this.show(); + this.conditionalFocus_(); + this.el().setAttribute('aria-hidden', 'false'); + + /** + * Fired just after a `ModalDialog` is opened. + * + * @event ModalDialog#modalopen + * @type {EventTarget~Event} + */ + this.trigger('modalopen'); + this.hasBeenOpened_ = true; + } + }; + + /** + * If the `ModalDialog` is currently open or closed. + * + * @param {boolean} [value] + * If given, it will open (`true`) or close (`false`) the modal. + * + * @return {boolean} + * the current open state of the modaldialog + */ + + + ModalDialog.prototype.opened = function opened(value) { + if (typeof value === 'boolean') { + this[value ? 'open' : 'close'](); + } + return this.opened_; + }; + + /** + * Closes the modal, does nothing if the `ModalDialog` is + * not open. + * + * @fires ModalDialog#beforemodalclose + * @fires ModalDialog#modalclose + */ + + + ModalDialog.prototype.close = function close() { + if (!this.opened_) { + return; + } + var player = this.player(); + + /** + * Fired just before a `ModalDialog` is closed. + * + * @event ModalDialog#beforemodalclose + * @type {EventTarget~Event} + */ + this.trigger('beforemodalclose'); + this.opened_ = false; + + if (this.wasPlaying_ && this.options_.pauseOnOpen) { + player.play(); + } + + if (this.closeable()) { + this.off(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress)); + } + + if (this.hadControls_) { + player.controls(true); + } + + this.hide(); + this.el().setAttribute('aria-hidden', 'true'); + + /** + * Fired just after a `ModalDialog` is closed. + * + * @event ModalDialog#modalclose + * @type {EventTarget~Event} + */ + this.trigger('modalclose'); + this.conditionalBlur_(); + + if (this.options_.temporary) { + this.dispose(); + } + }; + + /** + * Check to see if the `ModalDialog` is closeable via the UI. + * + * @param {boolean} [value] + * If given as a boolean, it will set the `closeable` option. + * + * @return {boolean} + * Returns the final value of the closable option. + */ + + + ModalDialog.prototype.closeable = function closeable(value) { + if (typeof value === 'boolean') { + var closeable = this.closeable_ = !!value; + var close = this.getChild('closeButton'); + + // If this is being made closeable and has no close button, add one. + if (closeable && !close) { + + // The close button should be a child of the modal - not its + // content element, so temporarily change the content element. + var temp = this.contentEl_; + + this.contentEl_ = this.el_; + close = this.addChild('closeButton', { controlText: 'Close Modal Dialog' }); + this.contentEl_ = temp; + this.on(close, 'close', this.close); + } + + // If this is being made uncloseable and has a close button, remove it. + if (!closeable && close) { + this.off(close, 'close', this.close); + this.removeChild(close); + close.dispose(); + } + } + return this.closeable_; + }; + + /** + * Fill the modal's content element with the modal's "content" option. + * The content element will be emptied before this change takes place. + */ + + + ModalDialog.prototype.fill = function fill() { + this.fillWith(this.content()); + }; + + /** + * Fill the modal's content element with arbitrary content. + * The content element will be emptied before this change takes place. + * + * @fires ModalDialog#beforemodalfill + * @fires ModalDialog#modalfill + * + * @param {Mixed} [content] + * The same rules apply to this as apply to the `content` option. + */ + + + ModalDialog.prototype.fillWith = function fillWith(content) { + var contentEl = this.contentEl(); + var parentEl = contentEl.parentNode; + var nextSiblingEl = contentEl.nextSibling; + + /** + * Fired just before a `ModalDialog` is filled with content. + * + * @event ModalDialog#beforemodalfill + * @type {EventTarget~Event} + */ + this.trigger('beforemodalfill'); + this.hasBeenFilled_ = true; + + // Detach the content element from the DOM before performing + // manipulation to avoid modifying the live DOM multiple times. + parentEl.removeChild(contentEl); + this.empty(); + insertContent(contentEl, content); + /** + * Fired just after a `ModalDialog` is filled with content. + * + * @event ModalDialog#modalfill + * @type {EventTarget~Event} + */ + this.trigger('modalfill'); + + // Re-inject the re-filled content element. + if (nextSiblingEl) { + parentEl.insertBefore(contentEl, nextSiblingEl); + } else { + parentEl.appendChild(contentEl); + } + + // make sure that the close button is last in the dialog DOM + var closeButton = this.getChild('closeButton'); + + if (closeButton) { + parentEl.appendChild(closeButton.el_); + } + }; + + /** + * Empties the content element. This happens anytime the modal is filled. + * + * @fires ModalDialog#beforemodalempty + * @fires ModalDialog#modalempty + */ + + + ModalDialog.prototype.empty = function empty() { + /** + * Fired just before a `ModalDialog` is emptied. + * + * @event ModalDialog#beforemodalempty + * @type {EventTarget~Event} + */ + this.trigger('beforemodalempty'); + emptyEl(this.contentEl()); + + /** + * Fired just after a `ModalDialog` is emptied. + * + * @event ModalDialog#modalempty + * @type {EventTarget~Event} + */ + this.trigger('modalempty'); + }; + + /** + * Gets or sets the modal content, which gets normalized before being + * rendered into the DOM. + * + * This does not update the DOM or fill the modal, but it is called during + * that process. + * + * @param {Mixed} [value] + * If defined, sets the internal content value to be used on the + * next call(s) to `fill`. This value is normalized before being + * inserted. To "clear" the internal content value, pass `null`. + * + * @return {Mixed} + * The current content of the modal dialog + */ + + + ModalDialog.prototype.content = function content(value) { + if (typeof value !== 'undefined') { + this.content_ = value; + } + return this.content_; + }; + + /** + * conditionally focus the modal dialog if focus was previously on the player. + * + * @private + */ + + + ModalDialog.prototype.conditionalFocus_ = function conditionalFocus_() { + var activeEl = document_1.activeElement; + var playerEl = this.player_.el_; + + this.previouslyActiveEl_ = null; + + if (playerEl.contains(activeEl) || playerEl === activeEl) { + this.previouslyActiveEl_ = activeEl; + + this.focus(); + + this.on(document_1, 'keydown', this.handleKeyDown); + } + }; + + /** + * conditionally blur the element and refocus the last focused element + * + * @private + */ + + + ModalDialog.prototype.conditionalBlur_ = function conditionalBlur_() { + if (this.previouslyActiveEl_) { + this.previouslyActiveEl_.focus(); + this.previouslyActiveEl_ = null; + } + + this.off(document_1, 'keydown', this.handleKeyDown); + }; + + /** + * Keydown handler. Attached when modal is focused. + * + * @listens keydown + */ + + + ModalDialog.prototype.handleKeyDown = function handleKeyDown(event) { + // exit early if it isn't a tab key + if (event.which !== 9) { + return; + } + + var focusableEls = this.focusableEls_(); + var activeEl = this.el_.querySelector(':focus'); + var focusIndex = void 0; + + for (var i = 0; i < focusableEls.length; i++) { + if (activeEl === focusableEls[i]) { + focusIndex = i; + break; + } + } + + if (document_1.activeElement === this.el_) { + focusIndex = 0; + } + + if (event.shiftKey && focusIndex === 0) { + focusableEls[focusableEls.length - 1].focus(); + event.preventDefault(); + } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { + focusableEls[0].focus(); + event.preventDefault(); + } + }; + + /** + * get all focusable elements + * + * @private + */ + + + ModalDialog.prototype.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 ModalDialog; + }(Component); + + /** + * Default options for `ModalDialog` default options. + * + * @type {Object} + * @private + */ + + + ModalDialog.prototype.options_ = { + pauseOnOpen: true, + temporary: true + }; + + Component.registerComponent('ModalDialog', ModalDialog); + + /** + * @file track-list.js + */ + + /** + * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and + * {@link VideoTrackList} + * + * @extends EventTarget + */ + + var TrackList = function (_EventTarget) { + inherits(TrackList, _EventTarget); + + /** + * Create an instance of this class + * + * @param {Track[]} tracks + * A list of tracks to initialize the list with. + * + * @abstract + */ + function TrackList() { + var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + classCallCheck(this, TrackList); + + var _this = possibleConstructorReturn(this, _EventTarget.call(this)); + + _this.tracks_ = []; + + /** + * @memberof TrackList + * @member {number} length + * The current number of `Track`s in the this Trackist. + * @instance + */ + Object.defineProperty(_this, 'length', { + get: function get$$1() { + return this.tracks_.length; + } + }); + + for (var i = 0; i < tracks.length; i++) { + _this.addTrack(tracks[i]); + } + return _this; + } + + /** + * Add a {@link Track} to the `TrackList` + * + * @param {Track} track + * The audio, video, or text track to add to the list. + * + * @fires TrackList#addtrack + */ + + + TrackList.prototype.addTrack = function addTrack(track) { + var index = this.tracks_.length; + + if (!('' + index in this)) { + Object.defineProperty(this, index, { + get: function get$$1() { + return this.tracks_[index]; + } + }); + } + + // Do not add duplicate tracks + if (this.tracks_.indexOf(track) === -1) { + this.tracks_.push(track); + /** + * Triggered when a track is added to a track list. + * + * @event TrackList#addtrack + * @type {EventTarget~Event} + * @property {Track} track + * A reference to track that was added. + */ + this.trigger({ + track: track, + type: 'addtrack' + }); + } + }; + + /** + * Remove a {@link Track} from the `TrackList` + * + * @param {Track} rtrack + * The audio, video, or text track to remove from the list. + * + * @fires TrackList#removetrack + */ + + + TrackList.prototype.removeTrack = function removeTrack(rtrack) { + var track = void 0; + + for (var i = 0, l = this.length; i < l; i++) { + if (this[i] === rtrack) { + track = this[i]; + if (track.off) { + track.off(); + } + + this.tracks_.splice(i, 1); + + break; + } + } + + if (!track) { + return; + } + + /** + * Triggered when a track is removed from track list. + * + * @event TrackList#removetrack + * @type {EventTarget~Event} + * @property {Track} track + * A reference to track that was removed. + */ + this.trigger({ + track: track, + type: 'removetrack' + }); + }; + + /** + * Get a Track from the TrackList by a tracks id + * + * @param {String} id - the id of the track to get + * @method getTrackById + * @return {Track} + * @private + */ + + + TrackList.prototype.getTrackById = function getTrackById(id) { + var result = null; + + for (var i = 0, l = this.length; i < l; i++) { + var track = this[i]; + + if (track.id === id) { + result = track; + break; + } + } + + return result; + }; + + return TrackList; + }(EventTarget); + + /** + * Triggered when a different track is selected/enabled. + * + * @event TrackList#change + * @type {EventTarget~Event} + */ + + /** + * Events that can be called with on + eventName. See {@link EventHandler}. + * + * @property {Object} TrackList#allowedEvents_ + * @private + */ + + + TrackList.prototype.allowedEvents_ = { + change: 'change', + addtrack: 'addtrack', + removetrack: 'removetrack' + }; + + // emulate attribute EventHandler support to allow for feature detection + for (var event in TrackList.prototype.allowedEvents_) { + TrackList.prototype['on' + event] = null; + } + + /** + * @file audio-track-list.js + */ + + /** + * Anywhere we call this function we diverge from the spec + * as we only support one enabled audiotrack at a time + * + * @param {AudioTrackList} list + * list to work on + * + * @param {AudioTrack} track + * The track to skip + * + * @private + */ + var disableOthers = function disableOthers(list, track) { + for (var i = 0; i < list.length; i++) { + if (!Object.keys(list[i]).length || track.id === list[i].id) { + continue; + } + // another audio track is enabled, disable it + list[i].enabled = false; + } + }; + + /** + * The current list of {@link AudioTrack} for a media file. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist} + * @extends TrackList + */ + + var AudioTrackList = function (_TrackList) { + inherits(AudioTrackList, _TrackList); + + /** + * Create an instance of this class. + * + * @param {AudioTrack[]} [tracks=[]] + * A list of `AudioTrack` to instantiate the list with. + */ + function AudioTrackList() { + var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + classCallCheck(this, AudioTrackList); + + // make sure only 1 track is enabled + // sorted from last index to first index + for (var i = tracks.length - 1; i >= 0; i--) { + if (tracks[i].enabled) { + disableOthers(tracks, tracks[i]); + break; + } + } + + var _this = possibleConstructorReturn(this, _TrackList.call(this, tracks)); + + _this.changing_ = false; + return _this; + } + + /** + * Add an {@link AudioTrack} to the `AudioTrackList`. + * + * @param {AudioTrack} track + * The AudioTrack to add to the list + * + * @fires TrackList#addtrack + */ + + + AudioTrackList.prototype.addTrack = function addTrack(track) { + var _this2 = this; + + if (track.enabled) { + disableOthers(this, track); + } + + _TrackList.prototype.addTrack.call(this, track); + // native tracks don't have this + if (!track.addEventListener) { + return; + } + + /** + * @listens AudioTrack#enabledchange + * @fires TrackList#change + */ + track.addEventListener('enabledchange', function () { + // when we are disabling other tracks (since we don't support + // more than one track at a time) we will set changing_ + // to true so that we don't trigger additional change events + if (_this2.changing_) { + return; + } + _this2.changing_ = true; + disableOthers(_this2, track); + _this2.changing_ = false; + _this2.trigger('change'); + }); + }; + + return AudioTrackList; + }(TrackList); + + /** + * @file video-track-list.js + */ + + /** + * Un-select all other {@link VideoTrack}s that are selected. + * + * @param {VideoTrackList} list + * list to work on + * + * @param {VideoTrack} track + * The track to skip + * + * @private + */ + var disableOthers$1 = function disableOthers(list, track) { + for (var i = 0; i < list.length; i++) { + if (!Object.keys(list[i]).length || track.id === list[i].id) { + continue; + } + // another video track is enabled, disable it + list[i].selected = false; + } + }; + + /** + * The current list of {@link VideoTrack} for a video. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist} + * @extends TrackList + */ + + var VideoTrackList = function (_TrackList) { + inherits(VideoTrackList, _TrackList); + + /** + * Create an instance of this class. + * + * @param {VideoTrack[]} [tracks=[]] + * A list of `VideoTrack` to instantiate the list with. + */ + function VideoTrackList() { + var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + classCallCheck(this, VideoTrackList); + + // make sure only 1 track is enabled + // sorted from last index to first index + for (var i = tracks.length - 1; i >= 0; i--) { + if (tracks[i].selected) { + disableOthers$1(tracks, tracks[i]); + break; + } + } + + var _this = possibleConstructorReturn(this, _TrackList.call(this, tracks)); + + _this.changing_ = false; + + /** + * @member {number} VideoTrackList#selectedIndex + * The current index of the selected {@link VideoTrack`}. + */ + Object.defineProperty(_this, 'selectedIndex', { + get: function get$$1() { + for (var _i = 0; _i < this.length; _i++) { + if (this[_i].selected) { + return _i; + } + } + return -1; + }, + set: function set$$1() {} + }); + return _this; + } + + /** + * Add a {@link VideoTrack} to the `VideoTrackList`. + * + * @param {VideoTrack} track + * The VideoTrack to add to the list + * + * @fires TrackList#addtrack + */ + + + VideoTrackList.prototype.addTrack = function addTrack(track) { + var _this2 = this; + + if (track.selected) { + disableOthers$1(this, track); + } + + _TrackList.prototype.addTrack.call(this, track); + // native tracks don't have this + if (!track.addEventListener) { + return; + } + + /** + * @listens VideoTrack#selectedchange + * @fires TrackList#change + */ + track.addEventListener('selectedchange', function () { + if (_this2.changing_) { + return; + } + _this2.changing_ = true; + disableOthers$1(_this2, track); + _this2.changing_ = false; + _this2.trigger('change'); + }); + }; + + return VideoTrackList; + }(TrackList); + + /** + * @file text-track-list.js + */ + + /** + * The current list of {@link TextTrack} for a media file. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist} + * @extends TrackList + */ + + var TextTrackList = function (_TrackList) { + inherits(TextTrackList, _TrackList); + + function TextTrackList() { + classCallCheck(this, TextTrackList); + return possibleConstructorReturn(this, _TrackList.apply(this, arguments)); + } + + /** + * Add a {@link TextTrack} to the `TextTrackList` + * + * @param {TextTrack} track + * The text track to add to the list. + * + * @fires TrackList#addtrack + */ + TextTrackList.prototype.addTrack = function addTrack(track) { + _TrackList.prototype.addTrack.call(this, track); + + /** + * @listens TextTrack#modechange + * @fires TrackList#change + */ + track.addEventListener('modechange', bind(this, function () { + this.trigger('change'); + })); + + var nonLanguageTextTrackKind = ['metadata', 'chapters']; + + if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) { + track.addEventListener('modechange', bind(this, function () { + this.trigger('selectedlanguagechange'); + })); + } + }; + + return TextTrackList; + }(TrackList); + + /** + * @file html-track-element-list.js + */ + + /** + * The current list of {@link HtmlTrackElement}s. + */ + var HtmlTrackElementList = function () { + + /** + * Create an instance of this class. + * + * @param {HtmlTrackElement[]} [tracks=[]] + * A list of `HtmlTrackElement` to instantiate the list with. + */ + function HtmlTrackElementList() { + var trackElements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + classCallCheck(this, HtmlTrackElementList); + + this.trackElements_ = []; + + /** + * @memberof HtmlTrackElementList + * @member {number} length + * The current number of `Track`s in the this Trackist. + * @instance + */ + Object.defineProperty(this, 'length', { + get: function get$$1() { + return this.trackElements_.length; + } + }); + + for (var i = 0, length = trackElements.length; i < length; i++) { + this.addTrackElement_(trackElements[i]); + } + } + + /** + * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList` + * + * @param {HtmlTrackElement} trackElement + * The track element to add to the list. + * + * @private + */ + + + HtmlTrackElementList.prototype.addTrackElement_ = function addTrackElement_(trackElement) { + var index = this.trackElements_.length; + + if (!('' + index in this)) { + Object.defineProperty(this, index, { + get: function get$$1() { + return this.trackElements_[index]; + } + }); + } + + // Do not add duplicate elements + if (this.trackElements_.indexOf(trackElement) === -1) { + this.trackElements_.push(trackElement); + } + }; + + /** + * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an + * {@link TextTrack}. + * + * @param {TextTrack} track + * The track associated with a track element. + * + * @return {HtmlTrackElement|undefined} + * The track element that was found or undefined. + * + * @private + */ + + + HtmlTrackElementList.prototype.getTrackElementByTrack_ = function getTrackElementByTrack_(track) { + var trackElement_ = void 0; + + for (var i = 0, length = this.trackElements_.length; i < length; i++) { + if (track === this.trackElements_[i].track) { + trackElement_ = this.trackElements_[i]; + + break; + } + } + + return trackElement_; + }; + + /** + * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList` + * + * @param {HtmlTrackElement} trackElement + * The track element to remove from the list. + * + * @private + */ + + + HtmlTrackElementList.prototype.removeTrackElement_ = function removeTrackElement_(trackElement) { + for (var i = 0, length = this.trackElements_.length; i < length; i++) { + if (trackElement === this.trackElements_[i]) { + this.trackElements_.splice(i, 1); + + break; + } + } + }; + + return HtmlTrackElementList; + }(); + + /** + * @file text-track-cue-list.js + */ + + /** + * @typedef {Object} TextTrackCueList~TextTrackCue + * + * @property {string} id + * The unique id for this text track cue + * + * @property {number} startTime + * The start time for this text track cue + * + * @property {number} endTime + * The end time for this text track cue + * + * @property {boolean} pauseOnExit + * Pause when the end time is reached if true. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue} + */ + + /** + * A List of TextTrackCues. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist} + */ + var TextTrackCueList = function () { + + /** + * Create an instance of this class.. + * + * @param {Array} cues + * A list of cues to be initialized with + */ + function TextTrackCueList(cues) { + classCallCheck(this, TextTrackCueList); + + TextTrackCueList.prototype.setCues_.call(this, cues); + + /** + * @memberof TextTrackCueList + * @member {number} length + * The current number of `TextTrackCue`s in the TextTrackCueList. + * @instance + */ + Object.defineProperty(this, 'length', { + get: function get$$1() { + return this.length_; + } + }); + } + + /** + * A setter for cues in this list. Creates getters + * an an index for the cues. + * + * @param {Array} cues + * An array of cues to set + * + * @private + */ + + + TextTrackCueList.prototype.setCues_ = function setCues_(cues) { + var oldLength = this.length || 0; + var i = 0; + var l = cues.length; + + this.cues_ = cues; + this.length_ = cues.length; + + var defineProp = function defineProp(index) { + if (!('' + index in this)) { + Object.defineProperty(this, '' + index, { + get: function get$$1() { + return this.cues_[index]; + } + }); + } + }; + + if (oldLength < l) { + i = oldLength; + + for (; i < l; i++) { + defineProp.call(this, i); + } + } + }; + + /** + * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id. + * + * @param {string} id + * The id of the cue that should be searched for. + * + * @return {TextTrackCueList~TextTrackCue|null} + * A single cue or null if none was found. + */ + + + TextTrackCueList.prototype.getCueById = function getCueById(id) { + var result = null; + + for (var i = 0, l = this.length; i < l; i++) { + var cue = this[i]; + + if (cue.id === id) { + result = cue; + break; + } + } + + return result; + }; + + return TextTrackCueList; + }(); + + /** + * @file track-kinds.js + */ + + /** + * All possible `VideoTrackKind`s + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind + * @typedef VideoTrack~Kind + * @enum + */ + var VideoTrackKind = { + alternative: 'alternative', + captions: 'captions', + main: 'main', + sign: 'sign', + subtitles: 'subtitles', + commentary: 'commentary' + }; + + /** + * All possible `AudioTrackKind`s + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind + * @typedef AudioTrack~Kind + * @enum + */ + var AudioTrackKind = { + 'alternative': 'alternative', + 'descriptions': 'descriptions', + 'main': 'main', + 'main-desc': 'main-desc', + 'translation': 'translation', + 'commentary': 'commentary' + }; + + /** + * All possible `TextTrackKind`s + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind + * @typedef TextTrack~Kind + * @enum + */ + var TextTrackKind = { + subtitles: 'subtitles', + captions: 'captions', + descriptions: 'descriptions', + chapters: 'chapters', + metadata: 'metadata' + }; + + /** + * All possible `TextTrackMode`s + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode + * @typedef TextTrack~Mode + * @enum + */ + var TextTrackMode = { + disabled: 'disabled', + hidden: 'hidden', + showing: 'showing' + }; + + /** + * @file track.js + */ + + /** + * A Track class that contains all of the common functionality for {@link AudioTrack}, + * {@link VideoTrack}, and {@link TextTrack}. + * + * > Note: This class should not be used directly + * + * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html} + * @extends EventTarget + * @abstract + */ + + var Track = function (_EventTarget) { + inherits(Track, _EventTarget); + + /** + * Create an instance of this class. + * + * @param {Object} [options={}] + * Object of option names and values + * + * @param {string} [options.kind=''] + * A valid kind for the track type you are creating. + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this AudioTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @abstract + */ + function Track() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, Track); + + var _this = possibleConstructorReturn(this, _EventTarget.call(this)); + + var trackProps = { + id: options.id || 'vjs_track_' + newGUID(), + kind: options.kind || '', + label: options.label || '', + language: options.language || '' + }; + + /** + * @memberof Track + * @member {string} id + * The id of this track. Cannot be changed after creation. + * @instance + * + * @readonly + */ + + /** + * @memberof Track + * @member {string} kind + * The kind of track that this is. Cannot be changed after creation. + * @instance + * + * @readonly + */ + + /** + * @memberof Track + * @member {string} label + * The label of this track. Cannot be changed after creation. + * @instance + * + * @readonly + */ + + /** + * @memberof Track + * @member {string} language + * The two letter language code for this track. Cannot be changed after + * creation. + * @instance + * + * @readonly + */ + + var _loop = function _loop(key) { + Object.defineProperty(_this, key, { + get: function get$$1() { + return trackProps[key]; + }, + set: function set$$1() {} + }); + }; + + for (var key in trackProps) { + _loop(key); + } + return _this; + } + + return Track; + }(EventTarget); + + /** + * @file url.js + * @module url + */ + + /** + * @typedef {Object} url:URLObject + * + * @property {string} protocol + * The protocol of the url that was parsed. + * + * @property {string} hostname + * The hostname of the url that was parsed. + * + * @property {string} port + * The port of the url that was parsed. + * + * @property {string} pathname + * The pathname of the url that was parsed. + * + * @property {string} search + * The search query of the url that was parsed. + * + * @property {string} hash + * The hash of the url that was parsed. + * + * @property {string} host + * The host of the url that was parsed. + */ + + /** + * Resolve and parse the elements of a URL. + * + * @param {String} url + * The url to parse + * + * @return {url:URLObject} + * An object of url details + */ + var parseUrl = function parseUrl(url) { + var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; + + // add the url to an anchor and let the browser parse the URL + var a = document_1.createElement('a'); + + a.href = url; + + // IE8 (and 9?) Fix + // ie8 doesn't parse the URL correctly until the anchor is actually + // added to the body, and an innerHTML is needed to trigger the parsing + var addToBody = a.host === '' && a.protocol !== 'file:'; + var div = void 0; + + if (addToBody) { + div = document_1.createElement('div'); + div.innerHTML = ''; + a = div.firstChild; + // prevent the div from affecting layout + div.setAttribute('style', 'display:none; position:absolute;'); + document_1.body.appendChild(div); + } + + // Copy the specific URL properties to a new object + // This is also needed for IE8 because the anchor loses its + // properties when it's removed from the dom + var details = {}; + + for (var i = 0; i < props.length; i++) { + details[props[i]] = a[props[i]]; + } + + // IE9 adds the port to the host property unlike everyone else. If + // a port identifier is added for standard ports, strip it. + if (details.protocol === 'http:') { + details.host = details.host.replace(/:80$/, ''); + } + + if (details.protocol === 'https:') { + details.host = details.host.replace(/:443$/, ''); + } + + if (!details.protocol) { + details.protocol = window_1.location.protocol; + } + + if (addToBody) { + document_1.body.removeChild(div); + } + + return details; + }; + + /** + * Get absolute version of relative URL. Used to tell flash correct URL. + * + * + * @param {string} url + * URL to make absolute + * + * @return {string} + * Absolute URL + * + * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue + */ + var getAbsoluteURL = function getAbsoluteURL(url) { + // Check if absolute URL + if (!url.match(/^https?:\/\//)) { + // Convert to absolute URL. Flash hosted off-site needs an absolute URL. + var div = document_1.createElement('div'); + + div.innerHTML = 'x'; + url = div.firstChild.href; + } + + return url; + }; + + /** + * Returns the extension of the passed file name. It will return an empty string + * if passed an invalid path. + * + * @param {string} path + * The fileName path like '/path/to/file.mp4' + * + * @returns {string} + * The extension in lower case or an empty string if no + * extension could be found. + */ + var getFileExtension = function getFileExtension(path) { + if (typeof path === 'string') { + var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i; + var pathParts = splitPathRe.exec(path); + + if (pathParts) { + return pathParts.pop().toLowerCase(); + } + } + + return ''; + }; + + /** + * Returns whether the url passed is a cross domain request or not. + * + * @param {string} url + * The url to check. + * + * @return {boolean} + * Whether it is a cross domain request or not. + */ + var isCrossOrigin = function isCrossOrigin(url) { + var winLoc = window_1.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 + // IE8 doesn't know location.origin, so we won't rely on it here + var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host; + + return crossOrigin; + }; + + var Url = /*#__PURE__*/Object.freeze({ + parseUrl: parseUrl, + getAbsoluteURL: getAbsoluteURL, + getFileExtension: getFileExtension, + isCrossOrigin: isCrossOrigin + }); + + var isFunction_1 = isFunction; + + var toString$1 = Object.prototype.toString; + + function isFunction(fn) { + var string = toString$1.call(fn); + return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( + // IE8 and below + fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt); + } + + var isFunction$1 = /*#__PURE__*/Object.freeze({ + default: isFunction_1, + __moduleExports: isFunction_1 + }); + + var trim_1 = createCommonjsModule(function (module, exports) { + exports = module.exports = trim; + + function trim(str) { + return str.replace(/^\s*|\s*$/g, ''); + } + + exports.left = function (str) { + return str.replace(/^\s*/, ''); + }; + + exports.right = function (str) { + return str.replace(/\s*$/, ''); + }; + }); + var trim_2 = trim_1.left; + var trim_3 = trim_1.right; + + var trim = /*#__PURE__*/Object.freeze({ + default: trim_1, + __moduleExports: trim_1, + left: trim_2, + right: trim_3 + }); + + var isFunction$2 = ( isFunction$1 && isFunction_1 ) || isFunction$1; + + var forEach_1 = forEach; + + var toString$2 = Object.prototype.toString; + var hasOwnProperty = Object.prototype.hasOwnProperty; + + function forEach(list, iterator, context) { + if (!isFunction$2(iterator)) { + throw new TypeError('iterator must be a function'); + } + + if (arguments.length < 3) { + context = this; + } + + if (toString$2.call(list) === '[object Array]') forEachArray(list, iterator, context);else if (typeof list === 'string') forEachString(list, iterator, context);else forEachObject(list, iterator, context); + } + + function forEachArray(array, iterator, context) { + for (var i = 0, len = array.length; i < len; i++) { + if (hasOwnProperty.call(array, i)) { + iterator.call(context, array[i], i, array); + } + } + } + + function forEachString(string, iterator, context) { + for (var i = 0, len = string.length; i < len; i++) { + // no such thing as a sparse string. + iterator.call(context, string.charAt(i), i, string); + } + } + + function forEachObject(object, iterator, context) { + for (var k in object) { + if (hasOwnProperty.call(object, k)) { + iterator.call(context, object[k], k, object); + } + } + } + + var forEach$1 = /*#__PURE__*/Object.freeze({ + default: forEach_1, + __moduleExports: forEach_1 + }); + + var trim$1 = ( trim && trim_1 ) || trim; + + var forEach$2 = ( forEach$1 && forEach_1 ) || forEach$1; + + var isArray = function isArray(arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + }; + + var parseHeaders = function parseHeaders(headers) { + if (!headers) return {}; + + var result = {}; + + forEach$2(trim$1(headers).split('\n'), function (row) { + var index = row.indexOf(':'), + key = trim$1(row.slice(0, index)).toLowerCase(), + value = trim$1(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 parseHeaders$1 = /*#__PURE__*/Object.freeze({ + default: parseHeaders, + __moduleExports: parseHeaders + }); + + 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 immutable$1 = /*#__PURE__*/Object.freeze({ + default: immutable, + __moduleExports: immutable + }); + + var parseHeaders$2 = ( parseHeaders$1 && parseHeaders ) || parseHeaders$1; + + var xtend = ( immutable$1 && immutable ) || immutable$1; + + 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$2(options)) { + callback = options; + if (typeof uri === "string") { + params = { uri: uri }; + } + } else { + params = xtend(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$2(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() {} + + /** + * @file text-track.js + */ + + /** + * Takes a webvtt file contents and parses it into cues + * + * @param {string} srcContent + * webVTT file contents + * + * @param {TextTrack} track + * TextTrack to add cues to. Cues come from the srcContent. + * + * @private + */ + var parseCues = function parseCues(srcContent, track) { + var parser = new window_1.WebVTT.Parser(window_1, window_1.vttjs, window_1.WebVTT.StringDecoder()); + var errors = []; + + parser.oncue = function (cue) { + track.addCue(cue); + }; + + parser.onparsingerror = function (error) { + errors.push(error); + }; + + parser.onflush = function () { + track.trigger({ + type: 'loadeddata', + target: track + }); + }; + + 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); + } + errors.forEach(function (error) { + return log$1.error(error); + }); + if (window_1.console && window_1.console.groupEnd) { + window_1.console.groupEnd(); + } + } + + parser.flush(); + }; + + /** + * Load a `TextTrack` from a specified url. + * + * @param {string} src + * Url to load track from. + * + * @param {TextTrack} track + * Track to add cues to. Comes from the content at the end of `url`. + * + * @private + */ + var loadTrack = function loadTrack(src, track) { + var opts = { + uri: src + }; + var crossOrigin = isCrossOrigin(src); + + if (crossOrigin) { + opts.cors = crossOrigin; + } + + xhr(opts, bind(this, function (err, response, responseBody) { + if (err) { + return log$1.error(err, response); + } + + 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 (track.tech_) { + var loadHandler = function loadHandler() { + return parseCues(responseBody, track); + }; + + track.tech_.on('vttjsloaded', loadHandler); + track.tech_.on('vttjserror', function () { + log$1.error('vttjs failed to load, stopping trying to process ' + track.src); + track.tech_.off('vttjsloaded', loadHandler); + }); + } + } else { + parseCues(responseBody, track); + } + })); + }; + + /** + * A representation of a single `TextTrack`. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack} + * @extends Track + */ + + var TextTrack = function (_Track) { + inherits(TextTrack, _Track); + + /** + * Create an instance of this class. + * + * @param {Object} options={} + * Object of option names and values + * + * @param {Tech} options.tech + * A reference to the tech that owns this TextTrack. + * + * @param {TextTrack~Kind} [options.kind='subtitles'] + * A valid text track kind. + * + * @param {TextTrack~Mode} [options.mode='disabled'] + * A valid text track mode. + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this TextTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @param {string} [options.srclang=''] + * A valid two character language code. An alternative, but deprioritized + * version of `options.language` + * + * @param {string} [options.src] + * A url to TextTrack cues. + * + * @param {boolean} [options.default] + * If this track should default to on or off. + */ + function TextTrack() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, TextTrack); + + if (!options.tech) { + throw new Error('A tech was not provided.'); + } + + var settings = mergeOptions(options, { + kind: TextTrackKind[options.kind] || 'subtitles', + language: options.language || options.srclang || '' + }); + var mode = TextTrackMode[settings.mode] || 'disabled'; + var default_ = settings.default; + + if (settings.kind === 'metadata' || settings.kind === 'chapters') { + mode = 'hidden'; + } + + var _this = possibleConstructorReturn(this, _Track.call(this, settings)); + + _this.tech_ = settings.tech; + + _this.cues_ = []; + _this.activeCues_ = []; + + var cues = new TextTrackCueList(_this.cues_); + var activeCues = new TextTrackCueList(_this.activeCues_); + var changed = false; + var timeupdateHandler = bind(_this, function () { + + // Accessing this.activeCues for the side-effects of updating itself + // due to it's nature as a getter function. Do not remove or cues will + // stop updating! + /* eslint-disable no-unused-expressions */ + this.activeCues; + /* eslint-enable no-unused-expressions */ + if (changed) { + this.trigger('cuechange'); + changed = false; + } + }); + + if (mode !== 'disabled') { + _this.tech_.ready(function () { + _this.tech_.on('timeupdate', timeupdateHandler); + }, true); + } + + Object.defineProperties(_this, { + /** + * @memberof TextTrack + * @member {boolean} default + * If this track was set to be on or off by default. Cannot be changed after + * creation. + * @instance + * + * @readonly + */ + default: { + get: function get$$1() { + return default_; + }, + set: function set$$1() {} + }, + + /** + * @memberof TextTrack + * @member {string} mode + * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will + * not be set if setting to an invalid mode. + * @instance + * + * @fires TextTrack#modechange + */ + mode: { + get: function get$$1() { + return mode; + }, + set: function set$$1(newMode) { + var _this2 = this; + + if (!TextTrackMode[newMode]) { + return; + } + mode = newMode; + if (mode === 'showing') { + + this.tech_.ready(function () { + _this2.tech_.on('timeupdate', timeupdateHandler); + }, true); + } + /** + * An event that fires when mode changes on this track. This allows + * the TextTrackList that holds this track to act accordingly. + * + * > Note: This is not part of the spec! + * + * @event TextTrack#modechange + * @type {EventTarget~Event} + */ + this.trigger('modechange'); + } + }, + + /** + * @memberof TextTrack + * @member {TextTrackCueList} cues + * The text track cue list for this TextTrack. + * @instance + */ + cues: { + get: function get$$1() { + if (!this.loaded_) { + return null; + } + + return cues; + }, + set: function set$$1() {} + }, + + /** + * @memberof TextTrack + * @member {TextTrackCueList} activeCues + * The list text track cues that are currently active for this TextTrack. + * @instance + */ + activeCues: { + get: function get$$1() { + if (!this.loaded_) { + return null; + } + + // nothing to do + if (this.cues.length === 0) { + return activeCues; + } + + var ct = this.tech_.currentTime(); + var active = []; + + for (var i = 0, l = this.cues.length; i < l; i++) { + var cue = this.cues[i]; + + if (cue.startTime <= ct && cue.endTime >= ct) { + active.push(cue); + } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) { + active.push(cue); + } + } + + changed = false; + + if (active.length !== this.activeCues_.length) { + changed = true; + } else { + for (var _i = 0; _i < active.length; _i++) { + if (this.activeCues_.indexOf(active[_i]) === -1) { + changed = true; + } + } + } + + this.activeCues_ = active; + activeCues.setCues_(this.activeCues_); + + return activeCues; + }, + set: function set$$1() {} + } + }); + + if (settings.src) { + _this.src = settings.src; + loadTrack(settings.src, _this); + } else { + _this.loaded_ = true; + } + return _this; + } + + /** + * Add a cue to the internal list of cues. + * + * @param {TextTrack~Cue} cue + * The cue to add to our internal list + */ + + + TextTrack.prototype.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); + + for (var prop in originalCue) { + if (!(prop in cue)) { + cue[prop] = originalCue[prop]; + } + } + + // make sure that `id` is copied over + cue.id = originalCue.id; + cue.originalCue_ = originalCue; + } + + var tracks = this.tech_.textTracks(); + + for (var i = 0; i < tracks.length; i++) { + if (tracks[i] !== this) { + tracks[i].removeCue(cue); + } + } + + this.cues_.push(cue); + this.cues.setCues_(this.cues_); + }; + + /** + * Remove a cue from our internal list + * + * @param {TextTrack~Cue} removeCue + * The cue to remove from our internal list + */ + + + TextTrack.prototype.removeCue = function removeCue(_removeCue) { + var i = this.cues_.length; + + while (i--) { + var cue = this.cues_[i]; + + if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) { + this.cues_.splice(i, 1); + this.cues.setCues_(this.cues_); + break; + } + } + }; + + return TextTrack; + }(Track); + + /** + * cuechange - One or more cues in the track have become active or stopped being active. + */ + + + TextTrack.prototype.allowedEvents_ = { + cuechange: 'cuechange' + }; + + /** + * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList} + * only one `AudioTrack` in the list will be enabled at a time. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack} + * @extends Track + */ + + var AudioTrack = function (_Track) { + inherits(AudioTrack, _Track); + + /** + * Create an instance of this class. + * + * @param {Object} [options={}] + * Object of option names and values + * + * @param {AudioTrack~Kind} [options.kind=''] + * A valid audio track kind + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this AudioTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @param {boolean} [options.enabled] + * If this track is the one that is currently playing. If this track is part of + * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled. + */ + function AudioTrack() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, AudioTrack); + + var settings = mergeOptions(options, { + kind: AudioTrackKind[options.kind] || '' + }); + + var _this = possibleConstructorReturn(this, _Track.call(this, settings)); + + var enabled = false; + + /** + * @memberof AudioTrack + * @member {boolean} enabled + * If this `AudioTrack` is enabled or not. When setting this will + * fire {@link AudioTrack#enabledchange} if the state of enabled is changed. + * @instance + * + * @fires VideoTrack#selectedchange + */ + Object.defineProperty(_this, 'enabled', { + get: function get$$1() { + return enabled; + }, + set: function set$$1(newEnabled) { + // an invalid or unchanged value + if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { + return; + } + enabled = newEnabled; + + /** + * An event that fires when enabled changes on this track. This allows + * the AudioTrackList that holds this track to act accordingly. + * + * > Note: This is not part of the spec! Native tracks will do + * this internally without an event. + * + * @event AudioTrack#enabledchange + * @type {EventTarget~Event} + */ + this.trigger('enabledchange'); + } + }); + + // if the user sets this track to selected then + // set selected to that true value otherwise + // we keep it false + if (settings.enabled) { + _this.enabled = settings.enabled; + } + _this.loaded_ = true; + return _this; + } + + return AudioTrack; + }(Track); + + /** + * A representation of a single `VideoTrack`. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack} + * @extends Track + */ + + var VideoTrack = function (_Track) { + inherits(VideoTrack, _Track); + + /** + * Create an instance of this class. + * + * @param {Object} [options={}] + * Object of option names and values + * + * @param {string} [options.kind=''] + * A valid {@link VideoTrack~Kind} + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this AudioTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @param {boolean} [options.selected] + * If this track is the one that is currently playing. + */ + function VideoTrack() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, VideoTrack); + + var settings = mergeOptions(options, { + kind: VideoTrackKind[options.kind] || '' + }); + + var _this = possibleConstructorReturn(this, _Track.call(this, settings)); + + var selected = false; + + /** + * @memberof VideoTrack + * @member {boolean} selected + * If this `VideoTrack` is selected or not. When setting this will + * fire {@link VideoTrack#selectedchange} if the state of selected changed. + * @instance + * + * @fires VideoTrack#selectedchange + */ + Object.defineProperty(_this, 'selected', { + get: function get$$1() { + return selected; + }, + set: function set$$1(newSelected) { + // an invalid or unchanged value + if (typeof newSelected !== 'boolean' || newSelected === selected) { + return; + } + selected = newSelected; + + /** + * An event that fires when selected changes on this track. This allows + * the VideoTrackList that holds this track to act accordingly. + * + * > Note: This is not part of the spec! Native tracks will do + * this internally without an event. + * + * @event VideoTrack#selectedchange + * @type {EventTarget~Event} + */ + this.trigger('selectedchange'); + } + }); + + // if the user sets this track to selected then + // set selected to that true value otherwise + // we keep it false + if (settings.selected) { + _this.selected = settings.selected; + } + return _this; + } + + return VideoTrack; + }(Track); + + /** + * @file html-track-element.js + */ + + /** + * @memberof HTMLTrackElement + * @typedef {HTMLTrackElement~ReadyState} + * @enum {number} + */ + var NONE = 0; + var LOADING = 1; + var LOADED = 2; + var ERROR = 3; + + /** + * A single track represented in the DOM. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement} + * @extends EventTarget + */ + + var HTMLTrackElement = function (_EventTarget) { + inherits(HTMLTrackElement, _EventTarget); + + /** + * Create an instance of this class. + * + * @param {Object} options={} + * Object of option names and values + * + * @param {Tech} options.tech + * A reference to the tech that owns this HTMLTrackElement. + * + * @param {TextTrack~Kind} [options.kind='subtitles'] + * A valid text track kind. + * + * @param {TextTrack~Mode} [options.mode='disabled'] + * A valid text track mode. + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this TextTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @param {string} [options.srclang=''] + * A valid two character language code. An alternative, but deprioritized + * vesion of `options.language` + * + * @param {string} [options.src] + * A url to TextTrack cues. + * + * @param {boolean} [options.default] + * If this track should default to on or off. + */ + function HTMLTrackElement() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, HTMLTrackElement); + + var _this = possibleConstructorReturn(this, _EventTarget.call(this)); + + var readyState = void 0; + + var track = new TextTrack(options); + + _this.kind = track.kind; + _this.src = track.src; + _this.srclang = track.language; + _this.label = track.label; + _this.default = track.default; + + Object.defineProperties(_this, { + + /** + * @memberof HTMLTrackElement + * @member {HTMLTrackElement~ReadyState} readyState + * The current ready state of the track element. + * @instance + */ + readyState: { + get: function get$$1() { + return readyState; + } + }, + + /** + * @memberof HTMLTrackElement + * @member {TextTrack} track + * The underlying TextTrack object. + * @instance + * + */ + track: { + get: function get$$1() { + return track; + } + } + }); + + readyState = NONE; + + /** + * @listens TextTrack#loadeddata + * @fires HTMLTrackElement#load + */ + track.addEventListener('loadeddata', function () { + readyState = LOADED; + + _this.trigger({ + type: 'load', + target: _this + }); + }); + return _this; + } + + return HTMLTrackElement; + }(EventTarget); + + HTMLTrackElement.prototype.allowedEvents_ = { + load: 'load' + }; + + HTMLTrackElement.NONE = NONE; + HTMLTrackElement.LOADING = LOADING; + HTMLTrackElement.LOADED = LOADED; + HTMLTrackElement.ERROR = ERROR; + + /* + * This file contains all track properties that are used in + * player.js, tech.js, html5.js and possibly other techs in the future. + */ + + var NORMAL = { + audio: { + ListClass: AudioTrackList, + TrackClass: AudioTrack, + capitalName: 'Audio' + }, + video: { + ListClass: VideoTrackList, + TrackClass: VideoTrack, + capitalName: 'Video' + }, + text: { + ListClass: TextTrackList, + TrackClass: TextTrack, + capitalName: 'Text' + } + }; + + Object.keys(NORMAL).forEach(function (type) { + NORMAL[type].getterName = type + 'Tracks'; + NORMAL[type].privateName = type + 'Tracks_'; + }); + + var REMOTE = { + remoteText: { + ListClass: TextTrackList, + TrackClass: TextTrack, + capitalName: 'RemoteText', + getterName: 'remoteTextTracks', + privateName: 'remoteTextTracks_' + }, + remoteTextEl: { + ListClass: HtmlTrackElementList, + TrackClass: HTMLTrackElement, + capitalName: 'RemoteTextTrackEls', + getterName: 'remoteTextTrackEls', + privateName: 'remoteTextTrackEls_' + } + }; + + var ALL = mergeOptions(NORMAL, REMOTE); + + REMOTE.names = Object.keys(REMOTE); + NORMAL.names = Object.keys(NORMAL); + ALL.names = [].concat(REMOTE.names).concat(NORMAL.names); + + /** + * Copyright 2013 vtt.js Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + /* -*- 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 function (o) { + if (arguments.length !== 1) { + throw new Error('Object.create shim only accepts one parameter.'); + } + F.prototype = o; + return new F(); + }; + }(); + + // Creates a new ParserError object from an errorData object. The errorData + // object should have default code and message properties. The default message + // property can be overriden by passing in a message parameter. + // See ParsingError.Errors below for acceptable errors. + function ParsingError(errorData, message) { + this.name = "ParsingError"; + this.code = errorData.code; + this.message = message || errorData.message; + } + ParsingError.prototype = _objCreate(Error.prototype); + ParsingError.prototype.constructor = ParsingError; + + // ParsingError metadata for acceptable ParsingErrors. + ParsingError.Errors = { + BadSignature: { + code: 0, + message: "Malformed WebVTT signature." + }, + BadTimeStamp: { + code: 1, + message: "Malformed time stamp." + } + }; + + // Try to parse input as a time stamp. + function parseTimeStamp(input) { + + function computeSeconds(h, m, s, f) { + return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000; + } + + var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/); + if (!m) { + return null; + } + + if (m[3]) { + // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] + return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]); + } else if (m[1] > 59) { + // Timestamp takes the form of [hours]:[minutes].[milliseconds] + // First position is hours as it's over 59. + return computeSeconds(m[1], m[2], 0, m[4]); + } else { + // Timestamp takes the form of [minutes]:[seconds].[milliseconds] + return computeSeconds(0, m[1], m[2], m[4]); + } + } + + // A settings object holds key/value pairs and will ignore anything but the first + // assignment to a specific key. + function Settings() { + this.values = _objCreate(null); + } + + Settings.prototype = { + // Only accept the first assignment to any key. + set: function set(k, v) { + if (!this.get(k) && v !== "") { + this.values[k] = v; + } + }, + // Return the value for a key, or a default value. + // If 'defaultKey' is passed then 'dflt' is assumed to be an object with + // a number of possible default values as properties where 'defaultKey' is + // the key of the property that will be chosen; otherwise it's assumed to be + // a single value. + get: function get(k, dflt, defaultKey) { + if (defaultKey) { + return this.has(k) ? this.values[k] : dflt[defaultKey]; + } + return this.has(k) ? this.values[k] : dflt; + }, + // Check whether we have a value for a key. + has: function has(k) { + return k in this.values; + }, + // Accept a setting if its one of the given alternatives. + alt: function alt(k, v, a) { + for (var n = 0; n < a.length; ++n) { + if (v === a[n]) { + this.set(k, v); + break; + } + } + }, + // Accept a setting if its a valid (signed) integer. + integer: function integer(k, v) { + if (/^-?\d+$/.test(v)) { + // integer + this.set(k, parseInt(v, 10)); + } + }, + // Accept a setting if its a valid percentage. + percent: function percent(k, v) { + var m; + if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) { + v = parseFloat(v); + if (v >= 0 && v <= 100) { + this.set(k, v); + return true; + } + } + return false; + } + }; + + // Helper function to parse input into groups separated by 'groupDelim', and + // interprete each group as a key/value pair separated by 'keyValueDelim'. + function parseOptions(input, callback, keyValueDelim, groupDelim) { + var groups = groupDelim ? input.split(groupDelim) : [input]; + for (var i in groups) { + if (typeof groups[i] !== "string") { + continue; + } + var kv = groups[i].split(keyValueDelim); + if (kv.length !== 2) { + continue; + } + var k = kv[0]; + var v = kv[1]; + callback(k, v); + } + } + + function parseCue(input, cue, regionList) { + // Remember the original input if we need to throw an error. + var oInput = input; + // 4.1 WebVTT timestamp + function consumeTimeStamp() { + var ts = parseTimeStamp(input); + if (ts === null) { + throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput); + } + // Remove time stamp from input. + input = input.replace(/^[^\sa-zA-Z-]+/, ""); + return ts; + } + + // 4.4.2 WebVTT cue settings + function consumeCueSettings(input, cue) { + var settings = new Settings(); + + parseOptions(input, function (k, v) { + switch (k) { + case "region": + // Find the last region we parsed with the same region id. + for (var i = regionList.length - 1; i >= 0; i--) { + if (regionList[i].id === v) { + settings.set(k, regionList[i].region); + break; + } + } + break; + case "vertical": + settings.alt(k, v, ["rl", "lr"]); + break; + case "line": + var vals = v.split(","), + vals0 = vals[0]; + settings.integer(k, vals0); + settings.percent(k, vals0) ? settings.set("snapToLines", false) : null; + settings.alt(k, vals0, ["auto"]); + if (vals.length === 2) { + settings.alt("lineAlign", vals[1], ["start", "middle", "end"]); + } + break; + case "position": + vals = v.split(","); + settings.percent(k, vals[0]); + if (vals.length === 2) { + settings.alt("positionAlign", vals[1], ["start", "middle", "end"]); + } + break; + case "size": + settings.percent(k, v); + break; + case "align": + settings.alt(k, v, ["start", "middle", "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"); + 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.positionAlign = settings.get("positionAlign", { + start: "start", + left: "start", + middle: "middle", + end: "end", + right: "end" + }, cue.align); + } + + function skipWhitespace() { + input = input.replace(/^\s+/, ""); + } + + // 4.1 WebVTT cue timings. + skipWhitespace(); + cue.startTime = consumeTimeStamp(); // (1) collect cue start time + skipWhitespace(); + if (input.substr(0, 3) !== "-->") { + // (3) next characters must match "-->" + throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput); + } + input = input.substr(3); + skipWhitespace(); + cue.endTime = consumeTimeStamp(); // (5) collect cue end time + + // 4.1 WebVTT cue settings list. + skipWhitespace(); + consumeCueSettings(input, cue); + } + + var ESCAPE = { + "&": "&", + "<": "<", + ">": ">", + "‎": "\u200E", + "‏": "\u200F", + " ": "\xA0" + }; + + var TAG_NAME = { + c: "span", + i: "i", + b: "b", + u: "u", + ruby: "ruby", + rt: "rt", + v: "span", + lang: "span" + }; + + var TAG_ANNOTATION = { + v: "title", + lang: "lang" + }; + + var NEEDS_PARENT = { + rt: "ruby" + }; + + // Parse content into a document fragment. + function parseContent(window, input) { + function nextToken() { + // Check for end-of-string. + if (!input) { + return null; + } + + // Consume 'n' characters from the input. + function consume(result) { + input = input.substr(result.length); + return result; + } + + var m = input.match(/^([^<]*)(<[^>]*>?)?/); + // If there is some text before the next tag, return it, otherwise return + // 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); + } + return s; + } + + function shouldAdd(current, element) { + return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName; + } + + // Create an element for this tag. + function createElement(type, annotation) { + var tagName = TAG_NAME[type]; + if (!tagName) { + return null; + } + var element = window.document.createElement(tagName); + element.localName = tagName; + var name = TAG_ANNOTATION[type]; + if (name && annotation) { + element[name] = annotation.trim(); + } + return element; + } + + var rootDiv = window.document.createElement("div"), + current = rootDiv, + t, + tagStack = []; + + while ((t = nextToken()) !== null) { + if (t[0] === '<') { + if (t[1] === "/") { + // If the closing tag matches, move back up to the parent node. + if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) { + tagStack.pop(); + current = current.parentNode; + } + // Otherwise just ignore the end tag. + continue; + } + var ts = parseTimeStamp(t.substr(1, t.length - 2)); + var node; + if (ts) { + // Timestamps are lead nodes as well. + node = window.document.createProcessingInstruction("timestamp", ts); + current.appendChild(node); + continue; + } + var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); + // If we can't parse the tag, skip to the next tag. + if (!m) { + continue; + } + // Try to construct an element, and ignore the tag if we couldn't. + node = createElement(m[1], m[3]); + if (!node) { + continue; + } + // Determine if the tag should be added based on the context of where it + // is placed in the cuetext. + if (!shouldAdd(current, node)) { + continue; + } + // Set the class list (as a list of classes, separated by space). + if (m[2]) { + node.className = m[2].substr(1).replace('.', ' '); + } + // Append the node to the current node, and enter the scope of the new + // node. + tagStack.push(m[1]); + current.appendChild(node); + current = node; + continue; + } + + // Text nodes are leaf nodes. + current.appendChild(window.document.createTextNode(unescape(t))); + } + + return rootDiv; + } + + // This is a list of all the Unicode characters that have a strong + // right-to-left category. What this means is that these characters are + // written right-to-left for sure. It was generated by pulling all the strong + // right-to-left characters out of the Unicode data table. That table can + // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt + var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 0x1093f], [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13], [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58], [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72], [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f], [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32], [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42], [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f], [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59], [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62], [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77], [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b], [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]]; + + function isStrongRTLChar(charCode) { + for (var i = 0; i < strongRTLRanges.length; i++) { + var currentRange = strongRTLRanges[i]; + if (charCode >= currentRange[0] && charCode <= currentRange[1]) { + return true; + } + } + + return false; + } + + function determineBidi(cueDiv) { + var nodeStack = [], + text = "", + charCode; + + if (!cueDiv || !cueDiv.childNodes) { + return "ltr"; + } + + function pushNodes(nodeStack, node) { + for (var i = node.childNodes.length - 1; i >= 0; i--) { + nodeStack.push(node.childNodes[i]); + } + } + + function nextTextNode(nodeStack) { + if (!nodeStack || !nodeStack.length) { + return null; + } + + var node = nodeStack.pop(), + text = node.textContent || node.innerText; + if (text) { + // TODO: This should match all unicode type B characters (paragraph + // separator characters). See issue #115. + var m = text.match(/^.*(\n|\r)/); + if (m) { + nodeStack.length = 0; + return m[0]; + } + return text; + } + if (node.tagName === "ruby") { + return nextTextNode(nodeStack); + } + if (node.childNodes) { + pushNodes(nodeStack, node); + return nextTextNode(nodeStack); + } + } + + pushNodes(nodeStack, cueDiv); + while (text = nextTextNode(nodeStack)) { + for (var i = 0; i < text.length; i++) { + charCode = text.charCodeAt(i); + if (isStrongRTLChar(charCode)) { + return "rtl"; + } + } + } + return "ltr"; + } + + function computeLinePos(cue) { + if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) { + return cue.line; + } + if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) { + return -1; + } + var track = cue.track, + trackList = track.textTrackList, + count = 0; + for (var i = 0; i < trackList.length && trackList[i] !== track; i++) { + if (trackList[i].mode === "showing") { + count++; + } + } + return ++count * -1; + } + + function StyleBox() {} + + // Apply styles to a div. If there is no div passed then it defaults to the + // div on 'this'. + StyleBox.prototype.applyStyles = function (styles, div) { + div = div || this.div; + for (var prop in styles) { + if (styles.hasOwnProperty(prop)) { + div.style[prop] = styles[prop]; + } + } + }; + + StyleBox.prototype.formatStyle = function (val, unit) { + return val === 0 ? 0 : val + unit; + }; + + // Constructs the computed display state of the cue (a div). Places the div + // into the overlay which should be a block level element (usually a div). + function CueStyleBox(window, cue, styleOptions) { + StyleBox.call(this); + this.cue = cue; + + // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will + // have inline positioning and will function as the cue background box. + this.cueDiv = parseContent(window, cue.text); + var styles = { + color: "rgba(255, 255, 255, 1)", + backgroundColor: "rgba(0, 0, 0, 0.8)", + position: "relative", + left: 0, + right: 0, + top: 0, + bottom: 0, + display: "inline", + writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", + unicodeBidi: "plaintext" + }; + + 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. + this.div = window.document.createElement("div"); + styles = { + direction: determineBidi(this.cueDiv), + writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", + unicodeBidi: "plaintext", + textAlign: cue.align === "middle" ? "center" : cue.align, + font: styleOptions.font, + whiteSpace: "pre-line", + position: "absolute" + }; + + this.applyStyles(styles); + this.div.appendChild(this.cueDiv); + + // Calculate the distance from the reference edge of the viewport to the text + // position of the cue box. The reference edge will be resolved later when + // the box orientation styles are applied. + var textPos = 0; + switch (cue.positionAlign) { + case "start": + textPos = cue.position; + break; + case "middle": + textPos = cue.position - cue.size / 2; + break; + case "end": + textPos = cue.position - cue.size; + break; + } + + // Horizontal box orientation; textPos is the distance from the left edge of the + // area to the left edge of the box and cue.size is the distance extending to + // the right from there. + if (cue.vertical === "") { + this.applyStyles({ + left: this.formatStyle(textPos, "%"), + width: this.formatStyle(cue.size, "%") + }); + // Vertical box orientation; textPos is the distance from the top edge of the + // area to the top edge of the box and cue.size is the height extending + // downwards from there. + } else { + this.applyStyles({ + top: this.formatStyle(textPos, "%"), + height: this.formatStyle(cue.size, "%") + }); + } + + this.move = function (box) { + this.applyStyles({ + top: this.formatStyle(box.top, "px"), + bottom: this.formatStyle(box.bottom, "px"), + left: this.formatStyle(box.left, "px"), + right: this.formatStyle(box.right, "px"), + height: this.formatStyle(box.height, "px"), + width: this.formatStyle(box.width, "px") + }); + }; + } + CueStyleBox.prototype = _objCreate(StyleBox.prototype); + CueStyleBox.prototype.constructor = CueStyleBox; + + // Represents the co-ordinates of an Element in a way that we can easily + // compute things with such as if it overlaps or intersects with another Element. + // Can initialize it with either a StyleBox or another BoxPosition. + function BoxPosition(obj) { + // Either a BoxPosition was passed in and we need to copy it, or a StyleBox + // was passed in and we need to copy the results of 'getBoundingClientRect' + // as the object returned is readonly. All co-ordinate values are in reference + // to the viewport origin (top left). + var lh, height, width, top; + if (obj.div) { + height = obj.div.offsetHeight; + width = obj.div.offsetWidth; + top = obj.div.offsetTop; + + var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects(); + obj = obj.div.getBoundingClientRect(); + // In certain cases the outter div will be slightly larger then the sum of + // the inner div's lines. This could be due to bold text, etc, on some platforms. + // In this case we should get the average line height and use that. This will + // result in the desired behaviour. + lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0; + } + this.left = obj.left; + this.right = obj.right; + this.top = obj.top || top; + this.height = obj.height || height; + this.bottom = obj.bottom || top + (obj.height || height); + this.width = obj.width || width; + this.lineHeight = lh !== undefined ? lh : obj.lineHeight; + } + + // Move the box along a particular axis. Optionally pass in an amount to move + // the box. If no amount is passed then the default is the line height of the + // box. + BoxPosition.prototype.move = function (axis, toMove) { + toMove = toMove !== undefined ? toMove : this.lineHeight; + switch (axis) { + case "+x": + this.left += toMove; + this.right += toMove; + break; + case "-x": + this.left -= toMove; + this.right -= toMove; + break; + case "+y": + this.top += toMove; + this.bottom += toMove; + break; + case "-y": + this.top -= toMove; + this.bottom -= toMove; + break; + } + }; + + // Check if this box overlaps another box, b2. + BoxPosition.prototype.overlaps = function (b2) { + return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top; + }; + + // Check if this box overlaps any other boxes in boxes. + BoxPosition.prototype.overlapsAny = function (boxes) { + for (var i = 0; i < boxes.length; i++) { + if (this.overlaps(boxes[i])) { + return true; + } + } + return false; + }; + + // Check if this box is within another box. + BoxPosition.prototype.within = function (container) { + return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right; + }; + + // Check if this box is entirely within the container or it is overlapping + // on the edge opposite of the axis direction passed. For example, if "+x" is + // passed and the box is overlapping on the left edge of the container, then + // return true. + BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) { + switch (axis) { + case "+x": + return this.left < container.left; + case "-x": + return this.right > container.right; + case "+y": + return this.top < container.top; + case "-y": + return this.bottom > container.bottom; + } + }; + + // Find the percentage of the area that this box is overlapping with another + // box. + BoxPosition.prototype.intersectPercentage = function (b2) { + var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)), + y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)), + intersectArea = x * y; + return intersectArea / (this.height * this.width); + }; + + // Convert the positions from this box to CSS compatible positions using + // the reference container's positions. This has to be done because this + // box's positions are in reference to the viewport origin, whereas, CSS + // values are in referecne to their respective edges. + BoxPosition.prototype.toCSSCompatValues = function (reference) { + return { + top: this.top - reference.top, + bottom: reference.bottom - this.bottom, + left: this.left - reference.left, + right: reference.right - this.right, + height: this.height, + width: this.width + }; + }; + + // Get an object that represents the box's position without anything extra. + // Can pass a StyleBox, HTMLElement, or another BoxPositon. + BoxPosition.getSimpleBoxPosition = function (obj) { + var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0; + var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0; + var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0; + + obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj; + var ret = { + left: obj.left, + right: obj.right, + top: obj.top || top, + height: obj.height || height, + bottom: obj.bottom || top + (obj.height || height), + width: obj.width || width + }; + return ret; + }; + + // Move a StyleBox to its specified, or next best, position. The containerBox + // is the box that contains the StyleBox, such as a div. boxPositions are + // a list of other boxes that the styleBox can't overlap with. + function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) { + + // Find the best position for a cue box, b, on the video. The axis parameter + // is a list of axis, the order of which, it will move the box along. For example: + // Passing ["+x", "-x"] will move the box first along the x axis in the positive + // direction. If it doesn't find a good position for it there it will then move + // it along the x axis in the negative direction. + function findBestPosition(b, axis) { + var bestPosition, + specifiedPosition = new BoxPosition(b), + percentage = 1; // Highest possible so the first thing we get is better. + + for (var i = 0; i < axis.length; i++) { + while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) { + b.move(axis[i]); + } + // We found a spot where we aren't overlapping anything. This is our + // best position. + if (b.within(containerBox)) { + return b; + } + var p = b.intersectPercentage(containerBox); + // If we're outside the container box less then we were on our last try + // then remember this position as the best position. + if (percentage > p) { + bestPosition = new BoxPosition(b); + percentage = p; + } + // Reset the box position to the specified position. + b = new BoxPosition(specifiedPosition); + } + return bestPosition || specifiedPosition; + } + + var boxPosition = new BoxPosition(styleBox), + cue = styleBox.cue, + linePos = computeLinePos(cue), + axis = []; + + // If we have a line number to align the cue to. + if (cue.snapToLines) { + var size; + switch (cue.vertical) { + case "": + axis = ["+y", "-y"]; + size = "height"; + break; + case "rl": + axis = ["+x", "-x"]; + size = "width"; + break; + case "lr": + axis = ["-x", "+x"]; + size = "width"; + break; + } + + var step = boxPosition.lineHeight, + position = step * Math.round(linePos), + maxPosition = containerBox[size] + step, + initialAxis = axis[0]; + + // If the specified intial position is greater then the max position then + // clamp the box to the amount of steps it would take for the box to + // reach the max position. + if (Math.abs(position) > maxPosition) { + position = position < 0 ? -1 : 1; + position *= Math.ceil(maxPosition / step) * step; + } + + // If computed line position returns negative then line numbers are + // relative to the bottom of the video instead of the top. Therefore, we + // need to increase our initial position by the length or width of the + // video, depending on the writing direction, and reverse our axis directions. + if (linePos < 0) { + position += cue.vertical === "" ? containerBox.height : containerBox.width; + axis = axis.reverse(); + } + + // Move the box to the specified position. This may not be its best + // position. + boxPosition.move(initialAxis, position); + } else { + // If we have a percentage line value for the cue. + var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100; + + switch (cue.lineAlign) { + case "middle": + linePos -= calculatedPercentage / 2; + break; + case "end": + linePos -= calculatedPercentage; + break; + } + + // Apply initial line position to the cue box. + switch (cue.vertical) { + case "": + styleBox.applyStyles({ + top: styleBox.formatStyle(linePos, "%") + }); + break; + case "rl": + styleBox.applyStyles({ + left: styleBox.formatStyle(linePos, "%") + }); + break; + case "lr": + styleBox.applyStyles({ + right: styleBox.formatStyle(linePos, "%") + }); + break; + } + + axis = ["+y", "-x", "+x", "-y"]; + + // Get the box position again after we've applied the specified positioning + // to it. + boxPosition = new BoxPosition(styleBox); + } + + var bestPosition = findBestPosition(boxPosition, axis); + styleBox.move(bestPosition.toCSSCompatValues(containerBox)); + } + + function WebVTT$1() {} + // Nothing + + + // Helper to allow strings to be decoded instead of the default binary utf8 data. + WebVTT$1.StringDecoder = function () { + return { + decode: function decode(data) { + if (!data) { + return ""; + } + if (typeof data !== "string") { + throw new Error("Error - expected string data."); + } + return decodeURIComponent(encodeURIComponent(data)); + } + }; + }; + + WebVTT$1.convertCueToDOMTree = function (window, cuetext) { + if (!window || !cuetext) { + return null; + } + return parseContent(window, cuetext); + }; + + var FONT_SIZE_PERCENT = 0.05; + var FONT_STYLE = "sans-serif"; + var CUE_BACKGROUND_PADDING = "1.5%"; + + // Runs the processing model over the cues and regions passed to it. + // @param overlay A block level element (usually a div) that the computed cues + // and regions will be placed into. + WebVTT$1.processCues = function (window, cues, overlay) { + if (!window || !cues || !overlay) { + return null; + } + + // Remove all previous children. + while (overlay.firstChild) { + overlay.removeChild(overlay.firstChild); + } + + var paddedOverlay = window.document.createElement("div"); + paddedOverlay.style.position = "absolute"; + paddedOverlay.style.left = "0"; + paddedOverlay.style.right = "0"; + paddedOverlay.style.top = "0"; + paddedOverlay.style.bottom = "0"; + paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; + overlay.appendChild(paddedOverlay); + + // Determine if we need to compute the display states of the cues. This could + // be the case if a cue's state has been changed since the last computation or + // if it has not been computed yet. + function shouldCompute(cues) { + for (var i = 0; i < cues.length; i++) { + if (cues[i].hasBeenReset || !cues[i].displayState) { + return true; + } + } + return false; + } + + // We don't need to recompute the cues' display states. Just reuse them. + if (!shouldCompute(cues)) { + for (var i = 0; i < cues.length; i++) { + paddedOverlay.appendChild(cues[i].displayState); + } + return; + } + + var boxPositions = [], + containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), + fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; + var styleOptions = { + font: fontSize + "px " + FONT_STYLE + }; + + (function () { + var styleBox, cue; + + for (var i = 0; i < cues.length; i++) { + cue = cues[i]; + + // Compute the intial position and styles of the cue div. + styleBox = new CueStyleBox(window, cue, styleOptions); + paddedOverlay.appendChild(styleBox.div); + + // Move the cue div to it's correct line position. + moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); + + // Remember the computed div so that we don't have to recompute it later + // if we don't have too. + cue.displayState = styleBox.div; + + boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); + } + })(); + }; + + WebVTT$1.Parser = function (window, vttjs, decoder) { + if (!decoder) { + decoder = vttjs; + vttjs = {}; + } + if (!vttjs) { + vttjs = {}; + } + + this.window = window; + this.vttjs = vttjs; + this.state = "INITIAL"; + this.buffer = ""; + this.decoder = decoder || new TextDecoder("utf8"); + this.regionList = []; + }; + + WebVTT$1.Parser.prototype = { + // If the error is a ParsingError then report it to the consumer if + // possible. If it's not a ParsingError then throw it like normal. + reportOrThrowError: function reportOrThrowError(e) { + if (e instanceof ParsingError) { + this.onparsingerror && this.onparsingerror(e); + } else { + throw e; + } + }, + parse: function parse(data) { + var self = this; + + // If there is no data then we won't decode it, but will just try to parse + // whatever is in buffer already. This may occur in circumstances, for + // example when flush() is called. + if (data) { + // Try to decode the data that we received. + self.buffer += self.decoder.decode(data, { stream: true }); + } + + function collectNextLine() { + var buffer = self.buffer; + var pos = 0; + while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { + ++pos; + } + var line = buffer.substr(0, pos); + // Advance the buffer early in case we fail below. + if (buffer[pos] === '\r') { + ++pos; + } + if (buffer[pos] === '\n') { + ++pos; + } + self.buffer = buffer.substr(pos); + return line; + } + + // 3.4 WebVTT region and WebVTT region settings syntax + function parseRegion(input) { + var settings = new Settings(); + + parseOptions(input, function (k, v) { + switch (k) { + case "id": + settings.set(k, v); + break; + case "width": + settings.percent(k, v); + break; + case "lines": + settings.integer(k, v); + break; + case "regionanchor": + case "viewportanchor": + var xy = v.split(','); + if (xy.length !== 2) { + break; + } + // We have to make sure both x and y parse, so use a temporary + // settings object here. + var anchor = new Settings(); + anchor.percent("x", xy[0]); + anchor.percent("y", xy[1]); + if (!anchor.has("x") || !anchor.has("y")) { + break; + } + settings.set(k + "X", anchor.get("x")); + settings.set(k + "Y", anchor.get("y")); + break; + case "scroll": + settings.alt(k, v, ["up"]); + break; + } + }, /=/, /\s/); + + // Create the region, using default values for any values that were not + // specified. + if (settings.has("id")) { + var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)(); + region.width = settings.get("width", 100); + region.lines = settings.get("lines", 3); + region.regionAnchorX = settings.get("regionanchorX", 0); + region.regionAnchorY = settings.get("regionanchorY", 100); + region.viewportAnchorX = settings.get("viewportanchorX", 0); + region.viewportAnchorY = settings.get("viewportanchorY", 100); + region.scroll = settings.get("scroll", ""); + // Register the region. + self.onregion && self.onregion(region); + // Remember the VTTRegion for later in case we parse any VTTCues that + // reference it. + self.regionList.push({ + id: settings.get("id"), + region: region + }); + } + } + + // draft-pantos-http-live-streaming-20 + // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5 + // 3.5 WebVTT + function parseTimestampMap(input) { + var settings = new Settings(); + + parseOptions(input, function (k, v) { + switch (k) { + case "MPEGT": + settings.integer(k + 'S', v); + break; + case "LOCA": + settings.set(k + 'L', parseTimeStamp(v)); + break; + } + }, /[^\d]:/, /,/); + + self.ontimestampmap && self.ontimestampmap({ + "MPEGTS": settings.get("MPEGTS"), + "LOCAL": settings.get("LOCAL") + }); + } + + // 3.2 WebVTT metadata header syntax + function parseHeader(input) { + if (input.match(/X-TIMESTAMP-MAP/)) { + // This line contains HLS X-TIMESTAMP-MAP metadata + parseOptions(input, function (k, v) { + switch (k) { + case "X-TIMESTAMP-MAP": + parseTimestampMap(v); + break; + } + }, /=/); + } else { + parseOptions(input, function (k, v) { + switch (k) { + case "Region": + // 3.3 WebVTT region metadata header syntax + parseRegion(v); + break; + } + }, /:/); + } + } + + // 5.1 WebVTT file parsing. + try { + var line; + if (self.state === "INITIAL") { + // We can't start parsing until we have the first line. + if (!/\r\n|\n/.test(self.buffer)) { + return this; + } + + line = collectNextLine(); + + var m = line.match(/^WEBVTT([ \t].*)?$/); + if (!m || !m[0]) { + throw new ParsingError(ParsingError.Errors.BadSignature); + } + + self.state = "HEADER"; + } + + var alreadyCollectedLine = false; + while (self.buffer) { + // We can't parse a line until we have the full line. + if (!/\r\n|\n/.test(self.buffer)) { + return this; + } + + if (!alreadyCollectedLine) { + line = collectNextLine(); + } else { + alreadyCollectedLine = false; + } + + switch (self.state) { + case "HEADER": + // 13-18 - Allow a header (metadata) under the WEBVTT line. + if (/:/.test(line)) { + parseHeader(line); + } else if (!line) { + // An empty line terminates the header and starts the body (cues). + self.state = "ID"; + } + continue; + case "NOTE": + // Ignore NOTE blocks. + if (!line) { + self.state = "ID"; + } + continue; + case "ID": + // Check for the start of NOTE blocks. + if (/^NOTE($|[ \t])/.test(line)) { + self.state = "NOTE"; + break; + } + // 19-29 - Allow any number of line terminators, then initialize new cue values. + if (!line) { + continue; + } + self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); + self.state = "CUE"; + // 30-39 - Check if self line contains an optional identifier or timing data. + if (line.indexOf("-->") === -1) { + self.cue.id = line; + continue; + } + // Process line as start of a cue. + /*falls through*/ + case "CUE": + // 40 - Collect cue timings and settings. + try { + parseCue(line, self.cue, self.regionList); + } catch (e) { + self.reportOrThrowError(e); + // In case of an error ignore rest of the cue. + self.cue = null; + self.state = "BADCUE"; + continue; + } + self.state = "CUETEXT"; + continue; + case "CUETEXT": + var hasSubstring = line.indexOf("-->") !== -1; + // 34 - If we have an empty line then report the cue. + // 35 - If we have the special substring '-->' then report the cue, + // but do not collect the line as we need to process the current + // one as a new cue. + if (!line || hasSubstring && (alreadyCollectedLine = true)) { + // We are done parsing self cue. + self.oncue && self.oncue(self.cue); + self.cue = null; + self.state = "ID"; + continue; + } + if (self.cue.text) { + self.cue.text += "\n"; + } + self.cue.text += line; + continue; + case "BADCUE": + // BADCUE + // 54-62 - Collect and discard the remaining cue. + if (!line) { + self.state = "ID"; + } + continue; + } + } + } catch (e) { + self.reportOrThrowError(e); + + // If we are currently parsing a cue, report what we have. + if (self.state === "CUETEXT" && self.cue && self.oncue) { + self.oncue(self.cue); + } + self.cue = null; + // Enter BADWEBVTT state if header was not parsed correctly otherwise + // another exception occurred so enter BADCUE state. + self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; + } + return this; + }, + flush: function flush() { + var self = this; + try { + // Finish decoding the stream. + self.buffer += self.decoder.decode(); + // Synthesize the end of the current cue or region. + if (self.cue || self.state === "HEADER") { + self.buffer += "\n\n"; + self.parse(); + } + // If we've flushed, parsed, and we're still on the INITIAL state then + // that means we don't have enough of the stream to parse the first + // line. + if (self.state === "INITIAL") { + throw new ParsingError(ParsingError.Errors.BadSignature); + } + } catch (e) { + self.reportOrThrowError(e); + } + self.onflush && self.onflush(); + return this; + } + }; + + var vtt = WebVTT$1; + + var vtt$1 = /*#__PURE__*/Object.freeze({ + default: vtt, + __moduleExports: vtt + }); + + /** + * Copyright 2013 vtt.js Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var autoKeyword = "auto"; + var directionSetting = { + "": 1, + "lr": 1, + "rl": 1 + }; + var alignSetting = { + "start": 1, + "middle": 1, + "end": 1, + "left": 1, + "right": 1 + }; + + function findDirectionSetting(value) { + if (typeof value !== "string") { + return false; + } + var dir = directionSetting[value.toLowerCase()]; + return dir ? value.toLowerCase() : false; + } + + function findAlignSetting(value) { + if (typeof value !== "string") { + return false; + } + var align = alignSetting[value.toLowerCase()]; + return align ? value.toLowerCase() : false; + } + + function VTTCue(startTime, endTime, text) { + /** + * Shim implementation specific properties. These properties are not in + * the spec. + */ + + // Lets us know when the VTTCue's data has changed in such a way that we need + // to recompute its display state. This lets us compute its display state + // lazily. + this.hasBeenReset = false; + + /** + * VTTCue and TextTrackCue properties + * http://dev.w3.org/html5/webvtt/#vttcue-interface + */ + + var _id = ""; + var _pauseOnExit = false; + var _startTime = startTime; + var _endTime = endTime; + var _text = text; + var _region = null; + var _vertical = ""; + var _snapToLines = true; + var _line = "auto"; + var _lineAlign = "start"; + var _position = 50; + var _positionAlign = "middle"; + var _size = 50; + var _align = "middle"; + + Object.defineProperties(this, { + "id": { + enumerable: true, + get: function get() { + return _id; + }, + set: function set(value) { + _id = "" + value; + } + }, + + "pauseOnExit": { + enumerable: true, + get: function get() { + return _pauseOnExit; + }, + set: function set(value) { + _pauseOnExit = !!value; + } + }, + + "startTime": { + enumerable: true, + get: function get() { + return _startTime; + }, + set: function set(value) { + if (typeof value !== "number") { + throw new TypeError("Start time must be set to a number."); + } + _startTime = value; + this.hasBeenReset = true; + } + }, + + "endTime": { + enumerable: true, + get: function get() { + return _endTime; + }, + set: function set(value) { + if (typeof value !== "number") { + throw new TypeError("End time must be set to a number."); + } + _endTime = value; + this.hasBeenReset = true; + } + }, + + "text": { + enumerable: true, + get: function get() { + return _text; + }, + set: function set(value) { + _text = "" + value; + this.hasBeenReset = true; + } + }, + + "region": { + enumerable: true, + get: function get() { + return _region; + }, + set: function set(value) { + _region = value; + this.hasBeenReset = true; + } + }, + + "vertical": { + enumerable: true, + get: function get() { + return _vertical; + }, + set: function set(value) { + 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."); + } + _vertical = setting; + this.hasBeenReset = true; + } + }, + + "snapToLines": { + enumerable: true, + get: function get() { + return _snapToLines; + }, + set: function set(value) { + _snapToLines = !!value; + this.hasBeenReset = true; + } + }, + + "line": { + enumerable: true, + get: function get() { + return _line; + }, + set: function set(value) { + if (typeof value !== "number" && value !== autoKeyword) { + throw new SyntaxError("An invalid number or illegal string was specified."); + } + _line = value; + this.hasBeenReset = true; + } + }, + + "lineAlign": { + enumerable: true, + get: function get() { + return _lineAlign; + }, + set: function set(value) { + var setting = findAlignSetting(value); + if (!setting) { + throw new SyntaxError("An invalid or illegal string was specified."); + } + _lineAlign = setting; + this.hasBeenReset = true; + } + }, + + "position": { + enumerable: true, + get: function get() { + return _position; + }, + set: function set(value) { + if (value < 0 || value > 100) { + throw new Error("Position must be between 0 and 100."); + } + _position = value; + this.hasBeenReset = true; + } + }, + + "positionAlign": { + enumerable: true, + get: function get() { + return _positionAlign; + }, + set: function set(value) { + var setting = findAlignSetting(value); + if (!setting) { + throw new SyntaxError("An invalid or illegal string was specified."); + } + _positionAlign = setting; + this.hasBeenReset = true; + } + }, + + "size": { + enumerable: true, + get: function get() { + return _size; + }, + set: function set(value) { + if (value < 0 || value > 100) { + throw new Error("Size must be between 0 and 100."); + } + _size = value; + this.hasBeenReset = true; + } + }, + + "align": { + enumerable: true, + get: function get() { + return _align; + }, + set: function set(value) { + var setting = findAlignSetting(value); + if (!setting) { + throw new SyntaxError("An invalid or illegal string was specified."); + } + _align = setting; + this.hasBeenReset = true; + } + } + }); + + /** + * Other spec defined properties + */ + + // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state + this.displayState = undefined; + } + + /** + * VTTCue methods + */ + + VTTCue.prototype.getCueAsHTML = function () { + // Assume WebVTT.convertCueToDOMTree is on the global. + return WebVTT.convertCueToDOMTree(window, this.text); + }; + + var vttcue = VTTCue; + + var vttcue$1 = /*#__PURE__*/Object.freeze({ + default: vttcue, + __moduleExports: vttcue + }); + + /** + * Copyright 2013 vtt.js Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var scrollSetting = { + "": true, + "up": true + }; + + function findScrollSetting(value) { + if (typeof value !== "string") { + return false; + } + var scroll = scrollSetting[value.toLowerCase()]; + return scroll ? value.toLowerCase() : false; + } + + function isValidPercentValue(value) { + return typeof value === "number" && value >= 0 && value <= 100; + } + + // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface + function VTTRegion() { + var _width = 100; + var _lines = 3; + var _regionAnchorX = 0; + var _regionAnchorY = 100; + var _viewportAnchorX = 0; + var _viewportAnchorY = 100; + var _scroll = ""; + + Object.defineProperties(this, { + "width": { + enumerable: true, + get: function get() { + return _width; + }, + set: function set(value) { + if (!isValidPercentValue(value)) { + throw new Error("Width must be between 0 and 100."); + } + _width = value; + } + }, + "lines": { + enumerable: true, + get: function get() { + return _lines; + }, + set: function set(value) { + if (typeof value !== "number") { + throw new TypeError("Lines must be set to a number."); + } + _lines = value; + } + }, + "regionAnchorY": { + enumerable: true, + get: function get() { + return _regionAnchorY; + }, + set: function set(value) { + if (!isValidPercentValue(value)) { + throw new Error("RegionAnchorX must be between 0 and 100."); + } + _regionAnchorY = value; + } + }, + "regionAnchorX": { + enumerable: true, + get: function get() { + return _regionAnchorX; + }, + set: function set(value) { + if (!isValidPercentValue(value)) { + throw new Error("RegionAnchorY must be between 0 and 100."); + } + _regionAnchorX = value; + } + }, + "viewportAnchorY": { + enumerable: true, + get: function get() { + return _viewportAnchorY; + }, + set: function set(value) { + if (!isValidPercentValue(value)) { + throw new Error("ViewportAnchorY must be between 0 and 100."); + } + _viewportAnchorY = value; + } + }, + "viewportAnchorX": { + enumerable: true, + get: function get() { + return _viewportAnchorX; + }, + set: function set(value) { + if (!isValidPercentValue(value)) { + throw new Error("ViewportAnchorX must be between 0 and 100."); + } + _viewportAnchorX = value; + } + }, + "scroll": { + enumerable: true, + get: function get() { + return _scroll; + }, + set: function set(value) { + 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."); + } + _scroll = setting; + } + } + }); + } + + var vttregion = VTTRegion; + + var vttregion$1 = /*#__PURE__*/Object.freeze({ + default: vttregion, + __moduleExports: vttregion + }); + + var require$$0 = ( vtt$1 && vtt ) || vtt$1; + + var require$$1 = ( vttcue$1 && vttcue ) || vttcue$1; + + var require$$2 = ( vttregion$1 && vttregion ) || vttregion$1; + + var browserIndex = createCommonjsModule(function (module) { + /** + * Copyright 2013 vtt.js Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + // Default exports for Node. Export the extended versions of VTTCue and + // VTTRegion in Node since we likely want the capability to convert back and + // forth between JSON. If we don't then it's not that big of a deal since we're + // off browser. + + + var vttjs = module.exports = { + WebVTT: require$$0, + VTTCue: require$$1, + VTTRegion: require$$2 + }; + + window_1.vttjs = vttjs; + window_1.WebVTT = vttjs.WebVTT; + + var cueShim = vttjs.VTTCue; + var regionShim = vttjs.VTTRegion; + var nativeVTTCue = window_1.VTTCue; + var nativeVTTRegion = window_1.VTTRegion; + + vttjs.shim = function () { + window_1.VTTCue = cueShim; + window_1.VTTRegion = regionShim; + }; + + vttjs.restore = function () { + window_1.VTTCue = nativeVTTCue; + window_1.VTTRegion = nativeVTTRegion; + }; + + if (!window_1.VTTCue) { + vttjs.shim(); + } + }); + var browserIndex_1 = browserIndex.WebVTT; + var browserIndex_2 = browserIndex.VTTCue; + var browserIndex_3 = browserIndex.VTTRegion; + + /** + * @file tech.js + */ + + /** + * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string + * that just contains the src url alone. + * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};` + * `var SourceString = 'http://example.com/some-video.mp4';` + * + * @typedef {Object|string} Tech~SourceObject + * + * @property {string} src + * The url to the source + * + * @property {string} type + * The mime type of the source + */ + + /** + * A function used by {@link Tech} to create a new {@link TextTrack}. + * + * @private + * + * @param {Tech} self + * An instance of the Tech class. + * + * @param {string} kind + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) + * + * @param {string} [label] + * Label to identify the text track + * + * @param {string} [language] + * Two letter language abbreviation + * + * @param {Object} [options={}] + * An object with additional text track options + * + * @return {TextTrack} + * The text track that was created. + */ + function createTrackHelper(self, kind, label, language) { + var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; + + var tracks = self.textTracks(); + + options.kind = kind; + + if (label) { + options.label = label; + } + if (language) { + options.language = language; + } + options.tech = self; + + var track = new ALL.text.TrackClass(options); + + tracks.addTrack(track); + + return track; + } + + /** + * This is the base class for media playback technology controllers, such as + * {@link Flash} and {@link HTML5} + * + * @extends Component + */ + + var Tech = function (_Component) { + inherits(Tech, _Component); + + /** + * Create an instance of this Tech. + * + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Component~ReadyCallback} ready + * Callback function to call when the `HTML5` Tech is ready. + */ + function Tech() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var ready = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; + classCallCheck(this, Tech); + + // we don't want the tech to report user activity automatically. + // This is done manually in addControlsListeners + options.reportTouchActivity = false; + + // keep track of whether the current source has played at all to + // implement a very limited played() + var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready)); + + _this.hasStarted_ = false; + _this.on('playing', function () { + this.hasStarted_ = true; + }); + _this.on('loadstart', function () { + this.hasStarted_ = false; + }); + + ALL.names.forEach(function (name) { + var props = ALL[name]; + + if (options && options[props.getterName]) { + _this[props.privateName] = options[props.getterName]; + } + }); + + // Manually track progress in cases where the browser/flash player doesn't report it. + if (!_this.featuresProgressEvents) { + _this.manualProgressOn(); + } + + // Manually track timeupdates in cases where the browser/flash player doesn't report it. + if (!_this.featuresTimeupdateEvents) { + _this.manualTimeUpdatesOn(); + } + + ['Text', 'Audio', 'Video'].forEach(function (track) { + if (options['native' + track + 'Tracks'] === false) { + _this['featuresNative' + track + 'Tracks'] = false; + } + }); + + if (options.nativeCaptions === false || options.nativeTextTracks === false) { + _this.featuresNativeTextTracks = false; + } else if (options.nativeCaptions === true || options.nativeTextTracks === true) { + _this.featuresNativeTextTracks = true; + } + + if (!_this.featuresNativeTextTracks) { + _this.emulateTextTracks(); + } + + _this.autoRemoteTextTracks_ = new ALL.text.ListClass(); + + _this.initTrackListeners(); + + // Turn on component tap events only if not using native controls + if (!options.nativeControlsForTouch) { + _this.emitTapEvents(); + } + + if (_this.constructor) { + _this.name_ = _this.constructor.name || 'Unknown Tech'; + } + return _this; + } + + /** + * A special function to trigger source set in a way that will allow player + * to re-trigger if the player or tech are not ready yet. + * + * @fires Tech#sourceset + * @param {string} src The source string at the time of the source changing. + */ + + + Tech.prototype.triggerSourceset = function triggerSourceset(src) { + var _this2 = this; + + if (!this.isReady_) { + // on initial ready we have to trigger source set + // 1ms after ready so that player can watch for it. + this.one('ready', function () { + return _this2.setTimeout(function () { + return _this2.triggerSourceset(src); + }, 1); + }); + } + + /** + * Fired when the source is set on the tech causing the media element + * to reload. + * + * @see {@link Player#event:sourceset} + * @event Tech#sourceset + * @type {EventTarget~Event} + */ + this.trigger({ + src: src, + type: 'sourceset' + }); + }; + + /* Fallbacks for unsupported event types + ================================================================================ */ + + /** + * Polyfill the `progress` event for browsers that don't support it natively. + * + * @see {@link Tech#trackProgress} + */ + + + Tech.prototype.manualProgressOn = function manualProgressOn() { + this.on('durationchange', this.onDurationChange); + + this.manualProgress = true; + + // Trigger progress watching when a source begins loading + this.one('ready', this.trackProgress); + }; + + /** + * Turn off the polyfill for `progress` events that was created in + * {@link Tech#manualProgressOn} + */ + + + Tech.prototype.manualProgressOff = function manualProgressOff() { + this.manualProgress = false; + this.stopTrackingProgress(); + + this.off('durationchange', this.onDurationChange); + }; + + /** + * This is used to trigger a `progress` event when the buffered percent changes. It + * sets an interval function that will be called every 500 milliseconds to check if the + * buffer end percent has changed. + * + * > This function is called by {@link Tech#manualProgressOn} + * + * @param {EventTarget~Event} event + * The `ready` event that caused this to run. + * + * @listens Tech#ready + * @fires Tech#progress + */ + + + Tech.prototype.trackProgress = function trackProgress(event) { + this.stopTrackingProgress(); + this.progressInterval = this.setInterval(bind(this, function () { + // Don't trigger unless buffered amount is greater than last time + + var numBufferedPercent = this.bufferedPercent(); + + if (this.bufferedPercent_ !== numBufferedPercent) { + /** + * See {@link Player#progress} + * + * @event Tech#progress + * @type {EventTarget~Event} + */ + this.trigger('progress'); + } + + this.bufferedPercent_ = numBufferedPercent; + + if (numBufferedPercent === 1) { + this.stopTrackingProgress(); + } + }), 500); + }; + + /** + * Update our internal duration on a `durationchange` event by calling + * {@link Tech#duration}. + * + * @param {EventTarget~Event} event + * The `durationchange` event that caused this to run. + * + * @listens Tech#durationchange + */ + + + Tech.prototype.onDurationChange = function onDurationChange(event) { + this.duration_ = this.duration(); + }; + + /** + * Get and create a `TimeRange` object for buffering. + * + * @return {TimeRange} + * The time range object that was created. + */ + + + Tech.prototype.buffered = function buffered() { + return createTimeRanges(0, 0); + }; + + /** + * Get the percentage of the current video that is currently buffered. + * + * @return {number} + * A number from 0 to 1 that represents the decimal percentage of the + * video that is buffered. + * + */ + + + Tech.prototype.bufferedPercent = function bufferedPercent$$1() { + return bufferedPercent(this.buffered(), this.duration_); + }; + + /** + * Turn off the polyfill for `progress` events that was created in + * {@link Tech#manualProgressOn} + * Stop manually tracking progress events by clearing the interval that was set in + * {@link Tech#trackProgress}. + */ + + + Tech.prototype.stopTrackingProgress = function stopTrackingProgress() { + this.clearInterval(this.progressInterval); + }; + + /** + * Polyfill the `timeupdate` event for browsers that don't support it. + * + * @see {@link Tech#trackCurrentTime} + */ + + + Tech.prototype.manualTimeUpdatesOn = function manualTimeUpdatesOn() { + this.manualTimeUpdates = true; + + this.on('play', this.trackCurrentTime); + this.on('pause', this.stopTrackingCurrentTime); + }; + + /** + * Turn off the polyfill for `timeupdate` events that was created in + * {@link Tech#manualTimeUpdatesOn} + */ + + + Tech.prototype.manualTimeUpdatesOff = function manualTimeUpdatesOff() { + this.manualTimeUpdates = false; + this.stopTrackingCurrentTime(); + this.off('play', this.trackCurrentTime); + this.off('pause', this.stopTrackingCurrentTime); + }; + + /** + * Sets up an interval function to track current time and trigger `timeupdate` every + * 250 milliseconds. + * + * @listens Tech#play + * @triggers Tech#timeupdate + */ + + + Tech.prototype.trackCurrentTime = function trackCurrentTime() { + if (this.currentTimeInterval) { + this.stopTrackingCurrentTime(); + } + this.currentTimeInterval = this.setInterval(function () { + /** + * Triggered at an interval of 250ms to indicated that time is passing in the video. + * + * @event Tech#timeupdate + * @type {EventTarget~Event} + */ + this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); + + // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 + }, 250); + }; + + /** + * Stop the interval function created in {@link Tech#trackCurrentTime} so that the + * `timeupdate` event is no longer triggered. + * + * @listens {Tech#pause} + */ + + + Tech.prototype.stopTrackingCurrentTime = function stopTrackingCurrentTime() { + this.clearInterval(this.currentTimeInterval); + + // #1002 - if the video ends right before the next timeupdate would happen, + // the progress bar won't make it all the way to the end + this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); + }; + + /** + * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList}, + * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. + * + * @fires Component#dispose + */ + + + Tech.prototype.dispose = function dispose() { + + // clear out all tracks because we can't reuse them between techs + this.clearTracks(NORMAL.names); + + // Turn off any manual progress or timeupdate tracking + if (this.manualProgress) { + this.manualProgressOff(); + } + + if (this.manualTimeUpdates) { + this.manualTimeUpdatesOff(); + } + + _Component.prototype.dispose.call(this); + }; + + /** + * Clear out a single `TrackList` or an array of `TrackLists` given their names. + * + * > Note: Techs without source handlers should call this between sources for `video` + * & `audio` tracks. You don't want to use them between tracks! + * + * @param {string[]|string} types + * TrackList names to clear, valid names are `video`, `audio`, and + * `text`. + */ + + + Tech.prototype.clearTracks = function clearTracks(types) { + var _this3 = this; + + types = [].concat(types); + // clear out all tracks because we can't reuse them between techs + types.forEach(function (type) { + var list = _this3[type + 'Tracks']() || []; + var i = list.length; + + while (i--) { + var track = list[i]; + + if (type === 'text') { + _this3.removeRemoteTextTrack(track); + } + list.removeTrack(track); + } + }); + }; + + /** + * Remove any TextTracks added via addRemoteTextTrack that are + * flagged for automatic garbage collection + */ + + + Tech.prototype.cleanupAutoTextTracks = function cleanupAutoTextTracks() { + var list = this.autoRemoteTextTracks_ || []; + var i = list.length; + + while (i--) { + var track = list[i]; + + this.removeRemoteTextTrack(track); + } + }; + + /** + * Reset the tech, which will removes all sources and reset the internal readyState. + * + * @abstract + */ + + + Tech.prototype.reset = function reset() {}; + + /** + * Get or set an error on the Tech. + * + * @param {MediaError} [err] + * Error to set on the Tech + * + * @return {MediaError|null} + * The current error object on the tech, or null if there isn't one. + */ + + + Tech.prototype.error = function error(err) { + if (err !== undefined) { + this.error_ = new MediaError(err); + this.trigger('error'); + } + return this.error_; + }; + + /** + * Returns the `TimeRange`s that have been played through for the current source. + * + * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. + * It only checks whether the source has played at all or not. + * + * @return {TimeRange} + * - A single time range if this video has played + * - An empty set of ranges if not. + */ + + + Tech.prototype.played = function played() { + if (this.hasStarted_) { + return createTimeRanges(0, 0); + } + return createTimeRanges(); + }; + + /** + * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was + * previously called. + * + * @fires Tech#timeupdate + */ + + + Tech.prototype.setCurrentTime = function setCurrentTime() { + // improve the accuracy of manual timeupdates + if (this.manualTimeUpdates) { + /** + * A manual `timeupdate` event. + * + * @event Tech#timeupdate + * @type {EventTarget~Event} + */ + this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); + } + }; + + /** + * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and + * {@link TextTrackList} events. + * + * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. + * + * @fires Tech#audiotrackchange + * @fires Tech#videotrackchange + * @fires Tech#texttrackchange + */ + + + Tech.prototype.initTrackListeners = function initTrackListeners() { + var _this4 = this; + + /** + * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} + * + * @event Tech#audiotrackchange + * @type {EventTarget~Event} + */ + + /** + * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} + * + * @event Tech#videotrackchange + * @type {EventTarget~Event} + */ + + /** + * Triggered when tracks are added or removed on the Tech {@link TextTrackList} + * + * @event Tech#texttrackchange + * @type {EventTarget~Event} + */ + NORMAL.names.forEach(function (name) { + var props = NORMAL[name]; + var trackListChanges = function trackListChanges() { + _this4.trigger(name + 'trackchange'); + }; + + var tracks = _this4[props.getterName](); + + tracks.addEventListener('removetrack', trackListChanges); + tracks.addEventListener('addtrack', trackListChanges); + + _this4.on('dispose', function () { + tracks.removeEventListener('removetrack', trackListChanges); + tracks.removeEventListener('addtrack', trackListChanges); + }); + }); + }; + + /** + * Emulate TextTracks using vtt.js if necessary + * + * @fires Tech#vttjsloaded + * @fires Tech#vttjserror + */ + + + Tech.prototype.addWebVttScript_ = function addWebVttScript_() { + var _this5 = this; + + if (window_1.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 + // before inserting the WebVTT script + if (document_1.body.contains(this.el())) { + + // load via require if available and vtt.js script location was not passed in + // as an option. novtt builds will turn the above require call into an empty object + // which will cause this if check to always fail. + if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) { + this.trigger('vttjsloaded'); + return; + } + + // load vtt.js via the script location option or the cdn of no location was + // passed in + var script = document_1.createElement('script'); + + script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js'; + script.onload = function () { + /** + * Fired when vtt.js is loaded. + * + * @event Tech#vttjsloaded + * @type {EventTarget~Event} + */ + _this5.trigger('vttjsloaded'); + }; + script.onerror = function () { + /** + * Fired when vtt.js was not loaded due to an error + * + * @event Tech#vttjsloaded + * @type {EventTarget~Event} + */ + _this5.trigger('vttjserror'); + }; + this.on('dispose', function () { + script.onload = null; + script.onerror = null; + }); + // 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; + this.el().parentNode.appendChild(script); + } else { + this.ready(this.addWebVttScript_); + } + }; + + /** + * Emulate texttracks + * + */ + + + Tech.prototype.emulateTextTracks = function emulateTextTracks() { + var _this6 = this; + + var tracks = this.textTracks(); + var remoteTracks = this.remoteTextTracks(); + var handleAddTrack = function handleAddTrack(e) { + return tracks.addTrack(e.track); + }; + var handleRemoveTrack = function handleRemoveTrack(e) { + return tracks.removeTrack(e.track); + }; + + remoteTracks.on('addtrack', handleAddTrack); + remoteTracks.on('removetrack', handleRemoveTrack); + + this.addWebVttScript_(); + + var updateDisplay = function updateDisplay() { + return _this6.trigger('texttrackchange'); + }; + + var textTracksChanges = function textTracksChanges() { + updateDisplay(); + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + + track.removeEventListener('cuechange', updateDisplay); + if (track.mode === 'showing') { + track.addEventListener('cuechange', updateDisplay); + } + } + }; + + textTracksChanges(); + tracks.addEventListener('change', textTracksChanges); + tracks.addEventListener('addtrack', textTracksChanges); + tracks.addEventListener('removetrack', textTracksChanges); + + this.on('dispose', function () { + remoteTracks.off('addtrack', handleAddTrack); + remoteTracks.off('removetrack', handleRemoveTrack); + tracks.removeEventListener('change', textTracksChanges); + tracks.removeEventListener('addtrack', textTracksChanges); + tracks.removeEventListener('removetrack', textTracksChanges); + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + + track.removeEventListener('cuechange', updateDisplay); + } + }); + }; + + /** + * Create and returns a remote {@link TextTrack} object. + * + * @param {string} kind + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) + * + * @param {string} [label] + * Label to identify the text track + * + * @param {string} [language] + * Two letter language abbreviation + * + * @return {TextTrack} + * The TextTrack that gets created. + */ + + + Tech.prototype.addTextTrack = function addTextTrack(kind, label, language) { + if (!kind) { + throw new Error('TextTrack kind is required but was not provided'); + } + + return createTrackHelper(this, kind, label, language); + }; + + /** + * Create an emulated TextTrack for use by addRemoteTextTrack + * + * This is intended to be overridden by classes that inherit from + * Tech in order to create native or custom TextTracks. + * + * @param {Object} options + * The object should contain the options to initialize the TextTrack with. + * + * @param {string} [options.kind] + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). + * + * @param {string} [options.label]. + * Label to identify the text track + * + * @param {string} [options.language] + * Two letter language abbreviation. + * + * @return {HTMLTrackElement} + * The track element that gets created. + */ + + + Tech.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) { + var track = mergeOptions(options, { + tech: this + }); + + return new REMOTE.remoteTextEl.TrackClass(track); + }; + + /** + * Creates a remote text track object and returns an html track element. + * + * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. + * + * @param {Object} options + * See {@link Tech#createRemoteTextTrack} for more detailed properties. + * + * @param {boolean} [manualCleanup=true] + * - When false: the TextTrack will be automatically removed from the video + * element whenever the source changes + * - When True: The TextTrack will have to be cleaned up manually + * + * @return {HTMLTrackElement} + * An Html Track Element. + * + * @deprecated The default functionality for this function will be equivalent + * to "manualCleanup=false" in the future. The manualCleanup parameter will + * also be removed. + */ + + + Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack() { + var _this7 = this; + + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var manualCleanup = arguments[1]; + + var htmlTrackElement = this.createRemoteTextTrack(options); + + if (manualCleanup !== true && manualCleanup !== false) { + // deprecation warning + log$1.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js'); + manualCleanup = true; + } + + // store HTMLTrackElement and TextTrack to remote list + this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); + this.remoteTextTracks().addTrack(htmlTrackElement.track); + + if (manualCleanup !== true) { + // create the TextTrackList if it doesn't exist + this.ready(function () { + return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track); + }); + } + + return htmlTrackElement; + }; + + /** + * Remove a remote text track from the remote `TextTrackList`. + * + * @param {TextTrack} track + * `TextTrack` to remove from the `TextTrackList` + */ + + + Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) { + var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); + + // remove HTMLTrackElement and TextTrack from remote list + this.remoteTextTrackEls().removeTrackElement_(trackElement); + this.remoteTextTracks().removeTrack(track); + this.autoRemoteTextTracks_.removeTrack(track); + }; + + /** + * Gets available media playback quality metrics as specified by the W3C's Media + * Playback Quality API. + * + * @see [Spec]{@link https://wicg.github.io/media-playback-quality} + * + * @return {Object} + * An object with supported media playback quality metrics + * + * @abstract + */ + + + Tech.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { + return {}; + }; + + /** + * A method to set a poster from a `Tech`. + * + * @abstract + */ + + + Tech.prototype.setPoster = function setPoster() {}; + + /** + * A method to check for the presence of the 'playsinline'