Live stream a gstreamer UDP video pipeline to a react application

476 views Asked by At

I have a camera which I access through a gstreamer pipeline. The pipeline sends the udp video to a local port 5000.

A nodejs server picks up the udp, handles offer/awnser and ice candidate, and video data with sockets.

A react application connects to the socket, handles the video data and uses mediasource api to play the live video.

The video is not showing in the web app and I don't have any apparant errors. The front end receives the video data as ArrayBuffer objects I use Chrome as browser and the video format and mime type seems to be correct.

Below is the code I am using :

Gstreamer pipeline I run in my terminal(mac) : gst-launch-1.0 -v avfvideosrc ! videoconvert ! x264enc tune=zerolatency ! h264parse ! queue ! rtph264pay ! udpsink host=127.0.0.1 port=5000

Note that I am using the computers webcam for the moment, the equivalent pipeline for pc would be : gst-launch-1.0 -v mfvideosrc ! videoconvert ! x264enc tune=zerolatency ! h264parse ! queue ! rtph264pay ! udpsink host=127.0.0.1 port=5000

My signaling server (server.js) :

const express = require("express");
const http = require("http");
const socketIO = require("socket.io");
const dgram = require("dgram");
const cors = require("cors");

const app = express();
const server = http.createServer(app);
const udpServer = dgram.createSocket("udp4");



// Use the cors middleware to allow cross-origin requests for HTTP requests
app.use(
  cors({
    origin: "http://localhost:3001", // Allow requests only from React app's URL
    methods: ["GET", "POST"],
  })
);

const io = socketIO(server, {
  cors: {
    origin: "http://localhost:3001", // Allow WebSocket connections only from React app's URL
    methods: ["GET", "POST"],
  },
});

udpServer.on("message", (message, remote) => {
    console.log("Received video data:", message.length, "bytes");
    io.emit("video-data", message);
  });

udpServer.bind(5000); // port 5000 where gstreamer is sending UDP


io.on("connection", (socket) => {
  console.log("A user connected");

  socket.on("offer", (offer) => {
    // Broadcast the offer to all other connected clients
    console.log("offer:", offer);
    socket.broadcast.emit("offer", offer);
  });

  socket.on("answer", (answer) => {
    // Broadcast the answer to all other connected clients
    console.log("answer:", answer);
    socket.broadcast.emit("answer", answer);
  });

  socket.on("ice-candidate", (candidate) => {
    // Broadcast the ICE candidate to all other connected clients
    console.log("ice-candidate", candidate);
    socket.broadcast.emit("ice-candidate", candidate);
  });

  socket.on("disconnect", () => {
    console.log("A user disconnected");
  });

  socket.on("error", (error) => {
    console.error("WebSocket error:", error);
  });
});

server.listen(3000, () => {
  console.log("Signaling server is running on port 3000");
});

My react application :

import React, { useEffect, useRef } from "react";
import io from "socket.io-client";

const App = () => {
  const videoRef = useRef();
  const socket = io.connect("http://localhost:3000");

  const mediaSource = useRef(null);
  const sourceBuffer = useRef(null);

  useEffect(() => {
    const videoElement = videoRef.current;

    mediaSource.current = new MediaSource();

    mediaSource.current.addEventListener("sourceopen", () => {
      sourceBuffer.current = mediaSource.current.addSourceBuffer("video/mp4; codecs=avc1.42E01E");

      sourceBuffer.current.addEventListener("updateend", () => {
        if (mediaSource.current.readyState === "open") {
          mediaSource.current.endOfStream();
        }
      });

      videoElement.srcObject = mediaSource.current;
      videoElement.play().catch((error) => {
        console.error("Autoplay error:", error);
      });
    });

    mediaSource.current.addEventListener("error", (e) => {
      console.error("MediaSource error:", e);
    });

    socket.on("video-data", (videoData) => {
      if (mediaSource.current.readyState === "open") {
        if (!sourceBuffer.current) {
          sourceBuffer.current = mediaSource.current.addSourceBuffer("video/mp4; codecs=avc1.42E01E");
          sourceBuffer.current.addEventListener("updateend", () => {
            if (mediaSource.current.readyState === "open") {
              mediaSource.current.endOfStream();
            }
          });
        }

        sourceBuffer.current.appendBuffer(new Uint8Array(videoData));
      }
    });

    return () => {
      if (mediaSource.current.readyState === "open") {
        mediaSource.current.endOfStream();
      }
    };
  }, []);

  return (
    <div>
      <h1>Video Stream</h1>
      <video ref={videoRef} playsInline />
    </div>
  );
};

export default App;

0

There are 0 answers