UNPKG

videojs-trimmer

Version:

Video.js Trimmer Plugin

325 lines (303 loc) 12.7 kB
/*! @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; }));