Video Player
export const videoPlayer = ()=>{
var document:any = window.document;
// Does the browser actually support the video element?
var supportsVideo = !!document.createElement('video').canPlayType;
if (supportsVideo) {
// Obtain handles to main elements
var videoContainer = document.getElementById('videoContainer');
var video = document.getElementById('video');
var videoControls = document.getElementById('video-controls');
videoContainer.oncontextmenu=function(){return false}
// Hide the default controls
video.controls = false;
// Display the user defined video controls
videoControls.style.display = 'block';
// Obtain handles to buttons and other elements
var playpause:any = document.getElementById('playpause');
var stop:any = document.getElementById('stop');
var mute:any = document.getElementById('mute');
var volinc:any = document.getElementById('volinc');
var voldec:any = document.getElementById('voldec');
var progress:any = document.getElementById('progress');
var progressBar:any = document.getElementById('progress-bar');
var fullscreen:any = document.getElementById('fs');
// Check if the browser supports the Fullscreen API
var fullScreenEnabled = !!(document.fullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled || document.webkitSupportsFullscreen || document.webkitFullscreenEnabled || document.createElement('video').webkitRequestFullScreen);
// If the browser doesn't support the Fulscreen API then hide the fullscreen button
if (!fullScreenEnabled) {
fullscreen.style.display = 'none';
playpause.style.textIndent = "1px"
playpause.style.color = "white"
}
var alterVolume = function(dir:any) {
var currentVolume = Math.floor(video.volume * 10) / 10;
if (dir === '+') {
if (currentVolume < 1) video.volume += 0.1;
}
else if (dir === '-') {
if (currentVolume > 0) video.volume -= 0.1;
}
}
// Set the video container's fullscreen state
var setFullscreenData = function(state:any) {
videoContainer.setAttribute('data-fullscreen', !!state);
}
// Checks if the document is currently in fullscreen mode
var isFullScreen = function() {
return !!(document.fullScreen || document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement || document.fullscreenElement);
}
// Fullscreen
var handleFullscreen = function() {
// If fullscreen mode is active...
if (isFullScreen()) {
// ...exit fullscreen mode
// (Note: this can only be called on document)
if (document.exitFullscreen) document.exitFullscreen();
else if (document.mozCancelFullScreen) document.mozCancelFullScreen();
else if (document.webkitCancelFullScreen) document.webkitCancelFullScreen();
else if (document.msExitFullscreen) document.msExitFullscreen();
setFullscreenData(false);
}
else {
// ...otherwise enter fullscreen mode
// (Note: can be called on document, but here the specific element is used as it will also ensure that the element's children, e.g. the custom controls, go fullscreen also)
if (videoContainer.requestFullscreen) videoContainer.requestFullscreen();
else if (videoContainer.mozRequestFullScreen) videoContainer.mozRequestFullScreen();
else if (videoContainer.webkitRequestFullScreen) {
// Safari 5.1 only allows proper fullscreen on the video element. This also works fine on other WebKit browsers as the following CSS (set in styles.css) hides the default controls that appear again, and
// ensures that our custom controls are visible:
// figure[data-fullscreen=true] video::-webkit-media-controls { display:none !important; }
// figure[data-fullscreen=true] .controls { z-index:2147483647; }
video.webkitRequestFullScreen();
}
else if (videoContainer.msRequestFullscreen) videoContainer.msRequestFullscreen();
setFullscreenData(true);
}
}
// Only add the events if addEventListener is supported (IE8 and less don't support it, but that will use Flash anyway)
if (document.addEventListener) {
// Wait for the video's meta data to be loaded, then set the progress bar's max value to the duration of the video
video.addEventListener('loadedmetadata', function() {
progress.setAttribute('max', video.duration);
});
// Add events for all buttons
playpause.addEventListener('click', function(e:any) {
if (video.paused || video.ended) video.play();
else video.pause();
});
// The Media API has no 'stop()' function, so pause the video and reset its time and the progress bar
stop.addEventListener('click', function(e:any) {
video.pause();
video.currentTime = 0;
progress.value = 0;
});
mute.addEventListener('click', function(e:any) {
video.muted = !video.muted;
});
volinc.addEventListener('click', function(e:any) {
alterVolume('+');
});
voldec.addEventListener('click', function(e:any) {
alterVolume('-');
});
fullscreen.addEventListener('click', function(e:any) {
handleFullscreen();
});
// As the video is playing, update the progress bar
video.addEventListener('timeupdate', function() {
// For mobile browsers, ensure that the progress element's max attribute is set
if (!progress.getAttribute('max')) progress.setAttribute('max', video.duration);
progress.value = video.currentTime;
progressBar.style.width = Math.floor((video.currentTime / video.duration) * 100) + '%';
});
// React to the user clicking within the progress bar
progress.addEventListener('click', function(e:any) {
try{
var rect = progress.getBoundingClientRect();
var pos = (e.pageX - rect.left) / rect.width;
video.currentTime = pos * video.duration;
}
catch(e){
console.log(e)
}
});
// Listen for fullscreen change events (from other controls, e.g. right clicking on the video itself)
document.addEventListener('fullscreenchange', function(e:any) {
setFullscreenData(!!(document.fullScreen || document.fullscreenElement));
});
document.addEventListener('webkitfullscreenchange', function() {
setFullscreenData(!!document.webkitIsFullScreen);
});
document.addEventListener('mozfullscreenchange', function() {
setFullscreenData(!!document.mozFullScreen);
});
document.addEventListener('msfullscreenchange', function() {
setFullscreenData(!!document.msFullscreenElement);
});
}
}
}
vid-marker
import videojs, { VideoJsPlayer } from 'video.js';
const Plugin = videojs.getPlugin('plugin');
// default setting
const defaultSetting: VideoJsMarkerPluginSettings = {
markerStyle: {
width: '7px',
'border-radius': '30%',
'background-color': 'red'
},
markerTip: {
display: true,
text(marker) {
return '' + marker.text;
},
time(marker) {
return marker.time;
}
},
breakOverlay: {
display: false,
displayTime: 3,
text(marker) {
return 'Break overlay: ' + marker.overlayText;
},
style: {
width: '100%',
height: '20%',
'background-color': 'rgba(0,0,0,0.7)',
color: 'white',
'font-size': '17px'
}
},
onMarkerClick(marker) {},
onMarkerReached(marker, index) {},
markers: []
};
// create a non-colliding random number
function generateUUID(): string {
let d = new Date().getTime();
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
// tslint:disable-next-line:no-bitwise
const r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
// tslint:disable-next-line:no-bitwise
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
});
return uuid;
}
function getElementBounding(
element: HTMLElement
): {
top: number;
bottom: number;
left: number;
right: number;
width: number;
height: number;
} {
let elementBounding;
const defaultBoundingRect = {
top: 0,
bottom: 0,
left: 0,
width: 0,
height: 0,
right: 0
};
try {
elementBounding = element.getBoundingClientRect();
} catch (e) {
elementBounding = defaultBoundingRect;
}
return elementBounding;
}
const millToTimeCode = (seconds:any, TimeFormat:TimeFormatTypes)=>{
//alert(milliseconds);
var h:any = Math.floor(seconds / 3600);
seconds = seconds - h * 3600;
var m:any = Math.floor(seconds / 60);
seconds = seconds - m * 60;
var s:any = Math.floor(seconds);
seconds = seconds - s;
let f:any = Math.floor((seconds * 1000) / 40);
if (TimeFormat === 'PAL') {
f = Math.floor((seconds * 1000) / 40);
}
else if (TimeFormat === 'NTSC') {
f = Math.floor((seconds * 30000) / 1001);
}
else if (TimeFormat === 'PALp') {
f = Math.floor((seconds * 1000) / 20);
}
else if (TimeFormat === 'NTSCp') {
f = Math.floor((seconds * 60000) / 1001);
}
else if (TimeFormat === 'STANDARD') {
f = Math.floor(seconds * 1000);
}
else {
// assume frame rate is given in numeric form
f = Math.floor(seconds * TimeFormat);
}
// Check if we need to show hours
h = (h < 10) ? ("0" + h) + ":" : h + ":";
// If hours are showing, we may need to add a leading zero.
// Always show at least one digit of minutes.
m = (((h) && m < 10) ? "0" + m : m) + ":";
// Check if leading zero is need for seconds
s = ((s < 10) ? "0" + s : s) + ":";
f = (f < 10) ? "0" + f : f;
if (TimeFormat === 'STANDARD')
f = (f < 100) ? "0" + f : f;
return h + m + s + f;
}
const NULL_INDEX = -1;
export class VideoJsMarkerPlugin extends Plugin {
private setting: any;
private markersMap: { [key: string]: VideoJsMarker } = {};
private markersList: Array = [];
private breakOverlay: any = null;
private markerTip: any = null;
private currentMarkerIndex = NULL_INDEX;
private overlayIndex = NULL_INDEX;
constructor(player: VideoJsPlayer, options?: VideoJsMarkerPluginSettings) {
super(player);
if (options) {
this.setting = videojs.mergeOptions(defaultSetting, options);
}
this.player.on('loadedmetadata', () => {
this.initialize();
});
}
initialize(): void {
if (this.setting.markerTip.display) {
this.initializeMarkerTip();
}
// remove existing markers if already initialized
this.removeAll();
this.addMarkers(this.setting.markers);
if (this.setting.breakOverlay.display) {
this.initializeOverlay();
}
const startOfGameMarkers: VideoJsMarker[] = this.setting.markers.filter((marker:any) => marker.text === 'start' && !marker.disabled);
if (startOfGameMarkers.length) {
this.player.currentTime(startOfGameMarkers[0].time);
}
this.onTimeUpdate();
this.player.on('timeupdate', () => this.onTimeUpdate());
this.player.off('loadedmetadata');
}
getMarkers(): Array {
return this.markersList;
}
setTimeCode():void{
const el:any = this.player.el()
const timeEl:any = el.querySelector('.vjs-current-time-display')
const durationEl:any = el.querySelector('.vjs-duration-display');
timeEl.text = millToTimeCode(this.player.currentTime(), this.player.options_.timeFormat);
durationEl.text= millToTimeCode(this.player.duration(), this.player.options_.timeFormat);
}
getTimeCode():TimeCodeType{
return {
playerTime:this.player.currentTime(),
currentTime:millToTimeCode(this.player.currentTime(), this.player.options_.timeFormat),
duration: millToTimeCode(this.player.duration(), this.player.options_.timeFormat)
}
}
onTimeUpdate(): void {
this.onUpdateMarker();
this.updateBreakOverlay();
// this.setTimeCode();
if (this.setting.onTimeUpdateAfterMarkerUpdate) {
this.setting.onTimeUpdateAfterMarkerUpdate();
}
}
onUpdateMarker() {
/*
check marker reached in between markers
the logic here is that it triggers a new marker reached event only if the player
enters a new marker range (e.g. from marker 1 to marker 2).
Thus, if player is on marker 1 and user clicked on marker 1 again, no new reached event is triggered)
*/
if (!this.markersList.length) {
return;
}
this.updateMarkers(true);
const getNextMarkerTime: (index: number) => number = (index: number) => {
if (index < this.markersList.length - 1) {
return this.setting.markerTip.time(this.markersList[index + 1]);
}
// next marker time of last marker would be end of video time
return this.player.duration();
};
const currentTime = this.player.currentTime();
let newMarkerIndex = NULL_INDEX;
if (this.currentMarkerIndex !== NULL_INDEX) {
// check if staying at same marker
const nextMarkerTime = getNextMarkerTime(this.currentMarkerIndex);
if (
currentTime >=
this.setting.markerTip.time(
this.markersList[this.currentMarkerIndex]
) &&
currentTime < nextMarkerTime
) {
return;
}
// check for ending (at the end current time equals player duration)
if (
this.currentMarkerIndex === this.markersList.length - 1 &&
currentTime === this.player.duration()
) {
return;
}
}
// check first marker, no marker is selected
if (currentTime < this.setting.markerTip.time(this.markersList[0])) {
newMarkerIndex = NULL_INDEX;
} else {
// look for new index
let nextMarkerTime;
for (let i = 0; i < this.markersList.length; i++) {
nextMarkerTime = getNextMarkerTime(i);
if (
currentTime >= this.setting.markerTip.time(this.markersList[i]) &&
currentTime < nextMarkerTime
) {
newMarkerIndex = i;
break;
}
}
}
// set new marker index
if (newMarkerIndex !== this.currentMarkerIndex) {
// trigger event if index is not null
if (newMarkerIndex !== NULL_INDEX && this.setting.onMarkerReached) {
this.setting.onMarkerReached(
this.markersList[newMarkerIndex],
newMarkerIndex
);
}
this.currentMarkerIndex = newMarkerIndex;
}
}
initializeOverlay(): void {
this.breakOverlay = videojs.dom.createEl('div', {
className: 'vjs-break-overlay',
innerHTML: ``
}) as HTMLElement;
Object.keys(this.setting.breakOverlay.style).forEach(key => {
if (this.breakOverlay) {
(this.breakOverlay.style as any)[key] = (this.setting.breakOverlay
.style as any)[key];
}
});
this.player.el().appendChild(this.breakOverlay);
this.overlayIndex = NULL_INDEX;
}
updateBreakOverlay(): void {
if (!this.setting.breakOverlay.display || this.currentMarkerIndex < 0) {
return;
}
const currentTime = this.player.currentTime();
const marker = this.markersList[this.currentMarkerIndex];
const markerTime = this.setting.markerTip.time(marker);
if (
currentTime >= markerTime &&
currentTime <= markerTime + this.setting.breakOverlay.displayTime
) {
if (this.overlayIndex !== this.currentMarkerIndex) {
this.overlayIndex = this.currentMarkerIndex;
if (this.breakOverlay) {
this.breakOverlay.querySelector(
'.vjs-break-overlay-text'
).innerHTML = this.setting.breakOverlay.text(marker);
}
}
if (this.breakOverlay) {
this.breakOverlay.style.visibility = 'visible';
}
} else {
this.overlayIndex = NULL_INDEX;
if (this.breakOverlay) {
this.breakOverlay.style.visibility = 'hidden';
}
}
}
setMarkderDivStyle(marker: VideoJsMarker, markerDiv: HTMLElement): void {
markerDiv.className = `vjs-marker ${marker.class || ''}`;
Object.keys(this.setting.markerStyle).forEach(key => {
(markerDiv.style as any)[key] = (this.setting.markerStyle as any)[key];
});
// hide out-of-bound markers
const ratio = marker.time / this.player.duration();
if (ratio < 0 || ratio > 1) {
markerDiv.style.display = 'none';
}
// set position
markerDiv.style.left = this.getPosition(marker) + '%';
if (marker.duration) {
markerDiv.style.width =
(marker.duration / this.player.duration()) * 100 + '%';
markerDiv.style.marginLeft = '0px';
} else {
const markerDivBounding = getElementBounding(markerDiv);
markerDiv.style.marginLeft = -markerDivBounding.width / 2 + 'px';
}
}
updateMarkers(force: boolean): void {
// update UI for markers whose time changed
this.markersList.forEach((marker: VideoJsMarker) => {
const markerDiv:any = this.player
.el()
.querySelector(
`.vjs-marker[data-marker-key='${marker.key}']`
) as HTMLElement;
const markerTime = this.setting.markerTip.time(marker);
if (
force ||
parseFloat(markerDiv.getAttribute('data-marker-time')) !== markerTime
) {
this.setMarkderDivStyle(marker, markerDiv);
markerDiv.setAttribute('data-marker-time', String(markerTime));
}
});
this.sortMarkersList();
}
registerMarkerTipHandler(markerDiv: any): void {
markerDiv.addEventListener('mouseover', () => {
const marker = this.markersMap[markerDiv.getAttribute('data-marker-key')];
if (!!this.markerTip) {
if (this.setting.markerTip.html) {
this.markerTip.querySelector(
'.vjs-tip-inner'
).innerHTML = this.setting.markerTip.html(marker);
} else {
(this.markerTip.querySelector(
'.vjs-tip-inner'
) as HTMLElement).innerHTML = this.setting.markerTip.text(marker);
}
// margin-left needs to minus the padding length to align correctly with the marker
this.markerTip.style.left = this.getPosition(marker) + '%';
const markerTipBounding = getElementBounding(this.markerTip);
const markerDivBounding = getElementBounding(markerDiv);
this.markerTip.style.marginLeft =
-(markerTipBounding.width / 2) + markerDivBounding.width / 4 + 'px';
this.markerTip.style.visibility = 'visible';
}
});
markerDiv.addEventListener('mouseout', () => {
if (!!this.markerTip) {
this.markerTip.style.visibility = 'hidden';
}
});
}
removeMarkers(indexArray: Array): void {
// reset overlay
if (!!this.breakOverlay) {
this.overlayIndex = NULL_INDEX;
this.breakOverlay.style.visibility = 'hidden';
}
this.currentMarkerIndex = NULL_INDEX;
const deleteIndexList: Array = [];
indexArray.forEach((index: number) => {
const marker:any = this.markersList[index];
if (marker) {
// delete from memory
delete this.markersMap[marker.key];
deleteIndexList.push(index);
// delete from dom
const el:any = this.player
.el()
.querySelector(`.vjs-marker[data-marker-key='${marker.key}']`);
if (el) {
el.parentNode.removeChild(el);
}
}
});
// clean up markers array
deleteIndexList.reverse();
deleteIndexList.forEach((deleteIndex: number) => {
this.markersList.splice(deleteIndex, 1);
});
// sort again
this.sortMarkersList();
}
next(): void {
// go to the next marker from current timestamp
const currentTime = this.player.currentTime();
for (const marker of this.markersList) {
const markerTime = this.setting.markerTip.time(marker);
if (markerTime > currentTime) {
this.player.currentTime(markerTime);
break;
}
}
}
prev(): void {
// go to previous marker
const currentTime = this.player.currentTime();
for (let i = this.markersList.length - 1; i >= 0; i--) {
const markerTime = this.setting.markerTip.time(this.markersList[i]);
// add a threshold
if (markerTime + 0.5 < currentTime) {
this.player.currentTime(markerTime);
return;
}
}
}
add(newMarkers: Array): void {
// add new markers given an array of index
this.addMarkers(newMarkers);
}
addMarkers(newMarkers: Array): void {
newMarkers.forEach((marker: VideoJsMarker) => {
try {
marker.key = generateUUID();
if(this.player!== null){
const el:any = this.player.el()
el.querySelector('.vjs-progress-holder').appendChild(this.createMarkerDiv(marker));
// store marker in an internal hash map
this.markersMap[marker.key] = marker;
this.markersList.push(marker);
}
} catch (e:any) {
console.error("marker not working:"+e.message);
}
});
this.sortMarkersList();
}
sortMarkersList(): void {
// sort the list by time in asc order
this.markersList.sort((a, b) => {
return this.setting.markerTip.time(a) - this.setting.markerTip.time(b);
});
}
createMarkerDiv(marker: VideoJsMarker): HTMLElement {
const markerDiv = videojs.dom.createEl(
'div',
{},
{
'data-marker-key': marker.key,
'data-marker-time': this.setting.markerTip.time(marker)
}
) as HTMLElement;
this.setMarkderDivStyle(marker, markerDiv);
// bind click event to seek to marker time
markerDiv.addEventListener('click', (e: Event) => {
let preventDefault = false;
if (typeof this.setting.onMarkerClick === 'function') {
// if return false, prevent default behavior
preventDefault = this.setting.onMarkerClick(marker) === false;
}
if (!preventDefault) {
const key:any = (e.target as HTMLElement).getAttribute('data-marker-key');
this.player.currentTime(
this.setting.markerTip.time(this.markersMap[key])
);
}
});
if (this.setting.markerTip.display) {
this.registerMarkerTipHandler(markerDiv);
}
return markerDiv;
}
getPosition(marker: VideoJsMarker): number {
const liveTracker = this.player.liveTracker;
const windowOrDuration = liveTracker.isLive()
? liveTracker.liveWindow()
: this.player.duration();
return (this.setting.markerTip.time(marker) / windowOrDuration) * 100;
}
remove(indexArray: Array): void {
// remove markers given an array of index
this.removeMarkers(indexArray);
}
removeAll(): void {
const indexArray = [];
for (let i = 0; i < this.markersList.length; i++) {
indexArray.push(i);
}
if(indexArray.length && this.player !== null) this.removeMarkers(indexArray);
}
// force - force all markers to be updated, regardless of if they have changed or not.
updateTime(force: boolean): void {
// notify the plugin to update the UI for changes in marker times
this.updateMarkers(force);
}
reset(newMarkers: Array): void {
// remove all the existing markers and add new ones
this.removeAll();
this.addMarkers(newMarkers);
}
destroy(): void {
// unregister the plugins and clean up even handlers
this.removeAll();
if (this.breakOverlay) {
this.breakOverlay.remove();
}
if (this.markerTip) {
this.markerTip.remove();
}
this.player.off('timeupdate', this.updateBreakOverlay);
// const {markers,...rest} = this.player;
// delete this.player.markers;
}
initializeMarkerTip(): void {
this.markerTip = videojs.dom.createEl('div', {
className: 'vjs-tip',
innerHTML: ``
}) as HTMLElement;
const el:any = this.player.el();
el.querySelector('.vjs-progress-holder')
.appendChild(this.markerTip);
}
}
videojs.registerPlugin('markers', VideoJsMarkerPlugin);
type TimeFormatTypes = 'PAL'| 'PALp' | 'NTSC' | 'NTSCp' | 'STANDARD';
declare module 'video.js' {
export interface VideoJsPlayer {
markers: (options?: VideoJsMarkerPluginSettings) => VideoJsMarkerPlugin;
}
export interface VideoJsPlayerOptions {
timeFormat:TimeFormatTypes,
markers?: boolean;
}
}
export interface VideoJsMarker {
time: number;
duration?: number;
text?: string;
class?: string;
overlayText?: string;
key?: string;
id?: string;
disabled?: boolean;
}
export interface VideoJsMarkerPluginSettings {
markerStyle?: object;
markerTip?: {
display?: boolean;
html?(marker: VideoJsMarker): string;
text?(marker: VideoJsMarker): string;
time?(marker: VideoJsMarker): number;
};
breakOverlay?: {
display: boolean;
displayTime: number;
style: object;
text(marker: VideoJsMarker): string;
};
markers?: VideoJsMarker[];
onMarkerClick?(marker: VideoJsMarker): boolean | void;
onMarkerReached?(marker: VideoJsMarker, index: number): void;
onTimeUpdateAfterMarkerUpdate?(): void;
}
0 Comments