diff --git a/example/src/renderer/components/BaseComponent.tsx b/example/src/renderer/components/BaseComponent.tsx index 1e2ab914f..d8b52a8b7 100644 --- a/example/src/renderer/components/BaseComponent.tsx +++ b/example/src/renderer/components/BaseComponent.tsx @@ -190,7 +190,6 @@ export abstract class BaseComponent< <> {!!startPreview || joinChannelSuccess ? this.renderUser({ - uid: 0, sourceType: VideoSourceType.VideoSourceCamera, }) : undefined} diff --git a/example/src/renderer/components/RtcSurfaceView/index.tsx b/example/src/renderer/components/RtcSurfaceView/index.tsx index 783b2e873..0476ce209 100644 --- a/example/src/renderer/components/RtcSurfaceView/index.tsx +++ b/example/src/renderer/components/RtcSurfaceView/index.tsx @@ -1,10 +1,12 @@ import { + AgoraEnv, IMediaPlayer, IRtcEngineEx, RtcConnection, VideoCanvas, VideoMirrorModeType, VideoSourceType, + VideoViewSetupMode, createAgoraRtcEngine, } from 'agora-electron-sdk'; import React, { Component } from 'react'; @@ -23,6 +25,12 @@ interface State { uniqueId: number; } +type SetupVideoFunc = + | typeof IRtcEngineEx.prototype.setupRemoteVideoEx + | typeof IRtcEngineEx.prototype.setupRemoteVideo + | typeof IRtcEngineEx.prototype.setupLocalVideo + | typeof IMediaPlayer.prototype.setView; + export class RtcSurfaceView extends Component { constructor(props: Props) { super(props); @@ -43,7 +51,16 @@ export class RtcSurfaceView extends Component { }; componentDidMount() { - this.updateRender(); + const { canvas, connection } = this.props; + this.getSetupVideoFunc().call( + this, + { + ...canvas, + setupMode: VideoViewSetupMode.VideoViewSetupAdd, + view: this.getHTMLElement(), + }, + connection! + ); } shouldComponentUpdate( @@ -59,59 +76,59 @@ export class RtcSurfaceView extends Component { } componentDidUpdate() { - this.updateRender(); + this.updateRenderer(); } componentWillUnmount() { - const dom = this.getHTMLElement(); + const { canvas, connection } = this.props; - createAgoraRtcEngine().destroyRendererByView(dom); + this.getSetupVideoFunc().call( + this, + { + ...canvas, + setupMode: VideoViewSetupMode.VideoViewSetupRemove, + view: this.getHTMLElement(), + }, + connection! + ); } - updateRender = () => { + getSetupVideoFunc = (): SetupVideoFunc => { const { canvas, connection } = this.props; - const { isMirror } = this.state; - const dom = this.getHTMLElement(); const engine = createAgoraRtcEngine(); - let funcName: - | typeof IRtcEngineEx.prototype.setupRemoteVideoEx - | typeof IRtcEngineEx.prototype.setupRemoteVideo - | typeof IRtcEngineEx.prototype.setupLocalVideo - | typeof IMediaPlayer.prototype.setView; + let func: SetupVideoFunc; if (canvas.sourceType === undefined) { if (canvas.uid) { - funcName = engine.setupRemoteVideo; + func = engine.setupRemoteVideo; } else { - funcName = engine.setupLocalVideo; + func = engine.setupLocalVideo; } } else if (canvas.sourceType === VideoSourceType.VideoSourceRemote) { - funcName = engine.setupRemoteVideo; + func = engine.setupRemoteVideo; } else { - funcName = engine.setupLocalVideo; + func = engine.setupLocalVideo; } - if (funcName === engine.setupRemoteVideo && connection) { - funcName = engine.setupRemoteVideoEx; + if (func === engine.setupRemoteVideo && connection) { + func = engine.setupRemoteVideoEx; } - try { - engine.destroyRendererByView(dom); - } catch (e) { - console.warn(e); - } - funcName.call( - this, - { - ...canvas, - mirrorMode: isMirror - ? VideoMirrorModeType.VideoMirrorModeEnabled - : VideoMirrorModeType.VideoMirrorModeDisabled, - view: dom, - }, - connection! - ); + return func; + }; + + updateRenderer = () => { + const { canvas, connection } = this.props; + const { isMirror } = this.state; + AgoraEnv.AgoraRendererManager?.setRendererContext({ + ...canvas, + ...connection, + mirrorMode: isMirror + ? VideoMirrorModeType.VideoMirrorModeEnabled + : VideoMirrorModeType.VideoMirrorModeDisabled, + view: this.getHTMLElement(), + }); }; render() { @@ -124,7 +141,7 @@ export class RtcSurfaceView extends Component { onClick={() => { this.setState((preState) => { return { isMirror: !preState.isMirror }; - }, this.updateRender); + }, this.updateRenderer); }} >
{startLocalVideoTranscoder ? this.renderUser({ - renderMode: RenderModeType.RenderModeFit, - uid: 0, sourceType: VideoSourceType.VideoSourceTranscoded, + renderMode: RenderModeType.RenderModeFit, }) : undefined} {startPreview || joinChannelSuccess ? videoDeviceId?.map((value) => this.renderUser({ - uid: 0, sourceType: this._getVideoSourceTypeCamera(value), }) ) @@ -574,8 +572,9 @@ export default class LocalVideoTranscoder {open ? ( ) : undefined} diff --git a/example/src/renderer/examples/advanced/MediaPlayer/MediaPlayer.tsx b/example/src/renderer/examples/advanced/MediaPlayer/MediaPlayer.tsx index 7134d97b2..6ee91f7eb 100644 --- a/example/src/renderer/examples/advanced/MediaPlayer/MediaPlayer.tsx +++ b/example/src/renderer/examples/advanced/MediaPlayer/MediaPlayer.tsx @@ -5,6 +5,7 @@ import { MediaPlayerEvent, MediaPlayerReason, MediaPlayerState, + RenderModeType, VideoSourceType, createAgoraRtcEngine, } from 'agora-electron-sdk'; @@ -351,8 +352,9 @@ export default class MediaPlayer {open ? ( ) : undefined} diff --git a/example/src/renderer/examples/advanced/ScreenShare/ScreenShare.tsx b/example/src/renderer/examples/advanced/ScreenShare/ScreenShare.tsx index 5882ad198..9b535bbdd 100644 --- a/example/src/renderer/examples/advanced/ScreenShare/ScreenShare.tsx +++ b/example/src/renderer/examples/advanced/ScreenShare/ScreenShare.tsx @@ -399,7 +399,6 @@ export default class ScreenShare {startScreenCapture ? ( ) : undefined} diff --git a/example/src/renderer/examples/advanced/VideoEncoderConfiguration/VideoEncoderConfiguration.tsx b/example/src/renderer/examples/advanced/VideoEncoderConfiguration/VideoEncoderConfiguration.tsx index 47b2f9885..a0fc81ffb 100644 --- a/example/src/renderer/examples/advanced/VideoEncoderConfiguration/VideoEncoderConfiguration.tsx +++ b/example/src/renderer/examples/advanced/VideoEncoderConfiguration/VideoEncoderConfiguration.tsx @@ -1,11 +1,9 @@ import { - AgoraEnv, ChannelProfileType, ClientRoleType, DegradationPreference, IRtcEngineEventHandler, OrientationMode, - RENDER_MODE, RenderModeType, VideoCodecType, VideoMirrorModeType, @@ -37,8 +35,7 @@ interface State extends BaseVideoComponentState { bitrate: number; minBitrate: number; orientationMode: OrientationMode; - renderMode: RENDER_MODE; - renderModeType: RenderModeType; + renderMode: RenderModeType; degradationPreference: DegradationPreference; mirrorMode: VideoMirrorModeType; } @@ -64,8 +61,7 @@ export default class VideoEncoderConfiguration bitrate: 0, minBitrate: -1, orientationMode: OrientationMode.OrientationModeAdaptive, - renderMode: RENDER_MODE.WEBGL, - renderModeType: RenderModeType.RenderModeFit, + renderMode: RenderModeType.RenderModeHidden, degradationPreference: DegradationPreference.MaintainQuality, mirrorMode: VideoMirrorModeType.VideoMirrorModeDisabled, }; @@ -128,20 +124,11 @@ export default class VideoEncoderConfiguration } /** - * Step 3-1: setRenderMode,need leave and join channel again - */ - setRenderMode = () => { - const { renderMode } = this.state; - // @ts-ignore - AgoraEnv?.AgoraRendererManager?.['setRenderMode'](renderMode); - }; - - /** - * Step 3-2: setVideoRenderMode + * Step 3-1: setVideoRenderMode */ setVideoRenderMode = () => { - const { renderModeType, mirrorMode } = this.state; - this.engine?.setLocalRenderMode(renderModeType, mirrorMode); + const { renderMode, mirrorMode } = this.state; + this.engine?.setLocalRenderMode(renderMode, mirrorMode); }; /** @@ -194,7 +181,6 @@ export default class VideoEncoderConfiguration codecType, orientationMode, renderMode, - renderModeType, degradationPreference, mirrorMode, } = this.state; @@ -283,16 +269,6 @@ export default class VideoEncoderConfiguration }} /> - { - this.setState({ renderMode: value }); - }} - /> - - { - this.setState({ renderModeType: value }); + this.setState({ renderMode: value }); }} /> {!!startPreview || joinChannelSuccess ? renderUser({ - uid: 0, sourceType: VideoSourceType.VideoSourceCamera, }) : undefined} diff --git a/source_code/agora_node_ext/agora_electron_bridge.cpp b/source_code/agora_node_ext/agora_electron_bridge.cpp index 1030bf187..3735ecacc 100644 --- a/source_code/agora_node_ext/agora_electron_bridge.cpp +++ b/source_code/agora_node_ext/agora_electron_bridge.cpp @@ -295,7 +295,7 @@ napi_value AgoraElectronBridge::EnableVideoFrameCache(napi_env env, unsigned int height = 0; napi_obj_get_property(env, obj, "uid", config.uid); - napi_obj_get_property(env, obj, "videoSourceType", config.video_source_type); + napi_obj_get_property(env, obj, "sourceType", config.video_source_type); napi_obj_get_property(env, obj, "channelId", channelId); strcpy(config.channelId, channelId.c_str()); napi_obj_get_property(env, obj, "width", width); @@ -340,7 +340,7 @@ AgoraElectronBridge::DisableVideoFrameCache(napi_env env, std::string channelId = ""; napi_obj_get_property(env, obj, "uid", config.uid); - napi_obj_get_property(env, obj, "videoSourceType", config.video_source_type); + napi_obj_get_property(env, obj, "sourceType", config.video_source_type); napi_obj_get_property(env, obj, "channelId", channelId); strcpy(config.channelId, channelId.c_str()); @@ -368,8 +368,8 @@ napi_value AgoraElectronBridge::GetVideoFrame(napi_env env, napi_callback_info info) { napi_status status; napi_value jsthis; - size_t argc = 1; - napi_value args[1]; + size_t argc = 2; + napi_value args[2]; status = napi_get_cb_info(env, info, &argc, args, &jsthis, nullptr); AgoraElectronBridge *agoraElectronBridge; @@ -377,9 +377,15 @@ napi_value AgoraElectronBridge::GetVideoFrame(napi_env env, napi_unwrap(env, jsthis, reinterpret_cast(&agoraElectronBridge)); IrisRtcVideoFrameConfig config = EmptyIrisRtcVideoFrameConfig; - napi_value obj = args[0]; - int videoSourceType; + napi_value obj0 = args[0]; std::string channel_id; + + napi_obj_get_property(env, obj0, "uid", config.uid); + napi_obj_get_property(env, obj0, "sourceType", config.video_source_type); + napi_obj_get_property(env, obj0, "channelId", channel_id); + strcpy(config.channelId, channel_id.c_str()); + + napi_value obj1 = args[1]; napi_value y_buffer_obj; void *y_buffer; size_t y_length; @@ -389,40 +395,35 @@ napi_value AgoraElectronBridge::GetVideoFrame(napi_env env, napi_value v_buffer_obj; void *v_buffer; size_t v_length; - int height; int width; + int height; int yStride; - napi_obj_get_property(env, obj, "uid", config.uid); - napi_obj_get_property(env, obj, "videoSourceType", config.video_source_type); - napi_obj_get_property(env, obj, "channelId", channel_id); - strcpy(config.channelId, channel_id.c_str()); - - napi_obj_get_property(env, obj, "yBuffer", y_buffer_obj); + napi_obj_get_property(env, obj1, "yBuffer", y_buffer_obj); napi_get_buffer_info(env, y_buffer_obj, &y_buffer, &y_length); - napi_obj_get_property(env, obj, "uBuffer", u_buffer_obj); + napi_obj_get_property(env, obj1, "uBuffer", u_buffer_obj); napi_get_buffer_info(env, u_buffer_obj, &u_buffer, &u_length); - napi_obj_get_property(env, obj, "vBuffer", v_buffer_obj); + napi_obj_get_property(env, obj1, "vBuffer", v_buffer_obj); napi_get_buffer_info(env, v_buffer_obj, &v_buffer, &v_length); - napi_obj_get_property(env, obj, "height", height); - napi_obj_get_property(env, obj, "width", width); - napi_obj_get_property(env, obj, "yStride", yStride); + napi_obj_get_property(env, obj1, "width", width); + napi_obj_get_property(env, obj1, "height", height); + napi_obj_get_property(env, obj1, "yStride", yStride); IrisCVideoFrame videoFrame; videoFrame.yBuffer = (uint8_t *) y_buffer; videoFrame.uBuffer = (uint8_t *) u_buffer; videoFrame.vBuffer = (uint8_t *) v_buffer; - videoFrame.height = height; videoFrame.width = width; + videoFrame.height = height; videoFrame.yStride = yStride; videoFrame.metadata_buffer = nullptr; videoFrame.metadata_size = 0; videoFrame.alphaBuffer = nullptr; - bool isFresh = false; + bool isNewFrame = false; napi_value retObj; int32_t ret = ERR_NOT_INITIALIZED; status = napi_create_object(env, &retObj); @@ -434,16 +435,23 @@ napi_value AgoraElectronBridge::GetVideoFrame(napi_env env, } ret = agoraElectronBridge->_iris_rendering->GetVideoFrameCache( - config, &videoFrame, isFresh); + config, &videoFrame, isNewFrame); - unsigned int rotation = 0; napi_obj_set_property(env, retObj, "ret", ret); - napi_obj_set_property(env, retObj, "isNewFrame", isFresh); - napi_obj_set_property(env, retObj, "width", videoFrame.width); - napi_obj_set_property(env, retObj, "height", videoFrame.height); - napi_obj_set_property(env, retObj, "yStride", videoFrame.yStride); - napi_obj_set_property(env, retObj, "rotation", rotation); - napi_obj_set_property(env, retObj, "timestamp", videoFrame.renderTimeMs); + napi_obj_set_property(env, retObj, "isNewFrame", isNewFrame); + + napi_obj_set_property(env, obj1, "type", videoFrame.type); + napi_obj_set_property(env, obj1, "width", videoFrame.width); + napi_obj_set_property(env, obj1, "height", videoFrame.height); + napi_obj_set_property(env, obj1, "yStride", videoFrame.yStride); + napi_obj_set_property(env, obj1, "uStride", videoFrame.uStride); + napi_obj_set_property(env, obj1, "vStride", videoFrame.vStride); + napi_obj_set_property(env, obj1, "rotation", videoFrame.rotation); + napi_obj_set_property(env, obj1, "renderTimeMs", videoFrame.renderTimeMs); + napi_obj_set_property(env, obj1, "avsync_type", videoFrame.avsync_type); + napi_obj_set_property(env, obj1, "metadata_size", videoFrame.metadata_size); + // napi_obj_set_property(env, obj1, "textureId", videoFrame.textureId); + return retObj; } diff --git a/ts/Private/internal/IrisApiEngine.ts b/ts/Private/internal/IrisApiEngine.ts index 614c96163..445aba7b3 100644 --- a/ts/Private/internal/IrisApiEngine.ts +++ b/ts/Private/internal/IrisApiEngine.ts @@ -1,7 +1,7 @@ import EventEmitter from 'eventemitter3'; import JSON from 'json-bigint'; -import { AgoraEnv } from '../../Utils'; +import { AgoraEnv, logDebug, logError, logInfo, logWarn } from '../../Utils'; import { IAudioEncodedFrameObserver } from '../AgoraBase'; import { AudioFrame, @@ -360,7 +360,7 @@ export const EVENT_PROCESSORS: EventProcessors = { function handleEvent(...[event, data, buffers]: any) { if (isDebuggable()) { - console.info('onEvent', event, data, buffers); + logInfo('onEvent', event, data, buffers); } let _event: string = event; @@ -513,35 +513,30 @@ export function callIrisApi(funcName: string, params: any): any { const retObj = JSON.parse(ret); if (isDebuggable()) { if (typeof retObj.result === 'number' && retObj.result < 0) { - console.error('callApi', funcName, JSON.stringify(params), ret); + logError('callApi', funcName, JSON.stringify(params), ret); } else { - console.debug('callApi', funcName, JSON.stringify(params), ret); + logDebug('callApi', funcName, JSON.stringify(params), ret); } } return retObj; } else { if (isDebuggable()) { - console.error( + logError( 'callApi', funcName, JSON.stringify(params), callApiReturnCode ); } else { - console.warn( - 'callApi', - funcName, - JSON.stringify(params), - callApiReturnCode - ); + logWarn('callApi', funcName, JSON.stringify(params), callApiReturnCode); } return { result: callApiReturnCode }; } } catch (e) { if (isDebuggable()) { - console.error('callApi', funcName, JSON.stringify(params), e); + logError('callApi', funcName, JSON.stringify(params), e); } else { - console.warn('callApi', funcName, JSON.stringify(params), e); + logWarn('callApi', funcName, JSON.stringify(params), e); } } return {}; diff --git a/ts/Private/internal/MediaPlayerInternal.ts b/ts/Private/internal/MediaPlayerInternal.ts index 888a513e7..4ce3c2cf0 100644 --- a/ts/Private/internal/MediaPlayerInternal.ts +++ b/ts/Private/internal/MediaPlayerInternal.ts @@ -1,7 +1,6 @@ import { createCheckers } from 'ts-interface-checker'; -import { AgoraEnv, logWarn } from '../../Utils'; - +import { AgoraEnv, logError } from '../../Utils'; import { ErrorCodeType } from '../AgoraBase'; import { IAudioPcmFrameSink, @@ -105,7 +104,7 @@ export class MediaPlayerInternal extends IMediaPlayerImpl { MediaPlayerInternal._audio_spectrum_observers.get(this._mediaPlayerId) ?.length === 0 ) { - console.error( + logError( 'Please call `registerMediaPlayerAudioSpectrumObserver` before you want to receive event by `addListener`' ); return false; @@ -303,32 +302,24 @@ export class MediaPlayerInternal extends IMediaPlayerImpl { } override setView(view: HTMLElement): number { - logWarn('Also can use other api setupLocalVideo'); - return ( - AgoraEnv.AgoraRendererManager?.setupVideo({ - videoSourceType: VideoSourceType.VideoSourceMediaPlayer, - uid: this._mediaPlayerId, - view, - }) ?? -ErrorCodeType.ErrNotInitialized - ); + if (!AgoraEnv.AgoraRendererManager) return -ErrorCodeType.ErrNotInitialized; + const renderer = AgoraEnv.AgoraRendererManager.addOrRemoveRenderer({ + sourceType: VideoSourceType.VideoSourceMediaPlayer, + mediaPlayerId: this._mediaPlayerId, + view, + }); + if (!renderer) return -ErrorCodeType.ErrFailed; + return ErrorCodeType.ErrOk; } override setRenderMode(renderMode: RenderModeType): number { - logWarn( - 'Also can use other api setRenderOption or setRenderOptionByConfig' - ); - return ( - AgoraEnv.AgoraRendererManager?.setRenderOptionByConfig({ - videoSourceType: VideoSourceType.VideoSourceMediaPlayer, - uid: this._mediaPlayerId, - rendererOptions: { - contentMode: - renderMode === RenderModeType.RenderModeFit - ? RenderModeType.RenderModeFit - : RenderModeType.RenderModeHidden, - mirror: true, - }, - }) ?? -ErrorCodeType.ErrNotInitialized - ); + if (!AgoraEnv.AgoraRendererManager) return -ErrorCodeType.ErrNotInitialized; + const renderer = AgoraEnv.AgoraRendererManager.setRendererContext({ + sourceType: VideoSourceType.VideoSourceMediaPlayer, + mediaPlayerId: this._mediaPlayerId, + renderMode, + }); + if (!renderer) return -ErrorCodeType.ErrFailed; + return ErrorCodeType.ErrOk; } } diff --git a/ts/Private/internal/RtcEngineExInternal.ts b/ts/Private/internal/RtcEngineExInternal.ts index babe9ff9d..b33ae53f1 100644 --- a/ts/Private/internal/RtcEngineExInternal.ts +++ b/ts/Private/internal/RtcEngineExInternal.ts @@ -1,7 +1,6 @@ import { createCheckers } from 'ts-interface-checker'; -import { Channel } from '../../Types'; -import { AgoraEnv } from '../../Utils'; +import { AgoraEnv, logError } from '../../Utils'; import { AudioEncodedFrameObserverConfig, AudioRecordingConfiguration, @@ -97,7 +96,6 @@ export class RtcEngineExInternal extends IRtcEngineExImpl { AgoraEnv.AgoraRendererManager = new RendererManager(); } } - AgoraEnv.AgoraRendererManager?.enableRender(); const ret = super.initialize(context); callIrisApi.call(this, 'RtcEngine_setAppType', { appType: 3, @@ -107,7 +105,7 @@ export class RtcEngineExInternal extends IRtcEngineExImpl { override release(sync: boolean = false) { AgoraEnv.AgoraElectronBridge.ReleaseRenderer(); - AgoraEnv.AgoraRendererManager?.clear(); + AgoraEnv.AgoraRendererManager?.release(); AgoraEnv.AgoraRendererManager = undefined; this._audio_device_manager.release(); this._video_device_manager.release(); @@ -145,7 +143,7 @@ export class RtcEngineExInternal extends IRtcEngineExImpl { if ( RtcEngineExInternal._direct_cdn_streaming_event_handler.length === 0 ) { - console.error( + logError( 'Please call `startDirectCdnStreaming` before you want to receive event by `addListener`' ); return false; @@ -157,7 +155,7 @@ export class RtcEngineExInternal extends IRtcEngineExImpl { }) ) { if (RtcEngineExInternal._metadata_observer.length === 0) { - console.error( + logError( 'Please call `registerMediaMetadataObserver` before you want to receive event by `addListener`' ); return false; @@ -169,7 +167,7 @@ export class RtcEngineExInternal extends IRtcEngineExImpl { }) ) { if (RtcEngineExInternal._audio_encoded_frame_observers.length === 0) { - console.error( + logError( 'Please call `registerAudioEncodedFrameObserver` before you want to receive event by `addListener`' ); return false; @@ -190,7 +188,7 @@ export class RtcEngineExInternal extends IRtcEngineExImpl { addListener( eventType: EventType, listener: IRtcEngineEvent[EventType] - ): void { + ) { this._addListenerPreCheck(eventType); const callback = (eventProcessor: EventProcessor, data: any) => { if (eventProcessor.type(data) !== EVENT_TYPE.IRtcEngine) { @@ -326,7 +324,7 @@ export class RtcEngineExInternal extends IRtcEngineExImpl { options: ChannelMediaOptions ): string { if (AgoraEnv.AgoraRendererManager) { - AgoraEnv.AgoraRendererManager.defaultRenderConfig.channelId = channelId; + AgoraEnv.AgoraRendererManager.defaultChannelId = channelId; } return 'RtcEngine_joinChannel2'; } @@ -430,7 +428,7 @@ export class RtcEngineExInternal extends IRtcEngineExImpl { options?: ChannelMediaOptions ): string { if (AgoraEnv.AgoraRendererManager) { - AgoraEnv.AgoraRendererManager.defaultRenderConfig.channelId = channelId; + AgoraEnv.AgoraRendererManager.defaultChannelId = channelId; } return options === undefined ? 'RtcEngine_joinChannelWithUserAccount' @@ -583,98 +581,44 @@ export class RtcEngineExInternal extends IRtcEngineExImpl { } override setupLocalVideo(canvas: VideoCanvas): number { - let { - sourceType = VideoSourceType.VideoSourceCamera, - uid, - mediaPlayerId, - view, - renderMode, - mirrorMode, - } = canvas; - if ( - sourceType === VideoSourceType.VideoSourceMediaPlayer && - mediaPlayerId !== undefined - ) { - uid = mediaPlayerId; - } - return ( - AgoraEnv.AgoraRendererManager?.setupLocalVideo({ - videoSourceType: sourceType, - channelId: '', - uid, - view, - rendererOptions: { - contentMode: renderMode, - mirror: mirrorMode === VideoMirrorModeType.VideoMirrorModeEnabled, - }, - }) ?? -ErrorCodeType.ErrNotInitialized - ); + if (!AgoraEnv.AgoraRendererManager) return -ErrorCodeType.ErrNotInitialized; + const renderer = AgoraEnv.AgoraRendererManager.addOrRemoveRenderer(canvas); + if (!renderer) return -ErrorCodeType.ErrFailed; + return ErrorCodeType.ErrOk; } override setupRemoteVideo(canvas: VideoCanvas): number { - const { - sourceType = VideoSourceType.VideoSourceRemote, - uid, - view, - renderMode, - mirrorMode, - } = canvas; - return ( - AgoraEnv.AgoraRendererManager?.setupRemoteVideo({ - videoSourceType: sourceType, - channelId: - AgoraEnv.AgoraRendererManager?.defaultRenderConfig?.channelId, - uid, - view, - rendererOptions: { - contentMode: renderMode, - mirror: mirrorMode === VideoMirrorModeType.VideoMirrorModeEnabled, - }, - }) ?? -ErrorCodeType.ErrNotInitialized - ); + if (!AgoraEnv.AgoraRendererManager) return -ErrorCodeType.ErrNotInitialized; + const renderer = AgoraEnv.AgoraRendererManager.addOrRemoveRenderer(canvas); + if (!renderer) return -ErrorCodeType.ErrFailed; + return ErrorCodeType.ErrOk; } override setupRemoteVideoEx( canvas: VideoCanvas, connection: RtcConnection ): number { - const { - sourceType = VideoSourceType.VideoSourceRemote, - uid, - view, - renderMode, - mirrorMode, - } = canvas; - const { channelId } = connection; - return ( - AgoraEnv.AgoraRendererManager?.setupRemoteVideo({ - videoSourceType: sourceType, - channelId, - uid, - view, - rendererOptions: { - contentMode: renderMode, - mirror: mirrorMode === VideoMirrorModeType.VideoMirrorModeEnabled, - }, - }) ?? -ErrorCodeType.ErrNotInitialized - ); + if (!AgoraEnv.AgoraRendererManager) return -ErrorCodeType.ErrNotInitialized; + const renderer = AgoraEnv.AgoraRendererManager.addOrRemoveRenderer({ + ...canvas, + ...connection, + }); + if (!renderer) return -ErrorCodeType.ErrFailed; + return ErrorCodeType.ErrOk; } override setLocalRenderMode( renderMode: RenderModeType, mirrorMode: VideoMirrorModeType = VideoMirrorModeType.VideoMirrorModeAuto ): number { - return ( - AgoraEnv.AgoraRendererManager?.setRenderOptionByConfig({ - videoSourceType: VideoSourceType.VideoSourceCamera, - channelId: '', - uid: 0, - rendererOptions: { - contentMode: renderMode, - mirror: mirrorMode === VideoMirrorModeType.VideoMirrorModeEnabled, - }, - }) ?? -ErrorCodeType.ErrNotInitialized - ); + if (!AgoraEnv.AgoraRendererManager) return -ErrorCodeType.ErrNotInitialized; + const result = AgoraEnv.AgoraRendererManager.setRendererContext({ + sourceType: VideoSourceType.VideoSourceCamera, + renderMode, + mirrorMode, + }); + if (!result) return -ErrorCodeType.ErrFailed; + return ErrorCodeType.ErrOk; } override setRemoteRenderMode( @@ -682,17 +626,15 @@ export class RtcEngineExInternal extends IRtcEngineExImpl { renderMode: RenderModeType, mirrorMode: VideoMirrorModeType ): number { - return ( - AgoraEnv.AgoraRendererManager?.setRenderOptionByConfig({ - videoSourceType: VideoSourceType.VideoSourceRemote, - channelId: AgoraEnv.AgoraRendererManager?.defaultRenderConfig.channelId, - uid, - rendererOptions: { - contentMode: renderMode, - mirror: mirrorMode === VideoMirrorModeType.VideoMirrorModeEnabled, - }, - }) ?? -ErrorCodeType.ErrNotInitialized - ); + if (!AgoraEnv.AgoraRendererManager) return -ErrorCodeType.ErrNotInitialized; + const result = AgoraEnv.AgoraRendererManager.setRendererContext({ + sourceType: VideoSourceType.VideoSourceRemote, + uid, + renderMode, + mirrorMode, + }); + if (!result) return -ErrorCodeType.ErrFailed; + return ErrorCodeType.ErrOk; } override setRemoteRenderModeEx( @@ -701,46 +643,41 @@ export class RtcEngineExInternal extends IRtcEngineExImpl { mirrorMode: VideoMirrorModeType, connection: RtcConnection ): number { - const { channelId } = connection; - return ( - AgoraEnv.AgoraRendererManager?.setRenderOptionByConfig({ - videoSourceType: VideoSourceType.VideoSourceRemote, - channelId, - uid, - rendererOptions: { - contentMode: renderMode, - mirror: mirrorMode === VideoMirrorModeType.VideoMirrorModeEnabled, - }, - }) ?? -ErrorCodeType.ErrNotInitialized - ); + if (!AgoraEnv.AgoraRendererManager) return -ErrorCodeType.ErrNotInitialized; + const result = AgoraEnv.AgoraRendererManager.setRendererContext({ + sourceType: VideoSourceType.VideoSourceRemote, + ...connection, + uid, + renderMode, + mirrorMode, + }); + if (!result) return -ErrorCodeType.ErrFailed; + return ErrorCodeType.ErrOk; } override setLocalVideoMirrorMode(mirrorMode: VideoMirrorModeType): number { - return ( - AgoraEnv.AgoraRendererManager?.setRenderOptionByConfig({ - videoSourceType: VideoSourceType.VideoSourceCamera, - channelId: '', - uid: 0, - rendererOptions: { - mirror: mirrorMode === VideoMirrorModeType.VideoMirrorModeEnabled, - }, - }) ?? -ErrorCodeType.ErrNotInitialized - ); + if (!AgoraEnv.AgoraRendererManager) return -ErrorCodeType.ErrNotInitialized; + const result = AgoraEnv.AgoraRendererManager.setRendererContext({ + sourceType: VideoSourceType.VideoSourceCamera, + mirrorMode, + }); + if (!result) return -ErrorCodeType.ErrFailed; + return ErrorCodeType.ErrOk; } - override destroyRendererByView(view: any) { - AgoraEnv.AgoraRendererManager?.destroyRendererByView(view); + override destroyRendererByView(view: Element) { + AgoraEnv.AgoraRendererManager?.removeRendererFromCache({ view }); } override destroyRendererByConfig( - videoSourceType: VideoSourceType, - channelId?: Channel, + sourceType: VideoSourceType, + channelId?: string, uid?: number ) { - AgoraEnv.AgoraRendererManager?.destroyRenderersByConfig( - videoSourceType, + AgoraEnv.AgoraRendererManager?.removeRendererFromCache({ + sourceType, channelId, - uid - ); + uid, + }); } } diff --git a/ts/Renderer/AgoraView.ts b/ts/Renderer/AgoraView.ts index 29f126474..b0ffae770 100644 --- a/ts/Renderer/AgoraView.ts +++ b/ts/Renderer/AgoraView.ts @@ -1,3 +1,4 @@ +import { VideoMirrorModeType, VideoViewSetupMode } from '../Private/AgoraBase'; import { RenderModeType, VideoSourceType } from '../Private/AgoraMediaBase'; import { AgoraEnv } from '../Utils'; @@ -70,12 +71,12 @@ export default class AgoraView extends HTMLElement { return observedAttributes; } - get videoSourceType(): VideoSourceType { + get sourceType(): VideoSourceType { const number = Number(this.getAttribute(VIDEO_SOURCE_TYPE_STRING)); return isNaN(number) ? 0 : number; } - set videoSourceType(val) { + set sourceType(val) { if (val) { this.setAttribute(VIDEO_SOURCE_TYPE_STRING, String(val)); } else { @@ -108,7 +109,7 @@ export default class AgoraView extends HTMLElement { } } - get renderContentMode(): RenderModeType { + get renderMode(): RenderModeType { const number = Number( this.getAttribute(RENDERER_CONTENT_MODE_STRING) || RenderModeType.RenderModeFit @@ -116,7 +117,7 @@ export default class AgoraView extends HTMLElement { return isNaN(number) ? RenderModeType.RenderModeFit : number; } - set renderContentMode(val) { + set renderMode(val) { if (val) { this.setAttribute(RENDERER_CONTENT_MODE_STRING, String(val)); } else { @@ -141,21 +142,28 @@ export default class AgoraView extends HTMLElement { } initializeRender = () => { - AgoraEnv.AgoraRendererManager?.destroyRendererByView(this); - AgoraEnv.AgoraRendererManager?.setupVideo({ - videoSourceType: this.videoSourceType, + const { channelId, uid, sourceType, renderMode, renderMirror } = this; + AgoraEnv.AgoraRendererManager?.addOrRemoveRenderer({ + sourceType, view: this, - uid: this.uid, - channelId: this.channelId, - rendererOptions: { - mirror: this.renderMirror, - contentMode: this.renderContentMode, - }, + uid, + channelId, + renderMode, + mirrorMode: renderMirror + ? VideoMirrorModeType.VideoMirrorModeEnabled + : VideoMirrorModeType.VideoMirrorModeDisabled, + setupMode: VideoViewSetupMode.VideoViewSetupReplace, }); }; destroyRender = () => { - AgoraEnv.AgoraRendererManager?.destroyRendererByView(this); + const { channelId, uid, sourceType } = this; + AgoraEnv.AgoraRendererManager?.removeRendererFromCache({ + channelId, + uid, + sourceType, + view: this, + }); }; connectedCallback() { @@ -173,11 +181,13 @@ export default class AgoraView extends HTMLElement { ].includes(attrName); if (isSetRenderOption) { - AgoraEnv.AgoraRendererManager?.setRenderOption( - this, - this.renderContentMode, - this.renderMirror - ); + AgoraEnv.AgoraRendererManager?.setRendererContext({ + view: this, + renderMode: this.renderMode, + mirrorMode: this.renderMirror + ? VideoMirrorModeType.VideoMirrorModeEnabled + : VideoMirrorModeType.VideoMirrorModeDisabled, + }); return; } const isNeedReInitialize = observedAttributes.includes(attrName); diff --git a/ts/Renderer/IRenderer.ts b/ts/Renderer/IRenderer.ts index f9388e510..d37016b21 100644 --- a/ts/Renderer/IRenderer.ts +++ b/ts/Renderer/IRenderer.ts @@ -1,19 +1,14 @@ -import { RenderModeType } from '../Private/AgoraMediaBase'; -import { RendererOptions, ShareVideoFrame } from '../Types'; +import { VideoMirrorModeType } from '../Private/AgoraBase'; +import { RenderModeType, VideoFrame } from '../Private/AgoraMediaBase'; +import { RendererContext } from '../Types'; + +type Context = Pick; export abstract class IRenderer { parentElement?: HTMLElement; container?: HTMLElement; canvas?: HTMLCanvasElement; - contentMode = RenderModeType.RenderModeHidden; - mirror?: boolean; - - public snapshot(fileType = 'image/png') { - if (this.canvas && this.canvas.toDataURL) { - return this.canvas.toDataURL(fileType); - } - return null; - } + _context: Context = {}; public bind(element: HTMLElement) { this.parentElement = element; @@ -47,22 +42,76 @@ export abstract class IRenderer { this.parentElement = undefined; } - public equalsElement(element: Element): boolean { - if (!this.parentElement) { - console.error('parentElement is null'); + public abstract drawFrame(videoFrame: VideoFrame): void; + + public set context({ renderMode, mirrorMode }: Context) { + if (this.context.renderMode !== renderMode) { + this.context.renderMode = renderMode; + this.updateRenderMode(); + } + + if (this.context.mirrorMode !== mirrorMode) { + this.context.mirrorMode = mirrorMode; + this.updateMirrorMode(); } - return element === this.parentElement; } - abstract drawFrame(imageData: ShareVideoFrame): void; + public get context(): Context { + return this._context; + } + + protected updateRenderMode() { + if (!this.canvas || !this.container) return; + + const { clientWidth, clientHeight } = this.container; + const { width, height } = this.canvas; - public setRenderOption({ contentMode, mirror }: RendererOptions) { - this.contentMode = contentMode ?? RenderModeType.RenderModeFit; - this.mirror = mirror; - Object.assign(this.parentElement!.style, { - transform: mirror ? 'rotateY(180deg)' : '', + const containerAspectRatio = clientWidth / clientHeight; + const canvasAspectRatio = width / height; + const widthScale = clientWidth / width; + const heightScale = clientHeight / height; + + const isHidden = + this.context?.renderMode === RenderModeType.RenderModeHidden; + + let scale = 1; + // If container's aspect ratio is larger than canvas's aspect ratio + if (containerAspectRatio > canvasAspectRatio) { + // Scale canvas to fit container's width on hidden mode + // Scale canvas to fit container's height on fit mode + scale = isHidden ? widthScale : heightScale; + } else { + // Scale canvas to fit container's height on hidden mode + // Scale canvas to fit container's width on fit mode + scale = isHidden ? heightScale : widthScale; + } + this.canvas.style.transform = `scale(${scale})`; + } + + protected updateMirrorMode(): void { + if (!this.parentElement) return; + + Object.assign(this.parentElement.style, { + transform: + this.context.mirrorMode === VideoMirrorModeType.VideoMirrorModeEnabled + ? 'rotateY(180deg)' + : '', }); } - abstract refreshCanvas(): void; + protected rotateCanvas({ width, height, rotation }: VideoFrame): void { + if (!this.canvas) return; + + if (rotation === 0 || rotation === 180) { + this.canvas.width = width!; + this.canvas.height = height!; + } else if (rotation === 90 || rotation === 270) { + this.canvas.height = width!; + this.canvas.width = height!; + } else { + throw new Error( + `Invalid rotation: ${rotation}, only 0, 90, 180, 270 are supported` + ); + } + } } diff --git a/ts/Renderer/IRendererManager.ts b/ts/Renderer/IRendererManager.ts index cba695edc..7ecf82c04 100644 --- a/ts/Renderer/IRendererManager.ts +++ b/ts/Renderer/IRendererManager.ts @@ -1,35 +1,284 @@ +import { VideoMirrorModeType, VideoViewSetupMode } from '../Private/AgoraBase'; import { RenderModeType, VideoSourceType } from '../Private/AgoraMediaBase'; -import { Channel, RendererVideoConfig } from '../Types'; +import { RendererContext, RendererType } from '../Types'; +import { logDebug } from '../Utils'; + +import { IRenderer } from './IRenderer'; +import { RendererCache, generateRendererCacheKey } from './RendererCache'; /** * @ignore */ export abstract class IRendererManager { - abstract get defaultRenderConfig(): RendererVideoConfig; + /** + * @ignore + */ + private _renderingFps: number; + /** + * @ignore + */ + private _currentFrameCount: number; + /** + * @ignore + */ + private _previousFirstFrameTime: number; + /** + * @ignore + */ + private _renderingTimer?: number; + /** + * @ignore + */ + private _rendererCaches: RendererCache[]; + /** + * @ignore + */ + private _context: RendererContext; + + constructor() { + this._renderingFps = 15; + this._currentFrameCount = 0; + this._previousFirstFrameTime = 0; + this._rendererCaches = []; + this._context = { + renderMode: RenderModeType.RenderModeHidden, + mirrorMode: VideoMirrorModeType.VideoMirrorModeDisabled, + }; + } + + public set renderingFps(fps: number) { + if (this._renderingFps !== fps) { + this._renderingFps = fps; + if (this._renderingTimer) { + this.stopRendering(); + this.startRendering(); + } + } + } + + public get renderingFps(): number { + return this._renderingFps; + } + + public set defaultChannelId(channelId: string) { + this._context.channelId = channelId; + } + + public get defaultChannelId(): string { + return this._context.channelId ?? ''; + } + + public get defaultRenderMode(): RenderModeType { + return this._context.renderMode!; + } + + public get defaultMirrorMode(): VideoMirrorModeType { + return this._context.mirrorMode!; + } + + public release(): void { + this.stopRendering(); + this.clearRendererCache(); + } + + private precheckRendererContext(context: RendererContext): RendererContext { + let { + sourceType, + uid, + channelId, + mediaPlayerId, + renderMode = this.defaultRenderMode, + mirrorMode = this.defaultMirrorMode, + } = context; + switch (sourceType) { + case VideoSourceType.VideoSourceRemote: + if (uid === undefined) { + throw new Error('uid is required'); + } + channelId = channelId ?? this.defaultChannelId; + break; + case VideoSourceType.VideoSourceMediaPlayer: + if (mediaPlayerId === undefined) { + throw new Error('mediaPlayerId is required'); + } + channelId = ''; + uid = mediaPlayerId; + break; + default: + channelId = ''; + uid = 0; + break; + } + return { ...context, sourceType, uid, channelId, renderMode, mirrorMode }; + } + + public addOrRemoveRenderer( + context: RendererContext + ): RendererCache | undefined { + // To be compatible with the old API + let { setupMode = VideoViewSetupMode.VideoViewSetupAdd } = context; + if (!context.view) setupMode = VideoViewSetupMode.VideoViewSetupRemove; + switch (setupMode) { + case VideoViewSetupMode.VideoViewSetupAdd: + return this.addRendererToCache(context); + case VideoViewSetupMode.VideoViewSetupRemove: + this.removeRendererFromCache(context); + return undefined; + case VideoViewSetupMode.VideoViewSetupReplace: + this.removeRendererFromCache(context); + return this.addRendererToCache(context); + } + } + + private addRendererToCache( + context: RendererContext + ): RendererCache | undefined { + const checkedContext = this.precheckRendererContext(context); + + if (!checkedContext.view) return undefined; + + if (this.findRenderer(checkedContext.view)) { + throw new Error('You have already added this view to the renderer'); + } + + let rendererCache = this.getRendererCache(checkedContext); + if (!rendererCache) { + rendererCache = new RendererCache(checkedContext); + this._rendererCaches.push(rendererCache); + } + rendererCache.addRenderer(this.createRenderer(checkedContext)); + this.startRendering(); + return rendererCache; + } + + public removeRendererFromCache(context: RendererContext): void { + const checkedContext = this.precheckRendererContext(context); + + const rendererCache = this.getRendererCache(checkedContext); + if (!rendererCache) return; + if (checkedContext.view) { + const renderer = rendererCache.findRenderer(checkedContext.view); + if (!renderer) return; + rendererCache.removeRenderer(renderer); + } else { + rendererCache.removeRenderer(); + } + if (rendererCache.renderers.length === 0) { + this._rendererCaches.splice( + this._rendererCaches.indexOf(rendererCache), + 1 + ); + } + } + + public clearRendererCache(): void { + for (const rendererCache of this._rendererCaches) { + rendererCache.removeRenderer(); + } + this._rendererCaches.splice(0); + } + + public getRendererCache(context: RendererContext): RendererCache | undefined { + return this._rendererCaches.find( + (cache) => cache.key === generateRendererCacheKey(context) + ); + } + + public getRenderers(context: RendererContext): IRenderer[] { + return this.getRendererCache(context)?.renderers || []; + } + + public findRenderer(view: Element): IRenderer | undefined { + for (const rendererCache of this._rendererCaches) { + const renderer = rendererCache.findRenderer(view); + if (renderer) return renderer; + } + return undefined; + } + + protected abstract createRenderer( + context: RendererContext, + rendererType?: RendererType + ): IRenderer; + + public startRendering(): void { + if (this._renderingTimer) return; + + const renderingLooper = () => { + if (this._previousFirstFrameTime === 0) { + // Get the current time as the time of the first frame of per second + this._previousFirstFrameTime = performance.now(); + // Reset the frame count + this._currentFrameCount = 0; + } + + // Increase the frame count + ++this._currentFrameCount; - abstract enableRender(enabled?: boolean): void; + // Get the current time + const currentFrameTime = performance.now(); + // Calculate the time difference between the current frame and the previous frame + const deltaTime = currentFrameTime - this._previousFirstFrameTime; + // Calculate the expected time of the current frame + const expectedTime = + (this._currentFrameCount * 1000) / this._renderingFps; + logDebug( + new Date().toLocaleTimeString(), + 'currentFrameCount', + this._currentFrameCount, + 'expectedTime', + expectedTime, + 'deltaTime', + deltaTime + ); - abstract clear(): void; + if (this._rendererCaches.length === 0) { + // If there is no renderer, stop rendering + this.stopRendering(); + return; + } - abstract setupVideo(rendererVideoConfig: RendererVideoConfig): number; + // Render all renderers + for (const rendererCache of this._rendererCaches) { + this.doRendering(rendererCache); + } - abstract setupLocalVideo(rendererConfig: RendererVideoConfig): number; + if (this._currentFrameCount >= this.renderingFps) { + this._previousFirstFrameTime = 0; + } - abstract setupRemoteVideo(rendererConfig: RendererVideoConfig): number; + if (deltaTime < expectedTime) { + // If the time difference between the current frame and the previous frame is less than the expected time, then wait for the difference + this._renderingTimer = window.setTimeout( + renderingLooper, + expectedTime - deltaTime + ); + } else { + // If the time difference between the current frame and the previous frame is greater than the expected time, then render immediately + renderingLooper(); + } + }; + renderingLooper(); + } - abstract setRenderOptionByConfig(rendererConfig: RendererVideoConfig): number; + public abstract doRendering(rendererCache: RendererCache): void; - abstract destroyRendererByView(view: Element): void; + public stopRendering(): void { + if (this._renderingTimer) { + window.clearTimeout(this._renderingTimer); + this._renderingTimer = undefined; + } + } - abstract destroyRenderersByConfig( - videoSourceType: VideoSourceType, - channelId?: Channel, - uid?: number - ): void; + public setRendererContext(context: RendererContext): boolean { + const checkedContext = this.precheckRendererContext(context); - abstract setRenderOption( - view: HTMLElement, - contentMode?: RenderModeType, - mirror?: boolean - ): void; + for (const rendererCache of this._rendererCaches) { + const result = rendererCache.setRendererContext(checkedContext); + if (result) { + return true; + } + } + return false; + } } diff --git a/ts/Renderer/RendererCache.ts b/ts/Renderer/RendererCache.ts new file mode 100644 index 000000000..08c5c743b --- /dev/null +++ b/ts/Renderer/RendererCache.ts @@ -0,0 +1,161 @@ +import { VideoFrame } from '../Private/AgoraMediaBase'; +import { RendererCacheContext, RendererContext } from '../Types'; +import { AgoraEnv, logWarn } from '../Utils'; + +import { IRenderer } from './IRenderer'; + +export function generateRendererCacheKey({ + channelId, + uid, + sourceType, +}: RendererContext): string { + return `${channelId}_${uid}_${sourceType}`; +} + +export class RendererCache { + private _renderers: IRenderer[]; + private _videoFrame: VideoFrame; + private _context: RendererCacheContext; + private _enabled: boolean; + + constructor({ channelId, uid, sourceType }: RendererContext) { + this._renderers = []; + this._videoFrame = { + yBuffer: Buffer.alloc(0), + uBuffer: Buffer.alloc(0), + vBuffer: Buffer.alloc(0), + }; + this._context = { channelId, uid, sourceType }; + this._enabled = false; + } + + public get key(): string { + return generateRendererCacheKey(this._context); + } + + public get renderers(): IRenderer[] { + return this._renderers; + } + + public get videoFrame(): VideoFrame { + return this._videoFrame; + } + + public get context(): RendererCacheContext { + return this._context; + } + + /** + * @deprecated Use renderers instead + */ + public get renders(): IRenderer[] { + return this.renderers; + } + + /** + * @deprecated Use videoFrame instead + */ + public get shareVideoFrame(): VideoFrame | undefined { + return this.videoFrame; + } + + private get bridge() { + return AgoraEnv.AgoraElectronBridge; + } + + private enable() { + if (this._enabled) return; + this.bridge.EnableVideoFrameCache(this._context); + this._enabled = true; + } + + private disable() { + if (!this._enabled) return; + this.bridge.DisableVideoFrameCache(this._context); + this._enabled = false; + } + + private shouldEnable() { + if (this.renderers.length > 0) { + this.enable(); + } else { + this.disable(); + } + } + + public draw() { + let { ret, isNewFrame } = this.bridge.GetVideoFrame( + this.context, + this.videoFrame + ); + + switch (ret) { + case 0: // GET_VIDEO_FRAME_CACHE_RETURN_TYPE::OK = 0 + // + break; + case 1: // GET_VIDEO_FRAME_CACHE_RETURN_TYPE::RESIZED = 1 + const { yStride, uStride, vStride, height } = this.videoFrame; + this.videoFrame.yBuffer = Buffer.alloc(yStride! * height!); + this.videoFrame.uBuffer = Buffer.alloc(uStride! * height!); + this.videoFrame.vBuffer = Buffer.alloc(vStride! * height!); + + const result = this.bridge.GetVideoFrame(this.context, this.videoFrame); + ret = result.ret; + isNewFrame = result.isNewFrame; + break; + case 2: // GET_VIDEO_FRAME_CACHE_RETURN_TYPE::NO_CACHE = 2 + logWarn('No renderer cache, please enable cache first'); + return; + } + + if (isNewFrame) { + this.renderers.forEach((renderer) => { + renderer.drawFrame(this.videoFrame); + }); + } + } + + public findRenderer(view: Element): IRenderer | undefined { + return this._renderers.find((renderer) => renderer.parentElement === view); + } + + public addRenderer(renderer: IRenderer): void { + this._renderers.push(renderer); + this.shouldEnable(); + } + + /** + * Remove the specified renderer if it is specified, otherwise remove all renderers + */ + public removeRenderer(renderer?: IRenderer): void { + let start = 0; + let deleteCount = this._renderers.length; + if (renderer) { + start = this._renderers.indexOf(renderer); + if (start < 0) return; + deleteCount = 1; + } + this._renderers.splice(start, deleteCount).forEach((it) => it.unbind()); + this.shouldEnable(); + } + + public setRendererContext({ + view, + renderMode, + mirrorMode, + }: RendererContext): boolean { + if (view) { + const renderer = this.findRenderer(view); + if (renderer) { + renderer.context = { renderMode, mirrorMode }; + return true; + } + return false; + } else { + this._renderers.forEach((it) => { + it.context = { renderMode, mirrorMode }; + }); + return this._renderers.length > 0; + } + } +} diff --git a/ts/Renderer/RendererManager.ts b/ts/Renderer/RendererManager.ts index 2888eb8df..09ed6b3f0 100644 --- a/ts/Renderer/RendererManager.ts +++ b/ts/Renderer/RendererManager.ts @@ -1,30 +1,14 @@ -import { ErrorCodeType } from '../Private/AgoraBase'; -import { RenderModeType, VideoSourceType } from '../Private/AgoraMediaBase'; import { - AgoraElectronBridge, - Channel, - ChannelIdMap, - FormatRendererVideoConfig, RENDER_MODE, - RenderConfig, - RenderMap, - RendererVideoConfig, - ShareVideoFrame, - UidMap, - VideoFrameCacheConfig, + RendererCacheContext, + RendererContext, + RendererType, } from '../Types'; -import { - AgoraEnv, - formatConfigByVideoSourceType, - getDefaultRendererVideoConfig, - logDebug, - logError, - logInfo, - logWarn, -} from '../Utils'; +import { isSupportWebGL } from '../Utils'; import { IRenderer } from './IRenderer'; import { IRendererManager } from './IRendererManager'; +import { RendererCache } from './RendererCache'; import { WebGLFallback, WebGLRenderer } from './WebGLRenderer'; import { YUVCanvasRenderer } from './YUVCanvasRenderer'; @@ -35,621 +19,92 @@ export class RendererManager extends IRendererManager { /** * @ignore */ - isRendering = false; - renderFps: number; - /** - * @ignore - */ - videoFrameUpdateInterval?: NodeJS.Timer; - /** - * @ignore - */ - renderers: RenderMap; - /** - * @ignore - */ - renderMode?: RENDER_MODE; - /** - * @ignore - */ - msgBridge: AgoraElectronBridge; - /** - * @ignore - */ - defaultRenderConfig: RendererVideoConfig; - - constructor() { - super(); - this.renderFps = 15; - this.renderers = new Map(); - this.setRenderMode(); - this.msgBridge = AgoraEnv.AgoraElectronBridge; - this.defaultRenderConfig = { - rendererOptions: { - contentMode: RenderModeType.RenderModeFit, - mirror: false, - }, - }; - } - - /** - * Sets the channel mode of the current audio file. - * In a stereo music file, the left and right channels can store different audio data. According to your needs, you can set the channel mode to original mode, left channel mode, right channel mode, or mixed channel mode. For example, in the KTV scenario, the left channel of the music file stores the musical accompaniment, and the right channel stores the singing voice. If you only need to listen to the accompaniment, call this method to set the channel mode of the music file to left channel mode; if you need to listen to the accompaniment and the singing voice at the same time, call this method to set the channel mode to mixed channel mode.Call this method after calling open .This method only applies to stereo audio files. - * - * @param mode The channel mode. See AudioDualMonoMode . - * - * @returns - * 0: Success.< 0: Failure. - */ - public setRenderMode(mode?: RENDER_MODE) { - if (mode === undefined) { - this.renderMode = this.checkWebglEnv() - ? RENDER_MODE.WEBGL - : RENDER_MODE.SOFTWARE; - return; - } - - if (mode !== this.renderMode) { - this.renderMode = mode; - logInfo( - 'setRenderMode: new render mode will take effect only if new view bind to render' - ); - } - } - - /** - * @ignore - */ - public setFPS(fps: number) { - this.renderFps = fps; - this.restartRender(); - } - - /** - * @ignore - */ - public setRenderOption( - view: HTMLElement, - contentMode = RenderModeType.RenderModeFit, - mirror: boolean = false - ): void { - if (!view) { - logError('setRenderOption: view not exist', view); - } - this.forEachStream(({ renders }) => { - renders?.forEach((render) => { - if (render.equalsElement(view)) { - render.setRenderOption({ contentMode, mirror }); - } - }); - }); - } - - /** - * @ignore - */ - public setRenderOptionByConfig(rendererConfig: RendererVideoConfig): number { - const { - uid, - channelId, - rendererOptions, - videoSourceType, - }: FormatRendererVideoConfig = - getDefaultRendererVideoConfig(rendererConfig); - - const renderList = this.getRenderers({ uid, channelId, videoSourceType }); - renderList - ? renderList - .filter((renderItem) => { - if (rendererConfig.view) { - return renderItem.equalsElement(rendererConfig.view); - } else { - return true; - } - }) - .forEach((renderItem) => renderItem.setRenderOption(rendererOptions)) - : logWarn( - `RenderStreamType: ${videoSourceType} channelId:${channelId} uid:${uid} have no render view, you need to call this api after setView` - ); - return ErrorCodeType.ErrOk; - } - - /** - * @ignore - */ - public checkWebglEnv(): boolean { - let flag = false; - const canvas: HTMLCanvasElement = document.createElement('canvas'); - try { - const getContext = ( - contextNames = ['webgl2', 'webgl', 'experimental-webgl'] - ): WebGLRenderingContext | WebGLRenderingContext | null => { - for (let i = 0; i < contextNames.length; i++) { - const contextName = contextNames[i]!; - const context = canvas?.getContext(contextName); - if (context) { - return context as WebGLRenderingContext | WebGLRenderingContext; - } - } - return null; - }; - let gl = getContext(); - flag = !!gl; - gl?.getExtension('WEBGL_lose_context')?.loseContext(); - gl = null; - logInfo('Your browser support webGL'); - } catch (e) { - logWarn('Your browser may not support webGL'); - flag = false; - } - return flag; - } - - /** - * @ignore - */ - public setupVideo(rendererVideoConfig: RendererVideoConfig): number { - const formatConfig = getDefaultRendererVideoConfig(rendererVideoConfig); - - const { uid, channelId, videoSourceType, rendererOptions, view } = - formatConfig; + private _rendererType: RendererType; - if (!formatConfig.view) { - logWarn('setupVideo->destroyRenderersByConfig, because of view is null'); - this.destroyRenderersByConfig(videoSourceType, channelId, uid); - return -ErrorCodeType.ErrInvalidArgument; + public set rendererType(rendererType: RendererType) { + if (this._rendererType !== rendererType) { + this._rendererType = rendererType; } - - // ensure a render to RenderMap - const render = this.bindHTMLElementToRender(formatConfig, view!); - - // render config - render?.setRenderOption(rendererOptions); - - // enable iris videoFrame - this.enableVideoFrameCache({ - uid, - channelId, - videoSourceType, - }); - - // enable render - this.enableRender(true); - return ErrorCodeType.ErrOk; } - /** - * @ignore - */ - public setupLocalVideo(rendererConfig: RendererVideoConfig): number { - const { videoSourceType } = rendererConfig; - if (videoSourceType === VideoSourceType.VideoSourceRemote) { - logError('setupLocalVideo videoSourceType error', videoSourceType); - return -ErrorCodeType.ErrInvalidArgument; - } - this.setupVideo({ ...rendererConfig }); - return ErrorCodeType.ErrOk; - } - - /** - * @ignore - */ - public setupRemoteVideo(rendererConfig: RendererVideoConfig): number { - const { videoSourceType } = rendererConfig; - if (videoSourceType !== VideoSourceType.VideoSourceRemote) { - logError('setupRemoteVideo videoSourceType error', videoSourceType); - return -ErrorCodeType.ErrInvalidArgument; - } - this.setupVideo({ ...rendererConfig }); - return ErrorCodeType.ErrOk; - } - - /** - * Destroys a video renderer object. - * - * @param view The HTMLElement object to be destroyed. - */ - public destroyRendererByView(view: Element): void { - const renders = this.renderers; - renders.forEach((channelMap, videoSourceType) => { - channelMap.forEach((uidMap, channelId) => { - uidMap.forEach((renderConfig, uid) => { - let hasRender = false; - const remainRenders = renderConfig.renders?.filter((render) => { - const isFilter = render.equalsElement(view); - - if (isFilter) { - hasRender = true; - render.unbind(); - } - return !isFilter; - }); - if (!hasRender) { - return; - } - - if (remainRenders?.length === 0 || !remainRenders) { - this.disableVideoFrameCache({ uid, channelId, videoSourceType }); - } - renderConfig.renders = remainRenders; - }); - }); - }); - } - - /** - * @ignore - */ - public destroyRenderersByConfig( - videoSourceType: VideoSourceType, - channelId?: Channel, - uid?: number - ): void { - const config = formatConfigByVideoSourceType( - videoSourceType, - channelId, - uid - ); - videoSourceType = config.videoSourceType; - channelId = config.channelId; - uid = config.uid; - - this.disableVideoFrameCache(config); - const uidMap = this.renderers.get(videoSourceType)?.get(channelId); - const renderMap = uidMap?.get(uid); - if (!renderMap) { - return; - } - renderMap.renders?.forEach((renderItem) => { - renderItem.unbind(); - }); - renderMap.renders = []; + public get rendererType(): RendererType { + return this._rendererType; } - /** - * @ignore - */ - public removeAllRenderer(): void { - const renderMap = this.forEachStream( - (renderConfig, videoFrameCacheConfig) => { - this.disableVideoFrameCache(videoFrameCacheConfig); - renderConfig.renders?.forEach((renderItem) => { - renderItem.unbind(); - }); - renderConfig.renders = []; - } - ); - renderMap.clear(); - } - - /** - * @ignore - */ - public clear(): void { - this.stopRender(); - this.removeAllRenderer(); - } - - /** - * Enables/Disables the local video capture. - * This method disables or re-enables the local video capture, and does not affect receiving the remote video stream.After calling enableVideo , the local video capture is enabled by default. You can call enableLocalVideo (false) to disable the local video capture. If you want to re-enable the local video capture, call enableLocalVideo(true).After the local video capturer is successfully disabled or re-enabled, the SDK triggers the onRemoteVideoStateChanged callback on the remote client.You can call this method either before or after joining a channel.This method enables the internal engine and is valid after leaving the channel. - * - * @param enabled Whether to enable the local video capture.true: (Default) Enable the local video capture.false: Disable the local video capture. Once the local video is disabled, the remote users cannot receive the video stream of the local user, while the local user can still receive the video streams of remote users. When set to false, this method does not require a local camera. - * - * @returns - * 0: Success.< 0: Failure. - */ - public enableRender(enabled = true): void { - if (enabled && this.isRendering) { - //is already _isRendering - } else if (enabled && !this.isRendering) { - this.startRenderer(); - } else { - this.stopRender(); - } + constructor() { + super(); + this._rendererType = isSupportWebGL() + ? RendererType.WEBGL + : RendererType.SOFTWARE; } /** - * @ignore + * @deprecated Use rendererType instead */ - public startRenderer(): void { - this.isRendering = true; - const renderFunc = ( - rendererItem: RenderConfig, - { videoSourceType, channelId, uid }: VideoFrameCacheConfig - ) => { - const { renders } = rendererItem; - if (!renders || renders?.length === 0) { - return; - } - let finalResult = this.msgBridge.GetVideoFrame( - rendererItem.shareVideoFrame - ); - - switch (finalResult.ret) { - case 0: - // GET_VIDEO_FRAME_CACHE_RETURN_TYPE::OK = 0, - // everything is ok - break; - case 1: { - // GET_VIDEO_FRAME_CACHE_RETURN_TYPE::RESIZED - const { width, height, yStride } = finalResult; - const newShareVideoFrame = this.resizeShareVideoFrame( - videoSourceType, - channelId, - uid, - width, - height, - yStride - ); - rendererItem.shareVideoFrame = newShareVideoFrame; - finalResult = this.msgBridge.GetVideoFrame(newShareVideoFrame); - break; - } - case 2: - // GET_VIDEO_FRAME_CACHE_RETURN_TYPE::NO_CACHE - // setupVideo/AgoraView render before initialize - // this.enableVideoFrameCache({ videoSourceType, channelId, uid }); - break; - default: - break; - } - if (finalResult.ret !== 0) { - logDebug('GetVideoFrame ret is', finalResult.ret, rendererItem); - return; - } - if (!finalResult.isNewFrame) { - logDebug('GetVideoFrame isNewFrame is false', rendererItem); - return; - } - const renderVideoFrame = rendererItem.shareVideoFrame; - if (renderVideoFrame.width > 0 && renderVideoFrame.height > 0) { - renders.forEach((renderItem) => { - renderItem.drawFrame(rendererItem.shareVideoFrame); - }); - } - }; - const render = () => { - this.forEachStream(renderFunc); - this.videoFrameUpdateInterval = setTimeout(render, 1000 / this.renderFps); - }; - render(); + public setRenderMode(mode: RENDER_MODE) { + this.rendererType = mode; } /** - * @ignore + * @deprecated Use renderingFps instead */ - public stopRender(): void { - this.isRendering = false; - if (this.videoFrameUpdateInterval) { - clearTimeout(this.videoFrameUpdateInterval); - this.videoFrameUpdateInterval = undefined; - } + public setFPS(fps: number) { + this.renderingFps = fps; } /** - * @ignore + * @deprecated Use getRendererCache instead */ - public restartRender(): void { - if (this.videoFrameUpdateInterval) { - this.stopRender(); - this.startRenderer(); - logInfo(`restartRender: Fps: ${this.renderFps} restartInterval`); - } + public getRender(context: RendererCacheContext): RendererCache | undefined { + return this.getRendererCache(context); } - /** - * @ignore - */ - private createRenderer( - renderMode?: RENDER_MODE, - fallback?: WebGLFallback + protected override createRenderer( + context: RendererContext, + rendererType?: RendererType ): IRenderer { - if (renderMode === RENDER_MODE.SOFTWARE) { - return new YUVCanvasRenderer(); - } else { - return new WebGLRenderer(fallback); + if (rendererType === undefined) { + rendererType = this.rendererType; } - } - /** - * @ignore - */ - private getRender({ - videoSourceType, - channelId, - uid, - }: VideoFrameCacheConfig) { - return this.renderers.get(videoSourceType)?.get(channelId)?.get(uid); - } - - /** - * @ignore - */ - private getRenderers({ - videoSourceType, - channelId, - uid, - }: VideoFrameCacheConfig): IRenderer[] { - return this.getRender({ videoSourceType, channelId, uid })?.renders || []; - } - - /** - * @ignore - */ - private bindHTMLElementToRender( - config: FormatRendererVideoConfig, - view: HTMLElement - ): IRenderer | undefined { - this.ensureRendererConfig(config); - const renderers = this.getRenderers(config); - const filterRenders = - renderers?.filter((render) => render.equalsElement(view)) || []; - const hasBeenAdd = filterRenders.length > 0; - if (hasBeenAdd) { - logWarn( - 'bindHTMLElementToRender: this view has bind to render', - filterRenders - ); - return filterRenders[0]; - } - const renderer = this.createRenderer( - this.renderMode, - this.handleWebGLFallback(config) - ); - renderer.bind(view); - renderers.push(renderer); - return renderer; - } - - /** - * @ignore - */ - private handleWebGLFallback = - (config: FormatRendererVideoConfig) => (renderer: WebGLRenderer) => { - const { contentMode, mirror } = renderer; - const view = renderer.parentElement!; - const renderers = this.getRenderers(config); - const index = renderers.indexOf(renderer); - renderer.unbind(); - const newRenderer = this.createRenderer(RENDER_MODE.SOFTWARE); - newRenderer.bind(view); - newRenderer.setRenderOption({ contentMode, mirror }); - renderers.splice(index, 1, newRenderer); + let renderer: IRenderer; + switch (rendererType) { + case RendererType.WEBGL: + renderer = new WebGLRenderer( + this.handleWebGLFallback(context).bind(this) + ); + break; + case RendererType.SOFTWARE: + renderer = new YUVCanvasRenderer(); + break; + default: + throw new Error('Unknown renderer type'); + } + + renderer.bind(context.view); + renderer.context = { + renderMode: context.renderMode, + mirrorMode: context.mirrorMode, }; - - /** - * @ignore - */ - private forEachStream( - callbackfn: ( - renderConfig: RenderConfig, - videoFrameCacheConfig: VideoFrameCacheConfig, - maps: { - channelMap: ChannelIdMap; - uidMap: UidMap; - } - ) => void - ): RenderMap { - const renders = this.renderers; - renders.forEach((channelMap, videoSourceType) => { - channelMap.forEach((uidMap, channelId) => { - uidMap.forEach((renderConfig, uid) => { - callbackfn( - renderConfig, - { videoSourceType, channelId, uid }, - { channelMap, uidMap } - ); - }); - }); - }); - return renders; - } - - /** - * @ignore - */ - private enableVideoFrameCache( - videoFrameCacheConfig: VideoFrameCacheConfig - ): void { - logDebug(`enableVideoFrameCache ${JSON.stringify(videoFrameCacheConfig)}`); - this.msgBridge.EnableVideoFrameCache(videoFrameCacheConfig); + return renderer; } - /** - * @ignore - */ - private disableVideoFrameCache( - videoFrameCacheConfig: VideoFrameCacheConfig - ): void { - logDebug(`disableVideoFrameCache ${JSON.stringify(videoFrameCacheConfig)}`); - this.msgBridge.DisableVideoFrameCache(videoFrameCacheConfig); + public override doRendering(rendererCache: RendererCache): void { + rendererCache.draw(); } - /** - * @ignore - */ - private ensureRendererConfig(config: VideoFrameCacheConfig): - | Map< - number, - { - shareVideoFrame: ShareVideoFrame; - renders: IRenderer[]; - } - > - | undefined { - const { videoSourceType, uid, channelId } = config; - const emptyRenderConfig = { - renders: [], - shareVideoFrame: this.resizeShareVideoFrame( - videoSourceType, - channelId, - uid - ), - }; - const emptyUidMap = new Map([[uid, emptyRenderConfig]]); - const emptyChannelMap = new Map([[channelId, emptyUidMap]]); - - const renderers = this.renderers; - const videoSourceMap = renderers.get(videoSourceType); - if (!videoSourceMap) { - renderers.set(videoSourceType, emptyChannelMap); - return emptyUidMap; - } - const channelMap = videoSourceMap.get(channelId); - if (!channelMap) { - videoSourceMap.set(channelId, emptyUidMap); - return emptyUidMap; - } - const renderConfig = channelMap?.get(uid); - if (!renderConfig) { - channelMap?.set(uid, emptyRenderConfig); - logWarn( - `ensureRendererMap uid map for channelId:${channelId} uid:${uid}` + private handleWebGLFallback(context: RendererContext): WebGLFallback { + return (renderer: WebGLRenderer) => { + const { + context: { renderMode, mirrorMode }, + } = renderer; + const renderers = this.getRenderers(context); + renderer.unbind(); + const newRenderer = this.createRenderer( + { ...context, renderMode, mirrorMode }, + RendererType.SOFTWARE ); - return emptyUidMap; - } - return channelMap; - } - - /** - * @ignore - */ - private resizeShareVideoFrame( - videoSourceType: VideoSourceType, - channelId: string, - uid: number, - width = 0, - height = 0, - yStride = 0 - ): ShareVideoFrame { - return { - videoSourceType, - channelId, - uid, - yBuffer: Buffer.alloc(yStride * height), - uBuffer: Buffer.alloc((yStride * height) / 4), - vBuffer: Buffer.alloc((yStride * height) / 4), - width, - height, - yStride, + renderers.splice(renderers.indexOf(renderer), 1, newRenderer); }; } - - /** - * @ignore - */ - public updateVideoFrameCacheInMap( - config: VideoFrameCacheConfig, - shareVideoFrame: ShareVideoFrame - ): void { - let rendererConfigMap = this.ensureRendererConfig(config); - rendererConfigMap - ? Object.assign(rendererConfigMap.get(config.uid) ?? {}, { - shareVideoFrame, - }) - : logWarn( - `updateVideoFrameCacheInMap videoSourceType:${config.videoSourceType} channelId:${config.channelId} uid:${config.uid} rendererConfigMap is null` - ); - } } diff --git a/ts/Renderer/WebGLRenderer/index.ts b/ts/Renderer/WebGLRenderer/index.ts index 59c44047c..d25fecde0 100644 --- a/ts/Renderer/WebGLRenderer/index.ts +++ b/ts/Renderer/WebGLRenderer/index.ts @@ -1,6 +1,5 @@ -import { RenderModeType } from '../../Private/AgoraMediaBase'; -import { ShareVideoFrame } from '../../Types'; -import { logError, logWarn } from '../../Utils'; +import { VideoFrame } from '../../Private/AgoraMediaBase'; +import { logWarn } from '../../Utils'; import { IRenderer } from '../IRenderer'; export type WebGLFallback = (renderer: WebGLRenderer, error: Error) => void; @@ -53,19 +52,6 @@ export class WebGLRenderer extends IRenderer { vTexture: WebGLTexture | null; texCoordBuffer: WebGLBuffer | null; surfaceBuffer: WebGLBuffer | null; - - initWidth = 0; - initHeight = 0; - initRotation = 0; - clientWidth = 0; - clientHeight = 0; - lastImageWidth = 0; - lastImageHeight = 0; - lastImageRotation = 0; - videoBuffer = {}; - - observer?: ResizeObserver; - fallback?: WebGLFallback; constructor(fallback?: WebGLFallback) { @@ -82,13 +68,6 @@ export class WebGLRenderer extends IRenderer { public override bind(view: HTMLElement) { super.bind(view); - const ResizeObserver = window.ResizeObserver; - if (ResizeObserver) { - this.observer = new ResizeObserver(this.refreshCanvas); - this.observer.observe(view); - } - - // context list after toggle resolution on electron 12.0.6 this.canvas?.addEventListener( 'webglcontextlost', this.handleContextLost, @@ -99,15 +78,63 @@ export class WebGLRenderer extends IRenderer { this.handleContextRestored, false ); - } - public override unbind() { - if (this.parentElement) { - this.observer?.unobserve(this.parentElement); + const getContext = ( + contextNames = ['webgl2', 'webgl', 'experimental-webgl'] + ): WebGLRenderingContext | WebGLRenderingContext | null => { + for (let i = 0; i < contextNames.length; i++) { + const contextName = contextNames[i]!; + const context = this.canvas?.getContext(contextName, { + depth: true, + stencil: true, + alpha: false, + antialias: false, + premultipliedAlpha: true, + preserveDrawingBuffer: false, + powerPreference: 'default', + failIfMajorPerformanceCaveat: true, + }); + if (context) { + return context as WebGLRenderingContext | WebGLRenderingContext; + } + } + return null; + }; + this.gl ??= getContext(); + + if (!this.gl) { + this.fallback?.call( + null, + this, + new Error('Browser not support! No WebGL detected.') + ); + return; } - this.observer?.disconnect(); - this.observer = undefined; + // Set clear color to black, fully opaque + this.gl.clearColor(0.0, 0.0, 0.0, 1.0); + // Enable depth testing + this.gl.enable(this.gl.DEPTH_TEST); + // Near things obscure far things + this.gl.depthFunc(this.gl.LEQUAL); + // Clear the color as well as the depth buffer. + this.gl.clear( + this.gl.COLOR_BUFFER_BIT | + this.gl.DEPTH_BUFFER_BIT | + this.gl.STENCIL_BUFFER_BIT + ); + + // Setup GLSL program + this.program = createProgramFromSources(this.gl, [ + vertexShaderSource, + yuvShaderSource, + ]) as WebGLProgram; + this.gl.useProgram(this.program); + + this.initTextures(); + } + + public override unbind() { this.canvas?.removeEventListener( 'webglcontextlost', this.handleContextLost, @@ -125,91 +152,45 @@ export class WebGLRenderer extends IRenderer { super.unbind(); } - public override drawFrame(videoFrame: ShareVideoFrame) { - let error; - try { - this.renderImage({ - width: videoFrame.width, - height: videoFrame.height, - left: 0, - top: 0, - right: videoFrame.yStride - videoFrame.width, - bottom: 0, - rotation: videoFrame.rotation || 0, - yplane: videoFrame.yBuffer, - uplane: videoFrame.uBuffer, - vplane: videoFrame.vBuffer, - }); - } catch (err) { - error = err; - } - if (!this.gl || error) { - this.fallback?.call( - null, - this, - new Error('webgl lost or webgl initialize failed') - ); - return; - } - } - - public override refreshCanvas() { - if (this.lastImageWidth) { - this.updateViewZoomLevel( - this.lastImageRotation, - this.lastImageWidth, - this.lastImageHeight - ); - } - } - - private renderImage(image: { - width: number; - height: number; - left: number; - top: number; - right: number; - bottom: number; - rotation: number; - yplane: Uint8Array; - uplane: Uint8Array; - vplane: Uint8Array; - }) { - // Rotation, width, height, left, top, right, bottom, yplane, uplane, vplane - - if ( - image.width != this.initWidth || - image.height != this.initHeight || - image.rotation != this.initRotation - ) { - const view = this.parentElement!; - this.unbind(); - - this.initCanvas(view, image.width, image.height, image.rotation); - } - - if (!this.gl || !this.program) { - return; - } + public override drawFrame({ + width, + height, + yStride, + uStride, + vStride, + yBuffer, + uBuffer, + vBuffer, + rotation, + }: VideoFrame) { + this.rotateCanvas({ width, height, rotation }); + this.updateRenderMode(); + + if (!this.gl || !this.program) return; + + const left = 0, + top = 0, + right = yStride! - width!, + bottom = 0; this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer); - const xWidth = image.width + image.left + image.right; - const xHeight = image.height + image.top + image.bottom; + const xWidth = width! + left + right; + const xHeight = height! + top + bottom; this.gl.bufferData( this.gl.ARRAY_BUFFER, new Float32Array([ - image.left / xWidth, - image.bottom / xHeight, - 1 - image.right / xWidth, - image.bottom / xHeight, - image.left / xWidth, - 1 - image.top / xHeight, - image.left / xWidth, - 1 - image.top / xHeight, - 1 - image.right / xWidth, - image.bottom / xHeight, - 1 - image.right / xWidth, - 1 - image.top / xHeight, + left / xWidth, + bottom / xHeight, + 1 - right / xWidth, + bottom / xHeight, + left / xWidth, + 1 - top / xHeight, + left / xWidth, + 1 - top / xHeight, + 1 - right / xWidth, + bottom / xHeight, + 1 - right / xWidth, + 1 - top / xHeight, ]), this.gl.STATIC_DRAW ); @@ -223,37 +204,20 @@ export class WebGLRenderer extends IRenderer { 0 ); - this.uploadYuv(xWidth, xHeight, image.yplane, image.uplane, image.vplane); - - this.updateCanvas(image.rotation, image.width, image.height); - this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); - } - - private uploadYuv( - width: number, - height: number, - yplane: Uint8Array, - uplane: Uint8Array, - vplane: Uint8Array - ) { - if (!this.gl || !this.yTexture || !this.uTexture || !this.vTexture) { - return; - } - this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT, 1); + this.gl.activeTexture(this.gl.TEXTURE0); this.gl.bindTexture(this.gl.TEXTURE_2D, this.yTexture); - this.gl.texImage2D( this.gl.TEXTURE_2D, 0, this.gl.LUMINANCE, - width, - height, + width!, + height!, 0, this.gl.LUMINANCE, this.gl.UNSIGNED_BYTE, - yplane + yBuffer! ); this.gl.activeTexture(this.gl.TEXTURE1); @@ -262,12 +226,12 @@ export class WebGLRenderer extends IRenderer { this.gl.TEXTURE_2D, 0, this.gl.LUMINANCE, - width / 2, - height / 2, + uStride!, + height! / 2, 0, this.gl.LUMINANCE, this.gl.UNSIGNED_BYTE, - uplane + uBuffer! ); this.gl.activeTexture(this.gl.TEXTURE2); @@ -276,34 +240,24 @@ export class WebGLRenderer extends IRenderer { this.gl.TEXTURE_2D, 0, this.gl.LUMINANCE, - width / 2, - height / 2, + vStride!, + height! / 2, 0, this.gl.LUMINANCE, this.gl.UNSIGNED_BYTE, - vplane + vBuffer! ); + + this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); } - private updateCanvas(rotation: number, width: number, height: number) { - // if (this.canvasUpdated) { - // return; - // } - if (width || height) { - this.lastImageWidth = width; - this.lastImageHeight = height; - this.lastImageRotation = rotation; - } else { - width = this.lastImageWidth; - height = this.lastImageHeight; - rotation = this.lastImageRotation; - } - if (!this.updateViewZoomLevel(rotation, width, height)) { - return; - } - if (!this.gl) { - return; - } + protected override rotateCanvas({ width, height, rotation }: VideoFrame) { + super.rotateCanvas({ width, height, rotation }); + + if (!this.gl) return; + + this.gl.viewport(0, 0, width!, height!); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.surfaceBuffer); this.gl.enableVertexAttribArray(this.positionLocation!); this.gl.vertexAttribPointer( @@ -321,9 +275,9 @@ export class WebGLRenderer extends IRenderer { // 180: 3,4,2/2,4,1 // 270: 4,1,3/3,1,2 const p1 = { x: 0, y: 0 }; - const p2 = { x: width, y: 0 }; - const p3 = { x: width, y: height }; - const p4 = { x: 0, y: height }; + const p2 = { x: width!, y: 0 }; + const p3 = { x: width!, y: height! }; + const p4 = { x: 0, y: height! }; let pp1 = p1, pp2 = p2, pp3 = p3, @@ -375,140 +329,7 @@ export class WebGLRenderer extends IRenderer { this.program!, 'u_resolution' ); - this.gl.uniform2f(resolutionLocation, width, height); - } - - private updateViewZoomLevel(rotation: number, width: number, height: number) { - if (!this.parentElement || !this.canvas) { - return; - } - this.clientWidth = this.parentElement.clientWidth; - this.clientHeight = this.parentElement.clientHeight; - - try { - if (this.contentMode === RenderModeType.RenderModeHidden) { - // Cover - if (rotation === 0 || rotation === 180) { - if (this.clientWidth / this.clientHeight > width / height) { - this.canvas.style.transform = `scale(${this.clientWidth / width})`; - } else { - this.canvas.style.transform = `scale(${ - this.clientHeight / height - })`; - } - } else { - // 90, 270 - if (this.clientHeight / this.clientWidth > width / height) { - this.canvas.style.transform = `scale(${this.clientHeight / width})`; - } else { - this.canvas.style.transform = `scale(${this.clientWidth / height})`; - } - } - // Contain - } else if (rotation === 0 || rotation === 180) { - if (this.clientWidth / this.clientHeight > width / height) { - this.canvas.style.transform = `scale(${this.clientHeight / height})`; - } else { - this.canvas.style.transform = `scale(${this.clientWidth / width})`; - } - } else { - // 90, 270 - if (this.clientHeight / this.clientWidth > width / height) { - this.canvas.style.transform = `scale(${this.clientWidth / height})`; - } else { - this.canvas.style.transform = `scale(${this.clientHeight / width})`; - } - } - } catch (e) { - logError('webgl updateViewZoomLevel', e); - return false; - } - - return true; - } - - private initCanvas( - view: HTMLElement, - width: number, - height: number, - rotation: number - ) { - this.clientWidth = view.clientWidth; - this.clientHeight = view.clientHeight; - - this.bind(view); - - if (this.canvas) { - if (rotation == 0 || rotation == 180) { - this.canvas.width = width; - this.canvas.height = height; - } else { - this.canvas.width = height; - this.canvas.height = width; - } - } - this.initWidth = width; - this.initHeight = height; - this.initRotation = rotation; - - try { - const getContext = ( - contextNames = ['webgl2', 'webgl', 'experimental-webgl'] - ): WebGLRenderingContext | WebGLRenderingContext | null => { - for (let i = 0; i < contextNames.length; i++) { - const contextName = contextNames[i]!; - const context = this.canvas?.getContext(contextName, { - depth: true, - stencil: true, - alpha: false, - antialias: false, - premultipliedAlpha: true, - preserveDrawingBuffer: false, - powerPreference: 'default', - failIfMajorPerformanceCaveat: true, - }); - if (context) { - return context as WebGLRenderingContext | WebGLRenderingContext; - } - } - return null; - }; - // Try to grab the standard context. If it fails, fallback to experimental. - this.gl ??= getContext(); - } catch (e) { - logWarn('webgl create happen some warming', this.gl, this.canvas); - } - - if (!this.gl) { - this.fallback?.call( - null, - this, - new Error('Browser not support! No WebGL detected.') - ); - return; - } - - // Set clear color to black, fully opaque - this.gl.clearColor(0.0, 0.0, 0.0, 1.0); - // Enable depth testing - this.gl.enable(this.gl.DEPTH_TEST); - // Near things obscure far things - this.gl.depthFunc(this.gl.LEQUAL); - // Clear the color as well as the depth buffer. - this.gl.clear( - this.gl.COLOR_BUFFER_BIT | - this.gl.DEPTH_BUFFER_BIT | - this.gl.STENCIL_BUFFER_BIT - ); - - // Setup GLSL program - this.program = createProgramFromSources(this.gl, [ - vertexShaderSource, - yuvShaderSource, - ]) as WebGLProgram; - this.gl.useProgram(this.program); - - this.initTextures(); + this.gl.uniform2f(resolutionLocation, width!, height!); } private initTextures() { @@ -533,7 +354,7 @@ export class WebGLRenderer extends IRenderer { this.gl.activeTexture(textureIndex); const texture = this.gl.createTexture(); this.gl.bindTexture(this.gl.TEXTURE_2D, texture); - // Set the parameters so we can render any size image. + // Set the parameters so we can render any size this.gl.texParameteri( this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, @@ -593,7 +414,7 @@ export class WebGLRenderer extends IRenderer { private handleContextLost = (event: Event) => { event.preventDefault(); - console.warn('webglcontextlost', event); + logWarn('webglcontextlost', event); this.releaseTextures(); @@ -606,7 +427,7 @@ export class WebGLRenderer extends IRenderer { private handleContextRestored = (event: Event) => { event.preventDefault(); - console.warn('webglcontextrestored', event); + logWarn('webglcontextrestored', event); // Setup GLSL program this.program = createProgramFromSources(this.gl, [ diff --git a/ts/Renderer/YUVCanvasRenderer/index.ts b/ts/Renderer/YUVCanvasRenderer/index.ts index 6b6f9b343..30fea481a 100644 --- a/ts/Renderer/YUVCanvasRenderer/index.ts +++ b/ts/Renderer/YUVCanvasRenderer/index.ts @@ -1,217 +1,64 @@ -import isEqual from 'lodash.isequal'; - -import { RenderModeType } from '../../Private/AgoraMediaBase'; -import { CanvasOptions, ShareVideoFrame } from '../../Types'; +import { VideoFrame } from '../../Private/AgoraMediaBase'; import { IRenderer } from '../IRenderer'; const YUVBuffer = require('yuv-buffer'); const YUVCanvas = require('yuv-canvas'); export class YUVCanvasRenderer extends IRenderer { - private _cacheCanvasOptions?: CanvasOptions; - private _yuvCanvasSink?: any; - private _videoFrame: ShareVideoFrame; - - constructor() { - super(); - this._videoFrame = { - rotation: 0, - width: 0, - height: 0, - yStride: 0, - yBuffer: new Uint8Array(0), - uBuffer: new Uint8Array(0), - vBuffer: new Uint8Array(0), - }; - } + private frameSink?: any; public override bind(element: HTMLElement) { super.bind(element); - this._yuvCanvasSink = YUVCanvas.attach(this.canvas, { + this.frameSink = YUVCanvas.attach(this.canvas, { webGL: false, }); } - public override unbind() { - if (this._yuvCanvasSink && this._yuvCanvasSink?.loseContext) { - this._yuvCanvasSink?.loseContext(); - } - super.unbind(); - } - - public override drawFrame(frame: ShareVideoFrame) { - if (!this.container || !this._yuvCanvasSink) { - return; - } - - let frameWidth = frame.width; - let frameHeight = frame.height; - - if ( - this._videoFrame.yStride === 0 || - this._videoFrame.height === 0 || - this._videoFrame.yStride != frame.yStride || - this._videoFrame.height != frame.height - ) { - this._videoFrame.yBuffer = new Uint8Array(frame.yStride * frameHeight); - this._videoFrame.uBuffer = new Uint8Array( - (frame.yStride * frameHeight) / 4 - ); - this._videoFrame.vBuffer = new Uint8Array( - (frame.yStride * frameHeight) / 4 - ); - } - - this._videoFrame.yBuffer.set(frame.yBuffer); - this._videoFrame.uBuffer.set(frame.uBuffer); - this._videoFrame.vBuffer.set(frame.vBuffer); - - this._videoFrame.width = frame.width; - this._videoFrame.height = frame.height; - this._videoFrame.yStride = frame.yStride; - this._videoFrame.rotation = frame.rotation; - - let options: CanvasOptions = { - frameWidth, - frameHeight, - rotation: frame.rotation ? frame.rotation : 0, - contentMode: this.contentMode, - clientWidth: this.container.clientWidth, - clientHeight: this.container.clientHeight, - }; - - this.updateCanvas(options); - - let format = YUVBuffer.format({ - width: frameWidth, - height: frameHeight, - chromaWidth: frameWidth / 2, - chromaHeight: frameHeight / 2, - cropLeft: frame.yStride - frameWidth, - }); - - let yuvBufferFrame = YUVBuffer.frame( - format, - { - bytes: this._videoFrame.yBuffer, - stride: frame.yStride, - }, - { - bytes: this._videoFrame.uBuffer, - stride: frame.yStride / 2, - }, - { - bytes: this._videoFrame.vBuffer, - stride: frame.yStride / 2, - } + public override drawFrame({ + width, + height, + yStride, + uStride, + vStride, + yBuffer, + uBuffer, + vBuffer, + rotation, + }: VideoFrame) { + this.rotateCanvas({ width, height, rotation }); + this.updateRenderMode(); + + if (!this.frameSink) return; + + this.frameSink.drawFrame( + YUVBuffer.frame( + YUVBuffer.format({ + width, + height, + chromaWidth: width! / 2, + chromaHeight: height! / 2, + cropLeft: yStride! - width!, + }), + { + bytes: yBuffer, + stride: yStride, + }, + { + bytes: uBuffer, + stride: uStride, + }, + { + bytes: vBuffer, + stride: vStride, + } + ) ); - this._yuvCanvasSink.drawFrame(yuvBufferFrame); - } - - public override refreshCanvas() { - if (this._cacheCanvasOptions) { - this.zoom( - this._cacheCanvasOptions.rotation === 90 || - this._cacheCanvasOptions.rotation === 270, - this._cacheCanvasOptions.contentMode, - this._cacheCanvasOptions.frameWidth, - this._cacheCanvasOptions.frameHeight, - this._cacheCanvasOptions.clientWidth, - this._cacheCanvasOptions.clientHeight - ); - } - } - - private updateCanvas( - options: CanvasOptions = { - frameWidth: 0, - frameHeight: 0, - rotation: 0, - contentMode: RenderModeType.RenderModeHidden, - clientWidth: 0, - clientHeight: 0, - } - ) { - if (this._cacheCanvasOptions) { - if (isEqual(this._cacheCanvasOptions, options)) { - return; - } - } - - this._cacheCanvasOptions = Object.assign({}, options); - - if (this.canvas) { - if (options.rotation === 0 || options.rotation === 180) { - this.canvas.width = options.frameWidth; - this.canvas.height = options.frameHeight; - Object.assign(this.canvas.style, { - 'width': options.frameWidth + 'px', - 'height': options.frameHeight + 'px', - 'object-fit': 'cover', - }); - } else if (options.rotation === 90 || options.rotation === 270) { - this.canvas.height = options.frameWidth; - this.canvas.width = options.frameHeight; - } else { - throw new Error( - 'Invalid value for rotation. Only support 0, 90, 180, 270' - ); - } - - let transformItems = []; - transformItems.push(`rotateZ(${options.rotation}deg)`); - - let scale = this.zoom( - options.rotation === 90 || options.rotation === 270, - options.contentMode, - options.frameWidth, - options.frameHeight, - options.clientWidth, - options.clientHeight - ); - - this.canvas.style.transform = `scale(${scale.toString()})`; - - if (transformItems.length > 0) { - this.canvas.style.transform += ` ${transformItems.join(' ')}`; - } - } } - private zoom( - vertical: boolean, - contentMode: RenderModeType = RenderModeType.RenderModeFit, - width: number, - height: number, - clientWidth: number, - clientHeight: number - ): number { - let localRatio = clientWidth / clientHeight; - let tempRatio = width / height; - if (isNaN(localRatio) || isNaN(tempRatio)) { - return 1; - } + protected override rotateCanvas({ width, height, rotation }: VideoFrame) { + super.rotateCanvas({ width, height, rotation }); - if (contentMode === RenderModeType.RenderModeHidden) { - if (vertical) { - return clientHeight / clientWidth < width / height - ? clientWidth / height - : clientHeight / width; - } else { - return clientWidth / clientHeight > width / height - ? clientWidth / width - : clientHeight / height; - } - } else { - if (vertical) { - return clientHeight / clientWidth < width / height - ? clientHeight / width - : clientWidth / height; - } else { - return clientWidth / clientHeight > width / height - ? clientHeight / height - : clientWidth / width; - } - } + if (!this.canvas) return; + this.canvas.style.transform += ` rotateZ(${rotation}deg)`; } } diff --git a/ts/Types.ts b/ts/Types.ts index 6dfb77f4e..704a14b20 100644 --- a/ts/Types.ts +++ b/ts/Types.ts @@ -1,5 +1,7 @@ -import { RenderModeType, VideoSourceType } from './Private/AgoraMediaBase'; -import { IRenderer, IRendererManager } from './Renderer'; +import { VideoCanvas } from './Private/AgoraBase'; +import { VideoFrame } from './Private/AgoraMediaBase'; +import { RtcConnection } from './Private/IAgoraRtcEngineEx'; +import { IRendererManager } from './Renderer'; /** * @ignore @@ -36,51 +38,7 @@ export interface AgoraEnvType extends AgoraEnvOptions { /** * @ignore */ -export interface CanvasOptions { - /** - * @ignore - */ - frameWidth: number; - /** - * @ignore - */ - frameHeight: number; - /** - * @ignore - */ - rotation: number; - /** - * @ignore - */ - contentMode: RenderModeType; - /** - * @ignore - */ - clientWidth: number; - /** - * @ignore - */ - clientHeight: number; -} - -/** - * @ignore - */ -export interface RendererOptions { - /** - * @ignore - */ - contentMode?: RenderModeType; - /** - * @ignore - */ - mirror?: boolean; -} - -/** - * @ignore - */ -export enum RENDER_MODE { +export enum RendererType { /** * @ignore */ @@ -91,129 +49,23 @@ export enum RENDER_MODE { SOFTWARE = 2, } -export type User = 'local' | 'videoSource' | number | string; - -export type Channel = '' | string; - -/** - * @ignore - */ -export interface RendererVideoConfig { - /** - * @ignore - */ - videoSourceType?: VideoSourceType; - /** - * @ignore - */ - channelId?: Channel; - /** - * @ignore - */ - uid?: number; - /** - * @ignore - */ - view?: HTMLElement; - /** - * @ignore - */ - rendererOptions?: RendererOptions; -} - /** * @ignore */ -export interface FormatRendererVideoConfig { - /** - * @ignore - */ - videoSourceType: VideoSourceType; - /** - * @ignore - */ - channelId: Channel; - /** - * @ignore - */ - uid: number; - /** - * @ignore - */ - view?: HTMLElement; - /** - * @ignore - */ - rendererOptions: RendererOptions; -} +export type RENDER_MODE = RendererType; /** * @ignore */ -export interface VideoFrameCacheConfig { - /** - * @ignore - */ - uid: number; - /** - * @ignore - */ - channelId: string; - /** - * @ignore - */ - videoSourceType: VideoSourceType; -} +export type RendererContext = VideoCanvas & RtcConnection; /** * @ignore */ -export interface ShareVideoFrame { - /** - * @ignore - */ - width: number; - /** - * @ignore - */ - height: number; - /** - * @ignore - */ - yStride: number; - /** - * @ignore - */ - yBuffer: Buffer | Uint8Array; - /** - * @ignore - */ - uBuffer: Buffer | Uint8Array; - /** - * @ignore - */ - vBuffer: Buffer | Uint8Array; - /** - * @ignore - */ - mirror?: boolean; - /** - * @ignore - */ - rotation?: number; - /** - * @ignore - */ - uid?: number; - /** - * @ignore - */ - channelId?: string; - /** - * @ignore - */ - videoSourceType?: VideoSourceType; -} +export type RendererCacheContext = Pick< + RendererContext, + 'channelId' | 'uid' | 'sourceType' +>; /** * @ignore @@ -260,20 +112,18 @@ export interface AgoraElectronBridge { ReleaseRenderer(): void; - EnableVideoFrameCache(config: VideoFrameCacheConfig): void; + EnableVideoFrameCache(context: RendererCacheContext): void; - DisableVideoFrameCache(config: VideoFrameCacheConfig): void; + DisableVideoFrameCache(context: RendererCacheContext): void; GetBuffer(ptr: number, length: number): Buffer; - GetVideoFrame(streamInfo: ShareVideoFrame): { + GetVideoFrame( + context: RendererCacheContext, + videoFrame: VideoFrame + ): { ret: number; isNewFrame: boolean; - yStride: number; - width: number; - height: number; - rotation: number; - timestamp: number; }; sendMsg: ( @@ -283,21 +133,3 @@ export interface AgoraElectronBridge { bufferCount?: number ) => Result; } - -/** - * @ignore - */ -export interface RenderConfig { - /** - * @ignore - */ - renders: IRenderer[]; - /** - * @ignore - */ - shareVideoFrame: ShareVideoFrame; -} - -export type UidMap = Map; -export type ChannelIdMap = Map; -export type RenderMap = Map; diff --git a/ts/Utils.ts b/ts/Utils.ts index f0b4adc7f..0933cccce 100644 --- a/ts/Utils.ts +++ b/ts/Utils.ts @@ -1,9 +1,4 @@ -import { VideoSourceType } from './Private/AgoraMediaBase'; -import { - AgoraEnvType, - FormatRendererVideoConfig, - RendererVideoConfig, -} from './Types'; +import { AgoraEnvType } from './Types'; /** * @ignore @@ -14,15 +9,6 @@ export const TAG = '[Agora]: '; */ export const DEBUG_TAG = '[Agora Debug]: '; -/** - * @ignore - */ -export const deprecate = (originApi?: string, replaceApi?: string) => - logError( - `${TAG} This method ${originApi} will be deprecated soon. `, - replaceApi ? `Please use ${replaceApi} instead` : '' - ); - /** * @ignore */ @@ -50,7 +36,7 @@ export const logInfo = (msg: string, ...optParams: any[]) => { if (!AgoraEnv.enableLogging) { return; } - console.log(`${TAG} ${msg}`, ...optParams); + console.info(`${TAG} ${msg}`, ...optParams); }; /** @@ -60,23 +46,7 @@ export const logDebug = (msg: string, ...optParams: any[]) => { if (!AgoraEnv.enableLogging || !AgoraEnv.enableDebugLogging) { return; } - console.warn(`${DEBUG_TAG} ${msg}`, ...optParams); -}; - -/** - * @ignore - */ -export const parseJSON = (jsonString: string) => { - if (jsonString === '') { - return jsonString; - } - let obj; - try { - obj = JSON.parse(jsonString); - } catch (error) { - logError('parseJSON', error); - } - return obj || jsonString; + console.debug(`${DEBUG_TAG} ${msg}`, ...optParams); }; /** @@ -93,71 +63,6 @@ export const objsKeysToLowerCase = (array: Array) => { }); }; -/** - * @ignore - */ -export const formatConfigByVideoSourceType = ( - videoSourceType?: VideoSourceType, - originChannelId = '', - originUid = 0 -): { - uid: number; - channelId: string; - videoSourceType: VideoSourceType; -} => { - if (videoSourceType === undefined || videoSourceType === null) { - throw new Error(`must set videoSourceType:${videoSourceType}`); - } - let uid = originUid; - let channelId = originChannelId; - - switch (videoSourceType) { - case VideoSourceType.VideoSourceCamera: - case VideoSourceType.VideoSourceCameraPrimary: - case VideoSourceType.VideoSourceScreen: - case VideoSourceType.VideoSourceScreenSecondary: - case VideoSourceType.VideoSourceTranscoded: - channelId = ''; - uid = 0; - break; - case VideoSourceType.VideoSourceRemote: - if (!uid || !channelId) { - throw new Error(`must set uid:${uid} and channelId:${channelId}`); - } - break; - case VideoSourceType.VideoSourceMediaPlayer: - channelId = ''; - if (!uid) { - throw new Error(`must set mediaPlayerId:${uid}`); - } - break; - default: - break; - } - return { uid, channelId, videoSourceType }; -}; - -/** - * @ignore - */ -export const getDefaultRendererVideoConfig = ( - config: RendererVideoConfig -): FormatRendererVideoConfig => { - const rendererOptions = Object.assign( - {}, - AgoraEnv.AgoraRendererManager?.defaultRenderConfig?.rendererOptions, - config.rendererOptions - ); - - const { uid, channelId, videoSourceType } = formatConfigByVideoSourceType( - config.videoSourceType, - config.channelId, - config.uid - ); - - return { ...config, uid, channelId, videoSourceType, rendererOptions }; -}; - /** * @ignore */ @@ -187,7 +92,38 @@ function copyProperties(target: T, source: any) { } } -const agora = require('../build/Release/agora_node_ext'); +/** + * @ignore + */ +export function isSupportWebGL(): boolean { + let flag = false; + const canvas: HTMLCanvasElement = document.createElement('canvas'); + try { + const getContext = ( + contextNames = ['webgl2', 'webgl', 'experimental-webgl'] + ): WebGLRenderingContext | WebGLRenderingContext | null => { + for (let i = 0; i < contextNames.length; i++) { + const contextName = contextNames[i]!; + const context = canvas?.getContext(contextName); + if (context) { + return context as WebGLRenderingContext | WebGLRenderingContext; + } + } + return null; + }; + let gl = getContext(); + flag = !!gl; + gl?.getExtension('WEBGL_lose_context')?.loseContext(); + gl = null; + logInfo('Your browser support webGL'); + } catch (e) { + logWarn('Your browser may not support webGL'); + flag = false; + } + return flag; +} + +const AgoraNode = require('../build/Release/agora_node_ext'); /** * @ignore @@ -196,5 +132,5 @@ export const AgoraEnv: AgoraEnvType = { enableLogging: true, enableDebugLogging: false, webEnvReady: true, - AgoraElectronBridge: new agora.AgoraElectronBridge(), + AgoraElectronBridge: new AgoraNode.AgoraElectronBridge(), };