package webrtc import ( "bytes" "encoding/json" "errors" "net/http" "net/url" "strings" "github.com/pion/webrtc/v4" "m7s.live/v5" ) type ( CFClient struct { Connection pullCtx m7s.PullJob pushCtx m7s.PushJob direction string ApiBase string sessionId string } SessionCreateResponse struct { SessionId string `json:"sessionId"` webrtc.SessionDescription `json:"sessionDescription"` } TrackInfo struct { Location string `json:"location"` TrackName string `json:"trackName"` SessionId string `json:"sessionId"` } TrackRequest struct { Tracks []TrackInfo `json:"tracks"` } NewTrackResponse struct { webrtc.SessionDescription `json:"sessionDescription"` Tracks []TrackInfo `json:"tracks"` RequiresImmediateRenegotiation bool `json:"requiresImmediateRenegotiation"` } RenegotiateResponse struct { ErrorCode int `json:"errorCode"` ErrorDescription string `json:"errorDescription"` } SDPBody struct { *webrtc.SessionDescription `json:"sessionDescription"` } ) func NewCFClient(direction string) *CFClient { return &CFClient{ direction: direction, } } func (c *CFClient) Start() (err error) { if c.direction == DIRECTION_PULL { err = c.pullCtx.Publish() if err != nil { return } c.Publisher = c.pullCtx.Publisher u, _ := url.Parse(c.pullCtx.RemoteURL) c.ApiBase, _, _ = strings.Cut(c.pullCtx.RemoteURL, "?") c.Receive() var transeiver *webrtc.RTPTransceiver transeiver, err = c.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo, webrtc.RTPTransceiverInit{ Direction: webrtc.RTPTransceiverDirectionRecvonly, }) if err != nil { return } c.Info("webrtc add transceiver", "transceiver", transeiver.Mid()) var sdpBody SDPBody sdpBody.SessionDescription, err = c.GetOffer() if err != nil { return } var result SessionCreateResponse err = c.request("new", sdpBody, &result) if err != nil { return } err = c.SetRemoteDescription(result.SessionDescription) if err != nil { return } c.sessionId = result.SessionId var result2 NewTrackResponse err = c.request("tracks/new", TrackRequest{[]TrackInfo{{ Location: "remote", TrackName: c.Publisher.StreamPath, SessionId: u.Query().Get("sessionId"), }}}, &result2) if err != nil { return } c.Info("cloudflare pull success", "result", result2) if result2.RequiresImmediateRenegotiation { err = c.PeerConnection.SetRemoteDescription(result2.SessionDescription) if err != nil { return } var renegotiate SDPBody renegotiate.SessionDescription, err = c.GetAnswer() if err != nil { return } var result RenegotiateResponse err = c.request("renegotiate", renegotiate, &result) if err != nil { return err } c.Info("cloudflare renegotiate", "result", result) } } return } func (c *CFClient) request(href string, body any, result any) (err error) { var req *http.Request var res *http.Response var bodyBytes []byte method := "POST" if href == "renegotiate" { method = "PUT" } bodyBytes, err = json.Marshal(body) if c.sessionId != "" { href = c.sessionId + "/" + href } href = c.ApiBase + "/sessions/" + href c.Debug("cloudflare request", "url", href, "body", string(bodyBytes)) req, err = http.NewRequestWithContext(c.Context, method, href, bytes.NewBuffer(bodyBytes)) if err != nil { return } for k, v := range c.pullCtx.Header { for _, v := range v { req.Header.Add(k, v) } } req.Header.Set("Content-Type", "application/json") res, err = c.pullCtx.HTTPClient.Do(req) if err != nil { return } if res.StatusCode >= 400 { err = errors.New("http status code " + res.Status) return } err = json.NewDecoder(res.Body).Decode(&result) return } func (c *CFClient) GetPullJob() *m7s.PullJob { return &c.pullCtx } func (c *CFClient) GetPushJob() *m7s.PushJob { return &c.pushCtx }