rtp.js
Version:
RTP stack for Node.js and browser written in TypeScript
262 lines (261 loc) • 9.63 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CompoundPacket = void 0;
const Packet_1 = require("../Packet");
const RtcpPacket_1 = require("./RtcpPacket");
const ReceiverReportPacket_1 = require("./ReceiverReportPacket");
const SenderReportPacket_1 = require("./SenderReportPacket");
const ByePacket_1 = require("./ByePacket");
const SdesPacket_1 = require("./SdesPacket");
const FeedbackPacket_1 = require("./FeedbackPacket");
const NackPacket_1 = require("./NackPacket");
const SrReqPacket_1 = require("./SrReqPacket");
const EcnPacket_1 = require("./EcnPacket");
const PliPacket_1 = require("./PliPacket");
const SliPacket_1 = require("./SliPacket");
const RpsiPacket_1 = require("./RpsiPacket");
const GenericFeedbackPacket_1 = require("./GenericFeedbackPacket");
const XrPacket_1 = require("./XrPacket");
const ExtendedJitterReportsPacket_1 = require("./ExtendedJitterReportsPacket");
const GenericPacket_1 = require("./GenericPacket");
/**
* RTCP Compound packet.
*
* @category RTCP
*
* @see
* - [RFC 3550](https://datatracker.ietf.org/doc/html/rfc3550)
*/
class CompoundPacket extends Packet_1.Packet {
// RTCP packets.
#packets = [];
/**
* @param view - If given it will be parsed. Otherwise an empty RTCP Compound
* packet will be created.
*
* @throws
* - If given `view` does not contain a valid RTCP Compound packet.
*/
constructor(view) {
super(view);
if (this.view && !(0, RtcpPacket_1.isRtcp)(this.view)) {
throw new TypeError('not a RTCP compound packet');
}
if (!this.view) {
this.view = new DataView(new ArrayBuffer(0));
return;
}
// Position relative to the DataView byte offset.
let pos = 0;
// Parse all RTCP packets.
while (pos < this.view.byteLength) {
const remainingView = new DataView(this.view.buffer, this.view.byteOffset + pos, this.view.byteLength - pos);
const packetLength = (0, RtcpPacket_1.getRtcpLength)(remainingView);
const packetView = new DataView(this.view.buffer, this.view.byteOffset + pos, packetLength);
let packet;
switch ((0, RtcpPacket_1.getRtcpPacketType)(remainingView)) {
case RtcpPacket_1.RtcpPacketType.RR: {
packet = new ReceiverReportPacket_1.ReceiverReportPacket(packetView);
break;
}
case RtcpPacket_1.RtcpPacketType.SR: {
packet = new SenderReportPacket_1.SenderReportPacket(packetView);
break;
}
case RtcpPacket_1.RtcpPacketType.BYE: {
packet = new ByePacket_1.ByePacket(packetView);
break;
}
case RtcpPacket_1.RtcpPacketType.SDES: {
packet = new SdesPacket_1.SdesPacket(packetView);
break;
}
case RtcpPacket_1.RtcpPacketType.RTPFB: {
switch ((0, FeedbackPacket_1.getRtcpFeedbackMessageType)(packetView)) {
case FeedbackPacket_1.RtpFeedbackMessageType.NACK: {
packet = new NackPacket_1.NackPacket(packetView);
break;
}
case FeedbackPacket_1.RtpFeedbackMessageType.SR_REQ: {
packet = new SrReqPacket_1.SrReqPacket(packetView);
break;
}
case FeedbackPacket_1.RtpFeedbackMessageType.ECN: {
packet = new EcnPacket_1.EcnPacket(packetView);
break;
}
default: {
packet = new GenericFeedbackPacket_1.GenericFeedbackPacket(packetView);
}
}
break;
}
case RtcpPacket_1.RtcpPacketType.PSFB: {
switch ((0, FeedbackPacket_1.getRtcpFeedbackMessageType)(packetView)) {
case FeedbackPacket_1.PsFeedbackMessageType.PLI: {
packet = new PliPacket_1.PliPacket(packetView);
break;
}
case FeedbackPacket_1.PsFeedbackMessageType.SLI: {
packet = new SliPacket_1.SliPacket(packetView);
break;
}
case FeedbackPacket_1.PsFeedbackMessageType.RPSI: {
packet = new RpsiPacket_1.RpsiPacket(packetView);
break;
}
default: {
packet = new GenericFeedbackPacket_1.GenericFeedbackPacket(packetView);
}
}
break;
}
case RtcpPacket_1.RtcpPacketType.XR: {
packet = new XrPacket_1.XrPacket(packetView);
break;
}
case RtcpPacket_1.RtcpPacketType.IJ: {
packet = new ExtendedJitterReportsPacket_1.ExtendedJitterReportsPacket(packetView);
break;
}
default: {
packet = new GenericPacket_1.GenericPacket(packetView);
}
}
pos += packetLength;
this.#packets.push(packet);
}
// 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 RTCP Compound packet info.
*/
dump() {
return {
...super.dump(),
packets: this.#packets.map(packet => packet.dump()),
};
}
/**
* @inheritDoc
*/
getByteLength() {
if (!this.needsSerialization()) {
return this.view.byteLength;
}
const packetLength = this.#packets.reduce((sum, packet) => sum + packet.getByteLength(), 0);
return packetLength;
}
/**
* Not implemented in RTCP Compound packet.
*
* @hidden
*/
getPadding() {
return 0;
}
/**
* Not implemented in RTCP Compound packet.
*
* @hidden
*/
padTo4Bytes() {
throw new Error('method not implemented in RTCP CompoundPacket');
}
/**
* @inheritDoc
*/
needsSerialization() {
return (super.needsSerialization() ||
this.#packets.some(packet => packet.needsSerialization()));
}
/**
* @inheritDoc
*/
serialize(buffer, byteOffset) {
const bufferData = this.getSerializationBuffer(buffer, byteOffset);
// Create new DataView with new buffer.
const view = new DataView(bufferData.buffer, bufferData.byteOffset, bufferData.byteLength);
// Position relative to the DataView byte offset.
let pos = 0;
for (const packet of this.#packets) {
packet.serialize(view.buffer, view.byteOffset + pos);
pos += packet.getByteLength();
}
// 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 CompoundPacket(view);
}
/**
* Return the {@link RtcpPacket} entries in this RTCP Compound packet.
*
* @remarks
* - The returned value is an array of {@link RtcpPacket}, which is an
* abstract class.
* - By inspecting {@link RtcpPacket.getPacketType} we can cast each packet
* to its specific class.
*
* @example
* ```ts
* import { packets } from 'rtp.js';
* const { CompoundPacket, RtcpPacketType, SdesPacket } = packets;
*
* const compoundPacket = new CompoundPacket(view);
*
* for (const packet of compoundPacket.getPackets())
* {
* switch (packet.getPacketType())
* {
* case RtcpPacketType.SDES:
* {
* const sdesPacket = packet as SdesPacket;
*
* console.log(sdesPacket.getChunks());
*
* break;
* }
*
* // etc.
* }
* }
* ```
*/
getPackets() {
return Array.from(this.#packets);
}
/**
* Set the {@link RtcpPacket} entries in this RTCP Compound packet.
*
* @remarks
* - Serialization is needed after calling this method.
*/
setPackets(packets) {
this.#packets = Array.from(packets);
this.setSerializationNeeded(true);
}
/**
* Add a new {@link RtcpPacket} at the end of this RTCP Compound packet.
*
* @remarks
* - Serialization is needed after calling this method.
*/
addPacket(packet) {
this.#packets.push(packet);
this.setSerializationNeeded(true);
}
}
exports.CompoundPacket = CompoundPacket;