Skip to content

Instantly share code, notes, and snippets.

@soyuka
Forked from Majaxx/stream.js
Last active August 29, 2015 14:08
Show Gist options
  • Save soyuka/a15d58f7379c9740929e to your computer and use it in GitHub Desktop.
Save soyuka/a15d58f7379c9740929e to your computer and use it in GitHub Desktop.

Revisions

  1. @Majaxx Majaxx created this gist Oct 29, 2014.
    285 changes: 285 additions & 0 deletions stream.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,285 @@
    var db = require('ezseed-database').db
    var spawn = require('child_process').spawn
    var logger = require('ezseed-logger')('stream')
    var fs = require('fs')
    var debug = require('debug')('ezseed:stream')
    var which = require('which')
    var config = require('./config')

    //sizes: https://www.ffmpeg.org/ffmpeg-utils.html
    const FFMPEG = [
    '-preset', 'ultrafast',
    //'-maxrate', '2500k',
    '-bufsize', '3200k',
    '-pix_fmt', 'yuv420p',
    '-strict', 'experimental',
    ]

    const FFMPEG_IOS = [
    '-vcodec', 'libx264',
    '-s', 'vga', //640x480
    '-segment_list_type', 'm3u8',
    '-map', '0:v',
    '-map', '0:a:0',
    '-c:a', 'aac', //audio codec
    '-ar', '44100', //audio bitrate
    '-ac', '2', //stereo
    '-f', 'hls',
    '-pix_fmt', 'yuv420p',
    '-crf', '35',
    '-framerate', '12',
    '-g', '24',
    '-hls_time', '10', //hls time in sec
    '-hls_list_size', '6', //max list size
    '-hls_wrap', '18', //wrap after 18
    // '-hls_allow_cache', 'allowcache',
    '-start_number', '1',
    ]

    //https://trac.ffmpeg.org/wiki/Encode/VP8
    const FFMPEG_ANDROID = [
    '-c:a', 'libvo_aacenc',
    '-profile:a', 'aac_he_v2',
    '-c:v', 'h264',
    '-pre:v', 'medium',
    '-f', 'mpegts',
    '-ac', '2',
    '-map', '0:v',
    '-ar', '44100', //audio bitrate
    '-ac', '2', //stereo
    '-f', 'hls',
    '-pix_fmt', 'yuv420p',
    '-crf', '35',
    '-framerate', '12',
    '-g', '24',
    '-hls_time', '10', //hls time in sec
    '-hls_list_size', '6', //max list size
    '-hls_wrap', '18', //wrap after 18
    // '-hls_allow_cache', 'allowcache',
    '-start_number', '1',
    ]

    //https://trac.ffmpeg.org/wiki/Encode/VP8
    const FFMPEG_ANDROID = [
    '-c:a', 'libvo_aacenc',
    '-profile:a', 'aac_he_v2',
    '-c:v', 'h264',
    '-pre:v', 'medium',
    '-f', 'mpegts',
    '-ac', '2',
    '-map', '0:v',
    '-map', '0:a:0',
    ]
    const FFMPEG_DESKTOP = [
    '-vcodec', 'libx264',
    '-acodec', 'aac',
    // '-framerate', '30',
    // '-g', '60',
    '-crf', '18',
    '-vb', '128k',
    '-ac', '2',
    '-ar', '44100',
    '-f', 'flv'
    ]

    var command = config.converter

    var spawner = function(res, arguments) {

    if(command === false) {
    //Not Implemented
    return res.status(501).end()
    }

    debug(command + ' ', arguments.join(' '))
    ]
    const FFMPEG_DESKTOP = [
    '-vcodec', 'libx264',
    '-acodec', 'aac',
    // '-framerate', '30',
    // '-g', '60',
    '-crf', '18',
    '-vb', '128k',
    '-ac', '2',
    '-ar', '44100',
    '-f', 'flv'
    ]

    var command = config.converter

    var spawner = function(res, arguments) {

    if(command === false) {
    //Not Implemented
    return res.status(501).end()
    }

    debug(command + ' ', arguments.join(' '))

    var ff = spawn(command, arguments, {cwd: config.tmp, stdio: ['ignore', 'pipe', 'pipe']})

    ff.stdout.pipe(res)

    ff.stderr.on('data', function(d) {
    debug(d.toString())
    })

    ff.stdout.on('close', function() {
    res.end()
    })

    // res.on('close', function() {
    // ff.kill()
    // res.end()
    // })

    res.on('eroor', function() {
    ff.kill()
    res.end()
    })

    ff.on('exit', function(code) {
    var ff = spawn(command, arguments, {cwd: config.tmp, stdio: ['ignore', 'pipe', 'pipe']})

    ff.stdout.pipe(res)

    ff.stderr.on('data', function(d) {
    debug(d.toString())
    })

    ff.stdout.on('close', function() {
    res.end()
    })

    // res.on('close', function() {
    // ff.kill()
    // res.end()
    // })

    res.on('eroor', function() {
    ff.kill()
    res.end()
    })

    ff.on('exit', function(code) {
    debug(command + ' closed with code', code)
    res.end()
    })
    }

    var stream = {
    ios: function(res, file) {
    res.type('m3u8')
    spawner(res,
    ['-i', file.path]
    .concat(FFMPEG_IOS)
    .concat(FFMPEG)
    .concat(['pipe:1'])
    )
    },
    android: function(res, file) {
    res.type('mpegts')
    spawner(res,
    ['-i', file.path]
    .concat(FFMPEG_ANDROID)
    .concat(FFMPEG)
    .concat(['pipe:1'])
    )
    },
    res.end()
    })
    }

    var stream = {
    ios: function(res, file) {
    res.type('m3u8')
    spawner(res,
    ['-i', file.path]
    .concat(FFMPEG_IOS)
    .concat(FFMPEG)
    .concat(['pipe:1'])
    )
    },
    android: function(res, file) {
    res.type('mpegts')
    spawner(res,
    ['-i', file.path]
    .concat(FFMPEG_ANDROID)
    .concat(FFMPEG)
    .concat(['pipe:1'])
    )
    },
    desktop: function(res, file) {
    res.type('flv')
    spawner(res,
    ['-i', file.path]
    .concat(FFMPEG_DESKTOP)
    .concat(FFMPEG)
    .concat(['pipe:1'])
    )
    }
    }

    module.exports = function(req, res) {

    db.files.get(req.params.id, function(err, file) {
    if(err) {
    logger.error('File download - ', err.message)
    return res.status(500).send({error: err.message}).end()
    }

    if(!file || !fs.existsSync(file.path)) {
    return res.status(404).send({error: 'File not found'}).end()
    }

    debug('streaming file', req.params.os)

    res.type('flv')
    spawner(res,
    ['-i', file.path]
    .concat(FFMPEG_DESKTOP)
    .concat(FFMPEG)
    .concat(['pipe:1'])
    )
    }
    }

    module.exports = function(req, res) {

    db.files.get(req.params.id, function(err, file) {
    if(err) {
    logger.error('File download - ', err.message)
    return res.status(500).send({error: err.message}).end()
    }

    if(!file || !fs.existsSync(file.path)) {
    return res.status(404).send({error: 'File not found'}).end()
    }

    debug('streaming file', req.params.os)

    if(req.params.os) {
    req.params.os = req.params.os.toLowerCase()
    }

    //http://hgoebl.github.io/mobile-detect.js/doc/MobileDetect.html#os
    switch(req.params.os) {
    case 'ios':
    case 'safari':
    return stream.ios(res, file)
    break;
    case 'androidos':
    return stream.android(res, file)
    break;
    //desktop
    default:
    return stream.desktop(res, file)
    break;
    }

    })

    }