rtp.js
Version:
RTP stack for Node.js and browser written in TypeScript
250 lines (249 loc) • 9.56 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.XrPacket = void 0;
const ExtendedReport_1 = require("./extendedReports/ExtendedReport");
const LrleExtendedReport_1 = require("./extendedReports/LrleExtendedReport");
const DrleExtendedReport_1 = require("./extendedReports/DrleExtendedReport");
const PrtExtendedReport_1 = require("./extendedReports/PrtExtendedReport");
const RrtExtendedReport_1 = require("./extendedReports/RrtExtendedReport");
const DlrrExtendedReport_1 = require("./extendedReports/DlrrExtendedReport");
const SsExtendedReport_1 = require("./extendedReports/SsExtendedReport");
const VmExtendedReport_1 = require("./extendedReports/VmExtendedReport");
const EcnExtendedReport_1 = require("./extendedReports/EcnExtendedReport");
const GenericExtendedReport_1 = require("./extendedReports/GenericExtendedReport");
const RtcpPacket_1 = require("./RtcpPacket");
// Common RTCP header length + 4 (SSRC of packet sender).
const FIXED_HEADER_LENGTH = RtcpPacket_1.COMMON_HEADER_LENGTH + 4;
/**
* RTCP XR packet.
*
* ```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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* header |V=2|P|reserved | PT=XR=207 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* report : report blocks :
* blocks +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* ```
*
* @category RTCP
*
* @see
* - [RFC 3611 section 2](https://datatracker.ietf.org/doc/html/rfc3611#section-2)
*/
class XrPacket extends RtcpPacket_1.RtcpPacket {
// Extended Reports.
#reports = [];
/**
* @param view - If given it will be parsed. Otherwise an empty RTCP XR packet
* will be created.
*
* @throws
* - If given `view` does not contain a valid RTCP XR packet.
*/
constructor(view) {
super(RtcpPacket_1.RtcpPacketType.XR, view);
if (!this.view) {
this.view = new DataView(new ArrayBuffer(FIXED_HEADER_LENGTH));
// Write version and packet type.
this.writeCommonHeader();
return;
}
// Position relative to the DataView byte offset.
let pos = 0;
// Move to Extended Reports.
pos += FIXED_HEADER_LENGTH;
while (pos < this.view.byteLength - this.padding) {
const remainingView = new DataView(this.view.buffer, this.view.byteOffset + pos, this.view.byteLength - this.padding - pos);
const reportType = (0, ExtendedReport_1.getExtendedReportType)(remainingView);
const reportLength = (0, ExtendedReport_1.getExtendedReportLength)(remainingView);
const reportView = new DataView(this.view.buffer, this.view.byteOffset + pos, reportLength);
let report;
switch (reportType) {
case ExtendedReport_1.ExtendedReportType.LRLE: {
report = new LrleExtendedReport_1.LrleExtendedReport(reportView);
break;
}
case ExtendedReport_1.ExtendedReportType.DRLE: {
report = new DrleExtendedReport_1.DrleExtendedReport(reportView);
break;
}
case ExtendedReport_1.ExtendedReportType.PRT: {
report = new PrtExtendedReport_1.PrtExtendedReport(reportView);
break;
}
case ExtendedReport_1.ExtendedReportType.RRT: {
report = new RrtExtendedReport_1.RrtExtendedReport(reportView);
break;
}
case ExtendedReport_1.ExtendedReportType.DLRR: {
report = new DlrrExtendedReport_1.DlrrExtendedReport(reportView);
break;
}
case ExtendedReport_1.ExtendedReportType.SS: {
report = new SsExtendedReport_1.SsExtendedReport(reportView);
break;
}
case ExtendedReport_1.ExtendedReportType.VM: {
report = new VmExtendedReport_1.VmExtendedReport(reportView);
break;
}
case ExtendedReport_1.ExtendedReportType.ECN: {
report = new EcnExtendedReport_1.EcnExtendedReport(reportView);
break;
}
default: {
report = new GenericExtendedReport_1.GenericExtendedReport(reportView);
}
}
pos += reportLength;
this.#reports.push(report);
}
pos += this.padding;
// Ensure that view length and parsed length match.
if (pos !== this.view.byteLength) {
throw new RangeError(`parsed length (${pos} bytes) does not match view length (${this.view.byteLength} bytes)`);
}
}
/**
* Dump XR packet info.
*/
dump() {
return {
...super.dump(),
ssrc: this.getSsrc(),
reports: this.#reports.map(report => report.dump()),
};
}
/**
* @inheritDoc
*/
getByteLength() {
if (!this.needsSerialization()) {
return this.view.byteLength;
}
const packetLength = FIXED_HEADER_LENGTH +
this.#reports.reduce((sum, report) => sum + report.getByteLength(), 0) +
this.padding;
return packetLength;
}
/**
* @inheritDoc
*/
needsSerialization() {
return (super.needsSerialization() ||
this.#reports.some(report => report.needsSerialization()));
}
/**
* @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 += RtcpPacket_1.COMMON_HEADER_LENGTH;
// Copy the rest of the fixed header into the new buffer.
uint8Array.set(new Uint8Array(this.view.buffer, this.view.byteOffset + pos, FIXED_HEADER_LENGTH - RtcpPacket_1.COMMON_HEADER_LENGTH), pos);
// Move to Reception Reports.
pos += FIXED_HEADER_LENGTH - RtcpPacket_1.COMMON_HEADER_LENGTH;
// Write Reception Reports.
for (const report of this.#reports) {
// Serialize the report into the current position.
report.serialize(view.buffer, view.byteOffset + pos);
pos += report.getByteLength();
}
pos += this.padding;
// Assert that current position is equal than new buffer length.
if (pos !== view.byteLength) {
throw new RangeError(`filled length (${pos} bytes) is different than 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 XrPacket(view);
}
/**
* Get sender SSRC.
*/
getSsrc() {
return this.view.getUint32(4);
}
/**
* Set sender SSRC.
*/
setSsrc(ssrc) {
this.view.setUint32(4, ssrc);
}
/**
* Get Extended Reports.
*
* @remarks
* - The returned value is an array of {@link ExtendedReport}, which is an
* abstract class.
* - By inspecting {@link ExtendedReport.getReportType} we can cast each
* extended report to its specific class.
*
* @example
* ```ts
* import { packets } from 'rtp.js';
* const { XrPacket, ExtendedReportType, LrleExtendedReport } = packets;
*
* const packet = new XrPacket(view);
*
* for (const extendedReport of packet.getReports())
* {
* switch (extendedReport.getReportType())
* {
* case ExtendedReportType.LRLE:
* {
* const lrleExtendedReport = extendedReport as LrleExtendedReport;
*
* console.log(lrleExtendedReport.getThinning());
*
* break;
* }
*
* // etc.
* }
* }
* ```
*/
getReports() {
return Array.from(this.#reports);
}
/**
* Set Extended Reports.
*
* @remarks
* - Serialization is needed after calling this method.
*/
setReports(reports) {
this.#reports = Array.from(reports);
// NOTE: Do not update RTCP count since XR packets do not have that field.
this.setSerializationNeeded(true);
}
/**
* Add Extended Report.
*
* @remarks
* - Serialization is needed after calling this method.
*/
addReport(report) {
this.#reports.push(report);
// Update RTCP count.
// NOTE: Do not update RTCP count since XR packets do not have that field.
this.setSerializationNeeded(true);
}
}
exports.XrPacket = XrPacket;