@@ -6,6 +6,7 @@ import { TrackEvent } from '../events';
66import { Mutex , compareVersions , isMobile , sleep } from '../utils' ;
77import { Track , attachToElement , detachTrack } from './Track' ;
88import type { VideoCodec } from './options' ;
9+ import type { TrackProcessor } from './processor/types' ;
910
1011const defaultDimensionsTimeout = 1000 ;
1112
@@ -26,6 +27,12 @@ export default abstract class LocalTrack extends Track {
2627
2728 protected pauseUpstreamLock : Mutex ;
2829
30+ protected processorElement ?: HTMLMediaElement ;
31+
32+ protected processor ?: TrackProcessor < typeof this . kind > ;
33+
34+ protected isSettingUpProcessor : boolean = false ;
35+
2936 /**
3037 *
3138 * @param mediaTrack
@@ -77,6 +84,10 @@ export default abstract class LocalTrack extends Track {
7784 return this . providedByUser ;
7885 }
7986
87+ get mediaStreamTrack ( ) {
88+ return this . processor ?. processedTrack ?? this . _mediaStreamTrack ;
89+ }
90+
8091 async waitForDimensions ( timeout = defaultDimensionsTimeout ) : Promise < Track . Dimensions > {
8192 if ( this . kind === Track . Kind . Audio ) {
8293 throw new Error ( 'cannot get dimensions for audio tracks' ) ;
@@ -153,6 +164,9 @@ export default abstract class LocalTrack extends Track {
153164
154165 this . mediaStream = new MediaStream ( [ track ] ) ;
155166 this . providedByUser = userProvidedTrack ;
167+ if ( this . processor ) {
168+ await this . stopProcessor ( ) ;
169+ }
156170 return this ;
157171 }
158172
@@ -175,7 +189,7 @@ export default abstract class LocalTrack extends Track {
175189
176190 // detach
177191 this . attachedElements . forEach ( ( el ) => {
178- detachTrack ( this . _mediaStreamTrack , el ) ;
192+ detachTrack ( this . mediaStreamTrack , el ) ;
179193 } ) ;
180194 this . _mediaStreamTrack . removeEventListener ( 'ended' , this . handleEnded ) ;
181195 // on Safari, the old audio track must be stopped before attempting to acquire
@@ -198,12 +212,16 @@ export default abstract class LocalTrack extends Track {
198212
199213 await this . resumeUpstream ( ) ;
200214
201- this . attachedElements . forEach ( ( el ) => {
202- attachToElement ( newTrack , el ) ;
203- } ) ;
204-
205215 this . mediaStream = mediaStream ;
206216 this . constraints = constraints ;
217+ if ( this . processor ) {
218+ const processor = this . processor ;
219+ await this . setProcessor ( processor ) ;
220+ } else {
221+ this . attachedElements . forEach ( ( el ) => {
222+ attachToElement ( this . _mediaStreamTrack , el ) ;
223+ } ) ;
224+ }
207225 this . emit ( TrackEvent . Restarted , this ) ;
208226 return this ;
209227 }
@@ -248,6 +266,12 @@ export default abstract class LocalTrack extends Track {
248266 this . emit ( TrackEvent . Ended , this ) ;
249267 } ;
250268
269+ stop ( ) {
270+ super . stop ( ) ;
271+ this . processor ?. destroy ( ) ;
272+ this . processor = undefined ;
273+ }
274+
251275 /**
252276 * pauses publishing to the server without disabling the local MediaStreamTrack
253277 * this is used to display a user's own video locally while pausing publishing to
@@ -297,5 +321,81 @@ export default abstract class LocalTrack extends Track {
297321 }
298322 }
299323
324+ /**
325+ * Sets a processor on this track.
326+ * See https://github.com/livekit/track-processors-js for example usage
327+ *
328+ * @experimental
329+ *
330+ * @param processor
331+ * @param showProcessedStreamLocally
332+ * @returns
333+ */
334+ async setProcessor (
335+ processor : TrackProcessor < typeof this . kind > ,
336+ showProcessedStreamLocally = true ,
337+ ) {
338+ if ( this . isSettingUpProcessor ) {
339+ log . warn ( 'already trying to set up a processor' ) ;
340+ return ;
341+ }
342+ log . debug ( 'setting up processor' ) ;
343+ this . isSettingUpProcessor = true ;
344+ if ( this . processor ) {
345+ await this . stopProcessor ( ) ;
346+ }
347+ if ( this . kind === 'unknown' ) {
348+ throw TypeError ( 'cannot set processor on track of unknown kind' ) ;
349+ }
350+ this . processorElement = this . processorElement ?? document . createElement ( this . kind ) ;
351+ this . processorElement . muted = true ;
352+
353+ attachToElement ( this . _mediaStreamTrack , this . processorElement ) ;
354+ this . processorElement . play ( ) . catch ( ( e ) => log . error ( e ) ) ;
355+
356+ const processorOptions = {
357+ kind : this . kind ,
358+ track : this . _mediaStreamTrack ,
359+ element : this . processorElement ,
360+ } ;
361+
362+ await processor . init ( processorOptions ) ;
363+ this . processor = processor ;
364+ if ( this . processor . processedTrack ) {
365+ for ( const el of this . attachedElements ) {
366+ if ( el !== this . processorElement && showProcessedStreamLocally ) {
367+ detachTrack ( this . _mediaStreamTrack , el ) ;
368+ attachToElement ( this . processor . processedTrack , el ) ;
369+ }
370+ }
371+ await this . sender ?. replaceTrack ( this . processor . processedTrack ) ;
372+ }
373+ this . isSettingUpProcessor = false ;
374+ }
375+
376+ getProcessor ( ) {
377+ return this . processor ;
378+ }
379+
380+ /**
381+ * Stops the track processor
382+ * See https://github.com/livekit/track-processors-js for example usage
383+ *
384+ * @experimental
385+ * @returns
386+ */
387+ async stopProcessor ( ) {
388+ if ( ! this . processor ) return ;
389+
390+ log . debug ( 'stopping processor' ) ;
391+ this . processor . processedTrack ?. stop ( ) ;
392+ await this . processor . destroy ( ) ;
393+ this . processor = undefined ;
394+ this . processorElement ?. remove ( ) ;
395+ this . processorElement = undefined ;
396+
397+ await this . restart ( ) ;
398+ }
399+
300400 protected abstract monitorSender ( ) : void ;
301401}
0 commit comments