From a61d2fabcd916f6db31e2c631af8d5c0f88b447c Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Thu, 5 Nov 2020 17:18:16 +0100 Subject: [PATCH] Update video --- index.html | 4 +- js/vd.js | 931 +++++++++++++++++++++++++++----------------------- src/Core.elm | 6 +- src/Ports.elm | 2 +- 4 files changed, 501 insertions(+), 442 deletions(-) diff --git a/index.html b/index.html index b48f62c..4fe54d1 100644 --- a/index.html +++ b/index.html @@ -22,7 +22,7 @@ if (app.ports !== undefined && app.ports.registerVideo !== undefined) { app.ports.registerVideo.subscribe(function(args) { window.scrollTo(0, 0); - var time = parseInt(args[2], 10) || undefined; + var time = vd.parseTime(args[2]) || undefined; requestAnimationFrame(function() { if (args[0] !== lastId) { @@ -30,7 +30,7 @@ player = vd.setup(args[0], { v: args[1] + "/manifest.mpd", - t: parseInt(args[2], 10) || 0, + t: time, focus: true }); } else if (time !== undefined ){ diff --git a/js/vd.js b/js/vd.js index 3eaffcb..e6acf5a 100644 --- a/js/vd.js +++ b/js/vd.js @@ -3,488 +3,547 @@ const vd = (function() { let vd = {}; - function createEl(tagName = 'div', properties = {}, attributes = {}, content) { - const el = document.createElement(tagName); +function createEl(tagName = 'div', properties = {}, attributes = {}, content) { + const el = document.createElement(tagName); - Object.getOwnPropertyNames(properties).forEach(function(propName) { - const val = properties[propName]; + Object.getOwnPropertyNames(properties).forEach(function(propName) { + const val = properties[propName]; - // See #2176 - // We originally were accepting both properties and attributes in the - // same object, but that doesn't work so well. - if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { - log.warn(tsml`Setting attributes in the second argument of createEl() + // See #2176 + // We originally were accepting both properties and attributes in the + // same object, but that doesn't work so well. + if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { + log.warn(tsml`Setting attributes in the second argument of createEl() has been deprecated. Use the third argument instead. createEl(type, properties, attributes). Attempting to set ${propName} to ${val}.`); - el.setAttribute(propName, val); + el.setAttribute(propName, val); - // Handle textContent since it's not supported everywhere and we have a - // method for it. - } else if (propName === 'textContent') { - textContent(el, val); - } else { - el[propName] = val; + // Handle textContent since it's not supported everywhere and we have a + // method for it. + } else if (propName === 'textContent') { + textContent(el, val); + } else { + el[propName] = val; + } + }); + + return el; +} + +function findPosition(el) { + let box; + + if (el.getBoundingClientRect && el.parentNode) { + box = el.getBoundingClientRect(); + } + + if (!box) { + return { + left: 0, + top: 0 + }; + } + + const docEl = document.documentElement; + const body = document.body; + + const clientLeft = docEl.clientLeft || body.clientLeft || 0; + const scrollLeft = window.pageXOffset || body.scrollLeft; + const left = box.left + scrollLeft - clientLeft; + + const clientTop = docEl.clientTop || body.clientTop || 0; + const scrollTop = window.pageYOffset || body.scrollTop; + const top = box.top + scrollTop - clientTop; + + // Android sometimes returns slightly off decimal values, so need to round + return { + left: Math.round(left), + top: Math.round(top) + }; +} + +function getBoundingClientRect(el) { + if (el && el.getBoundingClientRect && el.parentNode) { + const rect = el.getBoundingClientRect(); + const result = {}; + + ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(k => { + if (rect[k] !== undefined) { + result[k] = rect[k]; } }); + if (!result.height) { + result.height = parseFloat(computedStyle(el, 'height')); + } + + if (!result.width) { + result.width = parseFloat(computedStyle(el, 'width')); + } + + return result; + } +} + +function getPointerPosition(el, event) { + const position = {}; + const box = findPosition(el); + const boxW = el.offsetWidth; + const boxH = el.offsetHeight; + + const boxY = box.top; + const boxX = box.left; + let pageY = event.pageY; + let pageX = event.pageX; + + if (event.changedTouches) { + pageX = event.changedTouches[0].pageX; + pageY = event.changedTouches[0].pageY; + } + + position.y = Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH)); + position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW)); + + return position; +} + +let vjs = {}; +vjs.Component = videojs.getComponent('Component'); +vjs.Menu = videojs.getComponent('Menu'); +vjs.MenuButton = videojs.getComponent('MenuButton'); +vjs.MenuItem = videojs.getComponent('MenuItem'); + +class MenuButton extends vjs.MenuButton { + + constructor(player, options) { + super(player, options); + this.updateLabel(options === undefined ? "" : options.label || ""); + } + + createEl() { + const el = super.createEl(); + + this.labelEl_ = createEl('div', { + className: 'vjs-playback-rate-value', + innerHTML: 'auto' + }); + + el.appendChild(this.labelEl_); + return el; } - function findPosition(el) { - let box; - - if (el.getBoundingClientRect && el.parentNode) { - box = el.getBoundingClientRect(); - } - - if (!box) { - return { - left: 0, - top: 0 - }; - } - - const docEl = document.documentElement; - const body = document.body; - - const clientLeft = docEl.clientLeft || body.clientLeft || 0; - const scrollLeft = window.pageXOffset || body.scrollLeft; - const left = box.left + scrollLeft - clientLeft; - - const clientTop = docEl.clientTop || body.clientTop || 0; - const scrollTop = window.pageYOffset || body.scrollTop; - const top = box.top + scrollTop - clientTop; - - // Android sometimes returns slightly off decimal values, so need to round - return { - left: Math.round(left), - top: Math.round(top) - }; + dispose() { + this.labelEl_ = null; + super.dispose(); } - function getBoundingClientRect(el) { - if (el && el.getBoundingClientRect && el.parentNode) { - const rect = el.getBoundingClientRect(); - const result = {}; + buildCSSClass() { + return `vjs-playback-rate ${super.buildCSSClass()}`; + } - ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(k => { - if (rect[k] !== undefined) { - result[k] = rect[k]; - } + buildWrapperCSSClass() { + return `vjs-playback-rate ${super.buildWrapperCSSClass()}`; + } + + createMenu() { + const menu = new vjs.Menu(this.player()); + return menu; + } + + updateARIAAttributes() { + this.el().setAttribute('aria-valuenow', this.labelEl_.innerHTML); + } + + updateLabel(newLabel) { + this.labelEl_.innerHTML = newLabel; + } + +} + +class ResolutionItem extends vjs.MenuItem { + constructor() { + super(...arguments); + this.representation = arguments[1].representation; + this.menuButton = arguments[1].menuButton; + this.label = arguments[1].label; + } + + handleClick() { + if (this.representation === undefined) { + // Clicked on the auto button + this.player().tech({IWillNotUseThisInPlugins: true}).hls.representations().forEach(function(rep) { + rep.enabled(true); }); - if (!result.height) { - result.height = parseFloat(computedStyle(el, 'height')); - } - - if (!result.width) { - result.width = parseFloat(computedStyle(el, 'width')); - } - - return result; - } - } - - function getPointerPosition(el, event) { - const position = {}; - const box = findPosition(el); - const boxW = el.offsetWidth; - const boxH = el.offsetHeight; - - const boxY = box.top; - const boxX = box.left; - let pageY = event.pageY; - let pageX = event.pageX; - - if (event.changedTouches) { - pageX = event.changedTouches[0].pageX; - pageY = event.changedTouches[0].pageY; - } - - position.y = Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH)); - position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW)); - - return position; - } - - function setupRepresentations(menuButton, player) { - - let hls = player.tech({IWillNotUseThisInPlugins: true}).hls; - - if (hls === undefined) { - setTimeout(() => setupRepresentations(menuButton, player), 500); - return; - } - - let representations = hls.representations(); - representations.sort((a, b) => b.height - a.height); - - menuButton.menu.items = []; - menuButton.menu.addAndRecordItem = function(item) { - this.addItem(item); - this.items.push(item); - } - - menuButton.menu.addAndRecordItem(new MenuItem(player, { label: "auto", menuButton })); - for (let representation of representations) { - menuButton.menu.addAndRecordItem(new MenuItem(player, { - label: representation.height + "p", - representation, - menuButton - })); - } - } - - function setupSpeed(menuButton, player) { - let speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]; - menuButton.updateLabel('x1'); - menuButton.menu.items = []; - menuButton.menu.addAndRecordItem = function(item) { - this.addItem(item); - this.items.push(item); - } - - for (let speed of speeds) { - menuButton.menu.addAndRecordItem(new SpeedItem(player, { - label: "x" + speed, - speed, - })); - } - } - - let vjs = {}; - - vjs.Component = videojs.getComponent('Component'); - vjs.Menu = videojs.getComponent('Menu'); - vjs.MenuButton = videojs.getComponent('MenuButton'); - vjs.MenuItem = videojs.getComponent('MenuItem'); - - class MenuButton extends vjs.MenuButton { - - constructor(player, options) { - super(player, options); - this.updateLabel('auto'); - } - - createEl() { - const el = super.createEl(); - - this.labelEl_ = createEl('div', { - className: 'vjs-playback-rate-value', - innerHTML: 'auto' + } else { + // Clicked on another button + let self = this; + this.player().tech({IWillNotUseThisInPlugins: true}).hls.representations().forEach(function(rep) { + rep.enabled(rep.height === self.options_.representation.height); }); - - el.appendChild(this.labelEl_); - - return el; - } - - dispose() { - this.labelEl_ = null; - super.dispose(); - } - - buildCSSClass() { - return `vjs-playback-rate ${super.buildCSSClass()}`; - } - - buildWrapperCSSClass() { - return `vjs-playback-rate ${super.buildWrapperCSSClass()}`; - } - - createMenu() { - const menu = new vjs.Menu(this.player()); - return menu; - } - - updateARIAAttributes() { - this.el().setAttribute('aria-valuenow', this.labelEl_.innerHTML); - } - - updateLabel(newLabel) { - this.labelEl_.innerHTML = newLabel; } + this.menuButton.updateLabel(this.label); + } +} +class SpeedItem extends vjs.MenuItem { + constructor() { + super(...arguments); + this.label = arguments[1].label; + this.speed = arguments[1].speed; } - class MenuItem extends vjs.MenuItem { - constructor() { - super(...arguments); - this.representation = arguments[1].representation; - this.menuButton = arguments[1].menuButton; - this.label = arguments[1].label; - } + handleClick() { + this.player().playbackRate(this.speed); + } +} - handleClick() { - if (this.representation === undefined) { - // Clicked on the auto button - this.player().tech({IWillNotUseThisInPlugins: true}).hls.representations().forEach(function(rep) { - rep.enabled(true); - }); - - } else { - // Clicked on another button - let self = this; - this.player().tech({IWillNotUseThisInPlugins: true}).hls.representations().forEach(function(rep) { - rep.enabled(rep.height === self.options_.representation.height); - }); - } - this.menuButton.updateLabel(this.label); - } +class Thumbnail extends vjs.Component { + constructor() { + super(...arguments); + this.thumbnails = arguments[1].thumbnails; + this.width = arguments[1].width; + this.height = arguments[1].height; } - class SpeedItem extends vjs.MenuItem { - constructor() { - super(...arguments); - this.label = arguments[1].label; - this.speed = arguments[1].speed; - } - - handleClick() { - this.player().playbackRate(this.speed); - } - } - - class Thumbnail extends vjs.Component { - constructor() { - super(...arguments); - this.thumbnails = arguments[1].thumbnails; - this.width = arguments[1].width; - this.height = arguments[1].height; - } - - createEl() { - let el = super.createEl('img', { - src: this.options_.thumbnails[0], - width: '0px', - }); - - el.style.position = 'absolute'; - el.style.left = '0px'; - el.style.top = -this.options_.height + "px"; - el.style.border = "solid"; - el.style.borderColor = "black"; - el.style.borderWidth = "1px"; - - return el; - } - - update(ratio) { - this.el().src = this.options_.thumbnails[Math.round(100 * ratio)]; - } - } - - - vd.setup = function(video, args) { - - let src; - - if (typeof video === 'string' || video instanceof String) { - video = document.getElementById(video); - } - - if (video === undefined) { - throw new Error("video element or ID invalid"); - } - - if (typeof args === "string" || args instanceof String) { - src = args; - } - - if (typeof args === "object") { - src = args.v; - } - - if (src === undefined) { - throw new Error("video src is undefined"); - } - - let player = videojs(video); - player.src({ - src, - type: 'application/dash+xml' + createEl() { + let el = super.createEl('img', { + src: this.options_.thumbnails[0], + width: '0px', }); - if (args.focus === true) { - player.focus(); - } + el.style.position = 'absolute'; + el.style.left = '0px'; + el.style.top = -this.options_.height + "px"; + el.style.border = "solid"; + el.style.borderColor = "black"; + el.style.borderWidth = "1px"; - player._oldRequestFullscreen = player.requestFullscreen; - player.requestFullscreen = function() { - var player = document.getElementById(this.id()); - if (player === null) { + return el; + } + + update(ratio) { + this.el().src = this.options_.thumbnails[Math.round(100 * ratio)]; + } +} + +function createRepresentationButtons(player, menuButton = undefined) { + if (menuButton === undefined) { + menuButton = new MenuButton(player); + menuButton.updateLabel("auto"); + } + + let hls = player.tech({IWillNotUseThisInPlugins: true}).hls; + + if (hls === undefined) { + setTimeout(() => createRepresentationButtons(player, menuButton), 500); + return menuButton; + } + + let representations = hls.representations(); + representations.sort((a, b) => b.height - a.height); + + menuButton.menu.items = []; + menuButton.menu.addAndRecordItem = function(item) { + this.addItem(item); + this.items.push(item); + } + + menuButton.menu.addAndRecordItem(new ResolutionItem(player, { label: "auto", menuButton })); + for (let representation of representations) { + menuButton.menu.addAndRecordItem(new ResolutionItem(player, { + label: representation.height + "p", + representation, + menuButton + })); + } + + return menuButton; +} + +function createSpeedButtons(player) { + let menuButton = new MenuButton(player); + + let speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]; + menuButton.updateLabel('x1'); + menuButton.menu.items = []; + menuButton.menu.addAndRecordItem = function(item) { + this.addItem(item); + this.items.push(item); + } + + for (let speed of speeds) { + menuButton.menu.addAndRecordItem(new SpeedItem(player, { + label: "x" + speed, + speed, + })); + } + + return menuButton; +} + +vd.parseTime = function(t) { + let parsed = 1 * t; + + if (!isNaN(parsed)) { + return parsed; + } + + // Compute the split + let split = t.split("h"); + + let hours; + let minutes; + let seconds; + + switch (split.length) { + case 1: + hours = 0; + split = split[0].split("m"); + break; + case 2: + hours = 1 * split[0]; + if (isNaN(hours)) { + return NaN; + } + split = split[1].split("m"); + break; + default: + return NaN; + } + + switch (split.length) { + case 1: + minutes = 0; + split = split[0].split("s"); + break; + case 2: + minutes = 1 * split[0]; + if (isNaN(minutes)) { + return NaN; + } + split = split[1].split("s"); + break; + default: + return NaN; + } + + seconds = 1 * split[0]; + if ((split.length !== 1 && (! (split.length == 2 && split[1] === ""))) || isNaN(seconds)) { + return NaN; + } + + return 3600 * hours + 60 * minutes + seconds; +} + +vd.setup = function(video, args) { + + let src; + + if (typeof video === 'string' || video instanceof String) { + video = document.getElementById(video); + } + + if (video === undefined) { + throw new Error("video element or ID invalid"); + } + + if (typeof args === "string" || args instanceof String) { + src = args; + } + + if (typeof args === "object") { + src = args.v; + } + + if (src === undefined) { + throw new Error("video src is undefined"); + } + + let player = videojs(video); + player.src({ + src, + type: 'application/dash+xml' + }); + + if (args.focus === true) { + player.focus(); + } + + player._oldRequestFullscreen = player.requestFullscreen; + player.requestFullscreen = function() { + var player = document.getElementById(this.id()); + if (player === null) { + return; + } + player = typeof player.player === "function" ? player.player() : player.player; + player._oldRequestFullscreen(...arguments); + setTimeout(() => { + if (screen.orientation) { + screen.orientation.lock("landscape") + } + }, 1000); + }; + + player._oldExitFullscreen = player.exitFullscreen; + player.exitFullscreen = function() { + var player = document.getElementById(this.id()); + if (player === null) { + return; + } + player = typeof player.player === "function" ? player.player() : player.player; + player._oldExitFullscreen(...arguments); + setTimeout(() => { + if (screen.orientation) { + screen.orientation.unlock(); + } + }, 1000); + }; + + if (args.t !== undefined) { + let time = vd.parseTime(args.t); + if (!isNaN(time)) { + player.currentTime(time); + } + } + + + if (player.getAttribute('disable-shortcuts') == undefined) { + player.el().addEventListener('keydown', (e) => { + if (e.ctrlKey || e.shiftKey || e.altKey) { return; } - player = typeof player.player === "function" ? player.player() : player.player; - player._oldRequestFullscreen(...arguments); - setTimeout(() => { - if (screen.orientation) { - screen.orientation.lock("landscape") - } - }, 1000); - }; - player._oldExitFullscreen = player.exitFullscreen; - player.exitFullscreen = function() { - var player = document.getElementById(this.id()); - if (player === null) { + let p = document.getElementById(player.id()); + if (p === null) { return; } - player = typeof player.player === "function" ? player.player() : player.player; - player._oldExitFullscreen(...arguments); - setTimeout(() => { - if (screen.orientation) { - screen.orientation.unlock(); - } - }, 1000); - }; + p = typeof p.player === "function" ? p.player() : p.player; - if (args.t !== undefined) { - let time = parseFloat(args.t); - if (!isNaN(time)) { - player.currentTime(time); + switch (e.keyCode) { + case 37: e.preventDefault(); p.currentTime(p.currentTime() - 5); break; + case 39: e.preventDefault(); p.currentTime(p.currentTime() + 5); break; + case 32: e.preventDefault(); if (p.paused()) p.play(); else p.pause(); break; + case 40: e.preventDefault(); p.volume(p.volume() - 0.1); break; + case 38: e.preventDefault(); p.volume(p.volume() + 0.1); break; } - } + }, true); + document.body.addEventListener('keydown', (e) => { + if (e.ctrlKey || e.shiftKey || e.altKey) { + return; + } - if (player.getAttribute('disable-shortcuts') == undefined) { - player.el().addEventListener('keydown', (e) => { - if (e.ctrlKey || e.shiftKey || e.altKey) { - return; - } + let p = document.getElementById(player.id()); + if (p === null) { + return; + } + p = typeof p.player === "function" ? p.player() : p.player; - let p = document.getElementById(player.id()); - if (p === null) { - return; - } - p = typeof p.player === "function" ? p.player() : p.player; - - switch (e.keyCode) { - case 37: e.preventDefault(); p.currentTime(p.currentTime() - 5); break; - case 39: e.preventDefault(); p.currentTime(p.currentTime() + 5); break; - case 32: e.preventDefault(); if (p.paused()) p.play(); else p.pause(); break; - case 40: e.preventDefault(); p.volume(p.volume() - 0.1); break; - case 38: e.preventDefault(); p.volume(p.volume() + 0.1); break; - } - }, true); - - document.body.addEventListener('keydown', (e) => { - if (e.ctrlKey || e.shiftKey || e.altKey) { - return; - } - - let p = document.getElementById(player.id()); - if (p === null) { - return; - } - p = typeof p.player === "function" ? p.player() : p.player; - - switch (e.keyCode) { - case 74: e.preventDefault(); p.currentTime(p.currentTime() - 10); break; // J -> -10s - case 76: e.preventDefault(); p.currentTime(p.currentTime() + 10); break; // L -> +10s - case 75: e.preventDefault(); if (p.paused()) p.play(); else p.pause(); break; // K -> play/pause - case 77: e.preventDefault(); p.muted(!p.muted()); break; // M -> mute + switch (e.keyCode) { + case 74: e.preventDefault(); p.currentTime(p.currentTime() - 10); break; // J -> -10s + case 76: e.preventDefault(); p.currentTime(p.currentTime() + 10); break; // L -> +10s + case 75: e.preventDefault(); if (p.paused()) p.play(); else p.pause(); break; // K -> play/pause + case 77: e.preventDefault(); p.muted(!p.muted()); break; // M -> mute // F -> toggle fullscreen - case 70: - e.preventDefault(); + case 70: + e.preventDefault(); - if (p.isFullscreen()) { - p.exitFullscreen(); - } else { - p.requestFullscreen(); - } - break; + if (p.isFullscreen()) { + p.exitFullscreen(); + } else { + p.requestFullscreen(); + } + break; // Seek shortcuts - case 48: case 96: e.preventDefault(); p.currentTime(0); break; - case 49: case 97: e.preventDefault(); p.currentTime( p.duration() / 10); break; - case 50: case 98: e.preventDefault(); p.currentTime(2 * p.duration() / 10); break; - case 51: case 99: e.preventDefault(); p.currentTime(3 * p.duration() / 10); break; - case 52: case 100: e.preventDefault(); p.currentTime(4 * p.duration() / 10); break; - case 53: case 101: e.preventDefault(); p.currentTime(5 * p.duration() / 10); break; - case 54: case 102: e.preventDefault(); p.currentTime(6 * p.duration() / 10); break; - case 55: case 103: e.preventDefault(); p.currentTime(7 * p.duration() / 10); break; - case 56: case 104: e.preventDefault(); p.currentTime(8 * p.duration() / 10); break; - case 57: case 105: e.preventDefault(); p.currentTime(9 * p.duration() / 10); break; - } - }, true); - } - - let root = src.split('/').slice(0, -1).join('/'); - if (root !== "") { - root += "/"; - } - - if (player.getAttribute('disable-thumbnails') == undefined) { - - let thumbnails = []; - const MAX_INDEX = 100; - for (let i = 0; i <= MAX_INDEX; i++) { - thumbnails.push(root + "miniature-" + ("" + i).padStart(3, "0") + ".png"); + case 48: case 96: e.preventDefault(); p.currentTime(0); break; + case 49: case 97: e.preventDefault(); p.currentTime( p.duration() / 10); break; + case 50: case 98: e.preventDefault(); p.currentTime(2 * p.duration() / 10); break; + case 51: case 99: e.preventDefault(); p.currentTime(3 * p.duration() / 10); break; + case 52: case 100: e.preventDefault(); p.currentTime(4 * p.duration() / 10); break; + case 53: case 101: e.preventDefault(); p.currentTime(5 * p.duration() / 10); break; + case 54: case 102: e.preventDefault(); p.currentTime(6 * p.duration() / 10); break; + case 55: case 103: e.preventDefault(); p.currentTime(7 * p.duration() / 10); break; + case 56: case 104: e.preventDefault(); p.currentTime(8 * p.duration() / 10); break; + case 57: case 105: e.preventDefault(); p.currentTime(9 * p.duration() / 10); break; } - - let thumbnail = new Thumbnail(player, { - thumbnails, - width: 192 * 0.75, - height: 108 * 0.75, - }); - - player.controlBar.progressControl.addChild(thumbnail); - - - player.controlBar.progressControl.el().addEventListener('mouseenter', e => { - thumbnail.el().width = thumbnail.width; - thumbnail.update(e.offsetX / e.target.offsetWidth); - }); - - player.controlBar.progressControl.el().addEventListener('mouseleave', e => { - thumbnail.el().width = 0; - }); - - player.controlBar.progressControl.on('mousemove', (event) => { - const seekBar = player.controlBar.progressControl.seekBar; - - const seekBarEl = seekBar.el(); - const seekBarRect = getBoundingClientRect(seekBarEl); - let seekBarPoint = getPointerPosition(seekBarEl, event).x; - - seekBarPoint = Math.max(0, Math.min(1, seekBarPoint)); - thumbnail.update(seekBarPoint); - thumbnail.el().style.left = (seekBarPoint * seekBarRect.width - thumbnail.width / 2) + "px"; - }); - - } - - let controlBar = player.getChild('controlBar'); - let fullscreenButton = controlBar.children()[controlBar.children().length - 1]; - controlBar.removeChild(fullscreenButton); - let menuButton = new MenuButton(player); - let speedButton = new MenuButton(player); - controlBar.addChild(speedButton, {}); - controlBar.addChild(menuButton, {}); - controlBar.addChild(fullscreenButton, {}); - - setupSpeed(speedButton, player); - setupRepresentations(menuButton, player); - - // videojs.Html5DashJS.hook('beforeinitialize', (p, mp) => setupRepresentations(menuButton, p, mp)); - window.player = player; - - if (video.getAttribute('autoplay') != undefined) { - player.play(); - } - - return player; + }, true); } - for (let element of document.getElementsByTagName('video')) { - let src = element.getAttribute('data-dash-src'); - if (src != undefined) { - vd.setup(element, src); - } + let root = src.split('/').slice(0, -1).join('/'); + if (root !== "") { + root += "/"; } + if (player.getAttribute('disable-thumbnails') == undefined) { + + let thumbnails = []; + const MAX_INDEX = 100; + for (let i = 0; i <= MAX_INDEX; i++) { + thumbnails.push(root + "miniature-" + ("" + i).padStart(3, "0") + ".png"); + } + + let thumbnail = new Thumbnail(player, { + thumbnails, + width: 192 * 0.75, + height: 108 * 0.75, + }); + + player.controlBar.progressControl.addChild(thumbnail); + + + player.controlBar.progressControl.el().addEventListener('mouseenter', e => { + thumbnail.el().width = thumbnail.width; + thumbnail.update(e.offsetX / e.target.offsetWidth); + }); + + player.controlBar.progressControl.el().addEventListener('mouseleave', e => { + thumbnail.el().width = 0; + }); + + player.controlBar.progressControl.on('mousemove', (event) => { + const seekBar = player.controlBar.progressControl.seekBar; + + const seekBarEl = seekBar.el(); + const seekBarRect = getBoundingClientRect(seekBarEl); + let seekBarPoint = getPointerPosition(seekBarEl, event).x; + + seekBarPoint = Math.max(0, Math.min(1, seekBarPoint)); + thumbnail.update(seekBarPoint); + thumbnail.el().style.left = (seekBarPoint * seekBarRect.width - thumbnail.width / 2) + "px"; + }); + + } + + let controlBar = player.getChild('controlBar'); + let fullscreenButton = controlBar.children()[controlBar.children().length - 1]; + controlBar.removeChild(fullscreenButton); + + let menuButton = createRepresentationButtons(player); + let speedButton = createSpeedButtons(player); + controlBar.addChild(speedButton, {}); + controlBar.addChild(menuButton, {}); + controlBar.addChild(fullscreenButton, {}); + + window.player = player; + + if (video.getAttribute('autoplay') != undefined) { + player.play(); + } + + return player; +} + +for (let element of document.getElementsByTagName('video')) { + let src = element.getAttribute('data-dash-src'); + if (src != undefined) { + vd.setup(element, src); + } +} + + return vd; - })(); diff --git a/src/Core.elm b/src/Core.elm index 9e4aba4..bc85487 100644 --- a/src/Core.elm +++ b/src/Core.elm @@ -185,11 +185,11 @@ update msg model = ( [], Dict.empty ) time = - case Maybe.map String.toInt (Dict.get "t" args) of - Just (Just 0) -> + case Dict.get "t" args of + Just "0" -> Nothing - Just (Just t) -> + Just t -> Just t _ -> diff --git a/src/Ports.elm b/src/Ports.elm index cdf4d98..9756a7b 100644 --- a/src/Ports.elm +++ b/src/Ports.elm @@ -1,7 +1,7 @@ port module Ports exposing (eraseVideo, registerVideo) -port registerVideo : ( String, String, Maybe Int ) -> Cmd msg +port registerVideo : ( String, String, Maybe String ) -> Cmd msg port eraseVideo : () -> Cmd msg