UNPKG

trtc-electron-sdk

Version:

trtc electron sdk

535 lines (534 loc) 25.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = require("events"); const trtc_define_1 = require("../trtc_define"); const logger_1 = __importDefault(require("../logger")); const Movable_1 = __importDefault(require("./Movable")); const Resizable_1 = __importDefault(require("./Resizable")); const utils_1 = require("../utils"); const MIN_MOVE_DISTANCE = 5; // minimum moving distance class TRTCMediaMixingDesigner { constructor(options) { this.logPrefix = '[TRTCMediaMixingDesigner]'; this.mixingVideoWidth = 640; this.mixingVideoHeight = 360; this.canExceedContainer = false; this.movableHandler = null; this.resizableHandler = null; this.previewScale = 1; this.previewWidth = 0; this.previewHeight = 0; this.previewLeft = 0; this.previewTop = 0; this.BOUNDARY_ADSORPTION_THRESHOLD = 10; this.newSelected = null; this.clickedMediaSources = []; this.oldSelectedIndex = -1; this.mousedownLeft = null; this.mousedownTop = null; this.eventButton = null; this.resizeObserver = null; this.container = options.view; this.container.style.position = 'relative'; this.relativeWorkingArea = { left: 0, top: 0, right: 1, bottom: 1 }; this.absoluteWorkingArea = {}; this.fillMode = trtc_define_1.TRTCVideoFillMode.TRTCVideoFillMode_Fit; this.updateWorkingArea(); this.mixingVideoWidth = options.width; this.mixingVideoHeight = options.height; this.updatePreviewProperty(); this.canExceedContainer = options.canExceedContainer || false; this.eventEmitter = new events_1.EventEmitter(); this.mediaList = []; this.highlightColor = options.highlightColor; this.selectedMediaIndex = -1; this.moveAndResizeOverlay = document.createElement('div'); this.moveAndResizeOverlay.style.display = 'none'; this.moveAndResizeOverlay.style.position = 'absolute'; this.container.appendChild(this.moveAndResizeOverlay); this.initMediaMovable(); this.initMediaResizable(); this.initContainerMouseEventHander(); this.initContainerResizeObserver(); } updateOptions(options) { if (this.mixingVideoWidth !== options.width || this.mixingVideoHeight !== options.height) { this.mixingVideoWidth = options.width; this.mixingVideoHeight = options.height; this.updatePreviewProperty(); this.updateOverlay(); } } setWorkingArea(relative, fillMode = trtc_define_1.TRTCVideoFillMode.TRTCVideoFillMode_Fit) { if (relative) { this.relativeWorkingArea = Object.assign({}, relative); this.fillMode = fillMode; this.updateWorkingArea(); this.updatePreviewProperty(); this.updateOverlay(); } else { logger_1.default.error(`${this.logPrefix}setWorkingArea invalid parameter:`, relative); } } addMedia(media) { // sort by zOrder desc const length = this.mediaList.length; let insertIndex = -1; for (let i = 0; i < length; i++) { if (media.zOrder >= this.mediaList[i].zOrder) { insertIndex = i; break; } } if (insertIndex >= 0) { this.mediaList.splice(insertIndex, 0, media); } else { this.mediaList.push(media); } if (media.isSelected) { this.selectedMediaIndex = insertIndex >= 0 ? insertIndex : (this.mediaList.length - 1); this.updateOverlay(); } } removeMedia(media) { const targetIndex = this.mediaList.findIndex(item => item.id === media.id); if (targetIndex !== -1) { this.mediaList.splice(targetIndex, 1); if (this.selectedMediaIndex === targetIndex || media.isSelected) { this.selectedMediaIndex = -1; this.updateOverlay(); } } } updateMedia(media) { let targetIndex = this.mediaList.findIndex(item => item.id === media.id); if (targetIndex !== -1) { const isZOrderChanged = media.zOrder !== this.mediaList[targetIndex].zOrder; this.mediaList[targetIndex] = Object.assign({}, this.mediaList[targetIndex], media); if (isZOrderChanged) { this.mediaList.sort((a, b) => b.zOrder - a.zOrder); targetIndex = this.mediaList.findIndex(item => item.id === media.id); } if (media.isSelected) { this.selectedMediaIndex = targetIndex; this.updateOverlay(); } } } removeAllMedia() { this.mediaList = []; this.selectedMediaIndex = -1; this.updateOverlay(); } setHighlightColor(color) { if (this.highlightColor !== color) { this.highlightColor = color; if (this.resizableHandler) { const colorString = this.transferColorNumberToString(color); this.resizableHandler.setHighlightColor(colorString); } } } transferColorNumberToString(color) { if (color >= 0 && color <= 0xFFFFFF) { // RGB color let colorString = color.toString(16); while (colorString.length < 6) { colorString = '0' + colorString; } colorString = `#${colorString}`; return colorString; } else { // invalid color, set to transparent return 'transparent'; } } on(event, func) { var _a; (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.on(event, func); } off(event, func) { var _a; (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.off(event, func); } destroy() { var _a, _b, _c, _d; if (this.resizeObserver) { if (this.container) { this.resizeObserver.unobserve(this.container); } this.resizeObserver.disconnect(); this.resizeObserver = null; } (_a = this.movableHandler) === null || _a === void 0 ? void 0 : _a.destroy(); (_b = this.resizableHandler) === null || _b === void 0 ? void 0 : _b.destroy(); this.movableHandler = null; this.resizableHandler = null; (_c = this.container) === null || _c === void 0 ? void 0 : _c.removeEventListener('contextmenu', this.onRightButtonClicked, false); document.removeEventListener("mousedown", this.onContainerMousedown, false); if (this.moveAndResizeOverlay) { (_d = this.container) === null || _d === void 0 ? void 0 : _d.removeChild(this.moveAndResizeOverlay); this.moveAndResizeOverlay = null; } this.container = null; this.eventEmitter = null; this.mediaList = []; } initMediaMovable() { this.onMove = this.onMove.bind(this); this.movableHandler = new Movable_1.default(this.moveAndResizeOverlay, this.container, { canExceedContainer: this.canExceedContainer, calcPositionOnly: true, }); this.movableHandler.on('move', this.onMove); } initMediaResizable() { this.onResize = this.onResize.bind(this); this.resizableHandler = new Resizable_1.default(this.moveAndResizeOverlay, this.container, { keepRatio: true, stopPropagation: true, canExceedContainer: this.canExceedContainer, anchorMode: 0, hightlightColor: this.transferColorNumberToString(this.highlightColor), }); this.resizableHandler.on('resize', this.onResize); } initContainerMouseEventHander() { if (this.container) { this.onContainerMousedown = this.onContainerMousedown.bind(this); this.onContainerMousemove = this.onContainerMousemove.bind(this); this.onContainerMouseup = this.onContainerMouseup.bind(this); document.addEventListener("mousedown", this.onContainerMousedown, false); this.onRightButtonClicked = this.onRightButtonClicked.bind(this); this.container.addEventListener('contextmenu', this.onRightButtonClicked, false); } else { logger_1.default.error(`${this.logPrefix}initContainerMouseEventHander failed, no container view`); } } initContainerResizeObserver() { if (this.container) { this.onPreviewAreaResize = (0, utils_1.debounce)(this.onPreviewAreaResize.bind(this), 100); this.resizeObserver = new ResizeObserver(this.onPreviewAreaResize); this.resizeObserver.observe(this.container); } } onPreviewAreaResize(entries) { for (const entry of entries) { if (entry.target === this.container) { this.updateWorkingArea(); this.updatePreviewProperty(); this.updateOverlay(); break; } } } updateWorkingArea() { if (this.relativeWorkingArea && this.container) { const containerRect = this.container.getBoundingClientRect(); this.absoluteWorkingArea = { left: this.relativeWorkingArea.left * containerRect.width, top: this.relativeWorkingArea.top * containerRect.height, right: this.relativeWorkingArea.right * containerRect.width, bottom: this.relativeWorkingArea.bottom * containerRect.height, width: (this.relativeWorkingArea.right - this.relativeWorkingArea.left) * containerRect.width, height: (this.relativeWorkingArea.bottom - this.relativeWorkingArea.top) * containerRect.height, }; } else { logger_1.default.error(`${this.logPrefix}updateWorkingArea no data:`, this.relativeWorkingArea, this.container); } } updatePreviewProperty() { if (this.absoluteWorkingArea) { if (this.mixingVideoWidth <= 0 || this.mixingVideoHeight <= 0) { logger_1.default.error(`${this.logPrefix}updatePreviewProperty failed, mixingVideoWidth or mixingVideoHeight is zero`); return; } const workingAreaWidth = this.absoluteWorkingArea.width; const workingAreaHeight = this.absoluteWorkingArea.height; const widthScale = workingAreaWidth / this.mixingVideoWidth; const heightScale = workingAreaHeight / this.mixingVideoHeight; if (this.fillMode === trtc_define_1.TRTCVideoFillMode.TRTCVideoFillMode_Fill) { this.previewScale = widthScale > heightScale ? widthScale : heightScale; } else { this.fillMode = trtc_define_1.TRTCVideoFillMode.TRTCVideoFillMode_Fit; this.previewScale = widthScale > heightScale ? heightScale : widthScale; } this.previewWidth = this.mixingVideoWidth * this.previewScale; this.previewHeight = this.mixingVideoHeight * this.previewScale; this.previewLeft = (workingAreaWidth - this.previewWidth) / 2; this.previewTop = (workingAreaHeight - this.previewHeight) / 2; } else { logger_1.default.error(`${this.logPrefix}updatePreviewProperty failed, no HTML element to display`); } } updateOverlay() { if (this.moveAndResizeOverlay) { let left = this.absoluteWorkingArea.left + this.previewLeft; let top = this.absoluteWorkingArea.top + this.previewTop; let width = 0; let height = 0; if (this.selectedMediaIndex >= 0) { this.moveAndResizeOverlay.style.display = 'block'; const selectedPreviewRect = { left: this.mediaList[this.selectedMediaIndex].rect.left * this.previewScale, top: this.mediaList[this.selectedMediaIndex].rect.top * this.previewScale, right: this.mediaList[this.selectedMediaIndex].rect.right * this.previewScale, bottom: this.mediaList[this.selectedMediaIndex].rect.bottom * this.previewScale }; left = this.absoluteWorkingArea.left + this.previewLeft + selectedPreviewRect.left; top = this.absoluteWorkingArea.top + this.previewTop + selectedPreviewRect.top; width = selectedPreviewRect.right - selectedPreviewRect.left; height = selectedPreviewRect.bottom - selectedPreviewRect.top; } else { this.moveAndResizeOverlay.style.display = 'none'; } let renderScale = 1; if (this.container) { const containerLayoutWidth = this.container.offsetWidth; const containerRenderWidth = Math.round(this.container.getBoundingClientRect().width); if (containerLayoutWidth !== containerRenderWidth) { logger_1.default.warn(`${this.logPrefix}updateOverlay container layout width: ${containerLayoutWidth} container render width: ${containerRenderWidth}`); renderScale = containerRenderWidth / containerLayoutWidth; } } this.moveAndResizeOverlay.style.left = `${left}px`; this.moveAndResizeOverlay.style.top = `${top}px`; this.moveAndResizeOverlay.style.width = `${width}px`; this.moveAndResizeOverlay.style.height = `${height}px`; if (renderScale !== 1) { this.moveAndResizeOverlay.style.transform = `scale(${1 / renderScale})`; this.moveAndResizeOverlay.style.transformOrigin = `-${left}px -${top}px`; // Why? } else { this.moveAndResizeOverlay.style.transform = 'none'; this.moveAndResizeOverlay.style.transformOrigin = 'none'; } } } onMove(left, top) { var _a; const target = this.mediaList[this.selectedMediaIndex]; if (target && this.moveAndResizeOverlay) { // calc new preview rect const newPreviewRect = { left: left - this.absoluteWorkingArea.left - this.previewLeft, top: top - this.absoluteWorkingArea.top - this.previewTop, right: left - this.absoluteWorkingArea.left - this.previewLeft + this.moveAndResizeOverlay.offsetWidth, bottom: top - this.absoluteWorkingArea.top - this.previewTop + this.moveAndResizeOverlay.offsetHeight, }; this.doAdsorption(newPreviewRect); // calc new mixing rect const newRectInMixing = { left: Math.round(newPreviewRect.left / this.previewScale), top: Math.round(newPreviewRect.top / this.previewScale), right: Math.round(newPreviewRect.right / this.previewScale), bottom: Math.round(newPreviewRect.bottom / this.previewScale), }; (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.emit('onSourceMoved', Object.assign({}, target), newRectInMixing); } else { logger_1.default.warn(`${this.logPrefix}onMove no selected media`); } } doAdsorption(inputRect) { const threshold = this.BOUNDARY_ADSORPTION_THRESHOLD; if (Math.abs(inputRect.left) < threshold) { inputRect.right = inputRect.right - inputRect.left; inputRect.left = 0; } if (Math.abs(inputRect.top) < threshold) { inputRect.bottom = inputRect.bottom - inputRect.top; inputRect.top = 0; } if (Math.abs(inputRect.right - this.previewWidth) < threshold) { inputRect.left = inputRect.left + this.previewWidth - inputRect.right; inputRect.right = this.previewWidth; } if (Math.abs(inputRect.bottom - this.previewHeight) < threshold) { inputRect.top = inputRect.top + this.previewHeight - inputRect.bottom; inputRect.bottom = this.previewHeight; } } onResize(left, top, width, height) { var _a; const target = this.mediaList[this.selectedMediaIndex]; if (target) { // calc new preview rect const newPreviewRect = { left: left - this.absoluteWorkingArea.left - this.previewLeft, top: top - this.absoluteWorkingArea.top - this.previewTop, right: left - this.absoluteWorkingArea.left - this.previewLeft + width, bottom: top - this.absoluteWorkingArea.top - this.previewTop + height, }; // calc new mixing rect const newRectInMixing = { left: Math.round(newPreviewRect.left / this.previewScale), top: Math.round(newPreviewRect.top / this.previewScale), right: Math.round(newPreviewRect.right / this.previewScale), bottom: Math.round(newPreviewRect.bottom / this.previewScale), }; (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.emit('onSourceResized', Object.assign({}, target), newRectInMixing); } else { logger_1.default.warn(`${this.logPrefix}onResize no selected media`); } } emitOnSelect(media) { var _a; if (media) { const length = this.mediaList.length; for (let i = 0; i < length; i++) { if (media.id === this.mediaList[i].id) { this.selectedMediaIndex = i; break; } } } else { this.selectedMediaIndex = -1; } this.updateOverlay(); (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.emit('onSourceSelected', media ? Object.assign({}, media) : null); } onContainerMousedown(event) { const target = event.target; this.eventButton = event.button; if (target && this.container) { if (this.container.contains(target)) { // calc click point coordinates in mix video image const containerBounds = this.container.getBoundingClientRect(); const xInPreviewImage = event.clientX - containerBounds.left - this.absoluteWorkingArea.left - this.previewLeft; const yInPreviewImage = event.clientY - containerBounds.top - this.absoluteWorkingArea.top - this.previewTop; const xInImage = xInPreviewImage / this.previewScale; const yInImage = yInPreviewImage / this.previewScale; for (let i = 0; i < this.mediaList.length; i++) { const item = this.mediaList[i]; if (item.rect && xInImage >= item.rect.left && xInImage <= item.rect.right && yInImage >= item.rect.top && yInImage <= item.rect.bottom) { this.clickedMediaSources.push(item); if (this.mediaList[this.selectedMediaIndex]) { if (item.id === this.mediaList[this.selectedMediaIndex].id) { this.oldSelectedIndex = this.clickedMediaSources.length - 1; } } } } this.mousedownLeft = event.screenX; this.mousedownTop = event.screenY; } if (this.clickedMediaSources.length > 0) { if (this.eventButton === 2 && this.oldSelectedIndex === -1) { // select first media source this.newSelected = this.clickedMediaSources[0]; // mediaSourcesStore.setSelectedMediaKey(newSelected); this.emitOnSelect(this.newSelected); this.clickedMediaSources.splice(0, this.clickedMediaSources.length); } else { document.addEventListener("mousemove", this.onContainerMousemove, false); document.addEventListener("mouseup", this.onContainerMouseup, false); } } else { this.newSelected = null; this.emitOnSelect(null); this.mousedownLeft = null; this.mousedownTop = null; this.eventButton = null; } } } onContainerMousemove(event) { var _a; const target = event.target; if (target && this.container) { if (this.mousedownLeft !== null && this.mousedownTop !== null) { const leftMovedDistance = event.screenX - this.mousedownLeft; const topMovedDistance = event.screenY - this.mousedownTop; if (Math.abs(leftMovedDistance) >= MIN_MOVE_DISTANCE || Math.abs(topMovedDistance) >= MIN_MOVE_DISTANCE) { if (this.oldSelectedIndex >= 0) { // move or resize old selected media source this.clickedMediaSources.splice(0, this.clickedMediaSources.length); this.oldSelectedIndex = -1; } else if (this.clickedMediaSources.length > 0) { // select first media source this.newSelected = this.clickedMediaSources[0]; this.emitOnSelect(this.newSelected); this.clickedMediaSources.splice(0, this.clickedMediaSources.length); // 支持 mousedown 选中媒体源和 mousemove 移动媒体源同时触发 (_a = this.moveAndResizeOverlay) === null || _a === void 0 ? void 0 : _a.dispatchEvent(new MouseEvent('mousedown', { screenX: this.mousedownLeft, screenY: this.mousedownTop, button: this.eventButton, })); } } } } } onContainerMouseup(event) { document.removeEventListener("mousemove", this.onContainerMousemove, false); document.removeEventListener("mouseup", this.onContainerMouseup, false); const target = event.target; if (target && this.container) { if (this.clickedMediaSources.length > 0) { if (this.oldSelectedIndex >= 0) { if (this.eventButton === 0) { // select next zOrder media source const newSelectedIndex = (this.oldSelectedIndex + 1) % this.clickedMediaSources.length; this.newSelected = this.clickedMediaSources[newSelectedIndex]; this.emitOnSelect(this.newSelected); } else { // right click of selected media source, do not change selected media source // will show context menu } } else { // select first media source this.newSelected = this.clickedMediaSources[0]; this.emitOnSelect(this.newSelected); } } else { // do nothing } } else { // un-select current media source this.emitOnSelect(null); } this.mousedownLeft = null; this.mousedownTop = null; this.clickedMediaSources.splice(0, this.clickedMediaSources.length); this.oldSelectedIndex = -1; this.newSelected = null; this.eventButton = null; } onRightButtonClicked(event) { var _a; event.preventDefault(); (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.emit('onRightButtonClicked', Object.assign({}, this.mediaList[this.selectedMediaIndex]), { windowX: event.clientX, windowY: event.clientY, screenX: event.screenX, screenY: event.screenY }); } } exports.default = TRTCMediaMixingDesigner;