Skip to content

Commit

Permalink
Export Layer and allow updating params per layer (#175)
Browse files Browse the repository at this point in the history
* Export Layer type
* Allow tweaking encoding params per layer by using optional layer param. This can be used to disable certain layer (for example keep high layer disabled and enable it only for main speaker)

* Adding support for active layer channel message

Support legacy channel message
  • Loading branch information
dreamerns committed May 12, 2021
1 parent efcfba4 commit 2cd134d
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 18 deletions.
42 changes: 39 additions & 3 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export interface Trickle {
target: Role;
}

export interface ActiveLayer {
streamId: string,
activeLayer: string,
availableLayers: string[]
}

enum Role {
pub = 0,
sub = 1,
Expand Down Expand Up @@ -73,6 +79,7 @@ export default class Client {
offer?: RTCSessionDescriptionInit,
answer?: RTCSessionDescriptionInit,
) => void;
onactivelayer?: (al: ActiveLayer) => void;

constructor(
signal: Signal,
Expand Down Expand Up @@ -111,9 +118,14 @@ export default class Client {
this.transports![Role.sub].pc.ondatachannel = (ev: RTCDataChannelEvent) => {
if (ev.channel.label === API_CHANNEL) {
this.transports![Role.sub].api = ev.channel;
this.transports![Role.pub].api = ev.channel;
ev.channel.onmessage = (e) => {
if (this.onspeaker) {
this.onspeaker(JSON.parse(e.data));
try {
const msg = JSON.parse(e.data);
this.processChannelMessage(msg);
} catch (err) {
/* tslint:disable-next-line:no-console */
console.error(err);
}
};
resolve();
Expand Down Expand Up @@ -162,7 +174,7 @@ export default class Client {
if (!this.transports) {
throw Error(ERR_NO_SESSION);
}
stream.publish(this.transports[Role.pub].pc);
stream.publish(this.transports[Role.pub]);
}

createDataChannel(label: string) {
Expand Down Expand Up @@ -228,4 +240,28 @@ export default class Client {
if (this.onerrnegotiate) this.onerrnegotiate(Role.pub, err, offer, answer);
}
}

private processChannelMessage(msg: any) {
if (msg.method !== undefined && msg.params !== undefined) {
switch (msg.method) {
case "audioLevels":
if (this.onspeaker) {
this.onspeaker(msg.params);
}
break;
case "activeLayer":
if (this.onactivelayer) {
this.onactivelayer(msg.params);
}
break;
default:
// do nothing
}
} else {
// legacy channel message - payload contains audio levels
if (this.onspeaker) {
this.onspeaker(msg)
}
}
}
}
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Client from './client';
import { LocalStream, RemoteStream, Constraints } from './stream';
import { LocalStream, RemoteStream, Constraints, Layer } from './stream';
import { Signal, Trickle } from './signal';
export { Client, LocalStream, RemoteStream, Constraints, Signal, Trickle };
export { Client, LocalStream, RemoteStream, Constraints, Signal, Trickle, Layer };
55 changes: 42 additions & 13 deletions src/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const VideoConstraints: VideoConstraints = {
},
};

type Layer = 'low' | 'medium' | 'high';
export type Layer = 'low' | 'medium' | 'high';

export interface Encoding {
layer: Layer;
Expand Down Expand Up @@ -158,6 +158,7 @@ export class LocalStream extends MediaStream {

constraints: Constraints;
pc?: RTCPeerConnection;
api?: RTCDataChannel;

constructor(stream: MediaStream, constraints: Constraints) {
super(stream);
Expand Down Expand Up @@ -229,7 +230,6 @@ export class LocalStream extends MediaStream {
maxFramerate: VideoConstraints[resolutions[idx - 2]].encodings.maxFramerate,
});
}

const transceiver = this.pc.addTransceiver(track, {
streams: [this],
direction: 'sendonly',
Expand Down Expand Up @@ -327,8 +327,9 @@ export class LocalStream extends MediaStream {
return stream.getVideoTracks()[0];
}

publish(pc: RTCPeerConnection) {
this.pc = pc;
publish(transport: Transport) {
this.pc = transport.pc;
this.api = transport.api;
this.getTracks().forEach(this.publishTrack.bind(this));
}

Expand Down Expand Up @@ -383,22 +384,50 @@ export class LocalStream extends MediaStream {
this.updateTrack(track, prev);
}

updateMediaEncodingParams(encodingParams: RTCRtpEncodingParameters) {
async enableLayers(layers: Layer[]) {
const call = {
streamId: this.id,
layers
};
const callStr = JSON.stringify(call);

if (this.api) {
if (this.api.readyState !== 'open') {
// queue call if we aren't open yet
this.api.onopen = () => this.api?.send(JSON.stringify(call));
} else {
this.api.send(JSON.stringify(call));
}
}
const layerValues = ['high', 'medium', 'low'] as const;
await Promise.all(layerValues.map(async (layer) => {
await this.updateMediaEncodingParams({active: layers.includes(layer)}, layer)
}));
}

async updateMediaEncodingParams(encodingParams: RTCRtpEncodingParameters, layer?: Layer) {
if (!this.pc) return;
this.getTracks().forEach((track) => {
const senders = this.pc?.getSenders()?.filter((sender) => track.id === sender.track?.id);
senders?.forEach((sender) => {
const tracks = this.getTracks();
await Promise.all(this.pc?.getSenders()
.filter(sender => sender.track && tracks.includes(sender.track))
.map(async (sender) => {
const params = sender.getParameters();
if (!params.encodings) {
params.encodings = [{}];
}
params.encodings[0] = {
...params.encodings[0],
let idx = 0;
if (this.constraints.simulcast && layer) {
const rid = layer === 'high' ? 'f' : layer === 'medium' ? 'h' : 'q';
idx = params.encodings.findIndex(encoding => encoding.rid === rid);
if (params.encodings.length < idx + 1) return;
}
params.encodings[idx] = {
...params.encodings[idx],
...encodingParams,
};
sender.setParameters(params);
});
});
await sender.setParameters(params);
})
);
}
}

Expand Down

0 comments on commit 2cd134d

Please sign in to comment.