vidplayer
Version:
A simple to use videoplayer to do the styling and fixes the eventhandlers
693 lines (593 loc) • 29.4 kB
JavaScript
const errorMessages = {
warningStart: '"%c" +',
warningEnd: ', "color: yellow; font-size: 40px; font-weight: bold;"',
ErrorNoVideo: "There is no <video> element with the class name of video-object.\n\nFIX: Create a <video> element with a class of video-object \n\n\nEXAMPLE (best practice):\n<video class='video-object'>\n <source src='./video/source.mp4' type='video/mp4'>\n</video>",
noVidSettings: "Make sure the data-settings dataset is set on as an Object \n\nTry <video ... data-setting='{}'> </video> \n\n\nThe default value will now be used instead (default style='default')",
wrongTheme: "The theme you tried to use is undefined. \nTry eg. 'flasy', 'flashy-minimal', 'box', 'box-minimal', 'minimal' or 'default' \n\nMore styles coming very soon.."
};
const themes = ["flashy", "flashy-minimal", "box", "box-minimal", "minimal", "default"]
var Videos = document.querySelectorAll(".vidplayer");
class vidplayerJS {
constructor(node, index) {
this.node = node
this.defaultStyling = this.getDefaultStyling();
this.id = index + 1
this.settings = getVideoSettings(this.node)
if (this.settings) {
this.settings?.dev == true ? this.node.controls = true : this.controls = false;
if (this.settings?.theme != undefined) {
const item = themes.find(element => element == this.settings?.theme)
if(item) {
this.theme = this.settings.theme
} else {
warningMes(errorMessages.wrongTheme)
this.theme = "default"
}}
} else {
this.theme = "default";
}
// CHECK if user uses a phone in that case use the
this.render()
}
bindEvents() {
// LOAD METADATA
this.node.addEventListener("loadedmetadata", (e) => {
this.timeManager(this.elements.timer, this.node.currentTime)
})
// ACTION (play/pause/end)
this.elements.buttonMiddle.addEventListener("click", (e) => {
this.videoAction("play", e)
})
this.elements.buttonController.addEventListener("click", (e) => {
this.videoAction("play", e)
})
this.node.addEventListener("ended", (e) => {
this.videoAction("play", e)
})
this.node.addEventListener("click", (e) => {
this.videoAction("play", e)
})
// TIME + LOADING
this.node.addEventListener("stalled", (e) => {
this.videoAction("loading", e)
})
this.node.addEventListener("waiting", (e) => {
this.videoAction("loading", e)
})
this.node.addEventListener("canplay", (e) => {
this.videoAction("buffer", e)
this.videoAction("timer", e)
this.videoAction("loading", e)
})
this.node.addEventListener("canplaythrough", (e) => {
this.videoAction("buffer", e)
this.videoAction("timer", e)
})
this.elements.runnableTrack.addEventListener("input", (e) => {
this.videoAction("timer", e)
})
this.elements.runnableTrack.addEventListener("change", (e) => {
this.videoAction("timer", e)
})
// VOLUME
this.elements.volume.volumeButton.addEventListener("click", (e) => {
this.videoAction("volume", e)
})
this.elements.volume.volumeTrack.addEventListener("input", (e) => {
this.videoAction("volume", e)
})
// FULL SCREEN
this.elements.fullscreen.addEventListener("click", (e) => {
this.videoAction("fullscreen", e)
})
document.addEventListener("fullscreenchange", (e) => {
let isFullScreen = document.fullscreenElement || document.msFullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement;
if(e.target == this.node || e.target == this.blocks.parentNode){
if(!isFullScreen) {
this.videoAction("fullscreen", "close")
}
}
})
document.addEventListener("webkitfullscreenchange", (e) => {
let isFullScreen = document.fullscreenElement || document.msFullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement;
if(e.target == this.node || e.target == this.blocks.parentNode){
if(!isFullScreen) {
this.videoAction("fullscreen", "close")
}
}
});
this.node.addEventListener("dblclick", () => {
this.videoAction("fullscreen")
})
// KEY EVENTS
document.addEventListener("keydown", (e) => {
switch (e.key.toLowerCase()) {
case "m":
e.preventDefault()
this.videoAction("volume", e)
break;
case "f":
this.videoAction("fullscreen", "close")
default:
break;
}
})
}
init() {
if (!this.node.poster) {
this.node.style.background = "#333"
}
if(!!("ontouchstart" in window) || window.navigator.msMaxTouchPoints > 0){
document.all[0].setAttribute("onclick", "")
}
this.blocks = this.makeBlockElements()
this.elements = this.makeElementElements()
this.appendElements()
if(typeof this.settings.colorscheme === "string") {
this.blocks.parentNode.style.setProperty("--base-color", "rgba(11,11,11, .75)")
this.blocks.parentNode.style.setProperty("--tile-color", this.settings.colorscheme || "rgba(220, 220, 220, 1)")
this.blocks.parentNode.style.setProperty("--hover-color", colorScheme(this.settings.colorscheme) || "rgba(200, 200, 200, 1)")
} else {
this.blocks.parentNode.style.setProperty("--base-color", this.settings.colorscheme ? this.settings.colorscheme[0] : "rgba(11,11,11, .75)")
this.blocks.parentNode.style.setProperty("--tile-color", this.settings.colorscheme ? this.settings.colorscheme[1] : "rgba(220, 220, 220, 1)")
if(this.settings.colorscheme[2]) {
this.blocks.parentNode.style.setProperty("--hover-color", this.settings.colorscheme[2])
} else {
this.blocks.parentNode.style.setProperty("--hover-color", this.settings.colorscheme ? colorScheme(this.settings.colorscheme[1]) : "rgba(200, 200, 200, 1)")
}
}
}
makeBlockElements() {
const parentNode = assignParent("section", ["vidplayer-container", `vidplayer-container-${this.id}`], this.node, this.theme)
const blockControls = DOMelementWithClass("section", "vidplayer-controls"); // middle
const blockController = DOMelementWithClass("section", "vidplayer-controller"); // bottom
const styleSheet = this.makeElement("stylesheet")
return {
parentNode,
blockControls,
blockController,
styleSheet
}
}
makeElementElements() {
const buttonMiddle = this.makeElement("button", "action");
const buttonController = this.makeElement("button", "action");
const runnableTrack = this.makeElement("runnabletrack", "time")
const volume = this.makeElement("volume")
const timer = this.makeElement("timer")
const fullscreen = this.makeElement("fullscreen")
return {
buttonMiddle,
buttonController,
runnableTrack,
volume,
timer,
fullscreen
}
}
appendElements() {
document.head.append(this.blocks.styleSheet)
DOMappendChildren(this.blocks.parentNode, [this.blocks.blockControls, this.blocks.blockController, this.elements.fullscreen])
DOMappendChild(this.blocks.blockControls, this.elements.buttonMiddle)
if (navigator.platform == "iPhone") {
DOMappendChildren(this.blocks.blockController, [this.elements.buttonController, this.elements.runnableTrack, this.elements.timer])
} else {
DOMappendChildren(this.blocks.blockController, [this.elements.buttonController, this.elements.volume.volumeSection, this.elements.runnableTrack, this.elements.timer])
}
}
makeElement(type, modifier) {
if (type == "button") {
const element = DOMelementWithClass("button", "vidplayer-control")
if (modifier == "action") {
const elementObjectPlay = DOMelementWithClass("svg", ["vidplayer-control__icon", "vidplayer-control__icon--play"])
elementObjectPlay.setAttribute("viewBox", "0 0 175 200");
elementObjectPlay.dataset.action = "play";
const playPath = DOMelementWithClass("path")
playPath.setAttribute("d", "M0 0 L175 100 L0 200Z");
DOMappendChild(elementObjectPlay, playPath)
const elementObjectPause = DOMelementWithClass("svg", ["vidplayer-control__icon", "vidplayer-control__icon--pause"])
elementObjectPause.setAttribute("viewBox", "0 0 175 200");
elementObjectPause.dataset.action = "pause";
const pauseRectOne = DOMelementWithClass("rect");
pauseRectOne.setAttribute("height", "200");
pauseRectOne.setAttribute("width", "65");
const pauseRectTwo = DOMelementWithClass("rect");
pauseRectTwo.setAttribute("height", "200");
pauseRectTwo.setAttribute("width", "65");
pauseRectTwo.setAttribute("x", "110");
DOMappendChildren(elementObjectPause, [pauseRectOne, pauseRectTwo])
const elementObjectLoading = DOMelementWithClass("svg", ["vidplayer-control__icon", "vidplayer-control__icon--loading"])
elementObjectLoading.setAttribute("viewBox", "0 0 175 200");
const loadingCircle = DOMelementWithClass("circle")
loadingCircle.setAttribute("cx", "87.5");
loadingCircle.setAttribute("cy", "100");
loadingCircle.setAttribute("r", "75");
DOMappendChild(elementObjectLoading, loadingCircle);
DOMappendChildren(element, [elementObjectPlay, elementObjectPause, elementObjectLoading])
return element
} else if (modifier == "volume") {
element.classList.add("vidplayer-control--volume")
const elementObjectVolume = DOMelementWithClass("svg", ["vidplayer-control__icon", "vidplayer-control__icon--volume"])
elementObjectVolume.setAttribute("viewBox", "0 0 558 560");
const speakerBase = DOMelementWithClass("path", ["vidplayer-control__icon-path", "vidplayer-control__icon-path--base"]);
speakerBase.setAttribute("d", "M0 188.5V371.5H121.778L274 524V36L121.778 188.5H0Z");
const speakerPath = DOMelementWithClass("path", ["vidplayer-control__icon-path", "vidplayer-control__icon-path--low"]);
speakerPath.setAttribute("d", "M421 280C421 224.331 389.995 175.684 345 152V209.535C360.118 228.208 369.322 252.908 369.322 280C369.322 307.092 360.109 331.763 345 350.466V408C389.995 386.24 421 336.646 421 280Z");
const speakerPathBig = DOMelementWithClass("path", ["vidplayer-control__icon-path", "vidplayer-control__icon-path--high"]);
speakerPathBig.setAttribute("d", "M345 0V65.7736C432.936 93.2328 497.143 178.806 497.143 280C497.143 381.194 432.936 466.777 345 494.236V560C467.016 530.953 558 416.653 558 280C558 143.347 467.016 29.0572 345 0Z");
const speakerPathMute = DOMelementWithClass("path", ["vidplayer-control__icon-path", "vidplayer-control__icon-path--mute"]);
speakerPathMute.setAttribute("d", "M414.622 280.126L335.35 359.398L366.697 390.745L445.969 311.473L525.422 390.926L556.769 359.578L477.317 280.126L556.873 200.569L525.526 169.222L445.969 248.778L366.594 169.402L335.246 200.75L414.622 280.126Z");
DOMappendChildren(elementObjectVolume, [speakerBase, speakerPath, speakerPathBig, speakerPathMute])
DOMappendChild(element, elementObjectVolume)
return element
}
} else if (type == "runnabletrack") {
const runnableTrack = DOMelementWithClass("input", ["vidplayer-runnabletrack"])
// const runnableTrack_Track = DOMelementWithClass("input", ["vidplayer-runnabletrack__track", "vidplayer-runnabletrack__track--big"])
runnableTrack.setAttribute("type", "range");
runnableTrack.setAttribute("min", "0");
runnableTrack.setAttribute("max", "100");
if (modifier == "time") {
runnableTrack.classList.add("vidplayer-runnabletrack--time")
runnableTrack.setAttribute("value", 0);
runnableTrack.setAttribute("step", 0.000001);
}
if (modifier == "volume") {
runnableTrack.classList.add("vidplayer-runnabletrack--volume")
runnableTrack.setAttribute("value", 50)
}
// DOMappendChild(runnableTrack ,runnableTrack_Track);
return runnableTrack;
} else if (type == "volume") {
const volumeSection = DOMelementWithClass("section", "vidplayer-volume")
const volumeButton = this.makeElement("button", "volume")
volumeButton.dataset.volume = 2;
const volumeTrack = this.makeElement("runnabletrack", "volume")
DOMappendChildren(
volumeSection,
[
volumeButton,
volumeTrack
]
)
const returnedElement = {
volumeSection,
volumeButton,
volumeTrack
}
return returnedElement
} else if (type == "timer") {
const timer = DOMelementWithClass("p", "vidplayer-timer")
this.timeManager(timer, this.node.currentTime)
return timer
} else if (type == "stylesheet") {
const styleSheet = DOMelementWithClass("style", "vidplayer-stylesheet")
styleSheet.setAttribute("type", "text/css")
styleSheet.textContent = `/* This file is created for a vidplayer ${this.id} */`
return styleSheet
} else if (type == "fullscreen") {
const element = DOMelementWithClass("button", ["vidplayer-control", "vidplayer-control--fullscreen"])
const elementSvgFullScreenOpen = DOMelementWithClass("svg", ["vidplayer-control__icon", "vidplayer-control__icon--fs-open"])
elementSvgFullScreenOpen.setAttribute("viewBox", "0 0 219 164");
const elementPathFullScreenOpen = DOMelementWithClass("path")
elementPathFullScreenOpen.setAttribute("d", "M0 58V30V0H30H93V30H30V58H0ZM219 106V134V164H189H126V134H189V106H219ZM219 30V58H189V30H126V0H189H219V30ZM0 106V134V164H30H93V134H30V106H0Z");
DOMappendChild(elementSvgFullScreenOpen, elementPathFullScreenOpen)
const elementSvgFullScreenClose = DOMelementWithClass("svg", ["vidplayer-control__icon", "vidplayer-control__icon--fs-close"])
elementSvgFullScreenClose.setAttribute("viewBox", "0 0 219 164");
const elementPathFullScreenClose = DOMelementWithClass("path")
elementPathFullScreenClose.setAttribute("d", "M93 0V28V58H63H0V28H63V0H93ZM126 164V136V106H156H219V136H156V164H126ZM126 28V0H156V28H219V58H156H126V28ZM93 164V136V106H63H0V136H63V164H93Z");
DOMappendChild(elementSvgFullScreenClose, elementPathFullScreenClose)
DOMappendChildren(element, [elementSvgFullScreenOpen, elementSvgFullScreenClose])
return element;
}
}
timeManager(node, time) {
let currentTime = formatTime(time);
const endTime = formatTime(this.node.duration);
const countDown = currentTime + "/" + endTime;
node.textContent = countDown;
}
getDefaultStyling() {
var computedStyle = window.getComputedStyle(this.node);
var inlineStyles = this.node.style;
var styles = {};
for (var i = 0; i < computedStyle.length; i++) {
var property = computedStyle[i];
var value = computedStyle.getPropertyValue(property);
var isUserStyled = inlineStyles.getPropertyValue(property) !== "";
if (isUserStyled) {
var camelCasedProperty = property.replace(/([-_][a-z])/gi, function (group) {
return group.toUpperCase()
.replace('-', '')
.replace('_', '');
});
styles[camelCasedProperty] = value;
}
}
return styles;
}
videoAction(action, event) {
if (action == "play") {
if (event?.type == "ended") {
clearInterval(this.raceInterval)
this.blocks.parentNode.dataset.state = "paused"
} else if (this.node.paused) {
this.node.play()
this.blocks.parentNode.dataset.state = "playing"
this.raceInterval = setInterval(() => {
this.videoAction("timer")
this.videoAction("buffer")
}, 100)
} else if (!this.node.paused) {
this.node.pause()
clearInterval(this.raceInterval)
this.blocks.parentNode.dataset.state = "paused"
}
} else if (action == "loading") {
if (event?.type == "canplay") {
if (!this.node.paused) {
this.blocks.parentNode.dataset.state = "playing";
} else if (this.node.paused) {
this.blocks.parentNode.dataset.state = "paused";
}
} else if (event?.type == "waiting") {
this.blocks.parentNode.dataset.state = "loading";
} else if (event?.type == "stalled") {
this.blocks.parentNode.dataset.state = "loading";
}
} else if (action == "timer") {
let style;
let timePercentage;
if (event?.type == "input") {
if (!this.node.paused) {
this.videoAction("play")
}
timePercentage = intToProcent((this.node.duration * (this.elements.runnableTrack.value / 100)), this.node.duration)
const timeCount = (this.node.duration * (this.elements.runnableTrack.value / 100));
this.timeManager(this.elements.timer, timeCount)
} else if (event?.type == "change") {
if (this.node.paused) {
this.videoAction("play")
const timeCount = (this.node.duration * (this.elements.runnableTrack.value / 100));
timePercentage = intToProcent((this.node.duration * (this.elements.runnableTrack.value / 100)), this.node.duration);
this.node.currentTime = timeCount;
}
} else if (event?.type == "canplay") {
timePercentage = intToProcent((this.node.duration * (this.elements.runnableTrack.value / 100)), this.node.duration)
} else if (event?.type == "canplaythrough") {
timePercentage = intToProcent((this.node.duration * (this.elements.runnableTrack.value / 100)), this.node.duration)
} else if (!event) {
this.timeManager(this.elements.timer, this.node.currentTime);
timePercentage = intToProcent(this.node.currentTime, this.node.duration);
this.elements.runnableTrack.value = timePercentage;
}
// BETTER SOLUTION THAN CUSTOM STYLESHEET or INCLUDE ALSO THEME STYLING
style = `background:linear-gradient(to right, var(--hover-color) ${timePercentage}%, grey ${timePercentage}%, grey ${this.buffered}%, black ${this.buffered}%, black 100%) !important;`
let styleItem = `.vidplayer-container-${this.id} .vidplayer-runnabletrack--time::-webkit-slider-runnable-track{${style}}`
styleItem += `.vidplayer-container-${this.id} .vidplayer-runnabletrack--time::-moz-range-track{${style}}`
this.blocks.styleSheet.textContent = styleItem;
} else if (action == "volume") {
let value;
let trackValue = this.elements.volume.volumeTrack.value;
if (event.type == "input") {
value = trackValue
} else if (event.type == "click" || event.type == "keydown") {
if (trackValue != 0) {
value = 0
this.oldValue = trackValue;
this.elements.volume.volumeTrack.value = value
} else if (trackValue == 0) {
if (this.oldValue) {
value = this.oldValue
} else if (!this.oldValue) {
value = 50
} else if (this.oldValue == 0) {
value = 50
}
this.elements.volume.volumeTrack.value = value
this.oldValue = trackValue
}
}
this.node.volume = value / 100
const inputtedValue = value == 0 ? 0 : value <= 15 ? 1 : value <= 60 ? 2 : 3
if (inputtedValue == this.elements.volume.volumeButton.dataset.volume) {
return
}
this.elements.volume.volumeButton.dataset.volume = inputtedValue
} else if (action == "buffer") {
var buffered = this.node.buffered;
var duration = this.node.duration;
var bufferedTime = 0;
if (buffered.length > 0) {
bufferedTime = buffered.end(buffered.length - 1);
}
var percentBuffered = (bufferedTime / duration) * 100;
this.buffered = percentBuffered;
} else if (action == "fullscreen") {
let element = this.blocks.parentNode;
const request = () => {
element.classList.add("vidplayer-container--fullscreen")
Object.keys(this.defaultStyling).forEach((key) => {
this.node.style[key] = "unset"
})
if (element.requestFullscreen) {
return element.requestFullscreen()
}
if (element.mozRequestFullscreen) {
return element.mozRequestFullscreen()
} else if (element.msRequestFullscreen) {
return element.msRequestFullscreen()
}
else if (this.node.webkitEnterFullscreen) {
this.node.webkitEnterFullscreen()
}
}
const cancel = () => {
Object.keys(this.defaultStyling).forEach((key) => {
this.node.style[key] = this.defaultStyling[key]
})
element.classList.remove("vidplayer-container--fullscreen")
if(isFullScreen()){
if(document.exitFullscreen) {
document.exitFullscreen();
} else if(document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if(document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
}
}
const isFullScreen = () => {
return document.fullscreenElement || document.webkitIsFullScreen || document.msIsFullScreen || document.mozIsFullScreen;
}
const toggle = () => {
if (isFullScreen() || event == "close") {
return cancel()
} else {
return request()
};
}
toggle()
}
}
render() {
this.init()
this.bindEvents()
}
}
if (Videos) {
[...Videos].forEach((node, index) => new vidplayerJS(node, index))
}
function DOMelement(element) {
if (element == "path" || element == "svg" || element == "rect" || element == "polygon" || element == "circle") {
return document.createElementNS('http://www.w3.org/2000/svg', element);
}
return document.createElement(element);
}
function DOMaddClass(element, className) {
const newElement = DOMelement(element)
newElement.classList.add(className)
return newElement
}
function DOMaddClasses(element, classNames) {
const newElement = DOMelement(element)
classNames.map(className => (
newElement.classList.add(className)
))
return newElement
}
function getVideoSettings(element) {
if (element.dataset.settings) {
const parsedData = JSON.parse(
element?.dataset?.settings?.replaceAll(`'`, `"`) ?
element?.dataset?.settings?.replaceAll(`'`, `"`) :
"[]"
);
return parsedData
}
}
function assignParent(parentElement, classNames, element, theme) {
const container = DOMelementWithClass(parentElement, classNames); // container of the vidplayer -> used for css purpose.
element.parentNode.insertBefore(container, element)
container.appendChild(element)
if (theme) {
container.classList.add(`vidplayer_theme--${theme}`)
}
return container;
}
/**
* @param {String} element
* @param {Array || String || Object} classNames
*/
function DOMelementWithClass(element, classNames) {
if (classNames) {
if (typeof classNames === "string") {
return DOMaddClass(element, classNames)
} else if (Array.isArray(classNames)) {
return DOMaddClasses(element, classNames)
} else if (typeof classNames === "object") {
errorMes("Objects are not supported in this function")
return element
} else {
errorMes("Unknown type of variable, should be an Array or an String")
}
} else {
return DOMelement(element)
}
}
function DOMappendChild(parent, child) {
return parent.appendChild(child)
}
function DOMappendChildren(element, childrenArray) {
childrenArray.map(child => (
element.appendChild(child)
))
return element;
}
function DOMquerySelector(element) {
return document.querySelector(`${element}`)
}
function formatTime(timestamp) {
timestamp = ~~(timestamp)
let hours = ~~(timestamp / 3600);
timestamp = ~~(timestamp % 3600);
let minutes = ~~(timestamp / 60);
let seconds = ~~(timestamp % 60);
if (hours > 0) {
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
} else {
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
}
function intToProcent(amount, total) {
const fullTime = total;
const percentage = (((amount - fullTime) / fullTime) + 1) * 100;
return percentage
}
function warningMes(message) {
console.warn("%c" + `${message}`, "color: yellow; font-weight: bold;");
}
function errorMes(message) {
console.error("%c" + `${message}`, "color: #FF3333; font-weight: bold;");
};
function colorScheme (color) {
var colorValue = color
const regexTEXT = /[a-z, A-Z]/;
if (regexTEXT.test(color)) {
if (!color.startsWith("rgb")) {
colorValue = getCSSColorValue(color);
}
}
if (colorValue.startsWith("rgb")) {
var rgba = colorValue.match(/[\d.]+/g);
var r = rgba[0],
g = rgba[1],
b = rgba[2],
a = rgba[3] || 1;
r = (r * 0.8) | 0;
g = (g * 0.8) | 0;
b = (b * 0.8) | 0;
return `rgba(${r},${g},${b},${a})`;
} else if (colorValue.startsWith("#")) {
const secValue = colorValue.substring(1); // remove the "#" symbol
var r = parseInt(secValue.substring(0,2), 16);
var g = parseInt(secValue.substring(2,4), 16);
var b = parseInt(secValue.substring(4,6), 16);
r = (r * 0.8) | 0;
g = (g * 0.8) | 0;
b = (b * 0.8) | 0;
newColor = "#" + (r.toString(16).padStart(2, "0") + g.toString(16).padStart(2, "0") + b.toString(16).padStart(2, "0"));
return newColor;
} else {
warningMes("The colors you use must be RGB, RGBA, HEX, or a CSS color name");
}
}
function getCSSColorValue(colorName) {
var element = document.createElement("div");
element.style.display = "none";
element.style.color = colorName;
document.body.appendChild(element);
var computedColor = window.getComputedStyle(element).color;
document.body.removeChild(element);
return computedColor;
}