3

I'm new to web development and have been trying to solve a problem for some time but no luck. I'm using React and Django

The thing is, there is a 3rd party application that performs some image processing using opencv on video frames and I've to display those encoded frames on the web browser.

I want to receive those frames using Django API, decode them, and display them using React JS, also returning a response with every frame to that 3rd party app.

I've prepared a flowchart of how things should work but haven't been able to start at all.

Flowchart:

Flowchart

The outcome on the browser should appear something like this.

Outcome:

enter image description here

Need to know how to approach this, shall I use WebSockets or can I send the encoded frames directly to React taking Django out of the picture.

Edit:

  1. The frames will be served by the 3rd party app in cv2.imencode('.jpg', frame) encoded format along with some other data in a JSON packet.
  2. The decoding needs to be done by Django or React (not sure which one will or should handle this)
  3. The frames will keep on updating as if a real-time video is playing, ie. the moment a new frame is received, it must replace the old frame. The frame rate will be around 25 fps.
  4. A response must be returned for each frame. Django needs to perform anything apart from serving frames and sending back a response.
  5. Since there will be multiple cameras to be displayed simultaneously, do I need to use more than one port in Django or one is going to be sufficient?
Voldemort
  • 175
  • 5
  • 20
  • 1
    There isn't sufficient information to answer this question. How does the 3rd party application expect frames to be transmitted? Are the frames in conventional image formats that the browser supports? How long do the frames need to stick around? Outside of serving frames, is there anything that Django needs to perform? – plunker Jul 11 '23 at 01:20
  • @plunker I have edited the question. please check if the information provided is sufficient now. – Voldemort Jul 11 '23 at 06:08
  • 2
    Have you investigated video streaming? You don't really want ReactJS to be involved in every frame. You should be using a ` – Tim Roberts Jul 11 '23 at 06:36
  • @TimRoberts so you are suggesting to use Django templates instead of ReactJS here.. Right? – Voldemort Jul 11 '23 at 06:59
  • I'm not sure that's relevant. What I'm saying is, don't have Javascript touch the frames, if possible. Use a ` – Tim Roberts Jul 11 '23 at 22:55
  • Your font-end should only serve to output the processed result by your server. What you are looking for here, as Voldemort said, is a streaming service. In essence, you can set up Django to have video streaming capabilities, and do the frame processing within django. You essentially want WebRTC-like process with OpenCV in between. – Dimitar Jul 17 '23 at 13:29

3 Answers3

1

Creating a Django endpoint: WebSockets to receive encoded frames in Django, decode them, and return a response, while continuously updating the video frame on the web browser.

import cv2
import json
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync


@csrf_exempt
def process_frames(request):
    if request.method == 'POST':
        data = json.loads(request.body)
        
        # Extract the encoded frames and other data from the JSON packet
        encoded_frames = data['frames']
        # Process other data as needed
        
        # Decode the frames using cv2.imdecode()
        decoded_frames = []
        for encoded_frame in encoded_frames:
            frame = cv2.imdecode(encoded_frame, cv2.IMREAD_COLOR)
            decoded_frames.append(frame)
        
        # Perform any necessary operations with the frames
        
        # Return a response for each frame
        response = {'status': 'success'}
        return JsonResponse(response)

For video streaming you can either use browser(HTML) for video rendering or React(JS) for video rendering. Both have its pros and cons.


<!DOCTYPE html>
<html>
<head>
    <title>Integrating inside HTML</title>
</head>
<body>
    <video id="videoPlayer" autoplay controls></video>

    <script>
        const video = document.getElementById('videoPlayer');

        function updateVideoFrame(frame) {
            const blob = new Blob([frame], { type: 'image/jpeg' });
            const frameURL = URL.createObjectURL(blob);
            video.src = frameURL;
        }

        // Make a request to the Django endpoint to receive the frames
        setInterval(() => {
            fetch('/process_frames', { method: 'POST' })
                .then(response => response.json())
                .then(data => {
                    if (data.status === 'success') {
                        updateVideoFrame(data.frame);
                    }
                })
                .catch(error => {
                    console.error('Error:', error);
                });
        }, 40); // Adjust the interval to achieve the desired frame rate (25 fps = 40 ms delay)
    </script>
</body>
</html>

Integrating inside JS

import React, { useEffect, useState } from 'react';

const VideoPlayer = () => {
    const [frame, setFrame] = useState(null);

    useEffect(() => {
        const fetchFrame = async () => {
            try {
                const response = await fetch('/process_frames', { method: 'POST' });
                const data = await response.json();

                if (data.status === 'success') {
                    setFrame(data.frame);
                }
            } catch (error) {
                console.error('Error:', error);
            }
        };

        // Fetch frames at the desired frame rate
        const intervalId = setInterval(fetchFrame, 40); // Adjust the interval to achieve the desired frame rate (25 fps = 40 ms delay)

        return () => {
            clearInterval(intervalId);
        };
    }, []);

    const videoSource = frame ? URL.createObjectURL(new Blob([frame], { type: 'image/jpeg' })) : '';

    return (
        <video src={videoSource} autoPlay controls />
    );
};

export default VideoPlayer;

EDIT

Django endpoint using Django Channels

# This is a template code for using Django Channels 
import cv2
import json
from channels.generic.websocket import WebsocketConsumer


class FrameProcessingConsumer(WebsocketConsumer):
    def receive(self, text_data=None, bytes_data=None):
        if bytes_data:
            # Extract the encoded frames and other data from the JSON packet
            data = json.loads(bytes_data.decode())
            encoded_frames = data['frames']
            # Process other data as needed
            
            # Decode the frames using cv2.imdecode()
            decoded_frames = []
            for encoded_frame in encoded_frames:
                frame = cv2.imdecode(encoded_frame, cv2.IMREAD_COLOR)
                decoded_frames.append(frame)
            
            # Perform any necessary operations with the frames
            
            # Return a response for each frame
            response = {'status': 'success'}
            self.send(json.dumps(response))


@csrf_exempt
def process_frames(request):
    if request.method == 'POST':
        data = json.loads(request.body)
        
        # Extract the encoded frames and other data from the JSON packet
        encoded_frames = data['frames']
        # Process other data as needed
        
        # Decode the frames using cv2.imdecode()
        decoded_frames = []
        for encoded_frame in encoded_frames:
            frame = cv2.imdecode(encoded_frame, cv2.IMREAD_COLOR)
            decoded_frames.append(frame)
        
        # Perform any necessary operations with the frames
        
        # Return a response for each frame
        response = {'status': 'success'}
        return JsonResponse(response)

Make changes as per your requirements.

Hope this helps...

Anay
  • 741
  • 1
  • 5
  • 15
  • where have you used Websockets in your code? It looks to me just a simple views file in Django. – Voldemort Jul 15 '23 at 06:15
  • Using websockets in Django adds whole another layer of complexity. `Channels` in Django are used to extend HTTP capabilities which enables use of `websockets`. You would have to make configurations as per your requirements. Refer to docs : https://channels.readthedocs.io/en/stable/ – Anay Jul 15 '23 at 11:28
  • HTTP approach allows you to smoothly handle lower frame rates which might be useful when dealing with footage from security cameras. Use it based on your requirements. – Anay Jul 21 '23 at 04:42
  • In this example have you assumed that a single JSON packet will contain multiple frames? Since you have used ```encoded_frames = data['frames']``` and are iterating over this loop. In my case, every JSON packet will contain only one frame.. – Voldemort Jul 21 '23 at 12:26
  • Thanks for the answer. Though, it doesn't solve my problem entirely but has given me a platform to build my solution. – Voldemort Jul 27 '23 at 21:36
1

You seem to be dealing with a video stream in MJPEG format (Motion JPEG). It is a sequence of JPEG frames without any inter-frame compression.

Frontend only

You typically can capture the MJPEG stream from the frontend. But if the third-party accesses the IP camera directly without caching layer, you might effectively DDOS it with very little traffic. I managed to slow down my localhost webcam MJPEG server by just using a handful of receivers.

Also, you directly expose your third-party source to your users. And the third party can monitor your users.

Backend-frontend

Passing the stream through your own backend is more costly on resources. You make minimum requests per frame to the third-party server but have to serve the stream to all your clients.

Resolution

If your frontend is going to be used by you only, go for the backend-free solution. If you have enough resources for the backend, you expect more clients, and you don't want to expose your clients to the third party, serve the MJPEG in the backend.

As for technical part, there are plenty out-of-the-box solutions.

Martin Benes
  • 265
  • 10
  • I'll have to stick to the Backend-frontend approach as there can be multiple clients. Currently, I'm receiving JSON packets from third-party application via API. – Voldemort Jul 26 '23 at 19:06
0

Based on my experience, it's best to utilize server-sent events – if the communication is unidirectional and it's not needed to send data to the backend, here's what could be done:

I highly suggest to reduce complexity as much as possible, that means removing Django from the picture in this scenario (otherwise, you would have to consume event-stream content from there and relay it to the client with some additional configuration).

  1. Ensure that the 3rd-party app allows serving text/event-stream as a response type and has CORS enabled.
  2. In React, install @microsoft/fetch-event-source, it has all the built-in features of native Fetch API with the enhanced usability to consume event-stream content.
  3. Within your React component, add the following logic from fetch-event-source package within a useEffect hook (Others done an amazing job detailing the steps) Just make sure that the Content-type header value is set to text/event-stream.

Tip: make sure you cleanup your useEffect by returning the function to close the EventStream to avoid any performance issues or memory leaks.

Please let me know if you need further clarification.

Khaled
  • 93
  • 2
  • 9
  • I need to maintain bi-directional communication, because later on I might be asked to receive some response on the backend as well. Also, I can't make any changes to the 3rd party application as it is handled by a different team. So, if you can modify your answer by suggesting how to receive a response (bi-directional communication) at the backend then it would be much appreciated. – Voldemort Jul 26 '23 at 16:03