diff --git a/admin/__pycache__/admin.cpython-37.pyc b/admin/__pycache__/admin.cpython-37.pyc new file mode 100644 index 0000000..987a546 Binary files /dev/null and b/admin/__pycache__/admin.cpython-37.pyc differ diff --git a/admin/admin.py b/admin/admin.py new file mode 100644 index 0000000..5418962 --- /dev/null +++ b/admin/admin.py @@ -0,0 +1,15 @@ +from flask import Blueprint, flash, redirect, url_for, render_template +from flask_login import login_required, current_user +from sqlite_web import sqlite_web + +Admin = Blueprint("admin", __name__, template_folder="templates") +login_required(sqlite_web.index) + + +@Admin.route("/admin") +@login_required +def index(): + if not current_user.is_admin: + flash("you must be an admin to access this page, login with an admin account.") + return redirect(url_for("login")) + return render_template("admin/index.html", title="Admin") diff --git a/admin/templates/admin/index.html b/admin/templates/admin/index.html new file mode 100644 index 0000000..7ca22ee --- /dev/null +++ b/admin/templates/admin/index.html @@ -0,0 +1,5 @@ +{% extends "base.html" %} + +{% block content %} +{# #} +{% endblock %} diff --git a/app.py b/app.py index bba8d60..91d00ae 100644 --- a/app.py +++ b/app.py @@ -1,19 +1,33 @@ from flask import Flask -from flask import render_template, request, g, redirect, url_for, make_response, flash, session +from flask import render_template, request, g, redirect, url_for, flash from flask_login import LoginManager, current_user, login_user, logout_user, login_required from flask_log import Logging -from werkzeug.useragents import UserAgent -from urllib import parse -from io import BytesIO -from wand.image import Image - -import threading, os, datetime, pytz +import threading +import logging +import inotify.adapters, inotify.constants import scripts.func as func from scripts import database +from admin import admin +from movies import movies +from comics import comics + + +class NullHandler(logging.Handler): + def emit(self, record=None): + pass + + def debug(self, *arg): + pass +nullLog = NullHandler() +inotify.adapters._LOGGER = nullLog + app = Flask(__name__) +app.register_blueprint(comics.Comics) +app.register_blueprint(admin.Admin) +app.register_blueprint(movies.Movies) app.config["SECRET_KEY"] = "***REMOVED***" app.config["FLASK_LOG_LEVEL"] = "DEBUG" flask_log = Logging(app) @@ -28,20 +42,33 @@ MOBILE_DEVICES = ["android", "blackberry", "ipad", "iphone"] def get_comics(): with app.app_context(): func.get_comics() - - -def verify_paths(): - with app.app_context(): database.verify_paths() + i = inotify.adapters.InotifyTree(func.COMICS_DIRECTORY) + for event in i.event_gen(yield_nones=False): + (header, type_names, path, filename) = event + file_path = path+"/"+filename + if "IN_CLOSE_WRITE" in type_names: + func.get_comic(file_path) + if "IN_DELETE" in type_names: + database.verify_path(file_path) + + +def get_movies(): + with app.app_context(): + func.get_movies() + i = inotify.adapters.InotifyTree(func.MOVIES_DIRECTORY) + for event in i.event_gen(yield_nones=False): + (header, type_names, path, filename) = event + if "IN_CLOSE_WRITE" in type_names: + func.get_movies() with app.app_context(): - app.logger.debug("server start") database.initialize_db() thread = threading.Thread(target=get_comics, args=()) thread.daemon = True thread.start() - thread2 = threading.Thread(target=verify_paths, args=()) + thread2 = threading.Thread(target=get_movies, args=()) thread2.daemon = True thread2.start() @@ -60,7 +87,15 @@ def update_comic_db(sender, **kw): print(e) +def update_movie_db(sender, **kw): + try: + database.add_movies(kw["movies"]) + except Exception as e: + print(e) + + func.comic_loaded.connect(update_comic_db) +func.movie_loaded.connect(update_movie_db) @login_manager.user_loader @@ -111,12 +146,6 @@ def home(): return str(e) -@app.route("/movies") -@login_required -def movies(): - return "No Movies" - - @app.route("/music") @login_required def music(): @@ -129,107 +158,5 @@ def games(): return "No Games" -@app.route("/comics") -@login_required -def comics(): - polling = request.args.get("polling") - - if polling: - try: - return render_template("publisherList.html", comics=database.get_publishers()) - except Exception as e: - print(e) - return str(e) - try: - return render_template("publisherView.html", title="Comics", comics=database.get_publishers()) - except Exception as e: - print(e) - return str(e) - - -@app.route("/comics/") -def comic_reroute(): - return redirect(url_for("comics")) - - -@app.route("/comics/") -@login_required -def comics_publisher(publisher): - publisher = parse.unquote(publisher) - series = request.args.get("series") - series_year = request.args.get("seriesYear") - issue = request.args.get("issue") - page_number = request.args.get("pageNumber") - if series: - if issue: - if page_number: - return comic_viewer(publisher, series, series_year, issue) - return comic_gallery(publisher, series, series_year, issue) - return render_template("seriesView.html", title="Comics", comics=database.db_get_comics_in_series(series, publisher, series_year)) - return render_template("publisherSeriesView.html", title="Comics", comics=database.db_get_series_by_publisher(publisher)) - - -def comic_viewer(publisher, series, series_year, issue): - try: - on_mobile = False - if request.user_agent.platform in MOBILE_DEVICES: - on_mobile = True - publisher_parsed = parse.quote(publisher) - series_parsed = parse.quote(series) - page_number = int(request.args.get("pageNumber")) - meta = database.db_get_comic(publisher, series, series_year, issue) - page_count = int(meta["pageCount"]) - - prev_page = page_number - 1 - next_page = page_number + 1 - if next_page >= page_count: - next_page = 0 - if prev_page < 0: - prev_page = page_count - 1 - prev_url = "/comics/{}?series={}&seriesYear={}&issue={}&pageNumber={}".format(publisher_parsed, series_parsed, series_year, issue, prev_page) - next_url = "/comics/{}?series={}&seriesYear={}&issue={}&pageNumber={}".format(publisher_parsed, series_parsed, series_year, issue, next_page) - return render_template("comicView.html", title="Comics", on_mobile=on_mobile, prev_url=prev_url, next_url=next_url, comic=meta, page_number=page_number) - except Exception as e: - print(e) - return str(e) - - -def comic_gallery(publisher, series, series_year, issue): - try: - meta = database.db_get_comic(publisher, series, series_year, issue) - return render_template("comicGallery.html", title="Comics", comic=meta) - except Exception as e: - print(e) - return str(e) - - -@app.route("/comics/get_comic//") -@login_required -def get_comic_page(comic_id, page_number): - meta = database.db_get_comic_by_id(comic_id) - comic = func.open_comic(meta["path"]) - byteImage = BytesIO(comic.getPage(page_number)) - image = Image(file=byteImage) - response = make_response(image.make_blob()) - response.headers["cache-control"] = "public" - date = pytz.utc.localize(datetime.datetime.utcfromtimestamp(os.path.getmtime(meta["path"]))) - response.headers["last-modified"] = date.strftime('%a, %d %b %Y %H:%M:%S %Z') - response.headers["content-type"] = "image/"+image.format - return response - - -@app.route("/comics/get_comic///thumbnail") -@login_required -def get_comic_thumbnail(comic_id, page_number): - meta = database.db_get_comic_by_id(comic_id) - thumb = database.db_get_thumbnail_by_id_page(comic_id, page_number) - response = make_response(thumb["image"]) - response.headers["cache-control"] = "public" - date = pytz.utc.localize(datetime.datetime.utcfromtimestamp(os.path.getmtime(meta["path"]))) - response.headers["last-modified"] = date.strftime('%a, %d %b %Y %H:%M:%S %Z') - response.headers["content-type"] = thumb["type"] - return response - - if __name__ == '__main__': app.run() diff --git a/comics/__pycache__/comics.cpython-37.pyc b/comics/__pycache__/comics.cpython-37.pyc new file mode 100644 index 0000000..e205606 Binary files /dev/null and b/comics/__pycache__/comics.cpython-37.pyc differ diff --git a/comics/comics.py b/comics/comics.py new file mode 100644 index 0000000..c6e8ad6 --- /dev/null +++ b/comics/comics.py @@ -0,0 +1,124 @@ +from flask import Blueprint, render_template, request, make_response +from flask_login import login_required + +from urllib import parse +from io import BytesIO +from wand.image import Image +import os, pytz, datetime + +from scripts import database, func + +Comics = Blueprint("comics", __name__, template_folder="templates") + +MOBILE_DEVICES = ["android", "blackberry", "ipad", "iphone"] + + +@Comics.route("/comics") +@login_required +def index(): + try: + return render_template("comics/index.html", title="Comics", publishers=database.get_publishers()) + except Exception as e: + print(e) + return str(e) + + +@Comics.route("/comics/search") +@login_required +def search(): + try: + query = request.args.get("q") + results = { + "publisher": [], + "series": [], + "comics": [] + } + if query: + results = database.db_search_comics(query) + return render_template("comics/search.html", title="Comics: Search", + publishers=results["publisher"], publisher_series=results["series"], comics=results["comics"]) + except Exception as e: + print(type(e), e) + return str(type(e))+" "+str(e) + + +@Comics.route("/comics/") +@login_required +def comics_publisher(publisher): + publisher = parse.unquote(publisher) + series = request.args.get("series") + series_year = request.args.get("seriesYear") + issue = request.args.get("issue") + page_number = request.args.get("pageNumber") + if series: + if issue: + if page_number: + return comic_viewer(publisher, series, series_year, issue) + return comic_gallery(publisher, series, series_year, issue) + return render_template("comics/seriesView.html", title="Comics", + comics=database.db_get_comics_in_series(series, publisher, series_year)) + return render_template("comics/publisherSeriesView.html", title="Comics", + publisher_series=database.db_get_series_by_publisher(publisher)) + + +def comic_viewer(publisher, series, series_year, issue): + try: + on_mobile = False + if request.user_agent.platform in MOBILE_DEVICES: + on_mobile = True + publisher_parsed = parse.quote(publisher) + series_parsed = parse.quote(series) + page_number = int(request.args.get("pageNumber")) + meta = database.db_get_comic(publisher, series, series_year, issue) + page_count = int(meta["pageCount"]) + + prev_page = page_number - 1 + next_page = page_number + 1 + if next_page >= page_count: + next_page = 0 + if prev_page < 0: + prev_page = page_count - 1 + prev_url = "/comics/{}?series={}&seriesYear={}&issue={}&pageNumber={}".format(publisher_parsed, series_parsed, series_year, issue, prev_page) + next_url = "/comics/{}?series={}&seriesYear={}&issue={}&pageNumber={}".format(publisher_parsed, series_parsed, series_year, issue, next_page) + title = "Comics: "+meta["series"]+": #"+meta["issueText"]+" "+(meta["title"] or "") + return render_template("comics/comicView.html", title=title, on_mobile=on_mobile, prev_url=prev_url, next_url=next_url, comic=meta, page_number=page_number) + except Exception as e: + print(e) + return str(e) + + +def comic_gallery(publisher, series, series_year, issue): + try: + meta = database.db_get_comic(publisher, series, series_year, issue) + return render_template("comics/comicGallery.html", title="Comics", comic=meta) + except Exception as e: + print(e) + return str(e) + + +@Comics.route("/comics/get_comic//") +@login_required +def get_comic_page(comic_id, page_number): + meta = database.db_get_comic_by_id(comic_id) + comic = func.open_comic(meta["path"]) + byteImage = BytesIO(comic.getPage(page_number)) + image = Image(file=byteImage) + response = make_response(image.make_blob()) + response.headers["cache-control"] = "public" + date = pytz.utc.localize(datetime.datetime.utcfromtimestamp(os.path.getmtime(meta["path"]))) + response.headers["last-modified"] = date.strftime('%a, %d %b %Y %H:%M:%S %Z') + response.headers["content-type"] = "image/" + image.format + return response + + +@Comics.route("/comics/get_comic///thumbnail") +@login_required +def get_comic_thumbnail(comic_id, page_number): + meta = database.db_get_comic_by_id(comic_id) + thumb = database.db_get_thumbnail_by_id_page(comic_id, page_number) + response = make_response(thumb["image"]) + response.headers["cache-control"] = "public" + date = pytz.utc.localize(datetime.datetime.utcfromtimestamp(os.path.getmtime(meta["path"]))) + response.headers["last-modified"] = date.strftime('%a, %d %b %Y %H:%M:%S %Z') + response.headers["content-type"] = thumb["type"] + return response diff --git a/templates/PublisherSeriesList.html b/comics/templates/comics/PublisherSeriesList.html similarity index 92% rename from templates/PublisherSeriesList.html rename to comics/templates/comics/PublisherSeriesList.html index 982f524..6dcc683 100644 --- a/templates/PublisherSeriesList.html +++ b/comics/templates/comics/PublisherSeriesList.html @@ -1,4 +1,4 @@ -{% for comic in comics %} +{% for comic in publisher_series %}
diff --git a/templates/comicGallery.html b/comics/templates/comics/comicGallery.html similarity index 100% rename from templates/comicGallery.html rename to comics/templates/comics/comicGallery.html diff --git a/templates/comicView.html b/comics/templates/comics/comicView.html similarity index 100% rename from templates/comicView.html rename to comics/templates/comics/comicView.html diff --git a/comics/templates/comics/index.html b/comics/templates/comics/index.html new file mode 100644 index 0000000..d78aa53 --- /dev/null +++ b/comics/templates/comics/index.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% block nav %} + Search +{% endblock %} + +{% block content %} +
+
+ {% include "comics/publisherList.html" %} +
+
+{% endblock %} diff --git a/templates/publisherList.html b/comics/templates/comics/publisherList.html similarity index 89% rename from templates/publisherList.html rename to comics/templates/comics/publisherList.html index a8a9343..f29891f 100644 --- a/templates/publisherList.html +++ b/comics/templates/comics/publisherList.html @@ -1,4 +1,4 @@ -{% for publisher in comics %} +{% for publisher in publishers %}
diff --git a/templates/publisherSeriesView.html b/comics/templates/comics/publisherSeriesView.html similarity index 73% rename from templates/publisherSeriesView.html rename to comics/templates/comics/publisherSeriesView.html index 2ca95c8..36f7bf3 100644 --- a/templates/publisherSeriesView.html +++ b/comics/templates/comics/publisherSeriesView.html @@ -3,7 +3,7 @@ {% block content %}
- {% include "PublisherSeriesList.html" %} + {% include "comics/PublisherSeriesList.html" %}
{% endblock %} diff --git a/comics/templates/comics/search.html b/comics/templates/comics/search.html new file mode 100644 index 0000000..bcd0e0e --- /dev/null +++ b/comics/templates/comics/search.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block nav %} + +{% endblock %} + +{% block content %} +
+ {% if publishers != [] %} +

Publishers

+
+ {% include "comics/publisherList.html" %} +
+ {% endif %} + {% if publisher_series != [] %} +

Series

+
+ {% include "comics/PublisherSeriesList.html" %} +
+ {% endif %} + {% if comics != [] %} +

Comics

+
+ {% include "comics/seriesList.html" %} +
+ {% endif %} +
+{% endblock %} diff --git a/templates/seriesList.html b/comics/templates/comics/seriesList.html similarity index 100% rename from templates/seriesList.html rename to comics/templates/comics/seriesList.html diff --git a/templates/seriesView.html b/comics/templates/comics/seriesView.html similarity index 76% rename from templates/seriesView.html rename to comics/templates/comics/seriesView.html index 5948e5a..4afd431 100644 --- a/templates/seriesView.html +++ b/comics/templates/comics/seriesView.html @@ -3,7 +3,7 @@ {% block content %}
- {% include "seriesList.html" %} + {% include "comics/seriesList.html" %}
{% endblock %} diff --git a/database.db b/database.db deleted file mode 100644 index e69de29..0000000 diff --git a/movies/__pycache__/movies.cpython-37.pyc b/movies/__pycache__/movies.cpython-37.pyc new file mode 100644 index 0000000..e01ede9 Binary files /dev/null and b/movies/__pycache__/movies.cpython-37.pyc differ diff --git a/movies/movies.py b/movies/movies.py new file mode 100644 index 0000000..7f70e18 --- /dev/null +++ b/movies/movies.py @@ -0,0 +1,80 @@ +from flask import Blueprint, render_template, request, make_response, send_file, send_from_directory +from flask_login import login_required + +from urllib import parse +from io import BytesIO +from wand.image import Image +import os, pytz, datetime + +from scripts import database, func + +Movies = Blueprint("movies", __name__, template_folder="templates") + + +@Movies.route("/movies") +@login_required +def index(): + return render_template("movies/index.html", title="Movies", movies=database.db_get_all_movies()) + + +@Movies.route("/movies/") +@login_required +def movie_view(imdb_id): + try: + movie_data = database.db_get_movie_by_imdb_id(imdb_id) + return render_template("movies/movieViewer.html", title="Movies: " + movie_data["title"], movie=movie_data) + except Exception as e: + print(e) + return str(e) + + +@Movies.route("/movies//extended") +@login_required +def movie_view_extended(imdb_id): + try: + movie_data = database.db_get_movie_by_imdb_id(imdb_id, extended=1) + return render_template("movies/movieViewer.html", title="Movies: " + movie_data["title"], movie=movie_data) + except Exception as e: + print(e) + return str(e) + + +@Movies.route("/movies//directors_cut") +@login_required +def movie_view_directors_cut(imdb_id): + try: + movie_data = database.db_get_movie_by_imdb_id(imdb_id, directors_cut=1) + return render_template("movies/movieViewer.html", title="Movies: " + movie_data["title"], movie=movie_data) + except Exception as e: + print(e) + return str(e) + + +@Movies.route("/movies/get_movie/") +@login_required +def get_movie(imdb_id): + movie_data = database.db_get_movie_by_imdb_id(imdb_id) + filename = movie_data["title"]+" ("+str(movie_data["year"])+").mkv" + response = make_response(send_from_directory(func.MOVIES_DIRECTORY, filename)) + response.headers["content-type"] = "video/webm" + return response + + +@Movies.route("/movies/get_movie//extended") +@login_required +def get_movie_extended(imdb_id): + movie_data = database.db_get_movie_by_imdb_id(imdb_id, extended=1) + filename = movie_data["title"]+" ("+str(movie_data["year"])+").mkv" + response = make_response(send_from_directory(func.MOVIES_DIRECTORY, filename)) + response.headers["content-type"] = "video/webm" + return response + + +@Movies.route("/movies/get_movie//directors_cut") +@login_required +def get_movie_directors_cut(imdb_id): + movie_data = database.db_get_movie_by_imdb_id(imdb_id, directors_cut=1) + filename = movie_data["title"]+" ("+str(movie_data["year"])+").mkv" + response = make_response(send_from_directory(func.MOVIES_DIRECTORY, filename)) + response.headers["content-type"] = "video/webm" + return response diff --git a/movies/templates/movies/index.html b/movies/templates/movies/index.html new file mode 100644 index 0000000..491f315 --- /dev/null +++ b/movies/templates/movies/index.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} + +{% block content %} +
+
+ {% include "movies/moviesList.html" %} +
+
+{% endblock %} + +{% block footer %} +
+ +

This product uses the TMDb API but is not endorsed or certified by TMDb.

+
+{% endblock %} diff --git a/movies/templates/movies/movieViewer.html b/movies/templates/movies/movieViewer.html new file mode 100644 index 0000000..b2979db --- /dev/null +++ b/movies/templates/movies/movieViewer.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} + +{% block content %} +
+{% endblock %} diff --git a/movies/templates/movies/moviesList.html b/movies/templates/movies/moviesList.html new file mode 100644 index 0000000..2b8a27d --- /dev/null +++ b/movies/templates/movies/moviesList.html @@ -0,0 +1,12 @@ +{% for movie in movies %} + +{% endfor %} diff --git a/scripts/__pycache__/database.cpython-36.pyc b/scripts/__pycache__/database.cpython-36.pyc new file mode 100644 index 0000000..4ab2bdf Binary files /dev/null and b/scripts/__pycache__/database.cpython-36.pyc differ diff --git a/scripts/__pycache__/database.cpython-37.pyc b/scripts/__pycache__/database.cpython-37.pyc index 55560f3..b6496fe 100644 Binary files a/scripts/__pycache__/database.cpython-37.pyc and b/scripts/__pycache__/database.cpython-37.pyc differ diff --git a/scripts/__pycache__/func.cpython-36.pyc b/scripts/__pycache__/func.cpython-36.pyc new file mode 100644 index 0000000..646d91c Binary files /dev/null and b/scripts/__pycache__/func.cpython-36.pyc differ diff --git a/scripts/__pycache__/func.cpython-37.pyc b/scripts/__pycache__/func.cpython-37.pyc index 4098d6a..5166705 100644 Binary files a/scripts/__pycache__/func.cpython-37.pyc and b/scripts/__pycache__/func.cpython-37.pyc differ diff --git a/scripts/__pycache__/imdb_import.cpython-37.pyc b/scripts/__pycache__/imdb_import.cpython-37.pyc new file mode 100644 index 0000000..a7d714c Binary files /dev/null and b/scripts/__pycache__/imdb_import.cpython-37.pyc differ diff --git a/scripts/__pycache__/tmdb.cpython-37.pyc b/scripts/__pycache__/tmdb.cpython-37.pyc new file mode 100644 index 0000000..5ba3321 Binary files /dev/null and b/scripts/__pycache__/tmdb.cpython-37.pyc differ diff --git a/scripts/database.py b/scripts/database.py index f30898b..56481d4 100644 --- a/scripts/database.py +++ b/scripts/database.py @@ -7,17 +7,31 @@ import os, time from comicapi.issuestring import IssueString -DATABASE = "/var/lib/rpiWebApp/database.db" -DATABASE2 = "C:\\Users\\Matthew\\Documents\\MyPrograms\\Websites\\rpi web interface\\database.db" +from scripts import tmdb + +RPI_DATABASE = "/var/lib/rpiWebApp/database.db" +RPI_IMDB_DATABASE = "/var/lib/rpiWebApp/imdb.db" + +MC_DATABASE = "***REMOVED***" +MC_IMDB_DATABASE = "C:\\Users\\Matthew\\Documents\\MyPrograms\\Websites\\rpi_web_interface\\imdb.db" + +DATABASE = RPI_DATABASE if os.path.exists(RPI_DATABASE) else MC_DATABASE +IMDB_DATABASE = RPI_IMDB_DATABASE if os.path.exists(RPI_IMDB_DATABASE) else MC_IMDB_DATABASE def get_db(): db = getattr(g, '_database', None) if db is None: - try: - db = g._database = sqlite3.connect(DATABASE) - except Exception: - db = g._database = sqlite3.connect(DATABASE2) + db = g._database = sqlite3.connect(DATABASE) + + db.row_factory = sqlite3.Row + return db + + +def get_imdb(): + db = getattr(g, '_imdb_database', None) + if db is None: + db = g._imdb_database = sqlite3.connect(IMDB_DATABASE) db.row_factory = sqlite3.Row return db @@ -80,10 +94,27 @@ def initialize_db(): "passwordHash" VARCHAR(128), "isAdmin" INTEGER NOT NULL DEFAULT 0 )""") + get_db().execute("""CREATE TABLE IF NOT EXISTS "movies" ( + "path" TEXT, + "imdb_id" INTEGER, + "tmdb_id" INTEGER, + "title" TEXT, + "year" INTEGER, + "length" INTEGER, + "description" TEXT, + "extended" INTEGER, + "directors_cut" INTEGER, + "poster_path" TEXT, + "backdrop_path" TEXT +)""") get_db().execute("CREATE INDEX IF NOT EXISTS path_index ON comics(path);") get_db().execute("CREATE INDEX IF NOT EXISTS id_index ON comic_thumbnails(id);") get_db().commit() + get_imdb().execute("CREATE INDEX IF NOT EXISTS original_title_index ON title_basics(originalTitle)") + get_imdb().execute("CREATE INDEX IF NOT EXISTS primary_title_index ON title_basics(primaryTitle)") + get_imdb().commit() + def table_exists(table_name): cursor = get_db().execute("SELECT name FROM sqlite_master WHERE type='table' AND name='{}'".format(table_name)) @@ -93,17 +124,28 @@ def table_exists(table_name): return True +def add_movies(movies): + for movie in movies: + get_db().execute("INSERT INTO movies(path, imdb_id, tmdb_id, title, year, length, description, extended, directors_cut, poster_path, backdrop_path) VALUES(?,?,?,?,?,?,?,?,?,?,?)", + (movie[0], movie[1], movie[2], movie[3], movie[4], movie[5], movie[6], movie[7], movie[8], movie[9], movie[10])) + get_db().commit() + + def add_comics(meta, thumbnails): data = [] for info in meta: issue = IssueString(info[1].issue).asFloat() - data.append((info[0], info[1].tagOrigin, info[1].series, (issue or -1), info[1].issue, info[1].title, info[1].publisher, + data.append([info[0], info[1].tagOrigin, info[1].series, (issue or -1), info[1].issue, info[1].title, info[1].publisher, info[1].month, info[1].year, info[1].day, info[1].seriesYear, info[1].issueCount, info[1].volume, info[1].genre, info[1].language, info[1].comments, info[1].volumeCount, info[1].criticalRating, info[1].country, info[1].alternateSeries, info[1].alternateNumber, info[1].alternateCount, info[1].imprint, info[1].notes, info[1].webLink, info[1].format, info[1].manga, info[1].blackAndWhite, info[1].pageCount, info[1].maturityRating, info[1].storyArc, info[1].seriesGroup, info[1].scanInfo, - info[1].characters, info[1].teams, info[1].locations)) + info[1].characters, info[1].teams, info[1].locations]) + for comic in data: + for i in range(len(comic)): + if comic[i] == "": + comic[i] = None for comic, images in zip(data, thumbnails): get_db().execute("""INSERT INTO comics(path, tagOrigin, series, issue, issueText, title, publisher, month, year, day, seriesYear, issueCount, volume, genre, language, comments, volumeCount, @@ -116,6 +158,9 @@ def add_comics(meta, thumbnails): for index in range(len(images)): get_db().execute("INSERT INTO comic_thumbnails(id, pageNumber, image, type) VALUES (?,?,?,?)", (comic_id, index, images[index][0], images[index][1])) get_db().commit() + print("#"*18) + print("# {} comic{} added #".format(len(meta), "s" if len(meta)>1 else "")) + print("#"*18) def add_comic_thumbnails(thumbnails): @@ -185,7 +230,18 @@ def comic_path_in_db(path): try: result = get_db().execute("SELECT path FROM comics WHERE path=?", [path]) get_db().commit() + if result.fetchone(): + return True + except Exception as e: + print(path) + print(e) + return False + +def movie_path_in_db(path): + try: + result = get_db().execute("SELECT path FROM movies WHERE path=?", [path]) + get_db().commit() if result.fetchone(): return True except Exception as e: @@ -195,14 +251,91 @@ def comic_path_in_db(path): def verify_paths(): - while True: - paths = get_db().execute("SELECT path FROM comics").fetchall() + rows = get_db().execute("SELECT path FROM comics").fetchall() + get_db().commit() + for row in rows: + if not os.path.exists(row["path"]): + get_db().execute("DELETE FROM comic_thumbnails WHERE id IN (SELECT id FROM comics WHERE path=?)", [row["path"]]) + get_db().execute("DELETE FROM comics WHERE path=?", [row["path"]]) + get_db().commit() + + +def verify_path(path): + if not os.path.exists(path): + row = get_db().execute("SELECT path FROM comics WHERE path=?", [path]).fetchone() get_db().commit() - for path in paths: - if not os.path.exists(path[0]): - get_db().execute("DELETE FROM comics WHERE path LIKE ?", [path[0]]) - get_db().commit() - time.sleep(60*60*24*5) + if row: + get_db().execute("DELETE FROM comic_thumbnails WHERE id IN (SELECT id FROM comics WHERE path=?)", [path]) + get_db().execute("DELETE FROM comics WHERE path=?", [path]) + get_db().commit() + + +def imdb_get_movie(title, year): + row = get_imdb().execute("SELECT tconst, runtimeMinutes FROM title_basics WHERE (originalTitle LIKE ? OR primaryTitle LIKE ?) AND (titleType LIKE '%movie' OR titleType='video') AND startYear=?", (title, title, year)).fetchone() + return row + + +def tmdb_get_movie_by_imdb_id(imdb_id): + data = tmdb.get_movie_data(imdb_id) + return data + + +def db_get_all_movies(): + rows = get_db().execute("SELECT * FROM movies ORDER BY title, year;").fetchall() + return rows + + +def db_get_movie_by_imdb_id(imdb_id, extended=0, directors_cut=0): + row = get_db().execute("SELECT * FROM movies WHERE imdb_id=? AND extended=? AND directors_cut=?", [imdb_id, extended, directors_cut]).fetchone() + return row + + +def db_search_table_columns_by_query(query, table, columns, group="", order=""): + results = {} + final_query = "%"+query.replace(" ", "%")+"%" + sqlite_base_statement = "SELECT * FROM "+table+" WHERE {condition} {group} {order}" + if not group == "": + group = "GROUP BY "+group + if not order == "": + order = "ORDER BY "+order + for column in columns: + sqlite_statement = sqlite_base_statement.format(condition=column+" LIKE '"+final_query+"'", group=group, order=order) + results[column] = get_db().execute(sqlite_statement).fetchall() + + # sqlite_condition = "" + # for column in columns: + # sqlite_condition += column+" LIKE '"+final_query+"'"+(" OR " if column != columns[-1] else "") + # sqlite_statement = "SELECT * FROM {table} WHERE {condition}".format(table=table, condition=sqlite_condition) + # rows = get_db().execute(sqlite_statement).fetchall() + return results + + +def db_search_comics(query): + publishers = [] + series = [] + comics = [] + + results = db_search_table_columns_by_query(query, "comics", ["publisher", "title", "series", "year"]) + series_results = db_search_table_columns_by_query(query, "comics", ["publisher", "title", "series", "year"], + group="series, seriesYear", order="issue") + + for row in results["publisher"]: + if row["publisher"] not in publishers: + publishers.append(row["publisher"]) + for row in series_results["series"]: + dict = {"publisher": row["publisher"],"series": row["series"],"seriesYear": row["seriesYear"],"id": row["id"]} + if dict not in series: + series.append(dict) + for row in results["title"]: + dict = {"publisher": row["publisher"],"series": row["series"],"seriesYear": row["seriesYear"],"issue": row["issue"],"id": row["id"],"title": row["title"]} + if dict not in comics: + comics.append(dict) + for row in results["year"]: + dict = {"publisher": row["publisher"],"series": row["series"],"seriesYear": row["seriesYear"],"issue": row["issue"],"id": row["id"],"title": row["title"]} + if dict not in comics: + comics.append(dict) + + return {"publisher": publishers, "series": series, "comics": comics} def get_user(username): @@ -212,18 +345,6 @@ def get_user(username): return None -def verify_user(username, password): - password_hash = get_db().execute("SELECT passwordHash FROM users WHERE username=?", [username]).fetchone() - if password_hash: - valid_password = check_password_hash(password_hash["passwordHash"], password) - if valid_password: - user_data = get_db().execute("SELECT * FROM users WHERE username=?", [username]).fetchone() - user = User(user_data) - return user - else: - return False - - class User(UserMixin): def __init__(self, user): self.username = user["username"] diff --git a/scripts/func.py b/scripts/func.py index 184717c..adfafaf 100644 --- a/scripts/func.py +++ b/scripts/func.py @@ -1,14 +1,15 @@ from comicapi import comicarchive from blinker import Namespace + from io import BytesIO from wand.image import Image - -import os, sys +import os, sys, re from scripts import database rpi_signals = Namespace() comic_loaded = rpi_signals.signal("comic-loaded") +movie_loaded = rpi_signals.signal("movie-loaded") publishers_to_ignore = ["***REMOVED***"] @@ -24,6 +25,7 @@ RPI_MUSIC_DIRECTORY = "/usb/storage/media/Music/" MC_COMICS_DIRECTORY = "C:\\Users\\Matthew\\Documents\\Comics" COMICS_DIRECTORY = RPI_COMICS_DIRECTORY if os.path.exists(RPI_COMICS_DIRECTORY) else MC_COMICS_DIRECTORY +MOVIES_DIRECTORY = RPI_MOVIES_DIRECTORY ############# @@ -45,7 +47,6 @@ def get_comics(): test_path = path.encode("utf8") except Exception as e: print("encoding failed on:", path) - print(e) continue archive = open_comic(path) md = archive.readCIX() @@ -55,21 +56,38 @@ def get_comics(): meta.append((path, md)) thumbnails.append(get_comic_thumbnails(archive)) comics_added += 1 - comics_in_db += 1 i += 1 - if i >= 20: + if i >= 2: comic_loaded.send("anonymous", meta=meta.copy(), thumbnails=thumbnails.copy()) meta.clear() thumbnails.clear() i = 0 - else: - comics_in_db += 1 + comics_in_db += 1 print("total number of comics:", total_comics) print("comics in database:", comics_in_db) print("number of comics added:", comics_added) comic_loaded.send("anonymous", meta=meta, thumbnails=thumbnails) +def get_comic(path): + meta = [] + thumbnails = [] + if not database.comic_path_in_db(path): + try: + test_path = path.encode("utf8") + except Exception as e: + print("encoding failed on:", path) + return + archive = open_comic(path) + md = archive.readCIX() + if md.publisher in publishers_to_ignore: + return + print(path) + meta.append((path, md)) + thumbnails.append(get_comic_thumbnails(archive)) + comic_loaded.send("anonymous", meta=meta, thumbnails=thumbnails) + + def get_comic_thumbnails(comic): thumbnails = [] size = "256x256" @@ -81,11 +99,11 @@ def get_comic_thumbnails(comic): orig_height = image.height orig_width = image.width if (orig_width/orig_height)*new_height <= new_width: - height = int((orig_height/orig_width) * new_width) - width = new_width - else: width = int((orig_width/orig_height) * new_height) height = new_height + else: + height = int((orig_height/orig_width) * new_width) + width = new_width image.thumbnail(width, height) thumbnails.append((image.make_blob(), "image/"+image.format)) return thumbnails @@ -96,31 +114,59 @@ def open_comic(path): return archive -def bytestring_path(path): - """Given a path, which is either a bytes or a unicode, returns a str - path (ensuring that we never deal with Unicode pathnames). - """ - # Pass through bytestrings. - if isinstance(path, bytes): - return path +def get_movies(): + print("start load movies") + pattern = r"(.+)( \(....\))(\(extended\))?(\(Director's Cut\))?(\.mkv)" + movies = [] + total_movies = 0 + movies_in_db = 0 + movies_added = 0 + for root, dirs, files in os.walk(MOVIES_DIRECTORY): + for f in files: + if f.endswith(".mkv"): + path = os.path.join(root, f) + if not database.movie_path_in_db(path): + try: + match = re.fullmatch(pattern, f) + if not match: + continue + print("movie path:", path) + title = f[:-4].replace(match.group(2), "") + print("movie title:", title) + year = int(match.group(2)[2:-1]) + extended = 0 + directors_cut = 0 + if match.group(3): + extended = 1 + imdb_data = database.imdb_get_movie(title.replace(match.group(3), ""), year) + elif match.group(4): + imdb_data = database.imdb_get_movie(title.replace(match.group(4), ""), year) + directors_cut = 1 + else: + imdb_data = database.imdb_get_movie(title, year) + if not imdb_data: + print("could not get imdb data") + continue + imdb_id = imdb_data["tconst"] + length = imdb_data["runtimeMinutes"] - # Try to encode with default encodings, but fall back to UTF8. - try: - return path.encode(_fsencoding()) - except (UnicodeError, LookupError): - return path.encode('utf8') + tmdb_data = database.tmdb_get_movie_by_imdb_id(imdb_id) + if not tmdb_data: + print("could not get tmdb data") + continue + tmdb_id = tmdb_data[0] + description = tmdb_data[1] + poster_path = tmdb_data[2] + backdrop_path = tmdb_data[3] - -def _fsencoding(): - """Get the system's filesystem encoding. On Windows, this is always - UTF-8 (not MBCS). - """ - encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() - if encoding == 'mbcs': - # On Windows, a broken encoding known to Python as "MBCS" is - # used for the filesystem. However, we only use the Unicode API - # for Windows paths, so the encoding is actually immaterial so - # we can avoid dealing with this nastiness. We arbitrarily - # choose UTF-8. - encoding = 'utf8' - return encoding + movies.append((path, imdb_id, tmdb_id, title, year, length, description, extended, directors_cut, poster_path, backdrop_path)) + if len(movies) >= 20: + movie_loaded.send("anonymous", movies=movies.copy()) + movies.clear() + except Exception as e: + print(e) + movie_loaded.send("anonymous", movies=movies) + print("finish load movies") + print("total movies:", total_movies) + print("movies in database:", movies_in_db) + print("movies added:", movies_added) diff --git a/scripts/imdb_import.py b/scripts/imdb_import.py new file mode 100644 index 0000000..91fdfb7 --- /dev/null +++ b/scripts/imdb_import.py @@ -0,0 +1,34 @@ +import sqlite3, subprocess, os + +RPI_IMDB_DATABASE = "/var/lib/rpiWebApp/" +RPI_TSV_DIRECTORY = "/usb/storage/imdb-rename/" +RPI_CSV_DIRECTORY = "/home/matt/" + +MC_IMDB_DATABASE = "C:\\Users\\Matthew\\Documents\\MyPrograms\\Websites\\rpi_web_interface\\" +MC_TSV_DIRECTORY = "C:\\\\Users\\\\Matthew\\\\Documents\\\\IMDB\\\\" +MC_CSV_DIRECTORY = "C:\\\\Users\\\\Matthew\\\\Documents\\\\IMDB\\\\" + + +IMDB_DATABASE = RPI_IMDB_DATABASE if os.path.exists(RPI_IMDB_DATABASE) else MC_IMDB_DATABASE +TSV_DIRECTORY = RPI_TSV_DIRECTORY if os.path.exists(RPI_TSV_DIRECTORY) else MC_TSV_DIRECTORY +CSV_DIRECTORY = RPI_CSV_DIRECTORY if os.path.exists(RPI_CSV_DIRECTORY) else MC_CSV_DIRECTORY + + +def create_csv_files(): + print("start create csv") + subprocess.run(["xsv", "input", "-d", "\t", "--no-quoting", "{}title.akas.tsv".format(TSV_DIRECTORY), "-o", "{}title_akas.csv".format(CSV_DIRECTORY)]) + subprocess.run(["xsv", "input", "-d", "\t", "--no-quoting", "{}title.basics.tsv".format(TSV_DIRECTORY), "-o", "{}title_basics.csv".format(CSV_DIRECTORY)]) + subprocess.run(["xsv", "input", "-d", "\t", "--no-quoting", "{}title.episode.tsv".format(TSV_DIRECTORY), "-o", "{}title_episode.csv".format(CSV_DIRECTORY)]) + print("end create csv") + + +def import_csv_files(): + print("start import csv") + f = open("import_csv.sql").read() + sql_script = f.format(CSV_DIRECTORY) + subprocess.run(["sudo", "-u", "http", "sqlite3", IMDB_DATABASE+"imdb.db"], input=sql_script.encode("utf8")) + print("end import csv") + + +create_csv_files() +import_csv_files() diff --git a/scripts/import_csv.sql b/scripts/import_csv.sql new file mode 100644 index 0000000..37cb414 --- /dev/null +++ b/scripts/import_csv.sql @@ -0,0 +1,34 @@ +.mode csv +.import {0}title_akas.csv title_akas +.print "title_akas finished importing" +.import {0}title_basics.csv title_basics +.print "title_basics finished importing" +.import {0}title_episode.csv title_episode +.print "title_episode finished importing" +.print "" +.print "start nullify title_akas" +UPDATE title_akas SET title=NULL WHERE title='\\N'; +UPDATE title_akas SET region=NULL WHERE region='\\N'; +UPDATE title_akas SET language=NULL WHERE language='\\N'; +UPDATE title_akas SET types=NULL WHERE types='\\N'; +UPDATE title_akas SET attributes=NULL WHERE attributes='\\N'; +UPDATE title_akas SET isOriginalTitle=NULL WHERE isOriginalTitle='\\N'; +.print "end nullify title_akas" +.print "" +.print "start nullify title_basics" +UPDATE title_basics SET titleType=NULL WHERE titleType='\\N'; +UPDATE title_basics SET primaryTitle=NULL WHERE primaryTitle='\\N'; +UPDATE title_basics SET originalTitle=NULL WHERE originalTitle='\\N'; +UPDATE title_basics SET isAdult=NULL WHERE isAdult='\\N'; +UPDATE title_basics SET startYear=NULL WHERE startYear='\\N'; +UPDATE title_basics SET endYear=NULL WHERE endYear='\\N'; +UPDATE title_basics SET runtimeMinutes=NULL WHERE runtimeMinutes='\\N'; +UPDATE title_basics SET genres=NULL WHERE genres='\\N'; +.print "end nullify title_basics" +.print "" +.print "start nullify title_episode" +UPDATE title_episode SET parentTconst=NULL WHERE parentTconst='\\N'; +UPDATE title_episode SET seasonNumber=NULL WHERE seasonNumber='\\N'; +UPDATE title_episode SET episodeNumber=NULL WHERE episodeNumber='\\N'; +.print "end nullify title_episode" +.print "" diff --git a/scripts/tmdb.py b/scripts/tmdb.py new file mode 100644 index 0000000..aadc21e --- /dev/null +++ b/scripts/tmdb.py @@ -0,0 +1,26 @@ +import requests + +API_KEY = "***REMOVED***" +TMDB_FIND_URL = "https://api.themoviedb.org/3/find/" + +TMDB_IMG_URL = "https://image.tmdb.org/t/p/original" + + +def get_movie_data(imdb_id): + data = { + "api_key": API_KEY, + "language": "en-US", + "external_source": "imdb_id" + } + r = requests.get(TMDB_FIND_URL+imdb_id, data=data) + info = dict(r.json()) + if "status_code" in info.keys(): + print("error getting tmdb data, status code:", info["status_code"]) + return None + print("tmdb movie title:", info["movie_results"][0]["title"]) + movie_id = info["movie_results"][0]["id"] + overview = info["movie_results"][0]["overview"] + poster_path = info["movie_results"][0]["poster_path"] + backdrop_path = info["movie_results"][0]["backdrop_path"] + + return movie_id, overview, poster_path, backdrop_path diff --git a/static/images/Max.png b/static/images/Max.png new file mode 100644 index 0000000..4b0199c Binary files /dev/null and b/static/images/Max.png differ diff --git a/static/images/Skybound.png b/static/images/Skybound.png new file mode 100644 index 0000000..b55457d Binary files /dev/null and b/static/images/Skybound.png differ diff --git a/static/svg/tmdb.svg b/static/svg/tmdb.svg new file mode 100644 index 0000000..9a56bfc --- /dev/null +++ b/static/svg/tmdb.svg @@ -0,0 +1 @@ +PoweredByRectangle_Blue \ No newline at end of file diff --git a/static/video-js-7.6.0/alt/video-js-cdn.css b/static/video-js-7.6.0/alt/video-js-cdn.css new file mode 100644 index 0000000..03acff3 --- /dev/null +++ b/static/video-js-7.6.0/alt/video-js-cdn.css @@ -0,0 +1,1661 @@ +@charset "UTF-8"; +.vjs-modal-dialog .vjs-modal-dialog-content, .video-js .vjs-modal-dialog, .vjs-button > .vjs-icon-placeholder:before, .video-js .vjs-big-play-button .vjs-icon-placeholder:before { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.vjs-button > .vjs-icon-placeholder:before, .video-js .vjs-big-play-button .vjs-icon-placeholder:before { + text-align: center; +} + +@font-face { + font-family: VideoJS; + src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAABDkAAsAAAAAG6gAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADsAAABUIIslek9TLzIAAAFEAAAAPgAAAFZRiV3hY21hcAAAAYQAAADaAAADPv749/pnbHlmAAACYAAAC3AAABHQZg6OcWhlYWQAAA3QAAAAKwAAADYZw251aGhlYQAADfwAAAAdAAAAJA+RCLFobXR4AAAOHAAAABMAAACM744AAGxvY2EAAA4wAAAASAAAAEhF6kqubWF4cAAADngAAAAfAAAAIAE0AIFuYW1lAAAOmAAAASUAAAIK1cf1oHBvc3QAAA/AAAABJAAAAdPExYuNeJxjYGRgYOBiMGCwY2BycfMJYeDLSSzJY5BiYGGAAJA8MpsxJzM9kYEDxgPKsYBpDiBmg4gCACY7BUgAeJxjYGS7wTiBgZWBgaWQ5RkDA8MvCM0cwxDOeI6BgYmBlZkBKwhIc01hcPjI+FGJHcRdyA4RZgQRADK3CxEAAHic7dFZbsMgAEXRS0ycyZnnOeG7y+qC8pU1dHusIOXxuoxaOlwZYWQB0Aea4quIEN4E9LzKbKjzDeM6H/mua6Lmc/p8yhg0lvdYx15ZG8uOLQOGjMp3EzqmzJizYMmKNRu27Nhz4MiJMxeu3Ljz4Ekqm7T8P52G8PP3lnTOVk++Z6iN6QZzNN1F7ptuN7eGOjDUoaGODHVsuvU8MdTO9Hd5aqgzQ50b6sJQl4a6MtS1oW4MdWuoO0PdG+rBUI+GejLUs6FeDPVqqDdDvRvqw1CfhpqM9At0iFLaAAB4nJ1YDXBTVRZ+5/22TUlJ8we0pHlJm7RJf5O8F2j6EymlSPkpxaL8U2xpa3DKj0CBhc2IW4eWKSokIoLsuMqssM64f+jA4HSdWXXXscBq67IOs3FXZ1ZYWVyRFdo899yXtIBQZ90k7717zz3v3HPPOfd854YCCj9cL9dL0RQFOqCbGJnrHb5EayiKIWN8iA/hWBblo6hUWm8TtCDwE80WMJus/irwyxOdxeB0MDb14VNJHnXYoLLSl6FfCUYO9nYPTA8Epg9090LprfbBbZ2hY0UlJUXHQp3/vtWkS6EBv8+rPMq5u9692f/dNxJNiqwC1xPE9TCUgCsSdQWgE3XQD25lkG4CN2xmTcOXWBOyser6RN6KnGbKSbmQ3+d0OI1m2W8QzLLkI2sykrWAgJJEtA8vGGW/2Q+CmT3n8zS9wZwu2DCvtuZKZN3xkrLh36yCZuUomQSqGpY8t/25VfHVhw8z4ebGBtfLb0ya9PCaDc+8dGTvk2dsh6z7WzvowlXKUSWo9MJ15a3KrEP2loOr2Ojhw6iW6hf2BDdEccQvZGpaAy7YovSwq8kr7HGllxpd71rkS6G0Sf11sl9OvMK1+jwPPODxjUwkOim9CU3ix1wNjXDfmJSEn618Bs6lpWwUpU+8PCqLMY650zjq8VhCIP17NEKTx3eaLL+s5Pi6yJWaWjTHLR1jYzPSV9VF/6Ojdb/1kO3Mk3uhHC0x6gc1BjlKQ+nQFxTYdaJkZ7ySVxLBbhR1dsboNXp1tCYKW2LRaEzpYcIx2BKNxaL0ZaUnSqfFoiNhHKR/GkX6PWUSAaJelQaqZL1EpoHNsajSEyPSoJ9IjhIxTdjHLmwZvhRDOiFTY/YeQnvrVZmiTQtGncECXtFTBZLOVwwMRgoXHAkXzMzPn1nAJJ8jYSbMDaqN2waGLzNhih/bZynUBMpIWSg7VYi7DRx2m8ALkIdRCJwI6ArJx2EI8kaDWeTQKeAFk9fjl/1AvwktjQ1P7NjyMGQyfd4vjipX6M/i52D7Cq80kqlcxEcGXRr/FEcgs0u5uGgB4VWuMFfpdn2Re6Hi3PqzmxWKsz6+ae2Pn9hXXw/fqM859UiGC0oKYYILJBqJrsn1Z1E5qOs9rQCiUQRREjm8yJcbHF5cUJufX1vAHlefw0XgUoboS3ETfQlTxBC4SOtuE8VPRJTBSCQSjZCpk7Gqzu+masaZ2y7Zjehho4F3g82BNDkAHpORG4+OCS+f6JTPmtRn/PH1kch6d04sp7AQb25aQ/pqUyXeQ8vrebG8OYQdXOQ+585u0sdW9rqalzRURiJ+9F4MweRFrKUjl1GUYhH1A27WOHw5cTFSFPMo9EeUIGnQTZHIaJ7AHLaOKsOODaNF9jkBjYG2QEsQ2xjMUAx2bBEbeTBWMHwskBjngq56S/yfgkBnWBa4K9sqKtq2t1UI8S9He5XuBRbawAdatrQEAi30Aks2+LM8WeCbalVZkWNylvJ+dqJnzVb+OHlSoKW8nPCP7Rd+CcZ2DdWAGqJ2CBFOphgywFFCFBNtfAbGtNPBCwxvygHeYMZMY9ZboBqwq/pVrsbgN5tkv152ODlbMfiqwGMBgxa4Exz3QhovRIUp6acqZmQzRq0ypDXS2TPLT02YIkQETnOE445oOGxOmXAqUJNNG7XgupMjPq2ua9asrj5yY/yuKteO1Kx0YNJTufrirLe1mZnat7OL6rnUdCWenpW6I8mAnbsY8KWs1PuSovCW9A/Z25PQ24a7cNOqgmTkLmBMgh4THgc4b9k2IVv1/g/F5nGljwPLfOgHAzJzh45V/4+WenTzmMtR5Z7us2Tys909UHqrPY7KbckoxRvRHhmVc3cJGE97uml0R1S0jdULVl7EvZtDFVBF35N9cEdjpgmAiOlFZ+Dtoh93+D3zzHr8RRNZQhnCNMNbcegOvpEwZoL+06cJQ07h+th3fZ/7PVbVC6ngTAV/KoLFuO6+2KFcU651gEb5ugPSIb1D+Xp8V4+k3sEIGnw5mYe4If4k1lFYr6SCzmM2EQ8iWtmwjnBI9kTwe1TlfAmXh7H02by9fW2gsjKwtv0aaURKil4OdV7rDL1MXIFNrhdxohcZXYTnq47WisrKitaObbf5+yvkLi5J6lCNZZ+B6GC38VNBZBDidSS/+mSvh6s+srgC8pyKMvDtt+de3c9fU76ZPfuM8ud4Kv0fyP/LqfepMT/3oZxSqpZaTa1DaQYLY8TFsHYbWYsPoRhRWfL5eSSQbhUGgGC3YLbVMk6PitTFNGpAsNrC6D1VNBKgBHMejaiuRWEWGgsSDBTJjqWIl8kJLlsaLJ2tXDr6xGfT85bM2Q06a46x2HTgvdnV8z5YDy/27J4zt6x2VtkzjoYpkq36kaBr4eQSg7tyiVweWubXZugtadl58ydapfbORfKsDTuZ0OBgx4cfdjCf5tbWNITnL120fdOi1RV1C3uKGzNdwYLcMvZ3BxoPyTOCD1XvXTp7U10gWCVmTV9b3r2z0SkGWovb2hp9I89O8a2smlyaO8muMU+dRmtzp60IzAoFpjLr1n388boLyf0dRvxhsHZ0qbWqDkwqvvpkj4l0fY6EIXRi5sQSrAvsVYwXRy4qJ2EVtD1AN7a0HWth9ymvL1xc3WTUKK/TAHA/bXDVtVWfOMfuGxGZv4Ln/jVr9jc3j1yMv0tndmyt9Vq88Y9gH1wtLX3KWjot5++jWHgAoZZkQ14wGQ20Fli71UmKJAy4xKMSTGbVdybW7FDDAut9XpD5AzWrYO7zQ8qffqF8+Ynd/clrHcdyxGy3a/3+mfNnzC/cBsveTjnTvXf1o6vzOlZw7WtqtdmPK/Errz/6NNtD72zmNOZfbmYdTGHfoofqI79Oc+R2n1lrnL6pOm0Up7kwxhTW12Amm7WYkXR2qYrF2AmgmbAsxZjwy1xpg/m1Je2vrp8v/nz2xpmlBg4E9hrMU341wVpTOh/OfmGvAnra8q6uctr60ZQHV3Q+WMQJykMj8ZsWn2QBOmmHMB+m5pDIpTFonYigiaKAhGEiAHF7EliVnQkjoLVIMPtJpBKHYd3A8GYH9jJzrWwmHx5Qjp7vDAX0suGRym1vtm/9W1/HyR8vczfMs6Sk8DSv855/5dlX9oQq52hT8syyp2rx5Id17IAyAM3wIjQPMOHzytEB64q6D5zT91yNbnx3V/nqnd017S9Y0605k3izoXLpsxde2n38yoOV9s1LcjwzNjbdX6asnBVaBj/6/DwKwPkpcqbDG7BnsXoSqWnUAmottYF6jMSdVyYZh3zVXCjwTiwwHH6sGuRiEHQGzuRX6whZkp123oy1BWE2mEfJ/tvIRtM4ZM5bDXiMsPMaAKOTyc5uL57rqyyc5y5JE5pm1i2S2iUX0CcaQ6lC6Zog7JqSqZmYlosl2K6pwNA84zRnQW6SaALYZQGW5lhCtU/W34N6o+bKfZ8cf3/Cl/+iTX3wBzpOY4mRkeNf3rptycGSshQWgGbYt5jFc2e0+DglIrwl6DVWQ7BuwaJ3Xk1J4VL5urnLl/Wf+gHU/hZoZdKNym6lG+I34FaNeZKcSpJIo2IeCVvpdsDGfKvzJnAwmeD37Ow65ZWwSowpgwX5T69s/rB55dP5BcpgDKFV8p7q2sn/1uc93bVzT/w6UrCqDTWvfCq/oCD/qZXNoUj8BL5Kp6GU017frfNXkAtiiyf/SOCEeLqnd8R/Ql9GlCRfctS6k5chvIBuQ1zCCjoCHL2DHNHIXxMJ3kQeO8lbsUXONeSfA5EjcG6/E+KdhN4bP04vBhdi883+BFBzQbxFbvZzQeY9LNBZc0FNfn5NwfDn6rCTnTw6R8o+gfpf5hCom33cRuiTlss3KHmZjD+BPN+5gXuA2ziS/Q73mLxUkpbKN/eqwz5uK0X9F3h2d1V4nGNgZGBgAOJd776+iue3+crAzc4AAje5Bfcg0xz9YHEOBiYQBQA8FQlFAHicY2BkYGBnAAGOPgaG//85+hkYGVCBMgBGGwNYAAAAeJxjYGBgYB8EmKOPgQEAQ04BfgAAAAAAAA4AaAB+AMwA4AECAUIBbAGYAcICGAJYArQC4AMwA7AD3gQwBJYE3AUkBWYFigYgBmYGtAbqB1gIEghYCG4IhAi2COh4nGNgZGBgUGYoZWBnAAEmIOYCQgaG/2A+AwAYCQG2AHicXZBNaoNAGIZfE5PQCKFQ2lUps2oXBfOzzAESyDKBQJdGR2NQR3QSSE/QE/QEPUUPUHqsvsrXjTMw83zPvPMNCuAWP3DQDAejdm1GjzwS7pMmwi75XngAD4/CQ/oX4TFe4Qt7uMMbOzjuDc0EmXCP/C7cJ38Iu+RP4QEe8CU8pP8WHmOPX2EPz87TPo202ey2OjlnQSXV/6arOjWFmvszMWtd6CqwOlKHq6ovycLaWMWVydXKFFZnmVFlZU46tP7R2nI5ncbi/dDkfDtFBA2DDXbYkhKc+V0Bqs5Zt9JM1HQGBRTm/EezTmZNKtpcAMs9Yu6AK9caF76zoLWIWcfMGOSkVduvSWechqZsz040Ib2PY3urxBJTzriT95lipz+TN1fmAAAAeJxtkMl2wjAMRfOAhABlKm2h80C3+ajgCKKDY6cegP59TYBzukAL+z1Zsq8ctaJTTKPrsUQLbXQQI0EXKXroY4AbDDHCGBNMcYsZ7nCPB8yxwCOe8IwXvOIN7/jAJ76wxHfUqWX+OzgumWAjJMV17i0Ndlr6irLKO+qftdT7i6y4uFSUvCknay+lFYZIZaQcmfH/xIFdYn98bqhra1aKTM/6lWMnyaYirx1rFUQZFBkb2zJUtoXeJCeg0WnLtHeSFc3OtrnozNwqi0TkSpBMDB1nSde5oJXW23hTS2/T0LilglXX7dmFVxLnq5U0vYATHFk3zX3BOisoQHNDFDeZnqKDy9hRNawN7Vh727hFzcJ5c8TILrKZfH7tIPxAFP0BpLeJPA==) format("woff"); + font-weight: normal; + font-style: normal; +} +.vjs-icon-play, .video-js .vjs-play-control .vjs-icon-placeholder, .video-js .vjs-big-play-button .vjs-icon-placeholder:before { + font-family: VideoJS; + font-weight: normal; + font-style: normal; +} +.vjs-icon-play:before, .video-js .vjs-play-control .vjs-icon-placeholder:before, .video-js .vjs-big-play-button .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-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, .video-js .vjs-subtitles-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; +} +.vjs-icon-subtitles: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, .video-js .vjs-subtitles-button .vjs-icon-placeholder:before { + content: "\f10c"; +} + +.vjs-icon-captions, .video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder, +.video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder, .video-js .vjs-captions-button .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; +} +.vjs-icon-captions: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, .video-js .vjs-captions-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, .vjs-seek-to-live-control .vjs-icon-placeholder, .video-js .vjs-volume-level, .video-js .vjs-play-progress { + font-family: VideoJS; + font-weight: normal; + font-style: normal; +} +.vjs-icon-circle:before, .vjs-seek-to-live-control .vjs-icon-placeholder:before, .video-js .vjs-volume-level:before, .video-js .vjs-play-progress: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"; +} + +.vjs-icon-picture-in-picture-enter, .video-js .vjs-picture-in-picture-control .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; +} +.vjs-icon-picture-in-picture-enter:before, .video-js .vjs-picture-in-picture-control .vjs-icon-placeholder:before { + content: "\f121"; +} + +.vjs-icon-picture-in-picture-exit, .video-js.vjs-picture-in-picture .vjs-picture-in-picture-control .vjs-icon-placeholder { + font-family: VideoJS; + font-weight: normal; + font-style: normal; +} +.vjs-icon-picture-in-picture-exit:before, .video-js.vjs-picture-in-picture .vjs-picture-in-picture-control .vjs-icon-placeholder:before { + content: "\f122"; +} + +.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.63332em; + 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); + border-radius: 0.3em; + transition: all 0.4s; +} +.vjs-big-play-centered .vjs-big-play-button { + top: 50%; + left: 50%; + margin-top: -0.81666em; + 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); + 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: 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, +.js-focus-visible .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, +.js-focus-visible .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-layout-tiny .vjs-menu-button-popup .vjs-menu .vjs-menu-content, +.vjs-layout-x-small .vjs-menu-button-popup .vjs-menu .vjs-menu-content { + max-height: 5em; +} + +.vjs-layout-small .vjs-menu-button-popup .vjs-menu .vjs-menu-content { + max-height: 10em; +} + +.vjs-layout-medium .vjs-menu-button-popup .vjs-menu .vjs-menu-content { + max-height: 14em; +} + +.vjs-layout-large .vjs-menu-button-popup .vjs-menu .vjs-menu-content, +.vjs-layout-x-large .vjs-menu-button-popup .vjs-menu .vjs-menu-content, +.vjs-layout-huge .vjs-menu-button-popup .vjs-menu .vjs-menu-content { + max-height: 25em; +} + +.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 { + 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; + 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: 3em; + background-color: #2B333F; + background-color: rgba(43, 51, 63, 0.7); +} + +.vjs-has-started .vjs-control-bar { + display: flex; + visibility: visible; + opacity: 1; + transition: visibility 0.1s, opacity 0.1s; +} + +.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar { + visibility: visible; + opacity: 0; + 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; + 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; + flex: auto; + display: flex; + 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-liveui .vjs-progress-control { + display: flex; + align-items: center; +} + +.vjs-no-flex .vjs-progress-control { + width: auto; +} + +.video-js .vjs-progress-holder { + flex: auto; + 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.6666666667em; +} + +.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.3333333333em; + 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); + 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: absolute; + 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; + 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 */ + /* 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; + box-shadow: 0 0 1em #fff; +} + +.video-js .vjs-mute-control { + cursor: pointer; + flex: none; +} +.video-js .vjs-volume-control { + cursor: pointer; + margin-right: 1em; + 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 { + 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; + 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:hover .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical { + left: -3.5em; +} +.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; + transition: width 0.1s; +} +.video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-mute-toggle-only { + width: 4em; +} + +.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical { + height: 8em; + width: 3em; + left: -3000em; + 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 { + 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; + 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: 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: flex; + align-items: flex-start; + flex: auto; + font-size: 1em; + line-height: 3em; +} + +.vjs-no-flex .vjs-live-control { + display: table-cell; + width: auto; + text-align: left; +} + +.video-js:not(.vjs-live) .vjs-live-control, +.video-js.vjs-liveui .vjs-live-control { + display: none; +} + +.video-js .vjs-seek-to-live-control { + cursor: pointer; + flex: none; + display: inline-flex; + height: 100%; + padding-left: 0.5em; + padding-right: 0.5em; + font-size: 1em; + line-height: 3em; + width: auto; + min-width: 4em; +} + +.vjs-no-flex .vjs-seek-to-live-control { + display: table-cell; + width: auto; + text-align: left; +} + +.video-js.vjs-live:not(.vjs-liveui) .vjs-seek-to-live-control, +.video-js:not(.vjs-live) .vjs-seek-to-live-control { + display: none; +} + +.vjs-seek-to-live-control.vjs-control.vjs-at-live-edge { + cursor: auto; +} + +.vjs-seek-to-live-control .vjs-icon-placeholder { + margin-right: 0.5em; + color: #888; +} + +.vjs-seek-to-live-control.vjs-control.vjs-at-live-edge .vjs-icon-placeholder { + color: red; +} + +.video-js .vjs-time-control { + 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 { + cursor: pointer; +} + +.video-js .vjs-play-control .vjs-icon-placeholder { + 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 { + transform: translateY(-3em); +} + +.video-js.vjs-user-inactive.vjs-playing video::-webkit-media-text-track-display { + transform: translateY(-1.5em); +} + +.video-js .vjs-picture-in-picture-control { + cursor: pointer; + flex: none; +} +.video-js .vjs-fullscreen-control { + cursor: pointer; + 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; + -webkit-animation: vjs-spinner-show 0s linear 0.3s forwards; + animation: vjs-spinner-show 0s linear 0.3s forwards; +} + +.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: ""; + font-size: 1.5em; + line-height: inherit; +} + +.video-js .vjs-audio-button + .vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder { + vertical-align: middle; + display: inline-block; + margin-bottom: -0.1em; +} + +.video-js .vjs-audio-button + .vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before { + font-family: VideoJS; + content: " "; + font-size: 1.5em; + line-height: inherit; +} + +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-current-time, +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-time-divider, +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-duration, +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-remaining-time, +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-playback-rate, +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-chapters-button, +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-descriptions-button, +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-captions-button, +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-subtitles-button, +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-audio-button, +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-control, .video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-current-time, +.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-time-divider, +.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-duration, +.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-remaining-time, +.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-playback-rate, +.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-chapters-button, +.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-descriptions-button, +.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-captions-button, +.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-subtitles-button, +.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-audio-button, +.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-control, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-current-time, +.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-time-divider, +.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-duration, +.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-remaining-time, +.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-playback-rate, +.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-chapters-button, +.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-descriptions-button, +.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-captions-button, +.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-subtitles-button, +.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-audio-button, +.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-control { + display: none; +} +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover, +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:active, +.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover, +.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:active, +.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:hover, +.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:active, +.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active { + width: auto; + width: initial; +} +.video-js:not(.vjs-fullscreen).vjs-layout-x-small:not(.vjs-liveui) .vjs-subs-caps-button, .video-js:not(.vjs-fullscreen).vjs-layout-x-small:not(.vjs-live) .vjs-subs-caps-button, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-subs-caps-button { + display: none; +} +.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui .vjs-custom-control-spacer, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-custom-control-spacer { + flex: auto; + display: block; +} +.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui.vjs-no-flex .vjs-custom-control-spacer, .video-js:not(.vjs-fullscreen).vjs-layout-tiny.vjs-no-flex .vjs-custom-control-spacer { + width: auto; +} +.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui .vjs-progress-control, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-progress-control { + 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; + padding: 20px 24px 0px 24px; + } + + .vjs-track-settings-controls .vjs-default-button { + margin-bottom: 20px; + } + + .vjs-text-track-settings .vjs-track-settings-controls { + grid-column: 1/-1; + } + + .vjs-layout-small .vjs-text-track-settings .vjs-modal-dialog-content, +.vjs-layout-x-small .vjs-text-track-settings .vjs-modal-dialog-content, +.vjs-layout-tiny .vjs-text-track-settings .vjs-modal-dialog-content { + grid-template-columns: 1fr; + } +} +.vjs-track-setting > select { + margin-right: 1em; + margin-bottom: 0.5em; +} + +.vjs-text-track-settings fieldset { + margin: 5px; + padding: 3px; + border: none; +} + +.vjs-text-track-settings fieldset span { + display: inline-block; +} + +.vjs-text-track-settings fieldset span > select { + max-width: 7.3em; +} + +.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; + z-index: -1000; +} + +.js-focus-visible .video-js *:focus:not(.focus-visible) { + outline: none; + background: none; +} + +.video-js *:focus:not(:focus-visible), +.video-js .vjs-menu *:focus:not(:focus-visible) { + outline: none; + background: none; +} diff --git a/static/video-js-7.6.0/alt/video-js-cdn.min.css b/static/video-js-7.6.0/alt/video-js-cdn.min.css new file mode 100644 index 0000000..68c82db --- /dev/null +++ b/static/video-js-7.6.0/alt/video-js-cdn.min.css @@ -0,0 +1 @@ +@charset "UTF-8";.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.video-js .vjs-modal-dialog,.vjs-button>.vjs-icon-placeholder:before,.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,d09GRgABAAAAABDkAAsAAAAAG6gAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADsAAABUIIslek9TLzIAAAFEAAAAPgAAAFZRiV3hY21hcAAAAYQAAADaAAADPv749/pnbHlmAAACYAAAC3AAABHQZg6OcWhlYWQAAA3QAAAAKwAAADYZw251aGhlYQAADfwAAAAdAAAAJA+RCLFobXR4AAAOHAAAABMAAACM744AAGxvY2EAAA4wAAAASAAAAEhF6kqubWF4cAAADngAAAAfAAAAIAE0AIFuYW1lAAAOmAAAASUAAAIK1cf1oHBvc3QAAA/AAAABJAAAAdPExYuNeJxjYGRgYOBiMGCwY2BycfMJYeDLSSzJY5BiYGGAAJA8MpsxJzM9kYEDxgPKsYBpDiBmg4gCACY7BUgAeJxjYGS7wTiBgZWBgaWQ5RkDA8MvCM0cwxDOeI6BgYmBlZkBKwhIc01hcPjI+FGJHcRdyA4RZgQRADK3CxEAAHic7dFZbsMgAEXRS0ycyZnnOeG7y+qC8pU1dHusIOXxuoxaOlwZYWQB0Aea4quIEN4E9LzKbKjzDeM6H/mua6Lmc/p8yhg0lvdYx15ZG8uOLQOGjMp3EzqmzJizYMmKNRu27Nhz4MiJMxeu3Ljz4Ekqm7T8P52G8PP3lnTOVk++Z6iN6QZzNN1F7ptuN7eGOjDUoaGODHVsuvU8MdTO9Hd5aqgzQ50b6sJQl4a6MtS1oW4MdWuoO0PdG+rBUI+GejLUs6FeDPVqqDdDvRvqw1CfhpqM9At0iFLaAAB4nJ1YDXBTVRZ+5/22TUlJ8we0pHlJm7RJf5O8F2j6EymlSPkpxaL8U2xpa3DKj0CBhc2IW4eWKSokIoLsuMqssM64f+jA4HSdWXXXscBq67IOs3FXZ1ZYWVyRFdo899yXtIBQZ90k7717zz3v3HPPOfd854YCCj9cL9dL0RQFOqCbGJnrHb5EayiKIWN8iA/hWBblo6hUWm8TtCDwE80WMJus/irwyxOdxeB0MDb14VNJHnXYoLLSl6FfCUYO9nYPTA8Epg9090LprfbBbZ2hY0UlJUXHQp3/vtWkS6EBv8+rPMq5u9692f/dNxJNiqwC1xPE9TCUgCsSdQWgE3XQD25lkG4CN2xmTcOXWBOyser6RN6KnGbKSbmQ3+d0OI1m2W8QzLLkI2sykrWAgJJEtA8vGGW/2Q+CmT3n8zS9wZwu2DCvtuZKZN3xkrLh36yCZuUomQSqGpY8t/25VfHVhw8z4ebGBtfLb0ya9PCaDc+8dGTvk2dsh6z7WzvowlXKUSWo9MJ15a3KrEP2loOr2Ojhw6iW6hf2BDdEccQvZGpaAy7YovSwq8kr7HGllxpd71rkS6G0Sf11sl9OvMK1+jwPPODxjUwkOim9CU3ix1wNjXDfmJSEn618Bs6lpWwUpU+8PCqLMY650zjq8VhCIP17NEKTx3eaLL+s5Pi6yJWaWjTHLR1jYzPSV9VF/6Ojdb/1kO3Mk3uhHC0x6gc1BjlKQ+nQFxTYdaJkZ7ySVxLBbhR1dsboNXp1tCYKW2LRaEzpYcIx2BKNxaL0ZaUnSqfFoiNhHKR/GkX6PWUSAaJelQaqZL1EpoHNsajSEyPSoJ9IjhIxTdjHLmwZvhRDOiFTY/YeQnvrVZmiTQtGncECXtFTBZLOVwwMRgoXHAkXzMzPn1nAJJ8jYSbMDaqN2waGLzNhih/bZynUBMpIWSg7VYi7DRx2m8ALkIdRCJwI6ArJx2EI8kaDWeTQKeAFk9fjl/1AvwktjQ1P7NjyMGQyfd4vjipX6M/i52D7Cq80kqlcxEcGXRr/FEcgs0u5uGgB4VWuMFfpdn2Re6Hi3PqzmxWKsz6+ae2Pn9hXXw/fqM859UiGC0oKYYILJBqJrsn1Z1E5qOs9rQCiUQRREjm8yJcbHF5cUJufX1vAHlefw0XgUoboS3ETfQlTxBC4SOtuE8VPRJTBSCQSjZCpk7Gqzu+masaZ2y7Zjehho4F3g82BNDkAHpORG4+OCS+f6JTPmtRn/PH1kch6d04sp7AQb25aQ/pqUyXeQ8vrebG8OYQdXOQ+585u0sdW9rqalzRURiJ+9F4MweRFrKUjl1GUYhH1A27WOHw5cTFSFPMo9EeUIGnQTZHIaJ7AHLaOKsOODaNF9jkBjYG2QEsQ2xjMUAx2bBEbeTBWMHwskBjngq56S/yfgkBnWBa4K9sqKtq2t1UI8S9He5XuBRbawAdatrQEAi30Aks2+LM8WeCbalVZkWNylvJ+dqJnzVb+OHlSoKW8nPCP7Rd+CcZ2DdWAGqJ2CBFOphgywFFCFBNtfAbGtNPBCwxvygHeYMZMY9ZboBqwq/pVrsbgN5tkv152ODlbMfiqwGMBgxa4Exz3QhovRIUp6acqZmQzRq0ypDXS2TPLT02YIkQETnOE445oOGxOmXAqUJNNG7XgupMjPq2ua9asrj5yY/yuKteO1Kx0YNJTufrirLe1mZnat7OL6rnUdCWenpW6I8mAnbsY8KWs1PuSovCW9A/Z25PQ24a7cNOqgmTkLmBMgh4THgc4b9k2IVv1/g/F5nGljwPLfOgHAzJzh45V/4+WenTzmMtR5Z7us2Tys909UHqrPY7KbckoxRvRHhmVc3cJGE97uml0R1S0jdULVl7EvZtDFVBF35N9cEdjpgmAiOlFZ+Dtoh93+D3zzHr8RRNZQhnCNMNbcegOvpEwZoL+06cJQ07h+th3fZ/7PVbVC6ngTAV/KoLFuO6+2KFcU651gEb5ugPSIb1D+Xp8V4+k3sEIGnw5mYe4If4k1lFYr6SCzmM2EQ8iWtmwjnBI9kTwe1TlfAmXh7H02by9fW2gsjKwtv0aaURKil4OdV7rDL1MXIFNrhdxohcZXYTnq47WisrKitaObbf5+yvkLi5J6lCNZZ+B6GC38VNBZBDidSS/+mSvh6s+srgC8pyKMvDtt+de3c9fU76ZPfuM8ud4Kv0fyP/LqfepMT/3oZxSqpZaTa1DaQYLY8TFsHYbWYsPoRhRWfL5eSSQbhUGgGC3YLbVMk6PitTFNGpAsNrC6D1VNBKgBHMejaiuRWEWGgsSDBTJjqWIl8kJLlsaLJ2tXDr6xGfT85bM2Q06a46x2HTgvdnV8z5YDy/27J4zt6x2VtkzjoYpkq36kaBr4eQSg7tyiVweWubXZugtadl58ydapfbORfKsDTuZ0OBgx4cfdjCf5tbWNITnL120fdOi1RV1C3uKGzNdwYLcMvZ3BxoPyTOCD1XvXTp7U10gWCVmTV9b3r2z0SkGWovb2hp9I89O8a2smlyaO8muMU+dRmtzp60IzAoFpjLr1n388boLyf0dRvxhsHZ0qbWqDkwqvvpkj4l0fY6EIXRi5sQSrAvsVYwXRy4qJ2EVtD1AN7a0HWth9ymvL1xc3WTUKK/TAHA/bXDVtVWfOMfuGxGZv4Ln/jVr9jc3j1yMv0tndmyt9Vq88Y9gH1wtLX3KWjot5++jWHgAoZZkQ14wGQ20Fli71UmKJAy4xKMSTGbVdybW7FDDAut9XpD5AzWrYO7zQ8qffqF8+Ynd/clrHcdyxGy3a/3+mfNnzC/cBsveTjnTvXf1o6vzOlZw7WtqtdmPK/Errz/6NNtD72zmNOZfbmYdTGHfoofqI79Oc+R2n1lrnL6pOm0Up7kwxhTW12Amm7WYkXR2qYrF2AmgmbAsxZjwy1xpg/m1Je2vrp8v/nz2xpmlBg4E9hrMU341wVpTOh/OfmGvAnra8q6uctr60ZQHV3Q+WMQJykMj8ZsWn2QBOmmHMB+m5pDIpTFonYigiaKAhGEiAHF7EliVnQkjoLVIMPtJpBKHYd3A8GYH9jJzrWwmHx5Qjp7vDAX0suGRym1vtm/9W1/HyR8vczfMs6Sk8DSv855/5dlX9oQq52hT8syyp2rx5Id17IAyAM3wIjQPMOHzytEB64q6D5zT91yNbnx3V/nqnd017S9Y0605k3izoXLpsxde2n38yoOV9s1LcjwzNjbdX6asnBVaBj/6/DwKwPkpcqbDG7BnsXoSqWnUAmottYF6jMSdVyYZh3zVXCjwTiwwHH6sGuRiEHQGzuRX6whZkp123oy1BWE2mEfJ/tvIRtM4ZM5bDXiMsPMaAKOTyc5uL57rqyyc5y5JE5pm1i2S2iUX0CcaQ6lC6Zog7JqSqZmYlosl2K6pwNA84zRnQW6SaALYZQGW5lhCtU/W34N6o+bKfZ8cf3/Cl/+iTX3wBzpOY4mRkeNf3rptycGSshQWgGbYt5jFc2e0+DglIrwl6DVWQ7BuwaJ3Xk1J4VL5urnLl/Wf+gHU/hZoZdKNym6lG+I34FaNeZKcSpJIo2IeCVvpdsDGfKvzJnAwmeD37Ow65ZWwSowpgwX5T69s/rB55dP5BcpgDKFV8p7q2sn/1uc93bVzT/w6UrCqDTWvfCq/oCD/qZXNoUj8BL5Kp6GU017frfNXkAtiiyf/SOCEeLqnd8R/Ql9GlCRfctS6k5chvIBuQ1zCCjoCHL2DHNHIXxMJ3kQeO8lbsUXONeSfA5EjcG6/E+KdhN4bP04vBhdi883+BFBzQbxFbvZzQeY9LNBZc0FNfn5NwfDn6rCTnTw6R8o+gfpf5hCom33cRuiTlss3KHmZjD+BPN+5gXuA2ziS/Q73mLxUkpbKN/eqwz5uK0X9F3h2d1V4nGNgZGBgAOJd776+iue3+crAzc4AAje5Bfcg0xz9YHEOBiYQBQA8FQlFAHicY2BkYGBnAAGOPgaG//85+hkYGVCBMgBGGwNYAAAAeJxjYGBgYB8EmKOPgQEAQ04BfgAAAAAAAA4AaAB+AMwA4AECAUIBbAGYAcICGAJYArQC4AMwA7AD3gQwBJYE3AUkBWYFigYgBmYGtAbqB1gIEghYCG4IhAi2COh4nGNgZGBgUGYoZWBnAAEmIOYCQgaG/2A+AwAYCQG2AHicXZBNaoNAGIZfE5PQCKFQ2lUps2oXBfOzzAESyDKBQJdGR2NQR3QSSE/QE/QEPUUPUHqsvsrXjTMw83zPvPMNCuAWP3DQDAejdm1GjzwS7pMmwi75XngAD4/CQ/oX4TFe4Qt7uMMbOzjuDc0EmXCP/C7cJ38Iu+RP4QEe8CU8pP8WHmOPX2EPz87TPo202ey2OjlnQSXV/6arOjWFmvszMWtd6CqwOlKHq6ovycLaWMWVydXKFFZnmVFlZU46tP7R2nI5ncbi/dDkfDtFBA2DDXbYkhKc+V0Bqs5Zt9JM1HQGBRTm/EezTmZNKtpcAMs9Yu6AK9caF76zoLWIWcfMGOSkVduvSWechqZsz040Ib2PY3urxBJTzriT95lipz+TN1fmAAAAeJxtkMl2wjAMRfOAhABlKm2h80C3+ajgCKKDY6cegP59TYBzukAL+z1Zsq8ctaJTTKPrsUQLbXQQI0EXKXroY4AbDDHCGBNMcYsZ7nCPB8yxwCOe8IwXvOIN7/jAJ76wxHfUqWX+OzgumWAjJMV17i0Ndlr6irLKO+qftdT7i6y4uFSUvCknay+lFYZIZaQcmfH/xIFdYn98bqhra1aKTM/6lWMnyaYirx1rFUQZFBkb2zJUtoXeJCeg0WnLtHeSFc3OtrnozNwqi0TkSpBMDB1nSde5oJXW23hTS2/T0LilglXX7dmFVxLnq5U0vYATHFk3zX3BOisoQHNDFDeZnqKDy9hRNawN7Vh727hFzcJ5c8TILrKZfH7tIPxAFP0BpLeJPA==) format("woff");font-weight:400;font-style:normal}.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.video-js .vjs-play-control .vjs-icon-placeholder,.vjs-icon-play{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.video-js .vjs-play-control .vjs-icon-placeholder:before,.vjs-icon-play:before{content:"\f101"}.vjs-icon-play-circle{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-play-circle:before{content:"\f102"}.video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder,.vjs-icon-pause{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder:before,.vjs-icon-pause:before{content:"\f103"}.video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder,.vjs-icon-volume-mute{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder:before,.vjs-icon-volume-mute:before{content:"\f104"}.video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder,.vjs-icon-volume-low{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder:before,.vjs-icon-volume-low:before{content:"\f105"}.video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder,.vjs-icon-volume-mid{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder:before,.vjs-icon-volume-mid:before{content:"\f106"}.video-js .vjs-mute-control .vjs-icon-placeholder,.vjs-icon-volume-high{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control .vjs-icon-placeholder:before,.vjs-icon-volume-high:before{content:"\f107"}.video-js .vjs-fullscreen-control .vjs-icon-placeholder,.vjs-icon-fullscreen-enter{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-fullscreen-control .vjs-icon-placeholder:before,.vjs-icon-fullscreen-enter:before{content:"\f108"}.video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder,.vjs-icon-fullscreen-exit{font-family:VideoJS;font-weight:400;font-style:normal}.video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder:before,.vjs-icon-fullscreen-exit:before{content:"\f109"}.vjs-icon-square{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-square:before{content:"\f10a"}.vjs-icon-spinner{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-spinner:before{content:"\f10b"}.video-js .vjs-subs-caps-button .vjs-icon-placeholder,.video-js .vjs-subtitles-button .vjs-icon-placeholder,.video-js.video-js:lang(en-AU) .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-NZ) .vjs-subs-caps-button .vjs-icon-placeholder,.vjs-icon-subtitles{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js .vjs-subtitles-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-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-NZ) .vjs-subs-caps-button .vjs-icon-placeholder:before,.vjs-icon-subtitles:before{content:"\f10c"}.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,.vjs-icon-captions{font-family:VideoJS;font-weight:400;font-style:normal}.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,.vjs-icon-captions:before{content:"\f10d"}.video-js .vjs-chapters-button .vjs-icon-placeholder,.vjs-icon-chapters{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-chapters-button .vjs-icon-placeholder:before,.vjs-icon-chapters:before{content:"\f10e"}.vjs-icon-share{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-share:before{content:"\f10f"}.vjs-icon-cog{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-cog:before{content:"\f110"}.video-js .vjs-play-progress,.video-js .vjs-volume-level,.vjs-icon-circle,.vjs-seek-to-live-control .vjs-icon-placeholder{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-play-progress:before,.video-js .vjs-volume-level:before,.vjs-icon-circle:before,.vjs-seek-to-live-control .vjs-icon-placeholder:before{content:"\f111"}.vjs-icon-circle-outline{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-circle-outline:before{content:"\f112"}.vjs-icon-circle-inner-circle{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-circle-inner-circle:before{content:"\f113"}.vjs-icon-hd{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-hd:before{content:"\f114"}.video-js .vjs-control.vjs-close-button .vjs-icon-placeholder,.vjs-icon-cancel{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-control.vjs-close-button .vjs-icon-placeholder:before,.vjs-icon-cancel:before{content:"\f115"}.video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder,.vjs-icon-replay{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder:before,.vjs-icon-replay:before{content:"\f116"}.vjs-icon-facebook{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-facebook:before{content:"\f117"}.vjs-icon-gplus{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-gplus:before{content:"\f118"}.vjs-icon-linkedin{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-linkedin:before{content:"\f119"}.vjs-icon-twitter{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-twitter:before{content:"\f11a"}.vjs-icon-tumblr{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-tumblr:before{content:"\f11b"}.vjs-icon-pinterest{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-pinterest:before{content:"\f11c"}.video-js .vjs-descriptions-button .vjs-icon-placeholder,.vjs-icon-audio-description{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-descriptions-button .vjs-icon-placeholder:before,.vjs-icon-audio-description:before{content:"\f11d"}.video-js .vjs-audio-button .vjs-icon-placeholder,.vjs-icon-audio{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-audio-button .vjs-icon-placeholder:before,.vjs-icon-audio:before{content:"\f11e"}.vjs-icon-next-item{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-next-item:before{content:"\f11f"}.vjs-icon-previous-item{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-previous-item:before{content:"\f120"}.video-js .vjs-picture-in-picture-control .vjs-icon-placeholder,.vjs-icon-picture-in-picture-enter{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-picture-in-picture-control .vjs-icon-placeholder:before,.vjs-icon-picture-in-picture-enter:before{content:"\f121"}.video-js.vjs-picture-in-picture .vjs-picture-in-picture-control .vjs-icon-placeholder,.vjs-icon-picture-in-picture-exit{font-family:VideoJS;font-weight:400;font-style:normal}.video-js.vjs-picture-in-picture .vjs-picture-in-picture-control .vjs-icon-placeholder:before,.vjs-icon-picture-in-picture-exit:before{content:"\f122"}.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:400;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:0}.video-js *,.video-js :after,.video-js :before{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-16-9,.video-js.vjs-4-3,.video-js.vjs-fluid{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:.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:0 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.63332em;width:3em;display:block;position:absolute;top:10px;left:10px;padding:0;cursor:pointer;opacity:1;border:.06666em solid #fff;background-color:#2b333f;background-color:rgba(43,51,63,.7);border-radius:.3em;transition:all .4s}.vjs-big-play-centered .vjs-big-play-button{top:50%;left:50%;margin-top:-.81666em;margin-left:-1.5em}.video-js .vjs-big-play-button:focus,.video-js:hover .vjs-big-play-button{border-color:#fff;background-color:#73859f;background-color:rgba(115,133,159,.5);transition:all 0s}.vjs-controls-disabled .vjs-big-play-button,.vjs-error .vjs-big-play-button,.vjs-has-started .vjs-big-play-button,.vjs-using-native-controls .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:0 0;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:.5em;z-index:2}.video-js .vjs-modal-dialog{background:rgba(0,0,0,.8);background:linear-gradient(180deg,rgba(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:.2em 0;line-height:1.4em;font-size:1.2em;text-align:center;text-transform:lowercase}.js-focus-visible .vjs-menu li.vjs-menu-item:hover,.vjs-menu li.vjs-menu-item:focus,.vjs-menu li.vjs-menu-item:hover{background-color:#73859f;background-color:rgba(115,133,159,.5)}.js-focus-visible .vjs-menu li.vjs-selected:hover,.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 .3em 0;font-weight:700;cursor:default}.vjs-menu-button-popup .vjs-menu{display:none;position:absolute;bottom:0;width:10em;left:-3em;height:0;margin-bottom:1.5em;border-top-color:rgba(43,51,63,.7)}.vjs-menu-button-popup .vjs-menu .vjs-menu-content{background-color:#2b333f;background-color:rgba(43,51,63,.7);position:absolute;width:100%;bottom:1.5em;max-height:15em}.vjs-layout-tiny .vjs-menu-button-popup .vjs-menu .vjs-menu-content,.vjs-layout-x-small .vjs-menu-button-popup .vjs-menu .vjs-menu-content{max-height:5em}.vjs-layout-small .vjs-menu-button-popup .vjs-menu .vjs-menu-content{max-height:10em}.vjs-layout-medium .vjs-menu-button-popup .vjs-menu .vjs-menu-content{max-height:14em}.vjs-layout-huge .vjs-menu-button-popup .vjs-menu .vjs-menu-content,.vjs-layout-large .vjs-menu-button-popup .vjs-menu .vjs-menu-content,.vjs-layout-x-large .vjs-menu-button-popup .vjs-menu .vjs-menu-content{max-height:25em}.vjs-menu-button-popup .vjs-menu.vjs-lock-showing,.vjs-workinghover .vjs-menu-button-popup:hover .vjs-menu{display:block}.video-js .vjs-menu-button-inline{transition:all .4s;overflow:hidden}.video-js .vjs-menu-button-inline:before{width:2.222222222em}.video-js .vjs-menu-button-inline.vjs-slider-active,.video-js .vjs-menu-button-inline:focus,.video-js .vjs-menu-button-inline:hover,.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;transition:all .4s}.vjs-menu-button-inline.vjs-slider-active .vjs-menu,.vjs-menu-button-inline:focus .vjs-menu,.vjs-menu-button-inline:hover .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.vjs-slider-active .vjs-menu,.vjs-no-flex .vjs-menu-button-inline:focus .vjs-menu,.vjs-no-flex .vjs-menu-button-inline:hover .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:3em;background-color:#2b333f;background-color:rgba(43,51,63,.7)}.vjs-has-started .vjs-control-bar{display:flex;visibility:visible;opacity:1;transition:visibility .1s,opacity .1s}.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{visibility:visible;opacity:0;transition:visibility 1s,opacity 1s}.vjs-controls-disabled .vjs-control-bar,.vjs-error .vjs-control-bar,.vjs-using-native-controls .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;flex:none}.vjs-button>.vjs-icon-placeholder:before{font-size:1.8em;line-height:1.67}.video-js .vjs-control:focus,.video-js .vjs-control:focus:before,.video-js .vjs-control:hover:before{text-shadow:0 0 1em #fff}.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;flex:auto;display:flex;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-liveui .vjs-progress-control{display:flex;align-items:center}.vjs-no-flex .vjs-progress-control{width:auto}.video-js .vjs-progress-holder{flex:auto;transition:all .2s;height:.3em}.video-js .vjs-progress-control .vjs-progress-holder{margin:0 10px}.video-js .vjs-progress-control:hover .vjs-progress-holder{font-size:1.6666666667em}.video-js .vjs-progress-control:hover .vjs-progress-holder.disabled{font-size:1em}.video-js .vjs-progress-holder .vjs-load-progress,.video-js .vjs-progress-holder .vjs-load-progress div,.video-js .vjs-progress-holder .vjs-play-progress{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:.9em;position:absolute;right:-.5em;top:-.3333333333em;z-index:1}.video-js .vjs-load-progress{background:rgba(115,133,159,.5)}.video-js .vjs-load-progress div{background:rgba(115,133,159,.75)}.video-js .vjs-time-tooltip{background-color:#fff;background-color:rgba(255,255,255,.8);border-radius:.3em;color:#000;float:right;font-family:Arial,Helvetica,sans-serif;font-size:1em;padding:6px 8px 8px 8px;pointer-events:none;position:absolute;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-progress-holder:focus .vjs-time-tooltip,.video-js .vjs-progress-control:hover .vjs-time-tooltip{display:block;font-size:.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;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,.8)}.video-js .vjs-slider{position:relative;cursor:pointer;padding:0;margin:0 .45em 0 .45em;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#73859f;background-color:rgba(115,133,159,.5)}.video-js .vjs-slider.disabled{cursor:default}.video-js .vjs-slider:focus{text-shadow:0 0 1em #fff;box-shadow:0 0 1em #fff}.video-js .vjs-mute-control{cursor:pointer;flex:none}.video-js .vjs-volume-control{cursor:pointer;margin-right:1em;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{transition:width 1s}.video-js .vjs-volume-panel .vjs-mute-control:hover~.vjs-volume-control,.video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active,.video-js .vjs-volume-panel .vjs-volume-control:active,.video-js .vjs-volume-panel .vjs-volume-control:hover,.video-js .vjs-volume-panel:active .vjs-volume-control,.video-js .vjs-volume-panel:focus .vjs-volume-control,.video-js .vjs-volume-panel:hover .vjs-volume-control{visibility:visible;opacity:1;position:relative;transition:visibility .1s,opacity .1s,height .1s,width .1s,left 0s,top 0s}.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,.video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-horizontal,.video-js .vjs-volume-panel .vjs-volume-control:hover.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:hover .vjs-volume-control.vjs-volume-horizontal{width:5em;height:3em}.video-js .vjs-volume-panel .vjs-mute-control:hover~.vjs-volume-control.vjs-volume-vertical,.video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical,.video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical,.video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-vertical,.video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-vertical,.video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-vertical,.video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-vertical{left:-3.5em}.video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,.video-js .vjs-volume-panel.vjs-volume-panel-horizontal:active,.video-js .vjs-volume-panel.vjs-volume-panel-horizontal:hover{width:9em;transition:width .1s}.video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-mute-toggle-only{width:4em}.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical{height:8em;width:3em;left:-3000em;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{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;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:.5em}.video-js .vjs-volume-panel{display:flex}.video-js .vjs-volume-bar{margin:1.35em .45em}.vjs-volume-bar.vjs-slider-horizontal{width:5em;height:.3em}.vjs-volume-bar.vjs-slider-vertical{width:.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:.9em}.vjs-slider-vertical .vjs-volume-level{width:.3em}.vjs-slider-vertical .vjs-volume-level:before{top:-.5em;left:-.3em}.vjs-slider-horizontal .vjs-volume-level{height:.3em}.vjs-slider-horizontal .vjs-volume-level:before{top:-.3em;right:-.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,.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:#000;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:flex;align-items:flex-start;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-liveui .vjs-live-control,.video-js:not(.vjs-live) .vjs-live-control{display:none}.video-js .vjs-seek-to-live-control{cursor:pointer;flex:none;display:inline-flex;height:100%;padding-left:.5em;padding-right:.5em;font-size:1em;line-height:3em;width:auto;min-width:4em}.vjs-no-flex .vjs-seek-to-live-control{display:table-cell;width:auto;text-align:left}.video-js.vjs-live:not(.vjs-liveui) .vjs-seek-to-live-control,.video-js:not(.vjs-live) .vjs-seek-to-live-control{display:none}.vjs-seek-to-live-control.vjs-control.vjs-at-live-edge{cursor:auto}.vjs-seek-to-live-control .vjs-icon-placeholder{margin-right:.5em;color:#888}.vjs-seek-to-live-control.vjs-control.vjs-at-live-edge .vjs-icon-placeholder{color:red}.video-js .vjs-time-control{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{cursor:pointer}.video-js .vjs-play-control .vjs-icon-placeholder{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:.1em}.vjs-subtitles{color:#fff}.vjs-captions{color:#fc6}.vjs-tt-cue{display:block}video::-webkit-media-text-track-display{transform:translateY(-3em)}.video-js.vjs-user-inactive.vjs-playing video::-webkit-media-text-track-display{transform:translateY(-1.5em)}.video-js .vjs-picture-in-picture-control{cursor:pointer;flex:none}.video-js .vjs-fullscreen-control{cursor:pointer;flex:none}.vjs-playback-rate .vjs-playback-rate-value,.vjs-playback-rate>.vjs-menu-button{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:0}.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:-.5em;position:absolute;text-shadow:.05em .05em .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:.85;text-align:left;border:6px solid rgba(43,51,63,.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;-webkit-animation:vjs-spinner-show 0s linear .3s forwards;animation:vjs-spinner-show 0s linear .3s forwards}.vjs-loading-spinner:after,.vjs-loading-spinner:before{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:#fff}.vjs-seeking .vjs-loading-spinner:after,.vjs-seeking .vjs-loading-spinner:before,.vjs-waiting .vjs-loading-spinner:after,.vjs-waiting .vjs-loading-spinner:before{-webkit-animation:vjs-spinner-spin 1.1s cubic-bezier(.6,.2,0,.8) infinite,vjs-spinner-fade 1.1s linear infinite;animation:vjs-spinner-spin 1.1s cubic-bezier(.6,.2,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:#fff}.vjs-seeking .vjs-loading-spinner:after,.vjs-waiting .vjs-loading-spinner:after{border-top-color:#fff;-webkit-animation-delay:.44s;animation-delay:.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:#fff}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:#fff}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:-.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:"";font-size:1.5em;line-height:inherit}.video-js .vjs-audio-button+.vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder{vertical-align:middle;display:inline-block;margin-bottom:-.1em}.video-js .vjs-audio-button+.vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before{font-family:VideoJS;content:" ";font-size:1.5em;line-height:inherit}.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-audio-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-captions-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-chapters-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-current-time,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-descriptions-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-duration,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-playback-rate,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-remaining-time,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-subtitles-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-time-divider,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-control,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-audio-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-captions-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-chapters-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-current-time,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-descriptions-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-duration,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-playback-rate,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-remaining-time,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-subtitles-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-time-divider,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-control,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-audio-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-captions-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-chapters-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-current-time,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-descriptions-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-duration,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-playback-rate,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-remaining-time,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-subtitles-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-time-divider,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-control{display:none}.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:active,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:active,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:hover,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:active,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover{width:auto;width:initial}.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-subs-caps-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small:not(.vjs-live) .vjs-subs-caps-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small:not(.vjs-liveui) .vjs-subs-caps-button{display:none}.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-custom-control-spacer,.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui .vjs-custom-control-spacer{flex:auto;display:block}.video-js:not(.vjs-fullscreen).vjs-layout-tiny.vjs-no-flex .vjs-custom-control-spacer,.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui.vjs-no-flex .vjs-custom-control-spacer{width:auto}.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-progress-control,.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui .vjs-progress-control{display:none}.vjs-modal-dialog.vjs-text-track-settings{background-color:#2b333f;background-color:rgba(43,51,63,.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-controls,.vjs-text-track-settings .vjs-track-settings-font{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;padding:20px 24px 0 24px}.vjs-track-settings-controls .vjs-default-button{margin-bottom:20px}.vjs-text-track-settings .vjs-track-settings-controls{grid-column:1/-1}.vjs-layout-small .vjs-text-track-settings .vjs-modal-dialog-content,.vjs-layout-tiny .vjs-text-track-settings .vjs-modal-dialog-content,.vjs-layout-x-small .vjs-text-track-settings .vjs-modal-dialog-content{grid-template-columns:1fr}}.vjs-track-setting>select{margin-right:1em;margin-bottom:.5em}.vjs-text-track-settings fieldset{margin:5px;padding:3px;border:none}.vjs-text-track-settings fieldset span{display:inline-block}.vjs-text-track-settings fieldset span>select{max-width:7.3em}.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:active,.vjs-track-settings-controls button:focus{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,.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;z-index:-1000}.js-focus-visible .video-js :focus:not(.focus-visible){outline:0;background:0 0}.video-js .vjs-menu :focus:not(:focus-visible),.video-js :focus:not(:focus-visible){outline:0;background:0 0} \ No newline at end of file diff --git a/static/video-js-7.6.0/alt/video.core.js b/static/video-js-7.6.0/alt/video.core.js new file mode 100644 index 0000000..165f3fb --- /dev/null +++ b/static/video-js-7.6.0/alt/video.core.js @@ -0,0 +1,29710 @@ +/** + * @license + * Video.js 7.6.0 + * 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(require('global/window'), require('global/document')) : + typeof define === 'function' && define.amd ? define(['global/window', 'global/document'], factory) : + (global = global || self, global.videojs = factory(global.window, global.document)); +}(this, function (window$1, document) { + window$1 = window$1 && window$1.hasOwnProperty('default') ? window$1['default'] : window$1; + document = document && document.hasOwnProperty('default') ? document['default'] : document; + + var version = "7.6.0"; + + function _inheritsLoose(subClass, superClass) { + subClass.prototype = Object.create(superClass.prototype); + subClass.prototype.constructor = subClass; + subClass.__proto__ = superClass; + } + + function _setPrototypeOf(o, p) { + _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { + o.__proto__ = p; + return o; + }; + + return _setPrototypeOf(o, p); + } + + function isNativeReflectConstruct() { + if (typeof Reflect === "undefined" || !Reflect.construct) return false; + if (Reflect.construct.sham) return false; + if (typeof Proxy === "function") return true; + + try { + Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); + return true; + } catch (e) { + return false; + } + } + + function _construct(Parent, args, Class) { + if (isNativeReflectConstruct()) { + _construct = Reflect.construct; + } else { + _construct = function _construct(Parent, args, Class) { + var a = [null]; + a.push.apply(a, args); + var Constructor = Function.bind.apply(Parent, a); + var instance = new Constructor(); + if (Class) _setPrototypeOf(instance, Class.prototype); + return instance; + }; + } + + return _construct.apply(null, arguments); + } + + function _assertThisInitialized(self) { + if (self === void 0) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return self; + } + + function _taggedTemplateLiteralLoose(strings, raw) { + if (!raw) { + raw = strings.slice(0); + } + + strings.raw = raw; + return strings; + } + + /** + * @file create-logger.js + * @module create-logger + */ + + 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 LogByTypeFactory = function LogByTypeFactory(name, log) { + return function (type, level, 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 console prefix after adding to history. + + + args.unshift(name + ':'); // Add a clone of the args at this point to history. + + if (history) { + history.push([].concat(args)); + } // 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); + }; + }; + + function createLogger(name) { + // This is the private tracking variable for logging level. + var level = 'info'; // the curried logByType bound to the specific log and history + + var logByType; + /** + * Logs plain debug messages. Similar to `console.log`. + * + * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) + * of our JSDoc template, we cannot properly document this as both a function + * and a namespace, so its function signature is documented here. + * + * #### Arguments + * ##### *args + * Mixed[] + * + * Any combination of values that could be passed to `console.log()`. + * + * #### Return Value + * + * `undefined` + * + * @namespace + * @param {Mixed[]} args + * One or more messages or objects that should be logged. + */ + + var log = function log() { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + logByType('log', level, args); + }; // This is the logByType helper that the logging methods below use + + + logByType = LogByTypeFactory(name, log); + /** + * Create a new sublogger which chains the old name to the new name. + * + * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following: + * ```js + * mylogger('foo'); + * // > VIDEOJS: player: foo + * ``` + * + * @param {string} name + * The name to add call the new logger + * @return {Object} + */ + + log.createLogger = function (subname) { + return createLogger(name + ': ' + subname); + }; + /** + * 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 module:log.levels} is provided, acts + * as a setter. + * + * @param {string} [lvl] + * Pass a valid level 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) : []; + }; + /** + * Allows you to filter the history by the given logger name + * + * @param {string} fname + * The name to filter by + * + * @return {Array} + * The filtered list to return + */ + + + log.history.filter = function (fname) { + return (history || []).filter(function (historyItem) { + // if the first item in each historyItem includes `fname`, then it's a match + return new RegExp(".*" + fname + ".*").test(historyItem[0]); + }); + }; + /** + * 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 = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + return logByType('error', level, 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 = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + args[_key3] = arguments[_key3]; + } + + return logByType('warn', level, 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 = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { + args[_key4] = arguments[_key4]; + } + + return logByType('debug', level, args); + }; + + return log; + } + + /** + * @file log.js + * @module log + */ + var log = createLogger('VIDEOJS'); + var createLogger$1 = log.createLogger; + + 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; + }; + + /** + * @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, initial) { + if (initial === void 0) { + initial = 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 = new 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 === '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. + * + * @function + * @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 + */ + + 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 ''; + } + + function _templateObject() { + var data = _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 ", "."]); + + _templateObject = function _templateObject() { + return data; + }; + + return data; + } + /** + * Detect if a value is a string with any non-whitespace characters. + * + * @private + * @param {string} str + * The string to check + * + * @return {boolean} + * Will be `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. + * + * @private + * @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. + * + * @private + * @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 (i.e. not simulated). + * + * @return {boolean} + * Will be `true` if the DOM appears to be real, `false` otherwise. + */ + + + function isReal() { + // Both document and window will never be undefined thanks to `global`. + return document === window$1.document; + } + /** + * Determines, via duck typing, whether or not a value is a DOM element. + * + * @param {Mixed} value + * The value to check. + * + * @return {boolean} + * Will be `true` if the value 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} + * Will be `true` if the DOM is embedded in an iframe, `false` + * otherwise. + */ + + 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. + * + * @private + * @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[method](null); + } + + if (isNonBlankString(context)) { + context = document.querySelector(context); + } + + var ctx = isEl(context) ? context : document; + return ctx[method] && ctx[method](selector); + }; + } + /** + * Creates an element and applies properties, attributes, and inserts content. + * + * @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 {module:dom~ContentDescriptor} content + * A content descriptor object. + * + * @return {Element} + * The element that was created. + */ + + + function createEl(tagName, properties, attributes, content) { + if (tagName === void 0) { + tagName = 'div'; + } + + if (properties === void 0) { + properties = {}; + } + + if (attributes === void 0) { + attributes = {}; + } + + var el = document.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.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 class name. + * + * @param {Element} element + * Element to check + * + * @param {string} classToCheck + * Class name to check for + * + * @return {boolean} + * Will be `true` if the element has a 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 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 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 toggleClass. + * + * @callback module: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` is returned, the `classToToggle` will be added to the + * `element`. If `false`, the `classToToggle` will be removed from + * the `element`. If `undefined`, the callback will be ignored. + */ + + /** + * Adds or removes a class name to/from 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|module:dom~PredicateCallback} [predicate] + * See the return value for {@link module: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. + * + * @param {Element} tag + * Element from which to get tag attributes. + * + * @return {Object} + * All attributes of the element. Boolean attributes will be `true` or + * `false`, others will be strings. + */ + + 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} + * The 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. + */ + + function blockTextSelection() { + document.body.focus(); + + document.onselectstart = function () { + return false; + }; + } + /** + * Turn off text selection blocking. + */ + + function unblockTextSelection() { + document.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 object - or `undefined` if it cannot. + */ + + 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; + } + } + /** + * Represents the position of a DOM element on the page. + * + * @typedef {Object} module:dom~Position + * + * @property {number} left + * Pixels to the left. + * + * @property {number} top + * Pixels from the top. + */ + + /** + * Get the position of an element in the DOM. + * + * Uses `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; + + if (el.getBoundingClientRect && el.parentNode) { + box = el.getBoundingClientRect(); + } + + if (!box) { + return { + left: 0, + top: 0 + }; + } + + var docEl = document.documentElement; + var body = document.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) + }; + } + /** + * Represents x and y coordinates for a DOM element or mouse pointer. + * + * @typedef {Object} module:dom~Coordinates + * + * @property {number} x + * x coordinate in pixels + * + * @property {number} y + * y coordinate in pixels + */ + + /** + * Get the pointer position within an element. + * + * 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 {module: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} + * Will be `true` if the value 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; + } + /** + * This is a mixed value that describes content to be injected into the DOM + * via some method. It can be of the following types: + * + * Type | Description + * -----------|------------- + * `string` | The value will be normalized into a text node. + * `Element` | The value will be accepted as-is. + * `TextNode` | The value will be accepted as-is. + * `Array` | A one-dimensional array of strings, elements, text nodes, or functions. These functions should return a string, element, or text node (any other return value, like an array, will be ignored). + * `Function` | A function, which is expected to return a string, element, text node, or array - any of the other possible values described above. This means that a content descriptor could be a function that returns an array of functions, but those second-level functions must return strings, elements, or text nodes. + * + * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor + */ + + /** + * Normalizes content for eventual insertion into the DOM. + * + * This allows a wide range of content definition methods, but helps protect + * from falling into the trap of simply writing to `innerHTML`, which could + * be an XSS concern. + * + * The content for an element can be passed in multiple types and + * combinations, whose behavior is as follows: + * + * @param {module:dom~ContentDescriptor} content + * A content descriptor value. + * + * @return {Array} + * All of the content that was passed in, normalized to an array of + * elements or text nodes. + */ + + 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.createTextNode(value); + } + }).filter(function (value) { + return value; + }); + } + /** + * Normalizes and appends content to an element. + * + * @param {Element} el + * Element to append normalized content to. + * + * @param {module:dom~ContentDescriptor} content + * A content descriptor value. + * + * @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 {module:dom~ContentDescriptor} content + * A content descriptor value. + * + * @return {Element} + * The element with inserted normalized content. + */ + + function insertContent(el, content) { + return appendContent(emptyEl(el), content); + } + /** + * Check if an event was a single left click. + * + * @param {EventTarget~Event} event + * Event object. + * + * @return {boolean} + * Will be `true` if a single left click, `false` otherwise. + */ + + 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; + } // `mouseup` event on a single left click has + // `button` and `buttons` equal to 0 + + + if (event.button === 0 && event.buttons === 0) { + 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' + Math.floor(window$1.performance && window$1.performance.now() || Date.now()); + /** + * 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. + * + * @file events.js + * @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; + } // 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.documentElement; + var body = document.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.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 && event.target && event.target[event.type]) { + 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); + } + /** + * Trigger a listener only once and then turn if off for all + * configured events + * + * @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 any(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(); // multiple ons, but one off for everything + + on(elem, type, func); + } + + var Events = /*#__PURE__*/Object.freeze({ + fixEvent: fixEvent, + on: on, + off: off, + trigger: trigger, + one: one, + any: any + }); + + /** + * @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; + /** + * 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.getElementsByTagName('video')); + var audios = Array.prototype.slice.call(document.getElementsByTagName('audio')); + var divs = Array.prototype.slice.call(document.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.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.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. + * + * @function + * @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. + * + * @function + * @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 = window$1.performance.now(); + + var throttled = function throttled() { + var now = window$1.performance.now(); + + if (now - last >= wait) { + fn.apply(void 0, 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. + * + * @function + * @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, context) { + if (context === void 0) { + context = window$1; + } + + var timeout; + + var cancel = function cancel() { + context.clearTimeout(timeout); + timeout = null; + }; + /* eslint-disable consistent-this */ + + + var debounced = function debounced() { + 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 */ + + + debounced.cancel = cancel; + return debounced; + }; + + /** + * @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 aliasing 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; + }; + + EventTarget.prototype.any = function (type, fn) { + // Remove the addEventListener aliasing Events.on + // so we don't get into an infinite type loop + var ael = this.addEventListener; + + this.addEventListener = function () {}; + + any(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; // deprecation + // In a future version we should default target to `this` + // similar to how we default the target to `elem` in + // `Events.trigger`. Right now the default `target` will be + // `document` due to the `Event.fixEvent` call. + + 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; + var EVENT_MAP; + + EventTarget.prototype.queueTrigger = function (event) { + var _this = this; + + // only set up EVENT_MAP if it'll be used + if (!EVENT_MAP) { + EVENT_MAP = new Map(); + } + + var type = event.type || event; + var map = EVENT_MAP.get(this); + + if (!map) { + map = new Map(); + EVENT_MAP.set(this, map); + } + + var oldTimeout = map.get(type); + map["delete"](type); + window$1.clearTimeout(oldTimeout); + var timeout = window$1.setTimeout(function () { + // if we cleared out all timeouts for the current target, delete its map + if (map.size === 0) { + map = null; + EVENT_MAP["delete"](_this); + } + + _this.trigger(event); + }, 0); + map.set(type, timeout); + }; + + /** + * @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'; + }); + }; + /** + * Adds a callback to run after the evented mixin applied. + * + * @param {Object} object + * An object to Add + * @param {Function} callback + * The callback to run. + */ + + + var addEventedCallback = function addEventedCallback(target, callback) { + if (isEvented(target)) { + callback(); + } else { + if (!target.eventedCallbacks) { + target.eventedCallbacks = []; + } + + target.eventedCallbacks.push(callback); + } + }; + /** + * 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; + var type; + var listener; + + 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() { + var _this = this; + + for (var _len = arguments.length, args = new 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 be called once per event 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() { + var _this2 = this; + + for (var _len2 = arguments.length, args = new 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 { + // TODO: This wrapper is incorrect! It should only + // remove the wrapper for the event type that called it. + // Instead all listners are removed on the first trigger! + // see https://github.com/videojs/video.js/issues/5962 + var wrapper = function wrapper() { + _this2.off(target, type, wrapper); + + for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + largs[_key3] = arguments[_key3]; + } + + 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); + } + }, + + /** + * Add a listener to an event (or events) on this object or another evented + * object. The listener will only be called once for the first event that is triggered + * 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. + */ + any: function any() { + var _this3 = this; + + for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { + args[_key4] = arguments[_key4]; + } + + var _normalizeListenArgs3 = normalizeListenArgs(this, args), + isTargetingSelf = _normalizeListenArgs3.isTargetingSelf, + target = _normalizeListenArgs3.target, + type = _normalizeListenArgs3.type, + listener = _normalizeListenArgs3.listener; // Targeting this evented object. + + + if (isTargetingSelf) { + listen(target, 'any', type, listener); // Targeting another evented object. + } else { + var wrapper = function wrapper() { + _this3.off(target, type, wrapper); + + for (var _len5 = arguments.length, largs = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { + largs[_key5] = arguments[_key5]; + } + + 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, 'any', 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. + * + * @return {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, options) { + if (options === void 0) { + options = {}; + } + + var _options = options, + 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); + + if (target.eventedCallbacks) { + target.eventedCallbacks.forEach(function (callback) { + callback(); + }); + } // 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. + * + * @return {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; + 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. + * + * @return {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 + */ + /** + * Merge two objects recursively. + * + * Performs a deep merge like + * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges + * plain objects (not arrays, elements, or anything else). + * + * Non-plain object values will be copied directly from the right-most + * argument. + * + * @static + * @param {Object[]} sources + * One or more objects to merge into a new object. + * + * @return {Object} + * A new object that is the merged result of all sources. + */ + + function mergeOptions() { + var result = {}; + + for (var _len = arguments.length, sources = new 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 = + /*#__PURE__*/ + 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) { + // 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; + } // Hold the reference to the parent component via `addChild` method + + + this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults + + this.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 + */ + + + var _proto = Component.prototype; + + _proto.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; + this.parentComponent_ = 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. + */ + ; + + _proto.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. + */ + ; + + _proto.options = function options(obj) { + 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`. + */ + ; + + _proto.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. + */ + ; + + _proto.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. + */ + ; + + _proto.localize = function localize(string, tokens, defaultValue) { + if (defaultValue === void 0) { + defaultValue = 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`. + */ + ; + + _proto.contentEl = function contentEl() { + return this.contentEl_ || this.el_; + } + /** + * Get this `Component`s ID + * + * @return {string} + * The id of this `Component` + */ + ; + + _proto.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`. + */ + ; + + _proto.name = function name() { + return this.name_; + } + /** + * Get an array of all child components + * + * @return {Array} + * The children + */ + ; + + _proto.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. + */ + ; + + _proto.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. + */ + ; + + _proto.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. + */ + ; + + _proto.addChild = function addChild(child, options, index) { + if (options === void 0) { + options = {}; + } + + if (index === void 0) { + index = this.children_.length; + } + + var component; + var componentName; // 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; + } + + if (component.parentComponent_) { + component.parentComponent_.removeChild(component); + } + + this.children_.splice(index, 0, component); + component.parentComponent_ = this; + + 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. + */ + ; + + _proto.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; + } + + component.parentComponent_ = null; + 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. + */ + ; + + _proto.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; + 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; + var opts; + + 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 + */ + ; + + _proto.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. + */ + ; + + _proto.ready = function ready(fn, sync) { + if (sync === void 0) { + sync = 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 + */ + ; + + _proto.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) + */ + ; + + _proto.$ = 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) + */ + ; + + _proto.$$ = 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` + */ + ; + + _proto.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 + */ + ; + + _proto.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 + */ + ; + + _proto.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 + */ + ; + + _proto.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. + */ + ; + + _proto.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. + */ + ; + + _proto.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 + */ + ; + + _proto.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 + */ + ; + + _proto.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} + */ + ; + + _proto.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} + */ + ; + + _proto.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} + */ + ; + + _proto.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'. + */ + ; + + _proto.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'. + */ + ; + + _proto.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. + */ + ; + + _proto.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 + */ + ; + + _proto.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 computed width or the height of the component's element. + * + * 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. + */ + ; + + _proto.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 computed width and height values of the + * component's element. + * + * Uses `window.getComputedStyle`. + * + * @return {Component~DimensionObject} + * The computed dimensions of the component's element. + */ + ; + + _proto.currentDimensions = function currentDimensions() { + return { + width: this.currentDimension('width'), + height: this.currentDimension('height') + }; + } + /** + * Get the computed width of the component's element. + * + * Uses `window.getComputedStyle`. + * + * @return {number} + * The computed width of the component's element. + */ + ; + + _proto.currentWidth = function currentWidth() { + return this.currentDimension('width'); + } + /** + * Get the computed height of the component's element. + * + * Uses `window.getComputedStyle`. + * + * @return {number} + * The computed height of the component's element. + */ + ; + + _proto.currentHeight = function currentHeight() { + return this.currentDimension('height'); + } + /** + * Set the focus to this component + */ + ; + + _proto.focus = function focus() { + this.el_.focus(); + } + /** + * Remove the focus from this component + */ + ; + + _proto.blur = function blur() { + this.el_.blur(); + } + /** + * When this Component receives a `keydown` event which it does not process, + * it passes the event to the Player for handling. + * + * @param {EventTarget~Event} event + * The `keydown` event that caused this function to be called. + */ + ; + + _proto.handleKeyDown = function handleKeyDown(event) { + if (this.player_) { + // We only stop propagation here because we want unhandled events to fall + // back to the browser. + event.stopPropagation(); + this.player_.handleKeyDown(event); + } + } + /** + * Many components used to have a `handleKeyPress` method, which was poorly + * named because it listened to a `keydown` event. This method name now + * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress` + * will not see their method calls stop working. + * + * @param {EventTarget~Event} event + * The event that caused this function to be called. + */ + ; + + _proto.handleKeyPress = function handleKeyPress(event) { + this.handleKeyDown(event); + } + /** + * 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 + */ + ; + + _proto.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; + 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 = window$1.performance.now(); // 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 = window$1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap + + if (touchTime < touchTimeThreshold) { + // Don't let browser turn this into a click + 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 + */ + ; + + _proto.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; + 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} + */ + ; + + _proto.setTimeout = function setTimeout(fn, timeout) { + var _this2 = this; + + // declare as variables so they are properly available in timeout function + // eslint-disable-next-line + var timeoutId, disposeFn; + fn = bind(this, fn); + timeoutId = window$1.setTimeout(function () { + _this2.off('dispose', disposeFn); + + fn(); + }, timeout); + + 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} + */ + ; + + _proto.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} + */ + ; + + _proto.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} + */ + ; + + _proto.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} + */ + ; + + _proto.requestAnimationFrame = function requestAnimationFrame(fn) { + var _this4 = this; + + // declare as variables so they are properly available in rAF function + // eslint-disable-next-line + var id, disposeFn; + + if (this.supportsRaf_) { + fn = bind(this, fn); + id = window$1.requestAnimationFrame(function () { + _this4.off('dispose', disposeFn); + + fn(); + }); + + 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} + */ + ; + + _proto.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; + + 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; + /** + * Whether or not this device is an iPad. + * + * @static + * @const + * @type {Boolean} + */ + + var IS_IPAD = /iPad/i.test(USER_AGENT); + /** + * Whether or not this device is an iPhone. + * + * @static + * @const + * @type {Boolean} + */ + // The Facebook app's UIWebView identifies as both an iPhone and iPad, so + // to identify iPhones, we need to exclude iPads. + // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/ + + var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD; + /** + * Whether or not this device is an iPod. + * + * @static + * @const + * @type {Boolean} + */ + + var IS_IPOD = /iPod/i.test(USER_AGENT); + /** + * Whether or not this is an iOS device. + * + * @static + * @const + * @type {Boolean} + */ + + var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD; + /** + * The detected iOS version - or `null`. + * + * @static + * @const + * @type {string|null} + */ + + var IOS_VERSION = function () { + var match = USER_AGENT.match(/OS (\d+)_/i); + + if (match && match[1]) { + return match[1]; + } + + return null; + }(); + /** + * Whether or not this is an Android device. + * + * @static + * @const + * @type {Boolean} + */ + + var IS_ANDROID = /Android/i.test(USER_AGENT); + /** + * The detected Android version - or `null`. + * + * @static + * @const + * @type {number|string|null} + */ + + 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; + }(); + /** + * Whether or not this is a native Android browser. + * + * @static + * @const + * @type {Boolean} + */ + + var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537; + /** + * Whether or not this is Mozilla Firefox. + * + * @static + * @const + * @type {Boolean} + */ + + var IS_FIREFOX = /Firefox/i.test(USER_AGENT); + /** + * Whether or not this is Microsoft Edge. + * + * @static + * @const + * @type {Boolean} + */ + + var IS_EDGE = /Edge/i.test(USER_AGENT); + /** + * Whether or not this is Google Chrome. + * + * This will also be `true` for Chrome on iOS, which will have different support + * as it is actually Safari under the hood. + * + * @static + * @const + * @type {Boolean} + */ + + var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT)); + /** + * The detected Google Chrome version - or `null`. + * + * @static + * @const + * @type {number|null} + */ + + var CHROME_VERSION = function () { + var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/); + + if (match && match[2]) { + return parseFloat(match[2]); + } + + return null; + }(); + /** + * The detected Internet Explorer version - or `null`. + * + * @static + * @const + * @type {number|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; + }(); + /** + * Whether or not this is desktop Safari. + * + * @static + * @const + * @type {Boolean} + */ + + var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE; + /** + * Whether or not this is any flavor of Safari - including iOS. + * + * @static + * @const + * @type {Boolean} + */ + + var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME; + /** + * Whether or not this is a Windows machine. + * + * @static + * @const + * @type {Boolean} + */ + + var IS_WINDOWS = /Windows/i.test(USER_AGENT); + /** + * Whether or not this device is touch-enabled. + * + * @static + * @const + * @type {Boolean} + */ + + var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch); + + var 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, + IS_WINDOWS: IS_WINDOWS, + 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. + * + * @typedef {Function} TimeRangeIndex + * + * @param {number} [index=0] + * The range number to return the time for. + * + * @return {number} + * The time offset at the specified index. + * + * @deprecated The index argument must be provided. + * In the future, leaving it out will throw an error. + */ + + /** + * An object that contains ranges of time. + * + * @typedef {Object} TimeRange + * + * @property {number} length + * The number of time ranges represented by this object. + * + * @property {module:time-ranges~TimeRangeIndex} start + * Returns the time offset at which a specified time range begins. + * + * @property {module:time-ranges~TimeRangeIndex} 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. + * + * @private + * @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. + * + * @private + * @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. + * + * @deprecated 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. + * + * @private + * @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) + }; + } + /** + * Create a `TimeRange` object which mimics an + * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}. + * + * @param {number|Array[]} start + * The start of a single range (a number) or an array of ranges (an + * array of arrays of two numbers each). + * + * @param {number} end + * The end of a single range. Cannot be used with the array form of + * the `start` argument. + */ + + + 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; + var end; + + 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 = { + prefixed: true + }; // browser API methods + + var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit + ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla + ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft + ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']]; + var specApi = apiMap[0]; + var browserApi; // determine the supported set of functions + + for (var i = 0; i < apiMap.length; i++) { + // check for exitFullscreen function + if (apiMap[i][1] in document) { + 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]; + } + + FullscreenApi.prefixed = browserApi[0] !== specApi[0]; + } + + /** + * @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_ABORTED + * @property {string} 2 - MEDIA_ERR_NETWORK + * @property {string} 3 - MEDIA_ERR_DECODE + * @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; + } // jsdocs for instance/static members added above + + 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_ + }; + + function createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; + } + + var keycode = createCommonjsModule(function (module, exports) { + // Source: http://jsfiddle.net/vWx8V/ + // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes + + /** + * Conenience method returns corresponding value for given keyName or keyCode. + * + * @param {Mixed} keyCode {Number} or keyName {String} + * @return {Mixed} + * @api public + */ + function keyCode(searchInput) { + // Keyboard Events + if (searchInput && 'object' === typeof searchInput) { + var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode; + if (hasKeyCode) searchInput = hasKeyCode; + } // Numbers + + + if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string) + + var search = String(searchInput); // check codes + + var foundNamedKey = codes[search.toLowerCase()]; + if (foundNamedKey) return foundNamedKey; // check aliases + + var foundNamedKey = aliases[search.toLowerCase()]; + if (foundNamedKey) return foundNamedKey; // weird character? + + if (search.length === 1) return search.charCodeAt(0); + return undefined; + } + /** + * Compares a keyboard event with a given keyCode or keyName. + * + * @param {Event} event Keyboard event that should be tested + * @param {Mixed} keyCode {Number} or keyName {String} + * @return {Boolean} + * @api public + */ + + + keyCode.isEventKey = function isEventKey(event, nameOrCode) { + if (event && 'object' === typeof event) { + var keyCode = event.which || event.keyCode || event.charCode; + + if (keyCode === null || keyCode === undefined) { + return false; + } + + if (typeof nameOrCode === 'string') { + // check codes + var foundNamedKey = codes[nameOrCode.toLowerCase()]; + + if (foundNamedKey) { + return foundNamedKey === keyCode; + } // check aliases + + + var foundNamedKey = aliases[nameOrCode.toLowerCase()]; + + if (foundNamedKey) { + return foundNamedKey === keyCode; + } + } else if (typeof nameOrCode === 'number') { + return nameOrCode === keyCode; + } + + return false; + } + }; + + exports = module.exports = keyCode; + /** + * Get by name + * + * exports.code['enter'] // => 13 + */ + + var codes = exports.code = exports.codes = { + 'backspace': 8, + 'tab': 9, + 'enter': 13, + 'shift': 16, + 'ctrl': 17, + 'alt': 18, + 'pause/break': 19, + 'caps lock': 20, + 'esc': 27, + 'space': 32, + 'page up': 33, + 'page down': 34, + 'end': 35, + 'home': 36, + 'left': 37, + 'up': 38, + 'right': 39, + 'down': 40, + 'insert': 45, + 'delete': 46, + 'command': 91, + 'left command': 91, + 'right command': 93, + 'numpad *': 106, + 'numpad +': 107, + 'numpad -': 109, + 'numpad .': 110, + 'numpad /': 111, + 'num lock': 144, + 'scroll lock': 145, + 'my computer': 182, + 'my calculator': 183, + ';': 186, + '=': 187, + ',': 188, + '-': 189, + '.': 190, + '/': 191, + '`': 192, + '[': 219, + '\\': 220, + ']': 221, + "'": 222 // Helper aliases + + }; + var aliases = exports.aliases = { + 'windows': 91, + '⇧': 16, + '⌥': 18, + '⌃': 17, + '⌘': 91, + 'ctl': 17, + 'control': 17, + 'option': 18, + 'pause': 19, + 'break': 19, + 'caps': 20, + 'return': 13, + 'escape': 27, + 'spc': 32, + 'spacebar': 32, + 'pgup': 33, + 'pgdn': 34, + 'ins': 45, + 'del': 46, + 'cmd': 91 + /*! + * Programatically add the following + */ + // lower case chars + + }; + + for (i = 97; i < 123; i++) { + codes[String.fromCharCode(i)] = i - 32; + } // numbers + + + for (var i = 48; i < 58; i++) { + codes[i - 48] = i; + } // function keys + + + for (i = 1; i < 13; i++) { + codes['f' + i] = i + 111; + } // numpad keys + + + for (i = 0; i < 10; i++) { + codes['numpad ' + i] = i + 96; + } + /** + * Get by code + * + * exports.name[13] // => 'Enter' + */ + + + var names = exports.names = exports.title = {}; // title for backward compat + // Create reverse mapping + + for (i in codes) { + names[codes[i]] = i; + } // Add aliases + + + for (var alias in aliases) { + codes[alias] = aliases[alias]; + } + }); + var keycode_1 = keycode.code; + var keycode_2 = keycode.codes; + var keycode_3 = keycode.aliases; + var keycode_4 = keycode.names; + var keycode_5 = keycode.title; + + var MODAL_CLASS_NAME = 'vjs-modal-dialog'; + /** + * 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 = + /*#__PURE__*/ + function (_Component) { + _inheritsLoose(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.pauseOnOpen=true] + * If `true`, playback will will be paused if playing when + * the modal opens, and resumed when it closes. + * + * @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) { + var _this; + + _this = _Component.call(this, player, options) || this; + _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. + */ + + + var _proto = ModalDialog.prototype; + + _proto.createEl = function createEl() { + 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' + }); + }; + + _proto.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. + */ + ; + + _proto.buildCSSClass = function buildCSSClass() { + return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this); + } + /** + * Returns the label string for this modal. Primarily used for accessibility. + * + * @return {string} + * the localized or raw label of this modal. + */ + ; + + _proto.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. + */ + ; + + _proto.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 + */ + ; + + _proto.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(); + } + + this.on('keydown', this.handleKeyDown); // 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 + */ + ; + + _proto.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 + */ + ; + + _proto.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(); + } + + this.off('keydown', this.handleKeyDown); + + 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. + */ + ; + + _proto.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. + */ + ; + + _proto.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. + */ + ; + + _proto.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 + */ + ; + + _proto.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 + */ + ; + + _proto.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 + */ + ; + + _proto.conditionalFocus_ = function conditionalFocus_() { + var activeEl = document.activeElement; + var playerEl = this.player_.el_; + this.previouslyActiveEl_ = null; + + if (playerEl.contains(activeEl) || playerEl === activeEl) { + this.previouslyActiveEl_ = activeEl; + this.focus(); + } + } + /** + * conditionally blur the element and refocus the last focused element + * + * @private + */ + ; + + _proto.conditionalBlur_ = function conditionalBlur_() { + if (this.previouslyActiveEl_) { + this.previouslyActiveEl_.focus(); + this.previouslyActiveEl_ = null; + } + } + /** + * Keydown handler. Attached when modal is focused. + * + * @listens keydown + */ + ; + + _proto.handleKeyDown = function handleKeyDown(event) { + // Do not allow keydowns to reach out of the modal dialog. + event.stopPropagation(); + + if (keycode.isEventKey(event, 'Escape') && this.closeable()) { + event.preventDefault(); + this.close(); + return; + } // exit early if it isn't a tab key + + + if (!keycode.isEventKey(event, 'Tab')) { + return; + } + + var focusableEls = this.focusableEls_(); + var activeEl = this.el_.querySelector(':focus'); + var focusIndex; + + for (var i = 0; i < focusableEls.length; i++) { + if (activeEl === focusableEls[i]) { + focusIndex = i; + break; + } + } + + if (document.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 + */ + ; + + _proto.focusableEls_ = function focusableEls_() { + var allChildren = this.el_.querySelectorAll('*'); + return Array.prototype.filter.call(allChildren, function (child) { + return (child instanceof window$1.HTMLAnchorElement || child instanceof window$1.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window$1.HTMLInputElement || child instanceof window$1.HTMLSelectElement || child instanceof window$1.HTMLTextAreaElement || child instanceof window$1.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window$1.HTMLIFrameElement || child instanceof window$1.HTMLObjectElement || child instanceof window$1.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable'); + }); + }; + + return ModalDialog; + }(Component); + /** + * Default options for `ModalDialog` default options. + * + * @type {Object} + * @private + */ + + + ModalDialog.prototype.options_ = { + pauseOnOpen: true, + temporary: true + }; + Component.registerComponent('ModalDialog', ModalDialog); + + /** + * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and + * {@link VideoTrackList} + * + * @extends EventTarget + */ + + var TrackList = + /*#__PURE__*/ + function (_EventTarget) { + _inheritsLoose(TrackList, _EventTarget); + + /** + * Create an instance of this class + * + * @param {Track[]} tracks + * A list of tracks to initialize the list with. + * + * @abstract + */ + function TrackList(tracks) { + var _this; + + if (tracks === void 0) { + tracks = []; + } + + _this = _EventTarget.call(this) || this; + _this.tracks_ = []; + /** + * @memberof TrackList + * @member {number} length + * The current number of `Track`s in the this Trackist. + * @instance + */ + + Object.defineProperty(_assertThisInitialized(_this), 'length', { + get: function get() { + 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 + */ + + + var _proto = TrackList.prototype; + + _proto.addTrack = function addTrack(track) { + var index = this.tracks_.length; + + if (!('' + index in this)) { + Object.defineProperty(this, index, { + get: function get() { + 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', + target: this + }); + } + } + /** + * Remove a {@link Track} from the `TrackList` + * + * @param {Track} rtrack + * The audio, video, or text track to remove from the list. + * + * @fires TrackList#removetrack + */ + ; + + _proto.removeTrack = function removeTrack(rtrack) { + var track; + + 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', + target: this + }); + } + /** + * 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 + */ + ; + + _proto.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; + } + + /** + * 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 = + /*#__PURE__*/ + function (_TrackList) { + _inheritsLoose(AudioTrackList, _TrackList); + + /** + * Create an instance of this class. + * + * @param {AudioTrack[]} [tracks=[]] + * A list of `AudioTrack` to instantiate the list with. + */ + function AudioTrackList(tracks) { + var _this; + + if (tracks === void 0) { + tracks = []; + } + + // 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; + } + } + + _this = _TrackList.call(this, tracks) || this; + _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 + */ + + + var _proto = AudioTrackList.prototype; + + _proto.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; + } + + track.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'); + }; + /** + * @listens AudioTrack#enabledchange + * @fires TrackList#change + */ + + + track.addEventListener('enabledchange', track.enabledChange_); + }; + + _proto.removeTrack = function removeTrack(rtrack) { + _TrackList.prototype.removeTrack.call(this, rtrack); + + if (rtrack.removeEventListener && rtrack.enabledChange_) { + rtrack.removeEventListener('enabledchange', rtrack.enabledChange_); + rtrack.enabledChange_ = null; + } + }; + + return AudioTrackList; + }(TrackList); + + /** + * 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 = + /*#__PURE__*/ + function (_TrackList) { + _inheritsLoose(VideoTrackList, _TrackList); + + /** + * Create an instance of this class. + * + * @param {VideoTrack[]} [tracks=[]] + * A list of `VideoTrack` to instantiate the list with. + */ + function VideoTrackList(tracks) { + var _this; + + if (tracks === void 0) { + tracks = []; + } + + // 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; + } + } + + _this = _TrackList.call(this, tracks) || this; + _this.changing_ = false; + /** + * @member {number} VideoTrackList#selectedIndex + * The current index of the selected {@link VideoTrack`}. + */ + + Object.defineProperty(_assertThisInitialized(_this), 'selectedIndex', { + get: function get() { + for (var _i = 0; _i < this.length; _i++) { + if (this[_i].selected) { + return _i; + } + } + + return -1; + }, + set: function set() {} + }); + return _this; + } + /** + * Add a {@link VideoTrack} to the `VideoTrackList`. + * + * @param {VideoTrack} track + * The VideoTrack to add to the list + * + * @fires TrackList#addtrack + */ + + + var _proto = VideoTrackList.prototype; + + _proto.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; + } + + track.selectedChange_ = function () { + if (_this2.changing_) { + return; + } + + _this2.changing_ = true; + disableOthers$1(_this2, track); + _this2.changing_ = false; + + _this2.trigger('change'); + }; + /** + * @listens VideoTrack#selectedchange + * @fires TrackList#change + */ + + + track.addEventListener('selectedchange', track.selectedChange_); + }; + + _proto.removeTrack = function removeTrack(rtrack) { + _TrackList.prototype.removeTrack.call(this, rtrack); + + if (rtrack.removeEventListener && rtrack.selectedChange_) { + rtrack.removeEventListener('selectedchange', rtrack.selectedChange_); + rtrack.selectedChange_ = null; + } + }; + + return VideoTrackList; + }(TrackList); + + /** + * 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 = + /*#__PURE__*/ + function (_TrackList) { + _inheritsLoose(TextTrackList, _TrackList); + + function TextTrackList() { + return _TrackList.apply(this, arguments) || this; + } + + var _proto = TextTrackList.prototype; + + /** + * Add a {@link TextTrack} to the `TextTrackList` + * + * @param {TextTrack} track + * The text track to add to the list. + * + * @fires TrackList#addtrack + */ + _proto.addTrack = function addTrack(track) { + var _this = this; + + _TrackList.prototype.addTrack.call(this, track); + + if (!this.queueChange_) { + this.queueChange_ = function () { + return _this.queueTrigger('change'); + }; + } + + if (!this.triggerSelectedlanguagechange) { + this.triggerSelectedlanguagechange_ = function () { + return _this.trigger('selectedlanguagechange'); + }; + } + /** + * @listens TextTrack#modechange + * @fires TrackList#change + */ + + + track.addEventListener('modechange', this.queueChange_); + var nonLanguageTextTrackKind = ['metadata', 'chapters']; + + if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) { + track.addEventListener('modechange', this.triggerSelectedlanguagechange_); + } + }; + + _proto.removeTrack = function removeTrack(rtrack) { + _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added + + + if (rtrack.removeEventListener) { + if (this.queueChange_) { + rtrack.removeEventListener('modechange', this.queueChange_); + } + + if (this.selectedlanguagechange_) { + rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_); + } + } + }; + + return TextTrackList; + }(TrackList); + + /** + * @file html-track-element-list.js + */ + + /** + * The current list of {@link HtmlTrackElement}s. + */ + var HtmlTrackElementList = + /*#__PURE__*/ + function () { + /** + * Create an instance of this class. + * + * @param {HtmlTrackElement[]} [tracks=[]] + * A list of `HtmlTrackElement` to instantiate the list with. + */ + function HtmlTrackElementList(trackElements) { + if (trackElements === void 0) { + trackElements = []; + } + + 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() { + 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 + */ + + + var _proto = HtmlTrackElementList.prototype; + + _proto.addTrackElement_ = function addTrackElement_(trackElement) { + var index = this.trackElements_.length; + + if (!('' + index in this)) { + Object.defineProperty(this, index, { + get: function get() { + 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 + */ + ; + + _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) { + var trackElement_; + + 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 + */ + ; + + _proto.removeTrackElement_ = function removeTrackElement_(trackElement) { + for (var i = 0, length = this.trackElements_.length; i < length; i++) { + if (trackElement === this.trackElements_[i]) { + if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') { + this.trackElements_[i].track.off(); + } + + if (typeof this.trackElements_[i].off === 'function') { + this.trackElements_[i].off(); + } + + 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 = + /*#__PURE__*/ + function () { + /** + * Create an instance of this class.. + * + * @param {Array} cues + * A list of cues to be initialized with + */ + function TextTrackCueList(cues) { + 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() { + 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 + */ + + + var _proto = TextTrackCueList.prototype; + + _proto.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() { + 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. + */ + ; + + _proto.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' + }; + + /** + * 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 = + /*#__PURE__*/ + function (_EventTarget) { + _inheritsLoose(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(options) { + var _this; + + if (options === void 0) { + options = {}; + } + + _this = _EventTarget.call(this) || 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(_assertThisInitialized(_this), key, { + get: function get() { + return trackProps[key]; + }, + set: function set() {} + }); + }; + + 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. + * + * @function + * @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.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; + + if (addToBody) { + div = document.createElement('div'); + div.innerHTML = ""; + a = div.firstChild; // prevent the div from affecting layout + + div.setAttribute('style', 'display:none; position:absolute;'); + document.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.body.removeChild(div); + } + + return details; + }; + /** + * Get absolute version of relative URL. Used to tell Flash the correct URL. + * + * @function + * @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.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. + * + * @function + * @param {string} path + * The fileName path like '/path/to/file.mp4' + * + * @return {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. + * + * @function + * @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); + } + + /* eslint no-invalid-this: 1 */ + + var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible '; + var slice = Array.prototype.slice; + var toStr = Object.prototype.toString; + var funcType = '[object Function]'; + + var implementation = function bind(that) { + var target = this; + + if (typeof target !== 'function' || toStr.call(target) !== funcType) { + throw new TypeError(ERROR_MESSAGE + target); + } + + var args = slice.call(arguments, 1); + var bound; + + var binder = function binder() { + if (this instanceof bound) { + var result = target.apply(this, args.concat(slice.call(arguments))); + + if (Object(result) === result) { + return result; + } + + return this; + } else { + return target.apply(that, args.concat(slice.call(arguments))); + } + }; + + var boundLength = Math.max(0, target.length - args.length); + var boundArgs = []; + + for (var i = 0; i < boundLength; i++) { + boundArgs.push('$' + i); + } + + bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder); + + if (target.prototype) { + var Empty = function Empty() {}; + + Empty.prototype = target.prototype; + bound.prototype = new Empty(); + Empty.prototype = null; + } + + return bound; + }; + + var functionBind = Function.prototype.bind || implementation; + + var toStr$1 = Object.prototype.toString; + + var isArguments = function isArguments(value) { + var str = toStr$1.call(value); + var isArgs = str === '[object Arguments]'; + + if (!isArgs) { + isArgs = str !== '[object Array]' && value !== null && typeof value === 'object' && typeof value.length === 'number' && value.length >= 0 && toStr$1.call(value.callee) === '[object Function]'; + } + + return isArgs; + }; + + var keysShim; + + if (!Object.keys) { + // modified from https://github.com/es-shims/es5-shim + var has = Object.prototype.hasOwnProperty; + var toStr$2 = Object.prototype.toString; + var isArgs = isArguments; // eslint-disable-line global-require + + var isEnumerable = Object.prototype.propertyIsEnumerable; + var hasDontEnumBug = !isEnumerable.call({ + toString: null + }, 'toString'); + var hasProtoEnumBug = isEnumerable.call(function () {}, 'prototype'); + var dontEnums = ['toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor']; + + var equalsConstructorPrototype = function equalsConstructorPrototype(o) { + var ctor = o.constructor; + return ctor && ctor.prototype === o; + }; + + var excludedKeys = { + $applicationCache: true, + $console: true, + $external: true, + $frame: true, + $frameElement: true, + $frames: true, + $innerHeight: true, + $innerWidth: true, + $onmozfullscreenchange: true, + $onmozfullscreenerror: true, + $outerHeight: true, + $outerWidth: true, + $pageXOffset: true, + $pageYOffset: true, + $parent: true, + $scrollLeft: true, + $scrollTop: true, + $scrollX: true, + $scrollY: true, + $self: true, + $webkitIndexedDB: true, + $webkitStorageInfo: true, + $window: true + }; + + var hasAutomationEqualityBug = function () { + /* global window */ + if (typeof window === 'undefined') { + return false; + } + + for (var k in window) { + try { + if (!excludedKeys['$' + k] && has.call(window, k) && window[k] !== null && typeof window[k] === 'object') { + try { + equalsConstructorPrototype(window[k]); + } catch (e) { + return true; + } + } + } catch (e) { + return true; + } + } + + return false; + }(); + + var equalsConstructorPrototypeIfNotBuggy = function equalsConstructorPrototypeIfNotBuggy(o) { + /* global window */ + if (typeof window === 'undefined' || !hasAutomationEqualityBug) { + return equalsConstructorPrototype(o); + } + + try { + return equalsConstructorPrototype(o); + } catch (e) { + return false; + } + }; + + keysShim = function keys(object) { + var isObject = object !== null && typeof object === 'object'; + var isFunction = toStr$2.call(object) === '[object Function]'; + var isArguments = isArgs(object); + var isString = isObject && toStr$2.call(object) === '[object String]'; + var theKeys = []; + + if (!isObject && !isFunction && !isArguments) { + throw new TypeError('Object.keys called on a non-object'); + } + + var skipProto = hasProtoEnumBug && isFunction; + + if (isString && object.length > 0 && !has.call(object, 0)) { + for (var i = 0; i < object.length; ++i) { + theKeys.push(String(i)); + } + } + + if (isArguments && object.length > 0) { + for (var j = 0; j < object.length; ++j) { + theKeys.push(String(j)); + } + } else { + for (var name in object) { + if (!(skipProto && name === 'prototype') && has.call(object, name)) { + theKeys.push(String(name)); + } + } + } + + if (hasDontEnumBug) { + var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object); + + for (var k = 0; k < dontEnums.length; ++k) { + if (!(skipConstructor && dontEnums[k] === 'constructor') && has.call(object, dontEnums[k])) { + theKeys.push(dontEnums[k]); + } + } + } + + return theKeys; + }; + } + + var implementation$1 = keysShim; + + var slice$1 = Array.prototype.slice; + var origKeys = Object.keys; + var keysShim$1 = origKeys ? function keys(o) { + return origKeys(o); + } : implementation$1; + var originalKeys = Object.keys; + + keysShim$1.shim = function shimObjectKeys() { + if (Object.keys) { + var keysWorksWithArguments = function () { + // Safari 5.0 bug + var args = Object.keys(arguments); + return args && args.length === arguments.length; + }(1, 2); + + if (!keysWorksWithArguments) { + Object.keys = function keys(object) { + // eslint-disable-line func-name-matching + if (isArguments(object)) { + return originalKeys(slice$1.call(object)); + } + + return originalKeys(object); + }; + } + } else { + Object.keys = keysShim$1; + } + + return Object.keys || keysShim$1; + }; + + var objectKeys = keysShim$1; + + var hasSymbols = typeof Symbol === 'function' && typeof Symbol('foo') === 'symbol'; + var toStr$3 = Object.prototype.toString; + var concat = Array.prototype.concat; + var origDefineProperty = Object.defineProperty; + + var isFunction$1 = function isFunction(fn) { + return typeof fn === 'function' && toStr$3.call(fn) === '[object Function]'; + }; + + var arePropertyDescriptorsSupported = function arePropertyDescriptorsSupported() { + var obj = {}; + + try { + origDefineProperty(obj, 'x', { + enumerable: false, + value: obj + }); // eslint-disable-next-line no-unused-vars, no-restricted-syntax + + for (var _ in obj) { + // jscs:ignore disallowUnusedVariables + return false; + } + + return obj.x === obj; + } catch (e) { + /* this is IE 8. */ + return false; + } + }; + + var supportsDescriptors = origDefineProperty && arePropertyDescriptorsSupported(); + + var defineProperty = function defineProperty(object, name, value, predicate) { + if (name in object && (!isFunction$1(predicate) || !predicate())) { + return; + } + + if (supportsDescriptors) { + origDefineProperty(object, name, { + configurable: true, + enumerable: false, + value: value, + writable: true + }); + } else { + object[name] = value; + } + }; + + var defineProperties = function defineProperties(object, map) { + var predicates = arguments.length > 2 ? arguments[2] : {}; + var props = objectKeys(map); + + if (hasSymbols) { + props = concat.call(props, Object.getOwnPropertySymbols(map)); + } + + for (var i = 0; i < props.length; i += 1) { + defineProperty(object, props[i], map[props[i]], predicates[props[i]]); + } + }; + + defineProperties.supportsDescriptors = !!supportsDescriptors; + var defineProperties_1 = defineProperties; + + /* globals + Set, + Map, + WeakSet, + WeakMap, + + Promise, + + Symbol, + Proxy, + + Atomics, + SharedArrayBuffer, + + ArrayBuffer, + DataView, + Uint8Array, + Float32Array, + Float64Array, + Int8Array, + Int16Array, + Int32Array, + Uint8ClampedArray, + Uint16Array, + Uint32Array, + */ + + var undefined$1; // eslint-disable-line no-shadow-restricted-names + + var ThrowTypeError = Object.getOwnPropertyDescriptor ? function () { + return Object.getOwnPropertyDescriptor(arguments, 'callee').get; + }() : function () { + throw new TypeError(); + }; + var hasSymbols$1 = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'; + + var getProto = Object.getPrototypeOf || function (x) { + return x.__proto__; + }; // eslint-disable-line no-proto + + var generatorFunction = undefined$1; + + var asyncFunction = undefined$1; + + var asyncGenFunction = undefined$1; + var TypedArray = typeof Uint8Array === 'undefined' ? undefined$1 : getProto(Uint8Array); + var INTRINSICS = { + '$ %Array%': Array, + '$ %ArrayBuffer%': typeof ArrayBuffer === 'undefined' ? undefined$1 : ArrayBuffer, + '$ %ArrayBufferPrototype%': typeof ArrayBuffer === 'undefined' ? undefined$1 : ArrayBuffer.prototype, + '$ %ArrayIteratorPrototype%': hasSymbols$1 ? getProto([][Symbol.iterator]()) : undefined$1, + '$ %ArrayPrototype%': Array.prototype, + '$ %ArrayProto_entries%': Array.prototype.entries, + '$ %ArrayProto_forEach%': Array.prototype.forEach, + '$ %ArrayProto_keys%': Array.prototype.keys, + '$ %ArrayProto_values%': Array.prototype.values, + '$ %AsyncFromSyncIteratorPrototype%': undefined$1, + '$ %AsyncFunction%': asyncFunction, + '$ %AsyncFunctionPrototype%': undefined$1, + '$ %AsyncGenerator%': undefined$1, + '$ %AsyncGeneratorFunction%': asyncGenFunction, + '$ %AsyncGeneratorPrototype%': undefined$1, + '$ %AsyncIteratorPrototype%': undefined$1, + '$ %Atomics%': typeof Atomics === 'undefined' ? undefined$1 : Atomics, + '$ %Boolean%': Boolean, + '$ %BooleanPrototype%': Boolean.prototype, + '$ %DataView%': typeof DataView === 'undefined' ? undefined$1 : DataView, + '$ %DataViewPrototype%': typeof DataView === 'undefined' ? undefined$1 : DataView.prototype, + '$ %Date%': Date, + '$ %DatePrototype%': Date.prototype, + '$ %decodeURI%': decodeURI, + '$ %decodeURIComponent%': decodeURIComponent, + '$ %encodeURI%': encodeURI, + '$ %encodeURIComponent%': encodeURIComponent, + '$ %Error%': Error, + '$ %ErrorPrototype%': Error.prototype, + '$ %eval%': eval, + // eslint-disable-line no-eval + '$ %EvalError%': EvalError, + '$ %EvalErrorPrototype%': EvalError.prototype, + '$ %Float32Array%': typeof Float32Array === 'undefined' ? undefined$1 : Float32Array, + '$ %Float32ArrayPrototype%': typeof Float32Array === 'undefined' ? undefined$1 : Float32Array.prototype, + '$ %Float64Array%': typeof Float64Array === 'undefined' ? undefined$1 : Float64Array, + '$ %Float64ArrayPrototype%': typeof Float64Array === 'undefined' ? undefined$1 : Float64Array.prototype, + '$ %Function%': Function, + '$ %FunctionPrototype%': Function.prototype, + '$ %Generator%': undefined$1, + '$ %GeneratorFunction%': generatorFunction, + '$ %GeneratorPrototype%': undefined$1, + '$ %Int8Array%': typeof Int8Array === 'undefined' ? undefined$1 : Int8Array, + '$ %Int8ArrayPrototype%': typeof Int8Array === 'undefined' ? undefined$1 : Int8Array.prototype, + '$ %Int16Array%': typeof Int16Array === 'undefined' ? undefined$1 : Int16Array, + '$ %Int16ArrayPrototype%': typeof Int16Array === 'undefined' ? undefined$1 : Int8Array.prototype, + '$ %Int32Array%': typeof Int32Array === 'undefined' ? undefined$1 : Int32Array, + '$ %Int32ArrayPrototype%': typeof Int32Array === 'undefined' ? undefined$1 : Int32Array.prototype, + '$ %isFinite%': isFinite, + '$ %isNaN%': isNaN, + '$ %IteratorPrototype%': hasSymbols$1 ? getProto(getProto([][Symbol.iterator]())) : undefined$1, + '$ %JSON%': JSON, + '$ %JSONParse%': JSON.parse, + '$ %Map%': typeof Map === 'undefined' ? undefined$1 : Map, + '$ %MapIteratorPrototype%': typeof Map === 'undefined' || !hasSymbols$1 ? undefined$1 : getProto(new Map()[Symbol.iterator]()), + '$ %MapPrototype%': typeof Map === 'undefined' ? undefined$1 : Map.prototype, + '$ %Math%': Math, + '$ %Number%': Number, + '$ %NumberPrototype%': Number.prototype, + '$ %Object%': Object, + '$ %ObjectPrototype%': Object.prototype, + '$ %ObjProto_toString%': Object.prototype.toString, + '$ %ObjProto_valueOf%': Object.prototype.valueOf, + '$ %parseFloat%': parseFloat, + '$ %parseInt%': parseInt, + '$ %Promise%': typeof Promise === 'undefined' ? undefined$1 : Promise, + '$ %PromisePrototype%': typeof Promise === 'undefined' ? undefined$1 : Promise.prototype, + '$ %PromiseProto_then%': typeof Promise === 'undefined' ? undefined$1 : Promise.prototype.then, + '$ %Promise_all%': typeof Promise === 'undefined' ? undefined$1 : Promise.all, + '$ %Promise_reject%': typeof Promise === 'undefined' ? undefined$1 : Promise.reject, + '$ %Promise_resolve%': typeof Promise === 'undefined' ? undefined$1 : Promise.resolve, + '$ %Proxy%': typeof Proxy === 'undefined' ? undefined$1 : Proxy, + '$ %RangeError%': RangeError, + '$ %RangeErrorPrototype%': RangeError.prototype, + '$ %ReferenceError%': ReferenceError, + '$ %ReferenceErrorPrototype%': ReferenceError.prototype, + '$ %Reflect%': typeof Reflect === 'undefined' ? undefined$1 : Reflect, + '$ %RegExp%': RegExp, + '$ %RegExpPrototype%': RegExp.prototype, + '$ %Set%': typeof Set === 'undefined' ? undefined$1 : Set, + '$ %SetIteratorPrototype%': typeof Set === 'undefined' || !hasSymbols$1 ? undefined$1 : getProto(new Set()[Symbol.iterator]()), + '$ %SetPrototype%': typeof Set === 'undefined' ? undefined$1 : Set.prototype, + '$ %SharedArrayBuffer%': typeof SharedArrayBuffer === 'undefined' ? undefined$1 : SharedArrayBuffer, + '$ %SharedArrayBufferPrototype%': typeof SharedArrayBuffer === 'undefined' ? undefined$1 : SharedArrayBuffer.prototype, + '$ %String%': String, + '$ %StringIteratorPrototype%': hasSymbols$1 ? getProto(''[Symbol.iterator]()) : undefined$1, + '$ %StringPrototype%': String.prototype, + '$ %Symbol%': hasSymbols$1 ? Symbol : undefined$1, + '$ %SymbolPrototype%': hasSymbols$1 ? Symbol.prototype : undefined$1, + '$ %SyntaxError%': SyntaxError, + '$ %SyntaxErrorPrototype%': SyntaxError.prototype, + '$ %ThrowTypeError%': ThrowTypeError, + '$ %TypedArray%': TypedArray, + '$ %TypedArrayPrototype%': TypedArray ? TypedArray.prototype : undefined$1, + '$ %TypeError%': TypeError, + '$ %TypeErrorPrototype%': TypeError.prototype, + '$ %Uint8Array%': typeof Uint8Array === 'undefined' ? undefined$1 : Uint8Array, + '$ %Uint8ArrayPrototype%': typeof Uint8Array === 'undefined' ? undefined$1 : Uint8Array.prototype, + '$ %Uint8ClampedArray%': typeof Uint8ClampedArray === 'undefined' ? undefined$1 : Uint8ClampedArray, + '$ %Uint8ClampedArrayPrototype%': typeof Uint8ClampedArray === 'undefined' ? undefined$1 : Uint8ClampedArray.prototype, + '$ %Uint16Array%': typeof Uint16Array === 'undefined' ? undefined$1 : Uint16Array, + '$ %Uint16ArrayPrototype%': typeof Uint16Array === 'undefined' ? undefined$1 : Uint16Array.prototype, + '$ %Uint32Array%': typeof Uint32Array === 'undefined' ? undefined$1 : Uint32Array, + '$ %Uint32ArrayPrototype%': typeof Uint32Array === 'undefined' ? undefined$1 : Uint32Array.prototype, + '$ %URIError%': URIError, + '$ %URIErrorPrototype%': URIError.prototype, + '$ %WeakMap%': typeof WeakMap === 'undefined' ? undefined$1 : WeakMap, + '$ %WeakMapPrototype%': typeof WeakMap === 'undefined' ? undefined$1 : WeakMap.prototype, + '$ %WeakSet%': typeof WeakSet === 'undefined' ? undefined$1 : WeakSet, + '$ %WeakSetPrototype%': typeof WeakSet === 'undefined' ? undefined$1 : WeakSet.prototype + }; + + var GetIntrinsic = function GetIntrinsic(name, allowMissing) { + if (arguments.length > 1 && typeof allowMissing !== 'boolean') { + throw new TypeError('"allowMissing" argument must be a boolean'); + } + + var key = '$ ' + name; + + if (!(key in INTRINSICS)) { + throw new SyntaxError('intrinsic ' + name + ' does not exist!'); + } // istanbul ignore if // hopefully this is impossible to test :-) + + + if (typeof INTRINSICS[key] === 'undefined' && !allowMissing) { + throw new TypeError('intrinsic ' + name + ' exists, but is not available. Please file an issue!'); + } + + return INTRINSICS[key]; + }; + + var src = functionBind.call(Function.call, Object.prototype.hasOwnProperty); + + var $TypeError = GetIntrinsic('%TypeError%'); + var $SyntaxError = GetIntrinsic('%SyntaxError%'); + var predicates = { + // https://ecma-international.org/ecma-262/6.0/#sec-property-descriptor-specification-type + 'Property Descriptor': function isPropertyDescriptor(ES, Desc) { + if (ES.Type(Desc) !== 'Object') { + return false; + } + + var allowed = { + '[[Configurable]]': true, + '[[Enumerable]]': true, + '[[Get]]': true, + '[[Set]]': true, + '[[Value]]': true, + '[[Writable]]': true + }; + + for (var key in Desc) { + // eslint-disable-line + if (src(Desc, key) && !allowed[key]) { + return false; + } + } + + var isData = src(Desc, '[[Value]]'); + var IsAccessor = src(Desc, '[[Get]]') || src(Desc, '[[Set]]'); + + if (isData && IsAccessor) { + throw new $TypeError('Property Descriptors may not be both accessor and data descriptors'); + } + + return true; + } + }; + + var assertRecord = function assertRecord(ES, recordType, argumentName, value) { + var predicate = predicates[recordType]; + + if (typeof predicate !== 'function') { + throw new $SyntaxError('unknown record type: ' + recordType); + } + + if (!predicate(ES, value)) { + throw new $TypeError(argumentName + ' must be a ' + recordType); + } + + console.log(predicate(ES, value), value); + }; + + var _isNaN = Number.isNaN || function isNaN(a) { + return a !== a; + }; + + var $isNaN = Number.isNaN || function (a) { + return a !== a; + }; + + var _isFinite = Number.isFinite || function (x) { + return typeof x === 'number' && !$isNaN(x) && x !== Infinity && x !== -Infinity; + }; + + var sign = function sign(number) { + return number >= 0 ? 1 : -1; + }; + + var mod = function mod(number, modulo) { + var remain = number % modulo; + return Math.floor(remain >= 0 ? remain : remain + modulo); + }; + + var fnToStr = Function.prototype.toString; + var constructorRegex = /^\s*class\b/; + + var isES6ClassFn = function isES6ClassFunction(value) { + try { + var fnStr = fnToStr.call(value); + return constructorRegex.test(fnStr); + } catch (e) { + return false; // not a function + } + }; + + var tryFunctionObject = function tryFunctionToStr(value) { + try { + if (isES6ClassFn(value)) { + return false; + } + + fnToStr.call(value); + return true; + } catch (e) { + return false; + } + }; + + var toStr$4 = Object.prototype.toString; + var fnClass = '[object Function]'; + var genClass = '[object GeneratorFunction]'; + var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol'; + + var isCallable = function isCallable(value) { + if (!value) { + return false; + } + + if (typeof value !== 'function' && typeof value !== 'object') { + return false; + } + + if (typeof value === 'function' && !value.prototype) { + return true; + } + + if (hasToStringTag) { + return tryFunctionObject(value); + } + + if (isES6ClassFn(value)) { + return false; + } + + var strClass = toStr$4.call(value); + return strClass === fnClass || strClass === genClass; + }; + + var isPrimitive = function isPrimitive(value) { + return value === null || typeof value !== 'function' && typeof value !== 'object'; + }; + + var toStr$5 = Object.prototype.toString; // http://ecma-international.org/ecma-262/5.1/#sec-8.12.8 + + var ES5internalSlots = { + '[[DefaultValue]]': function DefaultValue(O) { + var actualHint; + + if (arguments.length > 1) { + actualHint = arguments[1]; + } else { + actualHint = toStr$5.call(O) === '[object Date]' ? String : Number; + } + + if (actualHint === String || actualHint === Number) { + var methods = actualHint === String ? ['toString', 'valueOf'] : ['valueOf', 'toString']; + var value, i; + + for (i = 0; i < methods.length; ++i) { + if (isCallable(O[methods[i]])) { + value = O[methods[i]](); + + if (isPrimitive(value)) { + return value; + } + } + } + + throw new TypeError('No default value'); + } + + throw new TypeError('invalid [[DefaultValue]] hint supplied'); + } + }; // http://ecma-international.org/ecma-262/5.1/#sec-9.1 + + var es5 = function ToPrimitive(input) { + if (isPrimitive(input)) { + return input; + } + + if (arguments.length > 1) { + return ES5internalSlots['[[DefaultValue]]'](input, arguments[1]); + } + + return ES5internalSlots['[[DefaultValue]]'](input); + }; + + var $Object = GetIntrinsic('%Object%'); + var $TypeError$1 = GetIntrinsic('%TypeError%'); + var $String = GetIntrinsic('%String%'); // https://es5.github.io/#x9 + + var ES5 = { + ToPrimitive: es5, + ToBoolean: function ToBoolean(value) { + return !!value; + }, + ToNumber: function ToNumber(value) { + return +value; // eslint-disable-line no-implicit-coercion + }, + ToInteger: function ToInteger(value) { + var number = this.ToNumber(value); + + if (_isNaN(number)) { + return 0; + } + + if (number === 0 || !_isFinite(number)) { + return number; + } + + return sign(number) * Math.floor(Math.abs(number)); + }, + ToInt32: function ToInt32(x) { + return this.ToNumber(x) >> 0; + }, + ToUint32: function ToUint32(x) { + return this.ToNumber(x) >>> 0; + }, + ToUint16: function ToUint16(value) { + var number = this.ToNumber(value); + + if (_isNaN(number) || number === 0 || !_isFinite(number)) { + return 0; + } + + var posInt = sign(number) * Math.floor(Math.abs(number)); + return mod(posInt, 0x10000); + }, + ToString: function ToString(value) { + return $String(value); + }, + ToObject: function ToObject(value) { + this.CheckObjectCoercible(value); + return $Object(value); + }, + CheckObjectCoercible: function CheckObjectCoercible(value, optMessage) { + /* jshint eqnull:true */ + if (value == null) { + throw new $TypeError$1(optMessage || 'Cannot call method on ' + value); + } + + return value; + }, + IsCallable: isCallable, + SameValue: function SameValue(x, y) { + if (x === y) { + // 0 === -0, but they are not identical. + if (x === 0) { + return 1 / x === 1 / y; + } + + return true; + } + + return _isNaN(x) && _isNaN(y); + }, + // https://www.ecma-international.org/ecma-262/5.1/#sec-8 + Type: function Type(x) { + if (x === null) { + return 'Null'; + } + + if (typeof x === 'undefined') { + return 'Undefined'; + } + + if (typeof x === 'function' || typeof x === 'object') { + return 'Object'; + } + + if (typeof x === 'number') { + return 'Number'; + } + + if (typeof x === 'boolean') { + return 'Boolean'; + } + + if (typeof x === 'string') { + return 'String'; + } + }, + // https://ecma-international.org/ecma-262/6.0/#sec-property-descriptor-specification-type + IsPropertyDescriptor: function IsPropertyDescriptor(Desc) { + if (this.Type(Desc) !== 'Object') { + return false; + } + + var allowed = { + '[[Configurable]]': true, + '[[Enumerable]]': true, + '[[Get]]': true, + '[[Set]]': true, + '[[Value]]': true, + '[[Writable]]': true + }; + + for (var key in Desc) { + // eslint-disable-line + if (src(Desc, key) && !allowed[key]) { + return false; + } + } + + var isData = src(Desc, '[[Value]]'); + var IsAccessor = src(Desc, '[[Get]]') || src(Desc, '[[Set]]'); + + if (isData && IsAccessor) { + throw new $TypeError$1('Property Descriptors may not be both accessor and data descriptors'); + } + + return true; + }, + // https://ecma-international.org/ecma-262/5.1/#sec-8.10.1 + IsAccessorDescriptor: function IsAccessorDescriptor(Desc) { + if (typeof Desc === 'undefined') { + return false; + } + + assertRecord(this, 'Property Descriptor', 'Desc', Desc); + + if (!src(Desc, '[[Get]]') && !src(Desc, '[[Set]]')) { + return false; + } + + return true; + }, + // https://ecma-international.org/ecma-262/5.1/#sec-8.10.2 + IsDataDescriptor: function IsDataDescriptor(Desc) { + if (typeof Desc === 'undefined') { + return false; + } + + assertRecord(this, 'Property Descriptor', 'Desc', Desc); + + if (!src(Desc, '[[Value]]') && !src(Desc, '[[Writable]]')) { + return false; + } + + return true; + }, + // https://ecma-international.org/ecma-262/5.1/#sec-8.10.3 + IsGenericDescriptor: function IsGenericDescriptor(Desc) { + if (typeof Desc === 'undefined') { + return false; + } + + assertRecord(this, 'Property Descriptor', 'Desc', Desc); + + if (!this.IsAccessorDescriptor(Desc) && !this.IsDataDescriptor(Desc)) { + return true; + } + + return false; + }, + // https://ecma-international.org/ecma-262/5.1/#sec-8.10.4 + FromPropertyDescriptor: function FromPropertyDescriptor(Desc) { + if (typeof Desc === 'undefined') { + return Desc; + } + + assertRecord(this, 'Property Descriptor', 'Desc', Desc); + + if (this.IsDataDescriptor(Desc)) { + return { + value: Desc['[[Value]]'], + writable: !!Desc['[[Writable]]'], + enumerable: !!Desc['[[Enumerable]]'], + configurable: !!Desc['[[Configurable]]'] + }; + } else if (this.IsAccessorDescriptor(Desc)) { + return { + get: Desc['[[Get]]'], + set: Desc['[[Set]]'], + enumerable: !!Desc['[[Enumerable]]'], + configurable: !!Desc['[[Configurable]]'] + }; + } else { + throw new $TypeError$1('FromPropertyDescriptor must be called with a fully populated Property Descriptor'); + } + }, + // https://ecma-international.org/ecma-262/5.1/#sec-8.10.5 + ToPropertyDescriptor: function ToPropertyDescriptor(Obj) { + if (this.Type(Obj) !== 'Object') { + throw new $TypeError$1('ToPropertyDescriptor requires an object'); + } + + var desc = {}; + + if (src(Obj, 'enumerable')) { + desc['[[Enumerable]]'] = this.ToBoolean(Obj.enumerable); + } + + if (src(Obj, 'configurable')) { + desc['[[Configurable]]'] = this.ToBoolean(Obj.configurable); + } + + if (src(Obj, 'value')) { + desc['[[Value]]'] = Obj.value; + } + + if (src(Obj, 'writable')) { + desc['[[Writable]]'] = this.ToBoolean(Obj.writable); + } + + if (src(Obj, 'get')) { + var getter = Obj.get; + + if (typeof getter !== 'undefined' && !this.IsCallable(getter)) { + throw new TypeError('getter must be a function'); + } + + desc['[[Get]]'] = getter; + } + + if (src(Obj, 'set')) { + var setter = Obj.set; + + if (typeof setter !== 'undefined' && !this.IsCallable(setter)) { + throw new $TypeError$1('setter must be a function'); + } + + desc['[[Set]]'] = setter; + } + + if ((src(desc, '[[Get]]') || src(desc, '[[Set]]')) && (src(desc, '[[Value]]') || src(desc, '[[Writable]]'))) { + throw new $TypeError$1('Invalid property descriptor. Cannot both specify accessors and a value or writable attribute'); + } + + return desc; + } + }; + var es5$1 = ES5; + + var replace = functionBind.call(Function.call, String.prototype.replace); + var leftWhitespace = /^[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]+/; + var rightWhitespace = /[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]+$/; + + var implementation$2 = function trim() { + var S = es5$1.ToString(es5$1.CheckObjectCoercible(this)); + return replace(replace(S, leftWhitespace, ''), rightWhitespace, ''); + }; + + var zeroWidthSpace = "\u200B"; + + var polyfill = function getPolyfill() { + if (String.prototype.trim && zeroWidthSpace.trim() === zeroWidthSpace) { + return String.prototype.trim; + } + + return implementation$2; + }; + + var shim = function shimStringTrim() { + var polyfill$1 = polyfill(); + defineProperties_1(String.prototype, { + trim: polyfill$1 + }, { + trim: function trim() { + return String.prototype.trim !== polyfill$1; + } + }); + return polyfill$1; + }; + + var boundTrim = functionBind.call(Function.call, polyfill()); + defineProperties_1(boundTrim, { + getPolyfill: polyfill, + implementation: implementation$2, + shim: shim + }); + var string_prototype_trim = boundTrim; + + var toStr$6 = Object.prototype.toString; + var hasOwnProperty = Object.prototype.hasOwnProperty; + + var forEachArray = function forEachArray(array, iterator, receiver) { + for (var i = 0, len = array.length; i < len; i++) { + if (hasOwnProperty.call(array, i)) { + if (receiver == null) { + iterator(array[i], i, array); + } else { + iterator.call(receiver, array[i], i, array); + } + } + } + }; + + var forEachString = function forEachString(string, iterator, receiver) { + for (var i = 0, len = string.length; i < len; i++) { + // no such thing as a sparse string. + if (receiver == null) { + iterator(string.charAt(i), i, string); + } else { + iterator.call(receiver, string.charAt(i), i, string); + } + } + }; + + var forEachObject = function forEachObject(object, iterator, receiver) { + for (var k in object) { + if (hasOwnProperty.call(object, k)) { + if (receiver == null) { + iterator(object[k], k, object); + } else { + iterator.call(receiver, object[k], k, object); + } + } + } + }; + + var forEach = function forEach(list, iterator, thisArg) { + if (!isCallable(iterator)) { + throw new TypeError('iterator must be a function'); + } + + var receiver; + + if (arguments.length >= 3) { + receiver = thisArg; + } + + if (toStr$6.call(list) === '[object Array]') { + forEachArray(list, iterator, receiver); + } else if (typeof list === 'string') { + forEachString(list, iterator, receiver); + } else { + forEachObject(list, iterator, receiver); + } + }; + + var forEach_1 = forEach; + + var isArray = function isArray(arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + }; + + var parseHeaders = function parseHeaders(headers) { + if (!headers) return {}; + var result = {}; + forEach_1(string_prototype_trim(headers).split('\n'), function (row) { + var index = row.indexOf(':'), + key = string_prototype_trim(row.slice(0, index)).toLowerCase(), + value = string_prototype_trim(row.slice(index + 1)); + + if (typeof result[key] === 'undefined') { + result[key] = value; + } else if (isArray(result[key])) { + result[key].push(value); + } else { + result[key] = [result[key], value]; + } + }); + return result; + }; + + var immutable = extend; + var hasOwnProperty$1 = Object.prototype.hasOwnProperty; + + function extend() { + var target = {}; + + for (var i = 0; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (hasOwnProperty$1.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target; + } + + var xhr = createXHR; + createXHR.XMLHttpRequest = window$1.XMLHttpRequest || noop; + createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$1.XDomainRequest; + forEachArray$1(["get", "put", "post", "patch", "head", "delete"], function (method) { + createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) { + options = initParams(uri, options, callback); + options.method = method.toUpperCase(); + return _createXHR(options); + }; + }); + + function forEachArray$1(array, iterator) { + for (var i = 0; i < array.length; i++) { + iterator(array[i]); + } + } + + function isEmpty(obj) { + for (var i in obj) { + if (obj.hasOwnProperty(i)) return false; + } + + return true; + } + + function initParams(uri, options, callback) { + var params = uri; + + if (isFunction_1(options)) { + callback = options; + + if (typeof uri === "string") { + params = { + uri: uri + }; + } + } else { + params = immutable(options, { + uri: uri + }); + } + + params.callback = callback; + return params; + } + + function createXHR(uri, options, callback) { + options = initParams(uri, options, callback); + return _createXHR(options); + } + + function _createXHR(options) { + if (typeof options.callback === "undefined") { + throw new Error("callback argument missing"); + } + + var called = false; + + var callback = function cbOnce(err, response, body) { + if (!called) { + called = true; + options.callback(err, response, body); + } + }; + + function readystatechange() { + if (xhr.readyState === 4) { + setTimeout(loadFunc, 0); + } + } + + function getBody() { + // Chrome with requestType=blob throws errors arround when even testing access to responseText + var body = undefined; + + if (xhr.response) { + body = xhr.response; + } else { + body = xhr.responseText || getXml(xhr); + } + + if (isJson) { + try { + body = JSON.parse(body); + } catch (e) {} + } + + return body; + } + + function errorFunc(evt) { + clearTimeout(timeoutTimer); + + if (!(evt instanceof Error)) { + evt = new Error("" + (evt || "Unknown XMLHttpRequest Error")); + } + + evt.statusCode = 0; + return callback(evt, failureResponse); + } // will load the data & process the response in a special response object + + + function loadFunc() { + if (aborted) return; + var status; + clearTimeout(timeoutTimer); + + if (options.useXDR && xhr.status === undefined) { + //IE8 CORS GET successful response doesn't have a status field, but body is fine + status = 200; + } else { + status = xhr.status === 1223 ? 204 : xhr.status; + } + + var response = failureResponse; + var err = null; + + if (status !== 0) { + response = { + body: getBody(), + statusCode: status, + method: method, + headers: {}, + url: uri, + rawRequest: xhr + }; + + if (xhr.getAllResponseHeaders) { + //remember xhr can in fact be XDR for CORS in IE + response.headers = parseHeaders(xhr.getAllResponseHeaders()); + } + } else { + err = new Error("Internal XMLHttpRequest Error"); + } + + return callback(err, response, response.body); + } + + var xhr = options.xhr || null; + + if (!xhr) { + if (options.cors || options.useXDR) { + xhr = new createXHR.XDomainRequest(); + } else { + xhr = new createXHR.XMLHttpRequest(); + } + } + + var key; + var aborted; + var uri = xhr.url = options.uri || options.url; + var method = xhr.method = options.method || "GET"; + var body = options.body || options.data; + var headers = xhr.headers = options.headers || {}; + var sync = !!options.sync; + var isJson = false; + var timeoutTimer; + var failureResponse = { + body: undefined, + headers: {}, + statusCode: 0, + method: method, + url: uri, + rawRequest: xhr + }; + + if ("json" in options && options.json !== false) { + isJson = true; + headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user + + if (method !== "GET" && method !== "HEAD") { + headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user + + body = JSON.stringify(options.json === true ? body : options.json); + } + } + + xhr.onreadystatechange = readystatechange; + xhr.onload = loadFunc; + xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function. + + xhr.onprogress = function () {// IE must die + }; + + xhr.onabort = function () { + aborted = true; + }; + + xhr.ontimeout = errorFunc; + xhr.open(method, uri, !sync, options.username, options.password); //has to be after open + + if (!sync) { + xhr.withCredentials = !!options.withCredentials; + } // Cannot set timeout with sync request + // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly + // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent + + + if (!sync && options.timeout > 0) { + timeoutTimer = setTimeout(function () { + if (aborted) return; + aborted = true; //IE9 may still call readystatechange + + xhr.abort("timeout"); + var e = new Error("XMLHttpRequest timeout"); + e.code = "ETIMEDOUT"; + errorFunc(e); + }, options.timeout); + } + + if (xhr.setRequestHeader) { + for (key in headers) { + if (headers.hasOwnProperty(key)) { + xhr.setRequestHeader(key, headers[key]); + } + } + } else if (options.headers && !isEmpty(options.headers)) { + throw new Error("Headers cannot be set on an XDomainRequest object"); + } + + if ("responseType" in options) { + xhr.responseType = options.responseType; + } + + if ("beforeSend" in options && typeof options.beforeSend === "function") { + options.beforeSend(xhr); + } // Microsoft Edge browser sends "undefined" when send is called with undefined value. + // XMLHttpRequest spec says to pass null as body to indicate no body + // See https://github.com/naugtur/xhr/issues/100. + + + xhr.send(body || null); + return xhr; + } + + function getXml(xhr) { + if (xhr.responseType === "document") { + return xhr.responseXML; + } + + var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; + + if (xhr.responseType === "" && !firefoxBugTakenEffect) { + return xhr.responseXML; + } + + return null; + } + + function noop() {} + + /** + * 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.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.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_) { + // to prevent use before define eslint error, we define loadHandler + // as a let here + var loadHandler; + + var errorHandler = function errorHandler() { + log.error("vttjs failed to load, stopping trying to process " + track.src); + track.tech_.off('vttjsloaded', loadHandler); + }; + + loadHandler = function loadHandler() { + track.tech_.off('vttjserror', errorHandler); + return parseCues(responseBody, track); + }; + + track.tech_.one('vttjsloaded', loadHandler); + track.tech_.one('vttjserror', errorHandler); + } + } 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 = + /*#__PURE__*/ + function (_Track) { + _inheritsLoose(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(options) { + var _this; + + if (options === void 0) { + options = {}; + } + + 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'; + } + + _this = _Track.call(this, settings) || this; + _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(_assertThisInitialized(_this), function () { + // Accessing this.activeCues for the side-effects of updating itself + // due to its nature as a getter function. Do not remove or cues will + // stop updating! + // Use the setter to prevent deletion from uglify (pure_getters rule) + this.activeCues = this.activeCues; + + if (changed) { + this.trigger('cuechange'); + changed = false; + } + }); + + if (mode !== 'disabled') { + _this.tech_.ready(function () { + _this.tech_.on('timeupdate', timeupdateHandler); + }, true); + } + + Object.defineProperties(_assertThisInitialized(_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() { + return default_; + }, + set: function set() {} + }, + + /** + * @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() { + return mode; + }, + set: function set(newMode) { + var _this2 = this; + + if (!TextTrackMode[newMode]) { + return; + } + + mode = newMode; + + if (mode !== 'disabled') { + this.tech_.ready(function () { + _this2.tech_.on('timeupdate', timeupdateHandler); + }, true); + } else { + this.tech_.off('timeupdate', timeupdateHandler); + } + /** + * 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() { + if (!this.loaded_) { + return null; + } + + return cues; + }, + set: function set() {} + }, + + /** + * @memberof TextTrack + * @member {TextTrackCueList} activeCues + * The list text track cues that are currently active for this TextTrack. + * @instance + */ + activeCues: { + get: function get() { + 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; + }, + // /!\ Keep this setter empty (see the timeupdate handler above) + set: function set() {} + } + }); + + if (settings.src) { + _this.src = settings.src; + loadTrack(settings.src, _assertThisInitialized(_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 + */ + + + var _proto = TextTrack.prototype; + + _proto.addCue = function addCue(originalCue) { + var cue = originalCue; + + if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) { + cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text); + + 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 + */ + ; + + _proto.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 = + /*#__PURE__*/ + function (_Track) { + _inheritsLoose(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(options) { + var _this; + + if (options === void 0) { + options = {}; + } + + var settings = mergeOptions(options, { + kind: AudioTrackKind[options.kind] || '' + }); + _this = _Track.call(this, settings) || this; + 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(_assertThisInitialized(_this), 'enabled', { + get: function get() { + return enabled; + }, + set: function set(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 = + /*#__PURE__*/ + function (_Track) { + _inheritsLoose(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(options) { + var _this; + + if (options === void 0) { + options = {}; + } + + var settings = mergeOptions(options, { + kind: VideoTrackKind[options.kind] || '' + }); + _this = _Track.call(this, settings) || this; + 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(_assertThisInitialized(_this), 'selected', { + get: function get() { + return selected; + }, + set: function set(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); + + /** + * @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 = + /*#__PURE__*/ + function (_EventTarget) { + _inheritsLoose(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(options) { + var _this; + + if (options === void 0) { + options = {}; + } + + _this = _EventTarget.call(this) || this; + var readyState; + 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(_assertThisInitialized(_this), { + /** + * @memberof HTMLTrackElement + * @member {HTMLTrackElement~ReadyState} readyState + * The current ready state of the track element. + * @instance + */ + readyState: { + get: function get() { + return readyState; + } + }, + + /** + * @memberof HTMLTrackElement + * @member {TextTrack} track + * The underlying TextTrack object. + * @instance + * + */ + track: { + get: function get() { + return track; + } + } + }); + readyState = NONE; + /** + * @listens TextTrack#loadeddata + * @fires HTMLTrackElement#load + */ + + track.addEventListener('loadeddata', function () { + readyState = LOADED; + + _this.trigger({ + type: 'load', + target: _assertThisInitialized(_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; + + /** + * 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; + + /** + * 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 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: vtt, + VTTCue: vttcue, + VTTRegion: vttregion + }; + 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; + + /** + * 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, options) { + if (options === void 0) { + options = {}; + } + + 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 = + /*#__PURE__*/ + function (_Component) { + _inheritsLoose(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(options, ready) { + var _this; + + if (options === void 0) { + options = {}; + } + + if (ready === void 0) { + ready = function ready() {}; + } + + // we don't want the tech to report user activity automatically. + // This is done manually in addControlsListeners + options.reportTouchActivity = false; + _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to + // implement a very limited played() + + _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. + */ + + + var _proto = Tech.prototype; + + _proto.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} + */ + ; + + _proto.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} + */ + ; + + _proto.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 + */ + ; + + _proto.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 + */ + ; + + _proto.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. + */ + ; + + _proto.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. + * + */ + ; + + _proto.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}. + */ + ; + + _proto.stopTrackingProgress = function stopTrackingProgress() { + this.clearInterval(this.progressInterval); + } + /** + * Polyfill the `timeupdate` event for browsers that don't support it. + * + * @see {@link Tech#trackCurrentTime} + */ + ; + + _proto.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} + */ + ; + + _proto.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 + */ + ; + + _proto.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} + */ + ; + + _proto.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 + */ + ; + + _proto.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`. + */ + ; + + _proto.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 + */ + ; + + _proto.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 + */ + ; + + _proto.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. + */ + ; + + _proto.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. + */ + ; + + _proto.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 + */ + ; + + _proto.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 + */ + ; + + _proto.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 + */ + ; + + _proto.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.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.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 + * + */ + ; + + _proto.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. + */ + ; + + _proto.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. + */ + ; + + _proto.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. + */ + ; + + _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { + var _this7 = this; + + if (options === void 0) { + options = {}; + } + + var htmlTrackElement = this.createRemoteTextTrack(options); + + if (manualCleanup !== true && manualCleanup !== false) { + // deprecation warning + log.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` + */ + ; + + _proto.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 + */ + ; + + _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { + return {}; + } + /** + * Attempt to create a floating video window always on top of other windows + * so that users may continue consuming media while they interact with other + * content sites, or applications on their device. + * + * @see [Spec]{@link https://wicg.github.io/picture-in-picture} + * + * @return {Promise|undefined} + * A promise with a Picture-in-Picture window if the browser supports + * Promises (or one was passed in as an option). It returns undefined + * otherwise. + * + * @abstract + */ + ; + + _proto.requestPictureInPicture = function requestPictureInPicture() { + var PromiseClass = this.options_.Promise || window$1.Promise; + + if (PromiseClass) { + return PromiseClass.reject(); + } + } + /** + * A method to set a poster from a `Tech`. + * + * @abstract + */ + ; + + _proto.setPoster = function setPoster() {} + /** + * A method to check for the presence of the 'playsinline'
{% block content %}

Hello World!

{% endblock %} +
+ +
+{% block footer %} +{% endblock %} +
diff --git a/templates/home.html b/templates/home.html index 3c69e80..e48212a 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,5 +1,4 @@ {% extends "base.html" %} {% block content %} - {% endblock %} diff --git a/templates/publisherView.html b/templates/publisherView.html deleted file mode 100644 index f2a61c3..0000000 --- a/templates/publisherView.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
-
- {% include "publisherList.html" %} -
-
-{% endblock %} diff --git a/test.py b/test.py index fc613cc..b67c36d 100644 --- a/test.py +++ b/test.py @@ -10,14 +10,72 @@ from PIL import Image import datetime, pytz from werkzeug.security import generate_password_hash -os.environ["UNRAR_LIB_PATH"] = 'C:\\Program Files (x86)\\UnrarDLL\\UnRAR.dll' +os.environ["UNRAR_LIB_PATH"] = "C:\\Program Files (x86)\\UnrarDLL\\UnRAR.dll" -DATABASE = "C:\\Users\\Matthew\\Documents\\MyPrograms\\Websites\\rpi web interface\\database.db" +DATABASE = "***REMOVED***" db = sqlite3.connect(DATABASE) +db.row_factory = sqlite3.Row + + +DC_Comics = ["DC", "Young Animal", "WildStorm", "Earth-M", "Vertigo", "Sandman Universe", "DC Black Label", "Wonder Comics", "DC Ink", "DC Zoom", "Mad", "All Star", "Amalgam Comics", "DC Focus", "Elseworlds", "First Wave", "Helix", "Impact Comics", "Johnny DC", "Minx", "Paradox Press", "Piranha Press", "Tangent Comics", "WildStorm Productions", "America's Best Comics", "Cliffhanger", "CMX Manga", "Homage Comics", "WildStorm Signature", "Zuda Comics"] + +Marvel = ["Aircel Comics", "alibu Comics‎", "Atlas Comics", "Atlas", "CrossGen comics‎", "CrossGen", "Curtis Magazines", "Disney Books Group", "Disney Kingdoms", "Epic Comics", "Eternity Comics", "Humorama", "Icon Comics", "Infinite Comics", "Malibu Comics", "Marvel 2099", "Marvel Absurd", "Marvel Adventures", "Marvel Age", "Marvel Books", "Marvel Comics 2", "Marvel Comics", "Marvel Edge", "Marvel Frontier", "Marvel Illustrated", "Marvel Knights", "Marvel Magazine Group", "Marvel Mangaverse", "Marvel Monsters Group", "Marvel Music", "Marvel Next", "Marvel Noir", "Marvel Press", "Marvel UK", "Marvel Unlimited", "Max", "MAX", "MC2", "New Universe", "Paramount Comics", "Power Comics", "Razorline", "Star Comics", "Timely Comics", "Timely", "Tsunami", "Ultimate Comics", "Ultimate Marvel"] def get_db(): return db -print(generate_password_hash("guanfacine")) +def db_search_table_columns_by_query(query, table, columns, group="", order=""): + results = {} + final_query = "%"+query.replace(" ", "%")+"%" + sqlite_base_statement = "SELECT * FROM "+table+" WHERE {condition} {group} {order}" + if not group == "": + group = "GROUP BY "+group + if not order == "": + order = "ORDER BY "+order + for column in columns: + sqlite_statement = sqlite_base_statement.format(condition=column+" LIKE '"+final_query+"'", group=group, order=order) + results[column] = get_db().execute(sqlite_statement).fetchall() + + # sqlite_condition = "" + # for column in columns: + # sqlite_condition += column+" LIKE '"+final_query+"'"+(" OR " if column != columns[-1] else "") + # sqlite_statement = "SELECT * FROM {table} WHERE {condition}".format(table=table, condition=sqlite_condition) + # rows = get_db().execute(sqlite_statement).fetchall() + return results + + +def db_search_comics(query): + publishers = [] + series = [] + comics = [] + + results = db_search_table_columns_by_query(query, "comics", ["publisher", "title", "series", "year"]) + series_results = db_search_table_columns_by_query(query, "comics", ["publisher", "title", "series", "year"], + group="series, seriesYear", order="issue") + + for row in results["publisher"]: + if row["publisher"] not in publishers: + publishers.append(row["publisher"]) + for row in series_results["series"]: + dict = {"publisher": row["publisher"],"series": row["series"],"seriesYear": row["seriesYear"],"id": row["id"]} + if dict not in series: + series.append(dict) + for row in results["title"]: + dict = {"publisher": row["publisher"],"series": row["series"],"seriesYear": row["seriesYear"],"issue": row["issue"],"id": row["id"],"title": row["title"]} + if dict not in comics: + comics.append(dict) + for row in results["year"]: + dict = {"publisher": row["publisher"],"series": row["series"],"seriesYear": row["seriesYear"],"issue": row["issue"],"id": row["id"],"title": row["title"]} + if dict not in comics: + comics.append(dict) + + return {"publishers": publishers, "series": series, "comics": comics} + + +results = db_search_comics("ar") + + +for key in results.keys(): + print(key, results[key]) diff --git a/test.sql b/test.sql new file mode 100644 index 0000000..d35c70e --- /dev/null +++ b/test.sql @@ -0,0 +1,20 @@ +.open "/var/lib/rpiWebApp/database.db" + +.print "Start database query" +.print "" + +.print "Number of publishers:" +SELECT count(DISTINCT publisher) FROM comics; +.print "" +.print "Number of series:" +SELECT count(DISTINCT series) FROM comics; +.print "" +.print "Number of comics:" +SELECT count(id) FROM comics; +.print "" + +.print "Number of movies" +SELECT count(*) FROM movies; + +.print "" +.print "End database query"