mirror of
https://github.com/Monibuca/engine.git
synced 2025-10-10 19:10:15 +08:00
4.0初步改造
This commit is contained in:
435
stream.go
435
stream.go
@@ -2,259 +2,228 @@ package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
utils "github.com/Monibuca/utils/v3"
|
||||
"github.com/Monibuca/engine/v4/track"
|
||||
"github.com/Monibuca/engine/v4/util"
|
||||
. "github.com/logrusorgru/aurora"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type StreamCollection struct {
|
||||
sync.RWMutex
|
||||
m map[string]*Stream
|
||||
type StreamState byte
|
||||
type StreamAction byte
|
||||
|
||||
const (
|
||||
STATE_WAITPUBLISH StreamState = iota // 等待发布者状态
|
||||
STATE_WAITTRACK // 等待Track
|
||||
STATE_PUBLISHING // 正在发布流状态
|
||||
STATE_WAITCLOSE // 等待关闭状态(自动关闭延时开启)
|
||||
STATE_CLOSED
|
||||
)
|
||||
|
||||
const (
|
||||
ACTION_PUBLISH StreamAction = iota
|
||||
ACTION_TIMEOUT // 发布流长时间没有数据/长时间没有发布者发布流/等待关闭时间到
|
||||
ACTION_PUBLISHLOST // 发布者意外断开
|
||||
ACTION_CLOSE // 主动关闭流
|
||||
ACTION_LASTLEAVE // 最后一个订阅者离开
|
||||
ACTION_FIRSTENTER // 第一个订阅者进入
|
||||
)
|
||||
|
||||
var StreamFSM = [STATE_CLOSED + 1]map[StreamAction]StreamState{
|
||||
{
|
||||
ACTION_PUBLISH: STATE_WAITTRACK,
|
||||
ACTION_LASTLEAVE: STATE_CLOSED,
|
||||
ACTION_CLOSE: STATE_CLOSED,
|
||||
},
|
||||
{
|
||||
ACTION_PUBLISHLOST: STATE_WAITPUBLISH,
|
||||
ACTION_TIMEOUT: STATE_PUBLISHING,
|
||||
ACTION_CLOSE: STATE_CLOSED,
|
||||
},
|
||||
{
|
||||
ACTION_PUBLISHLOST: STATE_WAITPUBLISH,
|
||||
ACTION_TIMEOUT: STATE_WAITPUBLISH,
|
||||
ACTION_LASTLEAVE: STATE_WAITCLOSE,
|
||||
ACTION_CLOSE: STATE_CLOSED,
|
||||
},
|
||||
{
|
||||
ACTION_PUBLISHLOST: STATE_CLOSED,
|
||||
ACTION_TIMEOUT: STATE_CLOSED,
|
||||
ACTION_FIRSTENTER: STATE_PUBLISHING,
|
||||
ACTION_CLOSE: STATE_CLOSED,
|
||||
},
|
||||
{},
|
||||
}
|
||||
|
||||
func (sc *StreamCollection) GetStream(streamPath string) *Stream {
|
||||
sc.RLock()
|
||||
defer sc.RUnlock()
|
||||
if s, ok := sc.m[streamPath]; ok {
|
||||
return s
|
||||
// Streams 所有的流集合
|
||||
var Streams = util.Map[string, *Stream]{Map: make(map[string]*Stream)}
|
||||
|
||||
type SubscribeAction *Subscriber
|
||||
type UnSubscibeAction *Subscriber
|
||||
|
||||
// Stream 流定义
|
||||
type Stream struct {
|
||||
context.Context
|
||||
cancel context.CancelFunc
|
||||
Publisher
|
||||
State StreamState
|
||||
timeout *time.Timer //当前状态的超时定时器
|
||||
actionChan chan any
|
||||
Config StreamConfig
|
||||
URL string //远程地址,仅远程拉流有值
|
||||
StreamPath string
|
||||
StartTime time.Time //流的创建时间
|
||||
Subscribers util.Slice[*Subscriber] // 订阅者
|
||||
Tracks
|
||||
FrameCount uint32 //帧总数
|
||||
}
|
||||
|
||||
func (r *Stream) Register(streamPath string) (result bool) {
|
||||
if r == nil {
|
||||
r = &Stream{
|
||||
Config: config.StreamConfig,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (sc *StreamCollection) Delete(streamPath string) {
|
||||
sc.Lock()
|
||||
delete(sc.m, streamPath)
|
||||
sc.Unlock()
|
||||
}
|
||||
|
||||
func (sc *StreamCollection) ToList() (r []*Stream) {
|
||||
sc.RLock()
|
||||
defer sc.RUnlock()
|
||||
for _, s := range sc.m {
|
||||
r = append(r, s)
|
||||
r.StreamPath = streamPath
|
||||
if result = Streams.Add(streamPath, r); result {
|
||||
r.actionChan = make(chan any, 1)
|
||||
r.StartTime = time.Now()
|
||||
r.timeout = time.NewTimer(r.Config.WaitTimeout.Duration())
|
||||
r.Context, r.cancel = context.WithCancel(Ctx)
|
||||
r.Init(r)
|
||||
go r.run()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sc *StreamCollection) Range(f func(*Stream)) {
|
||||
sc.RLock()
|
||||
defer sc.RUnlock()
|
||||
for _, s := range sc.m {
|
||||
f(s)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
Streams.m = make(map[string]*Stream)
|
||||
}
|
||||
|
||||
// Streams 所有的流集合
|
||||
var Streams StreamCollection
|
||||
var StreamTimeoutError = errors.New("timeout")
|
||||
|
||||
//FindStream 根据流路径查找流
|
||||
func FindStream(streamPath string) *Stream {
|
||||
return Streams.GetStream(streamPath)
|
||||
}
|
||||
|
||||
// Publish 直接发布
|
||||
func Publish(streamPath, t string) *Stream {
|
||||
var stream = &Stream{
|
||||
StreamPath: streamPath,
|
||||
Type: t,
|
||||
}
|
||||
if stream.Publish() {
|
||||
return stream
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type StreamContext struct {
|
||||
context.Context
|
||||
cancel context.CancelFunc
|
||||
timeout *time.Timer //更新时间用来做超时处理
|
||||
IsTimeout bool
|
||||
}
|
||||
|
||||
func (r *StreamContext) Err() error {
|
||||
if r.IsTimeout {
|
||||
return StreamTimeoutError
|
||||
}
|
||||
return r.Context.Err()
|
||||
}
|
||||
func (r *StreamContext) Update() {
|
||||
if r.timeout != nil {
|
||||
r.timeout.Reset(config.PublishTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
// Stream 流定义
|
||||
type Stream struct {
|
||||
URL string //远程地址,仅远程拉流有值
|
||||
StreamContext `json:"-"`
|
||||
StreamPath string
|
||||
Type string //流类型,来自发布者
|
||||
StartTime time.Time //流的创建时间
|
||||
Subscribers []*Subscriber // 订阅者
|
||||
VideoTracks Tracks
|
||||
AudioTracks Tracks
|
||||
DataTracks Tracks
|
||||
AutoCloseAfter *int //当无人订阅时延迟N秒后自动停止发布
|
||||
Transcoding map[string]string //转码配置,key:目标编码,value:发布者提供的编码
|
||||
subscribeMutex sync.Mutex
|
||||
OnClose func() `json:"-"`
|
||||
ExtraProp interface{} //额外的属性,用于实现子类化,减少map的使用
|
||||
closeDelay *time.Timer
|
||||
}
|
||||
|
||||
func (r *Stream) Close() {
|
||||
Streams.Lock()
|
||||
//如果没有发布过,就不需要进行处理
|
||||
if r.cancel == nil {
|
||||
Streams.Unlock()
|
||||
// ForceRegister 强制注册流,会将已有的流踢掉
|
||||
func (r *Stream) ForceRegister(streamPath string) {
|
||||
if ok := r.Register(streamPath); !ok {
|
||||
if s := Streams.Get(streamPath); s != nil {
|
||||
s.Close()
|
||||
<-s.Done()
|
||||
}
|
||||
r.ForceRegister(streamPath)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
if r.closeDelay != nil {
|
||||
r.closeDelay.Stop()
|
||||
}
|
||||
r.cancel()
|
||||
r.cancel = nil
|
||||
delete(Streams.m, r.StreamPath)
|
||||
Streams.Unlock()
|
||||
r.VideoTracks.Dispose()
|
||||
r.AudioTracks.Dispose()
|
||||
r.DataTracks.Dispose()
|
||||
if r.OnClose != nil {
|
||||
r.OnClose()
|
||||
}
|
||||
TriggerHook(HOOK_STREAMCLOSE, r)
|
||||
utils.Print(Yellow("Stream destoryed :"), BrightCyan(r.StreamPath))
|
||||
}
|
||||
|
||||
// Publish 发布者进行发布操作
|
||||
func (r *Stream) Publish() bool {
|
||||
Streams.Lock()
|
||||
defer Streams.Unlock()
|
||||
if _, ok := Streams.m[r.StreamPath]; ok {
|
||||
return false
|
||||
}
|
||||
if r.AutoCloseAfter == nil {
|
||||
r.AutoCloseAfter = &config.AutoCloseAfter
|
||||
}
|
||||
var closeChann <-chan time.Time
|
||||
if *r.AutoCloseAfter > 0 {
|
||||
r.closeDelay = time.NewTimer(time.Duration(*r.AutoCloseAfter) * time.Second)
|
||||
r.closeDelay.Stop()
|
||||
closeChann = r.closeDelay.C
|
||||
}
|
||||
r.Context, r.cancel = context.WithCancel(Ctx)
|
||||
r.VideoTracks.Init(r)
|
||||
r.AudioTracks.Init(r)
|
||||
r.DataTracks.Init(r)
|
||||
r.StartTime = time.Now()
|
||||
Streams.m[r.StreamPath] = r
|
||||
utils.Print(Green("Stream publish:"), BrightCyan(r.StreamPath))
|
||||
go r.waitClose(closeChann)
|
||||
//触发钩子
|
||||
TriggerHook(HOOK_PUBLISH, r)
|
||||
return true
|
||||
}
|
||||
|
||||
// 等待流关闭
|
||||
func (r *Stream) waitClose(closeChann <-chan time.Time) {
|
||||
r.timeout = time.NewTimer(config.PublishTimeout)
|
||||
defer r.timeout.Stop()
|
||||
if r.closeDelay != nil {
|
||||
defer r.closeDelay.Stop()
|
||||
}
|
||||
select {
|
||||
case <-r.Done():
|
||||
case <-closeChann:
|
||||
utils.Print(Yellow("Stream closeDelay:"), BrightCyan(r.StreamPath))
|
||||
r.Close()
|
||||
case <-r.timeout.C:
|
||||
utils.Print(Yellow("Stream timeout:"), BrightCyan(r.StreamPath))
|
||||
r.IsTimeout = true
|
||||
r.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Stream) WaitDataTrack(names ...string) *DataTrack {
|
||||
if !config.EnableVideo {
|
||||
return nil
|
||||
}
|
||||
if track := r.DataTracks.WaitTrack(names...); track != nil {
|
||||
return track.(*DataTrack)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Stream) WaitVideoTrack(names ...string) *VideoTrack {
|
||||
if !config.EnableVideo {
|
||||
return nil
|
||||
}
|
||||
if track := r.VideoTracks.WaitTrack(names...); track != nil {
|
||||
return track.(*VideoTrack)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: 触发转码逻辑
|
||||
func (r *Stream) WaitAudioTrack(names ...string) *AudioTrack {
|
||||
if !config.EnableAudio {
|
||||
return nil
|
||||
}
|
||||
if track := r.AudioTracks.WaitTrack(names...); track != nil {
|
||||
return track.(*AudioTrack)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//Subscribe 订阅流
|
||||
func (r *Stream) Subscribe(s *Subscriber) {
|
||||
if s.Stream = r; r.Err() == nil {
|
||||
s.SubscribeTime = time.Now()
|
||||
utils.Print(Sprintf(Yellow("subscribe :%s %s,to Stream %s"), Blue(s.Type), Cyan(s.ID), BrightCyan(r.StreamPath)))
|
||||
s.Context, s.cancel = context.WithCancel(r)
|
||||
r.subscribeMutex.Lock()
|
||||
if *r.AutoCloseAfter > 0 {
|
||||
r.closeDelay.Stop()
|
||||
}
|
||||
r.Subscribers = append(r.Subscribers, s)
|
||||
TriggerHook(HOOK_SUBSCRIBE, s, len(r.Subscribers))
|
||||
r.subscribeMutex.Unlock()
|
||||
utils.Print(Sprintf(Yellow("%s subscriber %s added remains:%d"), BrightCyan(r.StreamPath), Cyan(s.ID), Blue(len(r.Subscribers))))
|
||||
}
|
||||
}
|
||||
|
||||
//UnSubscribe 取消订阅流
|
||||
func (r *Stream) UnSubscribe(s *Subscriber) {
|
||||
if r.Err() == nil {
|
||||
var deleted bool
|
||||
r.subscribeMutex.Lock()
|
||||
defer r.subscribeMutex.Unlock()
|
||||
r.Subscribers, deleted = DeleteSliceItem_Subscriber(r.Subscribers, s)
|
||||
if deleted {
|
||||
utils.Print(Sprintf(Yellow("%s subscriber %s removed remains:%d"), BrightCyan(r.StreamPath), Cyan(s.ID), Blue(len(r.Subscribers))))
|
||||
l := len(r.Subscribers)
|
||||
TriggerHook(HOOK_UNSUBSCRIBE, s, l)
|
||||
if l == 0 && *r.AutoCloseAfter >= 0 {
|
||||
if *r.AutoCloseAfter == 0 {
|
||||
r.Close()
|
||||
} else {
|
||||
r.closeDelay.Reset(time.Duration(*r.AutoCloseAfter) * time.Second)
|
||||
}
|
||||
func (r *Stream) action(action StreamAction) {
|
||||
if next, ok := StreamFSM[r.State][action]; ok {
|
||||
if r.Publisher == nil || r.OnStateChange(r.State, next) {
|
||||
util.Print(Yellow("Stream "), BrightCyan(r.StreamPath), " state changed :", r.State, "->", next)
|
||||
r.State = next
|
||||
switch next {
|
||||
case STATE_WAITPUBLISH:
|
||||
r.timeout.Reset(r.Config.WaitTimeout.Duration())
|
||||
case STATE_WAITTRACK:
|
||||
r.timeout.Reset(time.Second * 5)
|
||||
case STATE_PUBLISHING:
|
||||
r.WaitDone()
|
||||
r.timeout.Reset(r.Config.PublishTimeout.Duration())
|
||||
case STATE_WAITCLOSE:
|
||||
r.timeout.Reset(r.Config.WaitCloseTimeout.Duration())
|
||||
case STATE_CLOSED:
|
||||
r.cancel()
|
||||
r.WaitDone()
|
||||
close(r.actionChan)
|
||||
Streams.Delete(r.StreamPath)
|
||||
fallthrough
|
||||
default:
|
||||
r.timeout.Stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func DeleteSliceItem_Subscriber(slice []*Subscriber, item *Subscriber) ([]*Subscriber, bool) {
|
||||
for i, val := range slice {
|
||||
if val == item {
|
||||
return append(slice[:i], slice[i+1:]...), true
|
||||
|
||||
func (r *Stream) Close() {
|
||||
r.actionChan <- ACTION_CLOSE
|
||||
}
|
||||
func (r *Stream) UnSubscribe(sub *Subscriber) {
|
||||
r.actionChan <- UnSubscibeAction(sub)
|
||||
}
|
||||
func (r *Stream) Subscribe(sub *Subscriber) {
|
||||
r.actionChan <- SubscribeAction(sub)
|
||||
}
|
||||
func (r *Stream) run() {
|
||||
for {
|
||||
select {
|
||||
case <-r.timeout.C:
|
||||
util.Print(Yellow("Stream "), BrightCyan(r.StreamPath), "timeout:", r.State)
|
||||
r.action(ACTION_TIMEOUT)
|
||||
case <-r.Done():
|
||||
r.action(ACTION_CLOSE)
|
||||
case action, ok := <-r.actionChan:
|
||||
if ok {
|
||||
switch v := action.(type) {
|
||||
case StreamAction:
|
||||
r.action(v)
|
||||
case SubscribeAction:
|
||||
v.Stream = r
|
||||
v.Context, v.cancel = context.WithCancel(r)
|
||||
r.Subscribers.Add(v)
|
||||
util.Print(Sprintf(Yellow("%s subscriber %s added remains:%d"), BrightCyan(r.StreamPath), Cyan(v.ID), Blue(len(r.Subscribers))))
|
||||
if r.Subscribers.Len() == 1 {
|
||||
r.action(ACTION_FIRSTENTER)
|
||||
}
|
||||
case UnSubscibeAction:
|
||||
if r.Subscribers.Delete(v) {
|
||||
util.Print(Sprintf(Yellow("%s subscriber %s removed remains:%d"), BrightCyan(r.StreamPath), Cyan(v.ID), Blue(len(r.Subscribers))))
|
||||
if r.Subscribers.Len() == 0 && r.Config.WaitCloseTimeout > 0 {
|
||||
r.action(ACTION_LASTLEAVE)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return slice, false
|
||||
}
|
||||
|
||||
// Update 更新数据重置超时定时器
|
||||
func (r *Stream) Update() uint32 {
|
||||
if r.State == STATE_PUBLISHING {
|
||||
r.timeout.Reset(r.Config.PublishTimeout.Duration())
|
||||
}
|
||||
return atomic.AddUint32(&r.FrameCount, 1)
|
||||
}
|
||||
|
||||
// 如果暂时不知道编码格式可以用这个
|
||||
func (r *Stream) NewVideoTrack() (vt *track.UnknowVideo) {
|
||||
vt = &track.UnknowVideo{
|
||||
Stream: r,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Stream) NewH264Track() (vt *track.H264) {
|
||||
return track.NewH264(r)
|
||||
}
|
||||
|
||||
func (r *Stream) NewH265Track() (vt *track.H265) {
|
||||
return track.NewH265(r)
|
||||
}
|
||||
|
||||
// func (r *Stream) WaitDataTrack(names ...string) DataTrack {
|
||||
// t := <-r.WaitTrack(names...)
|
||||
// return t.(DataTrack)
|
||||
// }
|
||||
|
||||
func (r *Stream) WaitVideoTrack(names ...string) track.Video {
|
||||
if !r.Config.EnableVideo {
|
||||
return nil
|
||||
}
|
||||
t := <-r.WaitTrack(names...)
|
||||
return t.(track.Video)
|
||||
}
|
||||
|
||||
func (r *Stream) WaitAudioTrack(names ...string) track.Audio {
|
||||
if !r.Config.EnableAudio {
|
||||
return nil
|
||||
}
|
||||
t := <-r.WaitTrack(names...)
|
||||
return t.(track.Audio)
|
||||
}
|
||||
|
Reference in New Issue
Block a user