UNPKG

trtc-electron-sdk

Version:

trtc electron sdk

753 lines (752 loc) 31.2 kB
"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';