A parser that reads piped data from ffmpeg containing a fragmented mp4 and splits it into an initialization segment and media segments. Designed for streaming live video relayed from cctv cameras.
You must use the correct output args with ffmpeg to create a compatible fragmented mp4 format similar to the following real world examples:
ffmpeg -loglevel quiet -rtsp_transport tcp -i rtsp://192.168.1.21:554/user=admin_password=pass_channel=0_stream=1.sdp?real_stream -reset_timestamps 1 -an -c:v copy -f mp4 -movflags +frag_every_frame+empty_moov+default_base_moof -min_frag_duration 500000 pipe:1
ffmpeg -loglevel quiet -rtsp_transport tcp -i rtsp://192.168.1.18:554/user=admin&password=pass&channel=1&stream=1.sdp -reset_timestamps 1 -an -c:v copy -f mp4 -movflags +frag_keyframe+empty_moov+default_base_moof pipe:1
count
parameter{ segment, sequence, duration, timestamp, keyframe }
keyframe >= 0
hlsBase
=> hlsPlaylistBase
string, accepts _
, a-z
, and A-Z
hlsSize
=> hlsPlaylistSize
integer, ranges from 2
to 20
, defaults to 4
hlsInit
=> hlsPlaylistInit
boolean, defaults to true
bufferListSize
=> segmentCount
integer, ranges from 2
to 30
, defaults to 2
mp4frag.on('segment', data => {
console.log(data);
});
{ segment, sequence, duration, timestamp }
hlsPlaylistBase
const mp4frag = new Mp4Frag({segmentCount: 3});
_
, a-z
, and A-Z
), setting this will generate a live fmp4 HLS m3u8 playlistconst mp4frag = new Mp4Frag({hlsPlaylistSize: 4, hlsPlaylistBase: 'my_String'});
const { spawn } = require('child_process');
const Mp4Frag = require('mp4frag');
const mp4frag = new Mp4Frag({hlsPlaylistSize: 3, hlsPlaylistBase: 'back_yard'});
const ffmpeg = spawn(
'ffmpeg',
['-loglevel', 'quiet', '-probesize', '64', '-analyzeduration', '100000', '-reorder_queue_size', '5', '-rtsp_transport', 'tcp', '-i', 'rtsp://216.4.116.29:554/axis-media/media.3gp', '-an', '-c:v', 'copy', '-f', 'mp4', '-movflags', '+frag_keyframe+empty_moov+default_base_moof', '-metadata', 'title="ip 216.4.116.29"', '-reset_timestamps', '1', 'pipe:1'],
{ stdio: ['ignore', 'pipe', 'inherit'] }
);
ffmpeg.stdio[1].pipe(mp4frag);
mp4frag.m3u8
and can be served to a client browser via expressmp4frag.getSegmentObject(6)
, with 6
being the current sequence number#EXTM3U
#EXT-X-VERSION:7
#EXT-X-ALLOW-CACHE:NO
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:6
#EXT-X-MAP:URI="init-back_yard.mp4"
#EXTINF:4.780000,
back_yard6.m4s
#EXTINF:5.439000,
back_yard7.m4s
#EXTINF:4.269000,
back_yard8.m4s
app.get('/back_yard.m3u8', (req, res) => {
if (mp4frag.m3u8) {
res.writeHead(200, {'Content-Type': 'application/vnd.apple.mpegurl'});
res.end(mp4frag.m3u8);
} else {
res.sendStatus(503);
}
});
app.get('/init-back_yard.mp4', (req, res) => {
if (mp4frag.initialization) {
res.writeHead(200, {'Content-Type': 'video/mp4'});
res.end(mp4frag.initialization);
} else {
res.sendStatus(503);
}
});
app.get('/back_yard:id.m4s', (req, res) => {
const segmentObject = mp4frag.getSegmentObject(req.params.id);
if (segmentObject) {
res.writeHead(200, {'Content-Type': 'video/mp4'});
res.end(segmentObject.segment);
} else {
res.sendStatus(503);
}
});
const { spawn } = require('child_process');
const Mp4Frag = require('mp4frag');
// 3 past segments will be held in memory for later access via mp4frag.segmentObjects
// if each segment has a duration of 2 seconds, then buffer will contain 6 seconds of video
const mp4frag = new Mp4Frag({segmentCount: 3});
const ffmpeg = spawn(
'ffmpeg',
['-loglevel', 'quiet', '-probesize', '64', '-analyzeduration', '100000', '-reorder_queue_size', '5', '-rtsp_transport', 'tcp', '-i', 'rtsp://131.95.3.162:554/axis-media/media.3gp', '-an', '-c:v', 'copy', '-f', 'mp4', '-movflags', '+frag_keyframe+empty_moov+default_base_moof', '-metadata', 'title="ip 131.95.3.162"', '-reset_timestamps', '1', 'pipe:1'],
{ stdio: ['ignore', 'pipe', 'inherit'] }
);
ffmpeg.stdio[1].pipe(mp4frag);
const fs = require('fs');
const { initialization, segmentObjects } = mp4frag;
if (initialization && segmentObjects) {
const fileName = `${Date.now()}.mp4`;
const writeStream = fs.createWriteStream(fileName);
// write the initialization fragment
writeStream.write(initialization);
// write the media segments
segmentObjects.forEach(({segment}) => {
writeStream.write(segment);
});
// end
writeStream.end();
}