2024-07-24 19:27:50 +07:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Credit: https://github.com/dank074/Discord-video-stream
|
|
|
|
|
The use of video streaming in this library is an incomplete implementation with many bugs, primarily aimed at lazy users.
|
|
|
|
|
The video streaming features in this library are sourced from https://github.com/dank074/Discord-video-stream.
|
|
|
|
|
|
|
|
|
|
Please use the @dank074/discord-video-stream library to access all advanced and professional features,
|
|
|
|
|
along with comprehensive support. I will not actively fix bugs related to streaming and encourage everyone to
|
|
|
|
|
use https://github.com/dank074/Discord-video-stream for stable and smooth streaming.
|
|
|
|
|
|
|
|
|
|
To reiterate: This is an incomplete implementation of the library https://github.com/dank074/Discord-video-stream.
|
|
|
|
|
|
|
|
|
|
Thanks to dank074 and longnguyen2004 for implementing new codecs (H264, H265).
|
|
|
|
|
Thanks to mrjvs for discovering how Discord transmits data and the VP8 codec.
|
|
|
|
|
|
|
|
|
|
Please use the @dank074/discord-video-stream library for the best support.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
const { Buffer } = require('buffer');
|
|
|
|
|
const VideoDispatcher = require('./VideoDispatcher');
|
2024-10-27 02:30:45 +07:00
|
|
|
const Util = require('../../../util/Util');
|
2024-07-24 19:27:50 +07:00
|
|
|
const { H264Helpers, H265Helpers } = require('../player/processing/AnnexBNalSplitter');
|
|
|
|
|
|
|
|
|
|
class AnnexBDispatcher extends VideoDispatcher {
|
2024-10-27 02:30:45 +07:00
|
|
|
constructor(player, highWaterMark = 12, streams, fps, nalFunctions, payloadType) {
|
|
|
|
|
super(player, highWaterMark, streams, fps, payloadType);
|
2024-07-24 19:27:50 +07:00
|
|
|
this._nalFunctions = nalFunctions;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-02 18:28:47 +07:00
|
|
|
_codecCallback(frame) {
|
2024-07-24 19:27:50 +07:00
|
|
|
let accessUnit = frame;
|
|
|
|
|
let offset = 0;
|
|
|
|
|
|
|
|
|
|
// Extract NALUs from the access unit
|
|
|
|
|
while (offset < accessUnit.length) {
|
|
|
|
|
const naluSize = accessUnit.readUInt32BE(offset);
|
|
|
|
|
offset += 4;
|
|
|
|
|
const nalu = accessUnit.subarray(offset, offset + naluSize);
|
2024-07-29 19:01:40 +07:00
|
|
|
const isLastNal = offset + naluSize >= accessUnit.length;
|
2024-07-24 19:27:50 +07:00
|
|
|
if (nalu.length <= this.mtu) {
|
2024-09-16 22:01:15 +07:00
|
|
|
// Send as Single NAL Unit Packet.
|
|
|
|
|
this._playChunk(Buffer.concat([this.createPayloadExtension(), nalu]), isLastNal);
|
2024-07-24 19:27:50 +07:00
|
|
|
} else {
|
|
|
|
|
const [naluHeader, naluData] = this._nalFunctions.splitHeader(nalu);
|
2025-03-02 18:28:47 +07:00
|
|
|
const dataFragments = this.partitionMtu(naluData);
|
2024-09-16 22:01:15 +07:00
|
|
|
// Send as Fragmentation Unit A (FU-A):
|
2024-07-29 19:01:40 +07:00
|
|
|
for (let fragmentIndex = 0; fragmentIndex < dataFragments.length; fragmentIndex++) {
|
|
|
|
|
const data = dataFragments[fragmentIndex];
|
2024-07-24 19:27:50 +07:00
|
|
|
const isFirstPacket = fragmentIndex === 0;
|
|
|
|
|
const isFinalPacket = fragmentIndex === dataFragments.length - 1;
|
|
|
|
|
|
|
|
|
|
this._playChunk(
|
|
|
|
|
Buffer.concat([
|
2024-09-16 22:01:15 +07:00
|
|
|
this.createPayloadExtension(),
|
2024-07-24 19:27:50 +07:00
|
|
|
this.makeFragmentationUnitHeader(isFirstPacket, isFinalPacket, naluHeader),
|
|
|
|
|
data,
|
|
|
|
|
]),
|
2024-07-29 19:01:40 +07:00
|
|
|
isLastNal && isFinalPacket,
|
2024-07-24 19:27:50 +07:00
|
|
|
);
|
2024-07-29 19:01:40 +07:00
|
|
|
}
|
2024-07-24 19:27:50 +07:00
|
|
|
}
|
2024-07-29 19:01:40 +07:00
|
|
|
offset += naluSize;
|
|
|
|
|
}
|
2024-07-24 19:27:50 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class H264Dispatcher extends AnnexBDispatcher {
|
|
|
|
|
constructor(player, highWaterMark = 12, streams, fps) {
|
2024-10-27 02:30:45 +07:00
|
|
|
super(player, highWaterMark, streams, fps, H264Helpers, Util.getPayloadType('H264'));
|
2024-07-24 19:27:50 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
makeFragmentationUnitHeader(isFirstPacket, isLastPacket, naluHeader) {
|
|
|
|
|
const nal0 = naluHeader[0];
|
|
|
|
|
const fuPayloadHeader = Buffer.alloc(2);
|
|
|
|
|
const nalType = H264Helpers.getUnitType(naluHeader);
|
|
|
|
|
const fnri = nal0 & 0xe0;
|
|
|
|
|
|
|
|
|
|
fuPayloadHeader[0] = 0x1c | fnri;
|
|
|
|
|
|
|
|
|
|
if (isFirstPacket) {
|
|
|
|
|
fuPayloadHeader[1] = 0x80 | nalType;
|
|
|
|
|
} else if (isLastPacket) {
|
|
|
|
|
fuPayloadHeader[1] = 0x40 | nalType;
|
|
|
|
|
} else {
|
|
|
|
|
fuPayloadHeader[1] = nalType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fuPayloadHeader;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class H265Dispatcher extends AnnexBDispatcher {
|
|
|
|
|
constructor(player, highWaterMark = 12, streams, fps) {
|
2024-10-27 02:30:45 +07:00
|
|
|
super(player, highWaterMark, streams, fps, H265Helpers, Util.getPayloadType('H265'));
|
2024-07-24 19:27:50 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
makeFragmentationUnitHeader(isFirstPacket, isLastPacket, naluHeader) {
|
|
|
|
|
const fuIndicatorHeader = Buffer.allocUnsafe(3);
|
|
|
|
|
naluHeader.copy(fuIndicatorHeader);
|
|
|
|
|
const nalType = H265Helpers.getUnitType(naluHeader);
|
|
|
|
|
|
|
|
|
|
fuIndicatorHeader[0] = (fuIndicatorHeader[0] & 0b10000001) | (49 << 1);
|
|
|
|
|
|
|
|
|
|
if (isFirstPacket) {
|
|
|
|
|
fuIndicatorHeader[2] = 0x80 | nalType;
|
|
|
|
|
} else if (isLastPacket) {
|
|
|
|
|
fuIndicatorHeader[2] = 0x40 | nalType;
|
|
|
|
|
} else {
|
|
|
|
|
fuIndicatorHeader[2] = nalType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fuIndicatorHeader;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
H264Dispatcher,
|
|
|
|
|
H265Dispatcher,
|
|
|
|
|
};
|