'WebRTC video not showing

I have a streaming server that receives RTMP video inside RTP packets and publishes these packets to a web browser. The connection is established, but no video is shown. My server looks like this:

func main() {
    http.HandleFunc("/createPeerConnection", createPeerConnection)
    panic(http.ListenAndServe(":8080", nil))
}

func createPeerConnection(w http.ResponseWriter, r *http.Request) {
    peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{})
    if err != nil {
        panic(err)
    }

    // Create a video track
    videoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion")
    if err != nil {
        panic(err)
    }
    rtpSender, err := peerConnection.AddTrack(videoTrack)
    if err != nil {
        panic(err)
    }
    processRTCP(rtpSender)

    // Create a video track
    audioTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "audio/opus"}, "audio", "pion")
    if err != nil {
        panic(err)
    }
    rtpSender, err = peerConnection.AddTrack(audioTrack)
    if err != nil {
        panic(err)
    }
    processRTCP(rtpSender)

    var offer webrtc.SessionDescription

    if err := json.NewDecoder(r.Body).Decode(&offer); err != nil {
        return
    }

    if err := peerConnection.SetRemoteDescription(offer); err != nil {
        panic(err)
    }

    answer, err := peerConnection.CreateAnswer(nil)
    if err != nil {
        panic(err)
    } else if err = peerConnection.SetLocalDescription(answer); err != nil {
        panic(err)
    }

    response, err := json.Marshal(answer)
    if err != nil {
        panic(err)
    }
    
    w.Header().Set("Content-Type", "application/json")
    if _, err := w.Write(response); err != nil {
        panic(err)
    }

    go rtpToTrack(videoTrack, &codecs.VP8Packet{}, 90000, 5004)
    go rtpToTrack(audioTrack, &codecs.OpusPacket{}, 48000, 5006)
}

func processRTCP(rtpSender *webrtc.RTPSender) {
    go func() {
        rtcpBuf := make([]byte, 1500)

        for {
            if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
                return
            }
        }
    }()
}

func rtpToTrack(track *webrtc.TrackLocalStaticSample, depacketizer rtp.Depacketizer, sampleRate uint32, port int) {
    listener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("10.0.1.166"), Port: port})
    if err != nil {
        panic(err)
    }
    defer func() {
        if err = listener.Close(); err != nil {
            panic(err)
        }
    }()

    sampleBuffer := samplebuilder.New(10, depacketizer, sampleRate)

    for {
        inboundRTPPacket := make([]byte, 1500) // UDP MTU
        packet := &rtp.Packet{}

        n, _, err := listener.ReadFrom(inboundRTPPacket)
        if err != nil {
            panic(fmt.Sprintf("error during read: %s", err))
        }

        if err = packet.Unmarshal(inboundRTPPacket[:n]); err != nil {
            panic(err)
        }

        sampleBuffer.Push(packet)
        for {
            sample := sampleBuffer.Pop()
            if sample == nil {
                break
            }

            if writeErr := track.WriteSample(*sample); writeErr != nil {
                panic(writeErr)
            }
        }
    }
}

And my sample javascript looks like this:

const setupRTMP = () => {
    const configuration = {
      iceServers: [
        { urls: "stun:stun.l.google.com:19302" },
        { urls: "stun:stun.1.google.com:19302" },
        //Prod
        {
          urls: "turn:34.243.91.76:3478",
          username: "XXXXXXX",
          credential: "XXX",
        },
        // Staging
        {
          urls: "turn:3.249.123.75:3478",
          username: "XXXXXXX",
          credential: "XXX",
        },
      ],
    };

    let pc = new RTCPeerConnection(configuration);
    pc.ontrack = function (event) {
      console.error(event);
      let el = document.getElementById("video-display");
      el.srcObject = event.streams[0];
      el.autoplay = true;
      el.controls = true;

    };

    pc.addTransceiver('video')
    pc.addTransceiver('audio')
    pc.createOffer()
      .then((offer) => {
        pc.setLocalDescription(offer);

        return fetch(
          "http://X.X.X.X:8080/createPeerConnection",
          {
            method: "post",
            headers: {
              Accept: "application/json, text/plain, */*",
              "Content-Type": "application/json",
            },
            body: JSON.stringify(offer),
          }
        );
      })
      .then((res) => res.json())
      .then((res) => pc.setRemoteDescription(res))
      .catch((err) => console.log(err));
};

setupRTMP();

I tried streaming using these two commands:

ffmpeg -re -i <rtmp_server> -vn -acodec libopus -f rtp rtp://<go_server>:5006 -vcodec copy -an -f rtp rtp://<go_server>:5004 -sdp_file video.sdp
ffmpeg -i <rtmp_server> -an -vcodec libvpx -cpu-used 5 -deadline 1 -g 10 -error-resilient 1 -auto-alt-ref 1 -f rtp rtp://<go_server>:5004 -vn -c:a libopus -f rtp rtp:/<go_server>:5006

When analyzing the webrtc internals, I can see that the connection is established, and I know the video is getting to the server and is being written to the tracks. Can anyone find the problem here? Thank you

EDIT

How to run the code

  1. Clone this repo
  2. Run python3 -m http.server
  3. Clone this repo
  4. Replace the CHANGEME by your private IP (hostname -I on Linux) on main.go
  5. Run go run main.go
  6. Run ffmpeg -re -i <someMediaFileOnYourComputer> -vn -acodec libopus -f rtp rtp://<go_server>:5006 -vcodec copy -an -f rtp rtp://<go_server>:5004 -sdp_file video.sdp
  7. The video/image streamed should appear on the website, but it doesn't


Solution 1:[1]

Your solution is use FFmpeg to covert RTMP to RTP, then covert RTP to WebRTC, that is too complex. The more simple and straight forward solution is use a media server to covert RTMP to WebRTC.

SRS supports coverting RTMP to WebRTC, or vice versa, please read RTMP to RTC.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Winlin