rtp.js
Version:
RTP stack for Node.js and browser written in TypeScript
227 lines (226 loc) • 7.61 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LrleExtendedReport = void 0;
const ExtendedReport_1 = require("./ExtendedReport");
const helpers_1 = require("../../../utils/helpers");
const bitOps_1 = require("../../../utils/bitOps");
// Common header + SSRC of source + begin seq + end seq.
const LRLE_EXTENDED_REPORT_MIN_LENGTH = ExtendedReport_1.COMMON_HEADER_LENGTH + 8;
/**
* Loss RLE Extended Report.
*
* ```text
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | BT=1 | rsvd. | T | block length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC of source |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | begin_seq | end_seq |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | chunk 1 | chunk 2 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* : ... :
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | chunk n-1 | chunk n |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* ```
*
* @category RTCP Extended Reports
*
* @see
* - [RFC 3611 section 4.1](https://datatracker.ietf.org/doc/html/rfc3611#section-4.1)
*/
class LrleExtendedReport extends ExtendedReport_1.ExtendedReport {
// Chunks (2 bytes numbers, unparsed).
#chunks = [];
/**
* @param view - If given it will be parsed. Otherwise an empty Loss RLE
* Extended Report will be created.
*/
constructor(view) {
super(ExtendedReport_1.ExtendedReportType.LRLE, view);
if (!this.view) {
this.view = new DataView(new ArrayBuffer(LRLE_EXTENDED_REPORT_MIN_LENGTH));
// Write report type.
this.writeCommonHeader();
return;
}
if (this.view.byteLength < LRLE_EXTENDED_REPORT_MIN_LENGTH) {
throw new TypeError('wrong byte length for a Loss RLE Extended Report');
}
// Position relative to the DataView byte offset.
let pos = 0;
// Move to chunks.
pos += LRLE_EXTENDED_REPORT_MIN_LENGTH;
while (pos < this.view.byteLength) {
const chunk = this.view.getUint16(pos);
if (chunk === 0) {
break;
}
this.#chunks.push(chunk);
pos += 2;
}
}
/**
* Dump Loss RLE Extended Report info.
*/
dump() {
return {
...super.dump(),
thinning: this.getThinning(),
ssrc: this.getSsrc(),
beginSeq: this.getBeginSeq(),
endSeq: this.getEndSeq(),
chunks: this.getChunks(),
};
}
/**
* @inheritDoc
*/
getByteLength() {
if (!this.needsSerialization()) {
return this.view.byteLength;
}
// Common header + SSRC + begin seq + end seq.
let reportLength = LRLE_EXTENDED_REPORT_MIN_LENGTH;
// Add chunks.
reportLength += this.#chunks.length * 2;
// The list of chunks must terminate in terminating null chunks, which
// basically means padding them to 4 bytes.
reportLength = (0, helpers_1.padTo4Bytes)(reportLength);
return reportLength;
}
/**
* @inheritDoc
*/
serialize(buffer, byteOffset) {
const view = this.serializeBase(buffer, byteOffset);
const uint8Array = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
// Position relative to the DataView byte offset.
let pos = 0;
// Move to the fixed header fields after the common header.
pos += ExtendedReport_1.COMMON_HEADER_LENGTH;
// Copy the rest of the fixed fields into the new buffer.
uint8Array.set(new Uint8Array(this.view.buffer, this.view.byteOffset + pos, LRLE_EXTENDED_REPORT_MIN_LENGTH - ExtendedReport_1.COMMON_HEADER_LENGTH), pos);
// Move to chunks.
pos += LRLE_EXTENDED_REPORT_MIN_LENGTH - ExtendedReport_1.COMMON_HEADER_LENGTH;
// Copy chunks.
for (const chunk of this.#chunks) {
view.setUint16(pos, chunk);
pos += 2;
}
// NOTE: Must pad the content to 4 bytes.
pos = (0, helpers_1.padTo4Bytes)(pos);
if (pos !== view.byteLength) {
throw new RangeError(`filled length (${pos} bytes) does not match the available buffer size (${view.byteLength} bytes)`);
}
// Update DataView.
this.view = view;
this.setSerializationNeeded(false);
}
/**
* @inheritDoc
*/
clone(buffer, byteOffset, serializationBuffer, serializationByteOffset) {
const view = this.cloneInternal(buffer, byteOffset, serializationBuffer, serializationByteOffset);
return new LrleExtendedReport(view);
}
/**
* Get thinning.
*/
getThinning() {
return (0, bitOps_1.readBitsInDataView)({ view: this.view, pos: 1, mask: 0x0f });
}
/**
* Set thinning.
*/
setThinning(thinning) {
(0, bitOps_1.writeBitsInDataView)({
view: this.view,
pos: 1,
mask: 0x0f,
value: thinning,
});
this.setSerializationNeeded(true);
}
/**
* Get SSRC of source.
*/
getSsrc() {
return this.view.getUint32(4);
}
/**
* Set SSRC of source.
*/
setSsrc(ssrc) {
this.view.setUint32(4, ssrc);
this.setSerializationNeeded(true);
}
/**
* Get begin sequence number.
*/
getBeginSeq() {
return this.view.getUint16(8);
}
/**
* Set begin sequence number.
*/
setBeginSeq(seq) {
this.view.setUint16(8, seq);
this.setSerializationNeeded(true);
}
/**
* Get end sequence number.
*/
getEndSeq() {
return this.view.getUint16(10);
}
/**
* Set end sequence number.
*/
setEndSeq(seq) {
this.view.setUint16(10, seq);
this.setSerializationNeeded(true);
}
/**
* Get chunks.
*
* @remarks
* - Chunks are given as a list of 2 byte integers.
* - Use {@link parseExtendedReportChunk} to parse them.
*/
getChunks() {
return Array.from(this.#chunks);
}
/**
* Set chunks.
*
* @remarks
* - Chunks must be given as a list of 2 byte integers.
* - Use {@link createExtendedReportRunLengthChunk} or
* {@link createExtendedReportBitVectorChunk} to create them.
*/
setChunks(chunks) {
this.#chunks = Array.from(chunks);
this.setSerializationNeeded(true);
}
/**
* Add chunk.
*
* @remarks
* - Chunk must be given as 2 byte integer.
* - Use {@link createExtendedReportRunLengthChunk} or
* {@link createExtendedReportBitVectorChunk} to create it.
* - Given chunk cannot be a terminating null chunk (0 number).
*/
addChunk(chunk) {
if (chunk === 0) {
throw new TypeError('cannot add terminating null chunks');
}
this.#chunks.push(chunk);
this.setSerializationNeeded(true);
}
}
exports.LrleExtendedReport = LrleExtendedReport;