UNPKG

@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

222 lines (204 loc) 6.63 kB
import {Buffer} from 'node:buffer'; import debug from 'debug'; import {readValue, writeValue} from '@mediafish/amf0'; import RTMPConnection from '../connection.js'; import RTMPStream from '../stream.js'; import * as protoCtrlUtil from './protocol-control.js'; import * as chunkUtil from './chunk.js'; const print = debug('rtmp-server'); function removeSlash(path) { return path.replace(/^\/+/, '').replace(/\/+$/, ''); } function samePath(a = '', b = '') { if (removeSlash(a) === removeSlash(b)) { return true; } return false; } function sameStreamName(a = '', b = '') { if (a === b || a === '*' || b === '*') { return true; } return false; } function getEventName(event, emitter, sameFunc) { for (const name of emitter.eventNames()) { if (name === 'error') { continue; } if (sameFunc(name, event)) { return name; } } return ''; } export function processMessage(connMgr, {data, messageHeader}) { // print(`[Command] processMessage(data.length=${data.length}) --- Enter`); // print(JSON.stringify(data, null, 4)); const command = readCommand(data); print(`[Command] Received: ${JSON.stringify(command, null, 4)}`); if (command.name === 'connect') { return processConnect(connMgr, command); } if (command.name === 'createStream') { return processCreateStream(connMgr, command); } if (command.name === 'publish') { return processPublish(connMgr, command, messageHeader); } return null; } function processConnect(connMgr, {data, transactionId}) { const {server, socket, windowSize, maxChunkSize} = connMgr; // print(`\tconnect`); const event = getEventName(data.app, server, samePath); if (!event) { sendResponse(socket, '_error', transactionId, [ null, { level: 'error', code: 'NetConnection.Connect.Failed', description: 'Connection failed.', }, ]); return new Error(`Unsupported path: "${data.app}"`); } protoCtrlUtil.sendWindowAckSize(socket, windowSize); protoCtrlUtil.setPeerBandWidth(socket, windowSize, 2); protoCtrlUtil.setChunkSize(socket, maxChunkSize); /* protoCtrlUtil.setUserControlMessage(socket, 9999, 1); */ sendResponse(socket, '_result', transactionId, [ { fmsVer: 'FMS/3,0,1,123', capabilities: 31, }, { level: 'status', code: 'NetConnection.Connect.Success', description: 'Connection succeeded.', objectEncoding: 0, // AMF0 }, ]); connMgr.connection = new RTMPConnection(event); connMgr.state = 'connected'; server.emit(event, connMgr.connection); print(`RTMPConnection object is created. path=${event}`); return null; } function processCreateStream(connMgr, {transactionId}) { const {socket, streams, maxStreamNum} = connMgr; if (streams.size === maxStreamNum) { sendResponse(socket, '_error', transactionId); return new Error(`Max stream count exceeded: ${maxStreamNum}`); } // print(`\tcreateStream`); sendResponse(socket, '_result', transactionId, [null, streams.size + 1]); return null; } function processPublish(connMgr, {data}, {messageStreamId}) { const {connection, streams, socket} = connMgr; const {publishingName, publishingType} = data; let err = null; // print(`\tconnect`); if (publishingType !== 'live') { err = new Error(`Unsupported stream type: "${publishingType}"`); sendResponse(socket, 'onStatus', 0, createOnStatusResponse(err)); return err; } const event = getEventName(publishingName, connection, sameStreamName); if (!event) { err = new Error(`Unsupported stream name: "${publishingName}"`); sendResponse(socket, 'onStatus', 0, createOnStatusResponse(err)); return err; } sendResponse(socket, 'onStatus', 0, createOnStatusResponse(err)); const stream = new RTMPStream(event, messageStreamId); streams.set(messageStreamId, stream); connMgr.state = 'publishing'; connection.emit(event, stream); print(`RTMPStream object is created. name=${event}, id=${messageStreamId}`); return err; } function createOnStatusResponse(err) { const infoObj = err ? { level: 'error', code: 'NetStream.Publish.Failed', description: 'Unable to start publishing', } : { level: 'status', code: 'NetStream.Publish.Start', description: 'Start publishing', audioCodecs: 0x04_00, // SUPPORT_SND_AAC videoCodecs: 0x00_80, // SUPPORT_VID_H264 }; return [null, infoObj]; } function readCommand(data) { // print(`[Command] readCommand(data.length=${data.length}) --- Enter`); let offset = 0, name = '', transactionId = 0, command = null, publishingName = '', publishingType = ''; [offset, name] = readValue(data, offset); [offset, transactionId] = readValue(data, offset); // print(`[Command] readCommand: name="${name}", transaction ID=${transactionId}`); switch (name) { case 'connect': case 'createStream': [offset, command] = readValue(data, offset); break; case 'releaseStream': case 'FCPublish': [offset] = readValue(data, offset); [offset, command] = readValue(data, offset); break; case 'publish': [offset] = readValue(data, offset); // Null Object [offset, publishingName] = readValue(data, offset); [offset, publishingType] = readValue(data, offset); command = {publishingName, publishingType}; break; default: command = null; } // Skip to the end while (offset < data.length) { [offset] = readValue(data, offset); } // print(`[Command] readCommand() --- Exit`); return {name, transactionId, data: command}; } function sendResponse(socket, name, transactionId, params) { const response = { name, transactionId, params, }; const len = writeResponse(null, 0, response); const buff = Buffer.alloc(len); writeResponse(buff, 0, response); writeMessage(buff, socket); print(`[Command] Response: ${JSON.stringify(response, null, 4)}`); } function writeResponse(buffer, offset, {name, transactionId, params}) { offset = writeValue(buffer, offset, name); offset = writeValue(buffer, offset, transactionId); if (params) { for (const param of params) { offset = writeValue(buffer, offset, param); } } else { offset = writeValue(buffer, offset, null); } return offset; } function writeMessage(buffer, socket) { // print(`[Command] writeMessage(buffer.length=${buffer.length})`); const msgHeader = { timestamp: 0, delta: 0, messageLength: buffer.length, messageTypeId: 20, messageStreamId: 0, }; chunkUtil.writeProtocolMessage(socket, msgHeader, buffer, 3); }