From: qydysky Date: Sun, 18 Apr 2021 10:09:59 +0000 (+0800) Subject: 默认使用hls保存直播流 X-Git-Tag: v0.5.9~1^2~24 X-Git-Url: http://127.0.0.1:8081/?a=commitdiff_plain;h=9e913f77426850d314ffd215b80d224040d94512;p=bili_danmu%2F.git 默认使用hls保存直播流 --- diff --git a/F/api.go b/F/api.go index 1064207..46b1d49 100644 --- a/F/api.go +++ b/F/api.go @@ -309,36 +309,66 @@ func Html() (missKey []string) { c.Liveing = j.Roominitres.Data.LiveStatus == 1 //当前直播流 - for _,v := range j.Roominitres.Data.PlayurlInfo.Playurl.Stream { - if v.ProtocolName != `http_stream` {continue} - - for _,v := range v.Format { - if v.FormatName != `flv` {continue} + { + type Stream_name struct { + Protocol_name string + Format_name string + Codec_name string + } + var name_map = map[string]Stream_name{ + `flv`: Stream_name{ + Protocol_name:"http_stream", + Format_name:"flv", + Codec_name:"avc", + }, + `hls`: Stream_name{ + Protocol_name:"http_hls", + Format_name:"fmp4", + Codec_name:"avc", + }, + } - for _,v := range v.Codec { - if v.CodecName != `avc` {continue} - - //当前直播流质量 - c.Live_qn = v.CurrentQn - //允许的清晰度 - { - var tmp = make(map[int]string) - for _,v := range v.AcceptQn { - if s,ok := c.AcceptQn[v];ok{ - tmp[v] = s + want_type := name_map[`flv`] + if v,ok := c.K_v.LoadV(`直播流类型`).(string);ok { + if v,ok := name_map[v];ok { + want_type = v + } else { + apilog.L(`I: `, `未找到`,v,`,默认flv`) + } + } else { + apilog.L(`T: `, `默认flv`) + } + + for _,v := range j.Roominitres.Data.PlayurlInfo.Playurl.Stream { + if v.ProtocolName != want_type.Protocol_name {continue} + + for _,v := range v.Format { + if v.FormatName != want_type.Format_name {continue} + + for _,v := range v.Codec { + if v.CodecName != want_type.Codec_name {continue} + + //当前直播流质量 + c.Live_qn = v.CurrentQn + //允许的清晰度 + { + var tmp = make(map[int]string) + for _,v := range v.AcceptQn { + if s,ok := c.AcceptQn[v];ok{ + tmp[v] = s + } } + c.AcceptQn = tmp + } + //直播流链接 + c.Live = []string{} + for _,v1 := range v.URLInfo { + c.Live = append(c.Live, v1.Host+v.BaseURL+v1.Extra) } - c.AcceptQn = tmp - } - //直播流链接 - c.Live = []string{} - for _,v1 := range v.URLInfo { - c.Live = append(c.Live, v1.Host+v.BaseURL+v1.Extra) } } } } - } //Roominfores @@ -521,31 +551,62 @@ func getRoomPlayInfo() (missKey []string) { c.Liveing = j.Data.LiveStatus == 1 //当前直播流 - for _,v := range j.Data.PlayurlInfo.Playurl.Stream { - if v.ProtocolName != `http_stream` {continue} - - for _,v := range v.Format { - if v.FormatName != `flv` {continue} + { + type Stream_name struct { + Protocol_name string + Format_name string + Codec_name string + } + var name_map = map[string]Stream_name{ + `flv`: Stream_name{ + Protocol_name:"http_stream", + Format_name:"flv", + Codec_name:"avc", + }, + `hls`: Stream_name{ + Protocol_name:"http_hls", + Format_name:"fmp4", + Codec_name:"avc", + }, + } - for _,v := range v.Codec { - if v.CodecName != `avc` {continue} - - //当前直播流质量 - c.Live_qn = v.CurrentQn - //允许的清晰度 - { - var tmp = make(map[int]string) - for _,v := range v.AcceptQn { - if s,ok := c.AcceptQn[v];ok{ - tmp[v] = s + want_type := name_map[`flv`] + if v,ok := c.K_v.LoadV(`直播流类型`).(string);ok { + if v,ok := name_map[v];ok { + want_type = v + } else { + apilog.L(`I: `, `未找到`,v,`,默认flv`) + } + } else { + apilog.L(`T: `, `默认flv`) + } + + for _,v := range j.Data.PlayurlInfo.Playurl.Stream { + if v.ProtocolName != want_type.Protocol_name {continue} + + for _,v := range v.Format { + if v.FormatName != want_type.Format_name {continue} + + for _,v := range v.Codec { + if v.CodecName != want_type.Codec_name {continue} + + //当前直播流质量 + c.Live_qn = v.CurrentQn + //允许的清晰度 + { + var tmp = make(map[int]string) + for _,v := range v.AcceptQn { + if s,ok := c.AcceptQn[v];ok{ + tmp[v] = s + } } + c.AcceptQn = tmp + } + //直播流链接 + c.Live = []string{} + for _,v1 := range v.URLInfo { + c.Live = append(c.Live, v1.Host+v.BaseURL+v1.Extra) } - c.AcceptQn = tmp - } - //直播流链接 - c.Live = []string{} - for _,v1 := range v.URLInfo { - c.Live = append(c.Live, v1.Host+v.BaseURL+v1.Extra) } } } @@ -628,42 +689,71 @@ func getRoomPlayInfoByQn() (missKey []string) { c.Liveing = j.Data.LiveStatus == 1 //当前直播流 - for _,v := range j.Data.PlayurlInfo.Playurl.Stream { - if v.ProtocolName != `http_stream` {continue} - - for _,v := range v.Format { - if v.FormatName != `flv` {continue} + { + type Stream_name struct { + Protocol_name string + Format_name string + Codec_name string + } + var name_map = map[string]Stream_name{ + `flv`: Stream_name{ + Protocol_name:"http_stream", + Format_name:"flv", + Codec_name:"avc", + }, + `hls`: Stream_name{ + Protocol_name:"http_hls", + Format_name:"fmp4", + Codec_name:"avc", + }, + } - for _,v := range v.Codec { - if v.CodecName != `avc` {continue} - - //当前直播流质量 - c.Live_qn = v.CurrentQn - //允许的清晰度 - { - var tmp = make(map[int]string) - for _,v := range v.AcceptQn { - if s,ok := c.AcceptQn[v];ok{ - tmp[v] = s + want_type := name_map[`flv`] + if v,ok := c.K_v.LoadV(`直播流类型`).(string);ok { + if v,ok := name_map[v];ok { + want_type = v + } else { + apilog.L(`I: `, `未找到`,v,`,默认flv`) + } + } else { + apilog.L(`T: `, `默认flv`) + } + + for _,v := range j.Data.PlayurlInfo.Playurl.Stream { + if v.ProtocolName != want_type.Protocol_name {continue} + + for _,v := range v.Format { + if v.FormatName != want_type.Format_name {continue} + + for _,v := range v.Codec { + if v.CodecName != want_type.Codec_name {continue} + + //当前直播流质量 + c.Live_qn = v.CurrentQn + //允许的清晰度 + { + var tmp = make(map[int]string) + for _,v := range v.AcceptQn { + if s,ok := c.AcceptQn[v];ok{ + tmp[v] = s + } } + c.AcceptQn = tmp + } + //直播流链接 + c.Live = []string{} + for _,v1 := range v.URLInfo { + c.Live = append(c.Live, v1.Host+v.BaseURL+v1.Extra) } - c.AcceptQn = tmp - } - //直播流链接 - c.Live = []string{} - for _,v1 := range v.URLInfo { - c.Live = append(c.Live, v1.Host+v.BaseURL+v1.Extra) } } } + if s,ok := c.AcceptQn[c.Live_qn];!ok{ + apilog.L(`W: `, `未知清晰度`, c.Live_qn) + } else { + apilog.L(`I: `, s) + } } - if s,ok := c.AcceptQn[c.Live_qn];!ok{ - apilog.L(`W: `, `未知清晰度`, c.Live_qn) - } else { - apilog.L(`I: `, s) - - } - } return } diff --git a/F/cmd.go b/F/cmd.go index 2dcd6a7..355452d 100644 --- a/F/cmd.go +++ b/F/cmd.go @@ -37,7 +37,7 @@ func Cmd() { cmdlog.L(`W: `, "不能切换录制状态,未在直播") continue } - c.Danmu_Main_mq.Push_tag(`saveflv`, nil) + c.Danmu_Main_mq.Push_tag(`savestream`, nil) continue } //直播间切换 diff --git a/README.md b/README.md index 2d713b1..8456ebc 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ golang go version go1.15 linux/amd64 - [x] GTK信息窗 - [x] 营收统计 - [x] 舰长数统计 -- [x] 直播流保存 +- [x] 直播流保存(默认hls,支持flv) - [x] ASS字幕生成 - [x] OBS调用 - [x] 节奏提示 @@ -112,7 +112,7 @@ golang go version go1.15 linux/amd64 - dtmp结尾:当前正在获取的流,播放此链接时进度将保持当前流进度 - flv结尾:保存完毕的直播流,播放此链接时将从头开始播放 -- ass结尾:保存完毕的直播流字幕,有些播放器会在播放flv串流时获取此文件 +- ass结尾:保存完毕的直播流字幕,有些播放器会在串流时获取此文件 服务地址也可通过命令行` room`查看。 diff --git a/Reply/F.go b/Reply/F.go index 510b7e9..7f3d782 100644 --- a/Reply/F.go +++ b/Reply/F.go @@ -2,6 +2,7 @@ package reply import ( "fmt" + "os" "strconv" "strings" "sync" @@ -9,8 +10,12 @@ import ( "time" "os/exec" "path/filepath" + "path" "net/http" "context" + "net/url" + "errors" + "bytes" c "github.com/qydysky/bili_danmu/CV" F "github.com/qydysky/bili_danmu/F" @@ -198,21 +203,21 @@ func dtos(t time.Duration) string { } //直播流保存 -type Saveflv struct { +type Savestream struct { path string wait *s.Signal cancel *s.Signal skipFunc funcCtrl.SkipFunc } -var saveflv = Saveflv { +var savestream = Savestream { } func init(){ //使用带tag的消息队列在功能间传递消息 c.Danmu_Main_mq.Pull_tag(msgq.FuncMap{ - `saveflv`:func(data interface{})(bool){ - if saveflv.cancel.Islive() { + `savestream`:func(data interface{})(bool){ + if savestream.cancel.Islive() { Saveflv_wait() } else { go Saveflvf() @@ -225,18 +230,18 @@ func init(){ //å·²go func形式调用,将会获取直播流 func Saveflvf(){ - l := c.Log.Base(`saveflv`) + l := c.Log.Base(`savestream`) //避免多次开播导致的多次触发 { - if saveflv.skipFunc.NeedSkip() { + if savestream.skipFunc.NeedSkip() { l.L(`T: `,`已存在实例`) return } - defer saveflv.skipFunc.UnSet() + defer savestream.skipFunc.UnSet() } - qn, ok := c.K_v.LoadV("flv直播流清晰度").(float64) + qn, ok := c.K_v.LoadV("直播流清晰度").(float64) if !ok || qn < 0 {return} { @@ -256,9 +261,152 @@ func Saveflvf(){ c.Live_qn = MaxQn } - if saveflv.cancel.Islive() {return} + if savestream.cancel.Islive() {return} - cuLinkIndex := 0 + var ( + no_found_link = errors.New("no_found_link") + // next_link = func(links []string,last_link string) (link string,err error) { + // if len(links) == 0 { + // err = no_found_link + // return + // } + + // var found bool + + // link = links[0] + + // if last_link == "" {return} + + // for i:=0;i= len(c.Live) {cuLinkIndex = 0} + savestream.wait.Done() + savestream.cancel.Done() + // cuLinkIndex += 1 + // if cuLinkIndex >= len(c.Live) {cuLinkIndex = 0} time.Sleep(time.Second*5) continue } } - Ass_f(saveflv.path, time.Now()) - l.L(`I: `,"保存到", saveflv.path + ".flv") + stream_type_is_flv := strings.Contains(c.Live[0],"flv") - if e := rr.Reqf(reqf.Rval{ - Url:c.Live[cuLinkIndex], - Retry:3, - SleepTime:3, - Header:map[string]string{ - `Cookie`:reqf.Map_2_Cookies_String(CookieM), - }, - SaveToPath:saveflv.path + ".flv", - Timeout:-1, - }); e != nil{l.L(`W: `,e)} + if stream_type_is_flv { + l.L(`I: `,"保存到", savestream.path + ".flv") + Ass_f(savestream.path, time.Now()) - l.L(`I: `,"结束") - Ass_f("", time.Now())//ass - p.FileMove(saveflv.path+".flv.dtmp", saveflv.path+".flv") + link := flv_get_link(c.Live[0]) + if e := rr.Reqf(reqf.Rval{ + Url:link, + Retry:3, + SleepTime:3, + Header:map[string]string{ + `Cookie`:reqf.Map_2_Cookies_String(CookieM), + }, + SaveToPath:savestream.path + ".flv", + Timeout:-1, + }); e != nil{l.L(`W: `,e)} + l.L(`I: `,"结束") + Ass_f("", time.Now())//ass + p.FileMove(savestream.path+".flv.dtmp", savestream.path+".flv") + } else { + savestream.path += "/" + l.L(`I: `,"保存到", savestream.path) + Ass_f(savestream.path+"0", time.Now()) + + last_download := "" + expires := 0 + for savestream.cancel.Islive() { + if expires != 0 && p.Sys().GetSTime() > int64(expires+60) { + F.Get(`Liveing`) + if !c.Liveing {break} + + F.Get(`Live`) + if len(c.Live)==0 {break} + } + + links,file_add,exp,e := hls_get_link(c.Live[0],last_download) + expires = exp + if e != nil {l.L(`E: `,e);break} + if len(links) == 0 { + time.Sleep(time.Second) + continue + } + + f := p.File() + f.FileWR(p.Filel{ + File:savestream.path+"0.m3u8.dtmp", + Write:true, + Loc:-1, + Context:[]interface{}{file_add}, + }) + + for i:=0;i15 {break} + end_offset += 7 + } + // end_offset = bytes.LastIndex(buf, []byte("#EXTINF")) + + A = buf[m4s_start_offset:m4s_end_offset] + B = buf[start_offset:end_offset] + return + } + + { + A,B,e := seed_m4s(f) + + if e != nil { + flog.Base_add(`直播Web服务`).L(`E: `,`error when seed_m4s`, e); + return + } + + res = append(res, A...) + + res = append(res, B...) + } + + if _,err = w.Write(res);err != nil { + flog.Base_add(`直播Web服务`).L(`E: `,err); + return } - if flushSupport {flusher.Flush()} } } else { http.FileServer(http.Dir(base_dir)).ServeHTTP(w,r) diff --git a/Reply/flvDecode.go b/Reply/flvDecode.go index 9abdb8a..396e958 100644 --- a/Reply/flvDecode.go +++ b/Reply/flvDecode.go @@ -42,9 +42,6 @@ func Stream(path string,streamChan chan []byte,cancel chan struct{}) (error) { defer f.Close() defer close(streamChan) - //init - f.Seek(0,0) - //get flv header(9byte) + FirstTagSize(4byte) { buf := make([]byte, flv_header_size+previou_tag_size) @@ -58,7 +55,7 @@ func Stream(path string,streamChan chan []byte,cancel chan struct{}) (error) { Offset int64 Timestamp int32 PreSize int32 - VideoFrame byte + FirstByte byte Buf *[]byte } @@ -110,7 +107,7 @@ func Stream(path string,streamChan chan []byte,cancel chan struct{}) (error) { t.Tag = eof_tag return } - t.VideoFrame = data[0] + t.FirstByte = data[0] pre_tag := make([]byte, previou_tag_size) if size,err := f.Read(pre_tag);err != nil || size == 0 { @@ -143,7 +140,7 @@ func Stream(path string,streamChan chan []byte,cancel chan struct{}) (error) { streamChan <- *t.Buf } - if t.VideoFrame & 0xf0 == 0x10 { + if t.FirstByte & 0xf0 == 0x10 { if len(last_keyframe_video_offsets) > 2 { // last_timestamps = append(last_timestamps[1:], t.Timestamp) last_keyframe_video_offsets = append(last_keyframe_video_offsets[1:], t.Offset) @@ -191,7 +188,7 @@ func Stream(path string,streamChan chan []byte,cancel chan struct{}) (error) { f.Seek(seachtag(f),1) continue } else if t.Tag == video_tag { - // if t.VideoFrame & 0xf0 == 0x10 { + // if t.FirstByte & 0xf0 == 0x10 { // video_keyframe_speed = t.Timestamp - last_video_keyframe_timestramp // fmt.Println(`video_keyframe_speed`,video_keyframe_speed) // last_video_keyframe_timestramp = t.Timestamp diff --git a/demo/config/config_K_v.json b/demo/config/config_K_v.json index 5e02930..8c2c309 100644 --- a/demo/config/config_K_v.json +++ b/demo/config/config_K_v.json @@ -44,7 +44,9 @@ "Gtk弹幕窗": false, "调用obs": false, "直播流清晰度-help": "清晰度可选-1:不保存 0:默认 10000:原画 800:4K 401:蓝光(杜比) 400:蓝光 250:超清 150:高清 80:流畅,无提供所选清晰度时,使用低一档清晰度", - "flv直播流清晰度": 150, + "直播流清晰度": 150, + "直播流类型-help": "flv or hls", + "直播流类型": "hls", "直播流保存位置": "./live", "直播保存位置Web服务":0, "ass-help": "只有保存直播流时才考虑生成ass",