Skip to content

Commit

Permalink
deduplicate setTargetBitrate and setTargetBitrateAsync
Browse files Browse the repository at this point in the history
as usual, the code I removed is guaranteed to be
identical in both sides.

an IMPORTANT side effect is that we're first getting
the active layers and THEN doing everything else, which
means we're now retrieving these two things
  prevEncodingId = this.encodingId
  this.maxWidth || this.maxHeight
once the getActiveLayersAsync() has completed, not
before. I think this is better behavior than what we
were doing before.
  • Loading branch information
mildsunrise committed Nov 4, 2024
1 parent a81c86c commit fbbade0
Showing 1 changed file with 11 additions and 193 deletions.
204 changes: 11 additions & 193 deletions lib/Transponder.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,196 +301,7 @@ class Transponder extends Emitter
if (!this.track)
//Ignore
return;
//Current encoding
const prevEncodingId = this.encodingId;
//For optimum fit
let current = -1;
let encodingId = "";
let spatialLayerId = LayerInfo.MaxLayerId;
let temporalLayerId = LayerInfo.MaxLayerId;
//For minimum fit
let min = Number.MAX_SAFE_INTEGER;
let encodingIdMin = "";
let spatialLayerIdMin = LayerInfo.MaxLayerId;
let temporalLayerIdMin = LayerInfo.MaxLayerId;

let ordering = /** @type {(a: LayerInfo, b: LayerInfo) => number} */ (/** @type {any} */ (false));

//Helper for retrieving spatial info
const getSpatialLayerId = function(/** @type {LayerInfo} */ layer) {
// Either spatialLayerId on SVC stream or simulcastIdx on simulcast stream
return layer.spatialLayerId!=LayerInfo.MaxLayerId ? layer.spatialLayerId : layer.simulcastIdx ;
};

//Depending on the traversal method
switch (options?.traversal)
{
case "spatial-temporal":
ordering = (a,b) => ((getSpatialLayerId(b)*LayerInfo.MaxLayerId+b.temporalLayerId) - (getSpatialLayerId(a)*LayerInfo.MaxLayerId+a.temporalLayerId));
break;
case "zig-zag-spatial-temporal":
ordering = (a,b) => (((getSpatialLayerId(b)+b.temporalLayerId+1)*LayerInfo.MaxLayerId-b.temporalLayerId) - ((getSpatialLayerId(a)+a.temporalLayerId+1)*LayerInfo.MaxLayerId-a.temporalLayerId));
break;
case "temporal-spatial":
ordering = (a,b) => ((b.temporalLayerId*LayerInfo.MaxLayerId+getSpatialLayerId(b)) - (a.temporalLayerId*LayerInfo.MaxLayerId+getSpatialLayerId(a)));
break;
case "zig-zag-temporal-spatial":
ordering = (a,b) => (((getSpatialLayerId(b)+b.temporalLayerId+1)*LayerInfo.MaxLayerId-getSpatialLayerId(b)) - ((getSpatialLayerId(a)+a.temporalLayerId+1)*LayerInfo.MaxLayerId-getSpatialLayerId(a)));
break;
default:
//If we are filtering bymin/max we use the "spatial-tempral" ordering
//TODO: use (target)Width/(target)Height for ordering too?
if (this.maxWidth || this.maxHeight)
ordering = (a,b) => ((getSpatialLayerId(b)*LayerInfo.MaxLayerId+b.temporalLayerId) - (getSpatialLayerId(a)*LayerInfo.MaxLayerId+a.temporalLayerId));
}

//If we want to filter by codecs
const codecs = options?.codecs?.map(codec => codec.toLowerCase());
/** @type {((a: LayerInfo, b: LayerInfo) => number) | undefined} */
const codecSortByPreference = codecs
? (a,b) => codecs.indexOf(a.codec) - codecs.indexOf(b.codec)
: undefined;

//Get all active layers
const info = this.track.getActiveLayers();

//Filter layers by max TL and SL
const filtered = info.layers.filter(layer=> this.maxSpatialLayerId>=layer.spatialLayerId
&& this.maxTemporalLayerId>=layer.temporalLayerId
&& (!codecs || codecs.includes(layer?.codec.toLowerCase()))
);

//Get layers and filter by max TL & SL
//We expect spatial/simulcast layers to ensure that a layer with higher bitrate has also higher width/heights in order to be able to properly select based on maxWidth/maxHeight
let layers = filtered;

//If doing any sorting
if (ordering && codecSortByPreference)
//Order codec preferences in descending order and traversal
layers = layers.sort((a,b) => codecSortByPreference(a,b) || ordering(a,b));
else if (ordering)
//Order by traversal
layers = layers.sort(ordering);
else if (codecSortByPreference)
//Order codec preferences in descending order and bitrate
layers = layers.sort((a,b) => codecSortByPreference(a,b) || IncomingStreamTrack.sortByBitrateReverse(a,b));

//If there are no layers
if (!layers.length)
{
//Check if we want to select the default encoding if no active layers are found
const fallback = this.track.getDefaultEncoding();
if (options?.useDefaultEncoding && fallback)
//Try to select it
this.selectEncoding(fallback.id, options?.smooth);

//Important: Do not mute us!
//Not sending anything
return Object.assign(new Number(0),{
layerIndex : -1, //Do not set the defaultEncodingId as the layers are empty
layers : layers
});
}

//selected layer index
let layerMinIndex = 0;
let layerIndex = 0;
//Try to do layer selection instead
for (let layer of layers)
{
//Use the max of the actual and signaled target bitrate
const layerBitrate = layer.targetBitrate ? Math.max(layer.bitrate, layer.targetBitrate) : layer.bitrate;
//If this layer is better than the one before
if (layerBitrate<=target && layerBitrate>current &&
this.maxSpatialLayerId>=layer.spatialLayerId && this.maxTemporalLayerId>=layer.temporalLayerId &&
(!this.maxWidth || ((layer.width || layer.targetWidth) <= this.maxWidth)) &&
(!this.maxHeight || ((layer.height || layer.targetHeight) <= this.maxHeight))
)
{
//Use it as is
encodingId = layer.encodingId;
spatialLayerId = layer.spatialLayerId;
temporalLayerId = layer.temporalLayerId;
//Update max current bitrate
current = layerBitrate;
//we don't want to look more
break;
}
//Check if it is the minimum
if (layerBitrate && layerBitrate<min &&
this.maxSpatialLayerId>=layer.spatialLayerId && this.maxTemporalLayerId>=layer.temporalLayerId)
{
//Use it as min
layerMinIndex = layerIndex;
encodingIdMin = layer.encodingId;
spatialLayerIdMin = layer.spatialLayerId;
temporalLayerIdMin = layer.temporalLayerId;
//Update min bitrate
min = layerBitrate;
}
//Next
layerIndex++;
}

//Check if we have been able to find a layer that matched the target bitrate
if (current<=0)
{
//If we can use the minimun
if (!options || !options["strict"])
{
//Unmute (jic)
this.mute(false);
//Select mimimun as no layer is able to match the desired bitrate
this.selectEncoding(encodingIdMin);
//And temporal/spatial layers
this.selectLayer(spatialLayerIdMin,temporalLayerIdMin);
//Return minimun bitrate for selected encoding/layer
return Object.assign(new Number(min),{
layer : layers[layerMinIndex],
layerIndex : layerMinIndex,
encodingId : encodingIdMin,
spatialLayerId : spatialLayerIdMin,
temporalLayerId : temporalLayerIdMin,
layers : layers
});
} else {
//Mute it
this.mute(true);
//Not sending anything
return Object.assign(new Number(0),{
layerIndex : -1,
layers : layers
});
}
}
//Unmute (jic)
this.mute(false);
//Find previous simulcastIdx and new one
let prevSimulcastIdx = -1;
let newSilmulcastIdx = -1;
//For each active encoding
for (const encoding of info.active)
if (encoding.id == encodingId)
//Get the simulcast Idx of the layer we are switching to
newSilmulcastIdx = encoding.simulcastIdx;
else if (encoding.id == prevEncodingId)
//Get the simulcast Idx of the layer we are switching from
prevSimulcastIdx = encoding.simulcastIdx;
//We switch inmediatelly if going to a lower simulcast layer
const smooth = !options || options.smooth || newSilmulcastIdx >= prevSimulcastIdx;
//Select enccoding
this.selectEncoding(encodingId,smooth);
//And temporal/spatial layers
this.selectLayer(spatialLayerId,temporalLayerId);
//Return current bitrate for selected encoding/layer
return Object.assign(new Number(current),{
layer : layers[layerIndex],
layerIndex : layerIndex,
encodingId : encodingId,
spatialLayerId : spatialLayerId,
temporalLayerId : temporalLayerId,
layers : layers
});
return this.__setTargetBitrate(target, options, this.track.getActiveLayers());
}

/**
Expand All @@ -506,6 +317,16 @@ class Transponder extends Emitter
if (!this.track)
//Ignore
return;
return this.__setTargetBitrate(target, options, await this.track.getActiveLayersAsync());
}

/**
* @private
* @param {number} target
* @param {SetTargetBitrateOptions | undefined} options
* @param {import("./IncomingStreamTrack").ActiveLayersInfo} info
*/
__setTargetBitrate(target, options, info) {
//Current encoding
const prevEncodingId = this.encodingId;
//For optimum fit
Expand Down Expand Up @@ -556,9 +377,6 @@ class Transponder extends Emitter
? (a,b) => codecs.indexOf(a.codec) - codecs.indexOf(b.codec)
: undefined;

//Get all active layers
const info = await this.track.getActiveLayersAsync();

//Filter layers by max TL and SL
const filtered = info.layers.filter(layer=> this.maxSpatialLayerId>=layer.spatialLayerId
&& this.maxTemporalLayerId>=layer.temporalLayerId
Expand Down

0 comments on commit fbbade0

Please sign in to comment.