videojs-trimmer
Version:
Video.js Trimmer Plugin
325 lines (303 loc) • 12.7 kB
JavaScript
/*! @name videojs-trimmer @version 1.0.0-beta.29 @license MIT */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js')) :
typeof define === 'function' && define.amd ? define(['video.js'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsTrimmer = factory(global.videojs));
})(this, (function (videojs) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
/*! @name videojs-offset @version 2.1.3 @license MIT */
var version = "2.1.3";
var defaults = {}; // Cross-compatibility for Video.js 5 and 6.
var registerPlugin = videojs__default["default"].registerPlugin || videojs__default["default"].plugin; // const dom = videojs.dom || videojs;
/**
* Checks whether the clip should be ended.
*
* @function onPlayerTimeUpdate
*
*/
var onPlayerTimeUpdate = function onPlayerTimeUpdate() {
var _this = this;
var curr = this.currentTime();
if (curr < 0) {
this.currentTime(0);
this.play();
}
if (this._offsetEnd > 0 && curr > this._offsetEnd - this._offsetStart) {
this.off('timeupdate', onPlayerTimeUpdate);
this.pause();
this.trigger('ended'); // Re-bind to timeupdate next time the video plays
this.one('play', function () {
_this.on('timeupdate', onPlayerTimeUpdate);
});
if (!this._restartBeginning) {
this.currentTime(this._offsetEnd - this._offsetStart);
} else {
this.trigger('loadstart');
this.currentTime(0);
}
}
};
/**
* Function to invoke when the player is ready.
*
* This is a great place for your plugin to initialize itself. When this
* function is called, the player will have its DOM and child components
* in place.
*
* @function onPlayerReady
* @param {Player} player
* A Video.js player.
* @param {Object} [options={}]
* An object of options left to the plugin author to define.
*/
var onPlayerReady = function onPlayerReady(player, options) {
player.one('play', function () {
player.on('timeupdate', onPlayerTimeUpdate);
});
};
/**
* A video.js plugin.
*
* In the plugin function, the value of `this` is a video.js `Player`
* instance. You cannot rely on the player being in a "ready" state here,
* depending on how the plugin is invoked. This may or may not be important
* to you; if not, remove the wait for "ready"!
*
* @function offset
* @param {Object} [options={}]
* An object of options left to the plugin author to define.
*/
var offset = function offset(options) {
var _this2 = this;
options = options || {};
var Player = this.constructor;
this._offsetStart = parseFloat(options.start || '0');
this._offsetEnd = parseFloat(options.end || '0');
this._restartBeginning = options.restart_beginning || false;
if (!Player.__super__ || !Player.__super__.__offsetInit) {
Player.__super__ = {
__offsetInit: true,
duration: Player.prototype.duration,
currentTime: Player.prototype.currentTime,
bufferedPercent: Player.prototype.bufferedPercent,
remainingTime: Player.prototype.remainingTime,
buffered: Player.prototype.buffered
};
Player.prototype.duration = function () {
if (this._offsetEnd !== undefined && this._offsetStart !== undefined) {
if (this._offsetEnd > 0) {
return this._offsetEnd - this._offsetStart;
}
return Player.__super__.duration.apply(this, arguments) - this._offsetStart;
}
return Player.__super__.duration.apply(this, arguments);
};
Player.prototype.currentTime = function (seconds) {
if (seconds !== undefined) {
if (this._offsetStart !== undefined) {
return Player.__super__.currentTime.call(this, seconds + this._offsetStart);
}
return Player.__super__.currentTime.call(this, seconds);
}
if (this._offsetStart !== undefined) {
var t = Player.__super__.currentTime.apply(this) - this._offsetStart;
this.getCache().currentTime = t;
return t;
}
return Player.__super__.currentTime.apply(this);
};
Player.prototype.remainingTime = function () {
return this.duration() - this.currentTime();
};
Player.prototype.startOffset = function () {
return this._offsetStart;
};
Player.prototype.endOffset = function () {
if (this._offsetEnd > 0) {
return this._offsetEnd;
}
return this.duration();
};
Player.prototype.buffered = function () {
var buff = Player.__super__.buffered.call(this);
var ranges = [];
for (var i = 0; i < buff.length; i++) {
ranges[i] = [Math.max(0, buff.start(i) - this._offsetStart), Math.min(Math.max(0, buff.end(i) - this._offsetStart), this.duration())];
}
return videojs__default["default"].createTimeRanges(ranges);
};
}
this.ready(function () {
onPlayerReady(_this2, videojs__default["default"].mergeOptions(defaults, options));
});
}; // Register the plugin with video.js.
registerPlugin('offset', offset); // Include the version number.
offset.VERSION = version;
const Plugin = videojs__default["default"].getPlugin("plugin");
class VideoTrimmer extends Plugin {
constructor(player, options) {
super(player);
this.options = videojs__default["default"].obj.merge(options);
this.startTime = 0;
this.endTime = 0;
this.originalDuration = 0;
this.isDragging = false;
this.createTrimmer();
this.player.on("loadedmetadata", () => {
this.originalDuration = this.player.duration();
this.endTime = this.originalDuration;
if (this.options.startTime) {
this.startTime = this.options.startTime;
}
if (this.options.endTimeOffset) {
this.endTime = this.originalDuration - this.options.endTimeOffset;
}
this.updateTrimmer();
});
this.player.addClass("video-js-trimmer");
this.bindEvents();
}
createTrimmer() {
// Create the trimmer container
this.trimmerContainer = document.createElement("div");
this.trimmerContainer.className = "vjs-trimmer-container";
// Insert it after the video element but before the control bar
const playerEl = this.player.el();
playerEl.insertBefore(this.trimmerContainer, this.player.controlBar.el());
// Create and append trimmer elements to the new container
this.trimmerEl = document.createElement("div");
this.trimmerEl.className = "vjs-trimmer";
this.trimmerContainer.appendChild(this.trimmerEl);
this.startHandle = document.createElement("div");
this.startHandle.className = "vjs-trimmer-handle start";
this.trimmerContainer.appendChild(this.startHandle);
this.endHandle = document.createElement("div");
this.endHandle.className = "vjs-trimmer-handle end";
this.trimmerContainer.appendChild(this.endHandle);
// Create progress indicator
this.progressIndicator = document.createElement("div");
this.progressIndicator.className = "vjs-trimmer-progress";
this.trimmerEl.appendChild(this.progressIndicator);
// Add duration display
this.durationDisplay = document.createElement("div");
this.durationDisplay.className = "vjs-trimmer-duration";
this.trimmerContainer.appendChild(this.durationDisplay);
this.updateTrimmer();
}
bindEvents() {
let isDraggingStart = false;
let isDraggingEnd = false;
let isDraggingBar = false;
let lastUpdateTime = 0;
const throttleDelay = 8;
const onMouseMove = event => {
if (!this.isDragging) return;
const now = performance.now();
if (now - lastUpdateTime < throttleDelay) return;
lastUpdateTime = now;
const rect = this.trimmerContainer.getBoundingClientRect();
const pos = (event.clientX - rect.left) / rect.width * this.originalDuration;
if (isDraggingStart) {
this.startTime = Math.min(Math.max(pos, 0), this.endTime);
} else if (isDraggingEnd) {
this.endTime = Math.max(Math.min(pos, this.originalDuration), this.startTime);
} else if (isDraggingBar) {
const trimmedDuration = this.endTime - this.startTime;
const newStartTime = Math.max(0, Math.min(pos, this.originalDuration - trimmedDuration));
this.startTime = newStartTime;
this.endTime = newStartTime + trimmedDuration;
}
this.player.currentTime(0);
requestAnimationFrame(() => {
this.updateTrimmer();
this.player.trigger("trimmerchange", {
startTime: this.startTime,
endTime: this.endTime
});
});
};
const onMouseUp = () => {
isDraggingStart = false;
isDraggingEnd = false;
isDraggingBar = false;
this.isDragging = false;
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
this.startHandle.addEventListener("mousedown", event => {
event.preventDefault();
isDraggingStart = true;
this.isDragging = true;
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
});
this.endHandle.addEventListener("mousedown", event => {
event.preventDefault();
isDraggingEnd = true;
this.isDragging = true;
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
});
this.trimmerEl.addEventListener("mousedown", event => {
if (!event.target.classList.contains("vjs-trimmer-handle")) {
event.preventDefault();
isDraggingBar = true;
this.isDragging = true;
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
}
});
// Update progress indicator on timeupdate
this.player.on("timeupdate", () => {
requestAnimationFrame(() => {
this.updateTrimmer();
});
});
}
updateTrimmer() {
const startPos = this.startTime / this.originalDuration * 100;
const endPos = this.endTime / this.originalDuration * 100;
this.trimmerEl.style.left = `${startPos}%`;
this.trimmerEl.style.width = `${endPos - startPos}%`;
this.startHandle.style.left = `${startPos}%`;
this.endHandle.style.left = `${endPos}%`;
this.player.offset({
start: this.startTime,
end: this.endTime
});
// Update duration display
const trimmedDuration = this.endTime - this.startTime;
this.durationDisplay.textContent = `Trimmed duration: ${this.formatTime(trimmedDuration)}`;
// Update progress indicator
const currentTime = this.player.currentTime() + this.startTime;
const trimmedProgress = (currentTime - this.startTime) / (this.endTime - this.startTime);
const progressPos = Math.min(Math.max(trimmedProgress, 0), 1) * 100;
this.progressIndicator.style.left = `${progressPos}%`;
}
createTimeMarkers() {
const markerContainer = document.createElement("div");
markerContainer.className = "vjs-time-markers";
this.trimmerContainer.appendChild(markerContainer);
const numMarkers = 20;
for (let i = 0; i <= numMarkers; i++) {
const marker = document.createElement("div");
marker.className = "vjs-time-marker";
const time = i / numMarkers * this.originalDuration;
marker.style.left = `${i / numMarkers * 100}%`;
const timeLabel = document.createElement("span");
timeLabel.className = "vjs-time-label";
timeLabel.textContent = this.formatTime(time);
marker.appendChild(timeLabel);
markerContainer.appendChild(marker);
}
}
formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
}
}
videojs__default["default"].registerPlugin("trimmer", VideoTrimmer);
return VideoTrimmer;
}));