0

I am attempting to display an Axis camera live stream in my Next.js front-end after making a request to the Axis API in my custom Node.js server. After making this request

const response = await client.fetch(`http://${ip}/axis-cgi/mjpg/video.cgi?resolution=${resolution}&fps=${fps}&compression=${compression}`, {
        method: 'GET',
        headers: {
            'Content-Type': 'multipart/x-mixed-replace; boundary=--myboundary',
        },
    });

I get a response with the status code of 200 and the following in the response body:

PassThrough {
  _readableState: ReadableState {
    objectMode: false,
    highWaterMark: 16384,
    buffer: BufferList { head: [Object], tail: [Object], length: 1 },
    length: 65351,
    pipes: [],
    flowing: null,
    ended: false,
    endEmitted: false,
    reading: false,
    constructed: true,
    sync: false,
    needReadable: false,
    emittedReadable: false,
    readableListening: false,
    resumeScheduled: false,
    errorEmitted: false,
    emitClose: true,
    autoDestroy: true,
    destroyed: false,
    errored: null,
    closed: false,
    closeEmitted: false,
    defaultEncoding: 'utf8',
    awaitDrainWriters: null,
    multiAwaitDrain: false,
    readingMore: false,
    dataEmitted: false,
    decoder: null,
    encoding: null,
    [Symbol(kPaused)]: null
  },
  _events: [Object: null prototype] {
    prefinish: [Function: prefinish],
    unpipe: [Function: onunpipe],
    error: [ [Function: onerror], [Function (anonymous)] ],
    close: [Function: bound onceWrapper] { listener: [Function: onclose] },
    finish: [Function: bound onceWrapper] { listener: [Function: onfinish] },
    drain: [Function: pipeOnDrainFunctionResult]
  },
  _eventsCount: 6,
  _maxListeners: undefined,
  _writableState: WritableState {
    objectMode: false,
    highWaterMark: 16384,
    finalCalled: false,
    needDrain: true,
    ending: false,
    ended: false,
    finished: false,
    destroyed: false,
    decodeStrings: true,
    defaultEncoding: 'utf8',
    length: 65351,
    writing: true,
    corked: 0,
    sync: false,
    bufferProcessing: false,
    onwrite: [Function: bound onwrite],
    writecb: [Function: nop],
    writelen: 65351,
    afterWriteTickInfo: null,
    buffered: [],
    bufferedIndex: 0,
    allBuffers: true,
    allNoop: true,
    pendingcb: 1,
    constructed: true,
    prefinished: false,
    errorEmitted: false,
    emitClose: true,
    autoDestroy: true,
    errored: null,
    closed: false,
    closeEmitted: false,
    [Symbol(kOnFinished)]: []
  },
  allowHalfOpen: true,
  [Symbol(kCapture)]: false,
  [Symbol(kCallback)]: [Function: bound onwrite]
}

My question is how do you use this data to display the MJPEG on the front-end? Originally, I was passing the URL to an img tag, but this approach does not work in production as the URL is not secure.

Myles Jefferson
  • 115
  • 1
  • 1
  • 5
  • To display the MJPEG stream on the front-end you set the src URL of in img tag. So just like what you did previously. That's the way to display MJPEG stream. There may be libraries that wrap this up for you in functions but at the end of the day all those libraries will just set the src URL of an img tag. If the URL is not secure then secure the URL. It is not the fault of the media player (in this case the img tag) that the URL is not secure. – slebetman Jul 21 '22 at 07:25
  • Note that any request made from your website to the same domain as your HTML page will be passed the regular cookies. So you can use session based authentication to secure the URL. Then you can hide the insecure URL at your server/proxy (which the public has no access to) – slebetman Jul 21 '22 at 07:26
  • That response variable you have there is a ReadableStream object. I think you can pipe it to `res` in Express/Connect which is a WritableStream object. I'm not sure though as I've never done it that way. The last time I did MJPEG streaming in node I wrote my own HTTP server code using `net.createServer()` because I couldn't figure out how to turn stream an infinite HTTP response using node's http module (which Express/Connect use internally) – slebetman Jul 21 '22 at 07:33
  • Personally, if I were you I'd abandon fetch and node's entire http stack altogether and write my own mjpeg proxy using low-level TCP/IP (net.createServer() and net.connect()) because to me it is much simpler. But that is because I have experience writing HTTP server/frameworks before - at least 4 times in 3 different languages. But you are not me so YMMV. One thing though. People use all these fancy frameworks and servers like Nginx, Apache, Express, Spring, Laravel etc. so most people think low-level HTTP protocol is hard. It is not that hard. HTTP1.0 especially is a very simple protocol. – slebetman Jul 21 '22 at 07:38

1 Answers1

-1

OK. I've never done it this way but after a quick googling around I think it's possible to do what you want. So for next.js you can do the following:

WARNING: Untested code

Create an API endpoint for your MJPEG stream.

From the answer to this question (multipart/mixed response using NodeJS) it looks like in the handler you can pipe the fetch response to the res object (assuming that your client.fetch function implements the fetch API).

function handler(req, res) {
    const response = await client.fetch(url, options);
    response.body.pipeTo(res);
}

On the client side just set the src property of an img tag to the URL of your API endpoint. You can protect the API endpoint with session cookies or a session hash in the URL but using Authorization header won't work because the img tag cannot set custom headers in its request.

slebetman
  • 109,858
  • 19
  • 140
  • 171