Files
nl-im-service/internal/mediaserver/server.go

248 lines
5.7 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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
}