处理请求体
func handlePublishRequest(request map[string]interface{}) error {
var meetingId, userId int
var meetingIdF, userIdF float64
var jsep map[string]interface{}
var sdp string
var ok bool
if userIdF, ok = request["userId"].(float64); !ok {
return errors.New("parameter \"userId\" must be integer")
}
userId = int(userIdF)
if meetingIdF, ok = request["meetingId"].(float64); !ok {
return errors.New("parameter \"meetingId\" must be integer")
}
meetingId = int(meetingIdF)
if jsep, ok = request["jsep"].(map[string]interface{}); !ok {
return errors.New("parameter \"jsep\" must be map[string]interface{}")
}
if sdp, ok = jsep["sdp"].(string); !ok {
return errors.New("parameter \"sdp\" must be string")
}
meeting := meetingList.GetMeetingById(meetingId)
if meeting == nil {
return errors.New("meeting is not found")
}
user := meeting.GetUserById(userId)
if user == nil {
return errors.New("user is not found")
}
offer := webrtc.SessionDescription{
Type: webrtc.SDPTypeOffer,
SDP: sdp,
}
meeting.AddPublisherById(userId)
answer, err := meeting.AnswerPublisher(userId, offer)
if err != nil {
fmt.Println(err)
return errors.New("fail to answer publisher")
}
response := make(map[string]interface{})
response["meetingId"] = meetingId
response["pubId"] = userId
response["jsep"] = answer
meeting.Broadcast(ResponsePublishSuccess, response)
return nil
}
当 publish 请求到来时,先解析请求体,获取userId
、meetingId
和 SDP 信息。
在会议列表中查询meetingId
,检测会议号的有效性。如果该会议确实存在,再检查该会议中是否有用户 id 为userId
的用户。
SDP 用于在数据传输时两端都能够理解彼此的数据,在这里利用客户端的 SDP 生成对应的服务端 SDP。
最后,将该用户发布媒体流的消息发给会议内的所有用户,便于他们订阅。
响应发布者
func (m *Meeting) AnswerPublisher(id int, offer webrtc.SessionDescription) (
webrtc.SessionDescription, error) {
p := m.GetPublisherById(id)
return p.AnswerPublisher(offer)
}
func (p *WebRTCPeer) AnswerPublisher(offer webrtc.SessionDescription) (
answer webrtc.SessionDescription, err error) {
return engine.CreateMediaStreamReceiver(offer, &p.PC,
&p.VideoTrack, &p.AudioTrack, p.stop, p.pli)
}
在这里将消息层层传递,传到引擎层。
接收媒体流
func (engine WebRTCEngine) CreateMediaStreamReceiver(offer webrtc.SessionDescription,
pc **webrtc.PeerConnection, videoTrack, audioTrack **webrtc.Track,
stop chan int, pli chan int) (answer webrtc.SessionDescription, err error) {
*pc, err = engine.api.NewPeerConnection(engine.config)
if err != nil {
return webrtc.SessionDescription{}, err
}
_, err = (*pc).AddTransceiverFromKind(webrtc.RTPCodecTypeVideo)
if err != nil {
return webrtc.SessionDescription{}, err
}
_, err = (*pc).AddTransceiverFromKind(webrtc.RTPCodecTypeAudio)
if err != nil {
return webrtc.SessionDescription{}, err
}
(*pc).OnTrack(func(track *webrtc.Track, receiver *webrtc.RTPReceiver) {
switch track.PayloadType() {
case webrtc.DefaultPayloadTypeVP8:
*videoTrack, err = (*pc).NewTrack(track.PayloadType(),
track.SSRC(), "video", track.Label())
go func() {
for {
select {
case <-pli:
_ = (*pc).WriteRTCP([]rtcp.Packet{
&rtcp.PictureLossIndication{MediaSSRC: track.SSRC()}})
case <-stop:
return
}
}
}()
builder := samplebuilder.New(7*5, &codecs.VP8Packet{})
for {
select {
case <-stop:
return
default:
r, err := track.ReadRTP()
if err != nil {
if err == io.EOF {
return
}
global.Logger.Error(err.Error())
}
builder.Push(r)
for s := builder.Pop(); s != nil; s = builder.Pop() {
if err = (*videoTrack).WriteSample(*s);
err != nil && err != io.ErrClosedPipe {
global.Logger.Error(err.Error())
}
}
}
}
case webrtc.DefaultPayloadTypeOpus:
*audioTrack, err = (*pc).NewTrack(track.PayloadType(),
track.SSRC(), "audio", track.Label())
rtpBuf := make([]byte, 1400)
for {
select {
case <-stop:
return
default:
i, err := track.Read(rtpBuf)
if err == nil {
_, _ = (*audioTrack).Write(rtpBuf[:i])
} else {
global.Logger.Error(err.Error())
}
}
}
default:
global.Logger.Error("This codec is not supported by the system")
}
})
err = (*pc).SetRemoteDescription(offer)
if err != nil {
return webrtc.SessionDescription{}, err
}
answer, err = (*pc).CreateAnswer(nil)
if err != nil {
return webrtc.SessionDescription{}, err
}
err = (*pc).SetLocalDescription(answer)
if err != nil {
return webrtc.SessionDescription{}, err
}
return answer, nil
先创建一个PeerConnection
对象,并在其中添加视频 RTC 收发器和音频 RTC 收发器。
设置一个事件处理函数,当远程 track 从远端 peer 到达时调用。在这个函数中,它会根据 track 的类型进行不同的解析方式。
然后设置远端 peer 和本地 peer 的 SDP 信息。