UNPKG

rtp.js

Version:

RTP stack for Node.js and browser written in TypeScript

189 lines (188 loc) 6.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NackPacket = void 0; exports.parseNackItem = parseNackItem; exports.createNackItem = createNackItem; const RtcpPacket_1 = require("./RtcpPacket"); const FeedbackPacket_1 = require("./FeedbackPacket"); const bitOps_1 = require("../../utils/bitOps"); /** * RTCP NACK packet (RTCP Transport Layer Feedback). * * ```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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |V=2|P| FMT=1 | PT=RTPFB=205 | length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SSRC of packet sender | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SSRC of media source | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | PID | BLP | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * : ... : * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * ``` * * @category RTCP * * @see * - [RFC 4585 section 6.2.1](https://datatracker.ietf.org/doc/html/rfc4585#section-6.2.1) */ class NackPacket extends FeedbackPacket_1.FeedbackPacket { #items = []; /** * @param view - If given it will be parsed. Otherwise an empty RTCP NACK * packet will be created. * * @throws * - If given `view` does not contain a valid RTCP NACK packet. */ constructor(view) { super(RtcpPacket_1.RtcpPacketType.RTPFB, FeedbackPacket_1.RtpFeedbackMessageType.NACK, view); if (!this.view) { this.view = new DataView(new ArrayBuffer(FeedbackPacket_1.FIXED_HEADER_LENGTH)); // Write version, packet type and feedback message type. this.writeFixedHeader(); return; } // Position relative to the DataView byte offset. let pos = 0; // Move to items. pos += FeedbackPacket_1.FIXED_HEADER_LENGTH; while (pos < this.view.byteLength - this.padding) { const pid = this.view.getUint16(pos); pos += 2; const bitmask = this.view.getUint16(pos); pos += 2; this.#items.push({ pid, bitmask }); } 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 RTCP NACK packet info. */ dump() { return { ...super.dump(), items: this.getItems(), }; } /** * @inheritDoc */ getByteLength() { if (!this.needsSerialization()) { return this.view.byteLength; } const packetLength = FeedbackPacket_1.FIXED_HEADER_LENGTH + this.#items.length * 4 + this.padding; return packetLength; } /** * @inheritDoc */ serialize(buffer, byteOffset) { const view = this.serializeBase(buffer, byteOffset); // Position relative to the DataView byte offset. let pos = 0; // Move to items. pos += FeedbackPacket_1.FIXED_HEADER_LENGTH; // Write items. for (const { pid, bitmask } of this.#items) { view.setUint16(pos, pid); pos += 2; view.setUint16(pos, bitmask); pos += 2; } 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 NackPacket(view); } /** * Get NACK items. * * @remarks * - Use {@link parseNackItem} to parse returned NACK items. */ getItems() { return Array.from(this.#items); } /** * Set NACK items. * * @remarks * - Use {@link createNackItem} to create NACK items. * - Serialization is needed after calling this method. */ setItems(items) { this.#items = Array.from(items); this.setSerializationNeeded(true); } /** * Add NACK item value. * * @remarks * - Use {@link createNackItem} to create the NACK item. * - Serialization is needed after calling this method. */ addItem(pid, bitmask) { this.#items.push({ pid, bitmask }); this.setSerializationNeeded(true); } } exports.NackPacket = NackPacket; /** * Parse a NACK item. It returns an array with RTP sequence numbers that are * included in the item (lost packets). * * @category RTCP */ function parseNackItem(pid, bitmask) { const seqs = [pid]; for (let i = 0; i <= 15; ++i) { if ((0, bitOps_1.readBit)({ value: bitmask, bit: i })) { seqs.push(pid + i + 1); } } return seqs; } /** * Create a NACK item. * * @param seqs - RTP sequence number of lost packets. As per NACK rules, there * can be up to 17 seq numbers and max diff between lowest and highest must * be 17. * * @category RTCP */ function createNackItem(seqs) { const orderedSeqs = [...seqs].sort(); const pid = orderedSeqs[0]; let bitmask = 0; for (let i = 1; i < orderedSeqs.length; ++i) { const seq = orderedSeqs[i]; const diff = (seq + 65536 - pid) % 65536; if (diff > 16) { throw new RangeError('cannot create a NACK bitmask with given seq numbers'); } bitmask = (0, bitOps_1.writeBit)({ value: bitmask, bit: diff - 1, flag: true }); } return { pid, bitmask }; }