mirror of https://github.com/OpenVidu/openvidu.git
ov-components: optimize background processor application for video tracks
parent
435db94254
commit
87ec92ecc8
|
|
@ -15,6 +15,7 @@ import {
|
||||||
VideoPresets,
|
VideoPresets,
|
||||||
createLocalTracks
|
createLocalTracks
|
||||||
} from 'livekit-client';
|
} from 'livekit-client';
|
||||||
|
import { BackgroundProcessor, BackgroundProcessorWrapper, SwitchBackgroundProcessorOptions } from '@livekit/track-processors';
|
||||||
import { ILogger } from '../../models/logger.model';
|
import { ILogger } from '../../models/logger.model';
|
||||||
import { OpenViduComponentsConfigService } from '../config/directive-config.service';
|
import { OpenViduComponentsConfigService } from '../config/directive-config.service';
|
||||||
import { DeviceService } from '../device/device.service';
|
import { DeviceService } from '../device/device.service';
|
||||||
|
|
@ -51,6 +52,12 @@ export class OpenViduService {
|
||||||
private livekitUrl = '';
|
private livekitUrl = '';
|
||||||
private log: ILogger;
|
private log: ILogger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Background processor for video tracks. Initialized in disabled mode.
|
||||||
|
* This processor is shared between prejoin and in-room states.
|
||||||
|
*/
|
||||||
|
private backgroundProcessor: BackgroundProcessorWrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
|
@ -62,6 +69,7 @@ export class OpenViduService {
|
||||||
) {
|
) {
|
||||||
this.log = this.loggerSrv.get('OpenViduService');
|
this.log = this.loggerSrv.get('OpenViduService');
|
||||||
// this.isSttReadyObs = this._isSttReady.asObservable();
|
// this.isSttReadyObs = this._isSttReady.asObservable();
|
||||||
|
this.backgroundProcessor = BackgroundProcessor({ mode: 'disabled' });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -265,6 +273,18 @@ export class OpenViduService {
|
||||||
return this.localTracks;
|
return this.localTracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches the background mode on the local video track.
|
||||||
|
* Works both in prejoin and in-room states.
|
||||||
|
* @param options - The switch options (mode, blurRadius, imagePath)
|
||||||
|
* @returns Promise<void>
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
async switchBackgroundMode(options: SwitchBackgroundProcessorOptions): Promise<void> {
|
||||||
|
await this.backgroundProcessor.switchTo(options);
|
||||||
|
this.log.d('Background mode switched:', options);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
**/
|
**/
|
||||||
|
|
@ -337,6 +357,14 @@ export class OpenViduService {
|
||||||
newLocalTracks = await createLocalTracks(options);
|
newLocalTracks = await createLocalTracks(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply background processor to video track (initialized in disabled mode)
|
||||||
|
// This ensures the processor is attached before publishing for smooth transitions
|
||||||
|
const videoTrack = newLocalTracks.find((t) => t.kind === Track.Kind.Video) as LocalVideoTrack | undefined;
|
||||||
|
if (videoTrack) {
|
||||||
|
await videoTrack.setProcessor(this.backgroundProcessor);
|
||||||
|
this.log.d('Background processor applied to newly created video track');
|
||||||
|
}
|
||||||
|
|
||||||
// Mute tracks if devices are disabled
|
// Mute tracks if devices are disabled
|
||||||
if (!this.deviceService.isCameraEnabled()) {
|
if (!this.deviceService.isCameraEnabled()) {
|
||||||
newLocalTracks.find((t) => t.kind === Track.Kind.Video)?.mute();
|
newLocalTracks.find((t) => t.kind === Track.Kind.Video)?.mute();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { BackgroundOptions, BackgroundProcessor, ProcessorWrapper } from '@livekit/track-processors';
|
import { SwitchBackgroundProcessorOptions } from '@livekit/track-processors';
|
||||||
import { LocalVideoTrack, Track } from 'livekit-client';
|
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { BackgroundEffect, EffectType } from '../../models/background-effect.model';
|
import { BackgroundEffect, EffectType } from '../../models/background-effect.model';
|
||||||
import { ILogger } from '../../models/logger.model';
|
import { ILogger } from '../../models/logger.model';
|
||||||
import { LoggerService } from '../logger/logger.service';
|
import { LoggerService } from '../logger/logger.service';
|
||||||
import { OpenViduService } from '../openvidu/openvidu.service';
|
import { OpenViduService } from '../openvidu/openvidu.service';
|
||||||
import { ParticipantService } from '../participant/participant.service';
|
|
||||||
import { StorageService } from '../storage/storage.service';
|
import { StorageService } from '../storage/storage.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -47,17 +45,14 @@ export class VirtualBackgroundService {
|
||||||
private HARD_BLUR_INTENSITY = 60;
|
private HARD_BLUR_INTENSITY = 60;
|
||||||
|
|
||||||
private log: ILogger;
|
private log: ILogger;
|
||||||
private processor: ProcessorWrapper<BackgroundOptions>;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private participantService: ParticipantService,
|
|
||||||
private openviduService: OpenViduService,
|
private openviduService: OpenViduService,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private loggerSrv: LoggerService
|
private loggerSrv: LoggerService
|
||||||
) {
|
) {
|
||||||
this.log = this.loggerSrv.get('VirtualBackgroundService');
|
this.log = this.loggerSrv.get('VirtualBackgroundService');
|
||||||
this.backgroundIdSelected$ = this.backgroundIdSelected.asObservable();
|
this.backgroundIdSelected$ = this.backgroundIdSelected.asObservable();
|
||||||
this.processor = BackgroundProcessor({ mode: 'disabled' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getBackgrounds(): BackgroundEffect[] {
|
getBackgrounds(): BackgroundEffect[] {
|
||||||
|
|
@ -74,126 +69,59 @@ export class VirtualBackgroundService {
|
||||||
if (!!bgId) {
|
if (!!bgId) {
|
||||||
const background = this.backgrounds.find((bg) => bg.id === bgId);
|
const background = this.backgrounds.find((bg) => bg.id === bgId);
|
||||||
if (background) {
|
if (background) {
|
||||||
this.applyBackground(background);
|
await this.applyBackground(background);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a background effect to the local video track.
|
||||||
|
* Works both in prejoin (using OpenViduService's processor) and in-room states.
|
||||||
|
* The background processor is centralized in OpenViduService for consistency.
|
||||||
|
*/
|
||||||
async applyBackground(bg: BackgroundEffect) {
|
async applyBackground(bg: BackgroundEffect) {
|
||||||
// If the background is already applied, do nothing
|
// If the background is already applied, do nothing
|
||||||
if (this.backgroundIsAlreadyApplied(bg.id)) return;
|
if (this.backgroundIsAlreadyApplied(bg.id)) return;
|
||||||
|
|
||||||
const cameraTrack = this.getCameraTrack();
|
|
||||||
if (!cameraTrack) {
|
|
||||||
this.log.e('No camera track found. Cannot apply background.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// If no effect is selected, remove the background
|
const options = this.getBackgroundOptions(bg);
|
||||||
if (bg.type === EffectType.NONE) {
|
await this.openviduService.switchBackgroundMode(options);
|
||||||
await this.removeBackground();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentProcessor = cameraTrack.getProcessor() as ProcessorWrapper<BackgroundOptions>;
|
|
||||||
|
|
||||||
if (currentProcessor) {
|
|
||||||
await this.replaceBackground(currentProcessor, bg);
|
|
||||||
} else {
|
|
||||||
await this.applyProcessorToCameraTrack(cameraTrack, this.processor);
|
|
||||||
await this.replaceBackground(this.processor, bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.storageService.setBackground(bg.id);
|
this.storageService.setBackground(bg.id);
|
||||||
this.backgroundIdSelected.next(bg.id);
|
this.backgroundIdSelected.next(bg.id);
|
||||||
|
this.log.d('Background applied:', options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log.e('Error applying background effect:', error);
|
this.log.e('Error applying background effect:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getBackgroundOptions(bg: BackgroundEffect): BackgroundOptions {
|
|
||||||
if (bg.type === EffectType.IMAGE && bg.src) {
|
|
||||||
return { imagePath: bg.src, blurRadius: undefined, backgroundDisabled: false };
|
|
||||||
} else if (bg.type === EffectType.BLUR) {
|
|
||||||
return {
|
|
||||||
blurRadius: bg.id === 'soft_blur' ? this.SOFT_BLUR_INTENSITY : this.HARD_BLUR_INTENSITY,
|
|
||||||
imagePath: undefined,
|
|
||||||
backgroundDisabled: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return { backgroundDisabled: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeBackground() {
|
async removeBackground() {
|
||||||
if (this.isBackgroundApplied()) {
|
if (this.isBackgroundApplied()) {
|
||||||
this.backgroundIdSelected.next('no_effect');
|
this.backgroundIdSelected.next('no_effect');
|
||||||
const cameraTrack = this.getCameraTrack();
|
|
||||||
const processor = cameraTrack?.getProcessor() as ProcessorWrapper<BackgroundOptions>;
|
|
||||||
if (processor) {
|
|
||||||
try {
|
try {
|
||||||
await processor.updateTransformerOptions({ backgroundDisabled: true });
|
await this.openviduService.switchBackgroundMode({ mode: 'disabled' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.log.w('Error disabling processor:', e);
|
this.log.w('Error disabling processor:', e);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
this.storageService.removeBackground();
|
this.storageService.removeBackground();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private getBackgroundOptions(bg: BackgroundEffect): SwitchBackgroundProcessorOptions {
|
||||||
* Gets the camera track from either the published tracks (if in room) or local tracks (if in prejoin)
|
if (bg.type === EffectType.NONE) {
|
||||||
* @returns The camera LocalTrack or undefined if not found
|
return { mode: 'disabled' };
|
||||||
* @private
|
} else if (bg.type === EffectType.IMAGE && bg.src) {
|
||||||
*/
|
return { mode: 'virtual-background', imagePath: bg.src };
|
||||||
private getCameraTrack(): LocalVideoTrack | undefined {
|
} else if (bg.type === EffectType.BLUR) {
|
||||||
// First, try to get from published tracks (when in room)
|
return {
|
||||||
if (this.openviduService.isRoomConnected()) {
|
mode: 'background-blur',
|
||||||
const localParticipant = this.participantService.getLocalParticipant();
|
blurRadius: bg.id === 'soft_blur' ? this.SOFT_BLUR_INTENSITY : this.HARD_BLUR_INTENSITY
|
||||||
const cameraTrackPublication = localParticipant?.cameraTracks?.[0];
|
};
|
||||||
if (cameraTrackPublication?.track) {
|
|
||||||
return cameraTrackPublication.track as LocalVideoTrack;
|
|
||||||
}
|
}
|
||||||
}
|
return { mode: 'disabled' };
|
||||||
|
|
||||||
// Fallback to local tracks (when in prejoin or tracks not yet published)
|
|
||||||
const localTracks = this.openviduService.getLocalTracks();
|
|
||||||
const cameraTrack = localTracks.find((track) => track.kind === Track.Kind.Video);
|
|
||||||
return cameraTrack as LocalVideoTrack | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies a background processor to the camera track
|
|
||||||
* @param cameraTrack The camera track to apply the processor to
|
|
||||||
* @param processor The background processor to apply
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private async applyProcessorToCameraTrack(cameraTrack: LocalVideoTrack, processor: ProcessorWrapper<BackgroundOptions>): Promise<void> {
|
|
||||||
await cameraTrack.setProcessor(processor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private backgroundIsAlreadyApplied(backgroundId: string): boolean {
|
private backgroundIsAlreadyApplied(backgroundId: string): boolean {
|
||||||
return backgroundId === this.backgroundIdSelected.getValue();
|
return backgroundId === this.backgroundIdSelected.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces the current background effect with a new one by updating the processor options.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param currentProcessor - The current processor wrapper that handles background effects
|
|
||||||
* @param bg - The new background effect to apply
|
|
||||||
* @returns A Promise that resolves when the background options have been updated
|
|
||||||
* @throws Will throw an error if updating the background options fails
|
|
||||||
*/
|
|
||||||
private async replaceBackground(currentProcessor: ProcessorWrapper<BackgroundOptions>, bg: BackgroundEffect) {
|
|
||||||
try {
|
|
||||||
const options = this.getBackgroundOptions(bg);
|
|
||||||
// Update the processor with the new options
|
|
||||||
await currentProcessor.updateTransformerOptions(options);
|
|
||||||
this.log.d('Background options updated:', options);
|
|
||||||
} catch (error) {
|
|
||||||
this.log.e('Error updating background options:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue