commit f85d36b1833312a6f013568c7686b987a7cc18ea Author: Smile Rex Date: Mon Jan 26 15:18:05 2026 +0300 add files and ci diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..7f070d4 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,46 @@ +name: Create and publish a Docker image 🚀 + +on: + push: + branches: + - main + +env: + REGISTRY: ${{ secrets.REGISTRY }} + IMAGE_NAME: ${{ gitea.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ gitea.actor }} + password: ${{ secrets.TOKEN }} + + - name: Extract metadata (tags, labels) for + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Debug variables + run: | + echo "REGISTRY: ${{ env.REGISTRY }}" + echo "IMAGE_NAME: ${{ env.IMAGE_NAME }}" + echo "Full image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..59503ab --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM golang:alpine AS builder +WORKDIR /app +COPY . . +RUN go build -o main . + +FROM alpine:latest +WORKDIR /app +COPY --from=builder /app/main /app/main +EXPOSE 8080 +CMD ["/app/main"] diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bfba74b --- /dev/null +++ b/go.mod @@ -0,0 +1,32 @@ +module sfu + +go 1.25.0 + +require ( + github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.3 + github.com/pion/webrtc/v4 v4.2.3 +) + +require ( + github.com/pion/datachannel v1.6.0 // indirect + github.com/pion/dtls/v3 v3.0.10 // indirect + github.com/pion/ice/v4 v4.2.0 // indirect + github.com/pion/interceptor v0.1.43 // indirect + github.com/pion/logging v0.2.4 // indirect + github.com/pion/mdns/v2 v2.1.0 // indirect + github.com/pion/randutil v0.1.0 // indirect + github.com/pion/rtcp v1.2.16 // indirect + github.com/pion/rtp v1.10.0 // indirect + github.com/pion/sctp v1.9.2 // indirect + github.com/pion/sdp/v3 v3.0.17 // indirect + github.com/pion/srtp/v3 v3.0.10 // indirect + github.com/pion/stun/v3 v3.1.1 // indirect + github.com/pion/transport/v4 v4.0.1 // indirect + github.com/pion/turn/v4 v4.1.4 // indirect + github.com/wlynxg/anet v0.0.5 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/time v0.10.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..58235d3 --- /dev/null +++ b/go.sum @@ -0,0 +1,56 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0= +github.com/pion/datachannel v1.6.0/go.mod h1:ur+wzYF8mWdC+Mkis5Thosk+u/VOL287apDNEbFpsIk= +github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg= +github.com/pion/dtls/v3 v3.0.10/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8= +github.com/pion/ice/v4 v4.2.0 h1:jJC8S+CvXCCvIQUgx+oNZnoUpt6zwc34FhjWwCU4nlw= +github.com/pion/ice/v4 v4.2.0/go.mod h1:EgjBGxDgmd8xB0OkYEVFlzQuEI7kWSCFu+mULqaisy4= +github.com/pion/interceptor v0.1.43 h1:6hmRfnmjogSs300xfkR0JxYFZ9k5blTEvCD7wxEDuNQ= +github.com/pion/interceptor v0.1.43/go.mod h1:BSiC1qKIJt1XVr3l3xQ2GEmCFStk9tx8fwtCZxxgR7M= +github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= +github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= +github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= +github.com/pion/mdns/v2 v2.1.0/go.mod h1:pcez23GdynwcfRU1977qKU0mDxSeucttSHbCSfFOd9A= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo= +github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo= +github.com/pion/rtp v1.10.0 h1:XN/xca4ho6ZEcijpdF2VGFbwuHUfiIMf3ew8eAAE43w= +github.com/pion/rtp v1.10.0/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= +github.com/pion/sctp v1.9.2 h1:HxsOzEV9pWoeggv7T5kewVkstFNcGvhMPx0GvUOUQXo= +github.com/pion/sctp v1.9.2/go.mod h1:OTOlsQ5EDQ6mQ0z4MUGXt2CgQmKyafBEXhUVqLRB6G8= +github.com/pion/sdp/v3 v3.0.17 h1:9SfLAW/fF1XC8yRqQ3iWGzxkySxup4k4V7yN8Fs8nuo= +github.com/pion/sdp/v3 v3.0.17/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo= +github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ= +github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M= +github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= +github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= +github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= +github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= +github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= +github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM= +github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ= +github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ= +github.com/pion/webrtc/v4 v4.2.3 h1:RtdWDnkenNQGxUrZqWa5gSkTm5ncsLg5d+zu0M4cXt4= +github.com/pion/webrtc/v4 v4.2.3/go.mod h1:7vsyFzRzaKP5IELUnj8zLcglPyIT6wWwqTppBZ1k6Kc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= +github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..606c0d1 --- /dev/null +++ b/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "log" + "net/http" + + "github.com/gorilla/websocket" +) + +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { return true }, +} + +var room = NewRoom() + +func wsHandler(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + return + } + + peer := NewPeer(conn) + room.AddPeer(peer) + log.Println("peer added", conn.LocalAddr().String()) + + go peer.ReadLoop() +} + +func main() { + http.HandleFunc("/ws", wsHandler) + + log.Println("listening on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} diff --git a/peer.go b/peer.go new file mode 100644 index 0000000..fa82a0d --- /dev/null +++ b/peer.go @@ -0,0 +1,100 @@ +package main + +import ( + "encoding/json" + + "github.com/google/uuid" + "github.com/gorilla/websocket" + "github.com/pion/webrtc/v4" +) + +type Peer struct { + ID string + Conn *websocket.Conn + PC *webrtc.PeerConnection + Room *Room + Senders map[string]*webrtc.RTPSender +} + +type Signal struct { + Type string `json:"type"` + Data json.RawMessage `json:"data"` +} + +func NewPeer(conn *websocket.Conn) *Peer { + pc, _ := webrtc.NewPeerConnection(webrtc.Configuration{}) + + p := &Peer{ + ID: uuid.NewString(), + Conn: conn, + PC: pc, + Senders: make(map[string]*webrtc.RTPSender), + } + + pc.OnTrack(func(track *webrtc.TrackRemote, _ *webrtc.RTPReceiver) { + localTrack := NewTrack(track) + + p.Room.ForwardTrack(p, localTrack) + + for { + pkt, _, err := track.ReadRTP() + if err != nil { + return + } + localTrack.Write(pkt) + } + }) + + pc.OnICECandidate(func(c *webrtc.ICECandidate) { + if c == nil { + return + } + msg, _ := json.Marshal(c.ToJSON()) + p.Send("ice", msg) + }) + + return p +} + +func (p *Peer) AddTrack(track *Track) { + _, err := p.PC.AddTrack(track.Local) + if err != nil { + return + } + + p.Send("renegotiate", nil) +} + +func (p *Peer) ReadLoop() { + defer p.Conn.Close() + + for { + var msg Signal + if err := p.Conn.ReadJSON(&msg); err != nil { + return + } + + switch msg.Type { + case "offer": + var offer webrtc.SessionDescription + json.Unmarshal(msg.Data, &offer) + + p.PC.SetRemoteDescription(offer) + answer, _ := p.PC.CreateAnswer(nil) + p.PC.SetLocalDescription(answer) + + data, _ := json.Marshal(answer) + p.Send("answer", data) + + case "ice": + var c webrtc.ICECandidateInit + json.Unmarshal(msg.Data, &c) + p.PC.AddICECandidate(c) + } + } +} + +func (p *Peer) Send(t string, data []byte) { + msg := Signal{Type: t, Data: data} + p.Conn.WriteJSON(msg) +} diff --git a/room.go b/room.go new file mode 100644 index 0000000..ea92b66 --- /dev/null +++ b/room.go @@ -0,0 +1,41 @@ +package main + +import "sync" + +type Room struct { + mu sync.Mutex + peers map[string]*Peer +} + +func NewRoom() *Room { + return &Room{ + peers: make(map[string]*Peer), + } +} + +func (r *Room) AddPeer(p *Peer) { + r.mu.Lock() + defer r.mu.Unlock() + + r.peers[p.ID] = p + p.Room = r +} + +func (r *Room) RemovePeer(id string) { + r.mu.Lock() + defer r.mu.Unlock() + + delete(r.peers, id) +} + +func (r *Room) ForwardTrack(from *Peer, track *Track) { + r.mu.Lock() + defer r.mu.Unlock() + + for _, peer := range r.peers { + if peer.ID == from.ID { + continue + } + peer.AddTrack(track) + } +} diff --git a/track.go b/track.go new file mode 100644 index 0000000..60c3e83 --- /dev/null +++ b/track.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/pion/rtp" + "github.com/pion/webrtc/v4" +) + +type Track struct { + Local *webrtc.TrackLocalStaticRTP +} + +func NewTrack(remote *webrtc.TrackRemote) *Track { + local, _ := webrtc.NewTrackLocalStaticRTP( + remote.Codec().RTPCodecCapability, + remote.ID(), + remote.StreamID(), + ) + return &Track{Local: local} +} + +func (t *Track) Write(pkt *rtp.Packet) { + t.Local.WriteRTP(pkt) +}