videojs-trimmer
Version:
Video.js Trimmer Plugin
218 lines (177 loc) • 6.92 kB
JavaScript
import videojs from "video.js";
import "videojs-offset";
const Plugin = videojs.getPlugin("plugin");
class VideoTrimmer extends Plugin {
constructor(player, options) {
super(player);
this.options = videojs.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.registerPlugin("trimmer", VideoTrimmer);
export default VideoTrimmer;