2025-12-15 09:04:14 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* package mediaserver
|
|
|
|
|
|
*
|
|
|
|
|
|
* 媒体服务器主入口
|
|
|
|
|
|
* 功能:
|
|
|
|
|
|
* 1. 集成 WebRTC SFU(支持 H5/App 端音视频通话)
|
|
|
|
|
|
* 2. 集成 RTMP 服务(支持微信小程序 live-pusher/live-player)
|
|
|
|
|
|
* 3. 实现 WebRTC ↔ RTMP 媒体桥接(跨平台互通)
|
|
|
|
|
|
*/
|
|
|
|
|
|
package mediaserver
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"log"
|
|
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// MediaServer 媒体服务器主结构
|
|
|
|
|
|
type MediaServer struct {
|
2025-12-15 21:57:21 +08:00
|
|
|
|
sfu *SFUServer // WebRTC SFU 服务
|
|
|
|
|
|
rtmp *RTMPServer // RTMP 服务
|
|
|
|
|
|
wsProxy *WebMToRTMPProxy // WebSocket to RTMP 代理
|
|
|
|
|
|
rooms map[string]*Room // 通话房间管理
|
|
|
|
|
|
mu sync.RWMutex // 房间锁
|
|
|
|
|
|
config *MediaServerConfig // 配置
|
2025-12-15 09:04:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MediaServerConfig 媒体服务器配置
|
|
|
|
|
|
type MediaServerConfig struct {
|
|
|
|
|
|
Enabled bool `mapstructure:"enabled"`
|
|
|
|
|
|
PublicIP string `mapstructure:"public_ip"`
|
|
|
|
|
|
WebRTCPort int `mapstructure:"webrtc_port"`
|
|
|
|
|
|
RTMPPort int `mapstructure:"rtmp_port"`
|
|
|
|
|
|
HTTPFLVPort int `mapstructure:"http_flv_port"`
|
|
|
|
|
|
// SFU 配置
|
|
|
|
|
|
MaxRoomSize int `mapstructure:"max_room_size"` // 单房间最大人数
|
|
|
|
|
|
// 编解码配置
|
|
|
|
|
|
VideoCodec string `mapstructure:"video_codec"` // 视频编解码:h264
|
|
|
|
|
|
AudioCodec string `mapstructure:"audio_codec"` // 音频编解码:opus/aac
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 全局媒体服务器实例
|
|
|
|
|
|
var (
|
|
|
|
|
|
server *MediaServer
|
|
|
|
|
|
once sync.Once
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// GetServer 获取媒体服务器单例
|
|
|
|
|
|
func GetServer() *MediaServer {
|
|
|
|
|
|
once.Do(func() {
|
|
|
|
|
|
server = newMediaServer()
|
|
|
|
|
|
})
|
|
|
|
|
|
return server
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// newMediaServer 创建媒体服务器实例
|
|
|
|
|
|
func newMediaServer() *MediaServer {
|
|
|
|
|
|
config := loadConfig()
|
|
|
|
|
|
|
|
|
|
|
|
ms := &MediaServer{
|
|
|
|
|
|
rooms: make(map[string]*Room),
|
|
|
|
|
|
config: config,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if config.Enabled {
|
|
|
|
|
|
ms.sfu = NewSFUServer(config)
|
|
|
|
|
|
ms.rtmp = NewRTMPServer(config)
|
2025-12-15 21:57:21 +08:00
|
|
|
|
// 创建 WebSocket to RTMP 代理
|
|
|
|
|
|
ms.wsProxy = NewWebMToRTMPProxy(ms.rtmp)
|
2025-12-15 09:04:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ms
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// loadConfig 从 viper 加载配置
|
|
|
|
|
|
func loadConfig() *MediaServerConfig {
|
|
|
|
|
|
config := &MediaServerConfig{
|
|
|
|
|
|
Enabled: viper.GetBool("media.enabled"),
|
|
|
|
|
|
PublicIP: viper.GetString("media.public_ip"),
|
|
|
|
|
|
WebRTCPort: viper.GetInt("media.webrtc_port"),
|
|
|
|
|
|
RTMPPort: viper.GetInt("media.rtmp_port"),
|
|
|
|
|
|
HTTPFLVPort: viper.GetInt("media.http_flv_port"),
|
|
|
|
|
|
MaxRoomSize: viper.GetInt("media.max_room_size"),
|
|
|
|
|
|
VideoCodec: viper.GetString("media.video_codec"),
|
|
|
|
|
|
AudioCodec: viper.GetString("media.audio_codec"),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置默认值
|
|
|
|
|
|
if config.PublicIP == "" {
|
|
|
|
|
|
config.PublicIP = viper.GetString("turn.public_ip") // 复用 TURN 的公网IP
|
|
|
|
|
|
}
|
|
|
|
|
|
if config.WebRTCPort == 0 {
|
|
|
|
|
|
config.WebRTCPort = 8080
|
|
|
|
|
|
}
|
|
|
|
|
|
if config.RTMPPort == 0 {
|
|
|
|
|
|
config.RTMPPort = 1935
|
|
|
|
|
|
}
|
|
|
|
|
|
if config.HTTPFLVPort == 0 {
|
|
|
|
|
|
config.HTTPFLVPort = 7001
|
|
|
|
|
|
}
|
|
|
|
|
|
if config.MaxRoomSize == 0 {
|
|
|
|
|
|
config.MaxRoomSize = 9 // 默认最多9人
|
|
|
|
|
|
}
|
|
|
|
|
|
if config.VideoCodec == "" {
|
|
|
|
|
|
config.VideoCodec = "h264"
|
|
|
|
|
|
}
|
|
|
|
|
|
if config.AudioCodec == "" {
|
|
|
|
|
|
config.AudioCodec = "opus"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return config
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Start 启动媒体服务器
|
|
|
|
|
|
func Start() {
|
|
|
|
|
|
ms := GetServer()
|
|
|
|
|
|
|
|
|
|
|
|
if !ms.config.Enabled {
|
|
|
|
|
|
log.Println("ℹ️ [MediaServer] 服务已禁用")
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Println("🎥 [MediaServer] 正在启动...")
|
|
|
|
|
|
|
|
|
|
|
|
// 启动 WebRTC SFU
|
|
|
|
|
|
if ms.sfu != nil {
|
|
|
|
|
|
go ms.sfu.Start()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动 RTMP 服务
|
|
|
|
|
|
if ms.rtmp != nil {
|
|
|
|
|
|
go ms.rtmp.Start()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Printf("🎥 [MediaServer] 已启动 | WebRTC: %d | RTMP: %d | HTTP-FLV: %d",
|
|
|
|
|
|
ms.config.WebRTCPort, ms.config.RTMPPort, ms.config.HTTPFLVPort)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Stop 停止媒体服务器
|
|
|
|
|
|
func (ms *MediaServer) Stop() {
|
|
|
|
|
|
log.Println("🛑 [MediaServer] 正在停止...")
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭所有房间
|
|
|
|
|
|
ms.mu.Lock()
|
|
|
|
|
|
for _, room := range ms.rooms {
|
|
|
|
|
|
room.Close()
|
|
|
|
|
|
}
|
|
|
|
|
|
ms.rooms = make(map[string]*Room)
|
|
|
|
|
|
ms.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
// 停止 SFU
|
|
|
|
|
|
if ms.sfu != nil {
|
|
|
|
|
|
ms.sfu.Stop()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 停止 RTMP
|
|
|
|
|
|
if ms.rtmp != nil {
|
|
|
|
|
|
ms.rtmp.Stop()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Println("🛑 [MediaServer] 已停止")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetOrCreateRoom 获取或创建房间
|
|
|
|
|
|
func (ms *MediaServer) GetOrCreateRoom(roomID string) *Room {
|
|
|
|
|
|
ms.mu.Lock()
|
|
|
|
|
|
defer ms.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
if room, exists := ms.rooms[roomID]; exists {
|
|
|
|
|
|
return room
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
room := NewRoom(roomID, ms.config.MaxRoomSize)
|
|
|
|
|
|
ms.rooms[roomID] = room
|
|
|
|
|
|
log.Printf("🎥 [MediaServer] 创建房间: %s", roomID)
|
|
|
|
|
|
return room
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetRoom 获取房间
|
|
|
|
|
|
func (ms *MediaServer) GetRoom(roomID string) *Room {
|
|
|
|
|
|
ms.mu.RLock()
|
|
|
|
|
|
defer ms.mu.RUnlock()
|
|
|
|
|
|
return ms.rooms[roomID]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RemoveRoom 移除房间
|
|
|
|
|
|
func (ms *MediaServer) RemoveRoom(roomID string) {
|
|
|
|
|
|
ms.mu.Lock()
|
|
|
|
|
|
defer ms.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
if room, exists := ms.rooms[roomID]; exists {
|
|
|
|
|
|
room.Close()
|
|
|
|
|
|
delete(ms.rooms, roomID)
|
|
|
|
|
|
log.Printf("🎥 [MediaServer] 移除房间: %s", roomID)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetConfig 获取配置
|
|
|
|
|
|
func (ms *MediaServer) GetConfig() *MediaServerConfig {
|
|
|
|
|
|
return ms.config
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetSFU 获取 SFU 服务
|
|
|
|
|
|
func (ms *MediaServer) GetSFU() *SFUServer {
|
|
|
|
|
|
return ms.sfu
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetRTMP 获取 RTMP 服务
|
|
|
|
|
|
func (ms *MediaServer) GetRTMP() *RTMPServer {
|
|
|
|
|
|
return ms.rtmp
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 21:57:21 +08:00
|
|
|
|
// GetWSProxy 获取 WebSocket to RTMP 代理
|
|
|
|
|
|
func (ms *MediaServer) GetWSProxy() *WebMToRTMPProxy {
|
|
|
|
|
|
return ms.wsProxy
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 09:04:14 +08:00
|
|
|
|
// GetRoomCount 获取房间数量
|
|
|
|
|
|
func (ms *MediaServer) GetRoomCount() int {
|
|
|
|
|
|
ms.mu.RLock()
|
|
|
|
|
|
defer ms.mu.RUnlock()
|
|
|
|
|
|
return len(ms.rooms)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetAllRooms 获取所有房间信息(用于调试/监控)
|
|
|
|
|
|
func (ms *MediaServer) GetAllRooms() []*RoomInfo {
|
|
|
|
|
|
ms.mu.RLock()
|
|
|
|
|
|
defer ms.mu.RUnlock()
|
|
|
|
|
|
|
|
|
|
|
|
infos := make([]*RoomInfo, 0, len(ms.rooms))
|
|
|
|
|
|
for _, room := range ms.rooms {
|
|
|
|
|
|
infos = append(infos, room.GetInfo())
|
|
|
|
|
|
}
|
|
|
|
|
|
return infos
|
|
|
|
|
|
}
|
|
|
|
|
|
|