@@ -15,10 +15,7 @@ import RTCEngine from '../RTCEngine';
15
15
import LocalAudioTrack from '../track/LocalAudioTrack' ;
16
16
import LocalTrack from '../track/LocalTrack' ;
17
17
import LocalTrackPublication from '../track/LocalTrackPublication' ;
18
- import LocalVideoTrack , {
19
- SimulcastTrackInfo ,
20
- videoLayersFromEncodings ,
21
- } from '../track/LocalVideoTrack' ;
18
+ import LocalVideoTrack , { videoLayersFromEncodings } from '../track/LocalVideoTrack' ;
22
19
import {
23
20
AudioCaptureOptions ,
24
21
CreateLocalTracksOptions ,
@@ -36,7 +33,7 @@ import { ParticipantTrackPermission, trackPermissionToProto } from './Participan
36
33
import { computeVideoEncodings , mediaTrackToLocalTrack } from './publishUtils' ;
37
34
import RemoteParticipant from './RemoteParticipant' ;
38
35
39
- const compatibleCodecForSVC = 'vp8' ;
36
+ const compatibleCodec = 'vp8' ;
40
37
export default class LocalParticipant extends Participant {
41
38
audioTracks : Map < string , LocalTrackPublication > ;
42
39
@@ -455,7 +452,6 @@ export default class LocalParticipant extends Participant {
455
452
// compute encodings and layers for video
456
453
let encodings : RTCRtpEncodingParameters [ ] | undefined ;
457
454
let simEncodings : RTCRtpEncodingParameters [ ] | undefined ;
458
- let simulcastTracks : SimulcastTrackInfo [ ] | undefined ;
459
455
if ( track . kind === Track . Kind . Video ) {
460
456
// TODO: support react native, which doesn't expose getSettings
461
457
const settings = track . mediaStreamTrack . getSettings ( ) ;
@@ -465,37 +461,38 @@ export default class LocalParticipant extends Participant {
465
461
req . width = width ?? 0 ;
466
462
req . height = height ?? 0 ;
467
463
// for svc codecs, disable simulcast and use vp8 for backup codec
468
- if (
469
- track instanceof LocalVideoTrack &&
470
- ( opts ?. videoCodec === 'vp9' || opts ?. videoCodec === 'av1' )
471
- ) {
472
- // set scalabilityMode to 'L3T3' by default
473
- opts . scalabilityMode = opts . scalabilityMode ?? 'L3T3' ;
474
-
475
- // add backup codec track
476
- const simOpts = { ...opts } ;
477
- simOpts . simulcast = true ;
478
- simOpts . scalabilityMode = undefined ;
479
- simEncodings = computeVideoEncodings (
480
- track . source === Track . Source . ScreenShare ,
481
- width ,
482
- height ,
483
- simOpts ,
484
- ) ;
485
- const simulcastTrack = track . addSimulcastTrack ( compatibleCodecForSVC , simEncodings ) ;
486
- simulcastTracks = [ simulcastTrack ] ;
487
- req . simulcastCodecs = [
488
- {
489
- codec : opts . videoCodec ,
490
- cid : track . mediaStreamTrack . id ,
491
- enableSimulcastLayers : true ,
492
- } ,
493
- {
494
- codec : simulcastTrack . codec ,
495
- cid : simulcastTrack . mediaStreamTrack . id ,
496
- enableSimulcastLayers : true ,
497
- } ,
498
- ] ;
464
+ if ( track instanceof LocalVideoTrack ) {
465
+ if ( opts ?. videoCodec === 'vp9' || opts ?. videoCodec === 'av1' ) {
466
+ // set scalabilityMode to 'L3T3' by default
467
+ opts . scalabilityMode = opts . scalabilityMode ?? 'L3T3' ;
468
+
469
+ // add backup codec track
470
+ const simOpts = { ...opts } ;
471
+ simOpts . simulcast = true ;
472
+ simOpts . scalabilityMode = undefined ;
473
+ simEncodings = computeVideoEncodings (
474
+ track . source === Track . Source . ScreenShare ,
475
+ width ,
476
+ height ,
477
+ simOpts ,
478
+ ) ;
479
+ }
480
+
481
+ // set vp8 codec as backup for any other codecs
482
+ if ( opts . videoCodec && opts . videoCodec !== 'vp8' ) {
483
+ req . simulcastCodecs = [
484
+ {
485
+ codec : opts . videoCodec ,
486
+ cid : track . mediaStreamTrack . id ,
487
+ enableSimulcastLayers : true ,
488
+ } ,
489
+ {
490
+ codec : compatibleCodec ,
491
+ cid : '' ,
492
+ enableSimulcastLayers : true ,
493
+ } ,
494
+ ] ;
495
+ }
499
496
}
500
497
501
498
encodings = computeVideoEncodings (
@@ -541,22 +538,6 @@ export default class LocalParticipant extends Participant {
541
538
track . codec = opts . videoCodec ;
542
539
}
543
540
544
- const localTrack = track as LocalVideoTrack ;
545
- if ( simulcastTracks ) {
546
- for await ( const simulcastTrack of simulcastTracks ) {
547
- const simTransceiverInit : RTCRtpTransceiverInit = { direction : 'sendonly' } ;
548
- if ( simulcastTrack . encodings ) {
549
- simTransceiverInit . sendEncodings = simulcastTrack . encodings ;
550
- }
551
- const simTransceiver = await this . engine . publisher ! . pc . addTransceiver (
552
- simulcastTrack . mediaStreamTrack ,
553
- simTransceiverInit ,
554
- ) ;
555
- this . setPreferredCodec ( simTransceiver , localTrack . kind , simulcastTrack . codec ) ;
556
- localTrack . setSimulcastTrackSender ( simulcastTrack . codec , simTransceiver . sender ) ;
557
- }
558
- }
559
-
560
541
this . engine . negotiate ( ) ;
561
542
562
543
// store RTPSender
@@ -574,6 +555,91 @@ export default class LocalParticipant extends Participant {
574
555
return publication ;
575
556
}
576
557
558
+ /** @internal
559
+ * publish additional codec to existing track
560
+ */
561
+ async publishAdditionalCodecForTrack (
562
+ track : LocalTrack | MediaStreamTrack ,
563
+ videoCodec : VideoCodec ,
564
+ options ?: TrackPublishOptions ,
565
+ ) {
566
+ const opts : TrackPublishOptions = {
567
+ ...this . roomOptions ?. publishDefaults ,
568
+ ...options ,
569
+ } ;
570
+ // clear scalabilityMode setting for backup codec
571
+ opts . scalabilityMode = undefined ;
572
+ opts . videoCodec = videoCodec ;
573
+ // is it not published? if so skip
574
+ let existingPublication : LocalTrackPublication | undefined ;
575
+ this . tracks . forEach ( ( publication ) => {
576
+ if ( ! publication . track ) {
577
+ return ;
578
+ }
579
+ if ( publication . track === track ) {
580
+ existingPublication = < LocalTrackPublication > publication ;
581
+ }
582
+ } ) ;
583
+ if ( ! existingPublication ) {
584
+ throw new TrackInvalidError ( 'track is not published' ) ;
585
+ }
586
+
587
+ if ( ! ( track instanceof LocalVideoTrack ) ) {
588
+ throw new TrackInvalidError ( 'track is not a video track' ) ;
589
+ }
590
+
591
+ const settings = track . mediaStreamTrack . getSettings ( ) ;
592
+ const width = settings . width ?? track . dimensions ?. width ;
593
+ const height = settings . height ?? track . dimensions ?. height ;
594
+
595
+ const encodings = computeVideoEncodings (
596
+ track . source === Track . Source . ScreenShare ,
597
+ width ,
598
+ height ,
599
+ opts ,
600
+ ) ;
601
+ const simulcastTrack = track . addSimulcastTrack ( opts . videoCodec , encodings ) ;
602
+ const req = AddTrackRequest . fromPartial ( {
603
+ cid : simulcastTrack . mediaStreamTrack . id ,
604
+ type : Track . kindToProto ( track . kind ) ,
605
+ muted : track . isMuted ,
606
+ source : Track . sourceToProto ( track . source ) ,
607
+ sid : track . sid ,
608
+ simulcastCodecs : [
609
+ {
610
+ codec : opts . videoCodec ,
611
+ cid : simulcastTrack . mediaStreamTrack . id ,
612
+ enableSimulcastLayers : opts . simulcast ,
613
+ } ,
614
+ ] ,
615
+ } ) ;
616
+ req . layers = videoLayersFromEncodings ( req . width , req . height , encodings ) ;
617
+
618
+ if ( ! this . engine || this . engine . isClosed ) {
619
+ throw new UnexpectedConnectionState ( 'cannot publish track when not connected' ) ;
620
+ }
621
+
622
+ const ti = await this . engine . addTrack ( req ) ;
623
+
624
+ if ( ! this . engine . publisher ) {
625
+ throw new UnexpectedConnectionState ( 'publisher is closed' ) ;
626
+ }
627
+ const transceiverInit : RTCRtpTransceiverInit = { direction : 'sendonly' } ;
628
+ if ( encodings ) {
629
+ transceiverInit . sendEncodings = encodings ;
630
+ }
631
+ // addTransceiver for react-native is async. web is synchronous, but await won't effect it.
632
+ const transceiver = await this . engine . publisher . pc . addTransceiver (
633
+ simulcastTrack . mediaStreamTrack ,
634
+ transceiverInit ,
635
+ ) ;
636
+ this . setPreferredCodec ( transceiver , track . kind , opts . videoCodec ) ;
637
+ track . setSimulcastTrackSender ( opts . videoCodec , transceiver . sender ) ;
638
+
639
+ this . engine . negotiate ( ) ;
640
+ log . debug ( `published ${ opts . videoCodec } for track ${ track . sid } ` , { encodings, trackInfo : ti } ) ;
641
+ }
642
+
577
643
unpublishTrack (
578
644
track : LocalTrack | MediaStreamTrack ,
579
645
stopOnUnpublish ?: boolean ,
@@ -761,7 +827,7 @@ export default class LocalParticipant extends Participant {
761
827
this . onTrackMuted ( track , track . isMuted ) ;
762
828
} ;
763
829
764
- private handleSubscribedQualityUpdate = ( update : SubscribedQualityUpdate ) => {
830
+ private handleSubscribedQualityUpdate = async ( update : SubscribedQualityUpdate ) => {
765
831
if ( ! this . roomOptions ?. dynacast ) {
766
832
return ;
767
833
}
@@ -774,7 +840,14 @@ export default class LocalParticipant extends Participant {
774
840
return ;
775
841
}
776
842
if ( update . subscribedCodecs . length > 0 ) {
777
- pub . videoTrack ?. setPublishingCodecs ( update . subscribedCodecs ) ;
843
+ if ( ! pub . videoTrack ) {
844
+ return ;
845
+ }
846
+ const newCodecs = await pub . videoTrack . setPublishingCodecs ( update . subscribedCodecs ) ;
847
+ for await ( const codec of newCodecs ) {
848
+ log . debug ( `publish ${ codec } for ${ pub . videoTrack . sid } ` ) ;
849
+ await this . publishAdditionalCodecForTrack ( pub . videoTrack , codec , pub . options ) ;
850
+ }
778
851
} else if ( update . subscribedQualities . length > 0 ) {
779
852
pub . videoTrack ?. setPublishingLayers ( update . subscribedQualities ) ;
780
853
}
0 commit comments