mirror of https://github.com/q191201771/lal.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
249 lines
6.9 KiB
Go
249 lines
6.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/q191201771/lal/httpflv"
|
|
"github.com/q191201771/lal/log"
|
|
"github.com/q191201771/lal/rtmp"
|
|
"github.com/q191201771/lal/util"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
//
|
|
//type InSession interface {
|
|
// SetStartTick(tick int64)
|
|
// StartTick() int64
|
|
//}
|
|
|
|
type Group struct {
|
|
config *Config
|
|
appName string
|
|
streamName string
|
|
|
|
exitChan chan bool
|
|
rtmpPullSession *rtmp.PullSession
|
|
httpFlvPullSession *httpflv.PullSession
|
|
subSessionList map[*httpflv.SubSession]bool
|
|
turnToEmptyTick int64 // trace while sub session list turn to empty
|
|
gopCache *httpflv.GOPCache
|
|
mutex sync.Mutex
|
|
|
|
UniqueKey string
|
|
}
|
|
|
|
func NewGroup(appName string, streamName string, config *Config) *Group {
|
|
uk := util.GenUniqueKey("FLVGROUP")
|
|
log.Infof("lifecycle new Group. [%s] appName=%s streamName=%s", uk, appName, streamName)
|
|
|
|
return &Group{
|
|
config: config,
|
|
appName: appName,
|
|
streamName: streamName,
|
|
exitChan: make(chan bool),
|
|
subSessionList: make(map[*httpflv.SubSession]bool),
|
|
gopCache: httpflv.NewGOPCache(config.GOPCacheNum),
|
|
UniqueKey: uk,
|
|
}
|
|
}
|
|
|
|
func (group *Group) RunLoop() {
|
|
t := time.NewTicker(300 * time.Millisecond)
|
|
defer t.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-group.exitChan:
|
|
return
|
|
case <-t.C:
|
|
now := time.Now().Unix()
|
|
|
|
// TODO chef: do timeout stuff. and do it fast.
|
|
|
|
group.mutex.Lock()
|
|
if group.httpFlvPullSession != nil {
|
|
if isReadTimeout, _ := group.httpFlvPullSession.ConnStat.Check(now); isReadTimeout {
|
|
log.Warnf("pull session read timeout. [%s]", group.httpFlvPullSession.UniqueKey)
|
|
group.disposePullSession(lalErr)
|
|
}
|
|
}
|
|
group.mutex.Unlock()
|
|
|
|
group.mutex.Lock()
|
|
for session := range group.subSessionList {
|
|
if _, isWriteTimeout := session.ConnStat.Check(now); isWriteTimeout {
|
|
log.Warnf("sub session write timeout. [%s]", session)
|
|
delete(group.subSessionList, session)
|
|
session.Dispose(lalErr)
|
|
}
|
|
}
|
|
group.mutex.Unlock()
|
|
|
|
if group.config.Pull.StopPullWhileNoSubTimeout != 0 {
|
|
group.mutex.Lock()
|
|
if group.httpFlvPullSession != nil && group.turnToEmptyTick != 0 && len(group.subSessionList) == 0 &&
|
|
now-group.turnToEmptyTick > group.config.Pull.StopPullWhileNoSubTimeout {
|
|
|
|
log.Infof("stop pull while no SubSession. [%s]", group.httpFlvPullSession.UniqueKey)
|
|
group.disposePullSession(lalErr)
|
|
}
|
|
group.mutex.Unlock()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (group *Group) Dispose(err error) {
|
|
log.Infof("lifecycle dispose Group. [%s] reason=%v", group.UniqueKey, err)
|
|
group.exitChan <- true
|
|
}
|
|
|
|
func (group *Group) AddSubSession(session *httpflv.SubSession) {
|
|
group.mutex.Lock()
|
|
log.Debugf("add SubSession into group. [%s]", session.UniqueKey)
|
|
group.subSessionList[session] = true
|
|
group.turnToEmptyTick = 0
|
|
|
|
go func() {
|
|
if err := session.RunLoop(); err != nil {
|
|
log.Debugf("SubSession loop done. [%s] err=%v", session.UniqueKey, err)
|
|
}
|
|
|
|
group.mutex.Lock()
|
|
defer group.mutex.Unlock()
|
|
log.Infof("del SubSession out of group. [%s]", session.UniqueKey)
|
|
delete(group.subSessionList, session)
|
|
if len(group.subSessionList) == 0 {
|
|
group.turnToEmptyTick = time.Now().Unix()
|
|
}
|
|
}()
|
|
|
|
session.WriteHTTPResponseHeader()
|
|
session.WriteFlvHeader()
|
|
if group.gopCache.WriteWholeThings(session) {
|
|
session.HasKeyFrame = true
|
|
}
|
|
group.mutex.Unlock()
|
|
}
|
|
|
|
func (group *Group) PullIfNeeded() {
|
|
group.mutex.Lock()
|
|
if group.isInExist() {
|
|
return
|
|
}
|
|
switch group.config.Pull.Type {
|
|
case "httpflv":
|
|
group.httpFlvPullSession = httpflv.NewPullSession(group, group.config.Pull.ConnectTimeout, group.config.Pull.ReadTimeout)
|
|
go group.pullByHTTPFlv()
|
|
case "rtmp":
|
|
group.rtmpPullSession = rtmp.NewPullSession(group, group.config.Pull.ConnectTimeout)
|
|
go group.pullByRTMP()
|
|
default:
|
|
log.Errorf("unknown pull type. type=%s", group.config.Pull.Type)
|
|
}
|
|
group.mutex.Unlock()
|
|
}
|
|
|
|
func (group *Group) IsTotalEmpty() bool {
|
|
group.mutex.Lock()
|
|
defer group.mutex.Unlock()
|
|
return group.httpFlvPullSession == nil && len(group.subSessionList) == 0
|
|
}
|
|
|
|
func (group *Group) ReadHTTPRespHeaderCB() {
|
|
//log.Debugf("ReadHTTPRespHeaderCb. [%s]", group.UniqueKey)
|
|
}
|
|
|
|
func (group *Group) ReadFlvHeaderCB(flvHeader []byte) {
|
|
//log.Debugf("ReadFlvHeaderCb. [%s]", group.UniqueKey)
|
|
}
|
|
|
|
func (group *Group) ReadTagCB(tag *httpflv.Tag) {
|
|
//log.Debug(header.t, header.timestamp)
|
|
group.mutex.Lock()
|
|
defer group.mutex.Unlock()
|
|
// TODO chef: assume that write fast and would not block
|
|
for session := range group.subSessionList {
|
|
if session.HasKeyFrame {
|
|
session.WritePacket(tag.Raw)
|
|
} else {
|
|
if tag.IsMetadata() || tag.IsAVCKeySeqHeader() || tag.IsAACSeqHeader() || tag.IsAVCKeyNalu() {
|
|
if tag.IsAVCKeyNalu() {
|
|
session.HasKeyFrame = true
|
|
}
|
|
session.WritePacket(tag.Raw)
|
|
}
|
|
}
|
|
}
|
|
group.gopCache.Push(tag)
|
|
}
|
|
|
|
func (group *Group) ReadAvMessageCB(t int, timestampAbs int, message []byte) {
|
|
//log.Info(t)
|
|
group.mutex.Lock()
|
|
defer group.mutex.Unlock()
|
|
flvTag := httpflv.PackHTTPFlvTag(uint8(t), timestampAbs, message)
|
|
for session := range group.subSessionList {
|
|
if session.HasKeyFrame {
|
|
session.WritePacket(flvTag)
|
|
} else {
|
|
if httpflv.IsMetadata(flvTag) || httpflv.IsAVCKeySeqHeader(flvTag) || httpflv.IsAACSeqHeader(flvTag) || httpflv.IsAVCKeyNalu(flvTag) {
|
|
if httpflv.IsAVCKeyNalu(flvTag) {
|
|
session.HasKeyFrame = true
|
|
}
|
|
session.WritePacket(flvTag)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (group *Group) pullByHTTPFlv() {
|
|
defer func() {
|
|
group.mutex.Lock()
|
|
defer group.mutex.Unlock()
|
|
group.httpFlvPullSession = nil
|
|
log.Infof("del httpflv PullSession out of group. [%s]", group.httpFlvPullSession.UniqueKey)
|
|
}()
|
|
|
|
log.Infof("<----- connect. [%s]", group.httpFlvPullSession.UniqueKey)
|
|
url := fmt.Sprintf("http://%s/%s/%s.flv", group.config.Pull.Addr, group.appName, group.streamName)
|
|
if err := group.httpFlvPullSession.Connect(url); err != nil {
|
|
log.Errorf("-----> connect error. [%s] err=%v", group.httpFlvPullSession.UniqueKey, err)
|
|
return
|
|
}
|
|
log.Infof("-----> connect succ. [%s]", group.httpFlvPullSession.UniqueKey)
|
|
|
|
if err := group.httpFlvPullSession.RunLoop(); err != nil {
|
|
log.Debugf("PullSession loop done. [%s] err=%v", group.httpFlvPullSession.UniqueKey, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (group *Group) pullByRTMP() {
|
|
defer func() {
|
|
group.mutex.Lock()
|
|
defer group.mutex.Unlock()
|
|
group.rtmpPullSession = nil
|
|
log.Infof("del rtmp PullSession out of group.")
|
|
}()
|
|
|
|
url := fmt.Sprintf("rtmp://%s/%s/%s", group.config.Pull.Addr, group.appName, group.streamName)
|
|
if err := group.rtmpPullSession.Pull(url); err != nil {
|
|
log.Error(err)
|
|
}
|
|
if err := group.rtmpPullSession.WaitLoop(); err != nil {
|
|
log.Debugf("rtmp PullSession loop done. [%s] err=%v", group.rtmpPullSession.UniqueKey, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (group *Group) disposePullSession(err error) {
|
|
group.httpFlvPullSession.Dispose(err)
|
|
group.httpFlvPullSession = nil
|
|
group.gopCache.ClearAll()
|
|
}
|
|
|
|
func (group *Group) isInExist() bool {
|
|
return group.httpFlvPullSession != nil || group.rtmpPullSession != nil
|
|
}
|