messages:

- [feat] HTTP端口复用:HTTP-FLV, HTTP-TS, HLS可使用相同的监听端口。HTTPS类似 #64
- [feat] HTTPS:支持HTTPS-TS, HLS over HTTPS(之前已支持HTTPS-FLV) #66
- [feat] WebSocket:支持WebSocket[s]-TS(之前已支持WebSocket[s]-FLV)#66
- [feat] 配置灵活。增加`default_http`。HTTP-FLV,HTTP-TS,HLS可以独立配置监听地址相关的项,也可以使用公共的`default_http`
- [refactor] 重构httpflv.SubSession和httpts.SubSession的重复代码
pull/78/head
q191201771 4 years ago
parent c0d600ea1b
commit 344a2c82b1

@ -9,7 +9,7 @@
[中文文档](https://pengrl.com/lal/#/)
LAL is an audio/video live streaming broadcast server written in Go. It's sort of like `nginx-rtmp-module`, but easier to use and with more features, e.g RTMP, RTSP(RTP/RTCP), HLS, HTTP[S]-FLV/HTTP-TS, WebSocket-FLV/TS, H264/H265/AAC, relay, cluster, record, HTTP API/Notify, GOP cache.
LAL is an audio/video live streaming broadcast server written in Go. It's sort of like `nginx-rtmp-module`, but easier to use and with more features, e.g RTMP, RTSP(RTP/RTCP), HLS, HTTP[S]/WebSocket[s]-FLV/TS, H264/H265/AAC, relay, cluster, record, HTTP API/Notify, GOP cache.
And [more than a server, act as package and client](https://github.com/q191201771/lal#more-than-a-server-act-as-package-and-client)

@ -31,7 +31,6 @@ func main() {
url, hlsOutPath, fragmentDurationMS, fragmentNum)
hlsMuxerConfig := hls.MuxerConfig{
Enable: true,
OutPath: hlsOutPath,
FragmentDurationMS: fragmentDurationMS,
FragmentNum: fragmentNum,
@ -43,7 +42,7 @@ func main() {
}
streamName := ctx.LastItemOfPath
hlsMuexer := hls.NewMuxer(streamName, &hlsMuxerConfig, nil)
hlsMuexer := hls.NewMuxer(streamName, true, &hlsMuxerConfig, nil)
hlsMuexer.Start()
pullSession := rtmp.NewPullSession(func(option *rtmp.PullSessionOption) {

@ -28,7 +28,8 @@ func main() {
defer nazalog.Sync()
confFile := parseFlag()
logic.Entry(confFile)
logic.Init(confFile)
logic.RunLoop()
}
func parseFlag() string {

@ -0,0 +1,86 @@
```
{
"# doc of config": "https://pengrl.com/lal/#/ConfigBrief", // 配置文件对应的文档说明链接,在程序中没实际用途
"conf_version": "0.2.0", // 配置文件版本号,业务方不应该手动修改,程序中会检查该版本号是否与代码中声明的一致
"rtmp": {
"enable": true, // 是否开启rtmp服务的监听
"addr": ":19350", // RTMP服务监听的端口客户端向lalserver推拉流都是这个地址
"gop_num": 2 // RTMP拉流的GOP缓存数量加速秒开
},
"default_http": { // http监听相关的默认配置如果hls, httpflv, httpts中没有单独配置以下配置项则使用default_http中的配置
// 注意hls, httpflv, httpts服务是否开启不由此处决定
"http_listen_addr": ":8080", // HTTP监听地址
"https_listen_addr": ":4433", // HTTPS监听地址
"https_cert_file": "./conf/cert.pem", // HTTPS的本地cert文件地址
"https_key_file": "./conf/key.pem" // HTTPS的本地key文件地址
},
"httpflv": {
"enable": true, // 是否开启HTTP-FLV服务的监听
"enable_https": true, // 是否开启HTTPS-FLV监听
"gop_num": 2
},
"hls": {
"enable": true, // 是否开启HLS服务的监听
"out_path": "/tmp/lal/hls/", // HLS文件保存根目录
"fragment_duration_ms": 3000, // 单个TS文件切片时长单位毫秒
"fragment_num": 6, // m3u8文件列表中ts文件的数量
"cleanup_mode": 1, // HLS文件清理模式
// 0 不删除m3u8+ts文件可用于录制等场景
// 1 在输入流结束后删除m3u8+ts文件
// 注意,确切的删除时间是推流结束后的<fragment_duration_ms> * <fragment_num> * 2的时间点
// 推迟一小段时间删除是为了避免输入流刚结束HLS的拉流端还没有拉取完
// 2 推流过程中持续删除过期的ts文件只保留最近的<fragment_num> * 2个左右的ts文件
"use_memory_as_disk_flag": false // 是否使用内存取代磁盘保存m3u8+ts文件
},
"httpts": {
"enable": true // 是否开启HTTP-TS服务的监听。注意这并不是HLS中的TS而是在一条HTTP长连接上持续性传输TS流
},
"rtsp": {
"enable": true, // 是否开启rtsp服务的监听目前只支持rtsp推流
"addr": ":5544" // rtsp推流地址
},
"record": {
"enable_flv": true, // 是否开启flv录制
"flv_out_path": "/tmp/lal/flv/", // flv录制目录
"enable_mpegts": true, // 是否开启mpegts录制。注意此处是长ts文件录制hls录制由上面的hls配置控制
"mpegts_out_path": "/tmp/lal/mpegts" // mpegts录制目录
},
"relay_push": {
"enable": false, // 是否开启中继转推功能,开启后,自身接收到的所有流都会转推出去
"addr_list":[ // 中继转推的对端地址支持填写多个地址做1对n的转推。格式举例 "127.0.0.1:19351"
]
},
"relay_pull": {
"enable": false, // 是否开启回源拉流功能,开启后,当自身接收到拉流请求,而流不存在时,会从其他服务器拉取这个流到本地
"addr": "" // 回源拉流的地址。格式举例 "127.0.0.1:19351"
},
"http_api": {
"enable": true, // 是否开启HTTP API接口
"addr": ":8083" // 监听地址
},
"server_id": "1", // 当前lalserver唯一ID。多个lalserver HTTP Notify同一个地址时可通过该ID区分
"http_notify": {
"enable": true, // 是否开启HTTP Notify事件回调
"update_interval_sec": 5, // update事件回调间隔单位毫秒
"on_server_start": "http://127.0.0.1:10101/on_server_start", // 各事件HTTP Notify事件回调地址
"on_update": "http://127.0.0.1:10101/on_update",
"on_pub_start": "http://127.0.0.1:10101/on_pub_start",
"on_pub_stop": "http://127.0.0.1:10101/on_pub_stop",
"on_sub_start": "http://127.0.0.1:10101/on_sub_start",
"on_sub_stop": "http://127.0.0.1:10101/on_sub_stop",
"on_rtmp_connect": "http://127.0.0.1:10101/on_rtmp_connect"
},
"pprof": {
"enable": true, // 是否开启Go pprof web服务的监听
"addr": ":8084" // Go pprof web地址
},
"log": {
"level": 1, // 日志级别0 trace, 1 debug, 2 info, 3 warn, 4 error, 5 fatal
"filename": "./logs/lalserver.log", // 日志输出文件
"is_to_stdout": true, // 是否打印至标志控制台输出
"is_rotate_daily": true, // 日志按天翻滚
"short_file_flag": true, // 日志末尾是否携带源码文件名以及行号的信息
"assert_behavior": 1 // 日志断言的行为1 只打印错误日志 2 打印并退出程序 3 打印并panic
}
}
```

@ -1,23 +1,25 @@
{
"# doc of config": "https://pengrl.com/lal/#/ConfigBrief",
"conf_version": "v0.1.2",
"conf_version": "v0.2.0",
"rtmp": {
"enable": true,
"addr": ":1935",
"gop_num": 2
},
"default_http": {
"http_listen_addr": ":8080",
"https_listen_addr": ":4433",
"https_cert_file": "./conf/cert.pem",
"https_key_file": "./conf/key.pem"
},
"httpflv": {
"enable": true,
"sub_listen_addr": ":8080",
"enable_https": false,
"https_addr": ":4433",
"https_cert_file": "./conf/cert.pem",
"https_key_file": "./conf/key.pem",
"gop_num": 2
},
"hls": {
"enable": true,
"sub_listen_addr": ":8081",
"enable_https": false,
"out_path": "/tmp/lal/hls/",
"fragment_duration_ms": 3000,
"fragment_num": 6,
@ -26,7 +28,7 @@
},
"httpts": {
"enable": true,
"sub_listen_addr": ":8082"
"enable_https":false
},
"rtsp": {
"enable": true,

@ -1,23 +1,25 @@
{
"# doc of config": "https://pengrl.com/lal/#/ConfigBrief",
"conf_version": "v0.1.2",
"conf_version": "v0.2.0",
"rtmp": {
"enable": true,
"addr": ":1935",
"gop_num": 2
},
"default_http": {
"http_listen_addr": ":8080",
"https_listen_addr": ":4433",
"https_cert_file": "./conf/cert.pem",
"https_key_file": "./conf/key.pem"
},
"httpflv": {
"enable": true,
"sub_listen_addr": ":8080",
"enable_https": false,
"https_addr": ":4433",
"https_cert_file": "./conf/cert.pem",
"https_key_file": "./conf/key.pem",
"gop_num": 2
},
"hls": {
"enable": true,
"sub_listen_addr": ":8081",
"enable_https": false,
"out_path": "/tmp/lal/hls/",
"fragment_duration_ms": 3000,
"fragment_num": 6,
@ -26,7 +28,7 @@
},
"httpts": {
"enable": true,
"sub_listen_addr": ":8082"
"enable_https":false
},
"rtsp": {
"enable": true,

@ -0,0 +1,149 @@
// Copyright 2021, Chef. All rights reserved.
// https://github.com/q191201771/lal
//
// Use of this source code is governed by a MIT-style license
// that can be found in the License file.
//
// Author: Chef (191201771@qq.com)
package base
import (
"crypto/tls"
"errors"
"net"
"net/http"
"reflect"
"github.com/q191201771/naza/pkg/nazaerrors"
)
// TODO(chef)
// - 考虑移入naza中
// - 考虑增加一个pattern全部未命中的mux回调
var (
ErrAddrEmpty = errors.New("lal.base: http server addr empty")
ErrMultiRegistForPattern = errors.New("lal.base: http server multiple registrations for pattern")
)
const (
NetworkTCP = "tcp"
)
type LocalAddrCtx struct {
IsHTTPS bool
Addr string
CertFile string
KeyFile string
Network string // 默认为NetworkTCP
}
type HTTPServerManager struct {
addr2ServerCtx map[string]*ServerCtx
}
type ServerCtx struct {
addrCtx LocalAddrCtx
listener net.Listener
httpServer http.Server
mux *http.ServeMux
pattern2Handler map[string]Handler
}
func NewHTTPServerManager() *HTTPServerManager {
return &HTTPServerManager{
addr2ServerCtx: make(map[string]*ServerCtx),
}
}
type Handler func(http.ResponseWriter, *http.Request)
// @param pattern 必须以`/`开始,并以`/`结束
func (s *HTTPServerManager) AddListen(addrCtx LocalAddrCtx, pattern string, handler Handler) error {
var (
ctx *ServerCtx
mux *http.ServeMux
ok bool
)
if addrCtx.Addr == "" {
return ErrAddrEmpty
}
ctx, ok = s.addr2ServerCtx[addrCtx.Addr]
if !ok {
l, err := listen(addrCtx)
if err != nil {
return err
}
mux = http.NewServeMux()
ctx = &ServerCtx{
addrCtx: addrCtx,
listener: l,
httpServer: http.Server{
Handler: mux,
},
mux: mux,
pattern2Handler: make(map[string]Handler),
}
s.addr2ServerCtx[addrCtx.Addr] = ctx
}
// 路径相同,比较回调函数是否相同
// 如果回调函数也相同,意味着重复绑定,这种情况是允许的
// 如果回调函数不同,返回错误
if prevHandler, ok := ctx.pattern2Handler[pattern]; ok {
if reflect.ValueOf(prevHandler).Pointer() == reflect.ValueOf(handler).Pointer() {
return nil
} else {
return ErrMultiRegistForPattern
}
}
ctx.pattern2Handler[pattern] = handler
ctx.mux.HandleFunc(pattern, handler)
return nil
}
func (s *HTTPServerManager) RunLoop() error {
errChan := make(chan error, len(s.addr2ServerCtx))
for _, v := range s.addr2ServerCtx {
go func(ctx *ServerCtx) {
errChan <- ctx.httpServer.Serve(ctx.listener)
_ = ctx.httpServer.Close()
}(v)
}
// 阻塞直到接到第一个error
return <-errChan
}
func (s *HTTPServerManager) Dispose() error {
var es []error
for _, v := range s.addr2ServerCtx {
err := v.httpServer.Close()
es = append(es, err)
}
return nazaerrors.CombineErrors(es...)
}
func listen(ctx LocalAddrCtx) (net.Listener, error) {
if ctx.Network == "" {
ctx.Network = NetworkTCP
}
if !ctx.IsHTTPS {
return net.Listen(ctx.Network, ctx.Addr)
}
cert, err := tls.LoadX509KeyPair(ctx.CertFile, ctx.KeyFile)
if err != nil {
return nil, err
}
tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}}
return tls.Listen(ctx.Network, ctx.Addr, tlsConfig)
}

@ -0,0 +1,49 @@
// Copyright 2021, Chef. All rights reserved.
// https://github.com/q191201771/lal
//
// Use of this source code is governed by a MIT-style license
// that can be found in the License file.
//
// Author: Chef (191201771@qq.com)
package base
import (
"testing"
)
func TestHTTPServerManager(t *testing.T) {
//var err error
//
//var fnFLV = func(writer http.ResponseWriter, request *http.Request) {
// nazalog.Debugf("> fnFLV. %+v, %+v", writer, request)
// conn, bio, err := writer.(http.Hijacker).Hijack()
// if err != nil {
// nazalog.Errorf("hijack failed. err=%+v", err)
// return
// }
// if bio.Reader.Buffered() != 0 || bio.Writer.Buffered() != 0 {
// nazalog.Errorf("hijack but buffer not empty. rb=%d, wb=%d", bio.Reader.Buffered(), bio.Writer.Buffered())
// }
// nazalog.Debugf("%+v, %+v, %+v", conn, bio, err)
//}
//
//sm := NewHTTPServerManager()
//
//err = sm.AddListen(
// LocalAddrCtx{IsHTTPS: false, Addr: ":8080"},
// "/live/",
// fnFLV,
//)
//assert.Equal(t, nil, err)
//
//err = sm.AddListen(
// LocalAddrCtx{IsHTTPS: true, Addr: ":4433", CertFile: "../../conf/cert.pem", KeyFile: "../../conf/key.pem"},
// "/live/",
// fnFLV,
//)
//assert.Equal(t, nil, err)
//
//err = sm.RunLoop()
//assert.Equal(t, nil, err)
}

@ -0,0 +1,149 @@
// Copyright 2019, Chef. All rights reserved.
// https://github.com/q191201771/lal
//
// Use of this source code is governed by a MIT-style license
// that can be found in the License file.
//
// Author: Chef (191201771@qq.com)
package base
import (
"net"
"strings"
"time"
"github.com/q191201771/naza/pkg/connection"
)
type HTTPSubSession struct {
HTTPSubSessionOption
suffix string
conn connection.Connection
prevConnStat connection.Stat
staleStat *connection.Stat
stat StatSession
}
type HTTPSubSessionOption struct {
Conn net.Conn
ConnModOption connection.ModOption
UK string // unique key
Protocol string
URLCtx URLContext
IsWebSocket bool
WebSocketKey string
}
func NewHTTPSubSession(option HTTPSubSessionOption) *HTTPSubSession {
s := &HTTPSubSession{
HTTPSubSessionOption: option,
conn: connection.New(option.Conn, option.ConnModOption),
stat: StatSession{
Protocol: option.Protocol,
SessionID: option.UK,
StartTime: time.Now().Format("2006-01-02 15:04:05.999"),
RemoteAddr: option.Conn.RemoteAddr().String(),
},
}
return s
}
func (session *HTTPSubSession) RunLoop() error {
buf := make([]byte, 128)
_, err := session.conn.Read(buf)
return err
}
func (session *HTTPSubSession) WriteHTTPResponseHeader(b []byte) {
if session.IsWebSocket {
session.Write(UpdateWebSocketHeader(session.WebSocketKey))
} else {
session.Write(b)
}
}
func (session *HTTPSubSession) Write(b []byte) {
if session.IsWebSocket {
wsHeader := WSHeader{
Fin: true,
Rsv1: false,
Rsv2: false,
Rsv3: false,
Opcode: WSO_Binary,
PayloadLength: uint64(len(b)),
Masked: false,
}
session.write(MakeWSFrameHeader(wsHeader))
}
session.write(b)
}
func (session *HTTPSubSession) Dispose() error {
return session.conn.Close()
}
func (session *HTTPSubSession) URL() string {
return session.URLCtx.URL
}
func (session *HTTPSubSession) AppName() string {
return session.URLCtx.PathWithoutLastItem
}
func (session *HTTPSubSession) StreamName() string {
var suffix string
switch session.Protocol {
case ProtocolHTTPFLV:
suffix = ".flv"
case ProtocolHTTPTS:
suffix = ".ts"
default:
Logger.Warnf("[%s] acquire stream name but protocol unknown.", session.UK)
}
return strings.TrimSuffix(session.URLCtx.LastItemOfPath, suffix)
}
func (session *HTTPSubSession) RawQuery() string {
return session.URLCtx.RawQuery
}
func (session *HTTPSubSession) UniqueKey() string {
return session.UK
}
func (session *HTTPSubSession) GetStat() StatSession {
currStat := session.conn.GetStat()
session.stat.ReadBytesSum = currStat.ReadBytesSum
session.stat.WroteBytesSum = currStat.WroteBytesSum
return session.stat
}
func (session *HTTPSubSession) UpdateStat(intervalSec uint32) {
currStat := session.conn.GetStat()
rDiff := currStat.ReadBytesSum - session.prevConnStat.ReadBytesSum
session.stat.ReadBitrate = int(rDiff * 8 / 1024 / uint64(intervalSec))
wDiff := currStat.WroteBytesSum - session.prevConnStat.WroteBytesSum
session.stat.WriteBitrate = int(wDiff * 8 / 1024 / uint64(intervalSec))
session.stat.Bitrate = session.stat.WriteBitrate
session.prevConnStat = currStat
}
func (session *HTTPSubSession) IsAlive() (readAlive, writeAlive bool) {
currStat := session.conn.GetStat()
if session.staleStat == nil {
session.staleStat = new(connection.Stat)
*session.staleStat = currStat
return true, true
}
readAlive = !(currStat.ReadBytesSum-session.staleStat.ReadBytesSum == 0)
writeAlive = !(currStat.WroteBytesSum-session.staleStat.WroteBytesSum == 0)
*session.staleStat = currStat
return
}
func (session *HTTPSubSession) write(b []byte) {
_, _ = session.conn.Write(b)
}

@ -134,11 +134,11 @@ func ParseRTMPURL(rawURL string) (ctx URLContext, err error) {
}
func ParseHTTPFLVURL(rawURL string, isHTTPS bool) (ctx URLContext, err error) {
return parsehttpURL(rawURL, isHTTPS, ".flv")
return ParseHTTPURL(rawURL, isHTTPS, ".flv")
}
func ParseHTTPTSURL(rawURL string, isHTTPS bool) (ctx URLContext, err error) {
return parsehttpURL(rawURL, isHTTPS, ".ts")
return ParseHTTPURL(rawURL, isHTTPS, ".ts")
}
func ParseRTSPURL(rawURL string) (ctx URLContext, err error) {
@ -184,7 +184,7 @@ func parseURLPath(stdURL *url.URL) (ctx URLPathContext, err error) {
return ctx, nil
}
func parsehttpURL(rawURL string, isHTTPS bool, suffix string) (ctx URLContext, err error) {
func ParseHTTPURL(rawURL string, isHTTPS bool, suffix string) (ctx URLContext, err error) {
var defaultPort int
if isHTTPS {
defaultPort = DefaultHTTPSPort

@ -0,0 +1,15 @@
// Copyright 2021, Chef. All rights reserved.
// https://github.com/q191201771/lal
//
// Use of this source code is governed by a MIT-style license
// that can be found in the License file.
//
// Author: Chef (191201771@qq.com)
package base
import "github.com/q191201771/naza/pkg/nazalog"
var (
Logger = nazalog.GetGlobalLogger()
)

@ -33,7 +33,6 @@ type MuxerObserver interface {
}
type MuxerConfig struct {
Enable bool `json:"enable"` // 如果false说明hls功能没开也即不写文件但是MuxerObserver依然会回调
OutPath string `json:"out_path"` // m3u8和ts文件的输出根目录注意末尾需以'/'结束
FragmentDurationMS int `json:"fragment_duration_ms"`
FragmentNum int `json:"fragment_num"`
@ -66,6 +65,7 @@ type Muxer struct {
recordPlayListFilenameBak string // const after init
config *MuxerConfig
enable bool
observer MuxerObserver
fragment Fragment
@ -91,8 +91,9 @@ type fragmentInfo struct {
filename string
}
// @param enable 如果false说明hls功能没开也即不写文件但是MuxerObserver依然会回调
// @param observer 可以为nil如果不为nilTS流将回调给上层
func NewMuxer(streamName string, config *MuxerConfig, observer MuxerObserver) *Muxer {
func NewMuxer(streamName string, enable bool, config *MuxerConfig, observer MuxerObserver) *Muxer {
uk := base.GenUKHLSMuxer()
op := getMuxerOutPath(config.OutPath, streamName)
playlistFilename := getM3U8Filename(op, streamName)
@ -108,6 +109,7 @@ func NewMuxer(streamName string, config *MuxerConfig, observer MuxerObserver) *M
playlistFilenameBak: playlistFilenameBak,
recordPlayListFilename: recordPlaylistFilename,
recordPlayListFilenameBak: recordPlaylistFilenameBak,
enable: enable,
config: config,
observer: observer,
frags: frags,
@ -182,7 +184,7 @@ func (m *Muxer) OnFrame(streamer *Streamer, frame *mpegts.Frame) {
}
mpegts.PackTSPacket(frame, func(packet []byte) {
if m.config.Enable {
if m.enable {
if err := m.fragment.WriteFile(packet); err != nil {
nazalog.Errorf("[%s] fragment write error. err=%+v", m.UniqueKey, err)
return
@ -281,7 +283,7 @@ func (m *Muxer) openFragment(ts uint64, discont bool) error {
filename := getTSFilename(m.streamName, id, int(time.Now().Unix()))
filenameWithPath := getTSFilenameWithPath(m.outPath, filename)
if m.config.Enable {
if m.enable {
if err := m.fragment.OpenFile(filenameWithPath); err != nil {
return err
}
@ -311,7 +313,7 @@ func (m *Muxer) closeFragment(isLast bool) error {
return nil
}
if m.config.Enable {
if m.enable {
if err := m.fragment.CloseFile(); err != nil {
return err
}
@ -346,7 +348,7 @@ func (m *Muxer) closeFragment(isLast bool) error {
}
func (m *Muxer) writeRecordPlaylist(isLast bool) {
if !m.config.Enable {
if !m.enable {
return
}
@ -401,7 +403,7 @@ func (m *Muxer) writeRecordPlaylist(isLast bool) {
}
func (m *Muxer) writePlaylist(isLast bool) {
if !m.config.Enable {
if !m.enable {
return
}
@ -442,7 +444,7 @@ func (m *Muxer) writePlaylist(isLast bool) {
}
func (m *Muxer) ensureDir() {
if !m.config.Enable {
if !m.enable {
return
}
//err := fslCtx.RemoveAll(m.outPath)

@ -9,7 +9,6 @@
package hls
import (
"net"
"net/http"
"github.com/q191201771/lal/pkg/base"
@ -17,40 +16,40 @@ import (
"github.com/q191201771/naza/pkg/nazalog"
)
type Server struct {
addr string
type ServerHandler struct {
outPath string
ln net.Listener
httpSrv *http.Server
//addr string
//ln net.Listener
//httpSrv *http.Server
}
func NewServer(addr string, outPath string) *Server {
return &Server{
addr: addr,
func NewServerHandler(outPath string) *ServerHandler {
return &ServerHandler{
outPath: outPath,
}
}
func (s *Server) Listen() (err error) {
if s.ln, err = net.Listen("tcp", s.addr); err != nil {
return
}
s.httpSrv = &http.Server{Addr: s.addr, Handler: s}
nazalog.Infof("start hls server listen. addr=%s", s.addr)
return
}
func (s *Server) RunLoop() error {
return s.httpSrv.Serve(s.ln)
}
func (s *Server) Dispose() {
if err := s.httpSrv.Close(); err != nil {
nazalog.Error(err)
}
}
//
//func (s *Server) Listen() (err error) {
// if s.ln, err = net.Listen("tcp", s.addr); err != nil {
// return
// }
// s.httpSrv = &http.Server{Addr: s.addr, Handler: s}
// nazalog.Infof("start hls server listen. addr=%s", s.addr)
// return
//}
//
//func (s *Server) RunLoop() error {
// return s.httpSrv.Serve(s.ln)
//}
//
//func (s *Server) Dispose() {
// if err := s.httpSrv.Close(); err != nil {
// nazalog.Error(err)
// }
//}
func (s *Server) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
func (s *ServerHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
//nazalog.Debugf("%+v", req)
// TODO chef:

@ -1,143 +0,0 @@
// Copyright 2019, Chef. All rights reserved.
// https://github.com/q191201771/lal
//
// Use of this source code is governed by a MIT-style license
// that can be found in the License file.
//
// Author: Chef (191201771@qq.com)
package httpflv
import (
"crypto/tls"
"net"
"sync"
"github.com/q191201771/naza/pkg/nazalog"
)
type ServerObserver interface {
// 通知上层有新的拉流者
// 返回值: true则允许拉流false则关闭连接
OnNewHTTPFLVSubSession(session *SubSession) bool
OnDelHTTPFLVSubSession(session *SubSession)
}
type ServerConfig struct {
Enable bool `json:"enable"`
SubListenAddr string `json:"sub_listen_addr"`
EnableHTTPS bool `json:"enable_https"`
HTTPSAddr string `json:"https_addr"`
HTTPSCertFile string `json:"https_cert_file"`
HTTPSKeyFile string `json:"https_key_file"`
}
type Server struct {
observer ServerObserver
config ServerConfig
ln net.Listener
httpsLn net.Listener
}
// TODO chef: 监听太难看了考虑直接传入Listener对象或直接路由进来使得不同server可以共用端口
func NewServer(observer ServerObserver, config ServerConfig) *Server {
return &Server{
observer: observer,
config: config,
}
}
func (server *Server) Listen() (err error) {
if server.config.Enable {
if server.ln, err = net.Listen("tcp", server.config.SubListenAddr); err != nil {
return
}
nazalog.Infof("start httpflv server listen. addr=%s", server.config.SubListenAddr)
}
if server.config.EnableHTTPS {
var cert tls.Certificate
cert, err = tls.LoadX509KeyPair(server.config.HTTPSCertFile, server.config.HTTPSKeyFile)
if err != nil {
return err
}
tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}}
if server.httpsLn, err = tls.Listen("tcp", server.config.HTTPSAddr, tlsConfig); err != nil {
return
}
nazalog.Infof("start httpsflv server listen. addr=%s", server.config.HTTPSAddr)
}
return
}
func (server *Server) RunLoop() error {
var wg sync.WaitGroup
// TODO chef: 临时这么搞,错误值丢失了,重构一下
if server.ln != nil {
wg.Add(1)
go func() {
for {
conn, err := server.ln.Accept()
if err != nil {
break
}
go server.handleConnect(conn, "http")
}
wg.Done()
}()
}
if server.httpsLn != nil {
wg.Add(1)
go func() {
for {
conn, err := server.httpsLn.Accept()
if err != nil {
break
}
go server.handleConnect(conn, "https")
}
wg.Done()
}()
}
wg.Wait()
return nil
}
func (server *Server) Dispose() {
if server.ln != nil {
if err := server.ln.Close(); err != nil {
nazalog.Error(err)
}
}
if server.httpsLn != nil {
if err := server.httpsLn.Close(); err != nil {
nazalog.Error(err)
}
}
}
func (server *Server) handleConnect(conn net.Conn, scheme string) {
nazalog.Infof("accept a httpflv connection. remoteAddr=%s", conn.RemoteAddr().String())
session := NewSubSession(conn, scheme)
if err := session.ReadRequest(); err != nil {
nazalog.Errorf("[%s] read httpflv SubSession request error. err=%v", session.uniqueKey, err)
return
}
nazalog.Debugf("[%s] < read http request. url=%s", session.uniqueKey, session.URL())
if !server.observer.OnNewHTTPFLVSubSession(session) {
session.Dispose()
}
err := session.RunLoop()
nazalog.Debugf("[%s] httpflv sub session loop done. err=%v", session.uniqueKey, err)
server.observer.OnDelHTTPFLVSubSession(session)
}

@ -9,15 +9,10 @@
package httpflv
import (
"fmt"
"net"
"strings"
"time"
"github.com/q191201771/lal/pkg/base"
"github.com/q191201771/naza/pkg/nazahttp"
"github.com/q191201771/naza/pkg/connection"
"github.com/q191201771/naza/pkg/nazalog"
@ -26,171 +21,48 @@ import (
var flvHTTPResponseHeader []byte
type SubSession struct {
uniqueKey string
IsFresh bool
scheme string
pathWithRawQuery string
headers map[string]string
urlCtx base.URLContext
conn connection.Connection
prevConnStat connection.Stat
staleStat *connection.Stat
stat base.StatSession
isWebSocket bool
*base.HTTPSubSession // 直接使用它提供的函数
IsFresh bool
}
func NewSubSession(conn net.Conn, scheme string) *SubSession {
func NewSubSession(conn net.Conn, urlCtx base.URLContext, isWebSocket bool, websocketKey string) *SubSession {
uk := base.GenUKFLVSubSession()
s := &SubSession{
uniqueKey: uk,
scheme: scheme,
IsFresh: true,
conn: connection.New(conn, func(option *connection.Option) {
option.ReadBufSize = readBufSize
option.WriteChanSize = wChanSize
option.WriteTimeoutMS = subSessionWriteTimeoutMS
base.NewHTTPSubSession(base.HTTPSubSessionOption{
Conn: conn,
ConnModOption: func(option *connection.Option) {
option.WriteChanSize = SubSessionWriteChanSize
option.WriteTimeoutMS = SubSessionWriteTimeoutMS
},
UK: uk,
Protocol: base.ProtocolHTTPFLV,
URLCtx: urlCtx,
IsWebSocket: isWebSocket,
WebSocketKey: websocketKey,
}),
stat: base.StatSession{
Protocol: base.ProtocolHTTPFLV,
SessionID: uk,
StartTime: time.Now().Format("2006-01-02 15:04:05.999"),
RemoteAddr: conn.RemoteAddr().String(),
},
true,
}
nazalog.Infof("[%s] lifecycle new httpflv SubSession. session=%p, remote addr=%s", uk, s, conn.RemoteAddr().String())
return s
}
// TODO chef: read request timeout
func (session *SubSession) ReadRequest() (err error) {
defer func() {
if err != nil {
session.Dispose()
}
}()
var requestLine string
if requestLine, session.headers, err = nazahttp.ReadHTTPHeader(session.conn); err != nil {
return
}
if _, session.pathWithRawQuery, _, err = nazahttp.ParseHTTPRequestLine(requestLine); err != nil {
return
}
rawURL := fmt.Sprintf("%s://%s%s", session.scheme, session.headers["Host"], session.pathWithRawQuery)
_ = rawURL
session.urlCtx, err = base.ParseHTTPFLVURL(rawURL, session.scheme == "https")
if session.headers["Connection"] == "Upgrade" && session.headers["Upgrade"] == "websocket" {
session.isWebSocket = true
//回复升级为websocket
session.writeRawPacket(base.UpdateWebSocketHeader(session.headers["Sec-WebSocket-Key"]))
}
return
}
func (session *SubSession) RunLoop() error {
buf := make([]byte, 128)
_, err := session.conn.Read(buf)
return err
}
func (session *SubSession) WriteHTTPResponseHeader() {
nazalog.Debugf("[%s] > W http response header.", session.uniqueKey)
if session.isWebSocket {
} else {
session.WriteRawPacket(flvHTTPResponseHeader)
}
nazalog.Debugf("[%s] > W http response header.", session.UniqueKey())
session.HTTPSubSession.WriteHTTPResponseHeader(flvHTTPResponseHeader)
}
func (session *SubSession) WriteFLVHeader() {
nazalog.Debugf("[%s] > W http flv header.", session.uniqueKey)
session.WriteRawPacket(FLVHeader)
nazalog.Debugf("[%s] > W http flv header.", session.UniqueKey())
session.Write(FLVHeader)
}
func (session *SubSession) WriteTag(tag *Tag) {
session.WriteRawPacket(tag.Raw)
}
func (session *SubSession) WriteRawPacket(pkt []byte) {
if session.isWebSocket {
wsHeader := base.WSHeader{
Fin: true,
Rsv1: false,
Rsv2: false,
Rsv3: false,
Opcode: base.WSO_Binary,
PayloadLength: uint64(len(pkt)),
Masked: false,
}
session.writeRawPacket(base.MakeWSFrameHeader(wsHeader))
}
session.writeRawPacket(pkt)
}
func (session *SubSession) writeRawPacket(pkt []byte) {
_, _ = session.conn.Write(pkt)
session.Write(tag.Raw)
}
func (session *SubSession) Dispose() error {
nazalog.Infof("[%s] lifecycle dispose httpflv SubSession.", session.uniqueKey)
return session.conn.Close()
}
func (session *SubSession) URL() string {
return session.urlCtx.URL
}
func (session *SubSession) AppName() string {
return session.urlCtx.PathWithoutLastItem
}
func (session *SubSession) StreamName() string {
return strings.TrimSuffix(session.urlCtx.LastItemOfPath, ".flv")
}
func (session *SubSession) RawQuery() string {
return session.urlCtx.RawQuery
}
func (session *SubSession) UniqueKey() string {
return session.uniqueKey
}
func (session *SubSession) GetStat() base.StatSession {
currStat := session.conn.GetStat()
session.stat.ReadBytesSum = currStat.ReadBytesSum
session.stat.WroteBytesSum = currStat.WroteBytesSum
return session.stat
}
func (session *SubSession) UpdateStat(intervalSec uint32) {
currStat := session.conn.GetStat()
rDiff := currStat.ReadBytesSum - session.prevConnStat.ReadBytesSum
session.stat.ReadBitrate = int(rDiff * 8 / 1024 / uint64(intervalSec))
wDiff := currStat.WroteBytesSum - session.prevConnStat.WroteBytesSum
session.stat.WriteBitrate = int(wDiff * 8 / 1024 / uint64(intervalSec))
session.stat.Bitrate = session.stat.WriteBitrate
session.prevConnStat = currStat
}
func (session *SubSession) IsAlive() (readAlive, writeAlive bool) {
currStat := session.conn.GetStat()
if session.staleStat == nil {
session.staleStat = new(connection.Stat)
*session.staleStat = currStat
return true, true
}
readAlive = !(currStat.ReadBytesSum-session.staleStat.ReadBytesSum == 0)
writeAlive = !(currStat.WroteBytesSum-session.staleStat.WroteBytesSum == 0)
*session.staleStat = currStat
return
nazalog.Infof("[%s] lifecycle dispose httpflv SubSession.", session.UniqueKey())
return session.HTTPSubSession.Dispose()
}
func init() {

@ -8,8 +8,10 @@
package httpflv
var readBufSize = 256 //16384 // ClientPullSession 和 SubSession 读取数据时
var wChanSize = 1024 // SubSession 发送数据时 channel 的大小
var subSessionWriteTimeoutMS = 10000
var (
SubSessionWriteChanSize = 1024 // SubSession发送数据时channel的大小
SubSessionWriteTimeoutMS = 10000
FLVHeader = []byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00}
)
var FLVHeader = []byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00}
var readBufSize = 256 //16384 // ClientPullSession读取数据时

@ -1,81 +0,0 @@
// Copyright 2020, Chef. All rights reserved.
// https://github.com/q191201771/lal
//
// Use of this source code is governed by a MIT-style license
// that can be found in the License file.
//
// Author: Chef (191201771@qq.com)
package httpts
import (
"net"
log "github.com/q191201771/naza/pkg/nazalog"
)
type ServerObserver interface {
// 通知上层有新的拉流者
// 返回值: true则允许拉流false则关闭连接
OnNewHTTPTSSubSession(session *SubSession) bool
OnDelHTTPTSSubSession(session *SubSession)
}
type Server struct {
observer ServerObserver
addr string
ln net.Listener
}
func NewServer(observer ServerObserver, addr string) *Server {
return &Server{
observer: observer,
addr: addr,
}
}
func (server *Server) Listen() (err error) {
if server.ln, err = net.Listen("tcp", server.addr); err != nil {
return
}
log.Infof("start httpts server listen. addr=%s", server.addr)
return
}
func (server *Server) RunLoop() error {
for {
conn, err := server.ln.Accept()
if err != nil {
return err
}
go server.handleConnect(conn)
}
}
func (server *Server) Dispose() {
if server.ln == nil {
return
}
if err := server.ln.Close(); err != nil {
log.Error(err)
}
}
func (server *Server) handleConnect(conn net.Conn) {
log.Infof("accept a httpts connection. remoteAddr=%s", conn.RemoteAddr().String())
session := NewSubSession(conn, "http")
if err := session.ReadRequest(); err != nil {
log.Errorf("[%s] read httpts SubSession request error. err=%v", session.uniqueKey, err)
return
}
log.Debugf("[%s] < read http request. url=%s", session.uniqueKey, session.URL())
if !server.observer.OnNewHTTPTSSubSession(session) {
session.Dispose()
}
err := session.RunLoop()
log.Debugf("[%s] httpts sub session loop done. err=%v", session.uniqueKey, err)
server.observer.OnDelHTTPTSSubSession(session)
}

@ -9,173 +9,49 @@
package httpts
import (
"fmt"
"net"
"strings"
"time"
"github.com/q191201771/lal/pkg/base"
"github.com/q191201771/naza/pkg/connection"
"github.com/q191201771/naza/pkg/nazahttp"
"github.com/q191201771/naza/pkg/nazalog"
)
var tsHTTPResponseHeader []byte
type SubSession struct {
uniqueKey string
IsFresh bool
scheme string
pathWithRawQuery string
headers map[string]string
urlCtx base.URLContext
conn connection.Connection
prevConnStat connection.Stat
staleStat *connection.Stat
stat base.StatSession
isWebSocket bool
*base.HTTPSubSession // 直接使用它提供的函数
IsFresh bool
}
func NewSubSession(conn net.Conn, scheme string) *SubSession {
func NewSubSession(conn net.Conn, urlCtx base.URLContext, isWebSocket bool, websocketKey string) *SubSession {
uk := base.GenUKTSSubSession()
s := &SubSession{
uniqueKey: uk,
scheme: scheme,
IsFresh: true,
conn: connection.New(conn, func(option *connection.Option) {
option.ReadBufSize = readBufSize
option.WriteChanSize = wChanSize
option.WriteTimeoutMS = subSessionWriteTimeoutMS
base.NewHTTPSubSession(base.HTTPSubSessionOption{
Conn: conn,
ConnModOption: func(option *connection.Option) {
option.WriteChanSize = SubSessionWriteChanSize
option.WriteTimeoutMS = SubSessionWriteTimeoutMS
},
UK: uk,
Protocol: base.ProtocolHTTPTS,
URLCtx: urlCtx,
IsWebSocket: isWebSocket,
WebSocketKey: websocketKey,
}),
stat: base.StatSession{
Protocol: base.ProtocolHTTPTS,
SessionID: uk,
StartTime: time.Now().Format("2006-01-02 15:04:05.999"),
RemoteAddr: conn.RemoteAddr().String(),
},
true,
}
nazalog.Infof("[%s] lifecycle new httpts SubSession. session=%p, remote addr=%s", uk, s, conn.RemoteAddr().String())
return s
}
// TODO chef: read request timeout
func (session *SubSession) ReadRequest() (err error) {
defer func() {
if err != nil {
session.Dispose()
}
}()
var requestLine string
if requestLine, session.headers, err = nazahttp.ReadHTTPHeader(session.conn); err != nil {
return
}
if _, session.pathWithRawQuery, _, err = nazahttp.ParseHTTPRequestLine(requestLine); err != nil {
return
}
rawURL := fmt.Sprintf("%s://%s%s", session.scheme, session.headers["Host"], session.pathWithRawQuery)
_ = rawURL
session.urlCtx, err = base.ParseHTTPTSURL(rawURL, session.scheme == "https")
if session.headers["Connection"] == "Upgrade" && session.headers["Upgrade"] == "websocket" {
session.isWebSocket = true
//回复升级为websocket
session.writeRawPacket(base.UpdateWebSocketHeader(session.headers["Sec-WebSocket-Key"]))
}
return
}
func (session *SubSession) RunLoop() error {
buf := make([]byte, 128)
_, err := session.conn.Read(buf)
return err
}
func (session *SubSession) WriteHTTPResponseHeader() {
nazalog.Debugf("[%s] > W http response header.", session.uniqueKey)
if session.isWebSocket {
} else {
session.WriteRawPacket(tsHTTPResponseHeader)
}
nazalog.Debugf("[%s] > W http response header.", session.UniqueKey())
session.HTTPSubSession.WriteHTTPResponseHeader(tsHTTPResponseHeader)
}
func (session *SubSession) WriteRawPacket(pkt []byte) {
if session.isWebSocket {
wsHeader := base.WSHeader{
Fin: true,
Rsv1: false,
Rsv2: false,
Rsv3: false,
Opcode: base.WSO_Binary,
PayloadLength: uint64(len(pkt)),
Masked: false,
}
session.writeRawPacket(base.MakeWSFrameHeader(wsHeader))
}
session.writeRawPacket(pkt)
}
func (session *SubSession) writeRawPacket(pkt []byte) {
_, _ = session.conn.Write(pkt)
}
func (session *SubSession) Dispose() error {
nazalog.Infof("[%s] lifecycle dispose httpts SubSession.", session.uniqueKey)
return session.conn.Close()
}
func (session *SubSession) UpdateStat(intervalSec uint32) {
currStat := session.conn.GetStat()
rDiff := currStat.ReadBytesSum - session.prevConnStat.ReadBytesSum
session.stat.ReadBitrate = int(rDiff * 8 / 1024 / uint64(intervalSec))
wDiff := currStat.WroteBytesSum - session.prevConnStat.WroteBytesSum
session.stat.WriteBitrate = int(wDiff * 8 / 1024 / uint64(intervalSec))
session.stat.Bitrate = session.stat.WriteBitrate
session.prevConnStat = currStat
}
func (session *SubSession) GetStat() base.StatSession {
connStat := session.conn.GetStat()
session.stat.ReadBytesSum = connStat.ReadBytesSum
session.stat.WroteBytesSum = connStat.WroteBytesSum
return session.stat
}
func (session *SubSession) IsAlive() (readAlive, writeAlive bool) {
currStat := session.conn.GetStat()
if session.staleStat == nil {
session.staleStat = new(connection.Stat)
*session.staleStat = currStat
return true, true
}
readAlive = !(currStat.ReadBytesSum-session.staleStat.ReadBytesSum == 0)
writeAlive = !(currStat.WroteBytesSum-session.staleStat.WroteBytesSum == 0)
*session.staleStat = currStat
return
}
func (session *SubSession) URL() string {
return session.urlCtx.URL
}
func (session *SubSession) AppName() string {
return session.urlCtx.PathWithoutLastItem
}
func (session *SubSession) StreamName() string {
return strings.TrimSuffix(session.urlCtx.LastItemOfPath, ".ts")
}
func (session *SubSession) RawQuery() string {
return session.urlCtx.RawQuery
}
func (session *SubSession) UniqueKey() string {
return session.uniqueKey
nazalog.Infof("[%s] lifecycle dispose httpts SubSession.", session.UniqueKey())
return session.HTTPSubSession.Dispose()
}
func init() {

@ -8,6 +8,7 @@
package httpts
var readBufSize = 256 //16384 // SubSession读取数据时
var wChanSize = 1024 // SubSession发送数据时channel的大小
var subSessionWriteTimeoutMS = 10000
var (
SubSessionWriteChanSize = 1024
SubSessionWriteTimeoutMS = 10000
)

@ -10,7 +10,6 @@ package innertest
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
@ -75,19 +74,16 @@ func InnerTestEntry(t *testing.T) {
var err error
go logic.Entry(confFile)
logic.Init(confFile)
go logic.RunLoop()
time.Sleep(200 * time.Millisecond)
var config logic.Config
rawContent, err := ioutil.ReadFile(confFile)
nazalog.Assert(nil, err)
err = json.Unmarshal(rawContent, &config)
nazalog.Assert(nil, err)
config := logic.GetConfig()
_ = os.RemoveAll(config.HLSConfig.OutPath)
pushURL = fmt.Sprintf("rtmp://127.0.0.1%s/live/innertest", config.RTMPConfig.Addr)
httpflvPullURL = fmt.Sprintf("http://127.0.0.1%s/live/innertest.flv", config.HTTPFLVConfig.SubListenAddr)
httpflvPullURL = fmt.Sprintf("http://127.0.0.1%s/live/innertest.flv", config.HTTPFLVConfig.HTTPListenAddr)
rtmpPullURL = fmt.Sprintf("rtmp://127.0.0.1%s/live/innertest", config.RTMPConfig.Addr)
err = fileReader.Open(rFLVFileName)

@ -9,24 +9,23 @@
package logic
import (
"github.com/q191201771/lal/pkg/httpflv"
"github.com/q191201771/lal/pkg/hls"
"github.com/q191201771/naza/pkg/nazalog"
)
const ConfVersion = "v0.1.2"
const ConfVersion = "v0.2.0"
type Config struct {
ConfVersion string `json:"conf_version"`
RTMPConfig RTMPConfig `json:"rtmp"`
HTTPFLVConfig HTTPFLVConfig `json:"httpflv"`
HLSConfig HLSConfig `json:"hls"`
HTTPTSConfig HTTPTSConfig `json:"httpts"`
RTSPConfig RTSPConfig `json:"rtsp"`
RecordConfig RecordConfig `json:"record"`
RelayPushConfig RelayPushConfig `json:"relay_push"`
RelayPullConfig RelayPullConfig `json:"relay_pull"`
ConfVersion string `json:"conf_version"`
RTMPConfig RTMPConfig `json:"rtmp"`
DefaultHTTPConfig DefaultHTTPConfig `json:"default_http"`
HTTPFLVConfig HTTPFLVConfig `json:"httpflv"`
HLSConfig HLSConfig `json:"hls"`
HTTPTSConfig HTTPTSConfig `json:"httpts"`
RTSPConfig RTSPConfig `json:"rtsp"`
RecordConfig RecordConfig `json:"record"`
RelayPushConfig RelayPushConfig `json:"relay_push"`
RelayPullConfig RelayPullConfig `json:"relay_pull"`
HTTPAPIConfig HTTPAPIConfig `json:"http_api"`
ServerID string `json:"server_id"`
@ -41,19 +40,24 @@ type RTMPConfig struct {
GOPNum int `json:"gop_num"`
}
type DefaultHTTPConfig struct {
CommonHTTPAddrConfig
}
type HTTPFLVConfig struct {
httpflv.ServerConfig
CommonHTTPServerConfig
GOPNum int `json:"gop_num"`
}
type HTTPTSConfig struct {
Enable bool `json:"enable"`
SubListenAddr string `json:"sub_listen_addr"`
CommonHTTPServerConfig
}
type HLSConfig struct {
SubListenAddr string `json:"sub_listen_addr"`
UseMemoryAsDiskFlag bool `json:"use_memory_as_disk_flag"`
CommonHTTPServerConfig
UseMemoryAsDiskFlag bool `json:"use_memory_as_disk_flag"`
hls.MuxerConfig
}
@ -100,3 +104,17 @@ type PProfConfig struct {
Enable bool `json:"enable"`
Addr string `json:"addr"`
}
type CommonHTTPServerConfig struct {
CommonHTTPAddrConfig
Enable bool `json:"enable"`
EnableHTTPS bool `json:"enable_https"`
}
type CommonHTTPAddrConfig struct {
HTTPListenAddr string `json:"http_listen_addr"`
HTTPSListenAddr string `json:"https_listen_addr"`
HTTPSCertFile string `json:"https_cert_file"`
HTTPSKeyFile string `json:"https_key_file"`
}

@ -32,11 +32,16 @@ var (
sm *ServerManager
)
func Entry(confFile string) {
// TODO(chef) 临时供innertest使用后面应该重构
func GetConfig() *Config {
return config
}
func Init(confFile string) {
LoadConfAndInitLog(confFile)
if dir, err := os.Getwd(); err == nil {
nazalog.Infof("wd: %s", dir)
}
dir, _ := os.Getwd()
nazalog.Infof("wd: %s", dir)
nazalog.Infof("args: %s", strings.Join(os.Args, " "))
nazalog.Infof("bininfo: %s", bininfo.StringifySingleLine())
nazalog.Infof("version: %s", base.LALFullInfo)
@ -56,7 +61,9 @@ func Entry(confFile string) {
nazalog.Errorf("record mpegts mkdir error. path=%s, err=%+v", config.RecordConfig.MPEGTSOutPath, err)
}
}
}
func RunLoop() {
sm = NewServerManager()
if config.PProfConfig.Enable {
@ -164,6 +171,11 @@ func LoadConfAndInitLog(confFile string) *Config {
}
}
// 如果具体的HTTP应用没有设置HTTP监听相关的配置则尝试使用全局配置
mergeCommonHTTPAddrConfig(&config.HTTPFLVConfig.CommonHTTPAddrConfig, &config.DefaultHTTPConfig.CommonHTTPAddrConfig)
mergeCommonHTTPAddrConfig(&config.HTTPTSConfig.CommonHTTPAddrConfig, &config.DefaultHTTPConfig.CommonHTTPAddrConfig)
mergeCommonHTTPAddrConfig(&config.HLSConfig.CommonHTTPAddrConfig, &config.DefaultHTTPConfig.CommonHTTPAddrConfig)
// 配置不存在时,设置默认值
if !j.Exist("hls.cleanup_mode") {
const defaultMode = hls.CleanupModeInTheEnd
@ -197,3 +209,18 @@ func runWebPProf(addr string) {
return
}
}
func mergeCommonHTTPAddrConfig(dst, src *CommonHTTPAddrConfig) {
if dst.HTTPListenAddr == "" && src.HTTPListenAddr != "" {
dst.HTTPListenAddr = src.HTTPListenAddr
}
if dst.HTTPSListenAddr == "" && src.HTTPSListenAddr != "" {
dst.HTTPSListenAddr = src.HTTPSListenAddr
}
if dst.HTTPSCertFile == "" && src.HTTPSCertFile != "" {
dst.HTTPSCertFile = src.HTTPSCertFile
}
if dst.HTTPSKeyFile == "" && src.HTTPSKeyFile != "" {
dst.HTTPSKeyFile = src.HTTPSKeyFile
}
}

@ -392,7 +392,7 @@ func (group *Group) DelHTTPFLVSubSession(session *httpflv.SubSession) {
// 这里应该也要考虑触发hls muxer开启
// 也即HTTPTS sub需要使用hls muxerhls muxer开启和关闭都要考虑HTTPTS sub
func (group *Group) AddHTTPTSSubSession(session *httpts.SubSession) {
nazalog.Debugf("[%s] [%s] add httpflv SubSession into group.", group.UniqueKey, session.UniqueKey())
nazalog.Debugf("[%s] [%s] add httpts SubSession into group.", group.UniqueKey, session.UniqueKey())
session.WriteHTTPResponseHeader()
group.mutex.Lock()
@ -498,12 +498,12 @@ func (group *Group) OnTSPackets(rawFrame []byte, boundary bool) {
for session := range group.httptsSubSessionSet {
if session.IsFresh {
if boundary {
session.WriteRawPacket(group.patpmt)
session.WriteRawPacket(rawFrame)
session.Write(group.patpmt)
session.Write(rawFrame)
session.IsFresh = false
}
} else {
session.WriteRawPacket(rawFrame)
session.Write(rawFrame)
}
}
@ -807,24 +807,24 @@ func (group *Group) broadcastRTMP(msg base.RTMPMsg) {
for session := range group.httpflvSubSessionSet {
if session.IsFresh {
if group.httpflvGopCache.Metadata != nil {
session.WriteRawPacket(group.httpflvGopCache.Metadata)
session.Write(group.httpflvGopCache.Metadata)
}
if group.httpflvGopCache.VideoSeqHeader != nil {
session.WriteRawPacket(group.httpflvGopCache.VideoSeqHeader)
session.Write(group.httpflvGopCache.VideoSeqHeader)
}
if group.httpflvGopCache.AACSeqHeader != nil {
session.WriteRawPacket(group.httpflvGopCache.AACSeqHeader)
session.Write(group.httpflvGopCache.AACSeqHeader)
}
for i := 0; i < group.httpflvGopCache.GetGOPCount(); i++ {
for _, item := range group.httpflvGopCache.GetGOPDataAt(i) {
session.WriteRawPacket(item)
session.Write(item)
}
}
session.IsFresh = false
}
session.WriteRawPacket(lrm2ft.Get())
session.Write(lrm2ft.Get())
}
// # 5. 录制flv文件
@ -1017,7 +1017,8 @@ func (group *Group) addIn() {
if group.hlsMuxer != nil {
nazalog.Errorf("[%s] hls muxer exist while addIn. muxer=%+v", group.UniqueKey, group.hlsMuxer)
}
group.hlsMuxer = hls.NewMuxer(group.streamName, &config.HLSConfig.MuxerConfig, group)
enable := config.HLSConfig.Enable || config.HLSConfig.EnableHTTPS
group.hlsMuxer = hls.NewMuxer(group.streamName, enable, &config.HLSConfig.MuxerConfig, group)
group.hlsMuxer.Start()
}

@ -0,0 +1,113 @@
// Copyright 2021, Chef. All rights reserved.
// https://github.com/q191201771/lal
//
// Use of this source code is governed by a MIT-style license
// that can be found in the License file.
//
// Author: Chef (191201771@qq.com)
package logic
import (
"fmt"
"net/http"
"strings"
"github.com/q191201771/lal/pkg/base"
"github.com/q191201771/lal/pkg/httpflv"
"github.com/q191201771/lal/pkg/httpts"
"github.com/q191201771/naza/pkg/nazalog"
)
type HTTPServerHandlerObserver interface {
// 通知上层有新的拉流者
// 返回值: true则允许拉流false则关闭连接
OnNewHTTPFLVSubSession(session *httpflv.SubSession) bool
OnDelHTTPFLVSubSession(session *httpflv.SubSession)
OnNewHTTPTSSubSession(session *httpts.SubSession) bool
OnDelHTTPTSSubSession(session *httpts.SubSession)
}
type HTTPServerHandler struct {
observer HTTPServerHandlerObserver
}
func NewHTTPServerHandler(observer HTTPServerHandlerObserver) *HTTPServerHandler {
return &HTTPServerHandler{
observer: observer,
}
}
func (h *HTTPServerHandler) ServeSubSession(writer http.ResponseWriter, req *http.Request) {
var (
isHTTPS bool
scheme string
)
// TODO(chef) 这里scheme直接使用http和https没有考虑ws和wss注意后续的逻辑可能会依赖此处
if req.TLS == nil {
isHTTPS = false
scheme = "http"
} else {
isHTTPS = true
scheme = "https"
}
rawURL := fmt.Sprintf("%s://%s%s", scheme, req.Host, req.RequestURI)
conn, bio, err := writer.(http.Hijacker).Hijack()
if err != nil {
nazalog.Errorf("hijack failed. err=%+v", err)
return
}
if bio.Reader.Buffered() != 0 || bio.Writer.Buffered() != 0 {
nazalog.Errorf("hijack but buffer not empty. rb=%d, wb=%d", bio.Reader.Buffered(), bio.Writer.Buffered())
}
var (
isWebSocket bool
webSocketKey string
)
if req.Header.Get("Connection") == "Upgrade" && req.Header.Get("Upgrade") == "websocket" {
isWebSocket = true
webSocketKey = req.Header.Get("Sec-WebSocket-Key")
}
if strings.HasSuffix(rawURL, ".flv") {
urlCtx, err := base.ParseHTTPURL(rawURL, isHTTPS, ".flv")
if err != nil {
nazalog.Errorf("parse http url failed. err=%+v", err)
_ = conn.Close()
return
}
session := httpflv.NewSubSession(conn, urlCtx, isWebSocket, webSocketKey)
nazalog.Debugf("[%s] < read http request. url=%s", session.UniqueKey(), session.URL())
if !h.observer.OnNewHTTPFLVSubSession(session) {
session.Dispose()
}
err = session.RunLoop()
nazalog.Debugf("[%s] httpflv sub session loop done. err=%v", session.UniqueKey(), err)
h.observer.OnDelHTTPFLVSubSession(session)
return
}
if strings.HasSuffix(rawURL, ".ts") {
urlCtx, err := base.ParseHTTPURL(rawURL, isHTTPS, ".ts")
if err != nil {
nazalog.Errorf("parse http url failed. err=%+v", err)
_ = conn.Close()
return
}
session := httpts.NewSubSession(conn, urlCtx, isWebSocket, webSocketKey)
nazalog.Debugf("[%s] < read http request. url=%s", session.UniqueKey(), session.URL())
if !h.observer.OnNewHTTPTSSubSession(session) {
session.Dispose()
}
err = session.RunLoop()
nazalog.Debugf("[%s] httpts sub session loop done. err=%v", session.UniqueKey(), err)
h.observer.OnDelHTTPTSSubSession(session)
return
}
}

@ -17,6 +17,8 @@ import (
"github.com/q191201771/lal/pkg/rtsp"
)
// TODO(chef) add base.HTTPSubSession
// client.pub: rtmp, rtsp
// client.sub: rtmp, rtsp, flv, ts
// server.push: rtmp, rtsp
@ -132,8 +134,7 @@ var (
var _ rtmp.ServerObserver = &ServerManager{}
var _ rtsp.ServerObserver = &ServerManager{}
var _ httpflv.ServerObserver = &ServerManager{}
var _ httpts.ServerObserver = &ServerManager{}
var _ HTTPServerHandlerObserver = &ServerManager{}
var _ HTTPAPIServerObserver = &ServerManager{}

@ -13,24 +13,25 @@ import (
"sync"
"time"
"github.com/q191201771/lal/pkg/hls"
"github.com/q191201771/lal/pkg/base"
"github.com/q191201771/lal/pkg/httpts"
"github.com/q191201771/lal/pkg/rtsp"
"github.com/q191201771/lal/pkg/hls"
"github.com/q191201771/lal/pkg/httpflv"
"github.com/q191201771/lal/pkg/rtmp"
"github.com/q191201771/naza/pkg/nazalog"
)
type ServerManager struct {
httpServerManager *base.HTTPServerManager
httpServerHandler *HTTPServerHandler
hlsServerHandler *hls.ServerHandler
rtmpServer *rtmp.Server
httpflvServer *httpflv.Server
hlsServer *hls.Server
httptsServer *httpts.Server
rtspServer *rtsp.Server
httpAPIServer *HTTPAPIServer
exitChan chan struct{}
@ -44,18 +45,18 @@ func NewServerManager() *ServerManager {
groupMap: make(map[string]*Group),
exitChan: make(chan struct{}),
}
if config.HTTPFLVConfig.Enable || config.HTTPFLVConfig.EnableHTTPS ||
config.HTTPTSConfig.Enable || config.HTTPTSConfig.EnableHTTPS ||
config.HLSConfig.Enable || config.HLSConfig.EnableHTTPS {
m.httpServerManager = base.NewHTTPServerManager()
m.httpServerHandler = NewHTTPServerHandler(m)
m.hlsServerHandler = hls.NewServerHandler(config.HLSConfig.OutPath)
}
if config.RTMPConfig.Enable {
m.rtmpServer = rtmp.NewServer(m, config.RTMPConfig.Addr)
}
if config.HTTPFLVConfig.Enable || config.HTTPFLVConfig.EnableHTTPS {
m.httpflvServer = httpflv.NewServer(m, config.HTTPFLVConfig.ServerConfig)
}
if config.HLSConfig.Enable {
m.hlsServer = hls.NewServer(config.HLSConfig.SubListenAddr, config.HLSConfig.OutPath)
}
if config.HTTPTSConfig.Enable {
m.httptsServer = httpts.NewServer(m, config.HTTPTSConfig.SubListenAddr)
}
if config.RTSPConfig.Enable {
m.rtspServer = rtsp.NewServer(config.RTSPConfig.Addr, m)
}
@ -68,50 +69,72 @@ func NewServerManager() *ServerManager {
func (sm *ServerManager) RunLoop() error {
httpNotify.OnServerStart()
if sm.rtmpServer != nil {
if err := sm.rtmpServer.Listen(); err != nil {
return err
var addMux = func(config CommonHTTPServerConfig, pattern string, handler base.Handler, name string) error {
if config.Enable {
err := sm.httpServerManager.AddListen(
base.LocalAddrCtx{Addr: config.HTTPListenAddr},
pattern,
handler,
)
if err != nil {
nazalog.Infof("add http listen for %s failed. addr=%s, pattern=%s, err=%+v", name, config.HTTPListenAddr, pattern, err)
return err
}
nazalog.Infof("add http listen for %s. addr=%s, pattern=%s", name, config.HTTPListenAddr, pattern)
}
go func() {
if err := sm.rtmpServer.RunLoop(); err != nil {
nazalog.Error(err)
if config.EnableHTTPS {
err := sm.httpServerManager.AddListen(
base.LocalAddrCtx{IsHTTPS: true, Addr: config.HTTPSListenAddr, CertFile: config.HTTPSCertFile, KeyFile: config.HTTPSKeyFile},
pattern,
handler,
)
if err != nil {
nazalog.Infof("add https listen for %s failed. addr=%s, pattern=%s, err=%+v", name, config.HTTPListenAddr, pattern, err)
return err
}
}()
nazalog.Infof("add https listen for %s. addr=%s, pattern=%s", name, config.HTTPSListenAddr, pattern)
}
return nil
}
if sm.httpflvServer != nil {
if err := sm.httpflvServer.Listen(); err != nil {
return err
}
go func() {
if err := sm.httpflvServer.RunLoop(); err != nil {
nazalog.Error(err)
}
}()
if err := addMux(config.HTTPFLVConfig.CommonHTTPServerConfig, HTTPFLVURLPath, sm.httpServerHandler.ServeSubSession, "httpflv"); err != nil {
return err
}
if err := addMux(config.HTTPTSConfig.CommonHTTPServerConfig, HTTPTSURLPath, sm.httpServerHandler.ServeSubSession, "httpts"); err != nil {
return err
}
if err := addMux(config.HTTPTSConfig.CommonHTTPServerConfig, HLSURLPath, sm.hlsServerHandler.ServeHTTP, "hls"); err != nil {
return err
}
if sm.httptsServer != nil {
if err := sm.httptsServer.Listen(); err != nil {
return err
go func() {
if err := sm.httpServerManager.RunLoop(); err != nil {
nazalog.Error(err)
}
go func() {
if err := sm.httptsServer.RunLoop(); err != nil {
nazalog.Error(err)
}
}()
}
}()
if sm.hlsServer != nil {
if err := sm.hlsServer.Listen(); err != nil {
if sm.rtmpServer != nil {
if err := sm.rtmpServer.Listen(); err != nil {
return err
}
go func() {
if err := sm.hlsServer.RunLoop(); err != nil {
if err := sm.rtmpServer.RunLoop(); err != nil {
nazalog.Error(err)
}
}()
}
//if sm.hlsServer != nil {
// if err := sm.hlsServer.Listen(); err != nil {
// return err
// }
// go func() {
// if err := sm.hlsServer.RunLoop(); err != nil {
// nazalog.Error(err)
// }
// }()
//}
if sm.rtspServer != nil {
if err := sm.rtspServer.Listen(); err != nil {
return err
@ -177,18 +200,15 @@ func (sm *ServerManager) RunLoop() error {
func (sm *ServerManager) Dispose() {
nazalog.Debug("dispose server manager.")
// TODO(chef) add httpServer
if sm.rtmpServer != nil {
sm.rtmpServer.Dispose()
}
if sm.httpflvServer != nil {
sm.httpflvServer.Dispose()
}
if sm.httptsServer != nil {
sm.httptsServer.Dispose()
}
if sm.hlsServer != nil {
sm.hlsServer.Dispose()
}
//if sm.hlsServer != nil {
// sm.hlsServer.Dispose()
//}
sm.mutex.Lock()
for _, group := range sm.groupMap {

@ -8,6 +8,12 @@
package logic
var (
HTTPFLVURLPath = "/live/"
HTTPTSURLPath = "/live/"
HLSURLPath = "/hls/"
)
//var relayPushCheckIntervalMS = 1000
var relayPushTimeoutMS = 5000
var relayPushWriteAVTimeoutMS = 5000

Loading…
Cancel
Save