Wire Facebetter beauty into TRTC, Agora, LiveKit, and other browser realtime pipelines
SDK 2.0.0. The Web SDK has no native-style unified “GL texture preprocess callback”. Use this pipeline instead:Capture → processImage → draw to canvas → captureStream / take a MediaStreamTrack → hand it to the third-party SDK (replaceTrack or a custom video track).Auth uses licenseToken only. Fetch it from your server before init(). Never embed appKey in frontend JS. See Auth & License and Implement Beauty.
import { BeautyEffectEngine, EngineConfig, FrameType, MirrorMode,} from 'facebetter';const engine = new BeautyEffectEngine(new EngineConfig({ licenseToken: await fetch('/api/facebetter/auth', { method: 'POST' }).then((r) => r.text()),}));await engine.setLogConfig({ consoleEnabled: true, level: 2 });await engine.init();const video = document.createElement('video');video.playsInline = true;video.muted = true;video.srcObject = await navigator.mediaDevices.getUserMedia({ video: { width: 1280, height: 720 }, audio: false,});await video.play();const canvas = document.createElement('canvas');const ctx = canvas.getContext('2d');function loop() { if (engine.initialized && video.readyState >= 2 && video.videoWidth > 0) { const result = engine.processImage( video, video.videoWidth, video.videoHeight, FrameType.Video, MirrorMode.Horizontal, // front camera as needed ); if (result) { if (canvas.width !== result.width) canvas.width = result.width; if (canvas.height !== result.height) canvas.height = result.height; ctx.putImageData(result, 0, 0); } } requestAnimationFrame(loop);}loop();// Beauty video track — pass to any RTC / meeting SDKconst beautyStream = canvas.captureStream(30);const beautyTrack = beautyStream.getVideoTracks()[0];
Prefer processImage. processTexture is for existing GPU texture handles and does not apply mirror. For live preview / publish, use the canvas pipeline above.
On page unload: beautyTrack.stop(), cancel requestAnimationFrame, and engine.destroy().
Two common patterns (check your trtc-js-sdk / TUI version):
Create the local stream from the beauty track (custom video source / pass a MediaStreamTrack)
Create a camera stream first, then replaceTrack(beautyTrack)
// Sketch: replace the video track on a local stream// const localStream = TRTC.createStream({ ... });// await localStream.initialize();// await localStream.replaceTrack(beautyTrack);// await client.publish(localStream);
Do not assume a native-style onProcessVideoFrame(textureId) exists on Web.
Use a custom video track or replaceTrack on an existing track:
import AgoraRTC from 'agora-rtc-sdk-ng';// A: custom video track from MediaStreamTrack (API name per agora-rtc-sdk-ng)const customVideoTrack = AgoraRTC.createCustomVideoTrack({ mediaStreamTrack: beautyTrack,});await client.publish([customVideoTrack /*, microphoneTrack */]);// B: already have a camera track// await cameraTrack.replaceTrack(beautyTrack);
Pass the processed track into LiveKit’s local video track factory, or replace on a published track:
import { Room, LocalVideoTrack, createLocalVideoTrack } from 'livekit-client';// Sketch: create from MediaStreamTrack and publish (factory name per livekit-client)const track = LocalVideoTrack.createFromMediaStreamTrack(beautyTrack);await room.localParticipant.publishTrack(track);// Or on an existing LocalVideoTrack:// await localVideoTrack.replaceTrack(beautyTrack);