JSPM

  • Created
  • Published
  • Downloads 984
  • Score
    100M100P100Q105263F
  • License MIT

Parser that works with ffmpeg to read piped data and fragment mp4 into an initialization segment and media segments. It can also get the codec info and generate an fmp4 HLS m3u8 playlist. Must use the following flags with ffmpeg targeting the output: -f mp4 -movflags +faststart+frag_keyframe.

Package Exports

  • mp4frag

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (mp4frag) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

Parser that works with ffmpeg to read piped data and fragment mp4 into an initialization segment and media segments. It can also get the codec info and generate an fmp4 HLS m3u8 playlist. Must use the following flags with ffmpeg targeting the output: -f mp4 -movflags +faststart+frag_keyframe.

jsdocs

Currently being used in a media source extension project @ https://github.com/kevinGodell/mse-live-player

Options for instantiating new Mp4Frag

bufferListSize: unsigned int (2 - 10), setting this value will store specified number of media segments in the buffer

const mp4frag = new Mp4Frag({bufferListSize: 3});

hlsListSize: unsigned int (2 - 10), setting this along with hlsBase will generate a live fmp4 HLS m3u8 playlist

hlsBase: 'string', setting this along with hlsListSize will generate a live fmp4 HLS m3u8 playlist

const mp4frag = new Mp4Frag({hlsListSize: 4, hlsBase: 'myString'});

Possible usage examples

Example 1: Generate a live fmp4 HLS m3u8 playlist with ffmpeg

const { spawn } = require('child_process');

const Mp4Frag = require('mp4frag');

const mp4frag = new Mp4Frag({hlsListSize: 3, hlsBase: 'pool'});

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);
   
  • m3u8 playlist will now be available via mp4frag.m3u8 and can be served to a client browser via express
  • segments in playlist can be accessed by sequence number via mp4frag.getHlsSegment(6), with 6 being the current sequence number

Generated m3u8 playlist will look like the following example pulled from my live feed

#EXTM3U
#EXT-X-VERSION:7
#EXT-X-ALLOW-CACHE:NO
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:6
#EXT-X-MAP:URI="init-pool.mp4"
#EXTINF:4.78,
pool6.m4s
#EXTINF:5.439,
pool7.m4s
#EXTINF:4.269,
pool8.m4s

Setting up some server routes to respond to http requests for playing live HLS feed

app.get('/pool.m3u8', (req, res) => {
    if (mp4frag.m3u8) {
        res.writeHead(200, {'Content-Type': 'application/vnd.apple.mpegurl'});
        res.end(mp4frag.m3u8);
    } else {
        res.sendStatus(503);//todo maybe send 400
    }
});

app.get('/init-pool.mp4', (req, res) => {
    if (mp4frag.initialization) {
        res.writeHead(200, {'Content-Type': 'video/mp4'});
        res.end(mp4.initialization);
    } else {
        res.sendStatus(503);
    }
});

app.get('/pool:id.m4s', (req, res) => {
    const segment = mp4frag.getHlsSegment(req.params.id);
    if (segment) {
        res.writeHead(200, {'Content-Type': 'video/mp4'});
        res.end(segment);
    } else {
        res.sendStatus(503);
    }
});

See it in use @ https://github.com/kevinGodell/mse-live-player/blob/master/compare.js

Example 2: Create a buffer of past video to store for later recording

const { spawn } = require('child_process');

const Mp4Frag = require('mp4frag');

//3 past segments will be held in buffer for later access via mp4frag.buffer
//if each segment has a duration of 2 seconds, then buffer will contain 6 seconds of video
const mp4frag = new Mp4Frag({bufferListSize: 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);
Moments later, some triggering event occurs such as motion detection and we need to record video including 6 seconds of buffered video from before motion was detected
const fs = require('fs');

const writeStream = fs.createWriteStream(`${Date.now()}.mp4`);

    //write in the initialization fragment of mp4 file
    writeStream.write(mp4frag.initialization);

    //write the buffered segments
    writeStream.write(mp4frag.buffer);
    
    /*
    //piping method as an alternative to passing data in "segment" event 
    
    //pipe fresh segments to writeStream
    mp4frag.pipe(writeStream);
    
    //when you need to stop recording
    
    //unpipe
    mp4frag.unpipe(writeStream);
    
    //end
    writeStream.end();
    */
    

See it in use @ https://github.com/kevinGodell/mse-live-player/blob/master/record2.js