UNPKG

rtp.js

Version:

RTP stack for Node.js and browser written in TypeScript

250 lines (249 loc) 9.56 kB
"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;