import { globalThis, document } from "../../utils/server-safe-globals.js"; import { MediaUIEvents, MediaUIAttributes } from "../../constants.js"; const template = document.createElement("template"); const HANDLE_W = 8; const Z = { 100: 100, 200: 200, 300: 300 }; function lockBetweenZeroAndOne(num) { return Math.max(0, Math.min(1, num)); } template.innerHTML = `
`; class MediaClipSelector extends globalThis.HTMLElement { static get observedAttributes() { return [ "thumbnails", MediaUIAttributes.MEDIA_DURATION, MediaUIAttributes.MEDIA_CURRENT_TIME ]; } constructor() { var _a, _b, _c; super(); if (!this.shadowRoot) { this.attachShadow({ mode: "open" }); this.shadowRoot.appendChild(template.content.cloneNode(true)); } this.draggingEl = null; this.wrapper = this.shadowRoot.querySelector("#selectorContainer"); this.selection = this.shadowRoot.querySelector("#selection"); this.playhead = this.shadowRoot.querySelector("#playhead"); this.leftTrim = this.shadowRoot.querySelector("#leftTrim"); this.spacerFirst = this.shadowRoot.querySelector("#spacerFirst"); this.startHandle = this.shadowRoot.querySelector("#startHandle"); this.spacerMiddle = this.shadowRoot.querySelector("#spacerMiddle"); this.endHandle = this.shadowRoot.querySelector("#endHandle"); this.spacerLast = this.shadowRoot.querySelector("#spacerLast"); this._clickHandler = this.handleClick.bind(this); this._dragStart = this.dragStart.bind(this); this._dragEnd = this.dragEnd.bind(this); this._drag = this.drag.bind(this); this.wrapper.addEventListener("click", this._clickHandler, false); this.wrapper.addEventListener("touchstart", this._dragStart, false); (_a = globalThis.window) == null ? void 0 : _a.addEventListener("touchend", this._dragEnd, false); this.wrapper.addEventListener("touchmove", this._drag, false); this.wrapper.addEventListener("mousedown", this._dragStart, false); (_b = globalThis.window) == null ? void 0 : _b.addEventListener("mouseup", this._dragEnd, false); (_c = globalThis.window) == null ? void 0 : _c.addEventListener("mousemove", this._drag, false); this.enableThumbnails(); } get mediaDuration() { return +this.getAttribute(MediaUIAttributes.MEDIA_DURATION); } get mediaCurrentTime() { return +this.getAttribute(MediaUIAttributes.MEDIA_CURRENT_TIME); } /* * pass in a mouse event (evt.clientX) * calculates the percentage progress based on the bounding rectang * converts the percentage progress into a duration in seconds */ getPlayheadBasedOnMouseEvent(evt) { const duration = this.mediaDuration; if (!duration) return; const mousePercent = lockBetweenZeroAndOne(this.getMousePercent(evt)); return mousePercent * duration; } getXPositionFromMouse(evt) { let clientX; if (["touchstart", "touchmove"].includes(evt.type)) { clientX = evt.touches[0].clientX; } return clientX || evt.clientX; } getMousePercent(evt) { const rangeRect = this.wrapper.getBoundingClientRect(); const mousePercent = (this.getXPositionFromMouse(evt) - rangeRect.left) / rangeRect.width; return lockBetweenZeroAndOne(mousePercent); } dragStart(evt) { if (evt.target === this.startHandle) { this.draggingEl = this.startHandle; } if (evt.target === this.endHandle) { this.draggingEl = this.endHandle; } this.initialX = this.getXPositionFromMouse(evt); } dragEnd() { this.initialX = null; this.draggingEl = null; } setSelectionWidth(selectionPercent, fullTimelineWidth) { let percent = selectionPercent; const minWidthPx = HANDLE_W * 3; const minWidthPercent = lockBetweenZeroAndOne( minWidthPx / fullTimelineWidth ); if (percent < minWidthPercent) { percent = minWidthPercent; } this.selection.style.width = `${percent * 100}%`; } drag(evt) { if (!this.draggingEl) { return; } evt.preventDefault(); const rangeRect = this.wrapper.getBoundingClientRect(); const fullTimelineWidth = rangeRect.width; const endXPosition = this.getXPositionFromMouse(evt); const xDelta = endXPosition - this.initialX; const percent = this.getMousePercent(evt); const selectionW = this.selection.getBoundingClientRect().width; if (this.draggingEl === this.startHandle) { this.initialX = this.getXPositionFromMouse(evt); this.leftTrim.style.width = `${percent * 100}%`; const selectionPercent = lockBetweenZeroAndOne( (selectionW - xDelta) / fullTimelineWidth ); this.setSelectionWidth(selectionPercent, fullTimelineWidth); } if (this.draggingEl === this.endHandle) { this.initialX = this.getXPositionFromMouse(evt); const selectionPercent = lockBetweenZeroAndOne( (selectionW + xDelta) / fullTimelineWidth ); this.setSelectionWidth(selectionPercent, fullTimelineWidth); } this.dispatchUpdate(); } dispatchUpdate() { const updateEvent = new CustomEvent("update", { detail: this.getCurrentClipBounds() }); this.dispatchEvent(updateEvent); } getCurrentClipBounds() { const rangeRect = this.wrapper.getBoundingClientRect(); const leftTrimRect = this.leftTrim.getBoundingClientRect(); const selectionRect = this.selection.getBoundingClientRect(); const percentStart = lockBetweenZeroAndOne( leftTrimRect.width / rangeRect.width ); const percentEnd = lockBetweenZeroAndOne( (leftTrimRect.width + selectionRect.width) / rangeRect.width ); return { startTime: Math.round(percentStart * this.mediaDuration), endTime: Math.round(percentEnd * this.mediaDuration) }; } isTimestampInBounds(timestamp) { const { startTime, endTime } = this.getCurrentClipBounds(); return startTime <= timestamp && endTime >= timestamp; } handleClick(evt) { const mousePercent = this.getMousePercent(evt); const timestampForClick = mousePercent * this.mediaDuration; if (this.isTimestampInBounds(timestampForClick)) { this.dispatchEvent( new globalThis.CustomEvent(MediaUIEvents.MEDIA_SEEK_REQUEST, { composed: true, bubbles: true, detail: timestampForClick }) ); } } mediaCurrentTimeSet() { const percentComplete = lockBetweenZeroAndOne( this.mediaCurrentTime / this.mediaDuration ); this.playhead.style.left = `${percentComplete * 100}%`; this.playhead.style.display = "block"; if (!this.mediaPaused) { const { startTime, endTime } = this.getCurrentClipBounds(); if (this.mediaCurrentTime < startTime || this.mediaCurrentTime > endTime) { this.dispatchEvent( new globalThis.CustomEvent(MediaUIEvents.MEDIA_SEEK_REQUEST, { composed: true, bubbles: true, detail: startTime }) ); } } } mediaUnsetCallback(media) { var _a, _b; super.mediaUnsetCallback(media); this.wrapper.removeEventListener("touchstart", this._dragStart); this.wrapper.removeEventListener("touchend", this._dragEnd); this.wrapper.removeEventListener("touchmove", this._drag); this.wrapper.removeEventListener("mousedown", this._dragStart); (_a = globalThis.window) == null ? void 0 : _a.removeEventListener("mouseup", this._dragEnd); (_b = globalThis.window) == null ? void 0 : _b.removeEventListener("mousemove", this._drag); } /* * This was copied over from media-time-range, we should have a way of making * this code shared between the two components */ enableThumbnails() { this.thumbnailPreview = this.shadowRoot.querySelector( "media-preview-thumbnail" ); const thumbnailContainer = this.shadowRoot.querySelector( "#thumbnailContainer" ); thumbnailContainer.classList.add("enabled"); let mouseMoveHandler; const trackMouse = () => { var _a; mouseMoveHandler = (evt) => { const duration = this.mediaDuration; if (!duration) return; const rangeRect = this.wrapper.getBoundingClientRect(); const mousePercent = this.getMousePercent(evt); const leftPadding = rangeRect.left - this.getBoundingClientRect().left; const thumbnailLeft = leftPadding + mousePercent * rangeRect.width; this.thumbnailPreview.style.left = `${thumbnailLeft}px`; this.dispatchEvent( new globalThis.CustomEvent(MediaUIEvents.MEDIA_PREVIEW_REQUEST, { composed: true, bubbles: true, detail: mousePercent * duration }) ); }; (_a = globalThis.window) == null ? void 0 : _a.addEventListener("mousemove", mouseMoveHandler, false); }; const stopTrackingMouse = () => { var _a; (_a = globalThis.window) == null ? void 0 : _a.removeEventListener("mousemove", mouseMoveHandler); }; let rangeEntered = false; const rangeMouseMoveHander = () => { var _a; if (!rangeEntered && this.mediaDuration) { rangeEntered = true; this.thumbnailPreview.style.display = "block"; trackMouse(); const offRangeHandler = (evt) => { var _a2; if (evt.target != this && !this.contains(evt.target)) { this.thumbnailPreview.style.display = "none"; (_a2 = globalThis.window) == null ? void 0 : _a2.removeEventListener( "mousemove", offRangeHandler ); rangeEntered = false; stopTrackingMouse(); } }; (_a = globalThis.window) == null ? void 0 : _a.addEventListener( "mousemove", offRangeHandler, false ); } if (!this.mediaDuration) { this.thumbnailPreview.style.display = "none"; } }; this.addEventListener("mousemove", rangeMouseMoveHander, false); } disableThumbnails() { const thumbnailContainer = this.shadowRoot.querySelector( "#thumbnailContainer" ); thumbnailContainer.classList.remove("enabled"); } } if (!globalThis.customElements.get("media-clip-selector")) { globalThis.customElements.define("media-clip-selector", MediaClipSelector); } var media_clip_selector_default = MediaClipSelector; export { media_clip_selector_default as default };