@mediafish/rtmp-server
Version:
A server that receives an RTMP live stream and populates a readable object stream of the published audio, video, and data messages
221 lines (205 loc) • 7.79 kB
JavaScript
import {Buffer} from 'node:buffer';
import {reader, writer} from '@mediafish/buffer-operator';
export function readMessage(buff, offset, prevMsgHeaders) {
let messageHeader;
const chunkType = (buff[offset] >> 6) & 0x03;
let chunkStreamId = buff[offset] & 0x3F;
offset += 1;
// [offset, chunkType] = reader.readBits(buff, offset, 0, 2);
// [offset, chunkStreamId] = reader.readBits(buff, offset, 2, 6);
if (chunkStreamId === 0) {
// Chunk basic header 2: id is encoded in 2 bytes, [64-319]
[offset, chunkStreamId] = reader.readNumber(buff, offset, 1);
chunkStreamId += 64;
} else if (chunkStreamId === 1) {
// Chunk basic header 3: id is encoded in 3 bytes, [64-65599]
let second = 0, third = 0;
[offset, second] = reader.readNumber(buff, offset, 1);
[offset, third] = reader.readNumber(buff, offset, 1);
chunkStreamId = third * 256 + second + 64;
}
const prevMsgHeader = prevMsgHeaders[chunkStreamId];
if (chunkType === 0) {
[offset, messageHeader] = readMsgType0(buff, offset);
} else if (chunkType === 1) {
[offset, messageHeader] = readMsgType1(buff, offset, prevMsgHeader);
} else if (chunkType === 2) {
[offset, messageHeader] = readMsgType2(buff, offset, prevMsgHeader);
} else if (chunkType === 3) {
[offset, messageHeader] = readMsgType3(buff, offset, prevMsgHeader);
}
if (!messageHeader) {
return [offset, null];
}
const data = buff.slice(offset, offset + messageHeader.messageLength);
offset += messageHeader.messageLength;
return [offset, {basicHeader: {chunkType, chunkStreamId}, messageHeader, data}];
}
function readNumberLSBFirst(buff, offset, length) {
let value = 0;
for (let i = 0; i < length; i++) {
value |= (buff[offset++] << (8 * i));
}
return [offset, value];
}
function readMsgType0(buff, offset) {
let timestamp, messageLength, messageTypeId, messageStreamId;
[offset, timestamp] = reader.readNumber(buff, offset, 3);
[offset, messageLength] = reader.readNumber(buff, offset, 3);
[offset, messageTypeId] = reader.readNumber(buff, offset, 1);
// [offset, messageStreamId] = reader.readNumber(buff, offset, 4);
[offset, messageStreamId] = readNumberLSBFirst(buff, offset, 4);
if (timestamp === 0xFF_FF_FF) {
// Read Extended Timestamp
[offset, timestamp] = reader.readNumber(buff, offset, 4);
}
return [offset, {timestamp, delta: 0, messageLength, messageTypeId, messageStreamId}];
}
function readMsgType1(buff, offset, prevMsgHeader) {
let delta, messageLength, messageTypeId;
[offset, delta] = reader.readNumber(buff, offset, 3);
[offset, messageLength] = reader.readNumber(buff, offset, 3);
[offset, messageTypeId] = reader.readNumber(buff, offset, 1);
if (delta === 0xFF_FF_FF) {
// Read Extended Timestamp
[offset, delta] = reader.readNumber(buff, offset, 4);
}
if (!prevMsgHeader) {
return [offset, null];
}
const timestamp = prevMsgHeader.timestamp + delta;
const {messageStreamId} = prevMsgHeader;
return [offset, {timestamp, delta, messageLength, messageTypeId, messageStreamId}];
}
function readMsgType2(buff, offset, prevMsgHeader) {
let delta;
[offset, delta] = reader.readNumber(buff, offset, 3);
if (delta === 0xFF_FF_FF) {
// Read Extended Timestamp
[offset, delta] = reader.readNumber(buff, offset, 4);
}
if (!prevMsgHeader) {
return [offset, null];
}
const timestamp = prevMsgHeader.timestamp + delta;
const {messageLength} = prevMsgHeader;
const {messageTypeId} = prevMsgHeader;
const {messageStreamId} = prevMsgHeader;
return [offset, {timestamp, delta, messageLength, messageTypeId, messageStreamId}];
}
function readMsgType3(buff, offset, prevMsgHeader) {
if (!prevMsgHeader) {
return [offset, null];
}
const {delta} = prevMsgHeader;
const timestamp = prevMsgHeader.timestamp + delta;
const {messageLength} = prevMsgHeader;
const {messageTypeId} = prevMsgHeader;
const {messageStreamId} = prevMsgHeader;
return [offset, {timestamp, delta, messageLength, messageTypeId, messageStreamId}];
}
export function writeProtocolMessage(socket, msgHeader, msgData, chunkStreamId = 2) {
writeMsgType0(socket, {chunkType: 0, chunkStreamId}, msgHeader, msgData);
}
export function writeMessage(socket, msgHeader, msgData, prevMsgHeader) {
let isDelta = false, chunkType = 0;
const chunkStreamId = 2; // Only Protocol Control Message is handled
if (msgHeader.timestamp > 0xFF_FF_FF_FF) {
// Need to wrap around
msgHeader.timestamp %= 0x1_00_00_00_00;
} else if (prevMsgHeader) {
msgHeader.delta = msgHeader.timestamp - prevMsgHeader.timestamp;
isDelta = true;
}
if (isDelta) {
if (msgHeader.messageStreamId === prevMsgHeader.messageStreamId) {
chunkType = 1;
}
if (msgHeader.messageLength === prevMsgHeader.messageLength) {
chunkType = 2;
}
if (msgHeader.delta === prevMsgHeader.delta) {
chunkType = 3;
}
}
switch (chunkType) {
case 0:
writeMsgType0(socket, {chunkType, chunkStreamId}, msgHeader, msgData);
break;
case 1:
writeMsgType1(socket, {chunkType, chunkStreamId}, msgHeader, msgData);
break;
case 2:
writeMsgType2(socket, {chunkType, chunkStreamId}, msgHeader, msgData);
break;
case 3:
default:
writeMsgType3(socket, {chunkType, chunkStreamId}, msgHeader, msgData);
}
}
function writeMsgType0(socket, {chunkType, chunkStreamId}, msgHeader, msgData) {
let extended = false, bufferLength = 12 + msgHeader.messageLength;
if (msgHeader.timestamp >= 0xFF_FF_FF) {
extended = true;
bufferLength = 16 + msgHeader.messageLength;
}
const buff = Buffer.alloc(bufferLength);
buff[0] = (chunkType << 6 | chunkStreamId);
if (extended) {
writer.writeNumber(0xFF_FF_FF, buff, 1, 3);
} else {
writer.writeNumber(msgHeader.timestamp, buff, 1, 3);
}
writer.writeNumber(msgHeader.messageLength, buff, 4, 3);
writer.writeNumber(msgHeader.messageTypeId, buff, 7, 1);
writer.writeNumber(msgHeader.messageStreamId, buff, 8, 4);
if (extended) {
writer.writeNumber(msgHeader.timestamp, buff, 12, 4);
}
msgData.copy(buff, extended ? 16 : 12);
socket.write(buff);
}
function writeMsgType1(socket, {chunkType, chunkStreamId}, msgHeader, msgData) {
let extended = false, bufferLength = 8 + msgHeader.messageLength;
if (msgHeader.delta >= 0xFF_FF_FF) {
extended = true;
bufferLength = 12 + msgHeader.messageLength;
}
const buff = Buffer.alloc(bufferLength);
buff[0] = (chunkType << 6 | chunkStreamId);
if (extended) {
writer.writeNumber(0xFF_FF_FF, buff, 1, 3);
} else {
writer.writeNumber(msgHeader.timestamp, buff, 1, 3);
}
writer.writeNumber(msgHeader.messageLength, buff, 4, 3);
writer.writeNumber(msgHeader.messageTypeId, buff, 7, 1);
if (extended) {
writer.writeNumber(msgHeader.timestamp, buff, 8, 4);
}
msgData.copy(buff, extended ? 12 : 8);
socket.write(buff);
}
function writeMsgType2(socket, {chunkType, chunkStreamId}, msgHeader, msgData) {
let extended = false, bufferLength = 4 + msgHeader.messageLength;
if (msgHeader.delta >= 0xFF_FF_FF) {
extended = true;
bufferLength = 8 + msgHeader.messageLength;
}
const buff = Buffer.alloc(bufferLength);
buff[0] = (chunkType << 6 | chunkStreamId);
if (extended) {
writer.writeNumber(0xFF_FF_FF, buff, 1, 3);
writer.writeNumber(msgHeader.timestamp, buff, 4, 4);
} else {
writer.writeNumber(msgHeader.timestamp, buff, 1, 3);
}
msgData.copy(buff, extended ? 8 : 4);
socket.write(buff);
}
function writeMsgType3(socket, {chunkType, chunkStreamId}, msgHeader, msgData) {
const buff = Buffer.alloc(msgHeader.messageLength + 1);
buff[0] = (chunkType << 6 | chunkStreamId);
msgData.copy(buff, 1);
socket.write(buff);
}