trtc-electron-sdk
Version:
trtc electron sdk
535 lines (534 loc) • 25.3 kB
JavaScript
"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;