Update video
This commit is contained in:
parent
57bc1e0a9f
commit
a61d2fabcd
|
@ -22,7 +22,7 @@
|
||||||
if (app.ports !== undefined && app.ports.registerVideo !== undefined) {
|
if (app.ports !== undefined && app.ports.registerVideo !== undefined) {
|
||||||
app.ports.registerVideo.subscribe(function(args) {
|
app.ports.registerVideo.subscribe(function(args) {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
var time = parseInt(args[2], 10) || undefined;
|
var time = vd.parseTime(args[2]) || undefined;
|
||||||
|
|
||||||
requestAnimationFrame(function() {
|
requestAnimationFrame(function() {
|
||||||
if (args[0] !== lastId) {
|
if (args[0] !== lastId) {
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
player = vd.setup(args[0], {
|
player = vd.setup(args[0], {
|
||||||
v: args[1] + "/manifest.mpd",
|
v: args[1] + "/manifest.mpd",
|
||||||
t: parseInt(args[2], 10) || 0,
|
t: time,
|
||||||
focus: true
|
focus: true
|
||||||
});
|
});
|
||||||
} else if (time !== undefined ){
|
} else if (time !== undefined ){
|
||||||
|
|
931
js/vd.js
931
js/vd.js
|
@ -3,488 +3,547 @@ const vd = (function() {
|
||||||
|
|
||||||
let vd = {};
|
let vd = {};
|
||||||
|
|
||||||
function createEl(tagName = 'div', properties = {}, attributes = {}, content) {
|
function createEl(tagName = 'div', properties = {}, attributes = {}, content) {
|
||||||
const el = document.createElement(tagName);
|
const el = document.createElement(tagName);
|
||||||
|
|
||||||
Object.getOwnPropertyNames(properties).forEach(function(propName) {
|
Object.getOwnPropertyNames(properties).forEach(function(propName) {
|
||||||
const val = properties[propName];
|
const val = properties[propName];
|
||||||
|
|
||||||
// See #2176
|
// See #2176
|
||||||
// We originally were accepting both properties and attributes in the
|
// We originally were accepting both properties and attributes in the
|
||||||
// same object, but that doesn't work so well.
|
// same object, but that doesn't work so well.
|
||||||
if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
|
if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
|
||||||
log.warn(tsml`Setting attributes in the second argument of createEl()
|
log.warn(tsml`Setting attributes in the second argument of createEl()
|
||||||
has been deprecated. Use the third argument instead.
|
has been deprecated. Use the third argument instead.
|
||||||
createEl(type, properties, attributes). Attempting to set ${propName} to ${val}.`);
|
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
|
// Handle textContent since it's not supported everywhere and we have a
|
||||||
// method for it.
|
// method for it.
|
||||||
} else if (propName === 'textContent') {
|
} else if (propName === 'textContent') {
|
||||||
textContent(el, val);
|
textContent(el, val);
|
||||||
} else {
|
} else {
|
||||||
el[propName] = val;
|
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;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findPosition(el) {
|
dispose() {
|
||||||
let box;
|
this.labelEl_ = null;
|
||||||
|
super.dispose();
|
||||||
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) {
|
buildCSSClass() {
|
||||||
if (el && el.getBoundingClientRect && el.parentNode) {
|
return `vjs-playback-rate ${super.buildCSSClass()}`;
|
||||||
const rect = el.getBoundingClientRect();
|
}
|
||||||
const result = {};
|
|
||||||
|
|
||||||
['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(k => {
|
buildWrapperCSSClass() {
|
||||||
if (rect[k] !== undefined) {
|
return `vjs-playback-rate ${super.buildWrapperCSSClass()}`;
|
||||||
result[k] = rect[k];
|
}
|
||||||
}
|
|
||||||
|
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) {
|
} else {
|
||||||
result.height = parseFloat(computedStyle(el, 'height'));
|
// Clicked on another button
|
||||||
}
|
let self = this;
|
||||||
|
this.player().tech({IWillNotUseThisInPlugins: true}).hls.representations().forEach(function(rep) {
|
||||||
if (!result.width) {
|
rep.enabled(rep.height === self.options_.representation.height);
|
||||||
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'
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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 {
|
handleClick() {
|
||||||
constructor() {
|
this.player().playbackRate(this.speed);
|
||||||
super(...arguments);
|
}
|
||||||
this.representation = arguments[1].representation;
|
}
|
||||||
this.menuButton = arguments[1].menuButton;
|
|
||||||
this.label = arguments[1].label;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick() {
|
class Thumbnail extends vjs.Component {
|
||||||
if (this.representation === undefined) {
|
constructor() {
|
||||||
// Clicked on the auto button
|
super(...arguments);
|
||||||
this.player().tech({IWillNotUseThisInPlugins: true}).hls.representations().forEach(function(rep) {
|
this.thumbnails = arguments[1].thumbnails;
|
||||||
rep.enabled(true);
|
this.width = arguments[1].width;
|
||||||
});
|
this.height = arguments[1].height;
|
||||||
|
|
||||||
} 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 SpeedItem extends vjs.MenuItem {
|
createEl() {
|
||||||
constructor() {
|
let el = super.createEl('img', {
|
||||||
super(...arguments);
|
src: this.options_.thumbnails[0],
|
||||||
this.label = arguments[1].label;
|
width: '0px',
|
||||||
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'
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (args.focus === true) {
|
el.style.position = 'absolute';
|
||||||
player.focus();
|
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;
|
return el;
|
||||||
player.requestFullscreen = function() {
|
}
|
||||||
var player = document.getElementById(this.id());
|
|
||||||
if (player === null) {
|
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;
|
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;
|
let p = document.getElementById(player.id());
|
||||||
player.exitFullscreen = function() {
|
if (p === null) {
|
||||||
var player = document.getElementById(this.id());
|
|
||||||
if (player === null) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
player = typeof player.player === "function" ? player.player() : player.player;
|
p = typeof p.player === "function" ? p.player() : p.player;
|
||||||
player._oldExitFullscreen(...arguments);
|
|
||||||
setTimeout(() => {
|
|
||||||
if (screen.orientation) {
|
|
||||||
screen.orientation.unlock();
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (args.t !== undefined) {
|
switch (e.keyCode) {
|
||||||
let time = parseFloat(args.t);
|
case 37: e.preventDefault(); p.currentTime(p.currentTime() - 5); break;
|
||||||
if (!isNaN(time)) {
|
case 39: e.preventDefault(); p.currentTime(p.currentTime() + 5); break;
|
||||||
player.currentTime(time);
|
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) {
|
let p = document.getElementById(player.id());
|
||||||
player.el().addEventListener('keydown', (e) => {
|
if (p === null) {
|
||||||
if (e.ctrlKey || e.shiftKey || e.altKey) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
p = typeof p.player === "function" ? p.player() : p.player;
|
||||||
|
|
||||||
let p = document.getElementById(player.id());
|
switch (e.keyCode) {
|
||||||
if (p === null) {
|
case 74: e.preventDefault(); p.currentTime(p.currentTime() - 10); break; // J -> -10s
|
||||||
return;
|
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
|
||||||
p = typeof p.player === "function" ? p.player() : p.player;
|
case 77: e.preventDefault(); p.muted(!p.muted()); break; // M -> mute
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
// F -> toggle fullscreen
|
// F -> toggle fullscreen
|
||||||
case 70:
|
case 70:
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (p.isFullscreen()) {
|
if (p.isFullscreen()) {
|
||||||
p.exitFullscreen();
|
p.exitFullscreen();
|
||||||
} else {
|
} else {
|
||||||
p.requestFullscreen();
|
p.requestFullscreen();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Seek shortcuts
|
// Seek shortcuts
|
||||||
case 48: case 96: e.preventDefault(); p.currentTime(0); break;
|
case 48: case 96: e.preventDefault(); p.currentTime(0); break;
|
||||||
case 49: case 97: e.preventDefault(); p.currentTime( p.duration() / 10); 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 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 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 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 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 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 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 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;
|
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");
|
|
||||||
}
|
}
|
||||||
|
}, true);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let element of document.getElementsByTagName('video')) {
|
let root = src.split('/').slice(0, -1).join('/');
|
||||||
let src = element.getAttribute('data-dash-src');
|
if (root !== "") {
|
||||||
if (src != undefined) {
|
root += "/";
|
||||||
vd.setup(element, src);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
return vd;
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -185,11 +185,11 @@ update msg model =
|
||||||
( [], Dict.empty )
|
( [], Dict.empty )
|
||||||
|
|
||||||
time =
|
time =
|
||||||
case Maybe.map String.toInt (Dict.get "t" args) of
|
case Dict.get "t" args of
|
||||||
Just (Just 0) ->
|
Just "0" ->
|
||||||
Nothing
|
Nothing
|
||||||
|
|
||||||
Just (Just t) ->
|
Just t ->
|
||||||
Just t
|
Just t
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
port module Ports exposing (eraseVideo, registerVideo)
|
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
|
port eraseVideo : () -> Cmd msg
|
||||||
|
|
Loading…
Reference in New Issue