├── .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 | 5 | 9 | Plan de travail 1 10 | 13 | 14 | 21 | 24 | 26 | 29 | 32 | 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