trtc-electron-sdk
Version:
trtc electron sdk
1,004 lines (1,003 loc) • 44.7 kB
JavaScript
"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;