236 lines
4.9 KiB
Go
236 lines
4.9 KiB
Go
package sfu
|
||
|
||
import (
|
||
connection "g365sfu/socket/struct"
|
||
"sync"
|
||
|
||
"github.com/pion/webrtc/v4"
|
||
)
|
||
|
||
// Структуры для управления комнатами и пирами в SFU
|
||
|
||
type Peer struct {
|
||
//Идентификатор пира, который будет использоваться для связи с ним
|
||
PeerID string
|
||
//Подсоединенный пир
|
||
PeerConnection *webrtc.PeerConnection
|
||
}
|
||
|
||
type RoomTrack struct {
|
||
TrackID string
|
||
OwnerPeer string
|
||
Local *webrtc.TrackLocalStaticRTP
|
||
}
|
||
|
||
type Room struct {
|
||
//Уникальный идентификатор комнаты
|
||
RoomID string
|
||
//Сервер который создал комнату
|
||
Server *connection.Connection
|
||
//Пиры которые подключились к комнате
|
||
Peers []Peer
|
||
|
||
Tracks []RoomTrack
|
||
|
||
mu sync.RWMutex
|
||
}
|
||
|
||
// Общие переменные
|
||
var (
|
||
rooms = make(map[string]*Room)
|
||
roomsMu sync.RWMutex
|
||
)
|
||
|
||
// CreateRoom создает комнату
|
||
func CreateRoom(server *connection.Connection, roomID string) (*Room, error) {
|
||
roomsMu.Lock()
|
||
defer roomsMu.Unlock()
|
||
|
||
room := &Room{
|
||
RoomID: roomID,
|
||
Server: server,
|
||
Peers: []Peer{},
|
||
}
|
||
rooms[roomID] = room
|
||
|
||
return room, nil
|
||
}
|
||
|
||
// GetRoom получает комнату по идентификатору
|
||
func GetRoom(roomID string) (*Room, bool) {
|
||
roomsMu.RLock()
|
||
defer roomsMu.RUnlock()
|
||
room, exists := rooms[roomID]
|
||
return room, exists
|
||
}
|
||
|
||
// JoinWithOffer позволяет пиру присоединиться к комнате с помощью SDP оффера
|
||
func JoinWithOffer(roomID string, peerID string, offer webrtc.SessionDescription) (*webrtc.SessionDescription, error) {
|
||
room, exists := GetRoom(roomID)
|
||
if !exists {
|
||
return nil, ErrRoomNotFound
|
||
}
|
||
|
||
peerConnection, err := api.NewPeerConnection(pcConfig)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
peerConnection.OnICECandidate(func(c *webrtc.ICECandidate) {
|
||
if c == nil {
|
||
return
|
||
}
|
||
if OnLocalICECandidate != nil {
|
||
OnLocalICECandidate(roomID, peerID, c.ToJSON())
|
||
}
|
||
})
|
||
|
||
BindPeerLifecycle(roomID, peerID, peerConnection)
|
||
SetupForwardingForPeer(roomID, peerID, peerConnection)
|
||
|
||
room.mu.RLock()
|
||
existingTracks := make([]RoomTrack, len(room.Tracks))
|
||
copy(existingTracks, room.Tracks)
|
||
room.mu.RUnlock()
|
||
|
||
for _, t := range existingTracks {
|
||
if t.OwnerPeer == peerID {
|
||
continue
|
||
}
|
||
|
||
sender, err := peerConnection.AddTrack(t.Local)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
|
||
go func() {
|
||
buf := make([]byte, 1500)
|
||
for {
|
||
if _, _, e := sender.Read(buf); e != nil {
|
||
return
|
||
}
|
||
}
|
||
}()
|
||
}
|
||
|
||
if err = peerConnection.SetRemoteDescription(offer); err != nil {
|
||
_ = peerConnection.Close()
|
||
return nil, err
|
||
}
|
||
|
||
answer, err := peerConnection.CreateAnswer(nil)
|
||
if err != nil {
|
||
_ = peerConnection.Close()
|
||
return nil, err
|
||
}
|
||
|
||
gatherDone := webrtc.GatheringCompletePromise(peerConnection)
|
||
if err = peerConnection.SetLocalDescription(answer); err != nil {
|
||
_ = peerConnection.Close()
|
||
return nil, err
|
||
}
|
||
<-gatherDone
|
||
|
||
room.mu.Lock()
|
||
room.Peers = append(room.Peers, Peer{
|
||
PeerID: peerID,
|
||
PeerConnection: peerConnection,
|
||
})
|
||
room.mu.Unlock()
|
||
|
||
return peerConnection.LocalDescription(), nil
|
||
}
|
||
|
||
func DeleteRoom(roomID string) error {
|
||
roomsMu.Lock()
|
||
room, exists := rooms[roomID]
|
||
server := room.Server
|
||
if !exists {
|
||
roomsMu.Unlock()
|
||
return ErrRoomNotFound
|
||
}
|
||
delete(rooms, roomID)
|
||
roomsMu.Unlock()
|
||
|
||
room.mu.Lock()
|
||
peers := make([]Peer, len(room.Peers))
|
||
copy(peers, room.Peers)
|
||
room.Peers = nil
|
||
room.Tracks = nil
|
||
room.mu.Unlock()
|
||
|
||
for _, p := range peers {
|
||
_ = p.PeerConnection.Close()
|
||
cleanupForwardingState(roomID, p.PeerID)
|
||
}
|
||
|
||
OnRoomDelete(roomID, server)
|
||
|
||
return nil
|
||
}
|
||
|
||
// LeaveRoom позволяет пиру покинуть комнату
|
||
func LeaveRoom(roomID string, peerID string) error {
|
||
room, exists := GetRoom(roomID)
|
||
if !exists {
|
||
return ErrRoomNotFound
|
||
}
|
||
|
||
var (
|
||
removedPC *webrtc.PeerConnection
|
||
removed bool
|
||
shouldDrop bool
|
||
)
|
||
|
||
room.mu.Lock()
|
||
// удаляем peer
|
||
nextPeers := make([]Peer, 0, len(room.Peers))
|
||
for _, p := range room.Peers {
|
||
if p.PeerID == peerID {
|
||
removedPC = p.PeerConnection
|
||
removed = true
|
||
continue
|
||
}
|
||
nextPeers = append(nextPeers, p)
|
||
}
|
||
room.Peers = nextPeers
|
||
|
||
// удаляем треки этого publisher
|
||
nextTracks := room.Tracks[:0]
|
||
for _, t := range room.Tracks {
|
||
if t.OwnerPeer != peerID {
|
||
nextTracks = append(nextTracks, t)
|
||
}
|
||
}
|
||
room.Tracks = nextTracks
|
||
|
||
shouldDrop = len(room.Peers) == 0
|
||
room.mu.Unlock()
|
||
|
||
if !removed {
|
||
return ErrPeerNotFound
|
||
}
|
||
|
||
if removedPC != nil {
|
||
_ = removedPC.Close()
|
||
}
|
||
cleanupForwardingState(roomID, peerID)
|
||
|
||
// Комната пустая -> удаляем
|
||
if shouldDrop {
|
||
return DeleteRoom(roomID)
|
||
}
|
||
|
||
// renegotiation оставшимся peer после удаления треков/peer
|
||
room.mu.RLock()
|
||
rest := make([]Peer, len(room.Peers))
|
||
copy(rest, room.Peers)
|
||
room.mu.RUnlock()
|
||
|
||
for _, p := range rest {
|
||
_ = renegotiatePeer(roomID, p.PeerID, p.PeerConnection)
|
||
}
|
||
|
||
return nil
|
||
}
|