trtc-electron-sdk
Version:
trtc electron sdk
753 lines (752 loc) • 31.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TRTCVodPlayer = exports.TRTCVodPlayerEvent = void 0;
const events_1 = require("events");
const trtc_define_1 = require("./trtc_define");
const video_render_1 = require("./Live/video-render");
const logger_1 = require("./logger");
const NodeTRTCEngine = require('../build/Release/trtc_electron_sdk.node');
/////////////////////////////////////////////////////////////////////////////////
//
// TRTCVodPlayer 相关类型定义
//
/////////////////////////////////////////////////////////////////////////////////
/**
* @namespace TRTCVodPlayerEvent
* @description 点播播放器(VOD Player)事件
*/
var TRTCVodPlayerEvent;
(function (TRTCVodPlayerEvent) {
/**
* @description 多媒体文件开始播放事件
*
* @event TRTCVodPlayerEvent#onVodPlayerStarted
* @param {Number} msLength 多媒体文件总长度,单位毫秒
*/
TRTCVodPlayerEvent["onVodPlayerStarted"] = "onVodPlayerStarted";
/**
* @description 播放器就绪事件
*
* 调用 `preload()` 完成后触发,或 `switchSource()` 切换成功后触发。
*
* @event TRTCVodPlayerEvent#onVodPlayerLoaded
* @param {Number} durationMs 视频总时长,单位毫秒
* @param {Number} width 视频宽度(像素),纯音频文件时为 0
* @param {Number} height 视频高度(像素),纯音频文件时为 0
*/
TRTCVodPlayerEvent["onVodPlayerLoaded"] = "onVodPlayerLoaded";
/**
* @description 多媒体文件播放进度改变事件
*
* @event TRTCVodPlayerEvent#onVodPlayerProgress
* @param {Number} msPos 多媒体文件当前播放进度,单位毫秒
*/
TRTCVodPlayerEvent["onVodPlayerProgress"] = "onVodPlayerProgress";
/**
* @description 多媒体文件播放暂停事件
*
* @event TRTCVodPlayerEvent#onVodPlayerPaused
*/
TRTCVodPlayerEvent["onVodPlayerPaused"] = "onVodPlayerPaused";
/**
* @description 多媒体文件播放恢复事件
*
* @event TRTCVodPlayerEvent#onVodPlayerResumed
*/
TRTCVodPlayerEvent["onVodPlayerResumed"] = "onVodPlayerResumed";
/**
* @description 多媒体文件播放停止事件
*
* @event TRTCVodPlayerEvent#onVodPlayerStopped
* @param {Number} reason 停止原因
* - 0:用户主动停止;
* - 1:文件播放完;
* - 2:视频断流。
*/
TRTCVodPlayerEvent["onVodPlayerStopped"] = "onVodPlayerStopped";
/**
* @description 多媒体文件播放出错事件
*
* @event TRTCVodPlayerEvent#onVodPlayerError
* @param {Number} error 错误码
* - -6001:未知错误;
* - -6002:通用错误;
* - -6003:解封装失败;
* - -6005:解封装超时;
* - -6006:视频解码错误;
* - -6007:音频解码错误;
* - -6009:视频渲染错误。
*/
TRTCVodPlayerEvent["onVodPlayerError"] = "onVodPlayerError";
})(TRTCVodPlayerEvent = exports.TRTCVodPlayerEvent || (exports.TRTCVodPlayerEvent = {}));
/**
*
* 腾讯云点播播放器
*
* 用于播放本地或在线的音视频文件,支持视频渲染、音量控制、
* 进度控制、TRTC 推流等功能。
*
* @version v13.3.800
*
* @example
* import { TRTCVodPlayer, TRTCVodPlayerEvent } from 'trtc-electron-sdk';
*
* const player = TRTCVodPlayer.createVodPlayer('/path/to/video.mp4', false);
* player.on(TRTCVodPlayerEvent.onVodPlayerStarted, (msLength) => {
* console.log('播放开始,总时长:', msLength);
* });
* player.setView(document.getElementById('video-container')!);
* player.start();
*
* // 使用完毕后销毁
* TRTCVodPlayer.releaseVodPlayer(player);
*/
class TRTCVodPlayer {
// ─── 构造函数 ───────────────────────────────────────────────
constructor(mediaFilePath, repeat = false) {
this.logger = new logger_1.Logger('TRTCVodPlayer');
this.eventEmitter = new events_1.EventEmitter();
this._dataRenderCallback = this._dataRenderCallback.bind(this);
this.nativeVodPlayer = new NodeTRTCEngine.NodeVodPlayer(mediaFilePath, repeat);
this.mediaFilePath = mediaFilePath;
this.isStarted = false;
this.isDestroyed = false;
this.isRenderPrepared = false;
this._setBufferRetryCount = 0;
this.videoRender = null;
this._createVideoRender();
this._initEventCallback();
}
/**
* 判断当前 native 构建是否启用了 VOD 播放器
*
* C++ 侧 VOD 模块为条件编译(`TRTC_VOD_PLAYER` 宏),未启用时 `NodeTRTCEngine.NodeVodPlayer`
* 不存在。调用方在 {@link createVodPlayer} 前应先用本方法探测,否则会拿到一个
* `Error: [TRTCVodPlayer][ERR_NO_VOD_SUPPORT] ...` 异常。
*
* @returns true 表示当前构建支持 VOD;false 表示未启用
*/
static isSupported() {
return TRTCVodPlayer.VOD_SUPPORTED;
}
/**
* 创建 TRTCVodPlayer 实例
*
* @param mediaFilePath - 媒体文件路径(本地绝对路径或在线 URL),必填且必须是非空字符串
* @param repeat - 是否循环播放,默认 false
* @returns TRTCVodPlayer 实例
*
* @throws {Error} 当前 native 构建未启用 VOD 时抛出,错误信息以
* `[TRTCVodPlayer][ERR_NO_VOD_SUPPORT]` 起始,便于日志聚合与告警
* (请先用 {@link isSupported} 探测)
* @throws {TypeError} 当 `mediaFilePath` 不是非空字符串时抛出
*/
static createVodPlayer(mediaFilePath, repeat = false) {
if (!TRTCVodPlayer.isSupported()) {
// C++ 层 VOD 模块为条件编译;未启用构建下 NodeTRTCEngine.NodeVodPlayer 为 undefined,
// 直接 new 会得到 "NodeVodPlayer is not a constructor" 这种内部堆栈。
// 这里提前抛出业务级错误,明确告知调用方"当前构建不支持 VOD,应先探测再使用"。
// 错误信息前缀 [TRTCVodPlayer][ERR_NO_VOD_SUPPORT] 是稳定标签,调用方可用于
// 日志过滤、告警聚合,请勿改动该前缀。
throw new Error('[TRTCVodPlayer][ERR_NO_VOD_SUPPORT] createVodPlayer: ' +
'current native build has no VOD support. ' +
'请先通过 TRTCVodPlayer.isSupported() 探测再调用。');
}
if (typeof mediaFilePath !== 'string' || mediaFilePath.length === 0) {
throw new TypeError(`TRTCVodPlayer.createVodPlayer: mediaFilePath must be a non-empty string, got: ${JSON.stringify(mediaFilePath)}`);
}
return new TRTCVodPlayer(mediaFilePath, repeat);
}
/**
* 销毁 TRTCVodPlayer 实例,释放相关资源
*
* @param player - 待销毁的 TRTCVodPlayer 实例
*/
static releaseVodPlayer(player) {
player._destroy();
}
/**
* 监听 TRTCVodPlayer 事件
*
* @param {TRTCVodPlayerEvent} event - 事件名称
* @param {Function} listener - 事件回调函数,参数随事件类型不同而不同
*/
on(event, listener) {
this.eventEmitter.on(event, listener);
return this;
}
/**
* 取消监听 TRTCVodPlayer 事件
*
* @param {TRTCVodPlayerEvent} event - 事件名称
* @param {Function} listener - 事件回调函数,必须与 on 时传入的为同一引用
*/
off(event, listener) {
this.eventEmitter.off(event, listener);
return this;
}
/**
* 设置视频渲染的 DOM 容器
*
* @param view - HTML 元素,用于承载视频画面;传 `null` 时仅解除当前 view 关联,
* 播放/渲染管线不受影响(可后续再次 `setView(newDom)` 切换容器)。
*
* @note 若调用方需要彻底停止渲染,请使用 {@link stop} 或 {@link destroy}。
*/
setView(view) {
var _a, _b;
if (this.isDestroyed) {
this.logger.warn('setView: player already destroyed');
return;
}
if (view === null) {
// 仅解除 view 关联,不下发 destroyRender / removeDataRenderCallback
// 否则后续 setView(newDom) 还需要重建整条渲染管线
this.logger.info('setView: view is null, detach current view');
(_a = this.videoRender) === null || _a === void 0 ? void 0 : _a.setRenderView(null);
return;
}
this.logger.info(`setView view: ${view}`);
(_b = this.videoRender) === null || _b === void 0 ? void 0 : _b.setRenderView(view);
}
/**
* 预加载多媒体文件
*
* 预加载完成后会触发 {@link TRTCVodPlayerEvent.onVodPlayerLoaded} 事件,事件参数携带
* 媒体信息(时长、宽高);同时 C++ 层会产生首帧并推送到 JS 层的视频渲染管线,
* 在 view 上展示首帧画面。
*
* @example
* player.on(TRTCVodPlayerEvent.onVodPlayerLoaded, (durationMs, width, height) => {
* console.log('预加载完成:', durationMs, width, height);
* player.start();
* });
* player.preload();
*
* @note 可以先调用 preload 再调用 {@link start} 播放,也可以不调用 preload 直接调用 {@link start}。
* @note 若已经调用过 {@link start},再调用 preload 将无效。
*/
preload() {
var _a;
if (this.isDestroyed) {
this.logger.warn('preload: player already destroyed');
return;
}
if (this.isStarted) {
this.logger.warn('preload: already started, ignored');
return;
}
this.logger.info(`preload mediaFile: ${this.mediaFilePath}`);
this._prepareRender();
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.preload();
}
/**
* 开始播放
*
* 支持的视频格式:mp4、mkv、mov。
* 支持的音频格式:opus、aac。
*
* 重复调用会被忽略。
*
* @note 可以先调用 {@link preload} 预加载完成后再调用 start,也可以跳过 preload 直接调用 start。
*/
start() {
var _a;
if (this.isDestroyed) {
this.logger.warn('start: player already destroyed');
return;
}
this.logger.info(`start mediaFile: ${this.mediaFilePath}`);
if (this.isStarted) {
this.logger.warn('start: already started, ignored');
return;
}
this._prepareRender();
this.attachTRTC();
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.start();
this.isStarted = true;
}
/**
* 停止播放
*/
stop() {
var _a;
if (this.isDestroyed) {
this.logger.warn('stop: player already destroyed');
return;
}
this.logger.info('stop');
if (!this.isStarted && !this.isRenderPrepared) {
return;
}
if (this.isStarted) {
this.isStarted = false;
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.stop();
this.detachTRTC();
}
this._teardownRender();
}
/**
* 暂停播放
*
* @note 仅在 {@link start} 之后调用才有效;未播放时调用会被忽略并打 warn 日志。
*/
pause() {
var _a;
if (this.isDestroyed) {
this.logger.warn('pause: player already destroyed');
return;
}
if (!this.isStarted) {
this.logger.warn('pause: player not started, ignored');
return;
}
this.logger.info('pause');
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.pause();
}
/**
* 恢复播放
*
* @note 仅在 {@link start} 之后调用才有效;未播放时调用会被忽略并打 warn 日志。
*/
resume() {
var _a;
if (this.isDestroyed) {
this.logger.warn('resume: player already destroyed');
return;
}
if (!this.isStarted) {
this.logger.warn('resume: player not started, ignored');
return;
}
this.logger.info('resume');
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.resume();
}
/**
* 跳转到指定播放位置(seek)
*
* @param msPos - 目标位置,单位毫秒
*
* @note 调用前必须先 {@link preload} 或 {@link start},否则会被忽略并打 warn 日志。
*/
seek(msPos) {
var _a;
if (this.isDestroyed) {
this.logger.warn('seek: player already destroyed');
return;
}
if (!this.isStarted && !this.isRenderPrepared) {
this.logger.warn(`seek: player not preloaded or started, ignored (msPos=${msPos})`);
return;
}
this.logger.info(`seek msPos: ${msPos}`);
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.seek(msPos);
}
/**
* 切换当前播放的媒体文件
*
* 底层实现等价于 `stop + 加载新源`,**切换后是否自动播放取决于切换前的状态**:
* - 切换前在播放中:底层会自动 Started 新源,业务方无需再调 {@link start};
* - 切换前在暂停中:底层只 Loaded 新源不自动播放,业务方需在
* {@link TRTCVodPlayerEvent.onVodPlayerLoaded} 之后调 {@link start} 才会播。
*
* 切换过程中会按顺序触发:
* `onVodPlayerStopped` → `onVodPlayerLoaded`(新源信息) →
* `onVodPlayerStarted`(仅"切换前在播放中"路径才会有)。
*
* @param newMediaFile - 新的媒体文件路径或 URL
*
* @note 切换过程中调用 {@link stop} 会取消切换并停止当前播放。
* @note 调用前必须先 {@link preload} 或 {@link start},否则会被忽略并打 warn 日志。
* @note 传入空串会被忽略并打 warn 日志(C++ 层亦有同等保护)。
*/
switchSource(newMediaFile) {
var _a;
if (this.isDestroyed) {
this.logger.warn('switchSource: player already destroyed');
return;
}
if (!newMediaFile) {
this.logger.warn('switchSource: newMediaFile is empty, ignored');
return;
}
if (!this.isStarted && !this.isRenderPrepared) {
this.logger.warn(`switchSource: player not preloaded or started, ignored (newMediaFile=${newMediaFile})`);
return;
}
this.logger.info(`switchSource newMediaFile: ${newMediaFile}`);
this.mediaFilePath = newMediaFile;
// 切换源后如果出现 buffer 尺寸变化,需要让 _dataRenderCallback 退避计数重置
this._setBufferRetryCount = 0;
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.switchSource(newMediaFile);
}
/**
* 获取视频总时长
*
* @returns 总时长,单位毫秒;播放器已销毁时返回 0。
*
* @note 跨平台口径已统一为 `int64_t`(即使 Windows x64 下底层 `long` 是 32 位,
* binding 层也会显式扩到 64 位避免 ~49 天回绕);JS `number` 安全整数上限 2^53,
* 业务上点播时长不会触及。
*/
getDuration() {
var _a, _b;
if (this.isDestroyed) {
this.logger.warn('getDuration: player already destroyed');
return 0;
}
return (_b = (_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.getDuration()) !== null && _b !== void 0 ? _b : 0;
}
/**
* 获取视频宽度
*
* @returns 视频宽度(像素);纯音频文件或播放器已销毁时返回 0
*/
getWidth() {
var _a, _b;
if (this.isDestroyed) {
this.logger.warn('getWidth: player already destroyed');
return 0;
}
return (_b = (_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.getWidth()) !== null && _b !== void 0 ? _b : 0;
}
/**
* 获取视频高度
*
* @returns 视频高度(像素);纯音频文件或播放器已销毁时返回 0
*/
getHeight() {
var _a, _b;
if (this.isDestroyed) {
this.logger.warn('getHeight: player already destroyed');
return 0;
}
return (_b = (_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.getHeight()) !== null && _b !== void 0 ? _b : 0;
}
/**
* 设置画面顺时针旋转角度
*
* 仅对窗口渲染模式生效。
*
* @param rotation - 旋转角度,参考 {@link TRTCVideoRotation} 枚举,
* 支持 TRTCVideoRotation0 / 90 / 180 / 270,默认 TRTCVideoRotation0
*/
setRenderRotation(rotation) {
var _a;
if (this.isDestroyed) {
this.logger.warn('setRenderRotation: player already destroyed');
return;
}
this.logger.info(`setRenderRotation rotation: ${rotation}`);
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.setRenderRotation(rotation);
}
/**
* 设置画面填充模式
*
* 仅对窗口渲染模式生效。
*
* @param mode - 填充模式,参考 {@link TRTCVideoFillMode} 枚举:
* - Fill:画面填满窗口,可能被拉伸或裁剪
* - Fit:画面按比例适配窗口,可能出现黑边(默认值)
*/
setFillMode(mode) {
var _a, _b;
if (this.isDestroyed) {
this.logger.warn('setFillMode: player already destroyed');
return;
}
this.logger.info(`setFillMode mode: ${mode}`);
(_a = this.videoRender) === null || _a === void 0 ? void 0 : _a.setVideoFillMode(mode);
(_b = this.nativeVodPlayer) === null || _b === void 0 ? void 0 : _b.setFillMode(mode);
}
/**
* 设置画面镜像
*
* @param mirror - 是否镜像
*/
setMirror(mirror) {
var _a;
if (this.isDestroyed) {
this.logger.warn('setMirror: player already destroyed');
return;
}
this.logger.info(`setMirror mirror: ${mirror}`);
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.setMirror(mirror);
}
/**
* 静音 / 取消静音
*
* @param mute - 是否静音
*/
mute(mute) {
var _a;
if (this.isDestroyed) {
this.logger.warn('mute: player already destroyed');
return;
}
this.logger.info(`mute: ${mute}`);
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.mute(mute);
}
/**
* 设置播放音量
*
* @param volume - 音量大小,取值范围 [0, 150],100 为原始音量,默认值 100
*/
setVolume(volume) {
var _a;
if (this.isDestroyed) {
this.logger.warn('setVolume: player already destroyed');
return;
}
this.logger.info(`setVolume volume: ${volume}`);
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.setVolume(volume);
}
/**
* 向已关联的 TRTC 实例推送视频流
*
* @note 推流前播放器需已关联到 TRTC 实例(内部由 {@link start} 自动处理)。
*/
publishVideo() {
var _a;
if (this.isDestroyed) {
this.logger.warn('publishVideo: player already destroyed');
return;
}
this.logger.info('publishVideo');
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.publishVideo();
}
/**
* 向已关联的 TRTC 实例推送音频流
*
* @note 绑定后音频播放由 TRTC 接管。
*/
publishAudio() {
var _a;
if (this.isDestroyed) {
this.logger.warn('publishAudio: player already destroyed');
return;
}
this.logger.info('publishAudio');
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.publishAudio();
}
/**
* 停止向已关联的 TRTC 实例推送视频流
*/
unpublishVideo() {
var _a;
if (this.isDestroyed) {
this.logger.warn('unpublishVideo: player already destroyed');
return;
}
this.logger.info('unpublishVideo');
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.unpublishVideo();
}
/**
* 停止向已关联的 TRTC 实例推送音频流
*/
unpublishAudio() {
var _a;
if (this.isDestroyed) {
this.logger.warn('unpublishAudio: player already destroyed');
return;
}
this.logger.info('unpublishAudio');
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.unpublishAudio();
}
// ─── 内部方法 ───────────────────────────────────────────────
/**
* @private
* 销毁播放器,释放所有资源
*
* 包括:解绑事件监听、停止播放、销毁渲染器、清理 native 回调。
*
* 注意:调用本方法后不再向业务方派发任何事件。即便 `stop()` 内部会异步触发
* `onVodPlayerStopped`,该事件也不会送达,因为监听器已在第一步被清空。
* 这是 `releaseVodPlayer` 的自然语义(资源释放后不再产生副作用)。
*/
_destroy() {
var _a, _b;
if (this.isDestroyed) {
this.logger.warn('destroy: player already destroyed');
return;
}
this.logger.info('destroy');
// 先清监听器,确保后续 stop 流程里通过 setImmediate 排队的事件(如 onVodPlayerStopped)
// 不会再回到业务方。此时 isDestroyed 尚为 false,后续 stop/detachTRTC 仍能正常走完。
this.eventEmitter.removeAllListeners();
this.stop();
this.isDestroyed = true;
this._destroyVideoRender();
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.setEventCallback(null);
(_b = this.nativeVodPlayer) === null || _b === void 0 ? void 0 : _b.setDataCallback(null);
this.nativeVodPlayer = null;
}
/**
* @private
* 将点播播放器关联到 TRTC 实例(用于辅助流推流,绑定后音频播放由 TRTC 接管)
*/
attachTRTC() {
var _a;
if (this.isDestroyed) {
this.logger.warn('attachTRTC: player already destroyed');
return;
}
this.logger.info('attachTRTC');
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.attachTRTC();
}
/**
* @private
* 将点播播放器从 TRTC 实例分离
*/
detachTRTC() {
var _a;
if (this.isDestroyed) {
this.logger.warn('detachTRTC: player already destroyed');
return;
}
this.logger.info('detachTRTC');
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.detachTRTC();
}
/**
* @private
* 类型安全地发射事件,参数类型由 `TRTCVodPlayerEventMap` 推导。
* 通过 `setImmediate` 让 emit 异步化,避免在 N-API 回调栈中触发用户代码。
*/
fire(event, ...args) {
setImmediate(() => {
this.eventEmitter.emit(event, ...args);
});
}
_createVideoRender() {
if (this.videoRender !== null) {
return;
}
this.videoRender = new video_render_1.VideoRender();
this.videoRender.setVideoPixelFormat(trtc_define_1.TRTCVideoPixelFormat.TRTCVideoPixelFormat_BGRA32);
}
_destroyVideoRender() {
this._teardownRender();
if (this.videoRender) {
this.videoRender.destroy();
this.videoRender = null;
}
}
_prepareRender() {
var _a;
if (this.isRenderPrepared) {
return;
}
(_a = this.videoRender) === null || _a === void 0 ? void 0 : _a.createRender();
this._setVideoRenderBuffer();
this._addDataRenderCallback();
this.isRenderPrepared = true;
this._setBufferRetryCount = 0;
}
_teardownRender() {
var _a;
if (!this.isRenderPrepared) {
return;
}
(_a = this.videoRender) === null || _a === void 0 ? void 0 : _a.destroyRender();
this._removeDataRenderCallback();
this.isRenderPrepared = false;
this._setBufferRetryCount = 0;
}
_setVideoRenderBuffer() {
var _a;
if (this.videoRender !== null) {
const videoBuffer = this.videoRender.getVideoBuffer();
this.logger.info(`_setVideoRenderBuffer: userId=${videoBuffer.userId}, width=${videoBuffer.width}, height=${videoBuffer.height}, bufferId=${videoBuffer.id}, pixelFormat=${videoBuffer.pixelFormat}`);
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.setVideoBuffer(videoBuffer.userId || '', videoBuffer.buffer, videoBuffer.streamType, videoBuffer.width, videoBuffer.height, videoBuffer.pixelFormat, videoBuffer.id);
}
}
_addDataRenderCallback() {
var _a;
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.setDataCallback(this._dataRenderCallback);
}
_removeDataRenderCallback() {
var _a;
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.setDataCallback(null);
}
_dataRenderCallback(args) {
if (!this.videoRender) {
this.logger.warn('_dataRenderCallback: videoRender is null, frame dropped');
return;
}
const [userId, type, width, height, timestamp, rotation, valid, bufferId] = args;
const result = this.videoRender.renderVideoData(userId, type, width, height, timestamp, rotation, valid, bufferId);
if (result) {
// 渲染成功,重置重试计数,避免长期累计后误触发降级
if (this._setBufferRetryCount !== 0) {
this._setBufferRetryCount = 0;
}
return;
}
// renderVideoData 返回 false 通常意味着 buffer 尺寸 / 格式不匹配,需要重新 setVideoBuffer。
// 但若底层持续失败(例如尺寸一直对不上),不能每帧都重置 buffer——30fps 下每秒会触发 30 次
// 跨 N-API 调用,引发明显性能抖动。这里加重试上限:前 N 次正常重置,超过后退避到周期性日志。
this._setBufferRetryCount += 1;
if (this._setBufferRetryCount <= TRTCVodPlayer.MAX_BUFFER_RETRY) {
this.logger.info(`_dataRenderCallback: renderVideoData returned false (retry ${this._setBufferRetryCount}/${TRTCVodPlayer.MAX_BUFFER_RETRY}), re-setting video buffer`);
this._setVideoRenderBuffer();
}
else if (this._setBufferRetryCount % TRTCVodPlayer.BUFFER_RETRY_LOG_INTERVAL === 0) {
this.logger.warn(`_dataRenderCallback: renderVideoData keeps failing for ${this._setBufferRetryCount} frames, giving up frame`);
}
}
_initEventCallback() {
var _a;
(_a = this.nativeVodPlayer) === null || _a === void 0 ? void 0 : _a.setEventCallback((args) => {
const event = args[0];
if ((event === TRTCVodPlayerEvent.onVodPlayerStopped || event === TRTCVodPlayerEvent.onVodPlayerError) && this.isStarted) {
this.isStarted = false;
}
switch (event) {
case TRTCVodPlayerEvent.onVodPlayerStarted:
if (!this.isStarted) {
this.isStarted = true;
}
this.fire(TRTCVodPlayerEvent.onVodPlayerStarted, args[1].msLength);
break;
case TRTCVodPlayerEvent.onVodPlayerLoaded: {
const data = args[1];
this.fire(TRTCVodPlayerEvent.onVodPlayerLoaded, data.durationMs, data.width, data.height);
break;
}
case TRTCVodPlayerEvent.onVodPlayerProgress:
this.fire(TRTCVodPlayerEvent.onVodPlayerProgress, args[1].msPos);
break;
case TRTCVodPlayerEvent.onVodPlayerPaused:
this.fire(TRTCVodPlayerEvent.onVodPlayerPaused);
break;
case TRTCVodPlayerEvent.onVodPlayerResumed:
this.fire(TRTCVodPlayerEvent.onVodPlayerResumed);
break;
case TRTCVodPlayerEvent.onVodPlayerStopped:
this.fire(TRTCVodPlayerEvent.onVodPlayerStopped, args[1].reason);
break;
case TRTCVodPlayerEvent.onVodPlayerError:
this.fire(TRTCVodPlayerEvent.onVodPlayerError, args[1].error);
break;
default: {
const exhaustiveCheck = args[0];
this.logger.warn(`unknown vod player event: ${String(exhaustiveCheck)}`);
break;
}
}
});
}
}
exports.TRTCVodPlayer = TRTCVodPlayer;
TRTCVodPlayer.MAX_BUFFER_RETRY = 3;
TRTCVodPlayer.BUFFER_RETRY_LOG_INTERVAL = 60;
// ─── 静态工厂方法 ───────────────────────────────────────────
/**
* 当前 native 构建是否启用了 VOD 播放器。
*
* 这是一次性的"构建期能力探测"——`NodeTRTCEngine.NodeVodPlayer` 是
* 加载 .node 时绑定,运行期不会变化,因此结果可在模块加载时缓存为常量。
*
* 通过 {@link isSupported} 对外暴露访问入口。
*/
TRTCVodPlayer.VOD_SUPPORTED = typeof (NodeTRTCEngine === null || NodeTRTCEngine === void 0 ? void 0 : NodeTRTCEngine.NodeVodPlayer) === 'function';