248 lines
5.7 KiB
Go
248 lines
5.7 KiB
Go
/**
|
||
* 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 {
|
||
sfu *SFUServer // WebRTC SFU 服务
|
||
rtmp *RTMPServer // RTMP 服务
|
||
wsProxy *WebMToRTMPProxy // WebSocket to RTMP 代理
|
||
rooms map[string]*Room // 通话房间管理
|
||
mu sync.RWMutex // 房间锁
|
||
config *MediaServerConfig // 配置
|
||
}
|
||
|
||
// 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)
|
||
// 创建 WebSocket to RTMP 代理
|
||
ms.wsProxy = NewWebMToRTMPProxy(ms.rtmp)
|
||
}
|
||
|
||
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] 正在启动...")
|
||
|
||
// 在启动时检测并安装 FFmpeg(用于 VP8/VP9 转码)
|
||
go func() {
|
||
log.Println("🔍 [MediaServer] 检测 FFmpeg...")
|
||
if path, err := EnsureFFmpeg(); err != nil {
|
||
log.Printf("⚠️ [MediaServer] FFmpeg 不可用: %v (VP8/VP9 转码将不可用)", err)
|
||
} else {
|
||
log.Printf("✅ [MediaServer] FFmpeg 就绪: %s", path)
|
||
}
|
||
}()
|
||
|
||
// 启动 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
|
||
}
|
||
|
||
// GetWSProxy 获取 WebSocket to RTMP 代理
|
||
func (ms *MediaServer) GetWSProxy() *WebMToRTMPProxy {
|
||
return ms.wsProxy
|
||
}
|
||
|
||
// 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
|
||
}
|
||
|