webrtc: Track is not sent back to the sender

Your environment.

  • Version: 79d29886d9dd508badb0610bf687ff2f78be25bf
  • Browser: Chrome 73.0.3683.86 (Build officiel) (64 bits)
  • OS: Linux, Fedora 29

What did you do?

I made a simple echo server that receives webcam video and send it back on a local page. Server and client are on the same computer. It works with a simple aiortc (python) server. I only reimplement the server with gin-gonic and pions/webrtc.

What did you expect?

I’m expecting the same result: the client sends webcam, and should receive back the track to be read in video element

What happened?

There is no error, but I never get the track back.

This is the server part:

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/gin-contrib/static"
	"github.com/gin-gonic/gin"
	"github.com/pions/rtcp"
	"github.com/pions/webrtc"
)

func manage(offer webrtc.SessionDescription, answer chan<- *webrtc.SessionDescription) {
	pc, err := webrtc.NewPeerConnection(webrtc.Configuration{})
	if err != nil {
		log.Panic(err)
	}

	pc.OnTrack(func(t *webrtc.Track, receiver *webrtc.RTPReceiver) {
		go func() {
			ticker := time.NewTicker(time.Second * 3)
			for range ticker.C {
				errSend := pc.SendRTCP(&rtcp.PictureLossIndication{MediaSSRC: t.SSRC()})
				if errSend != nil {
					fmt.Println(errSend)
				}
			}
		}()

		log.Println("Track acquired", t.Kind(), t.Codec())
		pc.AddTrack(t)

		log.Println("Track added")
	})

	pc.OnICEConnectionStateChange(func(cs webrtc.ICEConnectionState) {
		log.Println("Ice connection changed to ", cs.String())
		if cs == webrtc.ICEConnectionStateFailed {
			log.Println("Closing peer connection as ICE connection failed")
			pc.Close()
		}
	})

	if err := pc.SetRemoteDescription(offer); err != nil {
		log.Panic("Set remote description failed:: ", err)
	}

	if a, err := pc.CreateAnswer(nil); err != nil {
		log.Panic(err)
	} else {
		pc.SetLocalDescription(a)
		answer <- pc.LocalDescription()
	}

	select {} // I do it for test, commenting this line does nothing
}

// Offer get client offer information and give back an answer.
func Offer(c *gin.Context) {

	offer := webrtc.SessionDescription{}
	c.BindJSON(&offer)

	log.Println("Get offer", offer.Type)

	answer := make(chan *webrtc.SessionDescription)
	go manage(offer, answer)

	c.JSON(200, <-answer)
}

func main() {
	r := gin.Default()
	r.Use(static.Serve("/", static.LocalFile("./statics", false)))
	r.POST("/offer", Offer)
	r.Run(":8080")
}

The JS file - take a look on the console.log("TRACK") that is working with aiortc but not with pions/webrtc:

function E(selector) {
    return document.querySelector(selector);
}

function createPeerConnection() {
    // create a connection and set received track to the video element
    let config = {
        sdpSemantics: 'unified-plan'
    };

    let pc = new RTCPeerConnection(config);
    pc.addEventListener('track', (evt) => {
        // I never get this log with pions/webrtc, it works with aiortc python package:
        console.log("TRACK");
        if (evt.track.kind === "video") {
            E("#track").srcObject = evt.streams[0];
        }
    });
    return pc
}

function start() {
    let pc = createPeerConnection();

    // when captured webcam, add the streams to the peer connection to be sent
    // to the server
    navigator.mediaDevices.getUserMedia({
        audio: false,
        video: true,
    }).then(stream => {
        console.log("Add track");
        stream.getTracks().forEach(track => {
            pc.addTrack(track, stream)
        });
        // we can now try to negociate
        // connection on server
        return negociate(pc);
    }, (err) => {
        console.error(err);
    });
}

function negociate(pc) {
    // create an offer...
    pc.createOffer().then((offer) => {
        // .. then add offer to local description
        return pc.setLocalDescription(offer);
    }).then(() => {

        // we need to wait that ICE Gathering is complete before to send offer to
        // our server.
        return new Promise(resolve => {
            // add event listener on ice gathering state only if it's not complete.
            if (pc.iceGatheringState === 'complete') {
                resolve();
            } else {
                let waitComplete = () => {
                    if (pc.iceGatheringState === 'complete') {
                        pc.removeEventListener('icegatheringstatechange', waitComplete);
                        resolve();
                    }
                }
                pc.addEventListener('icegatheringstatechange', waitComplete);
            }
        });
    }).then(() => {
        // now that Ice Gathering is complete, we can send the offer
        // to our live streaming server.
        let offer = pc.localDescription;

        // "fetch()" is wonderful, really !
        return fetch('/offer', {
            method: 'POST',
            body: JSON.stringify({
                sdp: offer.sdp,
                type: offer.type,
            }),
            headers: {
                'Content-Type': 'application/json',
            }
        });
    }).then(response => {
        return response.json(); // this is a Promise
    }).then(answer => {
        // we received an anwser (resolved fetch response) that is
        // the sdp answer from server. The negociation seems to be OK, so
        // we add it to our RTCPeerConnection object as "remote description".
        return pc.setRemoteDescription(answer);
    }).catch(error => {
        console.error("Error in negociation", error);
    });
}

Actualy, I wonder if pc.AddTrack() is working as expected, in Python aiortc I do:

async def offer(request):
    params = await request.json()
    offer = RTCSessionDescription(
        sdp=params['sdp'],
        type=params['type']
    )

    pc = RTCPeerConnection()
    pc_id = 'PeerConnection(%s)' % uuid.uuid4()
    pcs.add(pc)
    print('New client', pc_id, request.remote)

    @pc.on('iceconnectionstatechange')
    async def on_ice_change():
        if pc.iceConnectionState == 'failed':
            await pc.close()
            pcs.discard(pc)

    @pc.on('track')
    def on_track(track):
        if track.kind == "video":
            print("Receive track", track.kind)
            pc.addTrack(track)

        @track.on('ended')
        async def ended():
            print("Tracked ended...")

    await pc.setRemoteDescription(offer)

    answer = await pc.createAnswer()
    await pc.setLocalDescription(answer)

    return web.Response(
        content_type='application/json',
        text=json.dumps({
            'sdp': pc.localDescription.sdp,
            'type': pc.localDescription.type,
        })
    )

This Python function does the job. It is more or less the same chaining process than those made in Golang.

At this time there is no “error”, offer and answer or passed and the only one code part that is not working is on('track') that is never called.

So, is there something I does wrong, or is it a bug in pions/webrtc ?

Thanks a lot for your help (and for your great job)

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 19 (9 by maintainers)

Commits related to this issue

Most upvoted comments

One more thing, maybe my “example” that you fixed can be nice to be shared to the community. This is a nice “hello world” example to start using Pion