/**
* Javascript tools which belongs to the HTML5 video category.
*/
export { HTML5Video }
/**
* Define methods for the HTML5 video player.
* @author ckraemme <ckraemme@awi.de>
*/
class HTML5Video {
/**
* Set a poster attribute in the HTML5 video element.
* @param {string/HTMLElement} target - HTML element.
* @param {string} path - Path to the poster image.
* @param {boolean} showPosterAfterVideoLoaded - Show poster after video has loaded completely, else show during loading.
* @param {boolean} removePosterAfterVideoLoaded - Indicates if poster should be removed from the video when it has loaded.
*/
static poster(target, path, showPosterAfterVideoLoaded = true, removePosterAfterVideoLoaded = false) {
const video = target.querySelector('video');
if (showPosterAfterVideoLoaded) {
video.addEventListener('loadeddata', function () {
this.setAttribute('poster', path)
}, false);
}
else
video.setAttribute('poster', path)
if (removePosterAfterVideoLoaded) {
video.addEventListener('loadeddata', function () {
this.removeAttribute('poster')
}, false);
}
}
/**
* Disable the picture-in-picture mode of HTML5 videos (default: enabled). Does not work in Firefox and Samsung Internet in 2022.
* @param {string/HTMLElement} target - HTML element.
*/
static disablePIP(target) {
const video = target.querySelector('video');
video.disablePictureInPicture = true;
}
/**
* Set the audio volume to 50%.
* @param {string/HTMLElement} target - HTML element.
*/
static initAudio(target) {
const video = target.querySelector('video');
video.addEventListener('canplaythrough', (event) => {
video.volume = 0.5;
});
}
/**
* Returns true if a HTML5 video tag contains audio.
* @param {string/HTMLElement} target - HTML element.
*/
static hasAudio(target) {
const video = target.querySelector('video');
return video.mozHasAudio ||
Boolean(video.webkitAudioDecodedByteCount) ||
Boolean(video.audioTracks && video.audioTracks.length);
}
/**
* Set an key eventlistener to the body.
* @param {string/HTMLElement} target - HTML element containing video element.
* @param {Boolean} initPlayerWithKeyFocus - Focus video element when key control is initialized, so key commands are sent directly to the video without having to click on it.
* @param {Array} playbackRates - List of playback rates for key control.
*/
static initKeyControl(target, initPlayerWithKeyFocus = false, playbackRates = [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0]) {
// Only the focused element receives the keydown event. Hence set the video element as focused whenever user clicks on the video and reset focus when user clicks next to video element.
document.body.addEventListener("click", (event) => {
const video = target.querySelector('video');
if (!target.contains(event.target) && document.activeElement == video) {
document.activeElement.blur();
}
});
const focus = () => {
const video = target.querySelector('video');
video.style.outline = "none";
video.focus();
}
target.addEventListener("click", (event) => {
focus();
}, true);
target.addEventListener("keydown", (event) => {
HTML5Video.keyControl_(target, event, playbackRates)
})
if (initPlayerWithKeyFocus)
focus();
}
/**
* Main method for handling the playback of HTML5 videos with keys.
* @private
* @param {string/HTMLElement} target - HTML element.
* @param {event} event - Event that triggered this function call.
* @param {Array} playbackRates - List of playback rates for key control.
*/
static keyControl_(target, event, playbackRates) {
const video = target.querySelector('video');
let isFullscreen = () => !!document.fullscreenElement
let audioVolume = video.volume;
const nearestIndexOfValueInArray = (target, list) => {
const nearestValue = list.reduce((prev, curr) => Math.abs(curr - target) < Math.abs(prev - target) ? curr : prev);
return list.indexOf(nearestValue)
}
if (event.shiftKey) {
switch (event.key) {
case ';':
event.preventDefault();
const previousIndex = Math.max(nearestIndexOfValueInArray(video.playbackRate, playbackRates) - 1, 0);
video.playbackRate = playbackRates[previousIndex];
break;
case ':':
event.preventDefault();
const nextIndex = Math.min(nearestIndexOfValueInArray(video.playbackRate, playbackRates) + 1, playbackRates.length - 1);
video.playbackRate = playbackRates[nextIndex];
break;
}
return true
}
switch (event.key) {
case 'f':
if (isFullscreen()) {
document.exitFullscreen();
} else {
var fullscreenElement = target.querySelector('.video-js');
if (!fullscreenElement) {
fullscreenElement = target;
}
fullscreenElement.requestFullscreen();
}
event.preventDefault();
break;
case ' ':
case 'Enter':
event.preventDefault();
if (video.paused) {
video.play();
} else {
video.pause();
}
break;
case 'ArrowUp':
event.preventDefault();
if (audioVolume != 1) {
try {
video.volume = audioVolume + 0.05;
} catch (err) {
video.volume = 1;
}
}
break;
case 'ArrowDown':
event.preventDefault();
if (audioVolume != 0) {
try {
video.volume = audioVolume - 0.05;
} catch (err) {
video.volume = 0;
}
}
break;
case 'ArrowLeft':
video.currentTime -= 10;
event.preventDefault();
break;
case 'ArrowRight':
video.currentTime += 10;
event.preventDefault();
break;
case '.':
video.currentTime += 0.05;
event.preventDefault();
break;
case ',':
video.currentTime -= 0.05;
event.preventDefault();
break;
case 'm':
if (video.muted) {
video.muted = false;
} else {
video.muted = true;
}
event.preventDefault();
break;
}
}
}