├── .gitignore
├── CNAME
├── .vscode
└── settings.json
├── no-thumbnail.png
├── index.html
├── package.json
├── example-captions.vtt
├── javascripts
├── snap.js
├── persistvolume.js
├── jsoun.js
├── localstorage.js
├── util.js
├── settingsmenubuttons.js
├── statistics.js
├── providers.js
├── branding.js
├── ipfsgatewayswitcher.js
├── plugin.js
├── settingsmenuitem.js
├── hotkeys.js
├── graph.js
├── thumbnails.js
├── resolutionswitcher.js
└── player.js
├── LICENSE
├── debug.html
├── DTube_White.svg
├── lib
├── swiftclick.min.js
├── lightrpc.min.js
└── playerjs.min.js
├── README.md
├── player.css
└── bin
└── dtube.css
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | emb.d.tube
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | }
--------------------------------------------------------------------------------
/no-thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dtube/embed/HEAD/no-thumbnail.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dtube-embed",
3 | "version": "0.9.1",
4 | "description": "embed player for d.tube",
5 | "main": "build.js",
6 | "scripts": {
7 | "build": "node build.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/dtube/embed.git"
12 | },
13 | "keywords": [
14 | "dtube",
15 | "video",
16 | "embed",
17 | "player"
18 | ],
19 | "author": "",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/dtube/embed/issues"
23 | },
24 | "homepage": "https://github.com/dtube/embed#readme",
25 | "devDependencies": {
26 | "clean-css": "^4.2.3",
27 | "terser": "^4.8.1"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/example-captions.vtt:
--------------------------------------------------------------------------------
1 | WEBVTT
2 |
3 | 00:00.700 --> 00:04.110
4 | Captions describe all relevant audio for the hearing impaired.
5 | [ Heroic music playing for a seagull ]
6 |
7 | 00:04.500 --> 00:05.000
8 | [ Splash!!! ]
9 |
10 | 00:05.100 --> 00:06.000
11 | [ Sploosh!!! ]
12 |
13 | 00:08.000 --> 00:09.225
14 | [ Splash...splash...splash splash splash ]
15 |
16 | 00:10.525 --> 00:11.255
17 | [ Splash, Sploosh again ]
18 |
19 | 00:13.500 --> 00:14.984
20 | Dolphin: eeeEEEEEeeee!
21 |
22 | 00:14.984 --> 00:16.984
23 | Dolphin: Squawk! eeeEEE?
24 |
25 | 00:25.000 --> 00:28.284
26 | [ A whole ton of splashes ]
27 |
28 | 00:29.500 --> 00:31.000
29 | Mine. Mine. Mine.
30 |
31 | 00:34.300 --> 00:36.000
32 | Shark: Chomp
33 |
34 | 00:36.800 --> 00:37.900
35 | Shark: CHOMP!!!
36 |
37 | 00:37.861 --> 00:41.193
38 | EEEEEEOOOOOOOOOOWHALENOISE
39 |
40 | 00:42.593 --> 00:45.611
41 | [ BIG SPLASH ]
--------------------------------------------------------------------------------
/javascripts/snap.js:
--------------------------------------------------------------------------------
1 | function takeSnap() {
2 | var video = document.querySelector('video')
3 | var canvas = document.querySelector('canvas')
4 | var context = canvas.getContext('2d')
5 | var w, h, ratio
6 |
7 | // Calculate the ratio of the video's width to height
8 | ratio = video.videoWidth / video.videoHeight
9 | // Define the required width as 100 pixels smaller than the actual video's width
10 | w = video.videoWidth - 100
11 | // Calculate the height based on the video's width and the ratio
12 | h = parseInt(w / ratio, 10)
13 | // Set the canvas width and height to the values just calculated
14 | canvas.width = w
15 | canvas.height = h
16 |
17 | // Define the size of the rectangle that will be filled (basically the entire element)
18 | context.fillRect(0, 0, w, h)
19 | // Grab the image from the video
20 | context.drawImage(video, 0, 0, w, h)
21 | // Save snap to disk
22 | var dt = canvas.toDataURL('image/jpeg')
23 | $('#snap').attr('href', dt)
24 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 heimindanger
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/javascripts/persistvolume.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | (function(factory){
3 | if (typeof define === 'function' && define['amd']) {
4 | define(['./video'], function(vjs){ factory(window, document, vjs) });
5 | } else if (typeof exports === 'object' && typeof module === 'object') {
6 | factory(window, document, require('video.js'));
7 | } else {
8 | factory(window, document, videojs);
9 | }
10 |
11 | })(function(window, document, vjs) {
12 | var defaults = {}
13 |
14 | var volumePersister = function(options) {
15 | var player = this;
16 | var settings = extend({}, defaults, options || {});
17 |
18 | var key = 'dvolume';
19 | var muteKey = 'dmute';
20 |
21 | player.on("volumechange", function() {
22 | setStorageItem(key, player.volume());
23 | setStorageItem(muteKey, player.muted());
24 | });
25 |
26 | var persistedVolume = getStorageItem(key);
27 | if(persistedVolume !== null){
28 | player.volume(persistedVolume);
29 | }
30 |
31 | var persistedMute = getStorageItem(muteKey);
32 | if(persistedMute !== null){
33 | player.muted('true' === persistedMute);
34 | }
35 | };
36 |
37 | vjs.registerPlugin("persistvolume", volumePersister);
38 |
39 | });
--------------------------------------------------------------------------------
/javascripts/jsoun.js:
--------------------------------------------------------------------------------
1 | const links = [
2 | ["'", '"'],
3 | ['(', '{'],
4 | [')', '}'],
5 | ['_', ' '],
6 | ['~', '['],
7 | [';', ']'],
8 | ['!', '/'],
9 | ]
10 |
11 | function randomChar(str) {
12 | var poss = '🧙♂️🕵🤡🤴🏻🐧'
13 | var char = poss[Math.floor(Math.random()*poss.length)]
14 | while (str.indexOf(char) != -1)
15 | char += poss[Math.floor(Math.random()*poss.length)]
16 | return char
17 | }
18 |
19 | function createRegex(char) {
20 | var str = char
21 | if (['(',')', '[',']'].indexOf(char) > -1)
22 | str = '\\'+str
23 | return new RegExp(str, 'g')
24 | }
25 |
26 | function swap(json) {
27 | for (let i = 0; i < links.length; i++) {
28 | let r0 = createRegex(links[i][0])
29 | let r1 = createRegex(links[i][1])
30 | var tmpChar = randomChar(json)
31 | let rt = createRegex(tmpChar)
32 | json = json.replace(r1, tmpChar)
33 | json = json.replace(r0, links[i][1])
34 | json = json.replace(rt, links[i][0])
35 | }
36 | return json
37 | }
38 |
39 | JSOUN = {
40 | encode: function(obj) {
41 | return encodeURI(swap(JSON.stringify(obj)))
42 | },
43 | decode: function(obj) {
44 | return JSON.parse(swap(decodeURI(obj)))
45 | }
46 | }
--------------------------------------------------------------------------------
/debug.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/javascripts/localstorage.js:
--------------------------------------------------------------------------------
1 | getCookieItem = function(sKey) {
2 | if (!sKey || !hasCookieItem(sKey)) { return null; }
3 | var reg_ex = new RegExp(
4 | "(?:^|.*;\\s*)" +
5 | window.escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") +
6 | "\\s*\\=\\s*((?:[^;](?!;))*[^;]?).*"
7 | );
8 | return window.unescape(document.cookie.replace(reg_ex,"$1"));
9 | }
10 |
11 | setCookieItem = function(sKey, sValue, vEnd, sPath, sDomain, bSecure) {
12 | if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return; }
13 | var sExpires = "";
14 | if (vEnd) {
15 | switch (vEnd.constructor) {
16 | case Number:
17 | sExpires = vEnd === Infinity ? "; expires=Tue, 19 Jan 2038 03:14:07 GMT" : "; max-age=" + vEnd;
18 | break;
19 | case String:
20 | sExpires = "; expires=" + vEnd;
21 | break;
22 | case Date:
23 | sExpires = "; expires=" + vEnd.toGMTString();
24 | break;
25 | }
26 | }
27 | document.cookie =
28 | window.escape(sKey) + "=" +
29 | window.escape(sValue) +
30 | sExpires +
31 | (sDomain ? "; domain=" + sDomain : "") +
32 | (sPath ? "; path=" + sPath : "") +
33 | (bSecure ? "; secure" : "");
34 | }
35 |
36 | hasCookieItem = function(sKey) {
37 | return (new RegExp(
38 | "(?:^|;\\s*)" +
39 | window.escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") +
40 | "\\s*\\=")
41 | ).test(document.cookie);
42 | }
43 |
44 | hasLocalStorage = function() {
45 | try {
46 | window.localStorage.setItem('persistVolume', 'persistVolume');
47 | window.localStorage.removeItem('persistVolume');
48 | return true;
49 | } catch(e) {
50 | return false;
51 | }
52 | }
53 |
54 | getStorageItem = function(key) {
55 | return hasLocalStorage() ? window.localStorage.getItem(key) : getCookieItem(key);
56 | }
57 |
58 | setStorageItem = function(key, value) {
59 | return hasLocalStorage() ? window.localStorage.setItem(key, value) : setCookieItem(key, value, Infinity, '/');
60 | }
61 |
62 | extend = function(obj) {
63 | var arg, i, k;
64 | for (i = 1; i < arguments.length; i++) {
65 | arg = arguments[i];
66 | for (k in arg) {
67 | if (arg.hasOwnProperty(k)) {
68 | obj[k] = arg[k];
69 | }
70 | }
71 | }
72 | return obj;
73 | }
--------------------------------------------------------------------------------
/javascripts/util.js:
--------------------------------------------------------------------------------
1 | // For now, these are copy-pasted from video.js until they are exposed.
2 |
3 | import document from 'global/document';
4 | import window from 'global/window';
5 |
6 | /**
7 | * Offset Left
8 | * getBoundingClientRect technique from
9 | * John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
10 | *
11 | * @function findElPosition
12 | * @param {Element} el Element from which to get offset
13 | * @return {Object}
14 | */
15 | export function findElPosition(el) {
16 | let box;
17 |
18 | if (el.getBoundingClientRect && el.parentNode) {
19 | box = el.getBoundingClientRect();
20 | }
21 |
22 | if (!box) {
23 | return {
24 | left: 0,
25 | top: 0
26 | };
27 | }
28 |
29 | const docEl = document.documentElement;
30 | const body = document.body;
31 |
32 | const clientLeft = docEl.clientLeft || body.clientLeft || 0;
33 | const scrollLeft = window.pageXOffset || body.scrollLeft;
34 | const left = box.left + scrollLeft - clientLeft;
35 |
36 | const clientTop = docEl.clientTop || body.clientTop || 0;
37 | const scrollTop = window.pageYOffset || body.scrollTop;
38 | const top = box.top + scrollTop - clientTop;
39 |
40 | // Android sometimes returns slightly off decimal values, so need to round
41 | return {
42 | left: Math.round(left),
43 | top: Math.round(top)
44 | };
45 | }
46 |
47 | /**
48 | * Get pointer position in element
49 | * Returns an object with x and y coordinates.
50 | * The base on the coordinates are the bottom left of the element.
51 | *
52 | * @function getPointerPosition
53 | * @param {Element} el Element on which to get the pointer position on
54 | * @param {Event} event Event object
55 | * @return {Object}
56 | * This object will have x and y coordinates corresponding to the
57 | * mouse position
58 | */
59 | export function getPointerPosition(el, event) {
60 | const position = {};
61 | const box = findElPosition(el);
62 | const boxW = el.offsetWidth;
63 | const boxH = el.offsetHeight;
64 | const boxY = box.top;
65 | const boxX = box.left;
66 | let pageY = event.pageY;
67 | let pageX = event.pageX;
68 |
69 | if (event.changedTouches) {
70 | pageX = event.changedTouches[0].pageX;
71 | pageY = event.changedTouches[0].pageY;
72 | }
73 |
74 | position.y = Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH));
75 | position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
76 |
77 | return position;
78 | }
79 |
--------------------------------------------------------------------------------
/DTube_White.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
--------------------------------------------------------------------------------
/javascripts/settingsmenubuttons.js:
--------------------------------------------------------------------------------
1 | const MenuButton = videojs.getComponent('MenuButton');
2 | const Menu = videojs.getComponent('Menu');
3 | const Component = videojs.getComponent('Component');
4 |
5 | /**
6 | * The component for controlling the settings menu
7 | *
8 | * @param {Player|Object} player
9 | * @param {Object=} options
10 | * @extends MenuButton
11 | * @class SettingsMenuButton
12 | */
13 | class SettingsMenuButton extends MenuButton {
14 |
15 | constructor(player, options) {
16 | super(player, options);
17 |
18 | this.el_.setAttribute('aria-label', 'Settings Menu');
19 |
20 | this.on('mouseleave', videojs.bind(this, this.hideChildren));
21 | for (let i = 0; i < this.children_[1].children_.length; i++) {
22 | this.on('click', videojs.bind(this.children_[1].children_[i], this.children_[1].children_[i].update));
23 | }
24 | }
25 |
26 | /**
27 | * Allow sub components to stack CSS class names
28 | *
29 | * @return {String} The constructed class name
30 | * @method buildCSSClass
31 | */
32 | buildCSSClass() {
33 | // vjs-icon-cog can be removed when the settings menu is integrated in video.js
34 | return `vjs-settings-menu vjs-icon-cog ${super.buildCSSClass()}`;
35 | }
36 |
37 | /**
38 | * Create the settings menu
39 | *
40 | * @return {Menu} Menu object populated with items
41 | * @method createMenu
42 | */
43 | createMenu() {
44 | let menu = new Menu(this.player());
45 | let entries = this.options_.entries;
46 |
47 | if (entries) {
48 |
49 | const openSubMenu = function() {
50 | videojs.dom.removeClass(this.el_.childNodes[2], 'vjs-hidden');
51 | videojs.dom.addClass(this.el_, 'open');
52 | videojs.dom.addClass(this.el_.childNodes[2].childNodes[0], 'vjs-lock-showing');
53 | };
54 |
55 | for (let entry of entries) {
56 | let settingsMenuItem = new SettingsMenuItem(this.player(), this.options_, entry);
57 | menu.addChild(settingsMenuItem);
58 |
59 | // Hide children to avoid sub menus stacking on top of each other
60 | // or having multiple menus open
61 | settingsMenuItem.on('click', videojs.bind(this, this.hideChildren));
62 |
63 | // Wether to add or remove selected class on the settings sub menu element
64 | settingsMenuItem.on('click', openSubMenu);
65 | }
66 | }
67 |
68 | return menu;
69 | }
70 |
71 | /**
72 | * Hide all the sub menus
73 | */
74 | hideChildren() {
75 | for (let menuChild of this.menu.children()) {
76 | menuChild.hideSubMenu();
77 | }
78 | }
79 |
80 | }
81 |
82 | SettingsMenuButton.prototype.controlText_ = 'Settings Menu';
83 |
84 | Component.registerComponent('SettingsMenuButton', SettingsMenuButton);
85 |
86 |
--------------------------------------------------------------------------------
/lib/swiftclick.min.js:
--------------------------------------------------------------------------------
1 | "use strict";function SwiftClick(a){function b(a){n(a)}function c(a){var b=a.target,c=b.nodeName.toLowerCase(),f=a.changedTouches[0];return void 0===l.options.elements[c]||(!!o||(l.options.useCssParser&&i(b)?(r=!1,!0):(a.stopPropagation(),o=!0,p.x=f.pageX,p.y=f.pageY,q=h(),b.removeEventListener("touchend",d,!1),b.addEventListener("touchend",d,!1),b.removeEventListener("touchcancel",e,!1),void b.addEventListener("touchcancel",e,!1))))}function d(a){var b=a.target,c=a.changedTouches[0];return b.removeEventListener("touchend",d,!1),o=!1,!!j(c)||(a.stopPropagation(),a.preventDefault(),r=!1,b.focus(),g(b,c),!1)}function e(a){a.target.removeEventListener("touchcancel",e,!1),o=!1}function f(a){var b=a.target,c=b.nodeName.toLowerCase();if(void 0!==l.options.elements[c]){if(r)return r=!1,a.stopPropagation(),a.preventDefault(),!1;r=!0}}function g(a,b){var c=document.createEvent("MouseEvents");c.initMouseEvent("click",!0,!0,window,1,b.screenX,b.screenY,b.clientX,b.clientY,!1,!1,!1,!1,0,null),a.dispatchEvent(c)}function h(){return{x:window.pageXOffset||document.body.scrollLeft||document.documentElement.scrollLeft||0,y:window.pageYOffset||document.body.scrollTop||document.documentElement.scrollTop||0}}function i(a){var b=a.parentNode,c=!1;if(k(a,"swiftclick-ignore"))return!0;if(k(a,"swiftclick-force"))return c;if(null===b)return c;for(;b;)k(b,"swiftclick-ignore")?(b=null,c=!0):b=b.parentNode;return c}function j(a){var b=l.options.maxTouchDrift,c=h();return Math.abs(a.pageX-p.x)>b||Math.abs(a.pageY-p.y)>b||Math.abs(c.x-q.x)>b||Math.abs(c.y-q.y)>b}function k(a,b){return void 0!==a.className&&(" "+a.className+" ").indexOf(" "+b+" ")>-1}if(void 0!==SwiftClick.swiftDictionary[a])return SwiftClick.swiftDictionary[a];SwiftClick.swiftDictionary[a]=this,this.options={elements:{a:"a",div:"div",span:"span",button:"button"},minTouchDrift:4,maxTouchDrift:16,useCssParser:!1};var l=this,m=a,n=m.onclick,o=!1,p={x:0,y:0},q={x:0,y:0},r=!1;"onorientationchange"in window&&"ontouchstart"in window&&function(){"function"==typeof n&&(m.addEventListener("click",b,!1),m.onclick=null),m.addEventListener("touchstart",c,!1),m.addEventListener("click",f,!0)}()}SwiftClick.swiftDictionary={},SwiftClick.prototype.setMaxTouchDrift=function(a){if("number"!=typeof a)throw new TypeError('expected "maxTouchDrift" to be of type "number"');a will autoplay and keep dtube branding
22 |
23 | Autoplay is not always going to happen. It depends on the media engagement, see chrome://media-engagement
24 |
25 | [https://emb.d.tube/#!/elsiekjay/QmQXCBVvVn6WRCuxV3K2FoYLX6F98TvWYPorJEdEyz7VPr/true/false/default/loop=true](https://emb.d.tube/#!/elsiekjay/QmQXCBVvVn6WRCuxV3K2FoYLX6F98TvWYPorJEdEyz7VPr/true/default/loop=true) -> will autoplay, keep dtube branding and also loop at the end, whilest using the recommended provider
26 |
27 |
28 | ## Supported Providers
29 |
30 | First-Party:
31 |
32 | * IPFS
33 | * BTFS
34 | * Skynet
35 |
36 | Third-Party:
37 |
38 | * Twitch
39 | * Dailymotion
40 | * Instagram
41 | * LiveLeak
42 | * Vimeo
43 | * Facebook
44 | * YouTube
45 |
46 | ## Additional Options
47 |
48 | | Key | Value | Explanation |
49 | |--------|-----------------------|-------------------------------------------|
50 | | loop | `true` / `false` | Makes the video loop at the end. Currently not supported for: Twitch, Dailymotion, Instagram, LiveLeak and Facebook |
51 |
52 | ## JavaScript API
53 |
54 | The embedded player can be controlled using JavaScript in compliance with the [Player.js specification](https://github.com/embedly/player.js/blob/master/SPEC.rst). You can use [Player.js](https://github.com/embedly/player.js#playerjs) as a library to control the player.
55 |
56 | Simply load the Player.js library:
57 | ```html
58 |
59 | ```
60 |
61 | After this you can initialize the library using the ID of the iframe:
62 |
63 | ```javascript
64 | const player = new playerjs.Player('your-iframe');
65 | ```
66 |
67 | You can then call a variety of methods suchs as `player.play()` or `player.setVolume(20)`.
68 |
69 | For a full list of methods see: https://github.com/embedly/player.js#methods
70 |
71 | For a full list of events see: https://github.com/embedly/player.js#events
72 |
--------------------------------------------------------------------------------
/javascripts/statistics.js:
--------------------------------------------------------------------------------
1 | // player.trigger('startStats') to open
2 | // player.trigger('stopStats') to close
3 |
4 | function getCurrentStats() {
5 | return {
6 | droppedVideoFrames: player.getVideoPlaybackQuality().droppedVideoFrames,
7 | totalVideoFrames: player.getVideoPlaybackQuality().totalVideoFrames,
8 | paused: player.paused(),
9 | volume: player.volume(),
10 | resolution: player.videoWidth() + 'x' + player.videoHeight(),
11 | viewport: window.innerWidth + 'x' + window.innerHeight,
12 | gateway: player.currentSource().src.split('/')[2],
13 | fileSrc: player.currentSource().src,
14 | fileType: player.currentSource().type,
15 | buffered: player.bufferedEnd() - player.currentTime(),
16 | }
17 | }
18 |
19 | function statisticsPlugin(options) {
20 | var isStarted = false
21 | var interval = null
22 | this.on('stopStats', function() {
23 | clearInterval(interval)
24 | var element = document.getElementById("infoPanel");
25 | if (element)
26 | element.parentNode.removeChild(element);
27 | isStarted = false
28 |
29 | })
30 | this.on('startStats', function() {
31 | if (!isStarted) {
32 | isStarted = true
33 | player.currentStats = getCurrentStats()
34 | player.statsBuffered = [player.currentStats.buffered]
35 | interval = setInterval(function() {
36 | player.currentStats = getCurrentStats()
37 | player.statsBuffered.push(player.currentStats.buffered)
38 | if (player.statsBuffered.length > 60 * 4)
39 | player.statsBuffered.splice(0, 1)
40 | var element = document.getElementById("infoPanel");
41 | if (element)
42 | element.parentNode.removeChild(element);
43 | var infoPanel = document.createElement("div")
44 | infoPanel.className += 'video-js-info-panel'
45 | infoPanel.id = 'infoPanel'
46 | for (var metric in player.currentStats) {
47 | var div = document.createElement("div")
48 | if (metric == 'buffered') {
49 | var textnode = document.createTextNode(metric + ': ')
50 | div.appendChild(textnode)
51 | var canvas = document.createElement('canvas');
52 | canvas.height = 20
53 | new Graph(player.statsBuffered, canvas, {
54 | background: "rgba(0,0,0,0)",
55 | lineWidth: 2,
56 | lineColor: "#F00"
57 | });
58 | div.appendChild(canvas)
59 | var textnode = document.createTextNode(player.currentStats[metric])
60 | div.appendChild(textnode)
61 | } else {
62 | var textnode = document.createTextNode(metric + ': ' + player.currentStats[metric])
63 | div.appendChild(textnode)
64 | }
65 |
66 | infoPanel.appendChild(div)
67 | }
68 | document.body.appendChild(infoPanel)
69 | }, 250)
70 |
71 | }
72 |
73 | })
74 | }
75 |
76 | videojs.registerPlugin('statistics', statisticsPlugin);
--------------------------------------------------------------------------------
/lib/lightrpc.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.LightRPC=e()}(this,function(){"use strict";function t(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function e(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function r(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}var n=function(n){function o(r,n){t(this,o);var i=e(this,(o.__proto__||Object.getPrototypeOf(o)).call(this,r));return i.name="JSONRPCError",i.error=n,"function"==typeof Error.captureStackTrace?Error.captureStackTrace(i,i.constructor):i.stack=new Error(r).stack,i}return r(o,Error),o}(),o=function(n){function o(r){t(this,o);var n=e(this,(o.__proto__||Object.getPrototypeOf(o)).call(this,r));return n.name="JSONRPCNoResult","function"==typeof Error.captureStackTrace?Error.captureStackTrace(n,n.constructor):n.stack=new Error(r).stack,n}return r(o,Error),o}(),i=Object.assign||function(t){for(var e=1;e1&&void 0!==arguments[1]?arguments[1]:{};if(function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),"string"!=typeof e)throw new Error("InvalidArgument: address has to ba a string");if("object"!==(void 0===r?"undefined":s(r)))throw new Error("InvalidArgument: defaultOptions has to be an object");this.address=e,this.options=i({timeout:5e3,headers:{"Content-Type":"application/json"}},r),this.nextRequestId=0,this.fetchURL=this.fetchURL.bind(this),this.send=this.send.bind(this)}return a(t,[{key:"fetchURL",value:function(t,e){return c(this.address,{body:JSON.stringify(t),headers:e.headers,method:"post",mode:"cors"})}},{key:"send",value:function(t,e,r,a){if("string"!=typeof t)throw new Error("InvalidArgument: method has to be a string");if(!(e instanceof Array))throw new Error("InvalidArgument: params has to be an array");if("function"!=typeof a)throw new Error("InvalidArgument: callback has to be a function");var c={id:this.nextRequestId,jsonrpc:"2.0",method:t,params:e};this.nextRequestId+=1;var u,f,h=this.options;"object"===(void 0===r?"undefined":s(r))&&(h=i({},h,r)),(u=h.timeout,f=this.fetchURL(c,h),new Promise(function(t,e){setTimeout(function(){e(new Error("Request has timed out. It should take no longer than "+u+"ms."))},u),f.then(t,e)})).then(function(t){return t.json()}).then(function(t){if(t.error)throw new n("Response contains error. See error property for details.",t.err);if(!t.result)throw new o("Response doesn't contain result");return t}).then(function(t){return a(null,t.result),t}).catch(function(t){return a(t,null)})}}]),t}()});
--------------------------------------------------------------------------------
/javascripts/providers.js:
--------------------------------------------------------------------------------
1 | var allProviders = [
2 | {id: 'btfs', disp: 'BTFS', dht: 1},
3 | {id: 'ipfs', disp: 'IPFS', dht: 1},
4 | {id: 'sia', disp: 'Skynet', dht: 1},
5 | {id: 'youtube', disp: 'YouTube'},
6 | {id: 'twitch', disp: 'Twitch'},
7 | {id: 'dailymotion', disp: 'Dailymotion'},
8 | {id: 'instagram', disp: 'Instagram'},
9 | {id: 'liveleak', disp: 'LiveLeak'},
10 | {id: 'vimeo', disp: 'Vimeo'},
11 | {id: 'facebook', disp: 'Facebook'},
12 | ]
13 | var failedProviders = []
14 | provider = null
15 | prov = {
16 | idToDisp: function(id) {
17 | for (let i = 0; i < allProviders.length; i++)
18 | if (allProviders[i].id == id)
19 | return allProviders[i].disp
20 | return
21 | },
22 | dispToId: function(disp) {
23 | for (let i = 0; i < allProviders.length; i++)
24 | if (allProviders[i].disp == disp)
25 | return allProviders[i].id
26 | return
27 | },
28 | tryNext: function(video) {
29 | failedProviders.push(provider)
30 | var available = this.available(video)
31 | var reallyAvailable = []
32 | for (let i = 0; i < available.length; i++) {
33 | if (failedProviders.indexOf(available[i]) == -1)
34 | reallyAvailable.push(available[i])
35 | }
36 | if (reallyAvailable.length > 0) {
37 | provider = reallyAvailable[0]
38 | handleVideo(video)
39 | }
40 | },
41 | getDefaultGateway: function(video) {
42 | if (provider == 'IPFS' && video && video.files && video.files.ipfs && video.files.ipfs.gw)
43 | return video.files.ipfs.gw + '/ipfs/'
44 | if (provider == 'BTFS' && video && video.files && video.files.btfs && video.files.btfs.gw)
45 | return video.files.btfs.gw + '/btfs/'
46 | if (provider == 'IPFS') return portals.IPFS[0]
47 | if (provider == 'BTFS') return portals.BTFS[0]
48 | if (provider == 'Skynet') return portals.Skynet[0]
49 | return
50 | },
51 | getFallbackGateway: function() {
52 | if (provider == 'IPFS') return portals.IPFS[1]
53 | if (provider == 'BTFS') return portals.BTFS[1]
54 | if (provider == 'Skynet') return portals.Skynet[1]
55 | },
56 | available: function(video) {
57 | var provs = []
58 | if (video && video.files) {
59 | for (let i = 0; i < allProviders.length; i++) {
60 | if (allProviders[i].dht == 1) {
61 | if (
62 | video.files[allProviders[i].id] &&
63 | video.files[allProviders[i].id].vid &&
64 | Object.keys(video.files[allProviders[i].id].vid).length > 0
65 | ) {
66 | provs.push(allProviders[i].disp)
67 | }
68 | } else {
69 | if (video.files[allProviders[i].id])
70 | provs.push(allProviders[i].disp)
71 | }
72 | }
73 | }
74 | if (video && video.providerName && provs.indexOf(video.providerName) == -1)
75 | provs.push(video.providerName)
76 | return provs
77 | },
78 | default: function(video) {
79 | if (video && video.providerName) return video.providerName
80 | if (video && video.files) {
81 | for (let i = 0; i < allProviders.length; i++) {
82 | if (allProviders[i].dht == 1) {
83 | if (
84 | video.files[allProviders[i].id] &&
85 | video.files[allProviders[i].id].vid &&
86 | Object.keys(video.files[allProviders[i].id].vid).length > 0
87 | ) {
88 | return allProviders[i].disp
89 | }
90 | } else {
91 | if (video.files[allProviders[i].id])
92 | return allProviders[i].disp
93 | }
94 | }
95 | }
96 | return 'IPFS'
97 | }
98 | }
--------------------------------------------------------------------------------
/javascripts/branding.js:
--------------------------------------------------------------------------------
1 | (function(f) {
2 | if (typeof exports === "object" && typeof module !== "undefined") { module.exports = f() } else if (typeof define === "function" && define.amd) { define([], f) } else {
3 | var g;
4 | if (typeof window !== "undefined") { g = window } else if (typeof global !== "undefined") { g = global } else if (typeof self !== "undefined") { g = self } else { g = this }
5 | g.videojsBrand = f()
6 | }
7 | })(function() {
8 | var define, module, exports;
9 | return (function e(t, n, r) {
10 | function s(o, u) {
11 | if (!n[o]) {
12 | if (!t[o]) { var a = typeof require == "function" && require; if (!u && a) return a(o, !0); if (i) return i(o, !0); var f = new Error("Cannot find module '" + o + "'"); throw f.code = "MODULE_NOT_FOUND", f }
13 | var l = n[o] = { exports: {} };
14 | t[o][0].call(l.exports, function(e) { var n = t[o][1][e]; return s(n ? n : e) }, l, l.exports, e, t, n, r)
15 | }
16 | return n[o].exports
17 | }
18 | var i = typeof require == "function" && require;
19 | for (var o = 0; o < r.length; o++) s(r[o]);
20 | return s
21 | })({
22 | 1: [function(require, module, exports) {
23 | (function(global) {
24 | "use strict";
25 |
26 | Object.defineProperty(exports, "__esModule", {
27 | value: true
28 | });
29 |
30 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
31 |
32 | var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
33 |
34 | var _videoJs2 = _interopRequireDefault(_videoJs);
35 |
36 | var defaults = {
37 | branding: true,
38 | qualities: [],
39 | title: "Logo Title",
40 | destination: "http://www.google.com",
41 | destinationTarget: "_blank"
42 | };
43 |
44 | var onPlayerReady = function onPlayerReady(player, options) {
45 |
46 | if (options.branding) {
47 | var containerElement = document.createElement("div");
48 | containerElement.className = "vjs-fullscreen-control vjs-control vjs-button";
49 | containerElement.style = 'padding: 12px 3px 3px 10px; width: 6em;text-align: center;'
50 |
51 | var linkElement = document.createElement("a");
52 | linkElement.className = "vjs-play-control vjs-control vjs-branding-logo";
53 | linkElement.setAttribute("href", options.destination || defaults.destination);
54 | linkElement.setAttribute("title", options.title || defaults.title);
55 | linkElement.setAttribute("target", options.destinationTarget || defaults.destinationTarget);
56 | // linkElement.style = 'width:80px'
57 | containerElement.appendChild(linkElement);
58 |
59 | player.controlBar.el().insertBefore(containerElement, player.controlBar.fullscreenToggle.el());
60 | player.addClass('vjs-brand');
61 | }
62 |
63 | };
64 |
65 | var brand = function brand(options) {
66 | var _this = this;
67 | this.ready(function() {
68 | onPlayerReady(_this, _videoJs2["default"].mergeOptions(defaults, options));
69 | });
70 | };
71 |
72 | _videoJs2["default"].registerPlugin('brand', brand);
73 | brand.VERSION = '0.0.4';
74 |
75 | exports["default"] = brand;
76 | module.exports = exports["default"];
77 | }).call(this, typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
78 | }, {}]
79 | }, {}, [1])(1)
80 | });
--------------------------------------------------------------------------------
/javascripts/ipfsgatewayswitcher.js:
--------------------------------------------------------------------------------
1 | // Modified by techcoderx from resolutionswitcher.js
2 | // https://github.com/dtube/embed/blob/master/javascripts/resolutionswitcher.js
3 |
4 | (function() {
5 | 'use strict';
6 | var videojs = null;
7 | if (typeof window.videojs === 'undefined' && typeof require === 'function') {
8 | videojs = require('video.js');
9 | } else {
10 | videojs = window.videojs;
11 | }
12 |
13 | (function(window, videojs) {
14 | var IPFSGatewaySwitcher,
15 | defaults = {
16 | ui: true
17 | };
18 | const Component = videojs.getComponent('Component');
19 | var MenuItem = videojs.getComponent('MenuItem');
20 | var GatewayMenuItem = videojs.extend(MenuItem, {
21 | constructor: function(player, options) {
22 | options.selectable = true;
23 | MenuItem.call(this, player, options);
24 | this.src = options.src;
25 | }
26 | })
27 | GatewayMenuItem.prototype.handleClick = function(event) {
28 | // Update source in resolution switcher plugin with selected gateway
29 | let newgw = event.target.innerText.replace(', selected','')
30 | let sourcesToChange = player.options_.sources
31 |
32 | console.log(newgw)
33 |
34 | for (let i = 0; i < sourcesToChange.length; i++) {
35 | sourcesToChange[i].src = newgw + sourcesToChange[i].hash
36 | }
37 |
38 | console.log('New sources',sourcesToChange)
39 | console.log('Gateways changed to ' + newgw)
40 | document.getElementsByClassName('vjs-settings-sub-menu-value')[document.getElementsByClassName('vjs-settings-sub-menu-value').length - 1].innerHTML = newgw
41 | player.updateSrc(sourcesToChange)
42 | }
43 |
44 | MenuItem.registerComponent('GatewayMenuItem', GatewayMenuItem);
45 |
46 | var MenuButton = videojs.getComponent('MenuButton');
47 | var GatewaySwitcherMenuButton = videojs.extend(MenuButton, {
48 | constructor: function(player, options) {
49 | this.label = document.createElement('span');
50 | options.label = 'Gateway';
51 | MenuButton.call(this, player, options);
52 | this.el().setAttribute('aria-label', 'Gateway');
53 | this.controlText('Gateway');
54 | this.controlText_ = 'Gateway'
55 | this.addClass('vjs-resolution-switcher')
56 |
57 | videojs.dom.addClass(this.label, 'vjs-resolution-button-label');
58 | this.el().appendChild(this.label);
59 | }
60 | });
61 | GatewaySwitcherMenuButton.prototype.createItems = function() {
62 | let menuItems = [];
63 | for (let i = 0; i < window.portals[provider].length; i++) {
64 | menuItems.push(new GatewayMenuItem(
65 | this.player_, {
66 | label: window.portals[provider][i]
67 | }
68 | ))
69 | }
70 |
71 | return menuItems
72 | }
73 |
74 | GatewaySwitcherMenuButton.prototype.buildCSSClass = function() {
75 | return MenuButton.prototype.buildCSSClass.call(this) + ' vjs-resolution-button';
76 | }
77 |
78 | MenuButton.registerComponent('GatewaySwitcherMenuButton', GatewaySwitcherMenuButton);
79 |
80 | IPFSGatewaySwitcher = function(options) {
81 | let settings = videojs.mergeOptions(defaults, options),
82 | player = this
83 |
84 | player.ready(function() {
85 | if (settings.ui) {
86 | let menuButton = new GatewaySwitcherMenuButton(player, settings)
87 | player.controlBar.gatewaySwitcher = player.controlBar.el_.insertBefore(menuButton.el_, player.controlBar.getChild('fullscreenToggle').el_)
88 | player.controlBar.gatewaySwitcher.dispose = function() {
89 | this.parentNode.removeChild(this)
90 | }
91 | }
92 | })
93 | }
94 |
95 | videojs.registerPlugin('IPFSGatewaySwitcher', IPFSGatewaySwitcher)
96 | })(window, videojs);
97 | })();
--------------------------------------------------------------------------------
/javascripts/plugin.js:
--------------------------------------------------------------------------------
1 | import document from 'global/document';
2 | import videojs from 'video.js';
3 | import ContextMenu from './context-menu';
4 | import {getPointerPosition} from './util';
5 |
6 | // support VJS5 & VJS6 at the same time
7 | const registerPlugin = videojs.registerPlugin || videojs.plugin;
8 |
9 | /**
10 | * Whether or not the player has an active context menu.
11 | *
12 | * @param {Player} player
13 | * @return {Boolean}
14 | */
15 | function hasMenu(player) {
16 | return player.hasOwnProperty('contextmenuUI') &&
17 | player.contextmenuUI.hasOwnProperty('menu') &&
18 | player.contextmenuUI.menu.el();
19 | }
20 |
21 | /**
22 | * Calculates the position of a menu based on the pointer position and player
23 | * size.
24 | *
25 | * @param {Object} pointerPosition
26 | * @param {Object} playerSize
27 | * @return {Object}
28 | */
29 | function findMenuPosition(pointerPosition, playerSize) {
30 | return {
31 | left: Math.round(playerSize.width * pointerPosition.x),
32 | top: Math.round(playerSize.height - (playerSize.height * pointerPosition.y))
33 | };
34 | }
35 |
36 | /**
37 | * Handles vjs-contextmenu events.
38 | *
39 | * @param {Event} e
40 | */
41 | function onVjsContextMenu(e) {
42 |
43 | // If this event happens while the custom menu is open, close it and do
44 | // nothing else. This will cause native contextmenu events to be intercepted
45 | // once again; so, the next time a contextmenu event is encountered, we'll
46 | // open the custom menu.
47 | if (hasMenu(this)) {
48 | this.contextmenuUI.menu.dispose();
49 | return;
50 | }
51 |
52 | // Stop canceling the native contextmenu event until further notice.
53 | this.contextmenu.options.cancel = false;
54 |
55 | // Calculate the positioning of the menu based on the player size and
56 | // triggering event.
57 | const pointerPosition = getPointerPosition(this.el(), e);
58 | const playerSize = this.el().getBoundingClientRect();
59 | const menuPosition = findMenuPosition(pointerPosition, playerSize);
60 |
61 | e.preventDefault();
62 |
63 | const menu = this.contextmenuUI.menu = new ContextMenu(this, {
64 | content: this.contextmenuUI.content,
65 | position: menuPosition
66 | });
67 |
68 | // This is for backward compatibility. We no longer have the `closeMenu`
69 | // function, but removing it would necessitate a major version bump.
70 | this.contextmenuUI.closeMenu = () => {
71 | videojs.warn('player.contextmenuUI.closeMenu() is deprecated, please use player.contextmenuUI.menu.dispose() instead!');
72 | menu.dispose();
73 | };
74 |
75 | menu.on('dispose', () => {
76 | // Begin canceling contextmenu events again, so subsequent events will
77 | // cause the custom menu to be displayed again.
78 | this.contextmenu.options.cancel = true;
79 | videojs.off(document, ['click', 'tap'], menu.dispose);
80 | this.removeChild(menu);
81 | delete this.contextmenuUI.menu;
82 | });
83 |
84 | this.addChild(menu);
85 | videojs.on(document, ['click', 'tap'], menu.dispose);
86 | }
87 |
88 | /**
89 | * Creates a menu for videojs-contextmenu abstract event(s).
90 | *
91 | * @function contextmenuUI
92 | * @param {Object} options
93 | * @param {Array} options.content
94 | * An array of objects which populate a content list within the menu.
95 | */
96 | function contextmenuUI(options) {
97 | if (!Array.isArray(options.content)) {
98 | throw new Error('"content" required');
99 | }
100 |
101 | // If we have already invoked the plugin, teardown before setting up again.
102 | if (hasMenu(this)) {
103 | this.contextmenuUI.menu.dispose();
104 | this.off('vjs-contextmenu', this.contextmenuUI.onVjsContextMenu);
105 |
106 | // Deleting the player-specific contextmenuUI plugin function/namespace will
107 | // restore the original plugin function, so it can be called again.
108 | delete this.contextmenuUI;
109 | }
110 |
111 | // If we are not already providing "vjs-contextmenu" events, do so.
112 | this.contextmenu();
113 |
114 | // Wrap the plugin function with an player instance-specific function. This
115 | // allows us to attach the menu to it without affecting other players on
116 | // the page.
117 | const cmui = this.contextmenuUI = function() {
118 | contextmenuUI.apply(this, arguments);
119 | };
120 |
121 | cmui.onVjsContextMenu = videojs.bind(this, onVjsContextMenu);
122 | cmui.content = options.content;
123 | cmui.VERSION = '__VERSION__';
124 |
125 | this.on('vjs-contextmenu', cmui.onVjsContextMenu);
126 | this.ready(() => this.addClass('vjs-contextmenu-ui'));
127 | }
128 |
129 | registerPlugin('contextmenuUI', contextmenuUI);
130 | contextmenuUI.VERSION = '__VERSION__';
131 |
132 | export default contextmenuUI;
133 |
--------------------------------------------------------------------------------
/javascripts/settingsmenuitem.js:
--------------------------------------------------------------------------------
1 | const MenuItem = videojs.getComponent('MenuItem');
2 | const playbackRateMenuButton = videojs.getComponent('PlaybackRateMenuButton');
3 | const component = videojs.getComponent('Component');
4 |
5 | const toTitleCase = function(string) {
6 | return string.charAt(0).toUpperCase() + string.slice(1);
7 | };
8 |
9 | /**
10 | * The specific menu item type for selecting a setting
11 | *
12 | * @param {Player|Object} player
13 | * @param {Object=} options
14 | * @param {String=} entry
15 | * @extends MenuItem
16 | * @class SettingsMenuItem
17 | */
18 | class SettingsMenuItem extends MenuItem {
19 |
20 | constructor(player, options, entry) {
21 | super(player, options);
22 |
23 | const subMenuName = toTitleCase(entry);
24 |
25 | const SubMenuComponent = videojs.getComponent(subMenuName);
26 |
27 | if (!SubMenuComponent) {
28 | throw new Error(`Component ${subMenuName} does not exist`);
29 | }
30 |
31 | this.subMenu = new SubMenuComponent(this.player(), options);
32 |
33 | const update = videojs.bind(this, this.update);
34 | setTimeout(function() {
35 | update
36 | }, 150);
37 | }
38 |
39 | /**
40 | * Create the component's DOM element
41 | *
42 | * @return {Element}
43 | * @method createEl
44 | */
45 | createEl() {
46 | // Hide this component by default
47 | const el = videojs.dom.createEl('li', {
48 | className: 'vjs-menu-item'
49 | });
50 |
51 | this.settingsSubMenuTitleEl_ = videojs.dom.createEl('div', {
52 | className: 'vjs-settings-sub-menu-title'
53 | });
54 |
55 | el.appendChild(this.settingsSubMenuTitleEl_);
56 |
57 | this.settingsSubMenuValueEl_ = videojs.dom.createEl('div', {
58 | className: 'vjs-settings-sub-menu-value'
59 | });
60 |
61 | el.appendChild(this.settingsSubMenuValueEl_);
62 |
63 | this.settingsSubMenuEl_ = videojs.dom.createEl('div', {
64 | className: 'vjs-settings-sub-menu vjs-hidden'
65 | });
66 |
67 | el.appendChild(this.settingsSubMenuEl_);
68 |
69 | return el;
70 | }
71 |
72 | /**
73 | * Handle click on menu item
74 | *
75 | * @method handleClick
76 | */
77 | handleClick() {
78 | // Remove open class to ensure only the open submenu gets this class
79 | videojs.dom.removeClass(this.el_, 'open');
80 |
81 | super.handleClick();
82 |
83 | // Wether to add or remove vjs-hidden class on the settingsSubMenuEl element
84 | if (videojs.dom.hasClass(this.settingsSubMenuEl_, 'vjs-hidden')) {
85 | videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden');
86 | } else {
87 | videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden');
88 | }
89 | }
90 |
91 | /**
92 | * Update the sub menus
93 | *
94 | * @method update
95 | */
96 | update() {
97 | this.settingsSubMenuTitleEl_.innerHTML = this.subMenu.controlText_ + ':';
98 | this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_);
99 |
100 | // Playback rate menu button doesn't get a vjs-selected class
101 | // or sets options_['selected'] on the selected playback rate.
102 | // Thus we get the submenu value based on the labelEl of playbackRateMenuButton
103 | if (this.subMenu instanceof playbackRateMenuButton) {
104 | this.settingsSubMenuValueEl_.innerHTML = this.subMenu.labelEl_.innerHTML;
105 | } else {
106 | // Loop trough the submenu items to find the selected child
107 | for (let subMenuItem of this.subMenu.menu.children_) {
108 | if (!(subMenuItem instanceof component)) {
109 | continue;
110 | }
111 | // Set submenu value based on what item is selected
112 | if (subMenuItem.options_.selected || subMenuItem.hasClass('vjs-selected')) {
113 | this.settingsSubMenuValueEl_.innerHTML = subMenuItem.options_.label.replace('subtitles ', '');
114 | }
115 | }
116 | }
117 |
118 | //console.log(this.subMenu.menu.children())
119 | for (let item of this.subMenu.menu.children()) {
120 | if (!(item instanceof component)) {
121 | continue;
122 | }
123 | if (!item.isClickInit) {
124 | item.isClickInit = true
125 | var menu = this
126 | item.on('click', function() {
127 | menu.update()
128 | });
129 | }
130 |
131 | }
132 | }
133 |
134 | /**
135 | * Hide the sub menu
136 | */
137 | hideSubMenu() {
138 | videojs.dom.addClass(this.el_.childNodes[2], 'vjs-hidden');
139 | videojs.dom.removeClass(this.el_, 'open');
140 | if (this.el_.childNodes[2].childNodes[0])
141 | videojs.dom.removeClass(this.el_.childNodes[2].childNodes[0], 'vjs-lock-showing');
142 | }
143 |
144 | }
145 |
146 | SettingsMenuItem.prototype.contentElType = 'button';
147 |
148 | videojs.registerComponent('SettingsMenuItem', SettingsMenuItem);
149 | // export default SettingsMenuItem;
150 |
--------------------------------------------------------------------------------
/javascripts/hotkeys.js:
--------------------------------------------------------------------------------
1 | /* videojs-hotkeys v0.2.20 - https://github.com/ctd1500/videojs-hotkeys */ ! function(e, t) { "function" == typeof define && define.amd ? define("videojs-hotkeys", ["video.js"], function(e) { return t(e.default || e) }) : "undefined" != typeof module && module.exports ? module.exports = t(require("video.js")) : t(videojs) }(0, function(e) {
2 | "use strict";
3 | "undefined" != typeof window && (window.videojs_hotkeys = { version: "0.2.20" });
4 | (e.registerPlugin || e.plugin)("hotkeys", function(t) {
5 | var r = this,
6 | n = r.el(),
7 | o = document,
8 | u = { volumeStep: .1, seekStep: 5, enableMute: !0, enableVolumeScroll: !1, enableFullscreen: !0, enableNumbers: !0, enableJogStyle: !1, alwaysCaptureHotkeys: !1, enableModifiersForNumbers: !0, enableInactiveFocus: !0, skipInitialFocus: !1, playPauseKey: function(e) { return 32 === e.which || 179 === e.which }, rewindKey: function(e) { return 37 === e.which || 177 === e.which }, forwardKey: function(e) { return 39 === e.which || 176 === e.which }, volumeUpKey: function(e) { return 38 === e.which }, volumeDownKey: function(e) { return 40 === e.which }, muteKey: function(e) { return 77 === e.which }, fullscreenKey: function(e) { return 70 === e.which }, customKeys: {} },
9 | l = e.mergeOptions || e.util.mergeOptions,
10 | i = (t = l(u, t || {})).volumeStep,
11 | a = t.seekStep,
12 | c = t.enableMute,
13 | s = t.enableVolumeScroll,
14 | m = t.enableFullscreen,
15 | y = t.enableNumbers,
16 | f = t.enableJogStyle,
17 | v = t.alwaysCaptureHotkeys,
18 | d = t.enableModifiersForNumbers,
19 | p = t.enableInactiveFocus,
20 | b = t.skipInitialFocus;
21 | n.hasAttribute("tabIndex") || n.setAttribute("tabIndex", "-1"), n.style.outline = "none", !v && r.autoplay() || b || r.one("play", function() { n.focus() }), p && r.on("userinactive", function() {
22 | var e = function() { clearTimeout(t) },
23 | t = setTimeout(function() { r.off("useractive", e), o.activeElement.parentElement == n.querySelector(".vjs-control-bar") && n.focus() }, 10);
24 | r.one("useractive", e)
25 | }), r.on("play", function() {
26 | var e = n.querySelector(".iframeblocker");
27 | e && "" === e.style.display && (e.style.display = "block", e.style.bottom = "39px")
28 | });
29 | var h = function(e) {
30 | if (r.controls()) {
31 | if (r.muted()) {
32 | r.muted(!r.muted())
33 | var t = e.relatedTarget || e.toElement || o.activeElement;
34 | if ((v || t == n || t == n.querySelector(".vjs-tech") || t == n.querySelector(".iframeblocker") || t == n.querySelector(".vjs-control-bar")) && s) {
35 | e = window.event || e;
36 | var u = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail));
37 | e.preventDefault(), 1 == u ? r.volume(r.volume() + i) : -1 == u && r.volume(r.volume() - i)
38 | }
39 | } else {
40 | var t = e.relatedTarget || e.toElement || o.activeElement;
41 | if ((v || t == n || t == n.querySelector(".vjs-tech") || t == n.querySelector(".iframeblocker") || t == n.querySelector(".vjs-control-bar")) && s) {
42 | e = window.event || e;
43 | var u = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail));
44 | e.preventDefault(), 1 == u ? r.volume(r.volume() + i) : -1 == u && r.volume(r.volume() - i)
45 | }
46 | }
47 | }
48 | },
49 | w = function(e, r) { return t.playPauseKey(e, r) ? 1 : t.rewindKey(e, r) ? 2 : t.forwardKey(e, r) ? 3 : t.volumeUpKey(e, r) ? 4 : t.volumeDownKey(e, r) ? 5 : t.muteKey(e, r) ? 6 : t.fullscreenKey(e, r) ? 7 : void 0 };
50 | return r.on("keydown", function(e) {
51 | var u, l, s = e.which,
52 | p = e.preventDefault,
53 | b = r.duration();
54 | if (r.controls()) {
55 | var h = o.activeElement;
56 | if (v || h == n || h == n.querySelector(".vjs-tech") || h == n.querySelector(".vjs-control-bar") || h == n.querySelector(".iframeblocker")) switch (w(e, r)) {
57 | case 1:
58 | p(), v && e.stopPropagation(), r.paused() ? r.play() : r.pause();
59 | break;
60 | case 2:
61 | u = !r.paused(), p(), u && r.pause(), l = r.currentTime() - a, r.currentTime() <= a && (l = 0), r.currentTime(l), u && r.play();
62 | break;
63 | case 3:
64 | u = !r.paused(), p(), u && r.pause(), (l = r.currentTime() + a) >= b && (l = u ? b - .001 : b), r.currentTime(l), u && r.play();
65 | break;
66 | case 5:
67 | p(), f ? (l = r.currentTime() - 1, r.currentTime() <= 1 && (l = 0), r.currentTime(l)) : r.volume(r.volume() - i);
68 | break;
69 | case 4:
70 | p(), f ? ((l = r.currentTime() + 1) >= b && (l = b), r.currentTime(l)) : r.volume(r.volume() + i);
71 | break;
72 | case 6:
73 | c && r.muted(!r.muted());
74 | break;
75 | case 7:
76 | m && (r.isFullscreen() ? r.exitFullscreen() : r.requestFullscreen());
77 | r.play();
78 | break;
79 | default:
80 | if ((s > 47 && s < 59 || s > 95 && s < 106) && (d || !(e.metaKey || e.ctrlKey || e.altKey)) && y) {
81 | var k = 48;
82 | s > 95 && (k = 96);
83 | var K = s - k;
84 | p(), r.currentTime(r.duration() * K * .1)
85 | }
86 | for (var S in t.customKeys) {
87 | var T = t.customKeys[S];
88 | T && T.key && T.handler && T.key(e) && (p(), T.handler(r, t, e))
89 | }
90 | }
91 | }
92 | }), r.on("dblclick", function(e) {
93 | if (r.controls()) {
94 | var t = e.relatedTarget || e.toElement || o.activeElement;
95 | t != n && t != n.querySelector(".vjs-tech") && t != n.querySelector(".iframeblocker") || m && (r.isFullscreen() ? r.exitFullscreen() : r.requestFullscreen())
96 | r.play();
97 | }
98 | }), r.on("mousewheel", h), r.on("DOMMouseScroll", h), this
99 | })
100 | });
101 |
--------------------------------------------------------------------------------
/javascripts/graph.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | function Graph(data, canvas, options) {
3 | this.options = {
4 | paddingBottom: 0,
5 | paddingLeft: 0,
6 | paddingRight: 0,
7 | paddingTop: 0,
8 | showCircle: false,
9 | circle: "#55AA77",
10 | circleSize: 4,
11 | background: "#FFFFFF",
12 | showZeroLine: false,
13 | centerZero: false,
14 | zeroLineColor: "#EEE",
15 | lineColor: "#C5C5C5",
16 | lineWidth: 3,
17 | showBounds: false,
18 | bounds: "#8888EE",
19 | boundsHeight: 14,
20 | boundsFont: "Arial",
21 | showLegend: false,
22 | legend: "#8888EE",
23 | legendHeight: 14,
24 | legendFont: "Arial",
25 | };
26 |
27 | if (typeof data !== 'object' && !Array.isArray(data)) throw new Error('Data is not an array');
28 | if (!canvas || !canvas.nodeName || canvas.nodeName !== "CANVAS") throw new Error('Canvas not defined');
29 |
30 |
31 | this.data = !!data.data ? data.data : data;
32 | this.legend = !!data.data ? data.legend : !1;
33 | this.canvas = canvas;
34 | this.context = canvas.getContext('2d');
35 |
36 | if (options) {
37 | for (var key in options) {
38 | if (options.hasOwnProperty(key)) this.options[key] = options[key];
39 | }
40 | }
41 |
42 | this.init();
43 | this.draw();
44 | }
45 |
46 | /**
47 | * Init graph
48 | */
49 | Graph.prototype.init = function() {
50 | var self = this;
51 |
52 | if (self.options.parentSize) {
53 | self.canvas.height = self.canvas.parentElement.offsetHeight;
54 | self.canvas.width = self.canvas.parentElement.offsetWidth;
55 | }
56 |
57 | if (self.options.showLegend) {
58 | self.options.paddingLeft = self.options.paddingLeft || self.options.legendHeight * 2;
59 | self.options.paddingRight = self.options.paddingRight || self.options.legendHeight * 2;
60 | self.options.paddingBottom = self.options.paddingBottom || self.options.legendHeight * 2.5;
61 | }
62 |
63 | if (self.options.showBounds) {
64 | self.options.paddingLeft = self.options.paddingLeft || self.options.boundsHeight / 1.4;
65 | self.options.paddingTop = self.options.paddingTop || self.options.boundsHeight * 1.4;
66 | self.options.paddingBottom = self.options.paddingBottom || self.options.boundsHeight * 1.4;
67 | }
68 |
69 | if (self.options.showCircle) {
70 | self.options.paddingRight = self.options.paddingRight || self.options.circleSize;
71 | self.options.paddingTop = self.options.paddingTop || self.options.circleSize;
72 | self.options.paddingBottom = self.options.paddingBottom || self.options.circleSize;
73 | }
74 |
75 |
76 | self.options.paddingTop = self.options.paddingTop || self.options.lineWidth;
77 | self.options.paddingBottom = self.options.paddingBottom || self.options.lineWidth;
78 | }
79 |
80 | /**
81 | * Draw the graph
82 | */
83 | Graph.prototype.draw = function() {
84 | var self = this;
85 |
86 | self.drawBackground();
87 | self.computeScale();
88 | self.drawMiddle();
89 | self.drawScale();
90 | self.drawData();
91 | self.drawCircle();
92 | };
93 |
94 | /**
95 | * Draw canvas brackground
96 | */
97 | Graph.prototype.drawBackground = function() {
98 | this.context.fillStyle = this.options.background;
99 | this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
100 | };
101 |
102 | /**
103 | * Compute scale for given canvas size
104 | */
105 | Graph.prototype.computeScale = function() {
106 | var self = this,
107 | height = self.canvas.height,
108 | width = self.canvas.width;
109 |
110 | self.maxPositive = Math.max.apply(null, self.data);
111 | self.maxNegative = Math.min.apply(null, self.data);
112 |
113 | if (self.maxPositive == self.maxNegative) {
114 | if (self.maxPositive == 0) self.maxPositive = 1;
115 | if (self.maxPositive > 0) self.maxNegative = 0;
116 | if (self.maxPositive < 0) self.maxPositive = 0;
117 | }
118 |
119 | self.maxPositive = Math.abs(self.maxPositive);
120 | self.maxNegative = Math.abs(self.maxNegative);
121 | self.max = Math.max(self.maxPositive, self.maxNegative);
122 |
123 | height -= self.options.paddingTop + self.options.paddingBottom;
124 | width -= self.options.paddingLeft + self.options.paddingRight;
125 |
126 | self.horizontalScale = width / (self.data.length - 1);
127 |
128 | if (self.options.centerZero) {
129 | self.middle = Math.round(height / 2);
130 | self.verticalScale = self.middle / self.max;
131 | } else {
132 | self.verticalScale = height / (self.maxPositive - self.maxNegative);
133 | self.middle = Math.round(self.maxPositive * self.verticalScale);
134 | }
135 | };
136 |
137 | /**
138 | * Draw middle line of a graph
139 | */
140 | Graph.prototype.drawMiddle = function() {
141 | var self = this;
142 |
143 | if (!self.options.showZeroLine) return;
144 |
145 | self.context.moveTo(self.options.paddingLeft, self.middle + self.options.paddingTop);
146 | self.context.lineTo(self.canvas.width - self.options.paddingRight, self.middle + self.options.paddingTop);
147 | self.context.strokeStyle = self.options.zeroLineColor;
148 | self.context.stroke();
149 | };
150 |
151 | /**
152 | * Draw scale line
153 | */
154 | Graph.prototype.drawScale = function() {
155 | var self = this;
156 |
157 | if (!self.options.showBounds) return;
158 |
159 | self.context.moveTo(self.options.paddingLeft, self.options.paddingTop);
160 | self.context.lineTo(self.options.paddingLeft, self.canvas.height - self.options.paddingBottom);
161 | self.context.strokeStyle = self.options.zeroLineColor;
162 | self.context.stroke();
163 | };
164 |
165 | /**
166 | * Draw data line
167 | */
168 | Graph.prototype.drawData = function() {
169 | var self = this,
170 | i = self.data.length - 2;
171 |
172 | self.context.strokeStyle = self.options.lineColor;
173 | self.context.lineWidth = self.options.lineWidth;
174 |
175 | self.context.beginPath();
176 | self.context.moveTo.apply(self.context, self.getPointCoordinates(self.data.length - 1));
177 |
178 | for (; i >= 0; i--) {
179 | self.context.lineTo.apply(self.context, self.getPointCoordinates(i));
180 | }
181 |
182 | self.context.stroke();
183 | };
184 |
185 | /**
186 | * Compute coordinates for asked data index
187 | * @param {index} index
188 | * @return {array}
189 | */
190 | Graph.prototype.getPointCoordinates = function(index) {
191 | return [
192 | this.options.paddingLeft + (index * this.horizontalScale),
193 | (this.middle + this.options.paddingTop) - (this.verticalScale * this.data[index])
194 | ]
195 | };
196 |
197 | /**
198 | * Draw circle to the end of the graph
199 | */
200 | Graph.prototype.drawCircle = function() {
201 | var self = this;
202 |
203 | if (!self.options.showCircle) return;
204 |
205 | var lastPoint = self.getPointCoordinates(self.data.length - 1);
206 |
207 | self.context.fillStyle = self.options.circle;
208 | self.context.beginPath();
209 | self.context.arc(lastPoint[0], lastPoint[1], self.options.circleSize, 0, 2 * Math.PI);
210 | self.context.closePath();
211 | self.context.fill();
212 | };
213 |
214 | this.Graph = Graph;
215 | })(this);
--------------------------------------------------------------------------------
/javascripts/thumbnails.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var defaults = {
3 | 0: {
4 | src: 'no-thumbnail.png',
5 | style: {
6 | clip: 'rect(0, 230px, 68px, 0)'
7 | }
8 |
9 | }
10 | },
11 | extend = function() {
12 | var args, target, i, object, property;
13 | args = Array.prototype.slice.call(arguments);
14 | target = args.shift() || {};
15 | for (i in args) {
16 | object = args[i];
17 | for (property in object) {
18 | if (object.hasOwnProperty(property)) {
19 | if (typeof object[property] === 'object') {
20 | target[property] = extend(target[property], object[property]);
21 | } else {
22 | target[property] = object[property];
23 | }
24 | }
25 | }
26 | }
27 | return target;
28 | },
29 | getComputedStyle = function(el, pseudo) {
30 | return function(prop) {
31 | if (window.getComputedStyle) {
32 | return window.getComputedStyle(el, pseudo)[prop];
33 | } else {
34 | return el.currentStyle[prop];
35 | }
36 | };
37 | },
38 | offsetParent = function(el) {
39 | if (el.nodeName !== 'HTML' && getComputedStyle(el)('position') === 'static') {
40 | return offsetParent(el.offsetParent);
41 | }
42 | return el;
43 | },
44 | getVisibleWidth = function(el, width) {
45 | var clip;
46 |
47 | if (width) {
48 | console.log(width)
49 | return parseFloat(width);
50 | }
51 |
52 | clip = getComputedStyle(el)('clip');
53 | if (clip !== 'auto' && clip !== 'inherit') {
54 | clip = clip.split(/(?:\(|\))/)[1].split(/(?:,| )/);
55 | if (clip.length === 4) {
56 | return (parseFloat(clip[1]) - parseFloat(clip[3]));
57 | }
58 | }
59 | return 0;
60 | },
61 | getScrollOffset = function() {
62 | if (window.pageXOffset) {
63 | console.log(window.pageXOffset)
64 | return {
65 | x: window.pageXOffset,
66 | y: window.pageYOffset
67 | };
68 | }
69 | return {
70 | x: document.documentElement.scrollLeft,
71 | y: document.documentElement.scrollTop
72 | };
73 | };
74 |
75 | /**
76 | * register the thubmnails plugin
77 | */
78 | videojs.registerPlugin('thumbnails', function(options) {
79 | var div, settings, img, player, progressControl, duration, moveListener, moveCancel;
80 | settings = extend({}, defaults, options);
81 | player = this;
82 |
83 | (function() {
84 | var progressControl, addFakeActive, removeFakeActive;
85 | // Android doesn't support :active and :hover on non-anchor and non-button elements
86 | // so, we need to fake the :active selector for thumbnails to show up.
87 | if (navigator.userAgent.toLowerCase().indexOf("android") !== -1) {
88 | progressControl = player.controlBar.progressControl;
89 |
90 | addFakeActive = function() {
91 | progressControl.addClass('fake-active');
92 | };
93 | removeFakeActive = function() {
94 | progressControl.removeClass('fake-active');
95 | };
96 |
97 | progressControl.on('touchstart', addFakeActive);
98 | progressControl.on('touchend', removeFakeActive);
99 | progressControl.on('touchcancel', removeFakeActive);
100 | }
101 | })();
102 |
103 | // create the thumbnail
104 | div = document.createElement('div');
105 | div.className = 'vjs-thumbnail-holder';
106 | img = document.createElement('img');
107 | div.appendChild(img);
108 | img.src = settings['0'].src;
109 | img.className = 'vjs-thumbnail';
110 | extend(img.style, settings['0'].style);
111 |
112 | // center the thumbnail over the cursor if an offset wasn't provided
113 | if (!img.style.left && !img.style.right) {
114 | img.onload = function() {
115 | img.style.left = -(img.naturalWidth / 2) + 'px';
116 | };
117 | }
118 |
119 | // keep track of the duration to calculate correct thumbnail to display
120 | duration = player.duration();
121 |
122 | // when the container is MP4
123 | player.on('durationchange', function(event) {
124 | duration = player.duration();
125 | });
126 |
127 | // when the container is HLS
128 | player.on('loadedmetadata', function(event) {
129 | duration = player.duration();
130 | });
131 |
132 | // add the thumbnail to the player
133 | progressControl = player.controlBar.progressControl;
134 | progressControl.el().appendChild(div);
135 |
136 | moveListener = function(event) {
137 | var mouseTime, time, active, left, setting, pageX, right, width, halfWidth, pageXOffset, clientRect;
138 | active = 0;
139 | pageXOffset = getScrollOffset().x;
140 | clientRect = offsetParent(progressControl.el()).getBoundingClientRect();
141 | right = (clientRect.width || clientRect.right) + pageXOffset;
142 |
143 | pageX = event.pageX;
144 | if (event.changedTouches) {
145 | pageX = event.changedTouches[0].pageX;
146 | }
147 |
148 | // find the page offset of the mouse
149 | left = pageX || (event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft);
150 | // subtract the page offset of the positioned offset parent
151 | left -= offsetParent(progressControl.el()).getBoundingClientRect().left + pageXOffset;
152 |
153 | // apply updated styles to the thumbnail if necessary
154 | // mouseTime is the position of the mouse along the progress control bar
155 | // `left` applies to the mouse position relative to the player so we need
156 | // to remove the progress control's left offset to know the mouse position
157 | // relative to the progress control
158 | mouseTime = Math.floor((left - progressControl.el().offsetLeft) / progressControl.width() * duration);
159 | for (time in settings) {
160 | if (mouseTime > time) {
161 | active = Math.max(active, time);
162 | }
163 | }
164 | setting = settings[active];
165 | if (setting.src && img.src != setting.src) {
166 | img.src = setting.src;
167 | }
168 | if (setting.style && img.style != setting.style) {
169 | extend(img.style, setting.style);
170 | }
171 |
172 | width = getVisibleWidth(img, setting.width || settings[0].width);
173 | halfWidth = width / 2;
174 |
175 | // make sure that the thumbnail doesn't fall off the right side of the left side of the player
176 | if ((left + halfWidth) > right) {
177 | left -= (left + halfWidth) - right;
178 | } else if (left < halfWidth) {
179 | left = halfWidth;
180 | }
181 |
182 | div.style.left = left + 'px';
183 | };
184 |
185 | // update the thumbnail while hovering
186 | // progressControl.on('mouseenter', moveListener);
187 | progressControl.on('mousemove', moveListener);
188 | progressControl.on('touchmove', moveListener);
189 |
190 | moveCancel = function(event) {
191 | div.style = 'position: absolute;bottom: 60px;max-height: 72px;width: 128px; margin-left:-64px;overflow: hidden;';
192 | };
193 |
194 | // move the placeholder out of the way when not hovering
195 | progressControl.on('mouseout', moveCancel);
196 | progressControl.on('touchcancel', moveCancel);
197 | progressControl.on('touchend', moveCancel);
198 | player.on('userinactive', moveCancel);
199 | });
200 | })();
--------------------------------------------------------------------------------
/javascripts/resolutionswitcher.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 | var videojs = null;
4 | if (typeof window.videojs === 'undefined' && typeof require === 'function') {
5 | videojs = require('video.js');
6 | } else {
7 | videojs = window.videojs;
8 | }
9 |
10 | (function(window, videojs) {
11 | var videoJsResolutionSwitcher,
12 | defaults = {
13 | ui: true
14 | };
15 | const Component = videojs.getComponent('Component');
16 | var MenuItem = videojs.getComponent('MenuItem');
17 | var ResolutionMenuItem = videojs.extend(MenuItem, {
18 | constructor: function(player, options) {
19 | options.selectable = true;
20 | MenuItem.call(this, player, options);
21 | this.src = options.src;
22 |
23 | player.on('resolutionchange', videojs.bind(this, this.update));
24 | }
25 | });
26 | ResolutionMenuItem.prototype.handleClick = function(event) {
27 | MenuItem.prototype.handleClick.call(this, event);
28 | this.player_.currentResolution(this.options_.label);
29 | setStorageItem('dquality', this.options_.label);
30 | };
31 | ResolutionMenuItem.prototype.update = function() {
32 | var selection = this.player_.currentResolution();
33 | this.selected(this.options_.label === selection.label);
34 | };
35 | MenuItem.registerComponent('ResolutionMenuItem', ResolutionMenuItem);
36 |
37 | var MenuButton = videojs.getComponent('MenuButton');
38 | var ResolutionMenuButton = videojs.extend(MenuButton, {
39 | constructor: function(player, options) {
40 | this.label = document.createElement('span');
41 | options.label = 'Quality';
42 | MenuButton.call(this, player, options);
43 | this.el().setAttribute('aria-label', 'Quality');
44 | this.controlText('Quality');
45 | this.controlText_ = 'Quality'
46 | this.addClass('vjs-resolution-switcher')
47 |
48 | videojs.dom.addClass(this.label, 'vjs-resolution-button-label');
49 |
50 | this.label.addEventListener("click", function() {
51 | var cur = player.currentResolutionState.label
52 | var i = 1;
53 | while (cur != player.currentSources[i - 1].label)
54 | i++
55 | if (i == player.currentSources.length)
56 | i = 0
57 | player.currentResolution(player.currentSources[i].label)
58 | });
59 | this.el().appendChild(this.label);
60 |
61 | player.on('updateSources', videojs.bind(this, this.update));
62 | player.on('resolutionchange', videojs.bind(this, this.update));
63 | }
64 | });
65 | ResolutionMenuButton.prototype.createItems = function() {
66 | var menuItems = [];
67 | var labels = (this.sources && this.sources.label) || {};
68 |
69 | for (var key in labels) {
70 | if (labels.hasOwnProperty(key)) {
71 | menuItems.push(new ResolutionMenuItem(
72 | this.player_, {
73 | label: key,
74 | src: labels[key],
75 | selected: key === (this.currentSelection ? this.currentSelection.label : false)
76 | }));
77 | }
78 | }
79 | return menuItems;
80 | };
81 | ResolutionMenuButton.prototype.update = function() {
82 | this.sources = this.player_.getGroupedSrc();
83 | this.currentSelection = this.player_.currentResolution();
84 | this.label.innerHTML = this.currentSelection ? this.currentSelection.label : '';
85 | return MenuButton.prototype.update.call(this);
86 | };
87 | ResolutionMenuButton.prototype.buildCSSClass = function() {
88 | return MenuButton.prototype.buildCSSClass.call(this) + ' vjs-resolution-button';
89 | };
90 | MenuButton.registerComponent('ResolutionMenuButton', ResolutionMenuButton);
91 |
92 | videoJsResolutionSwitcher = function(options) {
93 | var settings = videojs.mergeOptions(defaults, options),
94 | player = this,
95 | groupedSrc = {},
96 | currentSources = {},
97 | currentResolutionState = {};
98 |
99 | player.updateSrc = function(src) {
100 | if (!src) { return player.src(); }
101 |
102 | src = src.filter(function(source) {
103 | try {
104 | return (player.canPlayType(source.type) !== '');
105 | } catch (e) {
106 | return true;
107 | }
108 | });
109 | this.currentSources = src.sort(compareResolutions);
110 | this.groupedSrc = bucketSources(this.currentSources);
111 | var chosen = chooseSrc(this.groupedSrc, this.currentSources);
112 | this.currentResolutionState = {
113 | label: chosen.label,
114 | sources: chosen.sources
115 | };
116 | chosen.sources = []
117 | for (let i = 0; i < this.currentSources.length; i++) {
118 | if (this.currentSources[i].label == chosen.label)
119 | chosen.sources.push(this.currentSources[i])
120 | }
121 |
122 | player.trigger('updateSources');
123 | player.setSourcesSanitized(chosen.sources, chosen.label);
124 | player.trigger('resolutionchange');
125 | return player;
126 | };
127 |
128 | player.currentResolution = function(label, customSourcePicker) {
129 | if (label == null) { return this.currentResolutionState; }
130 |
131 | if (!this.groupedSrc || !this.groupedSrc.label || !this.groupedSrc.label[label]) {
132 | return;
133 | }
134 | var sources = this.groupedSrc.label[label];
135 | var currentTime = player.currentTime();
136 | var isPaused = player.paused();
137 |
138 | if (!isPaused && this.player_.options_.bigPlayButton) {
139 | this.player_.bigPlayButton.hide();
140 | }
141 |
142 | var handleSeekEvent = 'loadeddata';
143 | if (this.player_.preload() === 'none') {
144 | handleSeekEvent = 'timeupdate';
145 | }
146 | player
147 | .setSourcesSanitized(sources, label, customSourcePicker || settings.customSourcePicker)
148 | .one(handleSeekEvent, function() {
149 | player.currentTime(currentTime);
150 | player.handleTechSeeked_();
151 | if (!isPaused) {
152 | player.play();
153 | }
154 | player.trigger('resolutionchange');
155 | });
156 | return player;
157 | };
158 |
159 | player.getGroupedSrc = function() {
160 | return this.groupedSrc;
161 | };
162 |
163 | player.setSourcesSanitized = function(sources, label, customSourcePicker) {
164 | this.currentResolutionState = {
165 | label: label,
166 | sources: sources
167 | };
168 | if (typeof customSourcePicker === 'function') {
169 | return customSourcePicker(player, sources, label);
170 | }
171 | player.src(sources.map(function(src) {
172 | return { src: src.src, type: src.type, res: src.res };
173 | }));
174 | return player;
175 | };
176 |
177 | function compareResolutions(a, b) {
178 | if (!a.res || !b.res) { return 0; }
179 | return (+b.res) - (+a.res);
180 | }
181 |
182 | function bucketSources(src) {
183 | var resolutions = {
184 | label: {},
185 | res: {},
186 | type: {}
187 | };
188 | src.map(function(source) {
189 | initResolutionKey(resolutions, 'label', source);
190 | initResolutionKey(resolutions, 'res', source);
191 | initResolutionKey(resolutions, 'type', source);
192 |
193 | appendSourceToKey(resolutions, 'label', source);
194 | appendSourceToKey(resolutions, 'res', source);
195 | appendSourceToKey(resolutions, 'type', source);
196 | });
197 | return resolutions;
198 | }
199 |
200 | function initResolutionKey(resolutions, key, source) {
201 | if (resolutions[key][source[key]] == null) {
202 | resolutions[key][source[key]] = [];
203 | }
204 | }
205 |
206 | function appendSourceToKey(resolutions, key, source) {
207 | resolutions[key][source[key]].push(source);
208 | }
209 |
210 | function chooseSrc(groupedSrc, src) {
211 | var obj = {
212 | res: settings['default'],
213 | label: settings['default'],
214 | sources: groupedSrc.res[settings['default']]
215 | }
216 | return obj;
217 | }
218 |
219 |
220 | player.ready(function() {
221 | if (settings.ui) {
222 | var menuButton = new ResolutionMenuButton(player, settings);
223 | player.controlBar.resolutionSwitcher = player.controlBar.el_.insertBefore(menuButton.el_, player.controlBar.getChild('fullscreenToggle').el_);
224 | player.controlBar.resolutionSwitcher.dispose = function() {
225 | this.parentNode.removeChild(this);
226 | };
227 | }
228 | if (player.options_.sources.length > 1) {
229 | player.updateSrc(player.options_.sources);
230 | }
231 | });
232 |
233 | };
234 |
235 | videojs.registerPlugin('videoJsResolutionSwitcher', videoJsResolutionSwitcher);
236 | })(window, videojs);
237 | })();
--------------------------------------------------------------------------------
/lib/playerjs.min.js:
--------------------------------------------------------------------------------
1 | !function(a,b){function c(a){return function(){var b={method:a},c=Array.prototype.slice.call(arguments);/^get/.test(a)?(d.assert(c.length>0,"Get methods require a callback."),c.unshift(b)):(/^set/.test(a)&&(d.assert(0!==c.length,"Set methods require a value."),b.value=c[0]),c=[b]),this.send.apply(this,c)}}var d={};d.DEBUG=!1,d.VERSION="0.0.11",d.CONTEXT="player.js",d.POST_MESSAGE=!!a.postMessage,d.origin=function(b){return"//"===b.substr(0,2)&&(b=a.location.protocol+b),b.split("/").slice(0,3).join("/")},d.addEvent=function(a,b,c){a&&(a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent?a.attachEvent("on"+b,c):a["on"+b]=c)},d.log=function(){d.log.history=d.log.history||[],d.log.history.push(arguments),a.console&&d.DEBUG&&a.console.log(Array.prototype.slice.call(arguments))},d.isString=function(a){return"[object String]"===Object.prototype.toString.call(a)},d.isObject=function(a){return"[object Object]"===Object.prototype.toString.call(a)},d.isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)},d.isNone=function(a){return null===a||void 0===a},d.has=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)},d.indexOf=function(a,b){if(null==a)return-1;var c=0,d=a.length;if(Array.prototype.IndexOf&&a.indexOf===Array.prototype.IndexOf)return a.indexOf(b);for(;d>c;c++)if(a[c]===b)return c;return-1},d.assert=function(a,b){if(!a)throw b||"Player.js Assert Failed"},d.Keeper=function(){this.init()},d.Keeper.prototype.init=function(){this.data={}},d.Keeper.prototype.getUUID=function(){return"listener-xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g,function(a){var b=16*Math.random()|0,c="x"===a?b:3&b|8;return c.toString(16)})},d.Keeper.prototype.has=function(a,b){if(!this.data.hasOwnProperty(a))return!1;if(d.isNone(b))return!0;for(var c=this.data[a],e=0;e-1?f.loaded=!0:this.elem.onload=function(){f.loaded=!0}},d.Player.prototype.send=function(a,b,c){if(a.context=d.CONTEXT,a.version=d.VERSION,b){var e=this.keeper.getUUID();a.listener=e,this.keeper.one(e,a.method,b,c)}return this.isReady||"ready"===a.value?(d.log("Player.send",a,this.origin),this.loaded===!0&&this.elem.contentWindow.postMessage(JSON.stringify(a),this.origin),!0):(d.log("Player.queue",a),this.queue.push(a),!1)},d.Player.prototype.receive=function(a){if(d.log("Player.receive",a),a.origin!==this.origin)return!1;var b;try{b=JSON.parse(a.data)}catch(c){return!1}return b.context!==d.CONTEXT?!1:("ready"===b.event&&b.value&&b.value.src===this.elem.src&&this.ready(b),void(this.keeper.has(b.event,b.listener)&&this.keeper.execute(b.event,b.listener,b.value,this)))},d.Player.prototype.ready=function(a){if(this.isReady===!0)return!1;a.value.events&&(this.events=a.value.events),a.value.methods&&(this.methods=a.value.methods),this.isReady=!0,this.loaded=!0;for(var b=0;b0)for(var e in c)return this.send({method:"removeEventListener",value:a,listener:c[e]}),!0;return!1},d.Player.prototype.supports=function(a,b){d.assert(d.indexOf(["method","event"],a)>-1,'evtOrMethod needs to be either "event" or "method" got '+a),b=d.isArray(b)?b:[b];for(var c="event"===a?this.events:this.methods,e=0;ee;e++){var g=d.METHODS.all()[e];d.Player.prototype.hasOwnProperty(g)||(d.Player.prototype[g]=c(g))}d.addEvent(a,"message",function(a){var b;try{b=JSON.parse(a.data)}catch(c){return!1}return b.context!==d.CONTEXT?!1:void("ready"===b.event&&b.value&&b.value.src&&d.READIED.push(b.value.src))}),d.Receiver=function(a,b){this.init(a,b)},d.Receiver.prototype.init=function(c,e){var f=this;this.isReady=!1,this.origin=d.origin(b.referrer),this.methods={},this.supported={events:c?c:d.EVENTS.all(),methods:e?e:d.METHODS.all()},this.eventListeners={},this.reject=!(a.self!==a.top&&d.POST_MESSAGE),this.reject||d.addEvent(a,"message",function(a){f.receive(a)})},d.Receiver.prototype.receive=function(b){if(b.origin!==this.origin)return!1;var c={};if(d.isObject(b.data))c=b.data;else try{c=a.JSON.parse(b.data)}catch(e){d.log("JSON Parse Error",e)}if(d.log("Receiver.receive",b,c),!c.method)return!1;if(c.context!==d.CONTEXT)return!1;if(-1===d.indexOf(d.METHODS.all(),c.method))return this.emit("error",{code:2,msg:'Invalid Method "'+c.method+'"'}),!1;var f=d.isNone(c.listener)?null:c.listener;if("addEventListener"===c.method)this.eventListeners.hasOwnProperty(c.value)?-1===d.indexOf(this.eventListeners[c.value],f)&&this.eventListeners[c.value].push(f):this.eventListeners[c.value]=[f],"ready"===c.value&&this.isReady&&this.ready();else if("removeEventListener"===c.method){if(this.eventListeners.hasOwnProperty(c.value)){var g=d.indexOf(this.eventListeners[c.value],f);g>-1&&this.eventListeners[c.value].splice(g,1),0===this.eventListeners[c.value].length&&delete this.eventListeners[c.value]}}else this.get(c.method,c.value,f)},d.Receiver.prototype.get=function(a,b,c){var d=this;if(!this.methods.hasOwnProperty(a))return this.emit("error",{code:3,msg:'Method Not Supported"'+a+'"'}),!1;var e=this.methods[a];if("get"===a.substr(0,3)){var f=function(b){d.send(a,b,c)};e.call(this,f)}else e.call(this,b)},d.Receiver.prototype.on=function(a,b){this.methods[a]=b},d.Receiver.prototype.send=function(b,c,e){if(d.log("Receiver.send",b,c,e),this.reject)return d.log("Receiver.send.reject",b,c,e),!1;var f={context:d.CONTEXT,version:d.VERSION,event:b};d.isNone(c)||(f.value=c),d.isNone(e)||(f.listener=e);var g=JSON.stringify(f);a.parent.postMessage(g,""===this.origin?"*":this.origin)},d.Receiver.prototype.emit=function(a,b){if(!this.eventListeners.hasOwnProperty(a))return!1;d.log("Instance.emit",a,b,this.eventListeners[a]);for(var c=0;c.vjs-menu-button,
744 | .vjs-playback-rate .vjs-playback-rate-value {
745 | position: absolute;
746 | top: 3px;
747 | left: 0;
748 | width: 100%;
749 | height: 100%;
750 | font-size: 14px;
751 | }
752 |
753 | .vjs-menu {
754 | font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
755 | }
756 |
757 | .vjs-menu-content {
758 | overflow: inherit;
759 | }
760 |
761 | .vjs-menu-item {
762 | text-align: right!important;
763 | }
764 |
765 | .vjs-settings-sub-menu-value,
766 | .vjs-settings-sub-menu-title {
767 | display: inline-block;
768 | padding: 0 4px;
769 | }
770 |
771 |
772 | /* .vjs-menu-button-popup > .vjs-menu {
773 | width: 17em;
774 | left: -10em;
775 | }
776 | .vjs-menu-button-popup .vjs-settings-sub-menu .vjs-menu {
777 | left: -10em;
778 | } */
779 |
780 | .vjs-menu-content {
781 | bottom: -1.5em;
782 | }
783 |
784 | li {
785 | font-size: 1em;
786 | }
787 |
788 | .vjs-menu-item {
789 | text-align: center;
790 | }
791 |
792 | .vjs-settings-sub-menu-title {
793 | width: auto;
794 | text-transform: initial;
795 | }
796 |
797 | .vjs-settings-sub-menu {
798 | width: 200px;
799 | position: fixed;
800 | }
801 |
802 | .vjs-settings-menu.vjs-icon-cog.vjs-menu-button.vjs-menu-button-popup.vjs-button+.vjs-menu {
803 | width: 30%;
804 | /* display: inherit; */
805 | position: fixed;
806 | float: right;
807 | right: 0px;
808 | left: auto;
809 | }
810 |
811 | .vjs-settings-menu {
812 | font-size: 18px!important;
813 | }
814 |
815 | .vjs-menu-button.vjs-menu-button-popup.vjs-control.vjs-button {
816 | font-size: 14px;
817 | }
818 |
819 | .vjs-menu-button-popup .vjs-menu .vjs-menu-content {
820 | background-color: #2B333F;
821 | background-color: rgba(0, 0, 0, 0.85);
822 | position: absolute;
823 | width: 100%;
824 | bottom: 1.3em;
825 | max-height: 15em;
826 | }
827 |
828 | .video-js .vjs-fullscreen-control {
829 | cursor: pointer;
830 | -webkit-box-flex: none;
831 | -moz-box-flex: none;
832 | -webkit-flex: none;
833 | -ms-flex: none;
834 | flex: none;
835 | }
836 |
837 | .vjs-menu-button.vjs-menu-button-popup.vjs-control.vjs-button {
838 | font-size: 14px;
839 | }
840 |
841 | .video-js .vjs-live-control {
842 | display: -webkit-box;
843 | display: -webkit-flex;
844 | display: -ms-flexbox;
845 | display: flex;
846 | -webkit-box-align: flex-start;
847 | -webkit-align-items: flex-start;
848 | -ms-flex-align: flex-start;
849 | align-items: flex-start;
850 | -webkit-box-flex: auto;
851 | -moz-box-flex: auto;
852 | -webkit-flex: auto;
853 | -ms-flex: auto;
854 | flex: auto;
855 | font-size: 1em;
856 | line-height: 3em;
857 | visibility: hidden;
858 | display: block!important;
859 | }
860 |
861 | .vjs-thumbnail-holder {
862 | position: absolute;
863 | left: -1000px;
864 | }
865 |
866 |
867 | /* the thumbnail image itself */
868 |
869 | .vjs-thumbnail {
870 | opacity: 0;
871 | transition: opacity .2s ease;
872 | -webkit-transition: opacity .2s ease;
873 | -moz-transition: opacity .2s ease;
874 | -mz-transition: opacity .2s ease;
875 | }
876 |
877 |
878 | /* fade in the thumbnail when hovering over the progress bar */
879 |
880 |
881 | /* .fake-active is needed for Android only. It's removed on touchend/touchecancel */
882 |
883 | .vjs-progress-control:hover .vjs-thumbnail,
884 | .vjs-progress-control.fake-active .vjs-thumbnail,
885 | .vjs-progress-control:active .vjs-thumbnail {
886 | opacity: 1;
887 | }
888 |
889 |
890 | /* ... but hide the thumbnail when hovering directly over it */
891 |
892 | .vjs-progress-control:hover .vjs-thumbnail:hover,
893 | .vjs-progress-control:active .vjs-thumbnail:active {
894 | opacity: 0;
895 | }
896 |
897 | .video-js .vjs-progress-control .vjs-mouse-display {
898 | display: none;
899 | position: absolute;
900 | width: 0px;
901 | height: 100%;
902 | background-color: #000;
903 | z-index: 1;
904 | }
905 |
906 | .vjs-dtube-selected {
907 | color: red!important;
908 | }
--------------------------------------------------------------------------------
/javascripts/player.js:
--------------------------------------------------------------------------------
1 | portals = {
2 | IPFS: [
3 | "https://player.d.tube/ipfs/",
4 | "https://ipfs.d.tube/ipfs/",
5 | "https://video.oneloveipfs.com/ipfs/",
6 | "https://ipfs.infura.io/ipfs/",
7 | "https://gateway.temporal.cloud/ipfs/",
8 | "https://gateway.pinata.cloud/ipfs/",
9 | "https://ipfs.eternum.io/ipfs/",
10 | "https://ipfs.io/ipfs/"
11 | ],
12 | BTFS: [
13 | "https://player.d.tube/btfs/",
14 | "https://btfs.d.tube/btfs/"
15 | ],
16 | Skynet: [
17 | "https://siasky.net/",
18 | "https://skydrain.net/",
19 | "https://sialoop.net/",
20 | "https://skynet.luxor.tech/",
21 | "https://skynet.tutemwesi.com/",
22 | "https://siacdn.com/",
23 | "https://vault.lightspeedhosting.com/"
24 | ]
25 | }
26 | steemAPI = [
27 | "https://api.steemit.com/",
28 | "https://techcoderx.com",
29 | "https://steemd.minnowsupportproject.org/",
30 | "https://anyx.io/",
31 | "https://steemd.privex.io",
32 | "https://api.steem.house"
33 | ]
34 | avalonAPI = 'https://avalon.d.tube'
35 | IpfsShortTermGw = 'https://video.dtube.top'
36 | BtfsShortTermGw = "https://player.d.tube"
37 | player = null
38 | itLoaded = false
39 | timeout = 1500
40 | defaultOptions = {
41 | loop: false
42 | }
43 |
44 | if (window.location.search.indexOf('?v=') === 0) {
45 | // redirect query string to real url
46 | var newUrl = [window.location.href.split('?v=')[0]]
47 | newUrl.push('#!/')
48 | var rightPart = window.location.search.split('?v=')[1]
49 | if (rightPart.indexOf('&') === -1)
50 | newUrl.push(rightPart)
51 | else {
52 | var query = rightPart.split('&')
53 | newUrl.push(query[0])
54 | for (let i = 1; i < query.length; i++)
55 | if (query[i] == 'autoplay=1' || query[i] == 'auto_play=true') {
56 | newUrl.push('/true')
57 | break
58 | }
59 | }
60 |
61 | window.location.replace(newUrl.join(''))
62 | }
63 |
64 | var path = window.location.href.split("#!/")[1];
65 | var videoAuthor = path.split("/")[0]
66 | var videoPermlink = path.split("/")[1]
67 | var autoplay = (path.split("/")[2] == 'true')
68 | var nobranding = (path.split("/")[3] == 'true')
69 | if (path.split("/")[4] && path.split("/")[4] !== "default")
70 | provider = path.split("/")[4]
71 |
72 | var additionalOptions = {};
73 | if (path.split("/")[5]) {
74 | // The 5th part is parsed as a URL-Parameters if set
75 | // https://stackoverflow.com/questions/8648892/how-to-convert-url-parameters-to-a-javascript-object
76 | additionalOptions = JSON.parse('{"' + decodeURI(path.split("/")[5]).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"') + '"}')
77 | }
78 |
79 | function getOption(key) {
80 | if(typeof additionalOptions[key] !== "undefined") {
81 | return additionalOptions[key] == "false" ? false : additionalOptions[key];
82 | }
83 |
84 | if(typeof defaultOptions[key] !== "undefined") {
85 | return defaultOptions[key] == "false" ? false : additionalOptions[key];
86 | }
87 |
88 | return null;
89 | }
90 |
91 | document.addEventListener("DOMContentLoaded", function(event) {
92 | startup()
93 | });
94 |
95 | function startup() {
96 | // if you don't pass anything in the first field (emb.d.tube/#!//)
97 | // you can pass JSOUN data in the second field
98 | // and skip blockchain loading time
99 | if (videoAuthor === '') {
100 | try {
101 | var json = JSOUN.decode(videoPermlink)
102 | console.log('Video JSON loaded from URL', json)
103 | } catch (error) {
104 | console.log('Bad video JSON', error)
105 | return
106 | }
107 | handleVideo(json)
108 | }
109 | else
110 | findAvalon(videoAuthor, videoPermlink, function(err, res) {
111 | if (err || !res) {
112 | console.log(err, res)
113 | findVideo()
114 | } else {
115 | console.log('Video JSON loaded from '+avalonAPI, res)
116 | handleVideo(res.json)
117 | }
118 | })
119 | }
120 |
121 | function findInShortTerm(hash, video, cb) {
122 | var gw = prov.getDefaultGateway(video)
123 | const url = gw + hash
124 | const request = new XMLHttpRequest();
125 | request.open("HEAD", url, true);
126 | request.onerror = function(e) {
127 | console.log('Error: ' + url)
128 | }
129 | request.onreadystatechange = function() {
130 | if (request.readyState === request.DONE) {
131 | if (request.status === 200) {
132 | const headers = request.getAllResponseHeaders()
133 | console.log(headers, gw)
134 | cb(true)
135 | } else cb()
136 | }
137 | }
138 | request.send();
139 | }
140 |
141 | function findAvalon(author, link, cb) {
142 | fetch(avalonAPI+'/content/'+author+'/'+link, {
143 | method: 'get',
144 | headers: {
145 | 'Accept': 'application/json, text/plain, */*',
146 | 'Content-Type': 'application/json'
147 | }
148 | }).then(status).then(res => res.json()).then(function(res) {
149 | cb(null, res)
150 | }).catch(function(err) {
151 | cb(err)
152 | })
153 | }
154 |
155 | function findVideo(retries = 3) {
156 | var api = steemAPI[(3-retries)%steemAPI.length]
157 | var client = new LightRPC(api, {
158 | timeout: timeout
159 | })
160 |
161 | client.send('get_content', [videoAuthor, videoPermlink], {timeout: timeout}, function(err, b) {
162 | if (err) {
163 | console.log(retries, api, err)
164 | if (retries>0) {
165 | findVideo(--retries)
166 | } else {
167 | console.log('Stopped trying to load (tried too much)')
168 | }
169 | return
170 | }
171 | console.log('Video loaded from '+api, b)
172 | var a = JSON.parse(b.json_metadata).video;
173 | handleVideo(a)
174 | });
175 | timeout *= 2
176 | }
177 |
178 | function handleVideo(video) {
179 | if (!provider) provider = prov.default(video)
180 | console.log('Trying... '+provider)
181 | var redirectLink = 'https://'
182 | switch (provider) {
183 | // Our custom DTube Player
184 | case "IPFS":
185 | case "BTFS":
186 | var gw = prov.getDefaultGateway(video)
187 | var qualities = generateQualities(video)
188 | if (!qualities || qualities.length == 0) {
189 | prov.tryNext(video)
190 | return
191 | }
192 | findInShortTerm(qualities[0].hash, video, function(isAvail) {
193 | addQualitiesSource(qualities, (isAvail ? gw : prov.getFallbackGateway()))
194 |
195 | var coverUrl = getCoverUrl(video)
196 | var spriteHash = getSpriteHash(video)
197 | var duration = getDuration(video)
198 | var subtitles = getSubtitles(video)
199 | let spriteSource = getSpriteSource(video)
200 | createPlayer(coverUrl, autoplay, nobranding, qualities, spriteHash, spriteSource, duration, subtitles)
201 | })
202 | break;
203 |
204 | case "Skynet":
205 | var gw = prov.getDefaultGateway(video)
206 | var qualities = generateQualities(video)
207 | if (!qualities || qualities.length == 0) {
208 | prov.tryNext(video)
209 | return
210 | }
211 |
212 | addQualitiesSource(qualities, gw, 'Skynet')
213 |
214 | var coverUrl = getCoverUrl(video)
215 | var spriteHash = getSpriteHash(video)
216 | var duration = getDuration(video)
217 | var subtitles = getSubtitles(video)
218 | let spriteSource = getSpriteSource(video)
219 |
220 | createPlayer(coverUrl, autoplay, nobranding, qualities, spriteHash, spriteSource, duration, subtitles)
221 | break;
222 |
223 | // Redirects to 3rd party embeds
224 | case "Twitch":
225 | var parent = window.location.hostname
226 | if (window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0)
227 | parent = window.location.ancestorOrigins[0].split('//')[1]
228 | if (parent.indexOf(':') > -1)
229 | parent = parent.split(':')[0]
230 | if (video.twitch_type && video.twitch_type == 'clip')
231 | redirectLink += "clips.twitch.tv/embed?clip=" + getVideoId(video)
232 | else
233 | if (parseInt(getVideoId(video)) == getVideoId(video))
234 | redirectLink += "player.twitch.tv/?video=v" + getVideoId(video)
235 | else
236 | redirectLink += "player.twitch.tv/?channel=" + getVideoId(video)
237 |
238 | if (autoplay)
239 | redirectLink += "&autoplay=true"
240 | else
241 | redirectLink += "&autoplay=false"
242 | redirectLink += "&muted=false&parent="+parent
243 | break;
244 |
245 | case "Dailymotion":
246 | redirectLink += "www.dailymotion.com/embed/video/" + getVideoId(video)
247 | if (autoplay)
248 | redirectLink += "?autoplay=true"
249 | else
250 | redirectLink += "?autoplay=false"
251 | redirectLink += "&mute=false"
252 | break;
253 |
254 | case "Instagram":
255 | redirectLink += "www.instagram.com/p/" + getVideoId(video) + '/embed/'
256 | break;
257 |
258 | case "LiveLeak":
259 | redirectLink += "www.liveleak.com/e/" + getVideoId(video)
260 | break;
261 |
262 | case "Vimeo":
263 | redirectLink += "player.vimeo.com/video/" + getVideoId(video)
264 | if (autoplay)
265 | redirectLink += "?autoplay=1"
266 | else
267 | redirectLink += "?autoplay=0"
268 | redirectLink += "&muted=0"
269 | if(getOption("loop"))
270 | redirectLink += "&loop=1"
271 | break;
272 |
273 | case "Facebook":
274 | redirectLink += "www.facebook.com/v2.3/plugins/video.php?allowfullscreen=true"
275 | if (autoplay)
276 | redirectLink += "&autoplay=true"
277 | else
278 | redirectLink += "&autoplay=false"
279 | redirectLink += "&container_width=800&href=" + encodeURI('https://www.facebook.com/watch/?v=') + getVideoId(video)
280 | break;
281 |
282 | case "YouTube":
283 | redirectLink += "www.youtube.com/embed/" + getVideoId(video)
284 | if (autoplay)
285 | redirectLink += "?autoplay=1"
286 | else
287 | redirectLink += "?autoplay=0"
288 | redirectLink += "&showinfo=1"
289 | if (nobranding)
290 | redirectLink += "&modestbranding=1"
291 | if (getOption("loop"))
292 | redirectLink += "&loop=1"
293 |
294 | break;
295 |
296 | default:
297 | redirectLink = false
298 | break;
299 | }
300 |
301 | if (redirectLink && redirectLink != 'https://')
302 | window.location.replace(redirectLink)
303 | }
304 |
305 | function getVideoId(video) {
306 | if (video.providerName == provider && video.videoId)
307 | return video.videoId
308 | if (video.files && video.files[prov.dispToId(provider)])
309 | return video.files[prov.dispToId(provider)]
310 | return ''
311 | }
312 |
313 | function getCoverUrl(video) {
314 | var gw = 'https://ipfs.d.tube/ipfs/'
315 | if (video.files && video.files.btfs && video.files.btfs.img && video.files.btfs.img["360"])
316 | return gw+video.files.btfs.img["360"]
317 | if (video.files && video.files.ipfs && video.files.ipfs.img && video.files.ipfs.img["360"])
318 | return gw+video.files.ipfs.img["360"]
319 | if (video.files && video.files.btfs && video.files.btfs.img && video.files.btfs.img["118"])
320 | return gw+video.files.btfs.img["118"]
321 | if (video.files && video.files.ipfs && video.files.ipfs.img && video.files.ipfs.img["118"])
322 | return gw+video.files.ipfs.img["118"]
323 | if (video.ipfs && video.ipfs.snaphash) return gw+video.ipfs.snaphash
324 | if (video.info && video.info.snaphash) return gw+video.info.snaphash
325 | if (video.files && video.files.youtube)
326 | return 'http://i.ytimg.com/vi/'+video.files.youtube+'/hqdefault.jpg'
327 | return ''
328 | }
329 |
330 | function getSpriteHash(video) {
331 | if (video.files && video.files.btfs && video.files.btfs.img && video.files.btfs.img.spr)
332 | return video.files.btfs.img.spr
333 | if (video.files && video.files.ipfs && video.files.ipfs.img && video.files.ipfs.img.spr)
334 | return video.files.ipfs.img.spr
335 | if (video.files && video.files.sia && video.files.sia.img && video.files.sia.img.spr)
336 | return video.files.sia.img.spr
337 | if (video.ipfs && video.ipfs.spritehash) return video.ipfs.spritehash
338 | if (video.content && video.content.spritehash) return video.content.spritehash
339 | if (video.info && video.info.spritehash) return video.info.spritehash
340 | return ''
341 | }
342 |
343 | function getSpriteSource(video) {
344 | if (video.files && video.files.btfs && video.files.btfs.img && video.files.btfs.img.spr)
345 | return 'btfs'
346 | if (video.files && video.files.ipfs && video.files.ipfs.img && video.files.ipfs.img.spr)
347 | return 'ipfs'
348 | if (video.files && video.files.sia && video.files.sia.img && video.files.sia.img.spr)
349 | return 'sia'
350 | return 'ipfs'
351 | }
352 |
353 | function getDuration(video) {
354 | if (video.dur) return video.dur
355 | if (video.duration) return video.duration
356 | if (video.info && video.info.duration) return video.info.duration
357 | }
358 |
359 | function getSubtitles(video) {
360 | if (video.ipfs && video.ipfs.subtitles) return video.ipfs.subtitles
361 | if (video.content && video.content.subtitles) return video.content.subtitles
362 | if (video.files && video.files.ipfs && video.files.ipfs.sub) {
363 | var subs = []
364 | for (const lang in video.files.ipfs.sub) {
365 | subs.push({
366 | lang: lang,
367 | hash: video.files.ipfs.sub[lang]
368 | })
369 | }
370 | return subs
371 | }
372 | return null
373 | }
374 |
375 | function enableSprite(duration, sprite, source) {
376 | if (!duration) return
377 | if (!sprite) return
378 | var listThumbnails = {}
379 | var nFrames = 100
380 | if (duration < 100) nFrames = Math.floor(duration)
381 | for (let s = 0; s < nFrames; s++) {
382 | var nSeconds = s
383 | if (duration > 100) nSeconds = Math.floor(s * duration / 100)
384 | listThumbnails[nSeconds] = {
385 | src: spriteUrl(sprite, source),
386 | style: {
387 | margin: -72 * s + 'px 0px 0px 0px',
388 | }
389 | }
390 | }
391 | player.thumbnails(listThumbnails);
392 | }
393 |
394 | function createPlayer(snapUrl, autoplay, branding, qualities, sprite, spriteSource, duration, subtitles) {
395 | var c = document.createElement("video");
396 | if (snapUrl)
397 | c.poster = snapUrl;
398 | c.controls = true;
399 | c.autoplay = autoplay;
400 | c.id = "player";
401 | c.className = "video-js";
402 | c.style = "width:100%;height:100%";
403 | c.loop = getOption("loop");
404 | c.addEventListener('loadeddata', function() {
405 | if (c.readyState >= 3) {
406 | itLoaded = true
407 | // player.muted(true);
408 | // player.play();
409 | // player.muted(false);
410 | if (!duration) {
411 | duration = Math.round(player.duration())
412 | parent.postMessage({dur: duration}, "*")
413 | enableSprite(duration, sprite, spriteSource)
414 | }
415 | }
416 | });
417 |
418 | var video = document.body.appendChild(c);
419 |
420 | // Setting menu items
421 | var menuEntries = []
422 | menuEntries.push('PlaybackRateMenuButton')
423 | if (subtitles)
424 | menuEntries.push('SubtitlesButton')
425 | if (qualities.length > 1)
426 | menuEntries.push('ResolutionMenuButton')
427 | menuEntries.push('GatewaySwitcherMenuButton')
428 |
429 |
430 | var defaultQuality = qualities[0].label
431 | if (hasQuality('480p', qualities))
432 | defaultQuality = '480p'
433 | var persistedQuality = getStorageItem('dquality');
434 | if(persistedQuality !== null && hasQuality(persistedQuality, qualities)){
435 | defaultQuality = persistedQuality
436 | }
437 |
438 | player = videojs("player", {
439 | inactivityTimeout: 1000,
440 | sourceOrder: true,
441 | sources: qualities,
442 | techOrder: ["html5"],
443 | 'playbackRates': [0.5, 0.75, 1, 1.25, 1.5, 2],
444 | controlBar: {
445 | children: {
446 | 'playToggle': {},
447 | 'muteToggle': {},
448 | 'volumeControl': {},
449 | 'currentTimeDisplay': {},
450 | 'timeDivider': {},
451 | 'durationDisplay': {},
452 | 'liveDisplay': {},
453 | 'flexibleWidthSpacer': {},
454 | 'progressControl': {},
455 | 'settingsMenuButton': {
456 | entries: menuEntries
457 | },
458 | 'fullscreenToggle': {}
459 | }
460 | },
461 | plugins: {
462 | persistvolume: {
463 | namespace: 'dtube'
464 | },
465 | IPFSGatewaySwitcher: {},
466 | videoJsResolutionSwitcher: {
467 | default: defaultQuality,
468 | dynamicLabel: true
469 | },
470 | statistics: {
471 |
472 | }
473 | }
474 | })
475 | enableSprite(duration, sprite, spriteSource)
476 | videojs('player').ready(function() {
477 | const adapter = new playerjs.VideoJSAdapter(this)
478 |
479 | let loadedVidUrl = player.options_.sources[0].src
480 | let loadedGateway = loadedVidUrl.split('/')[2]
481 | document.getElementsByClassName('vjs-settings-sub-menu-value')[document.getElementsByClassName('vjs-settings-sub-menu-value').length - 1].innerHTML = loadedGateway
482 |
483 | this.hotkeys({
484 | seekStep: 5,
485 | enableModifiersForNumbers: false
486 | });
487 |
488 | window.onmessage = function(e) {
489 | if (e.data.seekTo)
490 | player.currentTime(e.data.seekTime)
491 | }
492 |
493 | if (subtitles) {
494 | for (let i = 0; i < subtitles.length; i++) {
495 | player.addRemoteTextTrack({
496 | kind: "subtitles",
497 | src: subtitleUrl(subtitles[i].hash),
498 | srclang: subtitles[i].lang,
499 | label: subtitles[i].lang
500 | })
501 |
502 | }
503 | }
504 |
505 | adapter.ready()
506 | });
507 |
508 | player.brand({
509 | branding: !JSON.parse(nobranding),
510 | title: "Watch on DTube",
511 | destination: "http://d.tube/#!/v/" + videoAuthor + '/' + videoPermlink,
512 | destinationTarget: "_blank"
513 | })
514 |
515 | handleResize()
516 | }
517 |
518 | function removePlayer() {
519 | var elem = document.getElementById('player');
520 | return elem.parentNode.removeChild(elem);
521 | }
522 |
523 | function subtitleUrl(ipfsHash) {
524 | return 'https://ipfs.d.tube/ipfs/' + ipfsHash
525 | }
526 |
527 | function spriteUrl(ipfsHash, source) {
528 | if (source === 'ipfs')
529 | return 'https://ipfs.d.tube/ipfs/' + ipfsHash
530 | else if (source === 'btfs')
531 | return 'https://btfs.d.tube/btfs/' + ipfsHash
532 | else if (source === 'sia')
533 | return 'https://siasky.net/' + source
534 | }
535 |
536 | function generateQualities(a) {
537 | var qualities = []
538 | var provId = prov.dispToId(provider)
539 | // latest format
540 | if (a.files) {
541 | if (!a.files[provId] || !a.files[provId].vid) return [];
542 | for (const key in a.files[provId].vid) {
543 | if (key == 'src') {
544 | qualities.push({
545 | label: 'Source',
546 | type: a.files[provId].vid.src.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4',
547 | hash: a.files[provId].vid.src,
548 | network: provider
549 | })
550 | continue
551 | }
552 | qualities.push({
553 | label: key+'p',
554 | type: a.files[provId].vid[key].endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4',
555 | hash: a.files[provId].vid[key],
556 | network: provider
557 | })
558 | }
559 | return qualities
560 | }
561 |
562 | // old video format
563 | if (a.ipfs) {
564 | if (a.ipfs.video240hash) {
565 | qualities.push({
566 | label: '240p',
567 | type: a.ipfs.video240hash.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4',
568 | hash: a.ipfs.video240hash,
569 | })
570 | }
571 | if (a.ipfs.video480hash) {
572 | qualities.push({
573 | label: '480p',
574 | type: a.ipfs.video480hash.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4',
575 | hash: a.ipfs.video480hash,
576 | })
577 | }
578 | if (a.ipfs.video720hash) {
579 | qualities.push({
580 | label: '720p',
581 | type: a.ipfs.video720hash.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4',
582 | hash: a.ipfs.video720hash,
583 | })
584 | }
585 | if (a.ipfs.video1080hash) {
586 | qualities.push({
587 | label: '1080p',
588 | type: a.ipfs.video1080hash.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4',
589 | hash: a.ipfs.video1080hash,
590 | })
591 | }
592 | if (a.ipfs.videohash) {
593 | qualities.push({
594 | label: 'Source',
595 | type: a.ipfs.videohash.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4',
596 | hash: a.ipfs.videohash,
597 | })
598 | }
599 | } else {
600 | // super old video format
601 | if (a.content && a.content.video240hash) {
602 | qualities.push({
603 | label: '240p',
604 | type: a.content.video240hash.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4',
605 | hash: a.content.video240hash,
606 | })
607 | }
608 | if (a.content && a.content.video480hash) {
609 | qualities.push({
610 | label: '480p',
611 | type: a.content.video480hash.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4',
612 | hash: a.content.video480hash,
613 | })
614 | }
615 | if (a.content && a.content.video720hash) {
616 | qualities.push({
617 | label: '720p',
618 | type: a.content.video720hash.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4',
619 | hash: a.content.video720hash,
620 | })
621 | }
622 | if (a.content && a.content.video1080hash) {
623 | qualities.push({
624 | label: '1080p',
625 | type: a.content.video1080hash.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4',
626 | hash: a.content.video1080hash,
627 | })
628 | }
629 | if (a.content && a.content.videohash) {
630 | qualities.push({
631 | label: 'Source',
632 | type: a.content.videohash.endsWith('.m3u8') ? 'application/x-mpegURL' : 'video/mp4',
633 | hash: a.content.videohash,
634 | })
635 | }
636 | }
637 | return qualities
638 | }
639 |
640 | function addQualitiesSource(qualities, gateway, prov) {
641 | if (prov == 'Skynet') {
642 | for (let i = 0; i < qualities.length; i++) {
643 | qualities[i].src = gateway + qualities[i].hash
644 | }
645 | return
646 | }
647 | for (let i = 0; i < qualities.length; i++) {
648 | qualities[i].src = gateway + qualities[i].hash
649 | }
650 | }
651 |
652 | function hasQuality(label, qualities) {
653 | for (let i = 0; i < qualities.length; i++)
654 | if (qualities[i].label == label) return true
655 | return false
656 | }
657 |
658 | window.onresize = handleResize;
659 | function handleResize() {
660 | if (!window) return
661 | if (document.getElementsByClassName('vjs-time-control').length != 3) return
662 | if (window.innerWidth >= 360) {
663 | document.getElementsByClassName('vjs-time-control')[0].style.display = "block"
664 | document.getElementsByClassName('vjs-time-control')[1].style.display = "block"
665 | document.getElementsByClassName('vjs-time-control')[2].style.display = "block"
666 | } else {
667 | document.getElementsByClassName('vjs-time-control')[0].style.display = "none"
668 | document.getElementsByClassName('vjs-time-control')[1].style.display = "none"
669 | document.getElementsByClassName('vjs-time-control')[2].style.display = "none"
670 | }
671 | }
--------------------------------------------------------------------------------
/bin/dtube.css:
--------------------------------------------------------------------------------
1 | .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(font/VideoJS.eot?#iefix) format("eot")}@font-face{font-family:VideoJS;src:url(data:application/font-woff;charset=utf-8;base64,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) format("woff"),url(data:application/x-font-ttf;charset=utf-8;base64,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AG8AbgB0AGUAbABsAG8AIABwAHIAbwBqAGUAYwB0AC4AaAB0AHQAcAA6AC8ALwBmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQAAAAIAAAAAAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQECAQMBBAEFAQYBBwEIAQkBCgELAQwBDQEOAQ8BEAERARIBEwEUARUBFgEXARgBGQEaARsBHAEdAR4BHwEgASEBIgAEcGxheQtwbGF5LWNpcmNsZQVwYXVzZQt2b2x1bWUtbXV0ZQp2b2x1bWUtbG93CnZvbHVtZS1taWQLdm9sdW1lLWhpZ2gQZnVsbHNjcmVlbi1lbnRlcg9mdWxsc2NyZWVuLWV4aXQGc3F1YXJlB3NwaW5uZXIJc3VidGl0bGVzCGNhcHRpb25zCGNoYXB0ZXJzBXNoYXJlA2NvZwZjaXJjbGUOY2lyY2xlLW91dGxpbmUTY2lyY2xlLWlubmVyLWNpcmNsZQJoZAZjYW5jZWwGcmVwbGF5CGZhY2Vib29rBWdwbHVzCGxpbmtlZGluB3R3aXR0ZXIGdHVtYmxyCXBpbnRlcmVzdBFhdWRpby1kZXNjcmlwdGlvbgVhdWRpbwluZXh0LWl0ZW0NcHJldmlvdXMtaXRlbQAAAAA=) format("truetype");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{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{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{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%;overflow-y:auto}.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.5em;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);-webkit-border-radius:.3em;-moz-border-radius:.3em;border-radius:.3em;-webkit-transition:all .4s;-moz-transition:all .4s;-ms-transition:all .4s;-o-transition:all .4s;transition:all .4s}.vjs-big-play-centered .vjs-big-play-button{top:50%;left:50%;margin-top:-.75em;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);-webkit-transition:all 0s;-moz-transition:all 0s;-ms-transition:all 0s;-o-transition:all 0s;transition:all 0s}.vjs-controls-disabled .vjs-big-play-button,.vjs-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;overflow:visible;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:-webkit-linear-gradient(-90deg,rgba(0,0,0,.8),rgba(255,255,255,0));background:linear-gradient(180deg,rgba(0,0,0,.8),rgba(255,255,255,0));overflow:auto;box-sizing:content-box}.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-menu .vjs-menu-content{display:block;padding:0;margin:0;font-family:Arial,Helvetica,sans-serif;overflow:auto;box-sizing:content-box}.vjs-menu .vjs-menu-content>*{box-sizing:border-box}.vjs-scrubbing .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}.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}.video-js .vjs-menu-button-inline{-webkit-transition:all .4s;-moz-transition:all .4s;-ms-transition:all .4s;-o-transition:all .4s;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;-webkit-transition:all .4s;-moz-transition:all .4s;-ms-transition:all .4s;-o-transition:all .4s;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:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;visibility:visible;opacity:1;-webkit-transition:visibility .1s,opacity .1s;-moz-transition:visibility .1s,opacity .1s;-ms-transition:visibility .1s,opacity .1s;-o-transition:visibility .1s,opacity .1s;transition:visibility .1s,opacity .1s}.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{visibility:visible;opacity:0;-webkit-transition:visibility 1s,opacity 1s;-moz-transition:visibility 1s,opacity 1s;-ms-transition:visibility 1s,opacity 1s;-o-transition:visibility 1s,opacity 1s;transition:visibility 1s,opacity 1s}.vjs-controls-disabled .vjs-control-bar,.vjs-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:3.2em;-webkit-box-flex:none;-moz-box-flex:none;-webkit-flex:none;-ms-flex:none;flex:none}.vjs-button>.vjs-icon-placeholder:before{font-size:1.8em;line-height:1.67}.video-js .vjs-control:focus,.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;-webkit-box-flex:auto;-moz-box-flex:auto;-webkit-flex:auto;-ms-flex:auto;flex:auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;min-width:4em}.video-js .vjs-progress-control.disabled{cursor:default}.vjs-live .vjs-progress-control{display:none}.vjs-no-flex .vjs-progress-control{width:auto}.video-js .vjs-progress-holder{-webkit-box-flex:auto;-moz-box-flex:auto;-webkit-flex:auto;-ms-flex:auto;flex:auto;-webkit-transition:all .2s;-moz-transition:all .2s;-ms-transition:all .2s;-o-transition:all .2s;transition:all .2s;height:.3em}.video-js .vjs-progress-control>.video-js .vjs-progress-control .vjs-progress-holder{margin:0 10px}.video-js.not-hover.vjs-has-started.vjs-paused.vjs-user-active .vjs-progress-control,.video-js.not-hover.vjs-has-started.vjs-paused.vjs-user-inactive .vjs-progress-control,.video-js.not-hover.vjs-has-started.vjs-playing.vjs-user-active .vjs-progress-control,.video-js.not-hover.vjs-has-started.vjs-playing.vjs-user-inactive .vjs-progress-control,.video-js.vjs-has-started.vjs-playing.vjs-user-inactive .vjs-progress-control>.video-js .vjs-progress-control .vjs-progress-holder{margin:0 0}.video-js .vjs-progress-control:hover .vjs-progress-holder{font-size:1.666666666666666666em}.video-js .vjs-progress-control:hover .vjs-progress-holder.disabled{font-size:1em}.video-js .vjs-progress-holder .vjs-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;left:0;top:0}.video-js .vjs-play-progress{background-color:#fff}.video-js .vjs-play-progress:before{font-size:.9em;position:absolute;right:-.5em;top:-.333333333333333em;z-index:1}.video-js .vjs-load-progress{background:#bfc7d3;background:rgba(115,133,159,.5)}.video-js .vjs-load-progress div{background:#fff;background:rgba(115,133,159,.75)}.video-js .vjs-time-tooltip{background-color:#fff;background-color:rgba(255,255,255,.8);-webkit-border-radius:.3em;-moz-border-radius:.3em;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:relative;top:-3.4em;visibility:hidden;z-index:1}.video-js .vjs-progress-holder:focus .vjs-time-tooltip{display:none}.video-js .vjs-progress-control:hover .vjs-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;-webkit-transition:visibility 1s,opacity 1s;-moz-transition:visibility 1s,opacity 1s;-ms-transition:visibility 1s,opacity 1s;-o-transition:visibility 1s,opacity 1s;transition:visibility 1s,opacity 1s}.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display{display:none}.vjs-mouse-display .vjs-time-tooltip{color:#fff;background-color:#000;background-color:rgba(0,0,0,.8)}.video-js .vjs-slider{position:relative;cursor:pointer;padding:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-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;-webkit-box-shadow:0 0 1em #fff;-moz-box-shadow:0 0 1em #fff;box-shadow:0 0 1em #fff}.video-js .vjs-mute-control{cursor:pointer;-webkit-box-flex:none;-moz-box-flex:none;-webkit-flex:none;-ms-flex:none;flex:none;padding-left:2em;padding-right:2em;padding-bottom:3em}.video-js .vjs-volume-control{cursor:pointer;margin-right:1em;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.video-js .vjs-volume-control.vjs-volume-horizontal{width:5em}.video-js .vjs-volume-panel .vjs-volume-control{visibility:visible;opacity:0;width:1px;height:1px;margin-left:-1px}.video-js .vjs-volume-panel{-webkit-transition:width 1s;-moz-transition:width 1s;-ms-transition:width 1s;-o-transition:width 1s;transition:width 1s}.video-js .vjs-volume-panel .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;-webkit-transition:visibility .1s,opacity .1s,height .1s,width .1s,left 0s,top 0s;-moz-transition:visibility .1s,opacity .1s,height .1s,width .1s,left 0s,top 0s;-ms-transition:visibility .1s,opacity .1s,height .1s,width .1s,left 0s,top 0s;-o-transition:visibility .1s,opacity .1s,height .1s,width .1s,left 0s,top 0s;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-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;-webkit-transition:width .1s;-moz-transition:width .1s;-ms-transition:width .1s;-o-transition:width .1s;transition:width .1s}.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical{height:8em;width:3em;left:-3.5em;-webkit-transition:visibility 1s,opacity 1s,height 1s 1s,width 1s 1s,left 1s 1s,top 1s 1s;-moz-transition:visibility 1s,opacity 1s,height 1s 1s,width 1s 1s,left 1s 1s,top 1s 1s;-ms-transition:visibility 1s,opacity 1s,height 1s 1s,width 1s 1s,left 1s 1s,top 1s 1s;-o-transition:visibility 1s,opacity 1s,height 1s 1s,width 1s 1s,left 1s 1s,top 1s 1s;transition:visibility 1s,opacity 1s,height 1s 1s,width 1s 1s,left 1s 1s,top 1s 1s}.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{-webkit-transition:visibility 1s,opacity 1s,height 1s 1s,width 1s,left 1s 1s,top 1s 1s;-moz-transition:visibility 1s,opacity 1s,height 1s 1s,width 1s,left 1s 1s,top 1s 1s;-ms-transition:visibility 1s,opacity 1s,height 1s 1s,width 1s,left 1s 1s,top 1s 1s;-o-transition:visibility 1s,opacity 1s,height 1s 1s,width 1s,left 1s 1s,top 1s 1s;transition:visibility 1s,opacity 1s,height 1s 1s,width 1s,left 1s 1s,top 1s 1s}.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{width:5em;height:3em;visibility:visible;opacity:1;position:relative;-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none}.video-js.vjs-no-flex .vjs-volume-control.vjs-volume-vertical,.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical{position:absolute;bottom:3em;left:.5em}.video-js .vjs-volume-panel{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.video-js .vjs-volume-bar{margin:1.35em .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-poster img{display:block;vertical-align:middle;margin:0 auto;max-height:100%;padding:0;width:100%}.vjs-has-started .vjs-poster{display:none}.vjs-audio.vjs-has-started .vjs-poster{display:block}.vjs-using-native-controls .vjs-poster{display:none}.video-js .vjs-live-control{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-flex:auto;-moz-box-flex:auto;-webkit-flex:auto;-ms-flex:auto;flex:auto;font-size:1em;line-height:3em}.vjs-no-flex .vjs-live-control{display:table-cell;width:auto;text-align:left}.video-js .vjs-time-control{-webkit-box-flex:none;-moz-box-flex:none;-webkit-flex:none;-ms-flex:none;flex:none;font-size:1em;line-height:3em;min-width:2em;width:auto;padding-left:1em;padding-right:1em}.vjs-live .vjs-time-control{display:none}.video-js .vjs-current-time,.vjs-no-flex .vjs-current-time{display:none}.vjs-no-flex .vjs-remaining-time.vjs-time-control.vjs-control{width:0!important;white-space:nowrap}.video-js .vjs-duration,.vjs-no-flex .vjs-duration{display:none}.vjs-time-divider{display:none;line-height:3em}.vjs-live .vjs-time-divider{display:none}.video-js .vjs-play-control .vjs-icon-placeholder{cursor:pointer;-webkit-box-flex:none;-moz-box-flex:none;-webkit-flex:none;-ms-flex:none;flex:none}.vjs-text-track-display{position:absolute;bottom:3em;left:0;right:0;top:0;pointer-events:none}.video-js.vjs-user-inactive.vjs-playing .vjs-text-track-display{bottom:1em}.video-js .vjs-text-track{font-size:1.4em;text-align:center;margin-bottom:.1em;background-color:#000;background-color:rgba(0,0,0,.5)}.vjs-subtitles{color:#fff}.vjs-captions{color:#fc6}.vjs-tt-cue{display:block}video::-webkit-media-text-track-display{-moz-transform:translateY(-3em);-ms-transform:translateY(-3em);-o-transform:translateY(-3em);-webkit-transform:translateY(-3em);transform:translateY(-3em)}.video-js.vjs-user-inactive.vjs-playing video::-webkit-media-text-track-display{-moz-transform:translateY(-1.5em);-ms-transform:translateY(-1.5em);-o-transform:translateY(-1.5em);-webkit-transform:translateY(-1.5em);transform:translateY(-1.5em)}.video-js .vjs-fullscreen-control{cursor:pointer;-webkit-box-flex:none;-moz-box-flex:none;-webkit-flex:none;-ms-flex:none;flex:none}.vjs-playback-rate .vjs-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;animation:0s linear .3s forwards vjs-spinner-show}.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{position:absolute}.video-js .vjs-subs-caps-button+.vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before{font-family:VideoJS;content:"\f10d";font-size:1.5em;line-height:inherit}.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-custom-control-spacer{-webkit-box-flex:auto;-moz-box-flex:auto;-webkit-flex:auto;-ms-flex:auto;flex:auto}.video-js.vjs-layout-tiny:not(.vjs-fullscreen).vjs-no-flex .vjs-custom-control-spacer{width:auto}.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-audio-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-captions-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-chapters-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-current-time,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-descriptions-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-duration,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-mute-control,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-playback-rate,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-progress-control,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-remaining-time,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-subtitles-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-time-divider,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-volume-control{display:none}.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-audio-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-captions-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-chapters-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-current-time,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-descriptions-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-duration,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-mute-control,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-playback-rate,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-remaining-time,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-subtitles-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-time-divider,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-volume-control{display:none}.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-captions-button,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-chapters-button,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-current-time,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-descriptions-button,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-duration,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-mute-control,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-playback-rate,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-remaining-time,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-subtitles-button .vjs-audio-button,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-time-divider,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-volume-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}.vjs-text-track-settings fieldset{margin:5px;padding:3px;border:none}.vjs-text-track-settings fieldset span{display:inline-block;margin-left:5px}.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;visibility:hidden}@media \0screen{.vjs-user-inactive.vjs-playing .vjs-control-bar :before{content:""}}@media \0screen{.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{visibility:hidden}}
2 | .video-js-info-panel{background:rgba(28,28,28,.8);border-radius:4px;color:#fff;left:10px;position:absolute;top:10px;z-index:64;min-width:26em;padding:10px}.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:10em}.vjs-user-inactive .vjs-menu-content{display:none!important}.video-js .vjs-controls-disabled .vjs-big-play-button{display:none!important}.video-js .vjs-control{width:3em}.video-js .vjs-menu-button-inline:before{width:1.5em}.vjs-menu-button-inline .vjs-menu{left:3em}.video-js.vjs-ended .vjs-big-play-button,.video-js.vjs-paused .vjs-big-play-button,.vjs-paused.vjs-has-started.video-js .vjs-big-play-button{display:block}.video-js .vjs-load-progress div,.vjs-seeking .vjs-big-play-button,.vjs-waiting .vjs-big-play-button{display:none!important}.video-js .vjs-mouse-display:after,.video-js .vjs-play-progress:after{padding:0 .4em .3em}.video-js.vjs-ended .vjs-loading-spinner{display:none}.video-js.vjs-ended .vjs-big-play-button{display:block!important}.video-js *,.video-js:after,.video-js:before{box-sizing:inherit;font-size:inherit;color:inherit;line-height:inherit}.video-js.vjs-fullscreen,.video-js.vjs-fullscreen .vjs-tech{width:100%!important;height:100%!important}.video-js{font-size:14px;overflow:hidden}.video-js .vjs-control{color:inherit}.video-js .vjs-menu-button-inline:hover,.video-js.vjs-no-flex .vjs-menu-button-inline{width:8.35em}.vjs-mute-control.vjs-control:hover~.vjs-volume-horizontal{height:3em;width:6.35em!important}.vjs-volume-control:hover~.vjs-volume-horizontal{height:3em;width:6.35em!important}.vjs-volume-horizontal{width:.5em!important}.vjs-volume-horizontal:hover{height:3em;width:6.35em!important}.video-js .vjs-spacer,.video-js .vjs-time-control{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-box-flex:1 1 auto;-moz-box-flex:1 1 auto;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto}.video-js .vjs-time-control{-webkit-box-flex:0 1 auto;-moz-box-flex:0 1 auto;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;width:auto}.video-js .vjs-time-control.vjs-time-divider{width:14px}.video-js .vjs-time-control.vjs-time-divider div{width:100%;text-align:center}.video-js .vjs-time-control.vjs-current-time{margin-left:0!important}.video-js .vjs-time-control .vjs-current-time-display,.video-js .vjs-time-control .vjs-duration-display{width:100%}.video-js .vjs-time-control .vjs-current-time-display{text-align:right}.video-js .vjs-time-control .vjs-duration-display{text-align:left}.video-js .vjs-play-progress:before,.video-js .vjs-progress-control .vjs-play-progress:before,.video-js .vjs-remaining-time,.video-js .vjs-volume-level:after,.video-js .vjs-volume-level:before,.video-js.vjs-live .vjs-time-control.vjs-current-time,.video-js.vjs-live .vjs-time-control.vjs-duration,.video-js.vjs-live .vjs-time-control.vjs-time-divider,.video-js.vjs-no-flex .vjs-time-control.vjs-remaining-time{display:none}.video-js.vjs-no-flex .vjs-time-control{display:table-cell;width:4em}.video-js.vjs-live .vjs-live-control{margin-left:1em}.video-js .vjs-big-play-button{top:50%;left:50%;margin-left:-1em;margin-top:-1em;width:2em;height:2em;line-height:2em;border:none;border-radius:50%;font-size:3.5em;background-color:rgba(0,0,0,.45);color:#fff;-webkit-transition:border-color .4s,outline .4s,background-color .4s;-moz-transition:border-color .4s,outline .4s,background-color .4s;-ms-transition:border-color .4s,outline .4s,background-color .4s;-o-transition:border-color .4s,outline .4s,background-color .4s;transition:border-color .4s,outline .4s,background-color .4s}.vjs-big-play-button .vjs-icon-placeholder{font-size:40px}.video-js .vjs-menu-button-popup .vjs-menu .vjs-menu-content{background-color:#111;display:table;float:right;position:fixed;right:10px;bottom:50px;width:auto;margin-right:0;padding:5px;border-top:2px red solid!important;border-radius:5px;min-height:84px;min-width:200px}.video-js .vjs-menu-button-popup .vjs-menu .vjs-menu-item,.video-js .vjs-menu-button-popup .vjs-menu .vjs-menu-title{margin:0;padding:.5em;text-align:right!important}.video-js .vjs-menu-button-popup .vjs-menu .vjs-menu-item.vjs-selected{background-color:#212121}.video-js .vjs-big-play-button{background-color:rgba(0,0,0,.5);font-size:2.5em;border-radius:20%;height:1.4em!important;line-height:1.4em!important;margin-top:-.7em!important}.video-js .vjs-big-play-button:active,.video-js .vjs-big-play-button:focus,.video-js:hover .vjs-big-play-button{background-color:#cc181e}.video-js .vjs-loading-spinner{border-color:#cc181e}.video-js .vjs-control-bar{background:linear-gradient(transparent,#1f1f1f)!important;color:#fff;font-size:12px}.video-js .vjs-play-progress,.video-js .vjs-volume-level{background-color:#cc181e}.quality-button{bottom:-76px}.vjs-resolution-button-label{font-size:1.2em;line-height:44px;position:absolute;top:0;left:0;width:100%;height:100%;text-align:center;box-sizing:inherit}.video-js .vjs-resolution-button .vjs-resolution-button-staticlabel:before{content:'\f110';font-size:1.5em;line-height:2em}.vjs-menu-button.vjs-settings-button{width:4em!important}.vjs-settings-button-staticlabel:before{content:'\f110';font-size:1.5em;line-height:2em}.vjs-settings-button:before{content:'\f110';font-size:1.5em;line-height:2em}.vjs-play-control.vjs-control.vjs-branding-logo{display:-webkit-box;width:5em}.vjs-menu-button.vjs-menu-button-popup.vjs-control.vjs-button.vjs-resolution-switcher{width:5em}.vjs-resolution-switcher{display:none}.vjs-branding-logo{color:#fff;display:block;background-image:url(https://emb.d.tube/DTube_White.svg);font-size:1.5em;line-height:2em;font-weight:600;background-repeat:no-repeat}a:-webkit-any-link{color:-webkit-link;cursor:auto;text-decoration:none}.vjs-resolution-button-label{line-height:44px!important}.video-js .vjs-resolution-button .vjs-menu li{text-transform:none;font-size:1em;font-family:Arial,Helvetica,sans-serif}.vjs-menu li{list-style:none;margin:0;padding:.2em 0;line-height:1.4em;font-size:1em!important;text-align:center;text-transform:lowercase}.video-js .vjs-load-progress{background:rgba(255,255,255,.3)}.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:10em}.video-js .vjs-controls-disabled .vjs-big-play-button{display:none!important}.video-js .vjs-menu-button-inline:before{width:1.5em}.vjs-button:focus,.vjs-menu-button:focus{outline:0}.vjs-menu-button-inline .vjs-menu{left:3em}.video-js.vjs-ended .vjs-big-play-button,.video-js.vjs-paused .vjs-big-play-button,.vjs-paused.vjs-has-started.video-js .vjs-big-play-button{display:block}.video-js .vjs-load-progress div,.vjs-seeking .vjs-big-play-button,.vjs-waiting .vjs-big-play-button{display:none!important}.video-js .vjs-mouse-display:after,.video-js .vjs-play-progress:after{padding:0 .4em .3em}.video-js.vjs-ended .vjs-loading-spinner{display:none}.video-js.vjs-ended .vjs-big-play-button{display:block!important}.video-js *,.video-js:after,.video-js:before{box-sizing:inherit;color:inherit;line-height:inherit}.video-js.vjs-fullscreen,.video-js.vjs-fullscreen .vjs-tech{width:100%!important;height:100%!important}.video-js{font-size:14px;overflow:hidden}.video-js .vjs-control{color:inherit;font-size:15px}.video-js .vjs-menu-button-inline:hover,.video-js.vjs-no-flex .vjs-menu-button-inline{width:8.35em}.video-js .vjs-volume-menu-button.vjs-volume-horizontal:hover .vjs-menu .vjs-menu-content{height:3em;width:6.35em}.video-js .vjs-spacer,.video-js .vjs-time-control{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-box-flex:1 1 auto;-moz-box-flex:1 1 auto;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto}.video-js .vjs-time-control{-webkit-box-flex:0 1 auto;-moz-box-flex:0 1 auto;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;width:auto}.video-js .vjs-time-control.vjs-time-divider{width:14px}.video-js .vjs-time-control.vjs-time-divider div{width:100%;text-align:center}.video-js .vjs-progress-control{position:absolute;width:100%;height:.4em;top:.6em;padding-top:0;-webkit-transition:height .15s,top .15s;-moz-transition:height .15s,top .15s;-ms-transition:height .15s,top .15s;-o-transition:height .15s,top .15s;transition:height .15s,top .15s}.video-js .vjs-progress-control:hover{height:1em;top:0;padding-left:5px;padding-right:5px}.video-js .vjs-progress-control .vjs-load-progress,.video-js .vjs-progress-control .vjs-play-progress,.video-js .vjs-progress-control .vjs-progress-holder{height:100%}.video-js .vjs-control-bar{padding-top:15px;height:5em;width:100%;-webkit-transition:-webkit-transform 0s ease 0s;-moz-transition:-moz-transform 0s ease 0s;-ms-transition:-ms-transform 0s ease 0s;-o-transition:-o-transform 0s ease 0s;transition:transform 0s ease 0s}.video-js.not-hover.vjs-has-started.vjs-paused.vjs-user-active .vjs-control-bar,.video-js.not-hover.vjs-has-started.vjs-paused.vjs-user-inactive .vjs-control-bar,.video-js.not-hover.vjs-has-started.vjs-playing.vjs-user-active .vjs-control-bar,.video-js.not-hover.vjs-has-started.vjs-playing.vjs-user-inactive .vjs-control-bar,.video-js.vjs-has-started.vjs-playing.vjs-user-inactive .vjs-control-bar{visibility:visible;opacity:1;-webkit-backface-visibility:hidden;-webkit-transform:translateY(4.8em);-moz-transform:translateY(4.8em);-ms-transform:translateY(4.8em);-o-transform:translateY(4.8em);transform:translateY(4.8em);-webkit-transition:-webkit-transform 0s ease 0s;-moz-transition:-moz-transform 0s ease 0s;-ms-transition:-ms-transform 0s ease 0s;-o-transition:-o-transform 0s ease 0s;transition:transform 0s ease 0s}.video-js.not-hover.vjs-has-started.vjs-paused.vjs-user-active .vjs-progress-control,.video-js.not-hover.vjs-has-started.vjs-paused.vjs-user-inactive .vjs-progress-control,.video-js.not-hover.vjs-has-started.vjs-playing.vjs-user-active .vjs-progress-control,.video-js.not-hover.vjs-has-started.vjs-playing.vjs-user-inactive .vjs-progress-control,.video-js.vjs-has-started.vjs-playing.vjs-user-inactive .vjs-progress-control{height:.25em;top:-.15em;pointer-events:none;-webkit-transition:height 0s,top 0s;-moz-transition:height 0s,top 0s;-ms-transition:height 0s,top 0s;-o-transition:height 0s,top 0s;transition:height 0s,top 0s;margin:0 0!important}.video-js.not-hover.vjs-has-started.vjs-paused.vjs-user-active.vjs-fullscreen .vjs-progress-control,.video-js.not-hover.vjs-has-started.vjs-paused.vjs-user-inactive.vjs-fullscreen .vjs-progress-control,.video-js.not-hover.vjs-has-started.vjs-playing.vjs-user-active.vjs-fullscreen .vjs-progress-control,.video-js.not-hover.vjs-has-started.vjs-playing.vjs-user-inactive.vjs-fullscreen .vjs-progress-control,.video-js.vjs-has-started.vjs-playing.vjs-user-inactive.vjs-fullscreen .vjs-progress-control{opacity:0;-webkit-transition:opacity 0s ease 0s;-moz-transition:opacity 0s ease 0s;-ms-transition:opacity 0s ease 0s;-o-transition:opacity 0s ease 0s;transition:opacity 0s ease 0s}.video-js.vjs-live .vjs-live-control{margin-left:1em}.vjs-fade-out{display:block;visibility:hidden;opacity:0;-webkit-transition:visibility 0s,opacity 0s;-moz-transition:visibility 0s,opacity 0s;-ms-transition:visibility 0s,opacity 0s;-o-transition:visibility 0s,opacity 0s;transition:visibility 0s,opacity 0s;-webkit-transition-delay:0s;-moz-transition-delay:0s;-ms-transition-delay:0s;-o-transition-delay:0s;transition-delay:0s}.video-js .vjs-big-play-button{z-index:99;bottom:0;left:56px;top:auto!important;margin-left:-1em;margin-top:-1em;width:84px;height:84px!important;line-height:2em;border:none;border-radius:50%;font-size:3.5em;background-color:rgba(0,0,0,.45);color:#fff;-webkit-transition:border-color .4s,outline .4s,background-color .4s;-moz-transition:border-color .4s,outline .4s,background-color .4s;-ms-transition:border-color .4s,outline .4s,background-color .4s;-o-transition:border-color .4s,outline .4s,background-color .4s;transition:border-color .4s,outline .4s,background-color .4s}.video-js.vjs-ended .vjs-big-play-button,.vjs-paused.vjs-has-started.video-js .vjs-big-play-button{display:none!important}.vjs-menu-button-popup .vjs-menu{display:none;position:absolute;bottom:0;width:10em;left:-3em;height:0;margin-bottom:1.3em;border-top-color:#000}.vjs-menu-button-popup .vjs-menu .vjs-menu-content{position:absolute;width:100%;bottom:1.3em;max-height:15em}.video-js .vjs-menu-button-inline{-webkit-transition:all 0ms;-moz-transition:all 0ms;-ms-transition:all 0ms;-o-transition:all 0ms;transition:all 0ms;overflow:hidden}.video-js .vjs-time-control{-webkit-box-flex:none;-moz-box-flex:none;-webkit-flex:none;-ms-flex:none;flex:none;font-size:1em;line-height:3em;min-width:2em;width:auto;margin-top:.38em;padding-left:0;padding-right:0}.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;-webkit-transition:all 0ms;-moz-transition:all 0ms;-ms-transition:all 0ms;-o-transition:all 0ms;transition:all 0ms}.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-big-play-button{background-color:rgba(255,0,0,.5);font-size:4em;border-radius:0;line-height:1.6em!important;margin-top:-.6em!important}.video-js .vjs-big-play-button:active,.video-js .vjs-big-play-button:focus,.video-js:hover .vjs-big-play-button{background-color:#ff0009}.video-js .vjs-loading-spinner{border-color:#cc181e}.video-js .vjs-control-bar{color:#fff;font-size:12px}.video-js .vjs-play-progress,.video-js .vjs-volume-level{background-color:#cc181e}.video-js .vjs-load-progress{background:rgba(255,255,255,.3)}.vjs-playback-rate .vjs-playback-rate-value{pointer-events:none;font-size:14px;line-height:2;text-align:center}.vjs-playback-rate .vjs-playback-rate-value,.vjs-playback-rate>.vjs-menu-button{position:absolute;top:3px;left:0;width:100%;height:100%;font-size:14px}.vjs-menu{font-family:Arial,'Helvetica Neue',Helvetica,sans-serif}.vjs-menu-content{overflow:inherit}.vjs-menu-item{text-align:right!important}.vjs-settings-sub-menu-title,.vjs-settings-sub-menu-value{display:inline-block;padding:0 4px}.vjs-menu-content{bottom:-1.5em}li{font-size:1em}.vjs-menu-item{text-align:center}.vjs-settings-sub-menu-title{width:auto;text-transform:initial}.vjs-settings-sub-menu{width:200px;position:fixed}.vjs-settings-menu.vjs-icon-cog.vjs-menu-button.vjs-menu-button-popup.vjs-button+.vjs-menu{width:30%;position:fixed;float:right;right:0;left:auto}.vjs-settings-menu{font-size:18px!important}.vjs-menu-button.vjs-menu-button-popup.vjs-control.vjs-button{font-size:14px}.vjs-menu-button-popup .vjs-menu .vjs-menu-content{background-color:#2b333f;background-color:rgba(0,0,0,.85);position:absolute;width:100%;bottom:1.3em;max-height:15em}.video-js .vjs-fullscreen-control{cursor:pointer;-webkit-box-flex:none;-moz-box-flex:none;-webkit-flex:none;-ms-flex:none;flex:none}.vjs-menu-button.vjs-menu-button-popup.vjs-control.vjs-button{font-size:14px}.video-js .vjs-live-control{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-flex:auto;-moz-box-flex:auto;-webkit-flex:auto;-ms-flex:auto;flex:auto;font-size:1em;line-height:3em;visibility:hidden;display:block!important}.vjs-thumbnail-holder{position:absolute;left:-1000px}.vjs-thumbnail{opacity:0;transition:opacity .2s ease;-webkit-transition:opacity .2s ease;-moz-transition:opacity .2s ease;-mz-transition:opacity .2s ease}.vjs-progress-control.fake-active .vjs-thumbnail,.vjs-progress-control:active .vjs-thumbnail,.vjs-progress-control:hover .vjs-thumbnail{opacity:1}.vjs-progress-control:active .vjs-thumbnail:active,.vjs-progress-control:hover .vjs-thumbnail:hover{opacity:0}.video-js .vjs-progress-control .vjs-mouse-display{display:none;position:absolute;width:0;height:100%;background-color:#000;z-index:1}.vjs-dtube-selected{color:red!important}
3 |
--------------------------------------------------------------------------------