UNPKG

trtc-electron-sdk

Version:

trtc electron sdk

1,004 lines (1,003 loc) 44.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TRTCMediaMixingManager = exports.TRTCMediaMixingEvent = exports.TRTCStreamLayoutMode = exports.TRTCMediaSourceType = exports.TRTCMediaMixingErrorCode = void 0; const events_1 = require("events"); const trtc_define_1 = require("../../trtc_define"); const utils_1 = require("../../utils"); const constant_1 = require("../../constant"); const logger_1 = __importDefault(require("../../logger")); const index_1 = __importDefault(require("../../MediaMixingDesigner/index")); const StreamLayout_1 = require("./StreamLayout"); var types_1 = require("./types"); Object.defineProperty(exports, "TRTCMediaMixingErrorCode", { enumerable: true, get: function () { return types_1.TRTCMediaMixingErrorCode; } }); Object.defineProperty(exports, "TRTCMediaSourceType", { enumerable: true, get: function () { return types_1.TRTCMediaSourceType; } }); Object.defineProperty(exports, "TRTCStreamLayoutMode", { enumerable: true, get: function () { return types_1.TRTCStreamLayoutMode; } }); const NodeTRTCEngine = require('../../../build/Release/trtc_electron_sdk.node'); /** * 媒体源本地混流错误码 * @enum {Number} */ const TRTCMediaMixingErrorCode_HACK_JSDOC = { /** 成功 */ Success: 0, /** 通用错误码 */ Error: -1, /** 参数非法 */ InvalidParams: 2, /** 找不到媒体源 */ NotFoundSource: 3, /** 图片加载失败 */ ImageSourceLoadFailed: 4, /** 摄像头未授权 */ CameraNotAuthorized: 5, /** 摄像头被占用 */ CameraIsOccupied: 6, /** 摄像头断开连接 */ CameraDisconnected: 7, }; /** * 媒体源类型 * @enum {Number} */ const TRTCMediaSourceType_HACK_JSDOC = { /** 摄像头 */ kCamera: 0, /** 屏幕/窗口分享 */ kScreen: 1, /** 图片 */ kImage: 2, /** 手机投屏 */ kPhoneMirror: 4, }; /** * 手机投屏媒体源参数 * * @typedef {Object} TRTCPhoneMirrorParam * @property {Number} platformType -1:未知类型, 0:安卓系统, 1:iOS 系统 * @property {Number} connectType -1:未知类型, 0:USB有线投屏, 1:WIFI无线投屏 * @property {String} deviceId 设备 ID * @property {String} deviceName 设备名称 * @property {String} placeholderImagePath 垫片图路径 * @property {Number} frameRate 视频帧率 * @property {Number} bitrateKbps 音频码率 * */ const TRTCPhoneMirrorParam_HACK_JSDOC = null; /** * 媒体源数据 * * @typedef {Object} TRTCMediaSource * @property {TRTCMediaSourceType} sourceType 媒体源类型 * @property {String} sourceId 媒体源唯一 ID * @property {Number} zOrder 媒体源展示层级,取值 0 - 15 * @property {Rect} rect 媒体源显示区域 * @property {Boolean} isSelected 媒体源是否选中,可选属性,默认:false * @property {TRTCVideoRotation} rotation 媒体源选中角度,可选属性 * @property {TRTCVideoFillMode} fillMode 媒体源显示模式,可选属性,默认 `TRTCVideoFillMode_Fit` * @property {TRTCVideoMirrorType} mirrorType 媒体源是否镜像,可选属性,默认不镜像 * @property {TRTCPhoneMirrorConnectionParam} connectionParam 手机投屏类型媒体源,连接参数 */ const TRTCMediaSource_HACK_JSDOC = null; /** * 媒体源本地混流编码参数 * * @typedef {Object} TRTCMediaMixingEncParam * @property {TRTCVideoEncParam} videoEncoderParams 本地混流编码参数 * @property {Number} canvasColor 混流视频背景色,默认值:0x0 代表黑色。格式为十六进制数字,比如:`0x61B9F1` 代表 RGB 颜色(97、158、241)。 * @property {Number} selectedBorderColor 选中媒体源的高亮边框颜色,默认黄色:0xFFFF00。 */ const TRTCMediaMixingEncParam_HACK_JSDOC = null; /** * 连麦视频流布局模式 * * 本地混流高性能渲染模式下,多路视频流布局模式 * * @enum {String} */ const TRTCStreamLayoutMode_HACK_JSDOC = { /** 自定义布局 */ Custom: 'Custom', /** 无布局,恢复默认 */ None: 'None', }; /** * 单路连麦视频流布局信息 * * 本地混流高性能渲染模式下,单路视频流布局信息 * @typedef {Object} TRTCStreamInfo * @property {String} userId 用户 ID,本地用户需填空字符长 * @property {TRTCVideoFillMode} fillMode 视频填充模式 * @property {Rect} rect 视频流显示区域 * @property {Number} zOrder 视频流展示层级,取值 0 - 15 */ const TRTCStreamInfo_HACK_JSDOC = null; /** * 连麦视频流布局信息 * * 本地混流高性能渲染模式下,所有视频流布局信息 * @typedef {Object} TRTCStreamLayout * @property {TRTCStreamLayoutMode} layoutMode 布局模式 * @property {Array<TRTCStreamInfo>} userList 非必填,用户视频流布局 */ const TRTCStreamLayout_HACK_JSDOC = null; const initResolutionMap = () => { const map = new Map(); for (const key in trtc_define_1.TRTCVideoResolution) { if (isNaN(Number(key))) { const value = trtc_define_1.TRTCVideoResolution[key]; const tmp = key.split('_'); map.set(value, { width: parseInt(tmp[1]), height: parseInt(tmp[2]), }); } } return map; }; const resolutionMap = initResolutionMap(); /** * @namespace TRTCMediaMixingEvent * @description 目前只支持 `Windows` 操作系统 */ var TRTCMediaMixingEvent; (function (TRTCMediaMixingEvent) { /** * @description 媒体源选中事件 * * @event TRTCMediaMixingEvent#onSourceSelected * @param {TRTCMediaInfo | null} mediaSource 新选中的媒体源数据,取消选中则为`null` */ TRTCMediaMixingEvent["onSourceSelected"] = "onSourceSelected"; /** * @description 媒体源移动事件 * * @event TRTCMediaMixingEvent#onSourceMoved * @param {TRTCMediaInfo} mediaSource 被移动的媒体源 * @param {Rect} rect 移动后的位置、区域数据 */ TRTCMediaMixingEvent["onSourceMoved"] = "onSourceMoved"; /** * @description 媒体源尺寸变化事件 * * @event TRTCMediaMixingEvent#onSourceResized * @param {TRTCMediaInfo} mediaSource 尺寸变化的媒体源 * @param {Rect} rect 尺寸变化后的位置、区域数据 */ TRTCMediaMixingEvent["onSourceResized"] = "onSourceResized"; /** * @description 媒体源鼠标右键事件 * * @event TRTCMediaMixingEvent#onRightButtonClicked * @param {TRTCMediaInfo} mediaSource 鼠标右键点击的媒体源 */ TRTCMediaMixingEvent["onRightButtonClicked"] = "onRightButtonClicked"; /** * @description 错误事件 * * @event TRTCMediaMixingEvent#onError * @param {TRTCMediaMixingErrorCode} errCode 错误码 * @param {String} errorMessage 错误信息 * */ TRTCMediaMixingEvent["onError"] = "onError"; /** * @description 设备插入事件 * * @event TRTCMediaMixingEvent#onSourcePlugged * @param {TRTCPhoneMirrorParam} source 投屏手机状态信息 * @param {String} detail 投屏手机状态变化详情 */ TRTCMediaMixingEvent["onSourcePlugged"] = "onSourcePlugged"; /** * @description 设备链接事件 * * @event TRTCMediaMixingEvent#onSourceConnected * @param {TRTCPhoneMirrorParam} source 投屏手机状态信息 * @param {String} detail 投屏手机状态变化详情 */ TRTCMediaMixingEvent["onSourceConnected"] = "onSourceConnected"; /** * @description 设备断开链接事件 * * @event TRTCMediaMixingEvent#onSourceDisconnected * @param {TRTCPhoneMirrorParam} source 投屏手机状态信息 * @param {String} detail 投屏手机状态变化详情 */ TRTCMediaMixingEvent["onSourceDisconnected"] = "onSourceDisconnected"; /** * @description 设备拔出事件 * * @event TRTCMediaMixingEvent#onSourceUnplugged * @param {TRTCPhoneMirrorParam} source 投屏手机状态信息 * @param {String} detail 投屏手机状态变化详情 */ TRTCMediaMixingEvent["onSourceUnplugged"] = "onSourceUnplugged"; /** * @description 媒体源画面大小发生变化 * * 当输入媒体源的画面大小发生变化时,会通过该接口返回该输入源最新的尺寸,您可以根据该尺寸来动态调整画面的比例。 * @event TRTCMediaMixingEvent#onMediaSourceSizeChanged * @param {TRTCMediaSource} mediasource 媒体源信息 * @param {Object} size 媒体源最新的画面大小 * @param {Number} size.width 媒体源最新宽度 * @param {Number} size.height 媒体源最新高度 */ TRTCMediaMixingEvent["onMediaSourceSizeChanged"] = "onMediaSourceSizeChanged"; })(TRTCMediaMixingEvent = exports.TRTCMediaMixingEvent || (exports.TRTCMediaMixingEvent = {})); /** * 本地混流管理器 */ class TRTCMediaMixingManager { constructor(options) { this.logPrefix = '[TRTCMediaMixingManager]'; this.mediaMixingDesigner = null; this.windowID = 0; this.view = null; this.resizeObserver = null; this.mixingVideoWidth = 0; this.mixingVideoHeight = 0; this.sourceList = []; this.streamLayoutManager = null; this.eventEmitter = new events_1.EventEmitter(); this.handleCppCallbackEvent = this.handleCppCallbackEvent.bind(this); this.nodeMediaMixingPlugin = new NodeTRTCEngine.NodeRemoteMultiSourcePlugin(); this.nodeMediaMixingPlugin.setRemoteMultiSourcePluginCallback(this.handleCppCallbackEvent); this.deviceManager = options.deviceManager; this.nodeTRTCCloud = options.nodeTRTCCloud; this.setMediaServerPath(''); this.publishParams = { videoEncoderParams: { videoResolution: trtc_define_1.TRTCVideoResolution.TRTCVideoResolution_1920_1080, resMode: trtc_define_1.TRTCVideoResolutionMode.TRTCVideoResolutionModeLandscape, videoFps: 30, videoBitrate: 3000, minVideoBitrate: 3000, enableAdjustRes: true, }, canvasColor: 0x0 }; this.onSourceSelected = this.onSourceSelected.bind(this); this.onSourceMoved = this.onSourceMoved.bind(this); this.onSourceResized = this.onSourceResized.bind(this); this.onRightButtonClicked = this.onRightButtonClicked.bind(this); this.onPreviewAreaResize = this.onPreviewAreaResize.bind(this); this.onVisibilityChange = this.onVisibilityChange.bind(this); document.addEventListener('visibilitychange', this.onVisibilityChange); } destroy() { document.removeEventListener('visibilitychange', this.onVisibilityChange); if (this.resizeObserver && this.view) { this.resizeObserver.unobserve(this.view); this.resizeObserver.disconnect(); } this.view = null; this.windowID = 0; this.destroyDesigner(); this.nodeTRTCCloud = null; this.deviceManager = null; this.nodeMediaMixingPlugin = null; this.eventEmitter = null; } /** * @deprecated * @private * 设置视频流预览参数 * * @param windowID {Number|Uint8Array} - 操作系统层的窗口 ID,Electron 下可以通过 Electron API [BrowserWindow.getNativeWindowHandle()]{@link https://www.electronjs.org/docs/latest/api/browser-window#wingetnativewindowhandle} 接口获取 * @param viewOrRegion {HTMLElement | Rect | null} - 本地混流视频显示位置 * - 传入 HTMLElement 元素,则 SDK 将本地混流视频显示在 HTMLElement 元素内,同时支持点击选中、移动、缩放媒体源,支持触发右键菜单事件。HTMLElement 元素必须是块元素。 * - 传入 Rect 显示区域,则 SDK 将本地混流视频显示在 Rect 指定区域内,不支持点击选中、移动、缩放媒体源,不支持触发右键菜单事件,这些功能您可以在 Web 页面中自行实现。 * - 传入 null 则 SDK 将停止显示本地混流视频。 * * @example * // Display in HTML Element * import TRTCCloud from 'trtc-electron-sdk'; * * const trtcCloud = TRTCCloud.getTRTCShareInstance({ * isIPCMode: true * }); * * const mediaMixingManager = trtcCloud.getMediaMixingManager(); * * const windowID = 0; // Use Electron API BrowserWindow.getNativeWindowHandle() * const previewDOM = document.getElementById("preview-local-mixed-media-stream"); * mediaMixingManager.setDisplayParams(windowID, previewDOM); * * @example * // Display in a rectangle section * import TRTCCloud from 'trtc-electron-sdk'; * * const trtcCloud = TRTCCloud.getTRTCShareInstance({ * isIPCMode: true * }); * * const mediaMixingManager = trtcCloud.getMediaMixingManager(); * * const windowID = 0; // Use Electron API BrowserWindow.getNativeWindowHandle() * const previewDOM = document.getElementById("preview-local-mixed-media-stream"); * const domRect = previewDOM.getBoundingClientRect(); * const rect = { * left: domRect.left * window.devicePixelRatio, * right: domRect.right * window.devicePixelRatio, * top: domRect.top * window.devicePixelRatio, * bottom: domRect.bottom * window.devicePixelRatio * }; * * mediaMixingManager.setDisplayParams(windowID, rect); */ setDisplayParams(windowID, viewOrRegion) { logger_1.default.log(`${this.logPrefix}setDisplayParams:`, windowID, viewOrRegion); let newWindowID = 0; if (windowID instanceof Uint8Array) { newWindowID = (0, utils_1.convertUint8ArrayToNumber)(windowID); } else { newWindowID = windowID; } this.windowID = newWindowID; if (newWindowID !== 0 && viewOrRegion !== null) { if (viewOrRegion instanceof HTMLElement) { this.destroyDesigner(); this.view = viewOrRegion; this.setDisplayRect(); this.initResizeObserver(); this.createDesigner(); } else { // Rect this.nodeMediaMixingPlugin.setDisplayParams(newWindowID, viewOrRegion); } } else { this.destroyDesigner(); this.nodeMediaMixingPlugin.setDisplayParams(newWindowID, { left: 0, right: 0, top: 0, bottom: 0 }); } } /** * 设置视频流预览区域 * * @param windowID {Uint8Array} - 操作系统层的窗口 ID,Electron 下可以通过 Electron API [BrowserWindow.getNativeWindowHandle()]{@link https://www.electronjs.org/docs/latest/api/browser-window#wingetnativewindowhandle} 接口获取 * @param viewOrRegion {HTMLElement | Rect | null} - 本地混流视频显示位置 * - 传入 HTMLElement 元素,则 SDK 将本地混流视频显示在 HTMLElement 元素内,同时支持点击选中、移动、缩放媒体源,支持触发右键菜单事件。HTMLElement 元素必须是块元素。 * - 传入 Rect 显示区域,则 SDK 将本地混流视频显示在 Rect 指定区域内,不支持点击选中、移动、缩放媒体源,不支持触发右键菜单事件,这些功能您可以在 Web 页面中自行实现。 * - 传入 null 则 SDK 将停止显示本地混流视频。 * * @example * // Display in HTML Element * import TRTCCloud from 'trtc-electron-sdk'; * * const trtcCloud = TRTCCloud.getTRTCShareInstance({ * isIPCMode: true * }); * * const mediaMixingManager = trtcCloud.getMediaMixingManager(); * * const windowID = 0; // Use Electron API BrowserWindow.getNativeWindowHandle() * const previewDOM = document.getElementById("preview-local-mixed-media-stream"); * mediaMixingManager.bindPreviewArea(windowID, previewDOM); * * @example * // Display in a rectangle section * import TRTCCloud from 'trtc-electron-sdk'; * * const trtcCloud = TRTCCloud.getTRTCShareInstance({ * isIPCMode: true * }); * * const mediaMixingManager = trtcCloud.getMediaMixingManager(); * * const windowID = 0; // Use Electron API BrowserWindow.getNativeWindowHandle() * const previewDOM = document.getElementById("preview-local-mixed-media-stream"); * const domRect = previewDOM.getBoundingClientRect(); * const rect = { * left: domRect.left * window.devicePixelRatio, * right: domRect.right * window.devicePixelRatio, * top: domRect.top * window.devicePixelRatio, * bottom: domRect.bottom * window.devicePixelRatio * }; * * mediaMixingManager.bindPreviewArea(windowID, rect); */ bindPreviewArea(windowID, viewOrRegion) { logger_1.default.log(`${this.logPrefix}bindPreviewArea:`, windowID, viewOrRegion); const realWindowID = (0, utils_1.convertUint8ArrayToNumber)(windowID); this.windowID = realWindowID; if (realWindowID !== 0 && viewOrRegion !== null) { if (viewOrRegion instanceof HTMLElement) { this.destroyDesigner(); this.view = viewOrRegion; this.setDisplayRect(); this.initResizeObserver(); this.createDesigner(); } else { // Rect this.nodeMediaMixingPlugin.setDisplayParams(realWindowID, viewOrRegion); } } else { this.destroyDesigner(); this.nodeMediaMixingPlugin.setDisplayParams(realWindowID, { left: 0, right: 0, top: 0, bottom: 0 }); } } /** * 添加本地混流媒体源 * @param mediaSource {TRTCMediaSource} - 媒体源信息 * @returns {Promise<Rect>} */ addMediaSource(mediaSource) { var _a; return __awaiter(this, void 0, void 0, function* () { logger_1.default.log(`${this.logPrefix}addMediaSource:`, mediaSource); const index = this.findMediaSourceIndex(mediaSource); if (index !== -1) { throw new Error("Media source already existed"); } const newMediaSource = (0, utils_1.safelyParse)(JSON.stringify(mediaSource)); if (newMediaSource.isSelected) { const oldSelectedIndex = this.findSelectedMediaSource(); if (oldSelectedIndex !== -1) { this.unselectMediaSource(oldSelectedIndex); } } const rect = yield this.nodeMediaMixingPlugin.addMediaSource(newMediaSource); (_a = this.mediaMixingDesigner) === null || _a === void 0 ? void 0 : _a.addMedia({ id: `${newMediaSource.sourceType}__${newMediaSource.sourceId}`, rect: newMediaSource.rect, isSelected: newMediaSource.isSelected || false, origin: newMediaSource }); this.sourceList.push(newMediaSource); return rect; }); } /** * 删除本地混流媒体源 * @param mediaSource {TRTCMediaSource} - 媒体源信息 * @returns {Promise<void>} */ removeMediaSource(mediaSource) { var _a; return __awaiter(this, void 0, void 0, function* () { logger_1.default.log(`${this.logPrefix}removeMediaSource:`, mediaSource); const index = this.findMediaSourceIndex(mediaSource); if (index === -1) { throw new Error("Not existing media source to remove"); } yield this.nodeMediaMixingPlugin.removeMediaSource(mediaSource); (_a = this.mediaMixingDesigner) === null || _a === void 0 ? void 0 : _a.removeMedia({ id: `${mediaSource.sourceType}__${mediaSource.sourceId}`, rect: mediaSource.rect, isSelected: mediaSource.isSelected || false, origin: (0, utils_1.safelyParse)(JSON.stringify(mediaSource)) }); this.sourceList.splice(index, 1); }); } /** * 更新本地混流媒体源 * @param mediaSource {TRTCMediaSource} - 媒体源信息 * @returns {Promise<void>} */ updateMediaSource(mediaSource) { var _a; return __awaiter(this, void 0, void 0, function* () { logger_1.default.log(`${this.logPrefix}updateMediaSource:`, mediaSource); const index = this.findMediaSourceIndex(mediaSource); if (index === -1) { throw new Error("Not existing media source to update"); } const newMediaSource = Object.assign(Object.assign({}, this.sourceList[index]), (0, utils_1.safelyParse)(JSON.stringify(mediaSource))); if (mediaSource.isSelected) { const oldSelectedIndex = this.findSelectedMediaSource(); if (oldSelectedIndex !== -1 && oldSelectedIndex !== index) { this.unselectMediaSource(oldSelectedIndex); } } yield this.nodeMediaMixingPlugin.updateMediaSource(newMediaSource); (_a = this.mediaMixingDesigner) === null || _a === void 0 ? void 0 : _a.updateMedia({ id: `${mediaSource.sourceType}__${mediaSource.sourceId}`, rect: mediaSource.rect, isSelected: mediaSource.isSelected || false, origin: (0, utils_1.safelyParse)(JSON.stringify(mediaSource)) }); this.sourceList[index] = newMediaSource; }); } /** * 设置摄像头采集参数 * @param cameraID {string} - 摄像头 ID * @param params {TRTCCameraCaptureParams} - 摄像头采集参数 */ setCameraCaptureParam(cameraID, params) { this.nodeMediaMixingPlugin.setCameraCaptureParam(cameraID, params); } /** * 设置手机投屏参数 * @param phoneMirrorSourceId {string} - 手机投屏媒体源 ID * @param param {TRTCPhoneMirrorParam} - 手机投屏参数,目前只支持优先投屏 */ setPhoneMirrorParam(phoneMirrorSourceId, param) { this.nodeMediaMixingPlugin.setPhoneMirrorParam(phoneMirrorSourceId, param); } /** * 设置屏幕采集参数 * @param screenOrWindowID {String} - 屏幕 ID 或 窗口 ID * @param property {TRTCScreenCaptureProperty} - 屏幕采集属性 */ setScreenCaptureProperty(screenOrWindowID, property) { this.nodeMediaMixingPlugin.setScreenCaptureProperty(screenOrWindowID, property); } /** * 本地混流开始推流 * @returns {Promise<void>} */ startPublish() { return __awaiter(this, void 0, void 0, function* () { logger_1.default.log(`${this.logPrefix}startPublish`); return yield this.nodeMediaMixingPlugin.startPublish(); }); } /** * 本地混流停止推流 * @returns {Promise<void>} */ stopPublish() { return __awaiter(this, void 0, void 0, function* () { logger_1.default.log(`${this.logPrefix}stopPublish`); return yield this.nodeMediaMixingPlugin.stopPublish(); }); } /** * 更新本地混流编码参数 * @param params {TRTCMediaMixingEncParam} - 推流视频编码参数、背景色等参数 * @returns {Promise<void>} */ updatePublishParams(params) { var _a; return __awaiter(this, void 0, void 0, function* () { logger_1.default.log(`${this.logPrefix}updatePublishParams:`, params); this.publishParams = params; this.updateMixingVideoSize(params.videoEncoderParams); (_a = this.mediaMixingDesigner) === null || _a === void 0 ? void 0 : _a.updateOptions({ width: this.mixingVideoWidth, height: this.mixingVideoHeight }); return yield this.nodeMediaMixingPlugin.updatePublishParams(params); }); } /** * 设置连麦视频流布局 * * @param layout {TRTCStreamLayout} - 视频流布局信息 * * @example * // 设置自定义连麦布局 * import TRTCCloud, { TRTCStreamLayout, TRTCStreamLayoutMode, TRTCVideoFillMode } from 'trtc-electron-sdk'; * * const trtcCloud = TRTCCloud.getTRTCShareInstance({ * isIPCMode: true * }); * * const mediaMixingManager = trtcCloud.getMediaMixingManager(); * if (mediaMixingManager) { * const streamLayout: TRTCStreamLayout = { * "layoutMode": TRTCStreamLayoutMode.Custom, * "userList": [ * { * "userId": "", // 本地用户 userId * "rect": { * "left": 0, * "top": 0, * "right": 296, * "bottom": 494 * }, * "fillMode": TRTCVideoFillMode.TRTCVideoFillMode_Fill, // 视频填满显示区 * "zOrder": 0 * }, * { * "userId": "849014", * "rect": { * "left": 296, * "top": 0, * "right": 592, * "bottom": 247 * }, * "fillMode": TRTCVideoFillMode.TRTCVideoFillMode_Fill, * "zOrder": 1 * }, * { * "userId": "p20", * "rect": { * "left": 296, * "top": 247, * "right": 592, * "bottom": 494 * }, * "fillMode": TRTCVideoFillMode.TRTCVideoFillMode_Fill, * "zOrder": 2 * } * ] * }; * mediaMixingManager.setStreamLayout(streamLayout); * } * * @example * // 清除连麦布局,本地混流恢复居中填充显示(默认效果) * import TRTCCloud, { TRTCStreamLayout, TRTCStreamLayoutMode, TRTCVideoFillMode } from 'trtc-electron-sdk'; * * const trtcCloud = TRTCCloud.getTRTCShareInstance({ * isIPCMode: true * }); * * const mediaMixingManager = trtcCloud.getMediaMixingManager(); * if (mediaMixingManager) { * const streamLayout: TRTCStreamLayout = { * "layoutMode": TRTCStreamLayoutMode.None * }; * mediaMixingManager.setStreamLayout(streamLayout); * } */ setStreamLayout(layout) { var _a, _b; logger_1.default.log(`${this.logPrefix}setStreamLayout:`, layout); const context = { container: this.view, mixingVideoSize: { width: this.mixingVideoWidth, height: this.mixingVideoHeight }, mediaMixingDesigner: this.mediaMixingDesigner }; if (!this.streamLayoutManager) { this.streamLayoutManager = StreamLayout_1.StreamLayoutFactory.create(layout.layoutMode, this.nodeMediaMixingPlugin, context); } else if (this.streamLayoutManager.getLayoutMode() !== layout.layoutMode) { this.streamLayoutManager.destroy(); this.streamLayoutManager = StreamLayout_1.StreamLayoutFactory.create(layout.layoutMode, this.nodeMediaMixingPlugin, context); } let transferredUserList = layout.userList; if (this.view) { const viewRect = this.view.getBoundingClientRect(); if ((_a = layout.userList) === null || _a === void 0 ? void 0 : _a.length) { const localUser = layout.userList.filter(user => user.userId === constant_1.LOCAL_USER_ID)[0]; if (localUser === null || localUser === void 0 ? void 0 : localUser.rect) { const workingArea = localUser.rect; (_b = this.mediaMixingDesigner) === null || _b === void 0 ? void 0 : _b.setWorkingArea({ left: workingArea.left / viewRect.width, top: workingArea.top / viewRect.height, right: workingArea.right / viewRect.width, bottom: workingArea.bottom / viewRect.height, }, localUser.fillMode); } transferredUserList = layout.userList.map((user) => { if (user.rect) { return Object.assign(Object.assign({}, user), { rect: { left: user.rect.left * window.devicePixelRatio, right: user.rect.right * window.devicePixelRatio, top: user.rect.top * window.devicePixelRatio, bottom: user.rect.bottom * window.devicePixelRatio, } }); } else { return user; } }); logger_1.default.log(`${this.logPrefix}setStreamLayout transfer layout to pixel unit:`, JSON.stringify(layout)); } } else { logger_1.default.warn(`${this.logPrefix}setStreamLayout: view is null, no container HTML element. User control the layout manually in device pixel unit.`); } this.streamLayoutManager.setLayout(Object.assign(Object.assign({}, layout), { userList: transferredUserList })); } /** * @private * 设置服务进程程序路径 * * 开发模式,默认路径:node_modules\\trtc-electron-sdk\\build\\Release\\liteav_media_server.exe * 构建模式,默认路径:${resourcesPath}\\liteav_media_server.exe * * 如果用户应用有特殊配置,默认路径可能找不到服务进程程序,需要自行传入路径。 * * @param path {string} - 服务进程程序路径 * * @returns {Promise<void>} */ setMediaServerPath(path) { return __awaiter(this, void 0, void 0, function* () { let serverPath = ''; if (path && path.trim() !== '') { serverPath = path; } else { const { resourcesPath, execPath } = globalThis.process; if (execPath.endsWith('electron.exe')) { serverPath = '..\\..\\..\\..\\node_modules\\trtc-electron-sdk\\build\\Release\\liteav_media_server.exe'; } else { serverPath = `${resourcesPath}\\liteav_media_server.exe`; } } logger_1.default.log(`${this.logPrefix} server path:${serverPath}`); return yield this.nodeMediaMixingPlugin.setMediaServerPath(serverPath); }); } /** * 注册事件监听 * * @param event {TRTCMediaMixingEvent} - 事件名称 * @param func {Function} - 事件回调函数 */ on(event, func) { var _a; (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.on(event, func); } /** * 取消事件监听 * * @param event {TRTCMediaMixingEvent} - 事件名 * @param func {Function} - 事件回调函数 */ off(event, func) { var _a; (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.off(event, func); } initResizeObserver() { if (this.view) { this.resizeObserver = new ResizeObserver(this.onPreviewAreaResize); this.resizeObserver.observe(this.view); } } onPreviewAreaResize(entries) { logger_1.default.log(`${this.logPrefix}onPreviewAreaResize:`, entries); for (const entry of entries) { if (this.windowID && entry.target === this.view) { this.setDisplayRect(); break; } } } onVisibilityChange() { logger_1.default.log(`${this.logPrefix}onVisibilityChange document.hidden:${document.hidden}`); if (document.hidden) { this.nodeMediaMixingPlugin.setDisplayParams(this.windowID, { left: 0, right: 0, top: 0, bottom: 0 }); } else { this.setDisplayRect(); } } setDisplayRect() { if (this.windowID && this.view) { const clientRect = this.view.getBoundingClientRect(); const rect = { left: clientRect.left * window.devicePixelRatio, right: clientRect.right * window.devicePixelRatio, top: clientRect.top * window.devicePixelRatio, bottom: clientRect.bottom * window.devicePixelRatio, }; this.nodeMediaMixingPlugin.setDisplayParams(this.windowID, rect); } } createDesigner() { if (this.view) { this.updateMixingVideoSize(this.publishParams.videoEncoderParams); this.mediaMixingDesigner = new index_1.default({ view: this.view, width: this.mixingVideoWidth, height: this.mixingVideoHeight, canExceedContainer: true, }); this.listenDesignerEvent(); } } destroyDesigner() { var _a; if (this.mediaMixingDesigner) { this.unlistenDesignerEvent(); (_a = this.mediaMixingDesigner) === null || _a === void 0 ? void 0 : _a.destroy(); this.mediaMixingDesigner = null; } } updateMixingVideoSize(params) { if (resolutionMap.has(params.videoResolution)) { const { width, height } = resolutionMap.get(params.videoResolution); if (params.resMode === trtc_define_1.TRTCVideoResolutionMode.TRTCVideoResolutionModeLandscape) { this.mixingVideoWidth = width; this.mixingVideoHeight = height; } else { this.mixingVideoWidth = height; this.mixingVideoHeight = width; } } } listenDesignerEvent() { var _a, _b, _c, _d, _e; (_a = this.mediaMixingDesigner) === null || _a === void 0 ? void 0 : _a.on('onError', this.onError); (_b = this.mediaMixingDesigner) === null || _b === void 0 ? void 0 : _b.on('onSourceSelected', this.onSourceSelected); (_c = this.mediaMixingDesigner) === null || _c === void 0 ? void 0 : _c.on('onSourceMoved', this.onSourceMoved); (_d = this.mediaMixingDesigner) === null || _d === void 0 ? void 0 : _d.on('onSourceResized', this.onSourceResized); (_e = this.mediaMixingDesigner) === null || _e === void 0 ? void 0 : _e.on('onRightButtonClicked', this.onRightButtonClicked); } unlistenDesignerEvent() { var _a, _b, _c, _d, _e; (_a = this.mediaMixingDesigner) === null || _a === void 0 ? void 0 : _a.off('onError', this.onError); (_b = this.mediaMixingDesigner) === null || _b === void 0 ? void 0 : _b.off('onSourceSelected', this.onSourceSelected); (_c = this.mediaMixingDesigner) === null || _c === void 0 ? void 0 : _c.off('onSourceMoved', this.onSourceMoved); (_d = this.mediaMixingDesigner) === null || _d === void 0 ? void 0 : _d.off('onSourceResized', this.onSourceResized); (_e = this.mediaMixingDesigner) === null || _e === void 0 ? void 0 : _e.off('onRightButtonClicked', this.onRightButtonClicked); } onError(errorCode, errorMessage) { logger_1.default.log(`${this.logPrefix}onError:`, errorCode, errorMessage); } onSourceSelected(media) { var _a; logger_1.default.log(`${this.logPrefix}onSourceSelected:`, media); if (media === null || media === void 0 ? void 0 : media.origin) { this.updateMediaSource(Object.assign(Object.assign({}, media.origin), { isSelected: true })); } else { const selected = this.sourceList.find(item => item.isSelected); if (selected) { this.updateMediaSource(Object.assign(Object.assign({}, selected), { isSelected: false })); } } (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.emit('onSourceSelected', media ? media.origin : null); } onSourceMoved(media, rect) { var _a; logger_1.default.log(`${this.logPrefix}onSourceMoved:`, media, rect); if (media === null || media === void 0 ? void 0 : media.origin) { this.updateMediaSource(Object.assign(Object.assign({}, media.origin), { rect })); } (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.emit('onSourceMoved', media.origin, rect); } onSourceResized(media, rect) { var _a; logger_1.default.log(`${this.logPrefix}onSourceResized:`, media, rect); if (media === null || media === void 0 ? void 0 : media.origin) { this.updateMediaSource(Object.assign(Object.assign({}, media.origin), { rect })); } (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.emit('onSourceResized', media.origin, rect); } onRightButtonClicked(media) { var _a; logger_1.default.log(`${this.logPrefix}onRightButtonClicked:`, media); (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.emit('onRightButtonClicked', media.origin); } isSameMediaSource(mediaSource1, mediaSource2) { return mediaSource1.sourceId === mediaSource2.sourceId && mediaSource1.sourceType === mediaSource2.sourceType; } isMediaSourceExisted(mediaSource) { return this.sourceList.some(item => this.isSameMediaSource(item, mediaSource)); } findMediaSourceIndex(mediaSource) { return this.sourceList.findIndex(item => item.sourceId === mediaSource.sourceId && item.sourceType === mediaSource.sourceType); } findSelectedMediaSource() { let index = -1; const length = this.sourceList.length; for (let i = 0; i < length; i++) { if (this.sourceList[i].isSelected) { index = i; break; } } return index; } unselectMediaSource(index) { var _a; return __awaiter(this, void 0, void 0, function* () { this.sourceList[index].isSelected = false; yield this.nodeMediaMixingPlugin.updateMediaSource(this.sourceList[index]); (_a = this.mediaMixingDesigner) === null || _a === void 0 ? void 0 : _a.updateMedia({ id: `${this.sourceList[index].sourceType}__${this.sourceList[index].sourceId}`, rect: this.sourceList[index].rect, isSelected: this.sourceList[index].isSelected || false, origin: (0, utils_1.safelyParse)(JSON.stringify(this.sourceList[index])) }); }); } handleCppCallbackEvent(args) { logger_1.default.log(`${this.logPrefix} event:`, args); const key = args[0]; const data = args[1]; // To do: 待完善 switch (key) { case TRTCMediaMixingEvent.onMediaSourceSizeChanged: this.onMediaSourceSizeChanged(data); break; case "onPhoneMirrorSourceChanged": this.onPhoneMirrorSourceChanged(data); break; default: break; } } onMediaSourceSizeChanged(data) { var _a; const index = this.findMediaSourceIndex(data); if (index !== -1) { let mediaSource = this.sourceList[index]; mediaSource = (0, utils_1.safelyParse)(JSON.stringify(mediaSource)); const size = { width: data.width, height: data.height, }; (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.emit(TRTCMediaMixingEvent.onMediaSourceSizeChanged, mediaSource, size); } else { logger_1.default.warn(`${this.logPrefix}onMediaSourceSizeChanged media source not existed:`, data); } } onPhoneMirrorSourceChanged(data) { var _a, _b, _c, _d; const { phoneMirrorSource, state } = data; if (state !== undefined && state !== null) { switch (state) { case 0: (_a = this.eventEmitter) === null || _a === void 0 ? void 0 : _a.emit(TRTCMediaMixingEvent.onSourcePlugged, phoneMirrorSource, 'Plugged'); break; case 1: (_b = this.eventEmitter) === null || _b === void 0 ? void 0 : _b.emit(TRTCMediaMixingEvent.onSourceConnected, phoneMirrorSource, 'Connected'); break; case 2: (_c = this.eventEmitter) === null || _c === void 0 ? void 0 : _c.emit(TRTCMediaMixingEvent.onSourceDisconnected, phoneMirrorSource, 'Disconnected'); break; case 3: (_d = this.eventEmitter) === null || _d === void 0 ? void 0 : _d.emit(TRTCMediaMixingEvent.onSourceUnplugged, phoneMirrorSource, 'Unplugged'); break; default: logger_1.default.warn(`${this.logPrefix}onPhoneMirrorSourceChanged invalid data:`, data); break; } } else { logger_1.default.warn(`${this.logPrefix}onPhoneMirrorSourceChanged invalid data:`, data); } } // ****** 这一部分接口暴露不合理,暂时不对暴露在 API 文档上 **************/ startCameraDeviceTest(windowID, rect) { var _a; return ((_a = this.deviceManager) === null || _a === void 0 ? void 0 : _a.startCameraDeviceTest(windowID, rect)) || Promise.reject(); } stopCameraDeviceTest() { var _a; return ((_a = this.deviceManager) === null || _a === void 0 ? void 0 : _a.stopCameraDeviceTest()) || Promise.reject(); } setCameraTestRenderMirror(mirror) { var _a; (_a = this.deviceManager) === null || _a === void 0 ? void 0 : _a.setCameraTestRenderMirror(mirror); } setCameraTestDeviceId(cameraId) { var _a; (_a = this.deviceManager) === null || _a === void 0 ? void 0 : _a.setCameraTestDeviceId(cameraId); } setCameraTestResolution(width, height) { var _a; (_a = this.deviceManager) === null || _a === void 0 ? void 0 : _a.setCameraTestResolution(width, height); } setCameraTestVideoPluginPath(path) { var _a; (_a = this.deviceManager) === null || _a === void 0 ? void 0 : _a.setCameraTestVideoPluginPath(path); } setCameraTestVideoPluginParameter(params) { var _a; (_a = this.deviceManager) === null || _a === void 0 ? void 0 : _a.setCameraTestVideoPluginParameter(params); } } exports.TRTCMediaMixingManager = TRTCMediaMixingManager; exports.default = TRTCMediaMixingManager;