Skip to content

Commit

Permalink
feat: Codec Switching
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverlaz committed Nov 8, 2024
1 parent fae292a commit ddf5f43
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 4 deletions.
31 changes: 31 additions & 0 deletions packages/client/src/gen/video/sfu/event/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,24 @@ export interface SfuEvent {
*/
participantMigrationComplete: ParticipantMigrationComplete;
}
| {
oneofKind: 'codecNegotiationComplete';
/**
* CodecNegotiationComplete is sent to signal the completion of a codec negotiation.
* SDKs can safely stop previous transceivers
*
* @generated from protobuf field: stream.video.sfu.event.CodecNegotiationComplete codec_negotiation_complete = 26;
*/
codecNegotiationComplete: CodecNegotiationComplete;
}
| {
oneofKind: undefined;
};
}
/**
* @generated from protobuf message stream.video.sfu.event.CodecNegotiationComplete
*/
export interface CodecNegotiationComplete {}
/**
* @generated from protobuf message stream.video.sfu.event.ParticipantMigrationComplete
*/
Expand Down Expand Up @@ -961,6 +975,13 @@ class SfuEvent$Type extends MessageType<SfuEvent> {
oneof: 'eventPayload',
T: () => ParticipantMigrationComplete,
},
{
no: 26,
name: 'codec_negotiation_complete',
kind: 'message',
oneof: 'eventPayload',
T: () => CodecNegotiationComplete,
},
]);
}
}
Expand All @@ -969,6 +990,16 @@ class SfuEvent$Type extends MessageType<SfuEvent> {
*/
export const SfuEvent = new SfuEvent$Type();
// @generated message type with reflection information, may provide speed optimized methods
class CodecNegotiationComplete$Type extends MessageType<CodecNegotiationComplete> {
constructor() {
super('stream.video.sfu.event.CodecNegotiationComplete', []);
}
}
/**
* @generated MessageType for protobuf message stream.video.sfu.event.CodecNegotiationComplete
*/
export const CodecNegotiationComplete = new CodecNegotiationComplete$Type();
// @generated message type with reflection information, may provide speed optimized methods
class ParticipantMigrationComplete$Type extends MessageType<ParticipantMigrationComplete> {
constructor() {
super('stream.video.sfu.event.ParticipantMigrationComplete', []);
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/rtc/Dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const sfuEventKinds: { [key in SfuEventKinds]: undefined } = {
callEnded: undefined,
participantUpdated: undefined,
participantMigrationComplete: undefined,
codecNegotiationComplete: undefined,
};

export const isSfuEvent = (
Expand Down
36 changes: 32 additions & 4 deletions packages/client/src/rtc/Publisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { getOptimalVideoCodec, getPreferredCodecs, isSvcCodec } from './codecs';
import { trackTypeToParticipantStreamKey } from './helpers/tracks';
import { CallingState, CallState } from '../store';
import { PublishOptions } from '../types';
import { PreferredCodec, PublishOptions } from '../types';
import {
enableHighQualityAudio,
extractMid,
Expand Down Expand Up @@ -61,6 +61,7 @@ export class Publisher {
* @internal
*/
private readonly transceiverInitOrder: TrackType[] = [];
private readonly transceiverOrder: RTCRtpTransceiver[] = [];
private readonly isDtxEnabled: boolean;
private readonly isRedEnabled: boolean;

Expand All @@ -71,6 +72,8 @@ export class Publisher {
private isIceRestarting = false;
private sfuClient: StreamSfuClient;

private readonly dispatcher: Dispatcher;

/**
* Constructs a new `Publisher` instance.
*/
Expand All @@ -90,6 +93,7 @@ export class Publisher {
this.state = state;
this.isDtxEnabled = isDtxEnabled;
this.isRedEnabled = isRedEnabled;
this.dispatcher = dispatcher;
this.onUnrecoverableError = onUnrecoverableError;

this.unsubscribeOnIceRestart = dispatcher.on('iceRestart', (iceRestart) => {
Expand Down Expand Up @@ -246,6 +250,7 @@ export class Publisher {

this.logger('debug', `Added ${TrackType[trackType]} transceiver`);
this.transceiverInitOrder.push(trackType);
this.transceiverOrder.push(transceiver);
this.transceiverCache.set(trackType, transceiver);
this.publishOptsForTrack.set(trackType, opts);

Expand Down Expand Up @@ -286,6 +291,24 @@ export class Publisher {
await transceiver.sender.replaceTrack(track);
};

publishTrack = (codec: PreferredCodec) => {
const currentTransceiver = this.transceiverCache.get(TrackType.VIDEO);
if (!currentTransceiver || !currentTransceiver.sender.track) return;
const track = currentTransceiver.sender.track;

const negotiationComplete = async () => {
this.dispatcher.off('codecNegotiationComplete', negotiationComplete);
this.logger('info', 'Codec negotiation complete');

await currentTransceiver.sender.replaceTrack(null);
currentTransceiver.stop();
};
this.dispatcher.on('codecNegotiationComplete', negotiationComplete);

const ms = new MediaStream([track]);
this.addTransceiver(TrackType.VIDEO, track, { preferredCodec: codec }, ms);
};

/**
* Stops publishing the given track type to the SFU, if it is currently being published.
* Underlying track will be stopped and removed from the publisher.
Expand Down Expand Up @@ -586,11 +609,12 @@ export class Publisher {
return this.pc
.getTransceivers()
.filter((t) => t.direction === 'sendonly' && t.sender.track)
.map<TrackInfo>((transceiver) => {
.map<TrackInfo | undefined>((transceiver) => {
let trackType!: TrackType;
this.transceiverCache.forEach((value, key) => {
if (value === transceiver) trackType = key;
});
if (!trackType) return;

Check warning on line 617 in packages/client/src/rtc/Publisher.ts

View workflow job for this annotation

GitHub Actions / test-and-build

Array.prototype.map() expects a return value from arrow function
const track = transceiver.sender.track!;
let optimalLayers: OptimalVideoLayer[];
const isTrackLive = track.readyState === 'live';
Expand Down Expand Up @@ -627,17 +651,21 @@ export class Publisher {
const isStereo = isAudioTrack && trackSettings.channelCount === 2;
const transceiverInitIndex =
this.transceiverInitOrder.indexOf(trackType);
const mid = this.transceiverOrder.indexOf(transceiver);

return {
trackId: track.id,
layers: layers,
trackType,
mid: extractMid(transceiver, transceiverInitIndex, sdp),
mid:
String(mid) ?? extractMid(transceiver, transceiverInitIndex, sdp),
stereo: isStereo,
dtx: isAudioTrack && this.isDtxEnabled,
red: isAudioTrack && this.isRedEnabled,
muted: !isTrackLive,
};
});
})
.filter(Boolean) as TrackInfo[];
};

private computeLayers = (
Expand Down

0 comments on commit ddf5f43

Please sign in to comment.