From 50d920644da6baca1640c076eda1067839ec723d Mon Sep 17 00:00:00 2001 From: srgooglo Date: Wed, 12 Oct 2022 00:48:11 +0200 Subject: [PATCH] remove `streaming-server` from repo --- packages/streaming-server/Dockerfile | 18 - packages/streaming-server/docker-compose.yml | 10 - packages/streaming-server/package.json | 28 - packages/streaming-server/src/index.js | 356 ----- .../streaming-server/src/internal-nms/ctx.js | 13 - .../src/internal-nms/index.js | 236 --- .../src/internal-nms/lib/amf_rules.js | 1214 --------------- .../src/internal-nms/lib/av.js | 515 ------- .../src/internal-nms/lib/bitop.js | 53 - .../src/internal-nms/lib/logger.js | 53 - .../src/internal-nms/lib/utils.js | 106 -- .../src/internal-nms/rtmp_client.js | 793 ---------- .../src/internal-nms/rtmp_handshake.js | 111 -- .../internal-nms/servers/fission_server.js | 103 -- .../src/internal-nms/servers/relay_server.js | 255 ---- .../src/internal-nms/servers/rtmp_server.js | 87 -- .../src/internal-nms/servers/trans_server.js | 105 -- .../sessionsModels/fission_session.js | 51 - .../sessionsModels/flv_session.js | 218 --- .../sessionsModels/relay_session.js | 60 - .../sessionsModels/rtmp_session.js | 1306 ----------------- .../sessionsModels/trans_session.js | 114 -- .../streaming-server/src/lib/cpu/index.js | 45 - .../getStreamingKeyFromStreamPath/index.js | 3 - packages/streaming-server/src/lib/index.js | 2 - .../src/managers/DbManager/index.js | 40 - .../src/managers/SessionsManager/index.js | 57 - .../streaming-server/src/managers/index.js | 2 - packages/streaming-server/src/models/index.js | 18 - .../streaming-server/src/schemas/index.js | 1 - .../src/schemas/streamingKey/index.js | 14 - 31 files changed, 5987 deletions(-) delete mode 100644 packages/streaming-server/Dockerfile delete mode 100644 packages/streaming-server/docker-compose.yml delete mode 100644 packages/streaming-server/package.json delete mode 100644 packages/streaming-server/src/index.js delete mode 100644 packages/streaming-server/src/internal-nms/ctx.js delete mode 100644 packages/streaming-server/src/internal-nms/index.js delete mode 100644 packages/streaming-server/src/internal-nms/lib/amf_rules.js delete mode 100644 packages/streaming-server/src/internal-nms/lib/av.js delete mode 100644 packages/streaming-server/src/internal-nms/lib/bitop.js delete mode 100644 packages/streaming-server/src/internal-nms/lib/logger.js delete mode 100644 packages/streaming-server/src/internal-nms/lib/utils.js delete mode 100644 packages/streaming-server/src/internal-nms/rtmp_client.js delete mode 100644 packages/streaming-server/src/internal-nms/rtmp_handshake.js delete mode 100644 packages/streaming-server/src/internal-nms/servers/fission_server.js delete mode 100644 packages/streaming-server/src/internal-nms/servers/relay_server.js delete mode 100644 packages/streaming-server/src/internal-nms/servers/rtmp_server.js delete mode 100644 packages/streaming-server/src/internal-nms/servers/trans_server.js delete mode 100644 packages/streaming-server/src/internal-nms/sessionsModels/fission_session.js delete mode 100644 packages/streaming-server/src/internal-nms/sessionsModels/flv_session.js delete mode 100644 packages/streaming-server/src/internal-nms/sessionsModels/relay_session.js delete mode 100644 packages/streaming-server/src/internal-nms/sessionsModels/rtmp_session.js delete mode 100644 packages/streaming-server/src/internal-nms/sessionsModels/trans_session.js delete mode 100644 packages/streaming-server/src/lib/cpu/index.js delete mode 100644 packages/streaming-server/src/lib/getStreamingKeyFromStreamPath/index.js delete mode 100644 packages/streaming-server/src/lib/index.js delete mode 100644 packages/streaming-server/src/managers/DbManager/index.js delete mode 100644 packages/streaming-server/src/managers/SessionsManager/index.js delete mode 100644 packages/streaming-server/src/managers/index.js delete mode 100644 packages/streaming-server/src/models/index.js delete mode 100644 packages/streaming-server/src/schemas/index.js delete mode 100755 packages/streaming-server/src/schemas/streamingKey/index.js diff --git a/packages/streaming-server/Dockerfile b/packages/streaming-server/Dockerfile deleted file mode 100644 index 0ed4e6ac..00000000 --- a/packages/streaming-server/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM node:16-alpine - -RUN apk add git -RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app - -WORKDIR /home/node/app -USER node - -EXPOSE 3010 - -COPY package.json ./ -COPY --chown=node:node . . - -RUN chmod -R 777 /home/node/app -RUN npm install -RUN npm run build - -CMD ["node", "/home/node/app/dist/index.js"] \ No newline at end of file diff --git a/packages/streaming-server/docker-compose.yml b/packages/streaming-server/docker-compose.yml deleted file mode 100644 index b0d637f3..00000000 --- a/packages/streaming-server/docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: "3" - -services: - node: - build: "." - ports: - - "1935:1935" - - "3002:3002" - volumes: - - "./.env:/home/node/app/.env" \ No newline at end of file diff --git a/packages/streaming-server/package.json b/packages/streaming-server/package.json deleted file mode 100644 index 2acac6f6..00000000 --- a/packages/streaming-server/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@comty/streaming-server", - "author": "RageStudio", - "version": "0.22.1", - "main": "dist/index.js", - "scripts": { - "dev": "nodemon --ignore dist/ --exec corenode-node ./src/index.js", - "build": "corenode-cli build" - }, - "dependencies": { - "@ffmpeg-installer/ffmpeg": "^1.1.0", - "linebridge": "0.11.19", - "lodash": "4.17.21", - "mongoose": "^6.3.3", - "node-media-server": "^2.3.9", - "chalk": "^2.4.2", - "dateformat": "^3.0.3", - "minimist": "^1.2.5", - "mkdirp": "1.0.3", - "ws": "^7.4.6", - "dotenv": "16.0.1" - }, - "devDependencies": { - "cross-env": "^7.0.3", - "nodemon": "^2.0.15", - "corenode": "0.28.26" - } -} diff --git a/packages/streaming-server/src/index.js b/packages/streaming-server/src/index.js deleted file mode 100644 index 7355e752..00000000 --- a/packages/streaming-server/src/index.js +++ /dev/null @@ -1,356 +0,0 @@ -require("dotenv").config() -const ffmpeg = require("@ffmpeg-installer/ffmpeg") - -import express from "express" -import path from "path" -import lodash from "lodash" -import { EventEmitter } from "events" - -import { Server } from "linebridge/dist/server" -import { SessionsManager, DbManager } from "./managers" -import { getStreamingKeyFromStreamPath } from "./lib" - -import MediaServer from "./internal-nms" -import FlvSession from "./internal-nms/sessionsModels/flv_session" - -import { StreamingKey } from "./models" - -const HTTPServerConfig = { - port: 3002, - httpEngine: "express" -} - -const MediaServerConfig = { - rtmp: { - port: 1935, - chunk_size: 60000, - gop_cache: true, - ping: 30, - ping_timeout: 60 - }, - //logType: 0, - mediaroot: path.resolve(process.cwd(), "./cache"), - trans: { - ffmpeg: ffmpeg.path, - tasks: [ - { - app: "live", - hls: true, - hlsFlags: "[hls_time=2:hls_list_size=3:hls_flags=delete_segments]", - dash: true, - dashFlags: "[f=dash:window_size=3:extra_window_size=5]" - } - ] - }, - fission: { - ffmpeg: ffmpeg.path, - tasks: [ - { - rule: "app/*", - model: [ - { - ab: "320k", - vb: "10500k", - vs: "1920x1080", - vf: "60", - }, - { - ab: "320k", - vb: "4500k", - vs: "1920x1080", - vf: "30", - }, - { - ab: "320k", - vb: "1500k", - vs: "1280x720", - vf: "30", - }, - { - ab: "96k", - vb: "1000k", - vs: "854x480", - vf: "24", - }, - { - ab: "96k", - vb: "600k", - vs: "640x360", - vf: "20", - }, - ] - }, - ] - } -} - -class StreamingServer { - IHTTPServer = new Server(HTTPServerConfig) - - IMediaServer = new MediaServer(MediaServerConfig) - - Db = new DbManager() - - Sessions = new SessionsManager() - - Stats = {} - - InternalEvents = new EventEmitter() - - constructor() { - this.registerMediaServerEvents() - this.registerHTTPServerEndpoints() - - global.resolveUserspaceOfStreamingKey = this.resolveUserspaceOfStreamingKey.bind(this) - - this.IHTTPServer.httpInterface.use("/media", express.static(path.resolve(process.cwd(), "./cache/live"))) - - // fire initization - this.initialize() - } - - registerMediaServerEvents = () => { - Object.keys(this.mediaServerEvents).forEach((eventName) => { - this.IMediaServer.on(eventName, this.mediaServerEvents[eventName]) - }) - } - - registerHTTPServerEndpoints = () => { - Object.keys(this.httpServerEndpoints).forEach((route) => { - this.IHTTPServer.registerHTTPEndpoint({ - route: route, - ...this.httpServerEndpoints[route] - }) - }) - } - - httpServerEndpoints = { - "/events/on-publish": { - method: "post", - fn: async (req, res) => { - req.body = Buffer.from(req.body).toString() - - // decode url-encoded body - req.body = req.body.split("&").reduce((acc, cur) => { - const [key, value] = cur.split("=") - acc[key] = value - - return acc - }, {}) - - const streamingKey = req.body.name - - const streamingUserspace = await StreamingKey.findOne({ - key: streamingKey - }) - - if (!streamingUserspace) { - return res.status(403).send("Invalid stream key") - } - - this.Sessions.publishStream({ - user_id: streamingUserspace.user_id, - stream_key: streamingKey - }) - - return res.send("OK") - } - }, - "/events/on-publish-done": { - method: "post", - fn: async (req, res) => { - req.body = Buffer.from(req.body).toString() - - // decode url-encoded body - req.body = req.body.split("&").reduce((acc, cur) => { - const [key, value] = cur.split("=") - acc[key] = value - - return acc - }, {}) - - const streamingKey = req.body.name - - const streamingUserspace = await StreamingKey.findOne({ - key: streamingKey - }) - - if (!streamingUserspace) { - return res.status(403).send("Invalid stream key") - } - - this.Sessions.unpublishStream(streamingKey) - - return res.send("OK") - } - }, - "/status": { - method: "get", - fn: async (req, res) => { - const serverStatus = await this.IMediaServer.getServerStatus() - - return res.json(serverStatus) - } - }, - "/streams": { - method: "get", - fn: async (req, res) => { - let streams = [] - - if (req.query?.username) { - streams = await this.Sessions.getStreamsByUsername(req.query?.username) - } else { - streams = this.Sessions.getPublicStreams() - } - - // retrieve streams details from internal media server api - let streamsListDetails = this.IMediaServer.getSessions()//await axios.get(`${internalMediaServerURI}/api/streams`) - - streamsListDetails = streamsListDetails?.live ?? {} - - // return only publisher details - streamsListDetails = Object.keys(streamsListDetails).map((streamKey) => { - return { - // filter unwanted properties - ...lodash.omit(streamsListDetails[streamKey].publisher, ["stream", "ip"]) - } - }) - - // reduce as an object - streamsListDetails = streamsListDetails.reduce((acc, cur) => { - acc[cur.clientId] = cur - - return acc - }, {}) - - // merge with public streams - streams = streams.map((stream) => { - return { - ...stream, - ...streamsListDetails[stream.id] - } - }) - - // if username is provided, return only streams for that user - // is supposed to be allowed only one stream per user - if (req.query?.username) { - return res.json(streams[0]) - } - - return res.json(streams) - } - }, - "/stream/:mode/:username": { - method: "get", - fn: async (req, res) => { - const { username, mode = "flv" } = req.params - - const streamSession = this.Sessions.publicStreams.find(stream => { - if (stream.username === username) { - return stream - } - }) - - if (!streamSession) { - return res.status(404).json({ - error: "Stream not found" - }) - } - - const streamKey = streamSession.stream_key - - switch (mode) { - case "flv": { - // fix streamKey - req.url = `/live/${streamKey}.flv` - - req.nmsConnectionType = "http" - - let session = new FlvSession(req, res) - - session.run() - - break; - } - - case "hls": { - return res.status(501).send("Not implemented") - } - - default: { - return res.status(400).json({ - error: "Stream mode not supported" - }) - } - } - } - } - } - - mediaServerEvents = { - prePublish: async (id, StreamPath, args) => { - // this event is fired before client is published - // here must be some validation (as key validation) - - // get session - const session = this.IMediaServer.getSession(id) - - // create a userspaced session for the client with containing session - this.Sessions.newSession(id, session) - - const streamingKey = getStreamingKeyFromStreamPath(StreamPath) - - const streamingUserspace = await StreamingKey.findOne({ - key: streamingKey - }) - - if (!streamingUserspace) { - this.Sessions.removeSession(id) - return false - } - - this.Sessions.publishStream({ - id, - user_id: streamingUserspace.user_id, - username: streamingUserspace.username, - stream_key: streamingKey - }) - }, - donePublish: async (id, StreamPath, args) => { - // this event is fired when client has ended the connection - - // stop the session - this.Sessions.removeSession(id) - - const streamingKey = getStreamingKeyFromStreamPath(StreamPath) - - this.Sessions.unpublishStream(streamingKey) - } - } - - resolveUserspaceOfStreamingKey = async (streamingKey) => { - const streamingUserspace = await StreamingKey.findOne({ - key: streamingKey - }) - - if (!streamingUserspace) { - return false - } - - return streamingUserspace - } - - initialize = async () => { - await this.Db.connect() - - // fix cors - this.IHTTPServer.httpInterface.options("*", require("cors")({ - origin: "localhost" - })) - - await this.IHTTPServer.initialize() - await this.IMediaServer.run() - } -} - -new StreamingServer() \ No newline at end of file diff --git a/packages/streaming-server/src/internal-nms/ctx.js b/packages/streaming-server/src/internal-nms/ctx.js deleted file mode 100644 index 99688688..00000000 --- a/packages/streaming-server/src/internal-nms/ctx.js +++ /dev/null @@ -1,13 +0,0 @@ -const EventEmitter = require("events") - -let sessions = new Map() -let publishers = new Map() -let idlePlayers = new Set() -let nodeEvent = new EventEmitter() -let stat = { - inbytes: 0, - outbytes: 0, - accepted: 0 -} - -module.exports = { sessions, publishers, idlePlayers, nodeEvent, stat } \ No newline at end of file diff --git a/packages/streaming-server/src/internal-nms/index.js b/packages/streaming-server/src/internal-nms/index.js deleted file mode 100644 index 82d2301c..00000000 --- a/packages/streaming-server/src/internal-nms/index.js +++ /dev/null @@ -1,236 +0,0 @@ -// -// Created by Mingliang Chen on 17/8/1. -// illuspas[a]gmail.com -// Copyright (c) 2018 Nodemedia. All rights reserved. -// -const lodash = require("lodash") -const os = require("os") - -const { cpu } = require("../lib") - -const Logger = require("./lib/logger") -const RtmpServer = require("./servers/rtmp_server") -const TransServer = require("./servers/trans_server") -const RelayServer = require("./servers/relay_server") -const FissionServer = require("./servers/fission_server") - -const context = require("./ctx") - -class MediaServer { - constructor(config) { - this.config = config - this.context = context - } - - run() { - Logger.setLogType(this.config.logType) - - if (this.config.rtmp) { - this.nrs = new RtmpServer(this.config) - this.nrs.run() - } - - if (this.config.trans) { - if (this.config.cluster) { - Logger.log("TransServer does not work in cluster mode") - } else { - this.nts = new TransServer(this.config) - this.nts.run() - } - } - - if (this.config.relay) { - if (this.config.cluster) { - Logger.log("RelayServer does not work in cluster mode") - } else { - this.nls = new RelayServer(this.config) - this.nls.run() - } - } - - if (this.config.fission) { - if (this.config.cluster) { - Logger.log("FissionServer does not work in cluster mode") - } else { - this.nfs = new FissionServer(this.config) - this.nfs.run() - } - } - - process.on("uncaughtException", function (err) { - Logger.error("uncaughtException", err) - }) - - process.on("SIGINT", function () { - process.exit() - }) - } - - on = (eventName, listener) => { - context.nodeEvent.on(eventName, listener) - } - - stop = () => { - if (this.nrs) { - this.nrs.stop() - } - if (this.nhs) { - this.nhs.stop() - } - if (this.nls) { - this.nls.stop() - } - if (this.nfs) { - this.nfs.stop() - } - } - - getSession = (id) => { - return context.sessions.get(id) - } - - getSessions = () => { - let stats = {}; - - this.context.sessions.forEach(function (session, id) { - if (session.isStarting) { - let regRes = /\/(.*)\/(.*)/gi.exec(session.publishStreamPath || session.playStreamPath) - - if (regRes === null) { - return - } - - let [app, stream] = lodash.slice(regRes, 1) - - if (!lodash.get(stats, [app, stream])) { - lodash.setWith(stats, [app, stream], { - publisher: null, - subscribers: [] - }, Object) - } - - switch (true) { - case session.isPublishing: { - lodash.setWith(stats, [app, stream, "publisher"], { - app: app, - stream: stream, - clientId: session.id, - connectCreated: session.connectTime, - bytes: session.socket.bytesRead, - ip: session.socket.remoteAddress, - audio: session.audioCodec > 0 ? { - codec: session.audioCodecName, - profile: session.audioProfileName, - samplerate: session.audioSamplerate, - channels: session.audioChannels - } : null, - video: session.videoCodec > 0 ? { - codec: session.videoCodecName, - width: session.videoWidth, - height: session.videoHeight, - profile: session.videoProfileName, - level: session.videoLevel, - fps: session.videoFps - } : null, - }, Object) - break; - } - case !!session.playStreamPath: { - switch (session.constructor.name) { - case "NodeRtmpSession": { - stats[app][stream]["subscribers"].push({ - app: app, - stream: stream, - clientId: session.id, - connectCreated: session.connectTime, - bytes: session.socket.bytesWritten, - ip: session.socket.remoteAddress, - protocol: "rtmp" - }) - - break - } - case "NodeFlvSession": { - stats[app][stream]["subscribers"].push({ - app: app, - stream: stream, - clientId: session.id, - connectCreated: session.connectTime, - bytes: session.req.connection.bytesWritten, - ip: session.req.connection.remoteAddress, - protocol: session.TAG === "websocket-flv" ? "ws" : "http" - }) - - break - } - } - - break - } - } - } - }) - - return stats - } - - getSessionsInfo = () => { - let info = { - inbytes: 0, - outbytes: 0, - rtmp: 0, - http: 0, - ws: 0, - } - - for (let session of this.context.sessions.values()) { - if (session.TAG === "relay") { - continue - } - - let socket = session.TAG === "rtmp" ? session.socket : session.req.socket - - info.inbytes += socket.bytesRead - info.outbytes += socket.bytesWritten - info.rtmp += session.TAG === "rtmp" ? 1 : 0 - info.http += session.TAG === "http-flv" ? 1 : 0 - info.ws += session.TAG === "websocket-flv" ? 1 : 0 - } - - return info - } - - getServerStatus = async () => { - const cpuPercentageUsage = await cpu.percentageUsage() - const sessionsInfo = this.getSessionsInfo() - - return { - os: { - arch: os.arch(), - platform: os.platform(), - release: os.release(), - }, - cpu: { - num: os.cpus().length, - load: cpuPercentageUsage, - model: os.cpus()[0].model, - speed: os.cpus()[0].speed, - }, - net: { - inbytes: this.context.stat.inbytes + sessionsInfo.inbytes, - outbytes: this.context.stat.outbytes + sessionsInfo.outbytes, - }, - mem: { - totle: os.totalmem(), - free: os.freemem() - }, - nodejs: { - uptime: Math.floor(process.uptime()), - version: process.version, - mem: process.memoryUsage() - }, - } - } -} - -module.exports = MediaServer \ No newline at end of file diff --git a/packages/streaming-server/src/internal-nms/lib/amf_rules.js b/packages/streaming-server/src/internal-nms/lib/amf_rules.js deleted file mode 100644 index 631b7eb3..00000000 --- a/packages/streaming-server/src/internal-nms/lib/amf_rules.js +++ /dev/null @@ -1,1214 +0,0 @@ -/** - * Created by delian on 3/12/14. - * This module provides encoding and decoding of the AMF0 and AMF3 format - */ -const Logger = require("./logger") - -const amf3dRules = { - 0x00: amf3decUndefined, - 0x01: amf3decNull, - 0x02: amf3decFalse, - 0x03: amf3decTrue, - 0x04: amf3decInteger, - 0x05: amf3decDouble, - 0x06: amf3decString, - 0x07: amf3decXmlDoc, - 0x08: amf3decDate, - 0x09: amf3decArray, - 0x0A: amf3decObject, - 0x0B: amf3decXml, - 0x0C: amf3decByteArray //, - // 0x0D: amf3decVecInt, - // 0x0E: amf3decVecUInt, - // 0x0F: amf3decVecDouble, - // 0x10: amf3decVecObject, - // 0x11: amf3decDict // No dictionary support for the moment! -} - -const amf3eRules = { - "string": amf3encString, - "integer": amf3encInteger, - "double": amf3encDouble, - "xml": amf3encXmlDoc, - "object": amf3encObject, - "array": amf3encArray, - "sarray": amf3encArray, - "binary": amf3encByteArray, - "true": amf3encTrue, - "false": amf3encFalse, - "undefined": amf3encUndefined, - "null": amf3encNull -} - -const amf0dRules = { - 0x00: amf0decNumber, - 0x01: amf0decBool, - 0x02: amf0decString, - 0x03: amf0decObject, - // 0x04: amf0decMovie, // Reserved - 0x05: amf0decNull, - 0x06: amf0decUndefined, - 0x07: amf0decRef, - 0x08: amf0decArray, - // 0x09: amf0decObjEnd, // Should never happen normally - 0x0A: amf0decSArray, - 0x0B: amf0decDate, - 0x0C: amf0decLongString, - // 0x0D: amf0decUnsupported, // Has been never originally implemented by Adobe! - // 0x0E: amf0decRecSet, // Has been never originally implemented by Adobe! - 0x0F: amf0decXmlDoc, - 0x10: amf0decTypedObj, - 0x11: amf0decSwitchAmf3 -} - -const amf0eRules = { - "string": amf0encString, - "integer": amf0encNumber, - "double": amf0encNumber, - "xml": amf0encXmlDoc, - "object": amf0encObject, - "array": amf0encArray, - "sarray": amf0encSArray, - "binary": amf0encString, - "true": amf0encBool, - "false": amf0encBool, - "undefined": amf0encUndefined, - "null": amf0encNull -} - -function amfType(o) { - let jsType = typeof o - - if (o === null) return "null" - if (jsType == "undefined") return "undefined" - if (jsType == "number") { - if (parseInt(o) == o) return "integer" - return "double" - } - if (jsType == "boolean") return o ? "true" : "false" - if (jsType == "string") return "string" - if (jsType == "object") { - if (o instanceof Array) { - if (o.sarray) return "sarray" - return "array" - } - return "object" - } - throw new Error("Unsupported type!") -} - -// AMF3 implementation - -/** - * AMF3 Decode undefined value - * @returns {{len: number, value: undefined}} - */ -function amf3decUndefined() { - return { len: 1, value: undefined } -} - -/** - * AMF3 Encode undefined value - * @returns {Buffer} - */ -function amf3encUndefined() { - let buf = Buffer.alloc(1) - buf.writeUInt8(0x00) - return buf -} - -/** - * AMF3 Decode null - * @returns {{len: number, value: null}} - */ -function amf3decNull() { - return { len: 1, value: null } -} - -/** - * AMF3 Encode null - * @returns {Buffer} - */ -function amf3encNull() { - let buf = Buffer.alloc(1) - buf.writeUInt8(0x01) - return buf -} - -/** - * AMF3 Decode false - * @returns {{len: number, value: boolean}} - */ -function amf3decFalse() { - return { len: 1, value: false } -} - -/** - * AMF3 Encode false - * @returns {Buffer} - */ -function amf3encFalse() { - let buf = Buffer.alloc(1) - buf.writeUInt8(0x02) - return buf -} - -/** - * AMF3 Decode true - * @returns {{len: number, value: boolean}} - */ -function amf3decTrue() { - return { len: 1, value: true } -} - -/** - * AMF3 Encode true - * @returns {Buffer} - */ -function amf3encTrue() { - let buf = Buffer.alloc(1) - buf.writeUInt8(0x03) - return buf -} - -/** - * Generic decode of AMF3 UInt29 values - * @param buf - * @returns {{len: number, value: number}} - */ -function amf3decUI29(buf) { - let val = 0 - let len = 1 - let b - - do { - b = buf.readUInt8(len++) - val = (val << 7) + (b & 0x7F) - } while (len < 5 && b > 0x7F) - - if (len == 5) val = val | b // Preserve the major bit of the last byte - - return { len: len, value: val } -} - -/** - * Generic encode of AMF3 UInt29 value - * @param num - * @returns {Buffer} - */ -function amf3encUI29(num) { - let len = 0 - if (num < 0x80) len = 1 - if (num < 0x4000) len = 2 - if (num < 0x200000) len = 3 - if (num >= 0x200000) len = 4 - let buf = Buffer.alloc(len) - switch (len) { - case 1: - buf.writeUInt8(num, 0) - break - case 2: - buf.writeUInt8(num & 0x7F, 0) - buf.writeUInt8((num >> 7) | 0x80, 1) - break - case 3: - buf.writeUInt8(num & 0x7F, 0) - buf.writeUInt8((num >> 7) & 0x7F, 1) - buf.writeUInt8((num >> 14) | 0x80, 2) - break - case 4: - buf.writeUInt8(num & 0xFF, 0) - buf.writeUInt8((num >> 8) & 0x7F, 1) - buf.writeUInt8((num >> 15) | 0x7F, 2) - buf.writeUInt8((num >> 22) | 0x7F, 3) - break - } - return buf -} - -/** - * AMF3 Decode an integer - * @param buf - * @returns {{len: number, value: number}} - */ -function amf3decInteger(buf) { // Invert the integer - let resp = amf3decUI29(buf) - if (resp.value > 0x0FFFFFFF) resp.value = (resp.value & 0x0FFFFFFF) - 0x10000000 - return resp -} - -/** - * AMF3 Encode an integer - * @param num - * @returns {Buffer} - */ -function amf3encInteger(num) { - let buf = Buffer.alloc(1) - buf.writeUInt8(0x4, 0) - return Buffer.concat([buf, amf3encUI29(num & 0x3FFFFFFF)]) // This AND will auto convert the sign bit! -} - -/** - * AMF3 Decode String - * @param buf - * @returns {{len: *, value: (*|String)}} - */ -function amf3decString(buf) { - let sLen = amf3decUI29(buf) - let s = sLen.value & 1 - sLen.value = sLen.value >> 1 // The real length without the lowest bit - if (s) return { len: sLen.value + sLen.len, value: buf.slice(sLen.len, sLen.len + sLen.value).toString("utf8") } - throw new Error("Error, we have a need to decode a String that is a Reference") // TODO: Implement references! -} - -/** - * AMF3 Encode String - * @param str - * @returns {Buffer} - */ -function amf3encString(str) { - let sLen = amf3encUI29(str.length << 1) - let buf = Buffer.alloc(1) - buf.writeUInt8(0x6, 0) - return Buffer.concat([buf, sLen, Buffer.from(str, "utf8")]) -} - -/** - * AMF3 Decode XMLDoc - * @param buf - * @returns {{len: *, value: (*|String)}} - */ -function amf3decXmlDoc(buf) { - let sLen = amf3decUI29(buf) - let s = sLen.value & 1 - sLen.value = sLen.value >> 1 // The real length without the lowest bit - if (s) return { len: sLen.value + sLen.len, value: buf.slice(sLen.len, sLen.len + sLen.value).toString("utf8") } - throw new Error("Error, we have a need to decode a String that is a Reference") // TODO: Implement references! -} - -/** - * AMF3 Encode XMLDoc - * @param str - * @returns {Buffer} - */ -function amf3encXmlDoc(str) { - let sLen = amf3encUI29(str.length << 1) - let buf = Buffer.alloc(1) - buf.writeUInt8(0x7, 0) - return Buffer.concat([buf, sLen, Buffer.from(str, "utf8")]) -} - -/** - * AMF3 Decode Generic XML - * @param buf - * @returns {{len: *, value: (*|String)}} - */ -function amf3decXml(buf) { - let sLen = amf3decUI29(buf) - let s = sLen.value & 1 - sLen.value = sLen.value >> 1 // The real length without the lowest bit - if (s) return { len: sLen.value + sLen.len, value: buf.slice(sLen.len, sLen.len + sLen.value).toString("utf8") } - throw new Error("Error, we have a need to decode a String that is a Reference") // TODO: Implement references! -} - -/** - * AMF3 Encode Generic XML - * @param str - * @returns {Buffer} - */ -function amf3encXml(str) { - let sLen = amf3encUI29(str.length << 1) - let buf = Buffer.alloc(1) - buf.writeUInt8(0x0B, 0) - return Buffer.concat([buf, sLen, Buffer.from(str, "utf8")]) -} - -/** - * AMF3 Decide Byte Array - * @param buf - * @returns {{len: *, value: (Array|string|*|Buffer|Blob)}} - */ -function amf3decByteArray(buf) { - let sLen = amf3decUI29(buf) - let s = sLen.value & 1 - sLen.value = sLen.value >> 1 // The real length without the lowest bit - if (s) return { len: sLen.value + sLen.len, value: buf.slice(sLen.len, sLen.len + sLen.value) } - throw new Error("Error, we have a need to decode a String that is a Reference") // TODO: Implement references! -} - -/** - * AMF3 Encode Byte Array - * @param str - * @returns {Buffer} - */ -function amf3encByteArray(str) { - let sLen = amf3encUI29(str.length << 1) - let buf = Buffer.alloc(1) - buf.writeUInt8(0x0C, 0) - return Buffer.concat([buf, sLen, (typeof str == "string") ? Buffer.from(str, "binary") : str]) -} - -/** - * AMF3 Decode Double - * @param buf - * @returns {{len: number, value: (*|Number)}} - */ -function amf3decDouble(buf) { - return { len: 9, value: buf.readDoubleBE(1) } -} - -/** - * AMF3 Encode Double - * @param num - * @returns {Buffer} - */ -function amf3encDouble(num) { - let buf = Buffer.alloc(9) - buf.writeUInt8(0x05, 0) - buf.writeDoubleBE(num, 1) - return buf -} - -/** - * AMF3 Decode Date - * @param buf - * @returns {{len: *, value: (*|Number)}} - */ -function amf3decDate(buf) { // The UI29 should be 1 - let uTz = amf3decUI29(buf) - let ts = buf.readDoubleBE(uTz.len) - return { len: uTz.len + 8, value: ts } -} - -/** - * AMF3 Encode Date - * @param ts - * @returns {Buffer} - */ -function amf3encDate(ts) { - let buf = Buffer.alloc(1) - buf.writeUInt8(0x8, 0) - let tsBuf = Buffer.alloc(8) - tsBuf.writeDoubleBE(ts, 0) - return Buffer.concat([buf, amf3encUI29(1), tsBuf]) // We always do 1 -} - -/** - * AMF3 Decode Array - * @param buf - * @returns {{len: *, value: *}} - */ -function amf3decArray(buf) { - let count = amf3decUI29(buf) - let obj = amf3decObject(buf.slice(count.len)) - if (count.value & 1) throw new Error("This is a reference to another array, which currently we don\"t support!") - return { len: count.len + obj.len, value: obj.value } -} - -/** - * AMF3 Encode Array - */ -function amf3encArray() { - throw new Error("Encoding arrays is not supported yet!") // TODO: Implement encoding of arrays -} - -/** - * AMF3 Decode Object - * @param buf - */ -function amf3decObject(buf) { - let obj = {} - let pos = 0 - return obj -} - -/** - * AMF3 Encode Object - * @param o - */ -function amf3encObject(o) { - -} - -// AMF0 Implementation - -/** - * AMF0 Decode Number - * @param buf - * @returns {{len: number, value: (*|Number)}} - */ -function amf0decNumber(buf) { - return { len: 9, value: buf.readDoubleBE(1) } -} - -/** - * AMF0 Encode Number - * @param num - * @returns {Buffer} - */ -function amf0encNumber(num) { - let buf = Buffer.alloc(9) - buf.writeUInt8(0x00, 0) - buf.writeDoubleBE(num, 1) - return buf -} - -/** - * AMF0 Decode Boolean - * @param buf - * @returns {{len: number, value: boolean}} - */ -function amf0decBool(buf) { - return { len: 2, value: (buf.readUInt8(1) != 0) } -} - -/** - * AMF0 Encode Boolean - * @param num - * @returns {Buffer} - */ -function amf0encBool(num) { - let buf = Buffer.alloc(2) - buf.writeUInt8(0x01, 0) - buf.writeUInt8((num ? 1 : 0), 1) - return buf -} - -/** - * AMF0 Decode Null - * @returns {{len: number, value: null}} - */ -function amf0decNull() { - return { len: 1, value: null } -} - -/** - * AMF0 Encode Null - * @returns {Buffer} - */ -function amf0encNull() { - let buf = Buffer.alloc(1) - buf.writeUInt8(0x05, 0) - return buf -} - -/** - * AMF0 Decode Undefined - * @returns {{len: number, value: undefined}} - */ -function amf0decUndefined() { - return { len: 1, value: undefined } -} - -/** - * AMF0 Encode Undefined - * @returns {Buffer} - */ -function amf0encUndefined() { - let buf = Buffer.alloc(1) - buf.writeUInt8(0x06, 0) - return buf -} - -/** - * AMF0 Decode Date - * @param buf - * @returns {{len: number, value: (*|Number)}} - */ -function amf0decDate(buf) { - // let s16 = buf.readInt16BE(1) - let ts = buf.readDoubleBE(3) - return { len: 11, value: ts } -} - -/** - * AMF0 Encode Date - * @param ts - * @returns {Buffer} - */ -function amf0encDate(ts) { - let buf = Buffer.alloc(11) - buf.writeUInt8(0x0B, 0) - buf.writeInt16BE(0, 1) - buf.writeDoubleBE(ts, 3) - return buf -} - -/** - * AMF0 Decode Object - * @param buf - * @returns {{len: number, value: {}}} - */ -function amf0decObject(buf) { // TODO: Implement references! - let obj = {} - let iBuf = buf.slice(1) - let len = 1 - // Logger.debug("ODec",iBuf.readUInt8(0)) - while (iBuf.readUInt8(0) != 0x09) { - // Logger.debug("Field", iBuf.readUInt8(0), iBuf) - let prop = amf0decUString(iBuf) - // Logger.debug("Got field for property", prop) - len += prop.len - if (iBuf.length < prop.len) { - break - } - if (iBuf.slice(prop.len).readUInt8(0) == 0x09) { - len++ - // Logger.debug("Found the end property") - break - } // END Object as value, we shall leave - if (prop.value == "") break - let val = amf0DecodeOne(iBuf.slice(prop.len)) - // Logger.debug("Got field for value", val) - obj[prop.value] = val.value - len += val.len - iBuf = iBuf.slice(prop.len + val.len) - } - return { len: len, value: obj } -} - -/** - * AMF0 Encode Object - */ -function amf0encObject(o) { - if (typeof o !== "object") return - - let data = Buffer.alloc(1) - data.writeUInt8(0x03, 0) // Type object - let k - for (k in o) { - data = Buffer.concat([data, amf0encUString(k), amf0EncodeOne(o[k])]) - } - let termCode = Buffer.alloc(1) - termCode.writeUInt8(0x09, 0) - return Buffer.concat([data, amf0encUString(""), termCode]) -} - -/** - * AMF0 Decode Reference - * @param buf - * @returns {{len: number, value: string}} - */ -function amf0decRef(buf) { - let index = buf.readUInt16BE(1) - return { len: 3, value: "ref" + index } -} - -/** - * AMF0 Encode Reference - * @param index - * @returns {Buffer} - */ -function amf0encRef(index) { - let buf = Buffer.alloc(3) - buf.writeUInt8(0x07, 0) - buf.writeUInt16BE(index, 1) - return buf -} - -/** - * AMF0 Decode String - * @param buf - * @returns {{len: *, value: (*|string|String)}} - */ -function amf0decString(buf) { - let sLen = buf.readUInt16BE(1) - return { len: 3 + sLen, value: buf.toString("utf8", 3, 3 + sLen) } -} - -/** - * AMF0 Decode Untyped (without the type byte) String - * @param buf - * @returns {{len: *, value: (*|string|String)}} - */ -function amf0decUString(buf) { - let sLen = buf.readUInt16BE(0) - return { len: 2 + sLen, value: buf.toString("utf8", 2, 2 + sLen) } -} - -/** - * Do AMD0 Encode of Untyped String - * @param s - * @returns {Buffer} - */ -function amf0encUString(str) { - let data = Buffer.from(str, "utf8") - let sLen = Buffer.alloc(2) - sLen.writeUInt16BE(data.length, 0) - return Buffer.concat([sLen, data]) -} - -/** - * AMF0 Encode String - * @param str - * @returns {Buffer} - */ -function amf0encString(str) { - let buf = Buffer.alloc(3) - buf.writeUInt8(0x02, 0) - buf.writeUInt16BE(str.length, 1) - return Buffer.concat([buf, Buffer.from(str, "utf8")]) -} - -/** - * AMF0 Decode Long String - * @param buf - * @returns {{len: *, value: (*|string|String)}} - */ -function amf0decLongString(buf) { - let sLen = buf.readUInt32BE(1) - return { len: 5 + sLen, value: buf.toString("utf8", 5, 5 + sLen) } -} - -/** - * AMF0 Encode Long String - * @param str - * @returns {Buffer} - */ -function amf0encLongString(str) { - let buf = Buffer.alloc(5) - buf.writeUInt8(0x0C, 0) - buf.writeUInt32BE(str.length, 1) - return Buffer.concat([buf, Buffer.from(str, "utf8")]) -} - -/** - * AMF0 Decode Array - * @param buf - * @returns {{len: *, value: ({}|*)}} - */ -function amf0decArray(buf) { - // let count = buf.readUInt32BE(1) - let obj = amf0decObject(buf.slice(4)) - return { len: 5 + obj.len, value: obj.value } -} - -/** - * AMF0 Encode Array - */ -function amf0encArray(a) { - let l = 0 - - if (a instanceof Array) { - l = a.length - } else { - l = Object.keys(a).length - } - - Logger.debug("Array encode", l, a) - - let buf = Buffer.alloc(5) - - buf.writeUInt8(8, 0) - buf.writeUInt32BE(l, 1) - - let data = amf0encObject(a) - - return Buffer.concat([buf, data.slice(1)]) -} - -/** - * AMF0 Encode Binary Array into binary Object - * @param aData - * @returns {Buffer} - */ -function amf0cnletray2Object(aData) { - let buf = Buffer.alloc(1) - buf.writeUInt8(0x3, 0) // Object id - return Buffer.concat([buf, aData.slice(5)]) -} - -/** - * AMF0 Encode Binary Object into binary Array - * @param oData - * @returns {Buffer} - */ -function amf0cnvObject2Array(oData) { - let buf = Buffer.alloc(5) - let o = amf0decObject(oData) - let l = Object.keys(o).length - buf.writeUInt32BE(l, 1) - return Buffer.concat([buf, oData.slice(1)]) -} - -/** - * AMF0 Decode XMLDoc - * @param buf - * @returns {{len: *, value: (*|string|String)}} - */ -function amf0decXmlDoc(buf) { - let sLen = buf.readUInt16BE(1) - return { len: 3 + sLen, value: buf.toString("utf8", 3, 3 + sLen) } -} - -/** - * AMF0 Encode XMLDoc - * @param str - * @returns {Buffer} - */ -function amf0encXmlDoc(str) { // Essentially it is the same as string - let buf = Buffer.alloc(3) - buf.writeUInt8(0x0F, 0) - buf.writeUInt16BE(str.length, 1) - return Buffer.concat([buf, Buffer.from(str, "utf8")]) -} - -/** - * AMF0 Decode Strict Array - * @param buf - * @returns {{len: number, value: Array}} - */ -function amf0decSArray(buf) { - let a = [] - let len = 5 - let ret - - for (let count = buf.readUInt32BE(1); count; count--) { - ret = amf0DecodeOne(buf.slice(len)) - a.push(ret.value) - len += ret.len - } - - return { len: len, value: amf0markSArray(a) } -} - -/** - * AMF0 Encode Strict Array - * @param a Array - */ -function amf0encSArray(a) { - Logger.debug("Do strict array!") - - let buf = Buffer.alloc(5) - - buf.writeUInt8(0x0A, 0) - buf.writeUInt32BE(a.length, 1) - - let i - - for (i = 0; i < a.length; i++) { - buf = Buffer.concat([buf, amf0EncodeOne(a[i])]) - } - - return buf -} - -function amf0markSArray(a) { - Object.defineProperty(a, "sarray", { value: true }) - - return a -} - -/** - * AMF0 Decode Typed Object - * @param buf - * @returns {{len: number, value: ({}|*)}} - */ -function amf0decTypedObj(buf) { - let className = amf0decString(buf) - let obj = amf0decObject(buf.slice(className.len - 1)) - - obj.value.__className__ = className.value - - return { len: className.len + obj.len - 1, value: obj.value } -} - -/** - * AMF0 Decode Switch AMF3 Object - * @param buf - * @returns {{len: number, value: ({}|*)}} - */ -function amf0decSwitchAmf3(buf) { - let r = amf3DecodeOne(buf.slice(1)) - - return r -} - -/** - * AMF0 Encode Typed Object - */ -function amf0encTypedObj() { - throw new Error("Error: SArray encoding is not yet implemented!") // TODO: Error -} - -/** - * Decode one value from the Buffer according to the applied rules - * @param rules - * @param buffer - * @returns {*} - */ -function amfXDecodeOne(rules, buffer) { - if (!rules[buffer.readUInt8(0)]) { - Logger.error("Unknown field", buffer.readUInt8(0)) - return null - } - - return rules[buffer.readUInt8(0)](buffer) -} - -/** - * Decode one AMF0 value - * @param buffer - * @returns {*} - */ -function amf0DecodeOne(buffer) { - return amfXDecodeOne(amf0dRules, buffer) -} - -/** - * Decode one AMF3 value - * @param buffer - * @returns {*} - */ -function amf3DecodeOne(buffer) { - return amfXDecodeOne(amf3dRules, buffer) -} - -/** - * Decode a whole buffer of AMF values according to rules and return in array - * @param rules - * @param buffer - * @returns {Array} - */ -function amfXDecode(rules, buffer) { - // We shall receive clean buffer and will respond with an array of values - let resp = [] - let res - - for (let i = 0; i < buffer.length;) { - res = amfXDecodeOne(rules, buffer.slice(i)) - i += res.len - resp.push(res.value) // Add the response - } - - return resp -} - -/** - * Decode a buffer of AMF3 values - * @param buffer - * @returns {Array} - */ -function amf3Decode(buffer) { - return amfXDecode(amf3dRules, buffer) -} - -/** - * Decode a buffer of AMF0 values - * @param buffer - * @returns {Array} - */ -function amf0Decode(buffer) { - return amfXDecode(amf0dRules, buffer) -} - -/** - * Encode one AMF value according to rules - * @param rules - * @param o - * @returns {*} - */ -function amfXEncodeOne(rules, o) { - // Logger.debug("amfXEncodeOne type",o,amfType(o),rules[amfType(o)]) - let f = rules[amfType(o)] - - if (f) { - return f(o) - } - - throw new Error("Unsupported type for encoding!") -} - -/** - * Encode one AMF0 value - * @param o - * @returns {*} - */ -function amf0EncodeOne(o) { - return amfXEncodeOne(amf0eRules, o) -} - -/** - * Encode one AMF3 value - * @param o - * @returns {*} - */ -function amf3EncodeOne(o) { - return amfXEncodeOne(amf3eRules, o) -} - -/** - * Encode an array of values into a buffer - * @param a - * @returns {Buffer} - */ -function amf3Encode(a) { - let buf = Buffer.alloc(0) - a.forEach(function (o) { - buf = Buffer.concat([buf, amf3EncodeOne(o)]) - }) - return buf -} - -/** - * Encode an array of values into a buffer - * @param a - * @returns {Buffer} - */ -function amf0Encode(a) { - let buf = Buffer.alloc(0) - a.forEach(function (o) { - buf = Buffer.concat([buf, amf0EncodeOne(o)]) - }) - return buf -} - - -const rtmpCmdCode = { - "_result": ["transId", "cmdObj", "info"], - "_error": ["transId", "cmdObj", "info", "streamId"], // Info / Streamid are optional - "onStatus": ["transId", "cmdObj", "info"], - "releaseStream": ["transId", "cmdObj", "streamName"], - "getStreamLength": ["transId", "cmdObj", "streamId"], - "getMovLen": ["transId", "cmdObj", "streamId"], - "FCPublish": ["transId", "cmdObj", "streamName"], - "FCUnpublish": ["transId", "cmdObj", "streamName"], - "FCSubscribe": ["transId", "cmdObj", "streamName"], - "onFCPublish": ["transId", "cmdObj", "info"], - "connect": ["transId", "cmdObj", "args"], - "call": ["transId", "cmdObj", "args"], - "createStream": ["transId", "cmdObj"], - "close": ["transId", "cmdObj"], - "play": ["transId", "cmdObj", "streamName", "start", "duration", "reset"], - "play2": ["transId", "cmdObj", "params"], - "deleteStream": ["transId", "cmdObj", "streamId"], - "closeStream": ["transId", "cmdObj"], - "receiveAudio": ["transId", "cmdObj", "bool"], - "receiveVideo": ["transId", "cmdObj", "bool"], - "publish": ["transId", "cmdObj", "streamName", "type"], - "seek": ["transId", "cmdObj", "ms"], - "pause": ["transId", "cmdObj", "pause", "ms"] -} - -const rtmpDataCode = { - "@setDataFrame": ["method", "dataObj"], - "onFI": ["info"], - "onMetaData": ["dataObj"], - "|RtmpSampleAccess": ["bool1", "bool2"], -} - - -/** - * Decode a data! - * @param dbuf - * @returns {{cmd: (*|string|String|*), value: *}} - */ -function decodeAmf0Data(dbuf) { - let buffer = dbuf - let resp = {} - - let cmd = amf0DecodeOne(buffer) - if (cmd) { - resp.cmd = cmd.value - buffer = buffer.slice(cmd.len) - - if (rtmpDataCode[cmd.value]) { - rtmpDataCode[cmd.value].forEach(function (n) { - if (buffer.length > 0) { - let r = amf0DecodeOne(buffer) - if (r) { - buffer = buffer.slice(r.len) - resp[n] = r.value - } - } - }) - } else { - Logger.error("Unknown command", resp) - } - } - - return resp -} - -/** - * Decode a command! - * @param dbuf - * @returns {{cmd: (*|string|String|*), value: *}} - */ -function decodeAMF0Cmd(dbuf) { - let buffer = dbuf - let resp = {} - - let cmd = amf0DecodeOne(buffer) - resp.cmd = cmd.value - buffer = buffer.slice(cmd.len) - - if (rtmpCmdCode[cmd.value]) { - rtmpCmdCode[cmd.value].forEach(function (n) { - if (buffer.length > 0) { - let r = amf0DecodeOne(buffer) - buffer = buffer.slice(r.len) - resp[n] = r.value - } - }) - } else { - Logger.error("Unknown command", resp) - } - return resp -} - -/** - * Encode AMF0 Command - * @param opt - * @returns {*} - */ -function encodeAMF0Cmd(opt) { - let data = amf0EncodeOne(opt.cmd) - - if (rtmpCmdCode[opt.cmd]) { - rtmpCmdCode[opt.cmd].forEach(function (n) { - if (opt.hasOwnProperty(n)) - data = Buffer.concat([data, amf0EncodeOne(opt[n])]) - }) - } else { - Logger.error("Unknown command", opt) - } - // Logger.debug("Encoded as",data.toString("hex")) - return data -} - -function encodeAMF0Data(opt) { - let data = amf0EncodeOne(opt.cmd) - - if (rtmpDataCode[opt.cmd]) { - rtmpDataCode[opt.cmd].forEach(function (n) { - if (opt.hasOwnProperty(n)) - data = Buffer.concat([data, amf0EncodeOne(opt[n])]) - }) - } else { - Logger.error("Unknown data", opt) - } - // Logger.debug("Encoded as",data.toString("hex")) - return data -} - -/** - * - * @param dbuf - * @returns {{}} - */ -function decodeAMF3Cmd(dbuf) { - let buffer = dbuf - let resp = {} - - let cmd = amf3DecodeOne(buffer) - resp.cmd = cmd.value - buffer = buffer.slice(cmd.len) - - if (rtmpCmdCode[cmd.value]) { - rtmpCmdCode[cmd.value].forEach(function (n) { - if (buffer.length > 0) { - let r = amf3DecodeOne(buffer) - buffer = buffer.slice(r.len) - resp[n] = r.value - } - }) - } else { - Logger.error("Unknown command", resp) - } - return resp -} - -/** - * Encode AMF3 Command - * @param opt - * @returns {*} - */ -function encodeAMF3Cmd(opt) { - let data = amf0EncodeOne(opt.cmd) - - if (rtmpCmdCode[opt.cmd]) { - rtmpCmdCode[opt.cmd].forEach(function (n) { - if (opt.hasOwnProperty(n)) - data = Buffer.concat([data, amf3EncodeOne(opt[n])]) - }) - } else { - Logger.error("Unknown command", opt) - } - return data -} - -module.exports = { - decodeAmf3Cmd: decodeAMF3Cmd, - encodeAmf3Cmd: encodeAMF3Cmd, - decodeAmf0Cmd: decodeAMF0Cmd, - encodeAmf0Cmd: encodeAMF0Cmd, - decodeAmf0Data: decodeAmf0Data, - encodeAmf0Data: encodeAMF0Data, - amfType: amfType, - amf0Encode: amf0Encode, - amf0EncodeOne: amf0EncodeOne, - amf0Decode: amf0Decode, - amf0DecodeOne: amf0DecodeOne, - amf3Encode: amf3Encode, - amf3EncodeOne: amf3EncodeOne, - amf3Decode: amf3Decode, - amf3DecodeOne: amf3DecodeOne, - amf0cnvA2O: amf0cnletray2Object, - amf0cnvO2A: amf0cnvObject2Array, - amf0markSArray: amf0markSArray, - amf0decArray: amf0decArray, - amf0decBool: amf0decBool, - amf0decDate: amf0decDate, - amf0decLongString: amf0decLongString, - amf0decNull: amf0decNull, - amf0decNumber: amf0decNumber, - amf0decObject: amf0decObject, - amf0decRef: amf0decRef, - amf0decSArray: amf0decSArray, - amf0decString: amf0decString, - amf0decTypedObj: amf0decTypedObj, - amf0decUndefined: amf0decUndefined, - amf0decXmlDoc: amf0decXmlDoc, - amf0encArray: amf0encArray, - amf0encBool: amf0encBool, - amf0encDate: amf0encDate, - amf0encLongString: amf0encLongString, - amf0encNull: amf0encNull, - amf0encNumber: amf0encNumber, - amf0encObject: amf0encObject, - amf0encRef: amf0encRef, - amf0encSArray: amf0encSArray, - amf0encString: amf0encString, - amf0encTypedObj: amf0encTypedObj, - amf0encUndefined: amf0encUndefined, - amf0encXmlDoc: amf0encXmlDoc, - amf3decArray: amf3decArray, - amf3decByteArray: amf3decByteArray, - amf3decDate: amf3decDate, - amf3decDouble: amf3decDouble, - amf3decFalse: amf3decFalse, - amf3decInteger: amf3decInteger, - amf3decNull: amf3decNull, - amf3decObject: amf3decObject, - amf3decString: amf3decString, - amf3decTrue: amf3decTrue, - amf3decUI29: amf3decUI29, - amf3decUndefined: amf3decUndefined, - amf3decXml: amf3decXml, - amf3decXmlDoc: amf3decXmlDoc, - amf3encArray: amf3encArray, - amf3encByteArray: amf3encByteArray, - amf3encDate: amf3encDate, - amf3encDouble: amf3encDouble, - amf3encFalse: amf3encFalse, - amf3encInteger: amf3encInteger, - amf3encNull: amf3encNull, - amf3encObject: amf3encObject, - amf3encString: amf3encString, - amf3encTrue: amf3encTrue, - amf3encUI29: amf3encUI29, - amf3encUndefined: amf3encUndefined, - amf3encXml: amf3encXml, - amf3encXmlDoc: amf3encXmlDoc -} diff --git a/packages/streaming-server/src/internal-nms/lib/av.js b/packages/streaming-server/src/internal-nms/lib/av.js deleted file mode 100644 index 637015fd..00000000 --- a/packages/streaming-server/src/internal-nms/lib/av.js +++ /dev/null @@ -1,515 +0,0 @@ -// -// Created by Mingliang Chen on 17/12/21. -// illuspas[a]gmail.com -// Copyright (c) 2018 Nodemedia. All rights reserved. -// - -const Bitop = require("./bitop") - -const AAC_SAMPLE_RATE = [ - 96000, 88200, 64000, 48000, - 44100, 32000, 24000, 22050, - 16000, 12000, 11025, 8000, - 7350, 0, 0, 0 -] - -const AAC_CHANNELS = [ - 0, 1, 2, 3, 4, 5, 6, 8 -] - -const AUDIO_CODEC_NAME = [ - "", - "ADPCM", - "MP3", - "LinearLE", - "Nellymoser16", - "Nellymoser8", - "Nellymoser", - "G711A", - "G711U", - "", - "AAC", - "Speex", - "", - "OPUS", - "MP3-8K", - "DeviceSpecific", - "Uncompressed" -] - -const AUDIO_SOUND_RATE = [ - 5512, 11025, 22050, 44100 -] - -const VIDEO_CODEC_NAME = [ - "", - "Jpeg", - "Sorenson-H263", - "ScreenVideo", - "On2-VP6", - "On2-VP6-Alpha", - "ScreenVideo2", - "H264", - "", - "", - "", - "", - "H265" -] - -function getObjectType(bitop) { - let audioObjectType = bitop.read(5) - if (audioObjectType === 31) { - audioObjectType = bitop.read(6) + 32 - } - return audioObjectType -} - -function getSampleRate(bitop, info) { - info.sampling_index = bitop.read(4) - return info.sampling_index == 0x0f ? bitop.read(24) : AAC_SAMPLE_RATE[info.sampling_index] -} - -function readAACSpecificConfig(aacSequenceHeader) { - let info = {} - let bitop = new Bitop(aacSequenceHeader) - bitop.read(16) - info.object_type = getObjectType(bitop) - info.sample_rate = getSampleRate(bitop, info) - info.chan_config = bitop.read(4) - if (info.chan_config < AAC_CHANNELS.length) { - info.channels = AAC_CHANNELS[info.chan_config] - } - info.sbr = -1 - info.ps = -1 - if (info.object_type == 5 || info.object_type == 29) { - if (info.object_type == 29) { - info.ps = 1 - } - info.ext_object_type = 5 - info.sbr = 1 - info.sample_rate = getSampleRate(bitop, info) - info.object_type = getObjectType(bitop) - } - - return info -} - -function getAACProfileName(info) { - switch (info.object_type) { - case 1: - return "Main" - case 2: - if (info.ps > 0) { - return "HEv2" - } - if (info.sbr > 0) { - return "HE" - } - return "LC" - case 3: - return "SSR" - case 4: - return "LTP" - case 5: - return "SBR" - default: - return "" - } -} - -function readH264SpecificConfig(avcSequenceHeader) { - let info = {} - let profile_idc, width, height, crop_left, crop_right, - crop_top, crop_bottom, frame_mbs_only, n, cf_idc, - num_ref_frames - - let bitop = new Bitop(avcSequenceHeader) - - bitop.read(48) - info.width = 0 - info.height = 0 - - do { - info.profile = bitop.read(8) - info.compat = bitop.read(8) - info.level = bitop.read(8) - info.nalu = (bitop.read(8) & 0x03) + 1 - info.nb_sps = bitop.read(8) & 0x1F - if (info.nb_sps == 0) { - break - } - /* nal size */ - bitop.read(16) - - /* nal type */ - if (bitop.read(8) != 0x67) { - break - } - /* SPS */ - profile_idc = bitop.read(8) - - /* flags */ - bitop.read(8) - - /* level idc */ - bitop.read(8) - - /* SPS id */ - bitop.read_golomb() - - if (profile_idc == 100 || profile_idc == 110 || - profile_idc == 122 || profile_idc == 244 || profile_idc == 44 || - profile_idc == 83 || profile_idc == 86 || profile_idc == 118) { - /* chroma format idc */ - cf_idc = bitop.read_golomb() - - if (cf_idc == 3) { - - /* separate color plane */ - bitop.read(1) - } - - /* bit depth luma - 8 */ - bitop.read_golomb() - - /* bit depth chroma - 8 */ - bitop.read_golomb() - - /* qpprime y zero transform bypass */ - bitop.read(1) - - /* seq scaling matrix present */ - if (bitop.read(1)) { - - for (n = 0; n < (cf_idc != 3 ? 8 : 12); n++) { - - /* seq scaling list present */ - if (bitop.read(1)) { - - /* TODO: scaling_list() - if (n < 6) { - } else { - } - */ - } - } - } - } - - /* log2 max frame num */ - bitop.read_golomb() - - /* pic order cnt type */ - switch (bitop.read_golomb()) { - case 0: - - /* max pic order cnt */ - bitop.read_golomb() - break - - case 1: - - /* delta pic order alwys zero */ - bitop.read(1) - - /* offset for non-ref pic */ - bitop.read_golomb() - - /* offset for top to bottom field */ - bitop.read_golomb() - - /* num ref frames in pic order */ - num_ref_frames = bitop.read_golomb() - - for (n = 0; n < num_ref_frames; n++) { - - /* offset for ref frame */ - bitop.read_golomb() - } - } - - /* num ref frames */ - info.avc_ref_frames = bitop.read_golomb() - - /* gaps in frame num allowed */ - bitop.read(1) - - /* pic width in mbs - 1 */ - width = bitop.read_golomb() - - /* pic height in map units - 1 */ - height = bitop.read_golomb() - - /* frame mbs only flag */ - frame_mbs_only = bitop.read(1) - - if (!frame_mbs_only) { - - /* mbs adaprive frame field */ - bitop.read(1) - } - - /* direct 8x8 inference flag */ - bitop.read(1) - - /* frame cropping */ - if (bitop.read(1)) { - - crop_left = bitop.read_golomb() - crop_right = bitop.read_golomb() - crop_top = bitop.read_golomb() - crop_bottom = bitop.read_golomb() - - } else { - crop_left = 0 - crop_right = 0 - crop_top = 0 - crop_bottom = 0 - } - info.level = info.level / 10.0 - info.width = (width + 1) * 16 - (crop_left + crop_right) * 2 - info.height = (2 - frame_mbs_only) * (height + 1) * 16 - (crop_top + crop_bottom) * 2 - - } while (0) - - return info -} - -function HEVCParsePtl(bitop, hevc, max_sub_layers_minus1) { - let general_ptl = {} - - general_ptl.profile_space = bitop.read(2) - general_ptl.tier_flag = bitop.read(1) - general_ptl.profile_idc = bitop.read(5) - general_ptl.profile_compatibility_flags = bitop.read(32) - general_ptl.general_progressive_source_flag = bitop.read(1) - general_ptl.general_interlaced_source_flag = bitop.read(1) - general_ptl.general_non_packed_constraint_flag = bitop.read(1) - general_ptl.general_frame_only_constraint_flag = bitop.read(1) - bitop.read(32) - bitop.read(12) - general_ptl.level_idc = bitop.read(8) - - general_ptl.sub_layer_profile_present_flag = [] - general_ptl.sub_layer_level_present_flag = [] - - for (let i = 0; i < max_sub_layers_minus1; i++) { - general_ptl.sub_layer_profile_present_flag[i] = bitop.read(1) - general_ptl.sub_layer_level_present_flag[i] = bitop.read(1) - } - - if (max_sub_layers_minus1 > 0) { - for (let i = max_sub_layers_minus1; i < 8; i++) { - bitop.read(2) - } - } - - general_ptl.sub_layer_profile_space = [] - general_ptl.sub_layer_tier_flag = [] - general_ptl.sub_layer_profile_idc = [] - general_ptl.sub_layer_profile_compatibility_flag = [] - general_ptl.sub_layer_progressive_source_flag = [] - general_ptl.sub_layer_interlaced_source_flag = [] - general_ptl.sub_layer_non_packed_constraint_flag = [] - general_ptl.sub_layer_frame_only_constraint_flag = [] - general_ptl.sub_layer_level_idc = [] - - for (let i = 0; i < max_sub_layers_minus1; i++) { - if (general_ptl.sub_layer_profile_present_flag[i]) { - general_ptl.sub_layer_profile_space[i] = bitop.read(2) - general_ptl.sub_layer_tier_flag[i] = bitop.read(1) - general_ptl.sub_layer_profile_idc[i] = bitop.read(5) - general_ptl.sub_layer_profile_compatibility_flag[i] = bitop.read(32) - general_ptl.sub_layer_progressive_source_flag[i] = bitop.read(1) - general_ptl.sub_layer_interlaced_source_flag[i] = bitop.read(1) - general_ptl.sub_layer_non_packed_constraint_flag[i] = bitop.read(1) - general_ptl.sub_layer_frame_only_constraint_flag[i] = bitop.read(1) - bitop.read(32) - bitop.read(12) - } - if (general_ptl.sub_layer_level_present_flag[i]) { - general_ptl.sub_layer_level_idc[i] = bitop.read(8) - } - else { - general_ptl.sub_layer_level_idc[i] = 1 - } - } - return general_ptl -} - -function HEVCParseSPS(SPS, hevc) { - let psps = {} - let NumBytesInNALunit = SPS.length - let NumBytesInRBSP = 0 - let rbsp_array = [] - let bitop = new Bitop(SPS) - - bitop.read(1)//forbidden_zero_bit - bitop.read(6)//nal_unit_type - bitop.read(6)//nuh_reserved_zero_6bits - bitop.read(3)//nuh_temporal_id_plus1 - - for (let i = 2; i < NumBytesInNALunit; i++) { - if (i + 2 < NumBytesInNALunit && bitop.look(24) == 0x000003) { - rbsp_array.push(bitop.read(8)) - rbsp_array.push(bitop.read(8)) - i += 2 - let emulation_prevention_three_byte = bitop.read(8) /* equal to 0x03 */ - } else { - rbsp_array.push(bitop.read(8)) - } - } - - let rbsp = Buffer.from(rbsp_array) - let rbspBitop = new Bitop(rbsp) - - psps.sps_video_parameter_set_id = rbspBitop.read(4) - psps.sps_max_sub_layers_minus1 = rbspBitop.read(3) - psps.sps_temporal_id_nesting_flag = rbspBitop.read(1) - psps.profile_tier_level = HEVCParsePtl(rbspBitop, hevc, psps.sps_max_sub_layers_minus1) - psps.sps_seq_parameter_set_id = rbspBitop.read_golomb() - psps.chroma_format_idc = rbspBitop.read_golomb() - - if (psps.chroma_format_idc == 3) { - psps.separate_colour_plane_flag = rbspBitop.read(1) - } else { - psps.separate_colour_plane_flag = 0 - } - - psps.pic_width_in_luma_samples = rbspBitop.read_golomb() - psps.pic_height_in_luma_samples = rbspBitop.read_golomb() - psps.conformance_window_flag = rbspBitop.read(1) - - if (psps.conformance_window_flag) { - let vert_mult = 1 + (psps.chroma_format_idc < 2) - let horiz_mult = 1 + (psps.chroma_format_idc < 3) - psps.conf_win_left_offset = rbspBitop.read_golomb() * horiz_mult - psps.conf_win_right_offset = rbspBitop.read_golomb() * horiz_mult - psps.conf_win_top_offset = rbspBitop.read_golomb() * vert_mult - psps.conf_win_bottom_offset = rbspBitop.read_golomb() * vert_mult - } - - // Logger.debug(psps) - return psps -} - -function readHEVCSpecificConfig(hevcSequenceHeader) { - let info = {} - info.width = 0 - info.height = 0 - info.profile = 0 - info.level = 0 - // let bitop = new Bitop(hevcSequenceHeader) - // bitop.read(48) - hevcSequenceHeader = hevcSequenceHeader.slice(5) - - do { - let hevc = {} - - if (hevcSequenceHeader.length < 23) { - break - } - - hevc.configurationVersion = hevcSequenceHeader[0] - - if (hevc.configurationVersion != 1) { - break - } - - hevc.general_profile_space = (hevcSequenceHeader[1] >> 6) & 0x03 - hevc.general_tier_flag = (hevcSequenceHeader[1] >> 5) & 0x01 - hevc.general_profile_idc = hevcSequenceHeader[1] & 0x1F - hevc.general_profile_compatibility_flags = (hevcSequenceHeader[2] << 24) | (hevcSequenceHeader[3] << 16) | (hevcSequenceHeader[4] << 8) | hevcSequenceHeader[5] - hevc.general_constraint_indicator_flags = ((hevcSequenceHeader[6] << 24) | (hevcSequenceHeader[7] << 16) | (hevcSequenceHeader[8] << 8) | hevcSequenceHeader[9]) - hevc.general_constraint_indicator_flags = (hevc.general_constraint_indicator_flags << 16) | (hevcSequenceHeader[10] << 8) | hevcSequenceHeader[11] - hevc.general_level_idc = hevcSequenceHeader[12] - hevc.min_spatial_segmentation_idc = ((hevcSequenceHeader[13] & 0x0F) << 8) | hevcSequenceHeader[14] - hevc.parallelismType = hevcSequenceHeader[15] & 0x03 - hevc.chromaFormat = hevcSequenceHeader[16] & 0x03 - hevc.bitDepthLumaMinus8 = hevcSequenceHeader[17] & 0x07 - hevc.bitDepthChromaMinus8 = hevcSequenceHeader[18] & 0x07 - hevc.avgFrameRate = (hevcSequenceHeader[19] << 8) | hevcSequenceHeader[20] - hevc.constantFrameRate = (hevcSequenceHeader[21] >> 6) & 0x03 - hevc.numTemporalLayers = (hevcSequenceHeader[21] >> 3) & 0x07 - hevc.temporalIdNested = (hevcSequenceHeader[21] >> 2) & 0x01 - hevc.lengthSizeMinusOne = hevcSequenceHeader[21] & 0x03 - - let numOfArrays = hevcSequenceHeader[22] - let p = hevcSequenceHeader.slice(23) - - for (let i = 0; i < numOfArrays; i++) { - if (p.length < 3) { - brak - } - let nalutype = p[0] - let n = (p[1]) << 8 | p[2] - // Logger.debug(nalutype, n) - p = p.slice(3) - - for (let j = 0; j < n; j++) { - if (p.length < 2) { - break - } - let k = (p[0] << 8) | p[1] - // Logger.debug("k", k) - if (p.length < 2 + k) { - break - } - p = p.slice(2) - if (nalutype == 33) { - //SPS - let sps = Buffer.alloc(k) - p.copy(sps, 0, 0, k) - // Logger.debug(sps, sps.length) - hevc.psps = HEVCParseSPS(sps, hevc) - info.profile = hevc.general_profile_idc - info.level = hevc.general_level_idc / 30.0 - info.width = hevc.psps.pic_width_in_luma_samples - (hevc.psps.conf_win_left_offset + hevc.psps.conf_win_right_offset) - info.height = hevc.psps.pic_height_in_luma_samples - (hevc.psps.conf_win_top_offset + hevc.psps.conf_win_bottom_offset) - } - p = p.slice(k) - } - } - } while (0) - - return info -} - -function readAVCSpecificConfig(avcSequenceHeader) { - let codec_id = avcSequenceHeader[0] & 0x0f - if (codec_id == 7) { - return readH264SpecificConfig(avcSequenceHeader) - } else if (codec_id == 12) { - return readHEVCSpecificConfig(avcSequenceHeader) - } -} - -function getAVCProfileName(info) { - switch (info.profile) { - case 1: - return "Main" - case 2: - return "Main 10" - case 3: - return "Main Still Picture" - case 66: - return "Baseline" - case 77: - return "Main" - case 100: - return "High" - default: - return "" - } -} - -module.exports = { - AUDIO_SOUND_RATE, - AUDIO_CODEC_NAME, - VIDEO_CODEC_NAME, - readAACSpecificConfig, - getAACProfileName, - readAVCSpecificConfig, - getAVCProfileName, -} diff --git a/packages/streaming-server/src/internal-nms/lib/bitop.js b/packages/streaming-server/src/internal-nms/lib/bitop.js deleted file mode 100644 index 047edd2e..00000000 --- a/packages/streaming-server/src/internal-nms/lib/bitop.js +++ /dev/null @@ -1,53 +0,0 @@ - -class Bitop { - constructor(buffer) { - this.buffer = buffer; - this.buflen = buffer.length; - this.bufpos = 0; - this.bufoff = 0; - this.iserro = false; - } - - read(n) { - let v = 0; - let d = 0; - while (n) { - if (n < 0 || this.bufpos >= this.buflen) { - this.iserro = true; - return 0; - } - - this.iserro = false; - d = this.bufoff + n > 8 ? 8 - this.bufoff : n; - - v <<= d; - v += (this.buffer[this.bufpos] >> (8 - this.bufoff - d)) & (0xff >> (8 - d)); - - this.bufoff += d; - n -= d; - - if (this.bufoff == 8) { - this.bufpos++; - this.bufoff = 0; - } - } - return v; - } - - look(n) { - let p = this.bufpos; - let o = this.bufoff; - let v = this.read(n); - this.bufpos = p; - this.bufoff = o; - return v; - } - - read_golomb() { - let n; - for (n = 0; this.read(1) == 0 && !this.iserro; n++); - return (1 << n) + this.read(n) - 1; - } -} - -module.exports = Bitop; diff --git a/packages/streaming-server/src/internal-nms/lib/logger.js b/packages/streaming-server/src/internal-nms/lib/logger.js deleted file mode 100644 index da087f3a..00000000 --- a/packages/streaming-server/src/internal-nms/lib/logger.js +++ /dev/null @@ -1,53 +0,0 @@ -const chalk = require("chalk") - -const LOG_TYPES = { - NONE: 0, - ERROR: 1, - NORMAL: 2, - DEBUG: 3, - FFDEBUG: 4 -} - -let logType = LOG_TYPES.NORMAL - -const setLogType = (type) => { - if (typeof type !== "number") return - - logType = type -} - -const logTime = () => { - let nowDate = new Date() - return nowDate.toLocaleDateString() + " " + nowDate.toLocaleTimeString([], { hour12: false }) -} - -const log = (...args) => { - if (logType < LOG_TYPES.NORMAL) return - - console.log(logTime(), process.pid, chalk.bold.green("[INFO]"), ...args) -} - -const error = (...args) => { - if (logType < LOG_TYPES.ERROR) return - - console.log(logTime(), process.pid, chalk.bold.red("[ERROR]"), ...args) -} - -const debug = (...args) => { - if (logType < LOG_TYPES.DEBUG) return - - console.log(logTime(), process.pid, chalk.bold.blue("[DEBUG]"), ...args) -} - -const ffdebug = (...args) => { - if (logType < LOG_TYPES.FFDEBUG) return - - console.log(logTime(), process.pid, chalk.bold.blue("[FFDEBUG]"), ...args) -} - -module.exports = { - LOG_TYPES, - setLogType, - - log, error, debug, ffdebug -} \ No newline at end of file diff --git a/packages/streaming-server/src/internal-nms/lib/utils.js b/packages/streaming-server/src/internal-nms/lib/utils.js deleted file mode 100644 index e96392ab..00000000 --- a/packages/streaming-server/src/internal-nms/lib/utils.js +++ /dev/null @@ -1,106 +0,0 @@ -// -// Created by Mingliang Chen on 17/8/23. -// illuspas[a]gmail.com -// Copyright (c) 2018 Nodemedia. All rights reserved. -// -const Crypto = require("crypto") -const { spawn } = require("child_process") - -const context = require("../ctx") - -function generateNewSessionID() { - let sessionID = "" - const possible = "ABCDEFGHIJKLMNOPQRSTUVWKYZ0123456789" - const numPossible = possible.length - - do { - for (let i = 0; i < 8; i++) { - sessionID += possible.charAt((Math.random() * numPossible) | 0) - } - } while (context.sessions.has(sessionID)) - - return sessionID -} - -function genRandomName() { - let name = "" - const possible = "abcdefghijklmnopqrstuvwxyz0123456789" - const numPossible = possible.length - - for (let i = 0; i < 4; i++) { - name += possible.charAt((Math.random() * numPossible) | 0) - } - - return name -} - -function verifyAuth(signStr, streamId, secretKey) { - if (signStr === undefined) { - return false - } - - let now = Date.now() / 1000 | 0 - let exp = parseInt(signStr.split("-")[0]) - let shv = signStr.split("-")[1] - let str = streamId + "-" + exp + "-" + secretKey - - if (exp < now) { - return false - } - - let md5 = Crypto.createHash("md5") - let ohv = md5.update(str).digest("hex") - - return shv === ohv -} - -function getFFmpegVersion(ffpath) { - return new Promise((resolve, reject) => { - let ffmpeg_exec = spawn(ffpath, ["-version"]) - let version = "" - - ffmpeg_exec.on("error", (e) => { - reject(e) - }) - - ffmpeg_exec.stdout.on("data", (data) => { - try { - version = data.toString().split(/(?:\r\n|\r|\n)/g)[0].split("\ ")[2] - } catch (e) { - } - }) - - ffmpeg_exec.on("close", (code) => { - resolve(version) - }) - }) -} - -function getFFmpegUrl() { - let url = "" - - switch (process.platform) { - case "darwin": - url = "https://ffmpeg.zeranoe.com/builds/macos64/static/ffmpeg-latest-macos64-static.zip" - break - case "win32": - url = "https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-latest-win64-static.zip | https://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-latest-win32-static.zip" - break - case "linux": - url = "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz | https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-i686-static.tar.xz" - break - default: - url = "http://ffmpeg.org/download.html" - break - } - - return url -} - -module.exports = { - generateNewSessionID, - verifyAuth, - genRandomName, - getFFmpegVersion, - getFFmpegUrl -} diff --git a/packages/streaming-server/src/internal-nms/rtmp_client.js b/packages/streaming-server/src/internal-nms/rtmp_client.js deleted file mode 100644 index f89d63d9..00000000 --- a/packages/streaming-server/src/internal-nms/rtmp_client.js +++ /dev/null @@ -1,793 +0,0 @@ -// -// Created by Mingliang Chen on 18/6/21. -// illuspas[a]gmail.com -// Copyright (c) 2018 Nodemedia. All rights reserved. -// - -const EventEmitter = require("events") -const Crypto = require("crypto") -const Url = require("url") -const Net = require("net") -const AMF = require("./lib/amf_rules") - -const Logger = require("./lib/logger") - -const FLASHVER = "LNX 9,0,124,2" -const RTMP_OUT_CHUNK_SIZE = 60000 -const RTMP_PORT = 1935 - -const RTMP_HANDSHAKE_SIZE = 1536 -const RTMP_HANDSHAKE_UNINIT = 0 -const RTMP_HANDSHAKE_0 = 1 -const RTMP_HANDSHAKE_1 = 2 -const RTMP_HANDSHAKE_2 = 3 - -const RTMP_PARSE_INIT = 0 -const RTMP_PARSE_BASIC_HEADER = 1 -const RTMP_PARSE_MESSAGE_HEADER = 2 -const RTMP_PARSE_EXTENDED_TIMESTAMP = 3 -const RTMP_PARSE_PAYLOAD = 4 - -const RTMP_CHUNK_HEADER_MAX = 18 - -const RTMP_CHUNK_TYPE_0 = 0 // 11-bytes: timestamp(3) + length(3) + stream type(1) + stream id(4) -const RTMP_CHUNK_TYPE_1 = 1 // 7-bytes: delta(3) + length(3) + stream type(1) -const RTMP_CHUNK_TYPE_2 = 2 // 3-bytes: delta(3) -const RTMP_CHUNK_TYPE_3 = 3 // 0-byte - -const RTMP_CHANNEL_PROTOCOL = 2 -const RTMP_CHANNEL_INVOKE = 3 -const RTMP_CHANNEL_AUDIO = 4 -const RTMP_CHANNEL_VIDEO = 5 -const RTMP_CHANNEL_DATA = 6 - -const rtmpHeaderSize = [11, 7, 3, 0] - - -/* Protocol Control Messages */ -const RTMP_TYPE_SET_CHUNK_SIZE = 1 -const RTMP_TYPE_ABORT = 2 -const RTMP_TYPE_ACKNOWLEDGEMENT = 3 // bytes read report -const RTMP_TYPE_WINDOW_ACKNOWLEDGEMENT_SIZE = 5 // server bandwidth -const RTMP_TYPE_SET_PEER_BANDWIDTH = 6 // client bandwidth - -/* User Control Messages Event (4) */ -const RTMP_TYPE_EVENT = 4 - -const RTMP_TYPE_AUDIO = 8 -const RTMP_TYPE_VIDEO = 9 - -/* Data Message */ -const RTMP_TYPE_FLEX_STREAM = 15 // AMF3 -const RTMP_TYPE_DATA = 18 // AMF0 - -/* Shared Object Message */ -const RTMP_TYPE_FLEX_OBJECT = 16 // AMF3 -const RTMP_TYPE_SHARED_OBJECT = 19 // AMF0 - -/* Command Message */ -const RTMP_TYPE_FLEX_MESSAGE = 17 // AMF3 -const RTMP_TYPE_INVOKE = 20 // AMF0 - -/* Aggregate Message */ -const RTMP_TYPE_METADATA = 22 - -const RTMP_CHUNK_SIZE = 128 -const RTMP_PING_TIME = 60000 -const RTMP_PING_TIMEOUT = 30000 - -const STREAM_BEGIN = 0x00 -const STREAM_EOF = 0x01 -const STREAM_DRY = 0x02 -const STREAM_EMPTY = 0x1f -const STREAM_READY = 0x20 - -const RTMP_TRANSACTION_CONNECT = 1 -const RTMP_TRANSACTION_CREATE_STREAM = 2 -const RTMP_TRANSACTION_GET_STREAM_LENGTH = 3 - -const RtmpPacket = { - create: (fmt = 0, cid = 0) => { - return { - header: { - fmt: fmt, - cid: cid, - timestamp: 0, - length: 0, - type: 0, - stream_id: 0 - }, - clock: 0, - delta: 0, - payload: null, - capacity: 0, - bytes: 0 - } - } -} - -class NodeRtmpClient { - constructor(rtmpUrl) { - this.url = rtmpUrl - this.info = this.rtmpUrlParser(rtmpUrl) - this.isPublish = false - this.launcher = new EventEmitter() - - this.handshakePayload = Buffer.alloc(RTMP_HANDSHAKE_SIZE) - this.handshakeState = RTMP_HANDSHAKE_UNINIT - this.handshakeBytes = 0 - - this.parserBuffer = Buffer.alloc(RTMP_CHUNK_HEADER_MAX) - this.parserState = RTMP_PARSE_INIT - this.parserBytes = 0 - this.parserBasicBytes = 0 - this.parserPacket = null - this.inPackets = new Map() - - this.inChunkSize = RTMP_CHUNK_SIZE - this.outChunkSize = RTMP_CHUNK_SIZE - - this.streamId = 0 - this.isSocketOpen = false - } - - onSocketData(data) { - let bytes = data.length - let p = 0 - let n = 0 - - while (bytes > 0) { - switch (this.handshakeState) { - case RTMP_HANDSHAKE_UNINIT: - // read s0 - // Logger.debug("[rtmp client] read s0") - this.handshakeState = RTMP_HANDSHAKE_0 - this.handshakeBytes = 0 - bytes -= 1 - p += 1 - break - case RTMP_HANDSHAKE_0: - // read s1 - n = RTMP_HANDSHAKE_SIZE - this.handshakeBytes - n = n <= bytes ? n : bytes - data.copy(this.handshakePayload, this.handshakeBytes, p, p + n) - this.handshakeBytes += n - bytes -= n - p += n - if (this.handshakeBytes === RTMP_HANDSHAKE_SIZE) { - // Logger.debug("[rtmp client] read s1") - this.handshakeState = RTMP_HANDSHAKE_1 - this.handshakeBytes = 0 - this.socket.write(this.handshakePayload)// write c2 - // Logger.debug("[rtmp client] write c2") - } - break - case RTMP_HANDSHAKE_1: - //read s2 - n = RTMP_HANDSHAKE_SIZE - this.handshakeBytes - n = n <= bytes ? n : bytes - data.copy(this.handshakePayload, this.handshakeBytes, p, n) - this.handshakeBytes += n - bytes -= n - p += n - if (this.handshakeBytes === RTMP_HANDSHAKE_SIZE) { - // Logger.debug("[rtmp client] read s2") - this.handshakeState = RTMP_HANDSHAKE_2 - this.handshakeBytes = 0 - this.handshakePayload = null - - this.rtmpSendConnect() - } - break - case RTMP_HANDSHAKE_2: - return this.rtmpChunkRead(data, p, bytes) - } - } - } - - onSocketError(e) { - Logger.error("rtmp_client", "onSocketError", e) - this.isSocketOpen = false - this.stop() - } - - onSocketClose() { - // Logger.debug("rtmp_client", "onSocketClose") - this.isSocketOpen = false - this.stop() - } - - onSocketTimeout() { - // Logger.debug("rtmp_client", "onSocketTimeout") - this.isSocketOpen = false - this.stop() - } - - on(event, callback) { - this.launcher.on(event, callback) - } - - startPull() { - this._start() - } - - startPush() { - this.isPublish = true - this._start() - } - - _start() { - this.socket = Net.createConnection(this.info.port, this.info.hostname, () => { - //rtmp handshark c0c1 - let c0c1 = Crypto.randomBytes(1537) - c0c1.writeUInt8(3) - c0c1.writeUInt32BE(Date.now() / 1000, 1) - c0c1.writeUInt32BE(0, 5) - this.socket.write(c0c1) - // Logger.debug("[rtmp client] write c0c1") - }) - this.socket.on("data", this.onSocketData.bind(this)) - this.socket.on("error", this.onSocketError.bind(this)) - this.socket.on("close", this.onSocketClose.bind(this)) - this.socket.on("timeout", this.onSocketTimeout.bind(this)) - this.socket.setTimeout(60000) - } - - stop() { - if (this.streamId > 0) { - if (!this.socket.destroyed) { - if (this.isPublish) { - this.rtmpSendFCUnpublish() - } - this.rtmpSendDeleteStream() - this.socket.destroy() - } - this.streamId = 0 - this.launcher.emit("close") - } - } - - pushAudio(audioData, timestamp) { - if (this.streamId == 0) return - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_AUDIO - packet.header.type = RTMP_TYPE_AUDIO - packet.payload = audioData - packet.header.length = packet.payload.length - packet.header.timestamp = timestamp - let rtmpChunks = this.rtmpChunksCreate(packet) - this.socket.write(rtmpChunks) - } - - pushVideo(videoData, timestamp) { - if (this.streamId == 0) return - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_VIDEO - packet.header.type = RTMP_TYPE_VIDEO - packet.payload = videoData - packet.header.length = packet.payload.length - packet.header.timestamp = timestamp - let rtmpChunks = this.rtmpChunksCreate(packet) - this.socket.write(rtmpChunks) - } - - pushScript(scriptData, timestamp) { - if (this.streamId == 0) return - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_DATA - packet.header.type = RTMP_TYPE_DATA - packet.payload = scriptData - packet.header.length = packet.payload.length - packet.header.timestamp = timestamp - let rtmpChunks = this.rtmpChunksCreate(packet) - this.socket.write(rtmpChunks) - } - - rtmpUrlParser(url) { - let urlInfo = Url.parse(url, true) - urlInfo.app = urlInfo.path.split("/")[1] - urlInfo.port = urlInfo.port ? urlInfo.port : RTMP_PORT - urlInfo.tcurl = urlInfo.href.match(/rtmp:\/\/([^\/]+)\/([^\/]+)/)[0] - urlInfo.stream = urlInfo.path.slice(urlInfo.app.length + 2) - return urlInfo - } - - rtmpChunkBasicHeaderCreate(fmt, cid) { - let out - if (cid >= 64 + 255) { - out = Buffer.alloc(3) - out[0] = (fmt << 6) | 1 - out[1] = (cid - 64) & 0xFF - out[2] = ((cid - 64) >> 8) & 0xFF - } else if (cid >= 64) { - out = Buffer.alloc(2) - out[0] = (fmt << 6) | 0 - out[1] = (cid - 64) & 0xFF - } else { - out = Buffer.alloc(1) - out[0] = (fmt << 6) | cid - } - return out - } - - rtmpChunkMessageHeaderCreate(header) { - let out = Buffer.alloc(rtmpHeaderSize[header.fmt % 4]) - if (header.fmt <= RTMP_CHUNK_TYPE_2) { - out.writeUIntBE(header.timestamp >= 0xffffff ? 0xffffff : header.timestamp, 0, 3) - } - - if (header.fmt <= RTMP_CHUNK_TYPE_1) { - out.writeUIntBE(header.length, 3, 3) - out.writeUInt8(header.type, 6) - } - - if (header.fmt === RTMP_CHUNK_TYPE_0) { - out.writeUInt32LE(header.stream_id, 7) - } - return out - } - - rtmpChunksCreate(packet) { - let header = packet.header - let payload = packet.payload - let payloadSize = header.length - let chunkSize = this.outChunkSize - let chunksOffset = 0 - let payloadOffset = 0 - - let chunkBasicHeader = this.rtmpChunkBasicHeaderCreate(header.fmt, header.cid) - let chunkBasicHeader3 = this.rtmpChunkBasicHeaderCreate(RTMP_CHUNK_TYPE_3, header.cid) - let chunkMessageHeader = this.rtmpChunkMessageHeaderCreate(header) - let useExtendedTimestamp = header.timestamp >= 0xffffff - let headerSize = chunkBasicHeader.length + chunkMessageHeader.length + (useExtendedTimestamp ? 4 : 0) - - let n = headerSize + payloadSize + Math.floor(payloadSize / chunkSize) - if (useExtendedTimestamp) { - n += Math.floor(payloadSize / chunkSize) * 4 - } - if (!(payloadSize % chunkSize)) { - n -= 1 - if (useExtendedTimestamp) { //TODO CHECK - n -= 4 - } - } - - let chunks = Buffer.alloc(n) - chunkBasicHeader.copy(chunks, chunksOffset) - chunksOffset += chunkBasicHeader.length - chunkMessageHeader.copy(chunks, chunksOffset) - chunksOffset += chunkMessageHeader.length - if (useExtendedTimestamp) { - chunks.writeUInt32BE(header.timestamp, chunksOffset) - chunksOffset += 4 - } - while (payloadSize > 0) { - if (payloadSize > chunkSize) { - payload.copy(chunks, chunksOffset, payloadOffset, payloadOffset + chunkSize) - payloadSize -= chunkSize - chunksOffset += chunkSize - payloadOffset += chunkSize - chunkBasicHeader3.copy(chunks, chunksOffset) - chunksOffset += chunkBasicHeader3.length - if (useExtendedTimestamp) { - chunks.writeUInt32BE(header.timestamp, chunksOffset) - chunksOffset += 4 - } - } else { - payload.copy(chunks, chunksOffset, payloadOffset, payloadOffset + payloadSize) - payloadSize -= payloadSize - chunksOffset += payloadSize - payloadOffset += payloadSize - } - } - return chunks - } - - rtmpChunkRead(data, p, bytes) { - let size = 0 - let offset = 0 - let extended_timestamp = 0 - - while (offset < bytes) { - switch (this.parserState) { - case RTMP_PARSE_INIT: - this.parserBytes = 1 - this.parserBuffer[0] = data[p + offset++] - if (0 === (this.parserBuffer[0] & 0x3F)) { - this.parserBasicBytes = 2 - } else if (1 === (this.parserBuffer[0] & 0x3F)) { - this.parserBasicBytes = 3 - } else { - this.parserBasicBytes = 1 - } - this.parserState = RTMP_PARSE_BASIC_HEADER - break - case RTMP_PARSE_BASIC_HEADER: - while (this.parserBytes < this.parserBasicBytes && offset < bytes) { - this.parserBuffer[this.parserBytes++] = data[p + offset++] - } - if (this.parserBytes >= this.parserBasicBytes) { - this.parserState = RTMP_PARSE_MESSAGE_HEADER - } - break - case RTMP_PARSE_MESSAGE_HEADER: - size = rtmpHeaderSize[this.parserBuffer[0] >> 6] + this.parserBasicBytes - while (this.parserBytes < size && offset < bytes) { - this.parserBuffer[this.parserBytes++] = data[p + offset++] - } - if (this.parserBytes >= size) { - this.rtmpPacketParse() - this.parserState = RTMP_PARSE_EXTENDED_TIMESTAMP - } - break - case RTMP_PARSE_EXTENDED_TIMESTAMP: - size = rtmpHeaderSize[this.parserPacket.header.fmt] + this.parserBasicBytes - if (this.parserPacket.header.timestamp === 0xFFFFFF) size += 4 - while (this.parserBytes < size && offset < bytes) { - this.parserBuffer[this.parserBytes++] = data[p + offset++] - } - if (this.parserBytes >= size) { - if (this.parserPacket.header.timestamp === 0xFFFFFF) { - extended_timestamp = this.parserBuffer.readUInt32BE(rtmpHeaderSize[this.parserPacket.header.fmt] + this.parserBasicBytes) - } - - if (0 === this.parserPacket.bytes) { - if (RTMP_CHUNK_TYPE_0 === this.parserPacket.header.fmt) { - this.parserPacket.clock = 0xFFFFFF === this.parserPacket.header.timestamp ? extended_timestamp : this.parserPacket.header.timestamp - this.parserPacket.delta = 0 - } else { - this.parserPacket.delta = 0xFFFFFF === this.parserPacket.header.timestamp ? extended_timestamp : this.parserPacket.header.timestamp - } - this.rtmpPacketAlloc() - } - this.parserState = RTMP_PARSE_PAYLOAD - } - break - case RTMP_PARSE_PAYLOAD: - size = Math.min(this.inChunkSize - (this.parserPacket.bytes % this.inChunkSize), this.parserPacket.header.length - this.parserPacket.bytes) - size = Math.min(size, bytes - offset) - if (size > 0) { - data.copy(this.parserPacket.payload, this.parserPacket.bytes, p + offset, p + offset + size) - } - this.parserPacket.bytes += size - offset += size - - if (this.parserPacket.bytes >= this.parserPacket.header.length) { - this.parserState = RTMP_PARSE_INIT - this.parserPacket.bytes = 0 - this.parserPacket.clock += this.parserPacket.delta - this.rtmpHandler() - } else if (0 === (this.parserPacket.bytes % this.inChunkSize)) { - this.parserState = RTMP_PARSE_INIT - } - break - } - } - } - - rtmpPacketParse() { - let fmt = this.parserBuffer[0] >> 6 - let cid = 0 - if (this.parserBasicBytes === 2) { - cid = 64 + this.parserBuffer[1] - } else if (this.parserBasicBytes === 3) { - cid = 64 + this.parserBuffer[1] + this.parserBuffer[2] << 8 - } else { - cid = this.parserBuffer[0] & 0x3F - } - let hasp = this.inPackets.has(cid) - if (!hasp) { - this.parserPacket = RtmpPacket.create(fmt, cid) - this.inPackets.set(cid, this.parserPacket) - } else { - this.parserPacket = this.inPackets.get(cid) - } - this.parserPacket.header.fmt = fmt - this.parserPacket.header.cid = cid - this.rtmpChunkMessageHeaderRead() - // Logger.log(this.parserPacket) - - } - - rtmpChunkMessageHeaderRead() { - let offset = this.parserBasicBytes - - // timestamp / delta - if (this.parserPacket.header.fmt <= RTMP_CHUNK_TYPE_2) { - this.parserPacket.header.timestamp = this.parserBuffer.readUIntBE(offset, 3) - offset += 3 - } - - // message length + type - if (this.parserPacket.header.fmt <= RTMP_CHUNK_TYPE_1) { - this.parserPacket.header.length = this.parserBuffer.readUIntBE(offset, 3) - this.parserPacket.header.type = this.parserBuffer[offset + 3] - offset += 4 - } - - if (this.parserPacket.header.fmt === RTMP_CHUNK_TYPE_0) { - this.parserPacket.header.stream_id = this.parserBuffer.readUInt32LE(offset) - offset += 4 - } - return offset - } - - rtmpPacketAlloc() { - if (this.parserPacket.capacity < this.parserPacket.header.length) { - this.parserPacket.payload = Buffer.alloc(this.parserPacket.header.length + 1024) - this.parserPacket.capacity = this.parserPacket.header.length + 1024 - } - } - - rtmpHandler() { - switch (this.parserPacket.header.type) { - case RTMP_TYPE_SET_CHUNK_SIZE: - case RTMP_TYPE_ABORT: - case RTMP_TYPE_ACKNOWLEDGEMENT: - case RTMP_TYPE_WINDOW_ACKNOWLEDGEMENT_SIZE: - case RTMP_TYPE_SET_PEER_BANDWIDTH: - return 0 === this.rtmpControlHandler() ? -1 : 0 - case RTMP_TYPE_EVENT: - return 0 === this.rtmpEventHandler() ? -1 : 0 - case RTMP_TYPE_AUDIO: - return this.rtmpAudioHandler() - case RTMP_TYPE_VIDEO: - return this.rtmpVideoHandler() - case RTMP_TYPE_FLEX_MESSAGE: - case RTMP_TYPE_INVOKE: - return this.rtmpInvokeHandler() - case RTMP_TYPE_FLEX_STREAM:// AMF3 - case RTMP_TYPE_DATA: // AMF0 - return this.rtmpDataHandler() - } - } - - rtmpControlHandler() { - let payload = this.parserPacket.payload - switch (this.parserPacket.header.type) { - case RTMP_TYPE_SET_CHUNK_SIZE: - this.inChunkSize = payload.readUInt32BE() - // Logger.debug("set inChunkSize", this.inChunkSize) - break - case RTMP_TYPE_ABORT: - break - case RTMP_TYPE_ACKNOWLEDGEMENT: - break - case RTMP_TYPE_WINDOW_ACKNOWLEDGEMENT_SIZE: - this.ackSize = payload.readUInt32BE() - // Logger.debug("set ack Size", this.ackSize) - break - case RTMP_TYPE_SET_PEER_BANDWIDTH: - break - } - } - - rtmpEventHandler() { - let payload = this.parserPacket.payload.slice(0, this.parserPacket.header.length) - let event = payload.readUInt16BE() - let value = payload.readUInt32BE(2) - // Logger.log("rtmpEventHandler", event, value) - switch (event) { - case 6: - this.rtmpSendPingResponse(value) - break - } - } - - rtmpInvokeHandler() { - let offset = this.parserPacket.header.type === RTMP_TYPE_FLEX_MESSAGE ? 1 : 0 - let payload = this.parserPacket.payload.slice(offset, this.parserPacket.header.length) - let invokeMessage = AMF.decodeAmf0Cmd(payload) - // Logger.log("rtmpInvokeHandler", invokeMessage) - - switch (invokeMessage.cmd) { - case "_result": - this.rtmpCommandOnresult(invokeMessage) - break - case "_error": - this.rtmpCommandOnerror(invokeMessage) - break - case "onStatus": - this.rtmpCommandOnstatus(invokeMessage) - break - } - } - - rtmpCommandOnresult(invokeMessage) { - // Logger.debug(invokeMessage) - switch (invokeMessage.transId) { - case RTMP_TRANSACTION_CONNECT: - this.launcher.emit("status", invokeMessage.info) - this.rtmpOnconnect() - break - case RTMP_TRANSACTION_CREATE_STREAM: - this.rtmpOncreateStream(invokeMessage.info) - break - } - } - - rtmpCommandOnerror(invokeMessage) { - this.launcher.emit("status", invokeMessage.info) - } - - rtmpCommandOnstatus(invokeMessage) { - this.launcher.emit("status", invokeMessage.info) - } - - rtmpOnconnect() { - if (this.isPublish) { - this.rtmpSendReleaseStream() - this.rtmpSendFCPublish() - } - this.rtmpSendCreateStream() - } - - rtmpOncreateStream(sid) { - this.streamId = sid - if (this.isPublish) { - this.rtmpSendPublish() - this.rtmpSendSetChunkSize() - } else { - this.rtmpSendPlay() - this.rtmpSendSetBufferLength(1000) - } - } - - rtmpAudioHandler() { - let payload = this.parserPacket.payload.slice(0, this.parserPacket.header.length) - this.launcher.emit("audio", payload, this.parserPacket.clock) - } - - rtmpVideoHandler() { - let payload = this.parserPacket.payload.slice(0, this.parserPacket.header.length) - this.launcher.emit("video", payload, this.parserPacket.clock) - } - - rtmpDataHandler() { - let payload = this.parserPacket.payload.slice(0, this.parserPacket.header.length) - this.launcher.emit("script", payload, this.parserPacket.clock) - } - - sendInvokeMessage(sid, opt) { - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_INVOKE - packet.header.type = RTMP_TYPE_INVOKE - packet.header.stream_id = sid - packet.payload = AMF.encodeAmf0Cmd(opt) - packet.header.length = packet.payload.length - let chunks = this.rtmpChunksCreate(packet) - this.socket.write(chunks) - } - - rtmpSendConnect() { - let opt = { - cmd: "connect", - transId: RTMP_TRANSACTION_CONNECT, - cmdObj: { - app: this.info.app, - flashVer: FLASHVER, - tcUrl: this.info.tcurl, - fpad: 0, - capabilities: 15, - audioCodecs: 3191, - videoCodecs: 252, - videoFunction: 1, - encoding: 0 - } - } - this.sendInvokeMessage(0, opt) - } - - rtmpSendReleaseStream() { - let opt = { - cmd: "releaseStream", - transId: 0, - cmdObj: null, - streamName: this.info.stream, - } - this.sendInvokeMessage(this.streamId, opt) - } - - rtmpSendFCPublish() { - let opt = { - cmd: "FCPublish", - transId: 0, - cmdObj: null, - streamName: this.info.stream, - } - this.sendInvokeMessage(this.streamId, opt) - } - - rtmpSendCreateStream() { - let opt = { - cmd: "createStream", - transId: RTMP_TRANSACTION_CREATE_STREAM, - cmdObj: null - } - this.sendInvokeMessage(0, opt) - } - - rtmpSendPlay() { - let opt = { - cmd: "play", - transId: 0, - cmdObj: null, - streamName: this.info.stream, - start: -2, - duration: -1, - reset: 1 - } - this.sendInvokeMessage(this.streamId, opt) - } - - rtmpSendSetBufferLength(bufferTime) { - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_PROTOCOL - packet.header.type = RTMP_TYPE_EVENT - packet.payload = Buffer.alloc(10) - packet.header.length = packet.payload.length - packet.payload.writeUInt16BE(0x03) - packet.payload.writeUInt32BE(this.streamId, 2) - packet.payload.writeUInt32BE(bufferTime, 6) - let chunks = this.rtmpChunksCreate(packet) - this.socket.write(chunks) - } - - rtmpSendPublish() { - let opt = { - cmd: "publish", - transId: 0, - cmdObj: null, - streamName: this.info.stream, - type: "live" - } - this.sendInvokeMessage(this.streamId, opt) - } - - rtmpSendSetChunkSize() { - let rtmpBuffer = Buffer.from("02000000000004010000000000000000", "hex") - rtmpBuffer.writeUInt32BE(this.inChunkSize, 12) - this.socket.write(rtmpBuffer) - this.outChunkSize = this.inChunkSize - } - - rtmpSendFCUnpublish() { - let opt = { - cmd: "FCUnpublish", - transId: 0, - cmdObj: null, - streamName: this.info.stream, - } - this.sendInvokeMessage(this.streamId, opt) - } - - rtmpSendDeleteStream() { - let opt = { - cmd: "deleteStream", - transId: 0, - cmdObj: null, - streamId: this.streamId - } - this.sendInvokeMessage(this.streamId, opt) - } - - rtmpSendPingResponse(time) { - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_PROTOCOL - packet.header.type = RTMP_TYPE_EVENT - packet.payload = Buffer.alloc(6) - packet.header.length = packet.payload.length - packet.payload.writeUInt16BE(0x07) - packet.payload.writeUInt32BE(time, 2) - let chunks = this.rtmpChunksCreate(packet) - this.socket.write(chunks) - } -} - -module.exports = NodeRtmpClient \ No newline at end of file diff --git a/packages/streaming-server/src/internal-nms/rtmp_handshake.js b/packages/streaming-server/src/internal-nms/rtmp_handshake.js deleted file mode 100644 index e466451a..00000000 --- a/packages/streaming-server/src/internal-nms/rtmp_handshake.js +++ /dev/null @@ -1,111 +0,0 @@ -// -// Created by Mingliang Chen on 17/8/1. -// illuspas[a]gmail.com -// Copyright (c) 2018 Nodemedia. All rights reserved. -// -const Crypto = require("crypto") - -const MESSAGE_FORMAT_0 = 0 -const MESSAGE_FORMAT_1 = 1 -const MESSAGE_FORMAT_2 = 2 - -const RTMP_SIG_SIZE = 1536 -const SHA256DL = 32 - -const RandomCrud = Buffer.from([ - 0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8, - 0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57, - 0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab, - 0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae -]) - -const GenuineFMSConst = "Genuine Adobe Flash Media Server 001" -const GenuineFMSConstCrud = Buffer.concat([Buffer.from(GenuineFMSConst, "utf8"), RandomCrud]) - -const GenuineFPConst = "Genuine Adobe Flash Player 001" -const GenuineFPConstCrud = Buffer.concat([Buffer.from(GenuineFPConst, "utf8"), RandomCrud]) - -function calcHmac(data, key) { - let hmac = Crypto.createHmac("sha256", key) - hmac.update(data) - return hmac.digest() -} - -function GetClientGenuineConstDigestOffset(buf) { - let offset = buf[0] + buf[1] + buf[2] + buf[3] - offset = (offset % 728) + 12 - return offset -} - -function GetServerGenuineConstDigestOffset(buf) { - let offset = buf[0] + buf[1] + buf[2] + buf[3] - offset = (offset % 728) + 776 - return offset -} - -function detectClientMessageFormat(clientsig) { - let computedSignature, msg, providedSignature, sdl - sdl = GetServerGenuineConstDigestOffset(clientsig.slice(772, 776)) - msg = Buffer.concat([clientsig.slice(0, sdl), clientsig.slice(sdl + SHA256DL)], 1504) - computedSignature = calcHmac(msg, GenuineFPConst) - providedSignature = clientsig.slice(sdl, sdl + SHA256DL) - if (computedSignature.equals(providedSignature)) { - return MESSAGE_FORMAT_2 - } - sdl = GetClientGenuineConstDigestOffset(clientsig.slice(8, 12)) - msg = Buffer.concat([clientsig.slice(0, sdl), clientsig.slice(sdl + SHA256DL)], 1504) - computedSignature = calcHmac(msg, GenuineFPConst) - providedSignature = clientsig.slice(sdl, sdl + SHA256DL) - if (computedSignature.equals(providedSignature)) { - return MESSAGE_FORMAT_1 - } - return MESSAGE_FORMAT_0 -} - -function generateS1(messageFormat) { - let randomBytes = Crypto.randomBytes(RTMP_SIG_SIZE - 8) - let handshakeBytes = Buffer.concat([Buffer.from([0, 0, 0, 0, 1, 2, 3, 4]), randomBytes], RTMP_SIG_SIZE) - - let serverDigestOffset - if (messageFormat === 1) { - serverDigestOffset = GetClientGenuineConstDigestOffset(handshakeBytes.slice(8, 12)) - } else { - serverDigestOffset = GetServerGenuineConstDigestOffset(handshakeBytes.slice(772, 776)) - } - - let msg = Buffer.concat([handshakeBytes.slice(0, serverDigestOffset), handshakeBytes.slice(serverDigestOffset + SHA256DL)], RTMP_SIG_SIZE - SHA256DL) - let hash = calcHmac(msg, GenuineFMSConst) - hash.copy(handshakeBytes, serverDigestOffset, 0, 32) - return handshakeBytes -} - -function generateS2(messageFormat, clientsig, callback) { - let randomBytes = Crypto.randomBytes(RTMP_SIG_SIZE - 32) - let challengeKeyOffset - if (messageFormat === 1) { - challengeKeyOffset = GetClientGenuineConstDigestOffset(clientsig.slice(8, 12)) - } else { - challengeKeyOffset = GetServerGenuineConstDigestOffset(clientsig.slice(772, 776)) - } - let challengeKey = clientsig.slice(challengeKeyOffset, challengeKeyOffset + 32) - let hash = calcHmac(challengeKey, GenuineFMSConstCrud) - let signature = calcHmac(randomBytes, hash) - let s2Bytes = Buffer.concat([randomBytes, signature], RTMP_SIG_SIZE) - return s2Bytes -} - -function generateS0S1S2(clientsig) { - let clientType = Buffer.alloc(1, 3) - let messageFormat = detectClientMessageFormat(clientsig) - let allBytes - if (messageFormat === MESSAGE_FORMAT_0) { - // Logger.debug("[rtmp handshake] using simple handshake.") - allBytes = Buffer.concat([clientType, clientsig, clientsig]) - } else { - // Logger.debug("[rtmp handshake] using complex handshake.") - allBytes = Buffer.concat([clientType, generateS1(messageFormat), generateS2(messageFormat, clientsig)]) - } - return allBytes -} - -module.exports = { generateS0S1S2 } diff --git a/packages/streaming-server/src/internal-nms/servers/fission_server.js b/packages/streaming-server/src/internal-nms/servers/fission_server.js deleted file mode 100644 index 2383b070..00000000 --- a/packages/streaming-server/src/internal-nms/servers/fission_server.js +++ /dev/null @@ -1,103 +0,0 @@ -const fs = require("fs") -const loadash = require("lodash") -const mkdirp = require("mkdirp") - -const Logger = require("../lib/logger") -const FissionSession = require("../sessionsModels/fission_session") -const { getFFmpegVersion, getFFmpegUrl } = require("../lib/utils") - -const context = require("../ctx") - -class NodeFissionServer { - constructor(config) { - this.config = config - this.fissionSessions = new Map() - } - - async run() { - try { - mkdirp.sync(this.config.mediaroot) - fs.accessSync(this.config.mediaroot, fs.constants.W_OK) - } catch (error) { - Logger.error(`Node Media Fission Server startup failed. MediaRoot:${this.config.mediaroot} cannot be written.`) - return - } - - try { - fs.accessSync(this.config.fission.ffmpeg, fs.constants.X_OK) - } catch (error) { - Logger.error(`Node Media Fission Server startup failed. ffmpeg:${this.config.fission.ffmpeg} cannot be executed.`) - return - } - - let version = await getFFmpegVersion(this.config.fission.ffmpeg) - - if (version === "" || parseInt(version.split(".")[0]) < 4) { - Logger.error("Node Media Fission Server startup failed. ffmpeg requires version 4.0.0 above") - Logger.error("Download the latest ffmpeg static program:", getFFmpegUrl()) - return - } - - context.nodeEvent.on("postPublish", this.onPostPublish.bind(this)) - context.nodeEvent.on("donePublish", this.onDonePublish.bind(this)) - - Logger.log(`Node Media Fission Server started, MediaRoot: ${this.config.mediaroot}, ffmpeg version: ${version}`) - } - - async onPostPublish(id, streamPath, args) { - const fixedStreamingKey = streamPath.split("/").pop() - const userspace = await global.resolveUserspaceOfStreamingKey(fixedStreamingKey) - - if (!userspace) { - console.error("No userspace found for streaming key:", fixedStreamingKey) - return false - } - - let regRes = /\/(.*)\/(.*)/gi.exec(streamPath) - let [app, name] = loadash.slice(regRes, 1) - - for (let task of this.config.fission.tasks) { - regRes = /(.*)\/(.*)/gi.exec(task.rule) - let [ruleApp, ruleName] = loadash.slice(regRes, 1) - - if ((app === ruleApp || ruleApp === "*") && (name === ruleName || ruleName === "*")) { - let s = context.sessions.get(id) - - if (s.isLocal && name.split("_")[1]) { - continue - } - - let conf = task - - conf.ffmpeg = this.config.fission.ffmpeg - conf.mediaroot = this.config.mediaroot - conf.rtmpPort = this.config.rtmp.port - conf.streamPath = streamPath - conf.streamApp = app - conf.streamName = name - conf.fixedStreamName = userspace.username - conf.args = args - - let session = new FissionSession(conf) - - this.fissionSessions.set(id, session) - - session.on("end", () => { - this.fissionSessions.delete(id) - }) - - session.run() - } - } - } - - onDonePublish(id, streamPath, args) { - let session = this.fissionSessions.get(id) - - if (session) { - session.end() - } - } -} - -module.exports = NodeFissionServer diff --git a/packages/streaming-server/src/internal-nms/servers/relay_server.js b/packages/streaming-server/src/internal-nms/servers/relay_server.js deleted file mode 100644 index db4ca652..00000000 --- a/packages/streaming-server/src/internal-nms/servers/relay_server.js +++ /dev/null @@ -1,255 +0,0 @@ -const fs = require("fs") -const _ = require("lodash") -const querystring = require("querystring") - -const { getFFmpegVersion, getFFmpegUrl } = require("../lib/utils") -const NodeCoreUtils = require("../lib/utils") -const Logger = require("../lib/logger") -const NodeRelaySession = require("../sessionsModels/relay_session") - -const context = require("../ctx") - -class NodeRelayServer { - constructor(config) { - this.config = config - this.staticCycle = null - this.staticSessions = new Map() - this.dynamicSessions = new Map() - } - - async run() { - try { - fs.accessSync(this.config.relay.ffmpeg, fs.constants.X_OK) - } catch (error) { - Logger.error(`Node Media Relay Server startup failed. ffmpeg:${this.config.relay.ffmpeg} cannot be executed.`) - return - } - - let version = await getFFmpegVersion(this.config.relay.ffmpeg) - - if (version === "" || parseInt(version.split(".")[0]) < 4) { - Logger.error("Node Media Relay Server startup failed. ffmpeg requires version 4.0.0 above") - Logger.error("Download the latest ffmpeg static program:", getFFmpegUrl()) - return - } - - context.nodeEvent.on("relayPull", this.onRelayPull.bind(this)) - context.nodeEvent.on("relayPush", this.onRelayPush.bind(this)) - context.nodeEvent.on("prePlay", this.onPrePlay.bind(this)) - context.nodeEvent.on("donePlay", this.onDonePlay.bind(this)) - context.nodeEvent.on("postPublish", this.onPostPublish.bind(this)) - context.nodeEvent.on("donePublish", this.onDonePublish.bind(this)) - - this.staticCycle = setInterval(this.onStatic.bind(this), 1000) - - Logger.log("Node Media Relay Server started") - } - - onStatic() { - if (!this.config.relay.tasks) { - return - } - - let i = this.config.relay.tasks.length - - while (i--) { - if (this.staticSessions.has(i)) { - continue - } - - let conf = this.config.relay.tasks[i] - let isStatic = conf.mode === "static" - - if (isStatic) { - conf.name = conf.name ? conf.name : NodeCoreUtils.genRandomName() - conf.ffmpeg = this.config.relay.ffmpeg - conf.inPath = conf.edge - conf.ouPath = `rtmp://127.0.0.1:${this.config.rtmp.port}/${conf.app}/${conf.name}` - - let session = new NodeRelaySession(conf) - - session.id = i - - session.streamPath = `/${conf.app}/${conf.name}` - - session.on("end", (id) => { - this.staticSessions.delete(id) - }) - - this.staticSessions.set(i, session) - - session.run() - - Logger.log("[relay static pull] start", i, conf.inPath, "to", conf.ouPath) - } - } - } - - //从远端拉推到本地 - onRelayPull(url, app, name) { - let conf = {} - - conf.app = app - conf.name = name - conf.ffmpeg = this.config.relay.ffmpeg - conf.inPath = url - conf.ouPath = `rtmp://127.0.0.1:${this.config.rtmp.port}/${app}/${name}` - - let session = new NodeRelaySession(conf) - - const id = session.id - - context.sessions.set(id, session) - - session.on("end", (id) => { - this.dynamicSessions.delete(id) - }) - - this.dynamicSessions.set(id, session) - - session.run() - - Logger.log("[relay dynamic pull] start id=" + id, conf.inPath, "to", conf.ouPath) - - return id - } - - //从本地拉推到远端 - onRelayPush(url, app, name) { - let conf = {} - - conf.app = app - conf.name = name - conf.ffmpeg = this.config.relay.ffmpeg - conf.inPath = `rtmp://127.0.0.1:${this.config.rtmp.port}/${app}/${name}` - conf.ouPath = url - - let session = new NodeRelaySession(conf) - - const id = session.id - - context.sessions.set(id, session) - - session.on("end", (id) => { - this.dynamicSessions.delete(id) - }) - - this.dynamicSessions.set(id, session) - - session.run() - - Logger.log("[relay dynamic push] start id=" + id, conf.inPath, "to", conf.ouPath) - } - - onPrePlay(id, streamPath, args) { - if (!this.config.relay.tasks) { - return - } - - let regRes = /\/(.*)\/(.*)/gi.exec(streamPath) - let [app, stream] = _.slice(regRes, 1) - let i = this.config.relay.tasks.length - - while (i--) { - let conf = this.config.relay.tasks[i] - let isPull = conf.mode === "pull" - - if (isPull && app === conf.app && !context.publishers.has(streamPath)) { - let hasApp = conf.edge.match(/rtmp:\/\/([^\/]+)\/([^\/]+)/) - - conf.ffmpeg = this.config.relay.ffmpeg - conf.inPath = hasApp ? `${conf.edge}/${stream}` : `${conf.edge}${streamPath}` - conf.ouPath = `rtmp://127.0.0.1:${this.config.rtmp.port}${streamPath}` - - if (Object.keys(args).length > 0) { - conf.inPath += "?" - conf.inPath += querystring.encode(args) - } - - let session = new NodeRelaySession(conf) - - session.id = id - - session.on("end", (id) => { - this.dynamicSessions.delete(id) - }) - - this.dynamicSessions.set(id, session) - - session.run() - - Logger.log("[relay dynamic pull] start id=" + id, conf.inPath, "to", conf.ouPath) - } - } - } - - onDonePlay(id, streamPath, args) { - let session = this.dynamicSessions.get(id) - let publisher = context.sessions.get(context.publishers.get(streamPath)) - if (session && publisher.players.size == 0) { - session.end() - } - } - - onPostPublish(id, streamPath, args) { - if (!this.config.relay.tasks) { - return - } - - let regRes = /\/(.*)\/(.*)/gi.exec(streamPath) - let [app, stream] = _.slice(regRes, 1) - let i = this.config.relay.tasks.length - - while (i--) { - let conf = this.config.relay.tasks[i] - let isPush = conf.mode === "push" - - if (isPush && app === conf.app) { - let hasApp = conf.edge.match(/rtmp:\/\/([^\/]+)\/([^\/]+)/) - - conf.ffmpeg = this.config.relay.ffmpeg - conf.inPath = `rtmp://127.0.0.1:${this.config.rtmp.port}${streamPath}` - conf.ouPath = conf.appendName === false ? conf.edge : (hasApp ? `${conf.edge}/${stream}` : `${conf.edge}${streamPath}`) - - if (Object.keys(args).length > 0) { - conf.ouPath += "?" - conf.ouPath += querystring.encode(args) - } - - let session = new NodeRelaySession(conf) - - session.id = id - - session.on("end", (id) => { - this.dynamicSessions.delete(id) - }) - - this.dynamicSessions.set(id, session) - - session.run() - - Logger.log("[relay dynamic push] start id=" + id, conf.inPath, "to", conf.ouPath) - } - } - } - - onDonePublish(id, streamPath, args) { - let session = this.dynamicSessions.get(id) - - if (session) { - session.end() - } - - for (session of this.staticSessions.values()) { - if (session.streamPath === streamPath) { - session.end() - } - } - } - - stop() { - clearInterval(this.staticCycle) - } -} - -module.exports = NodeRelayServer diff --git a/packages/streaming-server/src/internal-nms/servers/rtmp_server.js b/packages/streaming-server/src/internal-nms/servers/rtmp_server.js deleted file mode 100644 index 2793fbf2..00000000 --- a/packages/streaming-server/src/internal-nms/servers/rtmp_server.js +++ /dev/null @@ -1,87 +0,0 @@ -// -// Created by Mingliang Chen on 17/8/1. -// illuspas[a]gmail.com -// Copyright (c) 2018 Nodemedia. All rights reserved. -// - -const Tls = require("tls") -const Fs = require("fs") -const Net = require("net") - -const NodeRtmpSession = require("../sessionsModels/rtmp_session") -const Logger = require("../lib/logger") - -const context = require("../ctx") - -const RTMP_PORT = 1935 -const RTMPS_PORT = 443 - -class NodeRtmpServer { - constructor(config) { - config.rtmp.port = this.port = config.rtmp.port ? config.rtmp.port : RTMP_PORT - - this.tcpServer = Net.createServer((socket) => { - let session = new NodeRtmpSession(config, socket) - session.run() - }) - - if (config.rtmp.ssl){ - config.rtmp.ssl.port = this.sslPort = config.rtmp.ssl.port ? config.rtmp.ssl.port : RTMPS_PORT - try { - const options = { - key: Fs.readFileSync(config.rtmp.ssl.key), - cert: Fs.readFileSync(config.rtmp.ssl.cert) - } - this.tlsServer = Tls.createServer(options, (socket) => { - let session = new NodeRtmpSession(config, socket) - session.run() - }) - } catch (e) { - Logger.error(`Node Media Rtmps Server error while reading ssl certs: <${e}>`) - } - } - } - - run() { - this.tcpServer.listen(this.port, () => { - Logger.log(`Node Media Rtmp Server started on port: ${this.port}`) - }) - - this.tcpServer.on("error", (e) => { - Logger.error(`Node Media Rtmp Server ${e}`) - }) - - this.tcpServer.on("close", () => { - Logger.log("Node Media Rtmp Server Close.") - }) - - if (this.tlsServer) { - this.tlsServer.listen(this.sslPort, () => { - Logger.log(`Node Media Rtmps Server started on port: ${this.sslPort}`) - }) - - this.tlsServer.on("error", (e) => { - Logger.error(`Node Media Rtmps Server ${e}`) - }) - - this.tlsServer.on("close", () => { - Logger.log("Node Media Rtmps Server Close.") - }) - } - } - - stop() { - this.tcpServer.close() - - if (this.tlsServer) { - this.tlsServer.close() - } - - context.sessions.forEach((session, id) => { - if (session instanceof NodeRtmpSession) - session.stop() - }) - } -} - -module.exports = NodeRtmpServer diff --git a/packages/streaming-server/src/internal-nms/servers/trans_server.js b/packages/streaming-server/src/internal-nms/servers/trans_server.js deleted file mode 100644 index 54bc0224..00000000 --- a/packages/streaming-server/src/internal-nms/servers/trans_server.js +++ /dev/null @@ -1,105 +0,0 @@ -const fs = require("fs") -const lodash = require("lodash") -const mkdirp = require("mkdirp") - -const Logger = require("../lib/logger") -const TransSession = require("../sessionsModels/trans_session") - -const { getFFmpegVersion, getFFmpegUrl } = require("../lib/utils") -const context = require("../ctx") - -class NodeTransServer { - constructor(config) { - this.config = config - this.transSessions = new Map() - } - - async run() { - try { - mkdirp.sync(this.config.mediaroot) - fs.accessSync(this.config.mediaroot, fs.constants.W_OK) - } catch (error) { - Logger.error(`Node Media Trans Server startup failed. MediaRoot:${this.config.mediaroot} cannot be written.`) - return - } - - try { - fs.accessSync(this.config.trans.ffmpeg, fs.constants.X_OK) - } catch (error) { - Logger.error(`Node Media Trans Server startup failed. ffmpeg:${this.config.trans.ffmpeg} cannot be executed.`) - return - } - - let version = await getFFmpegVersion(this.config.trans.ffmpeg) - - if (version === "" || parseInt(version.split(".")[0]) < 4) { - Logger.error("Node Media Trans Server startup failed. ffmpeg requires version 4.0.0 above") - Logger.error("Download the latest ffmpeg static program:", getFFmpegUrl()) - - return - } - - let i = this.config.trans.tasks.length - let apps = "" - - while (i--) { - apps += this.config.trans.tasks[i].app - apps += " " - } - - context.nodeEvent.on("postPublish", this.onPostPublish.bind(this)) - context.nodeEvent.on("donePublish", this.onDonePublish.bind(this)) - - Logger.log(`Node Media Trans Server started for apps: [ ${apps}] , MediaRoot: ${this.config.mediaroot}, ffmpeg version: ${version}`) - } - - async onPostPublish(id, streamPath, args) { - const fixedStreamingKey = streamPath.split("/").pop() - const userspace = await global.resolveUserspaceOfStreamingKey(fixedStreamingKey) - - if (!userspace) { - console.error("No userspace found for streaming key:", fixedStreamingKey) - return false - } - - let regRes = /\/(.*)\/(.*)/gi.exec(streamPath) - let [app, name] = lodash.slice(regRes, 1) - - let i = this.config.trans.tasks.length - - while (i--) { - let conf = { ...this.config.trans.tasks[i] } - - conf.ffmpeg = this.config.trans.ffmpeg - conf.mediaroot = this.config.mediaroot - conf.rtmpPort = this.config.rtmp.port - conf.streamPath = streamPath - conf.streamApp = app - conf.streamName = name - conf.fixedStreamName = userspace.username - conf.args = args - - if (app === conf.app) { - let session = new TransSession(conf) - - this.transSessions.set(id, session) - - session.on("end", () => { - this.transSessions.delete(id) - }) - - session.run() - } - } - } - - onDonePublish(id, streamPath, args) { - let session = this.transSessions.get(id) - - if (session) { - session.end() - } - } -} - -module.exports = NodeTransServer diff --git a/packages/streaming-server/src/internal-nms/sessionsModels/fission_session.js b/packages/streaming-server/src/internal-nms/sessionsModels/fission_session.js deleted file mode 100644 index 2e17720c..00000000 --- a/packages/streaming-server/src/internal-nms/sessionsModels/fission_session.js +++ /dev/null @@ -1,51 +0,0 @@ -const EventEmitter = require("events") -const { spawn } = require("child_process") - -const Logger = require("../lib/logger") - -class NodeFissionSession extends EventEmitter { - constructor(conf) { - super() - this.conf = conf - } - - run() { - let inPath = "rtmp://127.0.0.1:" + this.conf.rtmpPort + this.conf.streamPath - let argv = ["-i", inPath] - - for (let m of this.conf.model) { - let x264 = ["-c:v", "libx264", "-preset", "veryfast", "-tune", "zerolatency", "-maxrate", m.vb, "-bufsize", m.vb, "-g", parseInt(m.vf) * 2, "-r", m.vf, "-s", m.vs] - let aac = ["-c:a", "aac", "-b:a", m.ab] - let outPath = ["-f", "flv", "rtmp://127.0.0.1:" + this.conf.rtmpPort + "/" + this.conf.streamApp + "/" + this.conf.fixedStreamName + "_" + m.vs.split("x")[1]] - argv.splice(argv.length, 0, ...x264) - argv.splice(argv.length, 0, ...aac) - argv.splice(argv.length, 0, ...outPath) - } - - argv = argv.filter((n) => { return n }) - - this.ffmpeg_exec = spawn(this.conf.ffmpeg, argv) - this.ffmpeg_exec.on("error", (e) => { - Logger.ffdebug(e) - }) - - this.ffmpeg_exec.stdout.on("data", (data) => { - Logger.ffdebug(`FF输出:${data}`) - }) - - this.ffmpeg_exec.stderr.on("data", (data) => { - Logger.ffdebug(`FF输出:${data}`) - }) - - this.ffmpeg_exec.on("close", (code) => { - Logger.log("[Fission end] " + this.conf.streamPath) - this.emit("end") - }) - } - - end() { - this.ffmpeg_exec.kill() - } -} - -module.exports = NodeFissionSession \ No newline at end of file diff --git a/packages/streaming-server/src/internal-nms/sessionsModels/flv_session.js b/packages/streaming-server/src/internal-nms/sessionsModels/flv_session.js deleted file mode 100644 index aab86e8a..00000000 --- a/packages/streaming-server/src/internal-nms/sessionsModels/flv_session.js +++ /dev/null @@ -1,218 +0,0 @@ -const URL = require("url") - -const Logger = require("../lib/logger") -const Utils = require("../lib/utils") -const context = require("../ctx") - -const FlvPacket = { - create: (payload = null, type = 0, time = 0) => { - return { - header: { - length: payload ? payload.length : 0, - timestamp: time, - type: type - }, - payload: payload - } - } -} - -class FlvSession { - constructor(req, res) { - this.req = req - this.res = res - - this.id = Utils.generateNewSessionID() - this.ip = this.req.socket.remoteAddress - - this.playStreamPath = "" - this.playArgs = null - - this.isStarting = false - this.isPlaying = false - this.isIdling = false - - if (this.req.nmsConnectionType === "ws") { - this.res.cork = this.res._socket.cork.bind(this.res._socket) - this.res.uncork = this.res._socket.uncork.bind(this.res._socket) - this.res.on("close", this.onReqClose.bind(this)) - this.res.on("error", this.onReqError.bind(this)) - this.res.write = this.res.send - this.res.end = this.res.close - this.TAG = "websocket-flv" - } else { - this.res.cork = this.res.socket.cork.bind(this.res.socket) - this.res.uncork = this.res.socket.uncork.bind(this.res.socket) - - this.req.socket.on("close", this.onReqClose.bind(this)) - this.req.on("error", this.onReqError.bind(this)) - - this.TAG = "http-flv" - } - - this.numPlayCache = 0 - context.sessions.set(this.id, this) - } - - run() { - let method = this.req.method - - let urlInfo = URL.parse(this.req.url, true) - let streamPath = urlInfo.pathname.split(".")[0] - - this.connectCmdObj = { ip: this.ip, method, streamPath, query: urlInfo.query } - this.connectTime = new Date() - this.isStarting = true - - Logger.log(`[${this.TAG} connect] id=${this.id} ip=${this.ip} args=${JSON.stringify(urlInfo.query)}`) - - context.nodeEvent.emit("preConnect", this.id, this.connectCmdObj) - - if (!this.isStarting) { - this.stop() - return - } - - context.nodeEvent.emit("postConnect", this.id, this.connectCmdObj) - - if (method === "GET") { - this.playStreamPath = streamPath - this.playArgs = urlInfo.query - - this.onPlay() - } else { - this.stop() - } - } - - stop() { - if (this.isStarting) { - this.isStarting = false - - let publisherId = context.publishers.get(this.playStreamPath) - - if (publisherId != null) { - context.sessions.get(publisherId).players.delete(this.id) - context.nodeEvent.emit("donePlay", this.id, this.playStreamPath, this.playArgs) - } - - Logger.log(`[${this.TAG} play] Close stream. id=${this.id} streamPath=${this.playStreamPath}`) - Logger.log(`[${this.TAG} disconnect] id=${this.id}`) - - context.nodeEvent.emit("doneConnect", this.id, this.connectCmdObj) - - this.res.end() - - context.idlePlayers.delete(this.id) - context.sessions.delete(this.id) - } - } - - onReqClose() { - this.stop() - } - - onReqError(e) { - this.stop() - } - - reject() { - Logger.log(`[${this.TAG} reject] id=${this.id}`) - this.stop() - } - - onPlay() { - context.nodeEvent.emit("prePlay", this.id, this.playStreamPath, this.playArgs) - - if (!this.isStarting) { - return - } - - if (!context.publishers.has(this.playStreamPath)) { - Logger.log(`[${this.TAG} play] Stream not found. id=${this.id} streamPath=${this.playStreamPath} `) - - context.idlePlayers.add(this.id) - - this.isIdling = true - - return - } - - this.onStartPlay() - } - - onStartPlay() { - let publisherId = context.publishers.get(this.playStreamPath) - let publisher = context.sessions.get(publisherId) - let players = publisher.players - - players.add(this.id) - - //send FLV header - let FLVHeader = Buffer.from([0x46, 0x4c, 0x56, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00]) - - if (publisher.isFirstAudioReceived) { - FLVHeader[4] |= 0b00000100 - } - - if (publisher.isFirstVideoReceived) { - FLVHeader[4] |= 0b00000001 - } - - this.res.write(FLVHeader) - - //send Metadata - if (publisher.metaData != null) { - let packet = FlvPacket.create(publisher.metaData, 18) - let tag = FlvSession.createFlvTag(packet) - this.res.write(tag) - } - - //send aacSequenceHeader - if (publisher.audioCodec == 10) { - let packet = FlvPacket.create(publisher.aacSequenceHeader, 8) - let tag = FlvSession.createFlvTag(packet) - this.res.write(tag) - } - - //send avcSequenceHeader - if (publisher.videoCodec == 7 || publisher.videoCodec == 12) { - let packet = FlvPacket.create(publisher.avcSequenceHeader, 9) - let tag = FlvSession.createFlvTag(packet) - this.res.write(tag) - } - - //send gop cache - if (publisher.flvGopCacheQueue != null) { - for (let tag of publisher.flvGopCacheQueue) { - this.res.write(tag) - } - } - - this.isIdling = false - this.isPlaying = true - - Logger.log(`[${this.TAG} play] Join stream. id=${this.id} streamPath=${this.playStreamPath} `) - context.nodeEvent.emit("postPlay", this.id, this.playStreamPath, this.playArgs) - } - - static createFlvTag(packet) { - let PreviousTagSize = 11 + packet.header.length - let tagBuffer = Buffer.alloc(PreviousTagSize + 4) - - tagBuffer[0] = packet.header.type - tagBuffer.writeUIntBE(packet.header.length, 1, 3) - tagBuffer[4] = (packet.header.timestamp >> 16) & 0xff - tagBuffer[5] = (packet.header.timestamp >> 8) & 0xff - tagBuffer[6] = packet.header.timestamp & 0xff - tagBuffer[7] = (packet.header.timestamp >> 24) & 0xff - - tagBuffer.writeUIntBE(0, 8, 3) - tagBuffer.writeUInt32BE(PreviousTagSize, PreviousTagSize) - packet.payload.copy(tagBuffer, 11, 0, packet.header.length) - - return tagBuffer - } -} - -module.exports = FlvSession \ No newline at end of file diff --git a/packages/streaming-server/src/internal-nms/sessionsModels/relay_session.js b/packages/streaming-server/src/internal-nms/sessionsModels/relay_session.js deleted file mode 100644 index 2334cf05..00000000 --- a/packages/streaming-server/src/internal-nms/sessionsModels/relay_session.js +++ /dev/null @@ -1,60 +0,0 @@ - -const EventEmitter = require("events") -const { spawn } = require("child_process") - -const Logger = require("../lib/logger") -const NodeCoreUtils = require("../lib/utils") - -const RTSP_TRANSPORT = ["udp", "tcp", "udp_multicast", "http"] - -class NodeRelaySession extends EventEmitter { - constructor(conf) { - super() - this.conf = conf - this.id = NodeCoreUtils.generateNewSessionID() - this.TAG = "relay" - } - - run() { - let format = this.conf.ouPath.startsWith("rtsp://") ? "rtsp" : "flv" - let argv = ["-re", "-i", this.conf.inPath, "-c", "copy", "-f", format, this.conf.ouPath] - - if (this.conf.inPath[0] === "/" || this.conf.inPath[1] === ":") { - argv.unshift("-1") - argv.unshift("-stream_loop") - } - - if (this.conf.inPath.startsWith("rtsp://") && this.conf.rtsp_transport) { - if (RTSP_TRANSPORT.indexOf(this.conf.rtsp_transport) > -1) { - argv.unshift(this.conf.rtsp_transport) - argv.unshift("-rtsp_transport") - } - } - - Logger.log("[relay task] id=" + this.id, "cmd=ffmpeg", argv.join(" ")) - - this.ffmpeg_exec = spawn(this.conf.ffmpeg, argv) - this.ffmpeg_exec.on("error", (e) => { - Logger.ffdebug(e) - }) - - this.ffmpeg_exec.stdout.on("data", (data) => { - Logger.ffdebug(`FF输出:${data}`) - }) - - this.ffmpeg_exec.stderr.on("data", (data) => { - Logger.ffdebug(`FF输出:${data}`) - }) - - this.ffmpeg_exec.on("close", (code) => { - Logger.log("[relay end] id=" + this.id, "code=" + code) - this.emit("end", this.id) - }) - } - - end() { - this.ffmpeg_exec.kill() - } -} - -module.exports = NodeRelaySession diff --git a/packages/streaming-server/src/internal-nms/sessionsModels/rtmp_session.js b/packages/streaming-server/src/internal-nms/sessionsModels/rtmp_session.js deleted file mode 100644 index 8aa244c9..00000000 --- a/packages/streaming-server/src/internal-nms/sessionsModels/rtmp_session.js +++ /dev/null @@ -1,1306 +0,0 @@ -// -// Created by Mingliang Chen on 18/4/1. -// illuspas[a]gmail.com -// Copyright (c) 2018 Nodemedia. All rights reserved. -// - -const QueryString = require("querystring") - -const Logger = require("../lib/logger") -const NodeCoreUtils = require("../lib/utils") -const context = require("../ctx") - -const AV = require("../lib/av") -const { AUDIO_SOUND_RATE, AUDIO_CODEC_NAME, VIDEO_CODEC_NAME } = require("../lib/av") -const AMF = require("../lib/amf_rules") -const Handshake = require("../rtmp_handshake") -const NodeFlvSession = require("./flv_session") - -const N_CHUNK_STREAM = 8 -const RTMP_VERSION = 3 -const RTMP_HANDSHAKE_SIZE = 1536 -const RTMP_HANDSHAKE_UNINIT = 0 -const RTMP_HANDSHAKE_0 = 1 -const RTMP_HANDSHAKE_1 = 2 -const RTMP_HANDSHAKE_2 = 3 - -const RTMP_PARSE_INIT = 0 -const RTMP_PARSE_BASIC_HEADER = 1 -const RTMP_PARSE_MESSAGE_HEADER = 2 -const RTMP_PARSE_EXTENDED_TIMESTAMP = 3 -const RTMP_PARSE_PAYLOAD = 4 - -const MAX_CHUNK_HEADER = 18 - -const RTMP_CHUNK_TYPE_0 = 0 // 11-bytes: timestamp(3) + length(3) + stream type(1) + stream id(4) -const RTMP_CHUNK_TYPE_1 = 1 // 7-bytes: delta(3) + length(3) + stream type(1) -const RTMP_CHUNK_TYPE_2 = 2 // 3-bytes: delta(3) -const RTMP_CHUNK_TYPE_3 = 3 // 0-byte - -const RTMP_CHANNEL_PROTOCOL = 2 -const RTMP_CHANNEL_INVOKE = 3 -const RTMP_CHANNEL_AUDIO = 4 -const RTMP_CHANNEL_VIDEO = 5 -const RTMP_CHANNEL_DATA = 6 - -const rtmpHeaderSize = [11, 7, 3, 0] - -/* Protocol Control Messages */ -const RTMP_TYPE_SET_CHUNK_SIZE = 1 -const RTMP_TYPE_ABORT = 2 -const RTMP_TYPE_ACKNOWLEDGEMENT = 3 // bytes read report -const RTMP_TYPE_WINDOW_ACKNOWLEDGEMENT_SIZE = 5 // server bandwidth -const RTMP_TYPE_SET_PEER_BANDWIDTH = 6 // client bandwidth - -/* User Control Messages Event (4) */ -const RTMP_TYPE_EVENT = 4 - -const RTMP_TYPE_AUDIO = 8 -const RTMP_TYPE_VIDEO = 9 - -/* Data Message */ -const RTMP_TYPE_FLEX_STREAM = 15 // AMF3 -const RTMP_TYPE_DATA = 18 // AMF0 - -/* Shared Object Message */ -const RTMP_TYPE_FLEX_OBJECT = 16 // AMF3 -const RTMP_TYPE_SHARED_OBJECT = 19 // AMF0 - -/* Command Message */ -const RTMP_TYPE_FLEX_MESSAGE = 17 // AMF3 -const RTMP_TYPE_INVOKE = 20 // AMF0 - -/* Aggregate Message */ -const RTMP_TYPE_METADATA = 22 - -const RTMP_CHUNK_SIZE = 128 -const RTMP_PING_TIME = 60000 -const RTMP_PING_TIMEOUT = 30000 - -const STREAM_BEGIN = 0x00 -const STREAM_EOF = 0x01 -const STREAM_DRY = 0x02 -const STREAM_EMPTY = 0x1f -const STREAM_READY = 0x20 - -const RtmpPacket = { - create: (fmt = 0, cid = 0) => { - return { - header: { - fmt: fmt, - cid: cid, - timestamp: 0, - length: 0, - type: 0, - stream_id: 0 - }, - clock: 0, - payload: null, - capacity: 0, - bytes: 0 - } - } -} - -class NodeRtmpSession { - constructor(config, socket) { - this.config = config - this.socket = socket - this.res = socket - this.id = NodeCoreUtils.generateNewSessionID() - this.ip = socket.remoteAddress - this.TAG = "rtmp" - - this.handshakePayload = Buffer.alloc(RTMP_HANDSHAKE_SIZE) - this.handshakeState = RTMP_HANDSHAKE_UNINIT - this.handshakeBytes = 0 - - this.parserBuffer = Buffer.alloc(MAX_CHUNK_HEADER) - this.parserState = RTMP_PARSE_INIT - this.parserBytes = 0 - this.parserBasicBytes = 0 - this.parserPacket = null - this.inPackets = new Map() - - this.inChunkSize = RTMP_CHUNK_SIZE - this.outChunkSize = config.rtmp.chunk_size ? config.rtmp.chunk_size : RTMP_CHUNK_SIZE - this.pingTime = config.rtmp.ping ? config.rtmp.ping * 1000 : RTMP_PING_TIME - this.pingTimeout = config.rtmp.ping_timeout ? config.rtmp.ping_timeout * 1000 : RTMP_PING_TIMEOUT - this.pingInterval = null - - this.isLocal = this.ip === "127.0.0.1" || this.ip === "::1" || this.ip == "::ffff:127.0.0.1" - this.isStarting = false - this.isPublishing = false - this.isPlaying = false - this.isIdling = false - this.isPause = false - this.isReceiveAudio = true - this.isReceiveVideo = true - this.metaData = null - this.aacSequenceHeader = null - this.avcSequenceHeader = null - this.audioCodec = 0 - this.audioCodecName = "" - this.audioProfileName = "" - this.audioSamplerate = 0 - this.audioChannels = 1 - this.videoCodec = 0 - this.videoCodecName = "" - this.videoProfileName = "" - this.videoWidth = 0 - this.videoHeight = 0 - this.videoFps = 0 - this.videoCount = 0 - this.videoLevel = 0 - this.bitrate = 0 - - this.gopCacheEnable = config.rtmp.gop_cache - this.rtmpGopCacheQueue = null - this.flvGopCacheQueue = null - - this.ackSize = 0 - this.inAckSize = 0 - this.inLastAck = 0 - - this.appname = "" - this.streams = 0 - - this.playStreamId = 0 - this.playStreamPath = "" - this.playArgs = {} - - this.publishStreamId = 0 - this.publishStreamPath = "" - this.publishArgs = {} - - this.players = new Set() - this.numPlayCache = 0 - this.bitrateCache = {} - context.sessions.set(this.id, this) - } - - run() { - this.socket.on("data", this.onSocketData.bind(this)) - this.socket.on("close", this.onSocketClose.bind(this)) - this.socket.on("error", this.onSocketError.bind(this)) - this.socket.on("timeout", this.onSocketTimeout.bind(this)) - this.socket.setTimeout(this.pingTimeout) - this.isStarting = true - } - - stop() { - if (this.isStarting) { - this.isStarting = false - - if (this.playStreamId > 0) { - this.onDeleteStream({ streamId: this.playStreamId }) - } - - if (this.publishStreamId > 0) { - this.onDeleteStream({ streamId: this.publishStreamId }) - } - - if (this.pingInterval != null) { - clearInterval(this.pingInterval) - this.pingInterval = null - } - - Logger.log(`[rtmp disconnect] id=${this.id}`) - context.nodeEvent.emit("doneConnect", this.id, this.connectCmdObj) - - context.sessions.delete(this.id) - this.socket.destroy() - } - } - - reject() { - Logger.log(`[rtmp reject] id=${this.id}`) - this.stop() - } - - flush() { - if (this.numPlayCache > 0) { - this.res.uncork() - } - } - - onSocketClose() { - // Logger.log("onSocketClose") - this.stop() - } - - onSocketError(e) { - // Logger.log("onSocketError", e) - this.stop() - } - - onSocketTimeout() { - // Logger.log("onSocketTimeout") - this.stop() - } - - /** - * onSocketData - * @param {Buffer} data - * @returns - */ - onSocketData(data) { - let bytes = data.length - let p = 0 - let n = 0 - while (bytes > 0) { - switch (this.handshakeState) { - case RTMP_HANDSHAKE_UNINIT: - // Logger.log("RTMP_HANDSHAKE_UNINIT") - this.handshakeState = RTMP_HANDSHAKE_0 - this.handshakeBytes = 0 - bytes -= 1 - p += 1 - break - case RTMP_HANDSHAKE_0: - // Logger.log("RTMP_HANDSHAKE_0") - n = RTMP_HANDSHAKE_SIZE - this.handshakeBytes - n = n <= bytes ? n : bytes - data.copy(this.handshakePayload, this.handshakeBytes, p, p + n) - this.handshakeBytes += n - bytes -= n - p += n - if (this.handshakeBytes === RTMP_HANDSHAKE_SIZE) { - this.handshakeState = RTMP_HANDSHAKE_1 - this.handshakeBytes = 0 - let s0s1s2 = Handshake.generateS0S1S2(this.handshakePayload) - this.socket.write(s0s1s2) - } - break - case RTMP_HANDSHAKE_1: - // Logger.log("RTMP_HANDSHAKE_1") - n = RTMP_HANDSHAKE_SIZE - this.handshakeBytes - n = n <= bytes ? n : bytes - data.copy(this.handshakePayload, this.handshakeBytes, p, n) - this.handshakeBytes += n - bytes -= n - p += n - if (this.handshakeBytes === RTMP_HANDSHAKE_SIZE) { - this.handshakeState = RTMP_HANDSHAKE_2 - this.handshakeBytes = 0 - this.handshakePayload = null - } - break - case RTMP_HANDSHAKE_2: - default: - // Logger.log("RTMP_HANDSHAKE_2") - return this.rtmpChunkRead(data, p, bytes) - } - } - } - - rtmpChunkBasicHeaderCreate(fmt, cid) { - let out - if (cid >= 64 + 255) { - out = Buffer.alloc(3) - out[0] = (fmt << 6) | 1 - out[1] = (cid - 64) & 0xff - out[2] = ((cid - 64) >> 8) & 0xff - } else if (cid >= 64) { - out = Buffer.alloc(2) - out[0] = (fmt << 6) | 0 - out[1] = (cid - 64) & 0xff - } else { - out = Buffer.alloc(1) - out[0] = (fmt << 6) | cid - } - return out - } - - rtmpChunkMessageHeaderCreate(header) { - let out = Buffer.alloc(rtmpHeaderSize[header.fmt % 4]) - if (header.fmt <= RTMP_CHUNK_TYPE_2) { - out.writeUIntBE(header.timestamp >= 0xffffff ? 0xffffff : header.timestamp, 0, 3) - } - - if (header.fmt <= RTMP_CHUNK_TYPE_1) { - out.writeUIntBE(header.length, 3, 3) - out.writeUInt8(header.type, 6) - } - - if (header.fmt === RTMP_CHUNK_TYPE_0) { - out.writeUInt32LE(header.stream_id, 7) - } - return out - } - - /** - * rtmpChunksCreate - * @param {RtmpPacket} packet - * @returns - */ - rtmpChunksCreate(packet) { - let header = packet.header - let payload = packet.payload - let payloadSize = header.length - let chunkSize = this.outChunkSize - let chunksOffset = 0 - let payloadOffset = 0 - let chunkBasicHeader = this.rtmpChunkBasicHeaderCreate(header.fmt, header.cid) - let chunkBasicHeader3 = this.rtmpChunkBasicHeaderCreate(RTMP_CHUNK_TYPE_3, header.cid) - let chunkMessageHeader = this.rtmpChunkMessageHeaderCreate(header) - let useExtendedTimestamp = header.timestamp >= 0xffffff - let headerSize = chunkBasicHeader.length + chunkMessageHeader.length + (useExtendedTimestamp ? 4 : 0) - let n = headerSize + payloadSize + Math.floor(payloadSize / chunkSize) - - if (useExtendedTimestamp) { - n += Math.floor(payloadSize / chunkSize) * 4 - } - if (!(payloadSize % chunkSize)) { - n -= 1 - if (useExtendedTimestamp) { - //TODO CHECK - n -= 4 - } - } - - let chunks = Buffer.alloc(n) - chunkBasicHeader.copy(chunks, chunksOffset) - chunksOffset += chunkBasicHeader.length - chunkMessageHeader.copy(chunks, chunksOffset) - chunksOffset += chunkMessageHeader.length - if (useExtendedTimestamp) { - chunks.writeUInt32BE(header.timestamp, chunksOffset) - chunksOffset += 4 - } - while (payloadSize > 0) { - if (payloadSize > chunkSize) { - payload.copy(chunks, chunksOffset, payloadOffset, payloadOffset + chunkSize) - payloadSize -= chunkSize - chunksOffset += chunkSize - payloadOffset += chunkSize - chunkBasicHeader3.copy(chunks, chunksOffset) - chunksOffset += chunkBasicHeader3.length - if (useExtendedTimestamp) { - chunks.writeUInt32BE(header.timestamp, chunksOffset) - chunksOffset += 4 - } - } else { - payload.copy(chunks, chunksOffset, payloadOffset, payloadOffset + payloadSize) - payloadSize -= payloadSize - chunksOffset += payloadSize - payloadOffset += payloadSize - } - } - return chunks - } - - /** - * rtmpChunkRead - * @param {Buffer} data - * @param {Number} p - * @param {Number} bytes - */ - rtmpChunkRead(data, p, bytes) { - // Logger.log("rtmpChunkRead", p, bytes) - let size = 0 - let offset = 0 - let extended_timestamp = 0 - - while (offset < bytes) { - switch (this.parserState) { - case RTMP_PARSE_INIT: - this.parserBytes = 1 - this.parserBuffer[0] = data[p + offset++] - if (0 === (this.parserBuffer[0] & 0x3f)) { - this.parserBasicBytes = 2 - } else if (1 === (this.parserBuffer[0] & 0x3f)) { - this.parserBasicBytes = 3 - } else { - this.parserBasicBytes = 1 - } - this.parserState = RTMP_PARSE_BASIC_HEADER - break - case RTMP_PARSE_BASIC_HEADER: - while (this.parserBytes < this.parserBasicBytes && offset < bytes) { - this.parserBuffer[this.parserBytes++] = data[p + offset++] - } - if (this.parserBytes >= this.parserBasicBytes) { - this.parserState = RTMP_PARSE_MESSAGE_HEADER - } - break - case RTMP_PARSE_MESSAGE_HEADER: - size = rtmpHeaderSize[this.parserBuffer[0] >> 6] + this.parserBasicBytes - while (this.parserBytes < size && offset < bytes) { - this.parserBuffer[this.parserBytes++] = data[p + offset++] - } - if (this.parserBytes >= size) { - this.rtmpPacketParse() - this.parserState = RTMP_PARSE_EXTENDED_TIMESTAMP - } - break - case RTMP_PARSE_EXTENDED_TIMESTAMP: - size = rtmpHeaderSize[this.parserPacket.header.fmt] + this.parserBasicBytes - if (this.parserPacket.header.timestamp === 0xffffff) size += 4 - while (this.parserBytes < size && offset < bytes) { - this.parserBuffer[this.parserBytes++] = data[p + offset++] - } - if (this.parserBytes >= size) { - if (this.parserPacket.header.timestamp === 0xffffff) { - extended_timestamp = this.parserBuffer.readUInt32BE(rtmpHeaderSize[this.parserPacket.header.fmt] + this.parserBasicBytes) - } else { - extended_timestamp = this.parserPacket.header.timestamp - } - - if (this.parserPacket.bytes === 0) { - if (RTMP_CHUNK_TYPE_0 === this.parserPacket.header.fmt) { - this.parserPacket.clock = extended_timestamp - } else { - this.parserPacket.clock += extended_timestamp - } - this.rtmpPacketAlloc() - } - this.parserState = RTMP_PARSE_PAYLOAD - } - break - case RTMP_PARSE_PAYLOAD: - size = Math.min(this.inChunkSize - (this.parserPacket.bytes % this.inChunkSize), this.parserPacket.header.length - this.parserPacket.bytes) - size = Math.min(size, bytes - offset) - if (size > 0) { - data.copy(this.parserPacket.payload, this.parserPacket.bytes, p + offset, p + offset + size) - } - this.parserPacket.bytes += size - offset += size - - if (this.parserPacket.bytes >= this.parserPacket.header.length) { - this.parserState = RTMP_PARSE_INIT - this.parserPacket.bytes = 0 - if (this.parserPacket.clock > 0xffffffff) { - break - } - this.rtmpHandler() - } else if (0 === this.parserPacket.bytes % this.inChunkSize) { - this.parserState = RTMP_PARSE_INIT - } - break - } - } - - this.inAckSize += data.length - if (this.inAckSize >= 0xf0000000) { - this.inAckSize = 0 - this.inLastAck = 0 - } - if (this.ackSize > 0 && this.inAckSize - this.inLastAck >= this.ackSize) { - this.inLastAck = this.inAckSize - this.sendACK(this.inAckSize) - } - - this.bitrateCache.bytes += bytes - let current_time = Date.now() - let diff = current_time - this.bitrateCache.last_update - if (diff >= this.bitrateCache.intervalMs) { - this.bitrate = Math.round(this.bitrateCache.bytes * 8 / diff) - this.bitrateCache.bytes = 0 - this.bitrateCache.last_update = current_time - } - } - - rtmpPacketParse() { - let fmt = this.parserBuffer[0] >> 6 - let cid = 0 - if (this.parserBasicBytes === 2) { - cid = 64 + this.parserBuffer[1] - } else if (this.parserBasicBytes === 3) { - cid = (64 + this.parserBuffer[1] + this.parserBuffer[2]) << 8 - } else { - cid = this.parserBuffer[0] & 0x3f - } - let hasp = this.inPackets.has(cid) - if (!hasp) { - this.parserPacket = RtmpPacket.create(fmt, cid) - this.inPackets.set(cid, this.parserPacket) - } else { - this.parserPacket = this.inPackets.get(cid) - } - this.parserPacket.header.fmt = fmt - this.parserPacket.header.cid = cid - this.rtmpChunkMessageHeaderRead() - - if (this.parserPacket.header.type > RTMP_TYPE_METADATA) { - Logger.error("rtmp packet parse error.", this.parserPacket) - this.stop() - } - } - - rtmpChunkMessageHeaderRead() { - let offset = this.parserBasicBytes - - // timestamp / delta - if (this.parserPacket.header.fmt <= RTMP_CHUNK_TYPE_2) { - this.parserPacket.header.timestamp = this.parserBuffer.readUIntBE(offset, 3) - offset += 3 - } - - // message length + type - if (this.parserPacket.header.fmt <= RTMP_CHUNK_TYPE_1) { - this.parserPacket.header.length = this.parserBuffer.readUIntBE(offset, 3) - this.parserPacket.header.type = this.parserBuffer[offset + 3] - offset += 4 - } - - if (this.parserPacket.header.fmt === RTMP_CHUNK_TYPE_0) { - this.parserPacket.header.stream_id = this.parserBuffer.readUInt32LE(offset) - offset += 4 - } - return offset - } - - rtmpPacketAlloc() { - if (this.parserPacket.capacity < this.parserPacket.header.length) { - this.parserPacket.payload = Buffer.alloc(this.parserPacket.header.length + 1024) - this.parserPacket.capacity = this.parserPacket.header.length + 1024 - } - } - - rtmpHandler() { - switch (this.parserPacket.header.type) { - case RTMP_TYPE_SET_CHUNK_SIZE: - case RTMP_TYPE_ABORT: - case RTMP_TYPE_ACKNOWLEDGEMENT: - case RTMP_TYPE_WINDOW_ACKNOWLEDGEMENT_SIZE: - case RTMP_TYPE_SET_PEER_BANDWIDTH: - return 0 === this.rtmpControlHandler() ? -1 : 0 - case RTMP_TYPE_EVENT: - return 0 === this.rtmpEventHandler() ? -1 : 0 - case RTMP_TYPE_AUDIO: - return this.rtmpAudioHandler() - case RTMP_TYPE_VIDEO: - return this.rtmpVideoHandler() - case RTMP_TYPE_FLEX_MESSAGE: - case RTMP_TYPE_INVOKE: - return this.rtmpInvokeHandler() - case RTMP_TYPE_FLEX_STREAM: // AMF3 - case RTMP_TYPE_DATA: // AMF0 - return this.rtmpDataHandler() - } - } - - rtmpControlHandler() { - let payload = this.parserPacket.payload - switch (this.parserPacket.header.type) { - case RTMP_TYPE_SET_CHUNK_SIZE: - this.inChunkSize = payload.readUInt32BE() - // Logger.debug("set inChunkSize", this.inChunkSize) - break - case RTMP_TYPE_ABORT: - break - case RTMP_TYPE_ACKNOWLEDGEMENT: - break - case RTMP_TYPE_WINDOW_ACKNOWLEDGEMENT_SIZE: - this.ackSize = payload.readUInt32BE() - // Logger.debug("set ack Size", this.ackSize) - break - case RTMP_TYPE_SET_PEER_BANDWIDTH: - break - } - } - - rtmpEventHandler() { } - - rtmpAudioHandler() { - let payload = this.parserPacket.payload.slice(0, this.parserPacket.header.length) - let sound_format = (payload[0] >> 4) & 0x0f - let sound_type = payload[0] & 0x01 - let sound_size = (payload[0] >> 1) & 0x01 - let sound_rate = (payload[0] >> 2) & 0x03 - - if (this.audioCodec == 0) { - this.audioCodec = sound_format - this.audioCodecName = AUDIO_CODEC_NAME[sound_format] - this.audioSamplerate = AUDIO_SOUND_RATE[sound_rate] - this.audioChannels = ++sound_type - - if (sound_format == 4) { - this.audioSamplerate = 16000 - } else if (sound_format == 5) { - this.audioSamplerate = 8000 - } else if (sound_format == 11) { - this.audioSamplerate = 16000 - } else if (sound_format == 14) { - this.audioSamplerate = 8000 - } - - if (sound_format != 10 && sound_format != 13) { - Logger.log( - `[rtmp publish] Handle audio. id=${this.id} streamPath=${this.publishStreamPath - } sound_format=${sound_format} sound_type=${sound_type} sound_size=${sound_size} sound_rate=${sound_rate} codec_name=${this.audioCodecName} ${this.audioSamplerate} ${this.audioChannels - }ch` - ) - } - } - - if ((sound_format == 10 || sound_format == 13) && payload[1] == 0) { - //cache aac sequence header - this.isFirstAudioReceived = true - this.aacSequenceHeader = Buffer.alloc(payload.length) - payload.copy(this.aacSequenceHeader) - if (sound_format == 10) { - let info = AV.readAACSpecificConfig(this.aacSequenceHeader) - this.audioProfileName = AV.getAACProfileName(info) - this.audioSamplerate = info.sample_rate - this.audioChannels = info.channels - } else { - this.audioSamplerate = 48000 - this.audioChannels = payload[11] - } - - Logger.log( - `[rtmp publish] Handle audio. id=${this.id} streamPath=${this.publishStreamPath - } sound_format=${sound_format} sound_type=${sound_type} sound_size=${sound_size} sound_rate=${sound_rate} codec_name=${this.audioCodecName} ${this.audioSamplerate} ${this.audioChannels - }ch` - ) - } - - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_AUDIO - packet.header.type = RTMP_TYPE_AUDIO - packet.payload = payload - packet.header.length = packet.payload.length - packet.header.timestamp = this.parserPacket.clock - let rtmpChunks = this.rtmpChunksCreate(packet) - let flvTag = NodeFlvSession.createFlvTag(packet) - - //cache gop - if (this.rtmpGopCacheQueue != null) { - if (this.aacSequenceHeader != null && payload[1] === 0) { - //skip aac sequence header - } else { - this.rtmpGopCacheQueue.add(rtmpChunks) - this.flvGopCacheQueue.add(flvTag) - } - } - - for (let playerId of this.players) { - let playerSession = context.sessions.get(playerId) - - if (playerSession.numPlayCache === 0) { - playerSession.res.cork() - } - - if (playerSession instanceof NodeRtmpSession) { - if (playerSession.isStarting && playerSession.isPlaying && !playerSession.isPause && playerSession.isReceiveAudio) { - rtmpChunks.writeUInt32LE(playerSession.playStreamId, 8) - playerSession.res.write(rtmpChunks) - } - } else if (playerSession instanceof NodeFlvSession) { - playerSession.res.write(flvTag, null, e => { - //websocket will throw a error if not set the cb when closed - }) - } - - playerSession.numPlayCache++ - - if (playerSession.numPlayCache === 10) { - process.nextTick(() => playerSession.res.uncork()) - playerSession.numPlayCache = 0 - } - } - } - - rtmpVideoHandler() { - let payload = this.parserPacket.payload.slice(0, this.parserPacket.header.length) - let frame_type = (payload[0] >> 4) & 0x0f - let codec_id = payload[0] & 0x0f - - if (this.videoFps === 0) { - if (this.videoCount++ === 0) { - setTimeout(() => { - this.videoFps = Math.ceil(this.videoCount / 5) - }, 5000) - } - } - - if (codec_id == 7 || codec_id == 12) { - //cache avc sequence header - if (frame_type == 1 && payload[1] == 0) { - this.avcSequenceHeader = Buffer.alloc(payload.length) - payload.copy(this.avcSequenceHeader) - let info = AV.readAVCSpecificConfig(this.avcSequenceHeader) - this.videoWidth = info.width - this.videoHeight = info.height - this.videoProfileName = AV.getAVCProfileName(info) - this.videoLevel = info.level - this.rtmpGopCacheQueue = this.gopCacheEnable ? new Set() : null - this.flvGopCacheQueue = this.gopCacheEnable ? new Set() : null - //Logger.log(`[rtmp publish] avc sequence header`,this.avcSequenceHeader) - } - } - - if (this.videoCodec == 0) { - this.videoCodec = codec_id - this.videoCodecName = VIDEO_CODEC_NAME[codec_id] - Logger.log( - `[rtmp publish] Handle video. id=${this.id} streamPath=${this.publishStreamPath} frame_type=${frame_type} codec_id=${codec_id} codec_name=${this.videoCodecName} ${this.videoWidth - }x${this.videoHeight}` - ) - } - - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_VIDEO - packet.header.type = RTMP_TYPE_VIDEO - packet.payload = payload - packet.header.length = packet.payload.length - packet.header.timestamp = this.parserPacket.clock - let rtmpChunks = this.rtmpChunksCreate(packet) - let flvTag = NodeFlvSession.createFlvTag(packet) - - //cache gop - if ((codec_id == 7 || codec_id == 12) && this.rtmpGopCacheQueue != null) { - if (frame_type == 1 && payload[1] == 1) { - this.rtmpGopCacheQueue.clear() - this.flvGopCacheQueue.clear() - } - if (frame_type == 1 && payload[1] == 0) { - //skip avc sequence header - } else { - this.rtmpGopCacheQueue.add(rtmpChunks) - this.flvGopCacheQueue.add(flvTag) - } - } - - // Logger.log(rtmpChunks) - for (let playerId of this.players) { - let playerSession = context.sessions.get(playerId) - - if (playerSession.numPlayCache === 0) { - playerSession.res.cork() - } - - if (playerSession instanceof NodeRtmpSession) { - if (playerSession.isStarting && playerSession.isPlaying && !playerSession.isPause && playerSession.isReceiveVideo) { - rtmpChunks.writeUInt32LE(playerSession.playStreamId, 8) - playerSession.res.write(rtmpChunks) - } - } else if (playerSession instanceof NodeFlvSession) { - playerSession.res.write(flvTag, null, e => { - //websocket will throw a error if not set the cb when closed - }) - } - - playerSession.numPlayCache++ - - if (playerSession.numPlayCache === 10) { - process.nextTick(() => playerSession.res.uncork()) - playerSession.numPlayCache = 0 - } - } - } - - rtmpDataHandler() { - let offset = this.parserPacket.header.type === RTMP_TYPE_FLEX_STREAM ? 1 : 0 - let payload = this.parserPacket.payload.slice(offset, this.parserPacket.header.length) - let dataMessage = AMF.decodeAmf0Data(payload) - switch (dataMessage.cmd) { - case "@setDataFrame": - if (dataMessage.dataObj) { - this.audioSamplerate = dataMessage.dataObj.audiosamplerate - this.audioChannels = dataMessage.dataObj.stereo ? 2 : 1 - this.videoWidth = dataMessage.dataObj.width - this.videoHeight = dataMessage.dataObj.height - this.videoFps = dataMessage.dataObj.framerate - } - - let opt = { - cmd: "onMetaData", - dataObj: dataMessage.dataObj - } - this.metaData = AMF.encodeAmf0Data(opt) - - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_DATA - packet.header.type = RTMP_TYPE_DATA - packet.payload = this.metaData - packet.header.length = packet.payload.length - let rtmpChunks = this.rtmpChunksCreate(packet) - let flvTag = NodeFlvSession.createFlvTag(packet) - - for (let playerId of this.players) { - let playerSession = context.sessions.get(playerId) - if (playerSession instanceof NodeRtmpSession) { - if (playerSession.isStarting && playerSession.isPlaying && !playerSession.isPause) { - rtmpChunks.writeUInt32LE(playerSession.playStreamId, 8) - playerSession.socket.write(rtmpChunks) - } - } else if (playerSession instanceof NodeFlvSession) { - playerSession.res.write(flvTag, null, e => { - //websocket will throw a error if not set the cb when closed - }) - } - } - break - } - } - - rtmpInvokeHandler() { - let offset = this.parserPacket.header.type === RTMP_TYPE_FLEX_MESSAGE ? 1 : 0 - let payload = this.parserPacket.payload.slice(offset, this.parserPacket.header.length) - let invokeMessage = AMF.decodeAmf0Cmd(payload) - // Logger.log(invokeMessage) - switch (invokeMessage.cmd) { - case "connect": - this.onConnect(invokeMessage) - break - case "releaseStream": - break - case "FCPublish": - break - case "createStream": - this.onCreateStream(invokeMessage) - break - case "publish": - this.onPublish(invokeMessage) - break - case "play": - this.onPlay(invokeMessage) - break - case "pause": - this.onPause(invokeMessage) - break - case "FCUnpublish": - break - case "deleteStream": - this.onDeleteStream(invokeMessage) - break - case "closeStream": - this.onCloseStream() - break - case "receiveAudio": - this.onReceiveAudio(invokeMessage) - break - case "receiveVideo": - this.onReceiveVideo(invokeMessage) - break - } - } - - sendACK(size) { - let rtmpBuffer = Buffer.from("02000000000004030000000000000000", "hex") - rtmpBuffer.writeUInt32BE(size, 12) - this.socket.write(rtmpBuffer) - } - - sendWindowACK(size) { - let rtmpBuffer = Buffer.from("02000000000004050000000000000000", "hex") - rtmpBuffer.writeUInt32BE(size, 12) - this.socket.write(rtmpBuffer) - } - - setPeerBandwidth(size, type) { - let rtmpBuffer = Buffer.from("0200000000000506000000000000000000", "hex") - rtmpBuffer.writeUInt32BE(size, 12) - rtmpBuffer[16] = type - this.socket.write(rtmpBuffer) - } - - setChunkSize(size) { - let rtmpBuffer = Buffer.from("02000000000004010000000000000000", "hex") - rtmpBuffer.writeUInt32BE(size, 12) - this.socket.write(rtmpBuffer) - } - - sendStreamStatus(st, id) { - let rtmpBuffer = Buffer.from("020000000000060400000000000000000000", "hex") - rtmpBuffer.writeUInt16BE(st, 12) - rtmpBuffer.writeUInt32BE(id, 14) - this.socket.write(rtmpBuffer) - } - - sendInvokeMessage(sid, opt) { - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_INVOKE - packet.header.type = RTMP_TYPE_INVOKE - packet.header.stream_id = sid - packet.payload = AMF.encodeAmf0Cmd(opt) - packet.header.length = packet.payload.length - let chunks = this.rtmpChunksCreate(packet) - this.socket.write(chunks) - } - - sendDataMessage(opt, sid) { - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_DATA - packet.header.type = RTMP_TYPE_DATA - packet.payload = AMF.encodeAmf0Data(opt) - packet.header.length = packet.payload.length - packet.header.stream_id = sid - let chunks = this.rtmpChunksCreate(packet) - this.socket.write(chunks) - } - - sendStatusMessage(sid, level, code, description) { - let opt = { - cmd: "onStatus", - transId: 0, - cmdObj: null, - info: { - level: level, - code: code, - description: description - } - } - this.sendInvokeMessage(sid, opt) - } - - sendRtmpSampleAccess(sid) { - let opt = { - cmd: "|RtmpSampleAccess", - bool1: false, - bool2: false - } - this.sendDataMessage(opt, sid) - } - - sendPingRequest() { - let currentTimestamp = Date.now() - this.startTimestamp - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_PROTOCOL - packet.header.type = RTMP_TYPE_EVENT - packet.header.timestamp = currentTimestamp - packet.payload = Buffer.from([0, 6, (currentTimestamp >> 24) & 0xff, (currentTimestamp >> 16) & 0xff, (currentTimestamp >> 8) & 0xff, currentTimestamp & 0xff]) - packet.header.length = packet.payload.length - let chunks = this.rtmpChunksCreate(packet) - this.socket.write(chunks) - } - - respondConnect(tid) { - let opt = { - cmd: "_result", - transId: tid, - cmdObj: { - fmsVer: "FMS/3,0,1,123", - capabilities: 31 - }, - info: { - level: "status", - code: "NetConnection.Connect.Success", - description: "Connection succeeded.", - objectEncoding: this.objectEncoding - } - } - this.sendInvokeMessage(0, opt) - } - - respondCreateStream(tid) { - this.streams++ - let opt = { - cmd: "_result", - transId: tid, - cmdObj: null, - info: this.streams - } - this.sendInvokeMessage(0, opt) - } - - respondPlay() { - this.sendStreamStatus(STREAM_BEGIN, this.playStreamId) - this.sendStatusMessage(this.playStreamId, "status", "NetStream.Play.Reset", "Playing and resetting stream.") - this.sendStatusMessage(this.playStreamId, "status", "NetStream.Play.Start", "Started playing stream.") - this.sendRtmpSampleAccess() - } - - onConnect(invokeMessage) { - invokeMessage.cmdObj.app = invokeMessage.cmdObj.app.replace("/", "") //fix jwplayer - context.nodeEvent.emit("preConnect", this.id, invokeMessage.cmdObj) - if (!this.isStarting) { - return - } - this.connectCmdObj = invokeMessage.cmdObj - this.appname = invokeMessage.cmdObj.app - this.objectEncoding = invokeMessage.cmdObj.objectEncoding != null ? invokeMessage.cmdObj.objectEncoding : 0 - this.connectTime = new Date() - this.startTimestamp = Date.now() - this.pingInterval = setInterval(() => { - this.sendPingRequest() - }, this.pingTime) - this.sendWindowACK(5000000) - this.setPeerBandwidth(5000000, 2) - this.setChunkSize(this.outChunkSize) - this.respondConnect(invokeMessage.transId) - this.bitrateCache = { - intervalMs: 1000, - last_update: this.startTimestamp, - bytes: 0, - } - Logger.log(`[rtmp connect] id=${this.id} ip=${this.ip} app=${this.appname} args=${JSON.stringify(invokeMessage.cmdObj)}`) - context.nodeEvent.emit("postConnect", this.id, invokeMessage.cmdObj) - } - - onCreateStream(invokeMessage) { - this.respondCreateStream(invokeMessage.transId) - } - - onPublish(invokeMessage) { - if (typeof invokeMessage.streamName !== "string") { - return - } - this.publishStreamPath = "/" + this.appname + "/" + invokeMessage.streamName.split("?")[0] - this.publishArgs = QueryString.parse(invokeMessage.streamName.split("?")[1]) - this.publishStreamId = this.parserPacket.header.stream_id - context.nodeEvent.emit("prePublish", this.id, this.publishStreamPath, this.publishArgs) - if (!this.isStarting) { - return - } - - if (this.config.auth && this.config.auth.publish && !this.isLocal) { - let results = NodeCoreUtils.verifyAuth(this.publishArgs.sign, this.publishStreamPath, this.config.auth.secret) - if (!results) { - Logger.log(`[rtmp publish] Unauthorized. id=${this.id} streamPath=${this.publishStreamPath} streamId=${this.publishStreamId} sign=${this.publishArgs.sign} `) - this.sendStatusMessage(this.publishStreamId, "error", "NetStream.publish.Unauthorized", "Authorization required.") - return - } - } - - if (context.publishers.has(this.publishStreamPath)) { - this.reject() - Logger.log(`[rtmp publish] Already has a stream. id=${this.id} streamPath=${this.publishStreamPath} streamId=${this.publishStreamId}`) - this.sendStatusMessage(this.publishStreamId, "error", "NetStream.Publish.BadName", "Stream already publishing") - } else if (this.isPublishing) { - Logger.log(`[rtmp publish] NetConnection is publishing. id=${this.id} streamPath=${this.publishStreamPath} streamId=${this.publishStreamId}`) - this.sendStatusMessage(this.publishStreamId, "error", "NetStream.Publish.BadConnection", "Connection already publishing") - } else { - Logger.log(`[rtmp publish] New stream. id=${this.id} streamPath=${this.publishStreamPath} streamId=${this.publishStreamId}`) - context.publishers.set(this.publishStreamPath, this.id) - this.isPublishing = true - - this.sendStatusMessage(this.publishStreamId, "status", "NetStream.Publish.Start", `${this.publishStreamPath} is now published.`) - for (let idlePlayerId of context.idlePlayers) { - let idlePlayer = context.sessions.get(idlePlayerId) - if (idlePlayer && idlePlayer.playStreamPath === this.publishStreamPath) { - idlePlayer.onStartPlay() - context.idlePlayers.delete(idlePlayerId) - } - } - context.nodeEvent.emit("postPublish", this.id, this.publishStreamPath, this.publishArgs) - } - } - - onPlay(invokeMessage) { - if (typeof invokeMessage.streamName !== "string") { - return - } - this.playStreamPath = "/" + this.appname + "/" + invokeMessage.streamName.split("?")[0] - this.playArgs = QueryString.parse(invokeMessage.streamName.split("?")[1]) - this.playStreamId = this.parserPacket.header.stream_id - context.nodeEvent.emit("prePlay", this.id, this.playStreamPath, this.playArgs) - - if (!this.isStarting) { - return - } - - if (this.config.auth && this.config.auth.play && !this.isLocal) { - let results = NodeCoreUtils.verifyAuth(this.playArgs.sign, this.playStreamPath, this.config.auth.secret) - if (!results) { - Logger.log(`[rtmp play] Unauthorized. id=${this.id} streamPath=${this.playStreamPath} streamId=${this.playStreamId} sign=${this.playArgs.sign}`) - this.sendStatusMessage(this.playStreamId, "error", "NetStream.play.Unauthorized", "Authorization required.") - return - } - } - - if (this.isPlaying) { - Logger.log(`[rtmp play] NetConnection is playing. id=${this.id} streamPath=${this.playStreamPath} streamId=${this.playStreamId} `) - this.sendStatusMessage(this.playStreamId, "error", "NetStream.Play.BadConnection", "Connection already playing") - } else { - this.respondPlay() - } - - if (context.publishers.has(this.playStreamPath)) { - this.onStartPlay() - } else { - Logger.log(`[rtmp play] Stream not found. id=${this.id} streamPath=${this.playStreamPath} streamId=${this.playStreamId}`) - this.isIdling = true - context.idlePlayers.add(this.id) - } - } - - onStartPlay() { - let publisherId = context.publishers.get(this.playStreamPath) - let publisher = context.sessions.get(publisherId) - let players = publisher.players - players.add(this.id) - - if (publisher.metaData != null) { - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_DATA - packet.header.type = RTMP_TYPE_DATA - packet.payload = publisher.metaData - packet.header.length = packet.payload.length - packet.header.stream_id = this.playStreamId - let chunks = this.rtmpChunksCreate(packet) - this.socket.write(chunks) - } - - if (publisher.audioCodec === 10 || publisher.audioCodec === 13) { - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_AUDIO - packet.header.type = RTMP_TYPE_AUDIO - packet.payload = publisher.aacSequenceHeader - packet.header.length = packet.payload.length - packet.header.stream_id = this.playStreamId - let chunks = this.rtmpChunksCreate(packet) - this.socket.write(chunks) - } - - if (publisher.videoCodec === 7 || publisher.videoCodec === 12) { - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_VIDEO - packet.header.type = RTMP_TYPE_VIDEO - packet.payload = publisher.avcSequenceHeader - packet.header.length = packet.payload.length - packet.header.stream_id = this.playStreamId - let chunks = this.rtmpChunksCreate(packet) - this.socket.write(chunks) - } - - if (publisher.rtmpGopCacheQueue != null) { - for (let chunks of publisher.rtmpGopCacheQueue) { - chunks.writeUInt32LE(this.playStreamId, 8) - this.socket.write(chunks) - } - } - - this.isIdling = false - this.isPlaying = true - context.nodeEvent.emit("postPlay", this.id, this.playStreamPath, this.playArgs) - Logger.log(`[rtmp play] Join stream. id=${this.id} streamPath=${this.playStreamPath} streamId=${this.playStreamId} `) - } - - onPause(invokeMessage) { - this.isPause = invokeMessage.pause - let c = this.isPause ? "NetStream.Pause.Notify" : "NetStream.Unpause.Notify" - let d = this.isPause ? "Paused live" : "Unpaused live" - Logger.log(`[rtmp play] ${d} stream. id=${this.id} streamPath=${this.playStreamPath} streamId=${this.playStreamId} `) - if (!this.isPause) { - this.sendStreamStatus(STREAM_BEGIN, this.playStreamId) - if (context.publishers.has(this.playStreamPath)) { - //fix ckplayer - let publisherId = context.publishers.get(this.playStreamPath) - let publisher = context.sessions.get(publisherId) - if (publisher.audioCodec === 10 || publisher.audioCodec === 13) { - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_AUDIO - packet.header.type = RTMP_TYPE_AUDIO - packet.payload = publisher.aacSequenceHeader - packet.header.length = packet.payload.length - packet.header.stream_id = this.playStreamId - packet.header.timestamp = publisher.parserPacket.clock // ?? 0 or clock - let chunks = this.rtmpChunksCreate(packet) - this.socket.write(chunks) - } - if (publisher.videoCodec === 7 || publisher.videoCodec === 12) { - let packet = RtmpPacket.create() - packet.header.fmt = RTMP_CHUNK_TYPE_0 - packet.header.cid = RTMP_CHANNEL_VIDEO - packet.header.type = RTMP_TYPE_VIDEO - packet.payload = publisher.avcSequenceHeader - packet.header.length = packet.payload.length - packet.header.stream_id = this.playStreamId - packet.header.timestamp = publisher.parserPacket.clock // ?? 0 or clock - let chunks = this.rtmpChunksCreate(packet) - this.socket.write(chunks) - } - } - } else { - this.sendStreamStatus(STREAM_EOF, this.playStreamId) - } - this.sendStatusMessage(this.playStreamId, c, d) - } - - onReceiveAudio(invokeMessage) { - this.isReceiveAudio = invokeMessage.bool - Logger.log(`[rtmp play] receiveAudio=${this.isReceiveAudio} id=${this.id} `) - } - - onReceiveVideo(invokeMessage) { - this.isReceiveVideo = invokeMessage.bool - Logger.log(`[rtmp play] receiveVideo=${this.isReceiveVideo} id=${this.id} `) - } - - onCloseStream() { - //red5-publisher - let closeStream = { streamId: this.parserPacket.header.stream_id } - this.onDeleteStream(closeStream) - } - - onDeleteStream(invokeMessage) { - if (invokeMessage.streamId == this.playStreamId) { - if (this.isIdling) { - context.idlePlayers.delete(this.id) - this.isIdling = false - } else { - let publisherId = context.publishers.get(this.playStreamPath) - if (publisherId != null) { - context.sessions.get(publisherId).players.delete(this.id) - } - context.nodeEvent.emit("donePlay", this.id, this.playStreamPath, this.playArgs) - this.isPlaying = false - } - Logger.log(`[rtmp play] Close stream. id=${this.id} streamPath=${this.playStreamPath} streamId=${this.playStreamId}`) - if (this.isStarting) { - this.sendStatusMessage(this.playStreamId, "status", "NetStream.Play.Stop", "Stopped playing stream.") - } - this.playStreamId = 0 - this.playStreamPath = "" - } - - if (invokeMessage.streamId == this.publishStreamId) { - if (this.isPublishing) { - Logger.log(`[rtmp publish] Close stream. id=${this.id} streamPath=${this.publishStreamPath} streamId=${this.publishStreamId}`) - context.nodeEvent.emit("donePublish", this.id, this.publishStreamPath, this.publishArgs) - if (this.isStarting) { - this.sendStatusMessage(this.publishStreamId, "status", "NetStream.Unpublish.Success", `${this.publishStreamPath} is now unpublished.`) - } - - for (let playerId of this.players) { - let playerSession = context.sessions.get(playerId) - if (playerSession instanceof NodeRtmpSession) { - playerSession.sendStatusMessage(playerSession.playStreamId, "status", "NetStream.Play.UnpublishNotify", "stream is now unpublished.") - playerSession.flush() - } else { - playerSession.stop() - } - } - - //let the players to idlePlayers - for (let playerId of this.players) { - let playerSession = context.sessions.get(playerId) - context.idlePlayers.add(playerId) - playerSession.isPlaying = false - playerSession.isIdling = true - if (playerSession instanceof NodeRtmpSession) { - playerSession.sendStreamStatus(STREAM_EOF, playerSession.playStreamId) - } - } - - context.publishers.delete(this.publishStreamPath) - if (this.rtmpGopCacheQueue) { - this.rtmpGopCacheQueue.clear() - } - if (this.flvGopCacheQueue) { - this.flvGopCacheQueue.clear() - } - this.players.clear() - this.isPublishing = false - } - this.publishStreamId = 0 - this.publishStreamPath = "" - } - } -} - -module.exports = NodeRtmpSession diff --git a/packages/streaming-server/src/internal-nms/sessionsModels/trans_session.js b/packages/streaming-server/src/internal-nms/sessionsModels/trans_session.js deleted file mode 100644 index d0ecfbfc..00000000 --- a/packages/streaming-server/src/internal-nms/sessionsModels/trans_session.js +++ /dev/null @@ -1,114 +0,0 @@ -// -// Created by Mingliang Chen on 18/3/9. -// illuspas[a]gmail.com -// Copyright (c) 2018 Nodemedia. All rights reserved. -// - -const fs = require("fs") -const EventEmitter = require("events") -const { spawn } = require("child_process") -const dateFormat = require("dateformat") -const mkdirp = require("mkdirp") - -const Logger = require("../lib/logger") - -class NodeTransSession extends EventEmitter { - constructor(conf) { - super() - this.conf = conf - } - - run() { - let vc = this.conf.vc || "copy" - let ac = this.conf.ac || "copy" - let inPath = "rtmp://127.0.0.1:" + this.conf.rtmpPort + this.conf.streamPath - let ouPath = `${this.conf.mediaroot}/${this.conf.streamApp}/${this.conf.fixedStreamName}` - let mapStr = "" - - if (this.conf.rtmp && this.conf.rtmpApp) { - if (this.conf.rtmpApp === this.conf.streamApp) { - Logger.error("[Transmuxing RTMP] Cannot output to the same app.") - } else { - let rtmpOutput = `rtmp://127.0.0.1:${this.conf.rtmpPort}/${this.conf.rtmpApp}/${this.conf.streamName}` - mapStr += `[f=flv]${rtmpOutput}|` - Logger.log("[Transmuxing RTMP] " + this.conf.streamPath + " to " + rtmpOutput) - } - } - - if (this.conf.mp4) { - this.conf.mp4Flags = this.conf.mp4Flags ? this.conf.mp4Flags : "" - let mp4FileName = dateFormat("yyyy-mm-dd-HH-MM-ss") + ".mp4" - let mapMp4 = `${this.conf.mp4Flags}${ouPath}/${mp4FileName}|` - mapStr += mapMp4 - Logger.log("[Transmuxing MP4] " + this.conf.streamPath + " to " + ouPath + "/" + mp4FileName) - } - if (this.conf.hls) { - this.conf.hlsFlags = this.conf.hlsFlags ? this.conf.hlsFlags : "" - - let hlsFileName = "index.m3u8" - let mapHls = `${this.conf.hlsFlags}${ouPath}/${hlsFileName}|` - - mapStr += mapHls - - Logger.log("[Transmuxing HLS] " + this.conf.streamPath + " to " + ouPath + "/" + hlsFileName) - } - if (this.conf.dash) { - this.conf.dashFlags = this.conf.dashFlags ? this.conf.dashFlags : "" - let dashFileName = "index.mpd" - let mapDash = `${this.conf.dashFlags}${ouPath}/${dashFileName}` - mapStr += mapDash - Logger.log("[Transmuxing DASH] " + this.conf.streamPath + " to " + ouPath + "/" + dashFileName) - } - - mkdirp.sync(ouPath) - - let argv = ["-y", "-i", inPath] - - Array.prototype.push.apply(argv, ["-c:v", vc]) - Array.prototype.push.apply(argv, this.conf.vcParam) - Array.prototype.push.apply(argv, ["-c:a", ac]) - Array.prototype.push.apply(argv, this.conf.acParam) - Array.prototype.push.apply(argv, ["-f", "tee", "-map", "0:a?", "-map", "0:v?", mapStr]) - - argv = argv.filter((n) => { return n }) //去空 - - this.ffmpeg_exec = spawn(this.conf.ffmpeg, argv) - this.ffmpeg_exec.on("error", (e) => { - Logger.ffdebug(e) - }) - - this.ffmpeg_exec.stdout.on("data", (data) => { - Logger.ffdebug(`FF输出:${data}`) - }) - - this.ffmpeg_exec.stderr.on("data", (data) => { - Logger.ffdebug(`FF输出:${data}`) - }) - - this.ffmpeg_exec.on("close", (code) => { - - Logger.log("[Transmuxing end] " + this.conf.streamPath) - this.emit("end") - - fs.readdir(ouPath, function (err, files) { - if (!err) { - files.forEach((filename) => { - if (filename.endsWith(".ts") - || filename.endsWith(".m3u8") - || filename.endsWith(".mpd") - || filename.endsWith(".m4s") - || filename.endsWith(".tmp")) { - fs.unlinkSync(ouPath + "/" + filename) - } - }) - } - }) - }) - } - - end() { - this.ffmpeg_exec.kill() - } -} - -module.exports = NodeTransSession \ No newline at end of file diff --git a/packages/streaming-server/src/lib/cpu/index.js b/packages/streaming-server/src/lib/cpu/index.js deleted file mode 100644 index 1d9b9ad0..00000000 --- a/packages/streaming-server/src/lib/cpu/index.js +++ /dev/null @@ -1,45 +0,0 @@ -import os from "os" - -export function averageUsage() { - //Initialise sum of idle and time of cores and fetch CPU info - let totalIdle = 0, totalTick = 0 - let cpus = os.cpus() - - //Loop through CPU cores - for (let i = 0, len = cpus.length; i < len; i++) { - //Select CPU core - let cpu = cpus[i] - - //Total up the time in the cores tick - if (cpu.times.type) { - for (type in cpu.times) { - totalTick += cpu.times[type] - } - } - - //Total up the idle time of the core - totalIdle += cpu.times.idle - } - - //Return the average Idle and Tick times - return { idle: totalIdle / cpus.length, total: totalTick / cpus.length } -} - -export function percentageUsage() { - return new Promise((resolve, reject) => { - let startMeasure = averageUsage() - - setTimeout(() => { - let endMeasure = averageUsage() - - //Calculate the difference in idle and total time between the measures - let idleDifference = endMeasure.idle - startMeasure.idle - let totalDifference = endMeasure.total - startMeasure.total - - //Calculate the average percentage CPU usage - let percentageCPU = 100 - ~~(100 * idleDifference / totalDifference) - - return resolve(percentageCPU) - }, 100) - }) -} \ No newline at end of file diff --git a/packages/streaming-server/src/lib/getStreamingKeyFromStreamPath/index.js b/packages/streaming-server/src/lib/getStreamingKeyFromStreamPath/index.js deleted file mode 100644 index dd42ae54..00000000 --- a/packages/streaming-server/src/lib/getStreamingKeyFromStreamPath/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function getStreamingKeyFromStreamPath(StreamPath) { - return StreamPath.split("/").pop() -} \ No newline at end of file diff --git a/packages/streaming-server/src/lib/index.js b/packages/streaming-server/src/lib/index.js deleted file mode 100644 index 6c99fae7..00000000 --- a/packages/streaming-server/src/lib/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default as getStreamingKeyFromStreamPath } from "./getStreamingKeyFromStreamPath" -export * as cpu from "./cpu" \ No newline at end of file diff --git a/packages/streaming-server/src/managers/DbManager/index.js b/packages/streaming-server/src/managers/DbManager/index.js deleted file mode 100644 index f50e2c14..00000000 --- a/packages/streaming-server/src/managers/DbManager/index.js +++ /dev/null @@ -1,40 +0,0 @@ -import mongoose from "mongoose" - -function getConnectionConfig(obj) { - const { db_user, db_driver, db_name, db_pwd, db_hostname, db_port } = obj - - return [`${db_driver ?? "mongodb"}://${db_user ? `${db_user}` : ""}${db_pwd ? `:${db_pwd}` : ""}${db_user ? "@" : ""}${db_hostname ?? "localhost"}:${db_port ?? "27017"}${db_user ? "/?authMechanism=DEFAULT" : ""}`, { - dbName: db_name, - useNewUrlParser: true, - useUnifiedTopology: true, - }] -} - -export default class DBManager { - constructor() { - this.env = process.env - } - - connect = () => { - return new Promise((resolve, reject) => { - try { - console.log("🌐 Trying to connect to DB...") - const dbConfig = getConnectionConfig(this.env) - - mongoose.connect(...dbConfig) - .then((res) => { return resolve(true) }) - .catch((err) => { return reject(err) }) - } catch (err) { - return reject(err) - } - }).then(done => { - console.log(`✅ Connected to DB`) - }).catch((error) => { - console.log(`❌ Failed to connect to DB, retrying...\n`) - console.log(error) - setTimeout(() => { - this.connect() - }, 1000) - }) - } -} \ No newline at end of file diff --git a/packages/streaming-server/src/managers/SessionsManager/index.js b/packages/streaming-server/src/managers/SessionsManager/index.js deleted file mode 100644 index def40749..00000000 --- a/packages/streaming-server/src/managers/SessionsManager/index.js +++ /dev/null @@ -1,57 +0,0 @@ -import lodash from "lodash" - -export default class SessionsManager { - constructor() { - this.sessions = {} - this.publicStreams = [] - } - - newSession = (id, session) => { - this.sessions[id] = session - } - - getSession = (id) => { - return this.sessions[id] - } - - removeSession = (id) => { - this.sessions[id].reject() - - delete this.sessions[id] - } - - publishStream = (payload) => { - if (typeof payload !== "object") { - throw new Error("Payload must be an object") - } - - this.publicStreams.push(payload) - } - - unpublishStream = (stream_key) => { - this.publicStreams = this.publicStreams.filter((stream) => stream.stream_key !== stream_key) - } - - getPublicStreams = () => { - // return this.publicStreams but without stream_key property - return lodash.map(this.publicStreams, (stream) => { - return lodash.omit(stream, "stream_key") - }) - } - - getStreamsByUserId = (user_id) => { - const streams = lodash.filter(this.publicStreams, (stream) => stream.user_id === user_id) - - return lodash.map(streams, (stream) => { - return lodash.omit(stream, "stream_key") - }) - } - - getStreamsByUsername = (username) => { - const streams = lodash.filter(this.publicStreams, (stream) => stream.username === username) - - return lodash.map(streams, (stream) => { - return lodash.omit(stream, "stream_key") - }) - } -} \ No newline at end of file diff --git a/packages/streaming-server/src/managers/index.js b/packages/streaming-server/src/managers/index.js deleted file mode 100644 index 11ce56ad..00000000 --- a/packages/streaming-server/src/managers/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default as DbManager } from "./DbManager" -export { default as SessionsManager } from "./SessionsManager" diff --git a/packages/streaming-server/src/models/index.js b/packages/streaming-server/src/models/index.js deleted file mode 100644 index 8945412e..00000000 --- a/packages/streaming-server/src/models/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import mongoose, { Schema } from "mongoose" - -function getSchemas() { - const obj = Object() - - const _schemas = require("../schemas") - - Object.keys(_schemas).forEach((key) => { - obj[key] = Schema(_schemas[key]) - }) - - return obj -} - -const schemas = getSchemas() - -// streaming -export const StreamingKey = mongoose.model("StreamingKey", schemas.streamingKey, "streamingKeys") \ No newline at end of file diff --git a/packages/streaming-server/src/schemas/index.js b/packages/streaming-server/src/schemas/index.js deleted file mode 100644 index 1f405d09..00000000 --- a/packages/streaming-server/src/schemas/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as streamingKey } from "./streamingKey" \ No newline at end of file diff --git a/packages/streaming-server/src/schemas/streamingKey/index.js b/packages/streaming-server/src/schemas/streamingKey/index.js deleted file mode 100755 index ebb47e30..00000000 --- a/packages/streaming-server/src/schemas/streamingKey/index.js +++ /dev/null @@ -1,14 +0,0 @@ -export default { - username: { - type: String, - required: true, - }, - user_id: { - type: String, - required: true, - }, - key: { - type: String, - required: true, - } -} \ No newline at end of file