cloudinary-video-player
Version:
Cloudinary Video Player
212 lines (171 loc) • 5.89 kB
JavaScript
import EventEmitter from 'events';
import { isPlainObject } from 'utils/type-inference';
import videojs from 'video.js';
import assign from 'utils/assign';
const EVENT_DEFAULTS = {
percentsplayed: {
percents: [25, 50, 75, 100]
}
};
const DEFAULT_EVENTS = [
'percentsplayed',
'pausenoseek',
'seek',
'mute',
'unmute',
'qualitychanged'
];
const DEFAULT_OPTIONS = {
events: DEFAULT_EVENTS
};
// Emits the following additional events:
// percentsplayed, timeplayed, pausenoseek, seek, mute, unmute
class ExtendedEvents extends EventEmitter {
constructor(player, options = {}) {
super();
this.player = player;
options = videojs.mergeOptions(DEFAULT_OPTIONS, options);
let _muteData = { lastState: undefined };
let _seekStart = 0;
let _seekEnd = 0;
let _seeking = false;
let _percentsTracked = [];
let _timesTracked = [];
let _currentSource = null;
const volumechange = (event) => {
if (this.player.muted() && _muteData.lastState !== 'muted') {
_muteData.lastState = 'muted';
this.emit('mute', event);
} else if (!this.player.muted() && _muteData.lastState !== 'unmuted') {
_muteData.lastState = 'unmuted';
this.emit('unmute', event);
}
};
const timeupdate = (event) => {
const currentTime = this.player.currentTime();
const duration = this.player.duration();
const _emit = (type, data) => {
data.originalType = 'timeupdate';
this.emit(type, event, data);
};
if (this.events.percentsplayed) {
this.events.percentsplayed.percents.forEach((percent) => {
if (playedAtPercentage(currentTime, duration, percent) && _percentsTracked.indexOf(percent) === -1) {
_percentsTracked.push(percent);
_emit('percentsplayed', { percent });
}
});
}
if (this.events.timeplayed) {
const timeplayed = this.events.timeplayed;
const times = timeplayed.interval ? [Math.floor(currentTime / timeplayed.interval) * timeplayed.interval] : timeplayed.times;
times.forEach((time) => {
if (playedAtTime(currentTime, time) && _timesTracked.indexOf(time) === -1) {
_timesTracked.push(time);
_emit('timeplayed', { time });
}
});
}
if (this.events.seek) {
_seekStart = _seekEnd;
_seekEnd = currentTime;
if (Math.abs(_seekStart - _seekEnd) > 1) {
_seeking = true;
_emit('seek', { seekStart: _seekStart, seekEnd: _seekEnd });
}
}
};
const pause = (event) => {
const currentTime = Math.round(this.player.currentTime());
const duration = Math.round(this.player.duration());
if (currentTime !== duration && !_seeking) {
this.emit('pausenoseek', event);
}
};
const play = () => {
_seeking = false;
};
const loadedmetadata = () => {
if (this.player.currentSource().src !== _currentSource) {
resetPerVideoState();
_currentSource = this.player.currentSource().src;
}
};
const adaptiveEvents = (event) => {
let ee = this;
let tracks = this.player.textTracks();
let segmentMetadataTrack = null;
for (let i = 0; i < tracks.length; i++) {
if (tracks[i].label === 'segment-metadata') {
segmentMetadataTrack = tracks[i];
}
}
let previousResolution = null;
if (segmentMetadataTrack) {
segmentMetadataTrack.on('cuechange', function() {
let activeCue = segmentMetadataTrack.activeCues[0];
if (activeCue) {
let currentRes = activeCue.value.resolution;
if (previousResolution !== currentRes) {
let data = {
from: previousResolution,
to: currentRes
};
ee.emit('qualitychanged', event, data);
}
previousResolution = currentRes;
}
});
}
};
const resetState = () => {
_muteData = { lastState: undefined };
_seekStart = _seekEnd = 0;
_seeking = false;
resetPerVideoState();
};
const resetPerVideoState = () => {
_percentsTracked = [];
_timesTracked = [];
};
this.events = normalizeEventsParam(options.events, EVENT_DEFAULTS);
resetState();
if (this.events.percentsplayed || this.events.timeplayed ||
this.events.seek || this.events.totaltimeplayed) {
this.player.on('timeupdate', timeupdate.bind(this));
}
if (this.events.mute || this.events.unmute) {
this.player.on('volumechange', volumechange.bind(this));
}
if (this.events.pausenoseek) {
this.player.on('pause', pause.bind(this));
this.player.on('play', play.bind(this));
}
this.player.on('loadedmetadata', loadedmetadata.bind(this));
this.player.on('loadeddata', adaptiveEvents.bind(this));
}
}
const normalizeEventsParam = (events, defaults) => {
let normalized = events;
if (events.constructor.name === 'Array') {
normalized = events.reduce((agg, item) => {
const eventDefaults = defaults[item] || {};
if (isPlainObject(item)) {
agg[item.type] = assign({}, eventDefaults, item);
} else {
agg[item] = eventDefaults;
}
return agg;
}, {});
}
return normalized;
};
const playedAtPercentage = (currentTime, duration, percentageCheckpoint, graceRangeSeconds = 0.5) => {
const checkPoint = duration * percentageCheckpoint / 100;
return playedAtTime(currentTime, checkPoint, graceRangeSeconds);
};
const playedAtTime = (currentTime, checkpoint, graceRangeSeconds = 0.5) =>
(currentTime <= checkpoint + graceRangeSeconds &&
currentTime >= checkpoint - graceRangeSeconds);
export { normalizeEventsParam };
export default ExtendedEvents;