]> 127.0.0.1 Git - bili_danmu/.git/commitdiff
Improve fmp4指向新链接 (#151)
authorqydysky <qydysky@foxmail.com>
Fri, 17 Jan 2025 18:37:34 +0000 (02:37 +0800)
committerGitHub <noreply@github.com>
Fri, 17 Jan 2025 18:37:34 +0000 (02:37 +0800)
.gitignore
Reply/F/comp.go
Reply/F/parseM3u8/parseM3u8.go [new file with mode: 0644]
Reply/F/parseM3u8/parseM3u8_test.go [new file with mode: 0644]
Reply/F/parseM3u8/test.m3u8 [new file with mode: 0644]
Reply/stream.go

index 03bdee391e074d94d309dc27426c5f1fea420839..90e5138d68f6b7a5624fd8ec26bcf6134a266ee6 100644 (file)
@@ -21,7 +21,6 @@ demo/qr.png
 demo/live
 demo/main.exe
 *.m4s
-*.m3u8
 demo/build.bat
 demo/build.sh
 Reply/0.flv.log
index 3c4406e39aa76318f9adb201b104c63bd0f32aeb..2bbb88845e011c902457e800ec1dfeafb3d705b2 100644 (file)
@@ -2,6 +2,7 @@ package f
 
 import (
        "context"
+       "iter"
        "net/http"
        "time"
 
@@ -9,6 +10,7 @@ import (
        _ "github.com/qydysky/bili_danmu/Reply/F/danmuCountPerMin"
        _ "github.com/qydysky/bili_danmu/Reply/F/danmuEmotes"
        _ "github.com/qydysky/bili_danmu/Reply/F/danmuji"
+       _ "github.com/qydysky/bili_danmu/Reply/F/parseM3u8"
        _ "github.com/qydysky/bili_danmu/Reply/F/videoFastSeed"
        comp "github.com/qydysky/part/component2"
        log "github.com/qydysky/part/log"
@@ -36,6 +38,14 @@ var VideoFastSeed = comp.Get[interface {
        InitSav(fastSeedFilePath string) (savIndex func(seedTo time.Duration, cuIndex int64) error, e error)
 }](`videoFastSeed`)
 
+var ParseM3u8 = comp.Get[interface {
+       Parse(respon []byte, lastNo int) (m4sLink iter.Seq[interface {
+               IsHeader() bool
+               M4sLink() string
+       }], redirectUrl string, err error)
+       IsErrRedirect(e error) bool
+}](`parseM3u8`)
+
 type DanmuEmotesS struct {
        Logg *log.Log_interface
        Info []any
diff --git a/Reply/F/parseM3u8/parseM3u8.go b/Reply/F/parseM3u8/parseM3u8.go
new file mode 100644 (file)
index 0000000..93ccf6a
--- /dev/null
@@ -0,0 +1,163 @@
+package parsem3u8
+
+import (
+       "bytes"
+       "encoding/base64"
+       "errors"
+       "iter"
+       "strconv"
+       "strings"
+
+       comp "github.com/qydysky/part/component2"
+)
+
+type TargetInterface interface {
+       Parse(respon []byte, lastNo int) (m4sLink iter.Seq[interface {
+               IsHeader() bool
+               M4sLink() string
+       }], redirectUrl string, err error)
+       IsErrRedirect(e error) bool
+}
+
+func init() {
+       if e := comp.Register[TargetInterface]("parseM3u8", parseM3u8{}); e != nil {
+               panic(e)
+       }
+}
+
+var (
+       ErrRedirect = errors.New(`ErrRedirect`)
+
+       extXStreamInf = []byte("#EXT-X-STREAM-INF")
+       extXMap       = []byte("#EXT-X-MAP")
+)
+
+type parseM3u8I struct {
+       link   string
+       header bool
+}
+
+func (t parseM3u8I) IsHeader() bool  { return t.header }
+func (t parseM3u8I) M4sLink() string { return t.link }
+
+type parseM3u8 struct{}
+
+func (t parseM3u8) IsErrRedirect(e error) bool {
+       return e != nil && errors.Is(e, ErrRedirect)
+}
+
+func (t parseM3u8) Parse(respon []byte, lastNo int) (m4sLink iter.Seq[interface {
+       IsHeader() bool
+       M4sLink() string
+}], redirectUrl string, err error) {
+       // base64解码
+       if len(respon) != 0 && !bytes.Contains(respon, []byte("#")) {
+               respon, err = base64.StdEncoding.DecodeString(string(respon))
+               if err != nil {
+                       return
+               }
+       }
+
+       m3u := bytes.Split(respon, []byte("\n"))
+       var maxqn int = -1
+       for i := 0; i < len(m3u); i++ {
+               if bytes.HasPrefix(m3u[i], extXStreamInf) {
+                       // m3u8 指向新连接
+                       tmp := strings.TrimSpace(string(m3u[i+1]))
+                       if redirectUrl == "" {
+                               redirectUrl = tmp
+                       }
+                       if qn, e := strconv.Atoi(ParseQuery(tmp, "qn=")); e == nil {
+                               if maxqn < qn {
+                                       maxqn = qn
+                                       redirectUrl = tmp
+                               }
+                       }
+                       err = ErrRedirect
+               }
+       }
+       if t.IsErrRedirect(err) {
+               return
+       }
+
+       m4sLink = func(yield func(interface {
+               IsHeader() bool
+               M4sLink() string
+       }) bool) {
+               for i := 0; i < len(m3u); i++ {
+                       line := m3u[i]
+                       if len(line) == 0 {
+                               continue
+                       }
+
+                       var (
+                               m4sLink  string //切片文件名
+                               isHeader bool
+                       )
+
+                       if line[0] == '#' {
+                               if bytes.HasPrefix(line, extXMap) {
+                                       if lastNo != 0 {
+                                               continue
+                                       }
+                                       e := bytes.Index(line[16:], []byte(`"`)) + 16
+                                       m4sLink = string(line[16:e])
+                                       isHeader = true
+                               } else {
+                                       continue
+                               }
+                       } else {
+                               m4sLink = string(line)
+                       }
+
+                       if !isHeader {
+                               // 只增加新的切片
+                               if no, _ := strconv.Atoi(m4sLink[:len(m4sLink)-4]); lastNo >= no {
+                                       continue
+                               }
+                       }
+
+                       if !yield(parseM3u8I{
+                               header: isHeader,
+                               link:   m4sLink,
+                       }) {
+                               break
+                       }
+               }
+       }
+       return
+}
+
+// just faster, use in right way
+//
+// eg. ParseQuery(`http://1.com/2?workspace=1`, "workspace=") => `1`
+func ParseQuery(rawURL, key string) string {
+       s := 0
+       for i := 0; i < len(rawURL); i++ {
+               if rawURL[i] == '?' {
+                       s = i + 1
+                       break
+               }
+       }
+
+       for i := s; i < len(rawURL); i++ {
+               for j := 0; i < len(rawURL) && j < len(key); j, i = j+1, i+1 {
+                       if rawURL[i] != key[j] {
+                               break
+                       } else if j == len(key)-1 {
+                               s = i + 1
+                               i = len(rawURL)
+                               break
+                       }
+               }
+       }
+
+       d := s
+       for ; d < len(rawURL); d++ {
+               if rawURL[d] == '&' || rawURL[d] == '#' {
+                       break
+               }
+       }
+
+       return rawURL[s:d]
+}
diff --git a/Reply/F/parseM3u8/parseM3u8_test.go b/Reply/F/parseM3u8/parseM3u8_test.go
new file mode 100644 (file)
index 0000000..89ca77f
--- /dev/null
@@ -0,0 +1,30 @@
+package parsem3u8
+
+import (
+       "iter"
+       "testing"
+
+       _ "embed"
+
+       comp "github.com/qydysky/part/component2"
+)
+
+//go:embed test.m3u8
+var testM3u8 []byte
+
+func TestMain(t *testing.T) {
+       var ParseM3u8 = comp.Get[interface {
+               Parse(respon []byte, lastNo int) (m4sLink iter.Seq[interface {
+                       IsHeader() bool
+                       M4sLink() string
+               }], redirectUrl string, err error)
+               IsErrRedirect(e error) bool
+       }](`parseM3u8`)
+       _, url, e := ParseM3u8.Parse(testM3u8, 0)
+       if !ParseM3u8.IsErrRedirect(e) {
+               t.Fatal()
+       }
+       if url != "https://d1--cn-gotcha209.bilivideo.com/live-bvc/192693/live_194484313_8775758_bluray/index.m3u8?expires=1737129387&len=0&oi=x&pt=web&qn=10000&trid=x&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha209&sign=x&site=x&free_type=0&mid=x&sche=ban&bvchls=1&trace=64&isp=cm&rg=South&pv=Guangdong&deploy_env=prod&hot_cdn=909773&origin_bitrate=1272698&source=puv3_master&flvsk=x&suffix=bluray&pp=rtmp&qp=bqor_250&sl=10&p2p_type=1&sk=x&score=42&info_source=cache&vd=bc&src=puv3&order=1" {
+               t.Fatal()
+       }
+}
diff --git a/Reply/F/parseM3u8/test.m3u8 b/Reply/F/parseM3u8/test.m3u8
new file mode 100644 (file)
index 0000000..64a9dc0
--- /dev/null
@@ -0,0 +1,26 @@
+#EXTM3U
+#EXT-X-CONTENT-STEERING:SERVER-URI="reserved",PATHWAY-ID="https://d1--cn-gotcha209.bilivideo.com",BILI-PATHWAY-PRIORITY="https://d1--cn-gotcha209.bilivideo.com,https://cn-hnzz-cm-01-11.bilivideo.com,https://d1--cn-gotcha204.bilivideo.com,https://d1--cn-gotcha208.bilivideo.com,https://cn-zjhz-cm-01-17.bilivideo.com,https://d1--cn-gotcha204-1.bilivideo.com,https://cn-hnzz-cm-01-09.bilivideo.com,https://d1--cn-gotcha204-4.bilivideo.com",BILI-CTR-MODE=1,BILI-TTL=150
+#EXT-X-STREAM-INF:BANDWIDTH=1187465,RESOLUTION=1280x720,CODECS="avc1.640028,mp4a.40.2",BILI-ORDER=0,BILI-DISPLAY="超清",BILI-QN=250,PATHWAY-ID="https://d1--cn-gotcha209.bilivideo.com"
+https://d1--cn-gotcha209.bilivideo.com/live-bvc/833265/live_194484313_8775758_2500/index.m3u8?expires=1737129387&len=0&oi=x&pt=web&qn=250&trid=x&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha209&sign=x&site=x&free_type=0&mid=x&sche=ban&bvchls=1&trace=64&isp=cm&rg=South&pv=Guangdong&p2p_type=1&sl=10&qp=bqor_250&source=puv3_master&sk=x&deploy_env=prod&score=37&hot_cdn=909773&info_source=cache&suffix=2500&origin_bitrate=1272698&flvsk=x&pp=rtmp&vd=bc&src=puv3&order=1
+#EXT-X-STREAM-INF:BANDWIDTH=1187465,RESOLUTION=1280x720,CODECS="avc1.640028,mp4a.40.2",BILI-ORDER=0,BILI-DISPLAY="超清",BILI-QN=250,PATHWAY-ID="https://cn-hnzz-cm-01-11.bilivideo.com"
+https://cn-hnzz-cm-01-11.bilivideo.com/live-bvc/833265/live_194484313_8775758_2500/index.m3u8?expires=1737129387&len=0&oi=x&pt=web&qn=250&trid=x&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha01&sign=x&site=x&free_type=0&mid=x&sche=ban&bvchls=1&sid=cn-hnzz-cm-01-11&chash=1&bmt=1&sg=lr&trace=65&isp=cm&rg=South&pv=Guangdong&p2p_type=1&sl=10&qp=bqor_250&source=puv3_master&deploy_env=prod&score=37&hot_cdn=909773&info_source=cache&suffix=2500&origin_bitrate=1272698&flvsk=x&sk=x&pp=rtmp&vd=nc&zoneid_l=151371779&sid_l=live_194484313_8775758_2500&src=puv3&order=2
+#EXT-X-STREAM-INF:BANDWIDTH=1187465,RESOLUTION=1280x720,CODECS="avc1.640028,mp4a.40.2",BILI-ORDER=0,BILI-DISPLAY="超清",BILI-QN=250,PATHWAY-ID="https://d1--cn-gotcha204.bilivideo.com"
+https://d1--cn-gotcha204.bilivideo.com/live-bvc/833265/live_194484313_8775758_2500/index.m3u8?expires=1737129387&len=0&oi=x&pt=web&qn=250&trid=x&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha204&sign=x&site=x&free_type=0&mid=x&sche=ban&bvchls=1&trace=64&isp=cm&rg=South&pv=Guangdong&p2p_type=1&sl=10&qp=bqor_250&source=puv3_master&deploy_env=prod&score=37&hot_cdn=909773&info_source=cache&suffix=2500&origin_bitrate=1272698&flvsk=x&sk=x&pp=rtmp&vd=bc&src=puv3&order=3
+#EXT-X-STREAM-INF:BANDWIDTH=1187465,RESOLUTION=1280x720,CODECS="avc1.640028,mp4a.40.2",BILI-ORDER=0,BILI-DISPLAY="超清",BILI-QN=250,PATHWAY-ID="https://d1--cn-gotcha208.bilivideo.com"
+https://d1--cn-gotcha208.bilivideo.com/live-bvc/833265/live_194484313_8775758_2500/index.m3u8?expires=1737129387&len=0&oi=x&pt=web&qn=250&trid=x&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha208&sign=x&site=x&free_type=0&mid=x&sche=ban&bvchls=1&trace=64&isp=cm&rg=South&pv=Guangdong&flvsk=x&sk=x&deploy_env=prod&score=37&hot_cdn=909773&info_source=cache&suffix=2500&origin_bitrate=1272698&pp=rtmp&source=puv3_master&p2p_type=1&sl=10&qp=bqor_250&vd=bc&src=puv3&order=4
+#EXT-X-STREAM-INF:BANDWIDTH=1724079,RESOLUTION=1920x1080,CODECS="avc1.640032,mp4a.40.2",BILI-ORDER=1,BILI-DISPLAY="蓝光",BILI-QN=400,PATHWAY-ID="https://d1--cn-gotcha209.bilivideo.com"
+https://d1--cn-gotcha209.bilivideo.com/live-bvc/378106/live_194484313_8775758_4000/index.m3u8?expires=1737129387&len=0&oi=x&pt=web&qn=400&trid=x&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha209&sign=x&site=x&free_type=0&mid=x&sche=ban&bvchls=1&trace=64&isp=cm&rg=South&pv=Guangdong&sl=10&qp=bqor_250&flvsk=x&deploy_env=prod&suffix=4000&p2p_type=1&score=57&origin_bitrate=1272698&source=puv3_master&sk=x&info_source=cache&hot_cdn=909773&pp=rtmp&vd=bc&src=puv3&order=1
+#EXT-X-STREAM-INF:BANDWIDTH=1724079,RESOLUTION=1920x1080,CODECS="avc1.640032,mp4a.40.2",BILI-ORDER=1,BILI-DISPLAY="蓝光",BILI-QN=400,PATHWAY-ID="https://cn-zjhz-cm-01-17.bilivideo.com"
+https://cn-zjhz-cm-01-17.bilivideo.com/live-bvc/378106/live_194484313_8775758_4000/index.m3u8?expires=1737129387&len=0&oi=x&pt=web&qn=400&trid=x&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha01&sign=x&site=x&free_type=0&mid=x&sche=ban&bvchls=1&sid=cn-zjhz-cm-01-17&chash=1&bmt=1&sg=lr&trace=65&isp=cm&rg=South&pv=Guangdong&deploy_env=prod&suffix=4000&p2p_type=1&sl=10&qp=bqor_250&flvsk=x&sk=x&info_source=cache&hot_cdn=909773&pp=rtmp&score=57&origin_bitrate=1272698&source=puv3_master&vd=nc&zoneid_l=151371779&sid_l=live_194484313_8775758_4000&src=puv3&order=2
+#EXT-X-STREAM-INF:BANDWIDTH=1724079,RESOLUTION=1920x1080,CODECS="avc1.640032,mp4a.40.2",BILI-ORDER=1,BILI-DISPLAY="蓝光",BILI-QN=400,PATHWAY-ID="https://d1--cn-gotcha204-1.bilivideo.com"
+https://d1--cn-gotcha204-1.bilivideo.com/live-bvc/378106/live_194484313_8775758_4000/index.m3u8?expires=1737129387&len=0&oi=x&pt=web&qn=400&trid=x&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha204&sign=x&site=x&free_type=0&mid=x&sche=ban&bvchls=1&trace=64&isp=cm&rg=South&pv=Guangdong&sk=x&info_source=cache&hot_cdn=909773&pp=rtmp&score=57&origin_bitrate=1272698&source=puv3_master&deploy_env=prod&suffix=4000&p2p_type=1&sl=10&qp=bqor_250&flvsk=x&vd=bc&src=puv3&order=3
+#EXT-X-STREAM-INF:BANDWIDTH=1724079,RESOLUTION=1920x1080,CODECS="avc1.640032,mp4a.40.2",BILI-ORDER=1,BILI-DISPLAY="蓝光",BILI-QN=400,PATHWAY-ID="https://d1--cn-gotcha208.bilivideo.com"
+https://d1--cn-gotcha208.bilivideo.com/live-bvc/378106/live_194484313_8775758_4000/index.m3u8?expires=1737129387&len=0&oi=x&pt=web&qn=400&trid=x&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha208&sign=x&site=x&free_type=0&mid=x&sche=ban&bvchls=1&trace=64&isp=cm&rg=South&pv=Guangdong&sl=10&qp=bqor_250&flvsk=x&deploy_env=prod&suffix=4000&p2p_type=1&score=57&origin_bitrate=1272698&source=puv3_master&sk=x&info_source=cache&hot_cdn=909773&pp=rtmp&vd=bc&src=puv3&order=4
+#EXT-X-STREAM-INF:BANDWIDTH=2108696,RESOLUTION=1920x1080,CODECS="avc1.640032,mp4a.40.2",BILI-ORDER=2,BILI-DISPLAY="原画",BILI-QN=10000,PATHWAY-ID="https://d1--cn-gotcha209.bilivideo.com"
+https://d1--cn-gotcha209.bilivideo.com/live-bvc/192693/live_194484313_8775758_bluray/index.m3u8?expires=1737129387&len=0&oi=x&pt=web&qn=10000&trid=x&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha209&sign=x&site=x&free_type=0&mid=x&sche=ban&bvchls=1&trace=64&isp=cm&rg=South&pv=Guangdong&deploy_env=prod&hot_cdn=909773&origin_bitrate=1272698&source=puv3_master&flvsk=x&suffix=bluray&pp=rtmp&qp=bqor_250&sl=10&p2p_type=1&sk=x&score=42&info_source=cache&vd=bc&src=puv3&order=1
+#EXT-X-STREAM-INF:BANDWIDTH=2108696,RESOLUTION=1920x1080,CODECS="avc1.640032,mp4a.40.2",BILI-ORDER=2,BILI-DISPLAY="原画",BILI-QN=10000,PATHWAY-ID="https://cn-hnzz-cm-01-09.bilivideo.com"
+https://cn-hnzz-cm-01-09.bilivideo.com/live-bvc/192693/live_194484313_8775758_bluray/index.m3u8?expires=1737129387&len=0&oi=x&pt=web&qn=10000&trid=x&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha01&sign=x&site=x&free_type=0&mid=x&sche=ban&bvchls=1&sid=cn-hnzz-cm-01-09&chash=1&bmt=1&sg=lr&trace=65&isp=cm&rg=South&pv=Guangdong&flvsk=x&suffix=bluray&pp=rtmp&qp=bqor_250&deploy_env=prod&hot_cdn=909773&origin_bitrate=1272698&source=puv3_master&info_source=cache&sl=10&p2p_type=1&sk=x&score=42&vd=nc&zoneid_l=151371779&sid_l=live_194484313_8775758_bluray&src=puv3&order=2
+#EXT-X-STREAM-INF:BANDWIDTH=2108696,RESOLUTION=1920x1080,CODECS="avc1.640032,mp4a.40.2",BILI-ORDER=2,BILI-DISPLAY="原画",BILI-QN=10000,PATHWAY-ID="https://d1--cn-gotcha204-4.bilivideo.com"
+https://d1--cn-gotcha204-4.bilivideo.com/live-bvc/192693/live_194484313_8775758_bluray/index.m3u8?expires=1737129387&len=0&oi=x&pt=web&qn=10000&trid=x&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha204&sign=x&site=x&free_type=0&mid=x&sche=ban&bvchls=1&trace=64&isp=cm&rg=South&pv=Guangdong&sl=10&p2p_type=1&sk=x&score=42&info_source=cache&pp=rtmp&qp=bqor_250&deploy_env=prod&hot_cdn=909773&origin_bitrate=1272698&source=puv3_master&flvsk=x&suffix=bluray&vd=bc&src=puv3&order=3
+#EXT-X-STREAM-INF:BANDWIDTH=2108696,RESOLUTION=1920x1080,CODECS="avc1.640032,mp4a.40.2",BILI-ORDER=2,BILI-DISPLAY="原画",BILI-QN=10000,PATHWAY-ID="https://d1--cn-gotcha208.bilivideo.com"
+https://d1--cn-gotcha208.bilivideo.com/live-bvc/192693/live_194484313_8775758_bluray/index.m3u8?expires=1737129387&len=0&oi=x&pt=web&qn=10000&trid=x&sigparams=cdn,expires,len,oi,pt,qn,trid&cdn=cn-gotcha208&sign=x&site=x&free_type=0&mid=x&sche=ban&bvchls=1&trace=64&isp=cm&rg=South&pv=Guangdong&qp=bqor_250&deploy_env=prod&hot_cdn=909773&origin_bitrate=1272698&source=puv3_master&flvsk=x&suffix=bluray&pp=rtmp&sl=10&p2p_type=1&sk=x&score=42&info_source=cache&vd=bc&src=puv3&order=4
\ No newline at end of file
index 40fdcfeddcace26c688af11ae64e3cd6d234327e..70212066f3c9534220ba4af5ae7e5a71d6e7c87e 100644 (file)
@@ -1,11 +1,9 @@
 package reply
 
 import (
-       "bytes"
        "context"
        "crypto/md5"
        _ "embed"
-       "encoding/base64"
        "encoding/json"
        "errors"
        "fmt"
@@ -448,7 +446,9 @@ func (t *M4SStream) fetchParseM3U8(lastM4s *m4s_link_item, fmp4ListUpdateTo floa
        defer t.reqPool.Put(r)
 
        // 请求解析m3u8内容
-       for _, v := range t.common.Live {
+       for i := 0; i < len(t.common.Live); i++ {
+               v := t.common.Live[i]
+
                // 跳过尚未启用的live地址
                if !v.Valid() {
                        continue
@@ -477,7 +477,6 @@ func (t *M4SStream) fetchParseM3U8(lastM4s *m4s_link_item, fmp4ListUpdateTo floa
                }
 
                if err := r.Reqf(rval); err != nil {
-                       // 1min后重新启用
                        v.DisableAuto()
                        t.log.L("W: ", fmt.Sprintf("服务器 %s 发生故障 %s", F.ParseHost(v.Url), pe.ErrorFormat(err, pe.ErrSimplifyFunc)))
                        if t.common.ValidLive() == nil {
@@ -499,19 +498,6 @@ func (t *M4SStream) fetchParseM3U8(lastM4s *m4s_link_item, fmp4ListUpdateTo floa
                        }
                }
 
-               // m3u8字节流
-               var m3u8_respon = r.Respon
-
-               // base64解码
-               if len(m3u8_respon) != 0 && !bytes.Contains(m3u8_respon, []byte("#")) {
-                       var err error
-                       m3u8_respon, err = base64.StdEncoding.DecodeString(string(m3u8_respon))
-                       if err != nil {
-                               e = err
-                               return
-                       }
-               }
-
                // 解析m3u8
                // var tmp []*m4s_link_item
                var lastNo int
@@ -519,56 +505,33 @@ func (t *M4SStream) fetchParseM3U8(lastM4s *m4s_link_item, fmp4ListUpdateTo floa
                        lastNo, _ = lastM4s.getNo()
                }
 
-               m3u := bytes.Split(m3u8_respon, []byte("\n"))
-               for i := 0; i < len(m3u); i++ {
-                       line := m3u[i]
-                       if len(line) == 0 {
-                               continue
-                       }
-
-                       var (
-                               m4s_link string //切片文件名
-                               isHeader bool
-                       )
-
-                       if line[0] == '#' {
-                               if strings.HasPrefix(string(line), "#EXT-X-MAP") {
-                                       if lastM4s != nil {
-                                               continue
-                                       }
-                                       e := bytes.Index(line[16:], []byte(`"`)) + 16
-                                       m4s_link = string(line[16:e])
-                                       isHeader = true
-                               } else if strings.HasPrefix(string(line), "#EXT-X-STREAM-INF") {
-                                       // m3u8 指向新连接
-                                       i += 1
-                                       line = m3u[i]
-                                       newUrl := strings.TrimSpace(string(line))
-                                       t.log.L(`I: `, `指向新连接`, v.Host(), "=>", F.ParseHost(newUrl))
-                                       v.SetUrl(newUrl)
-                                       continue
-                               } else {
-                                       continue
-                               }
+               if rg, redirectUrl, err := replyFunc.ParseM3u8.Parse(r.Respon, lastNo); err != nil {
+                       if replyFunc.ParseM3u8.IsErrRedirect(err) {
+                               // 指向新连接
+                               t.log.L(`I: `, `指向新连接`, v.Host(), "=>", F.ParseHost(redirectUrl))
+                               v.SetUrl(redirectUrl)
+                               i -= 1
                        } else {
-                               m4s_link = string(line)
-                       }
-
-                       if !isHeader {
-                               // 只增加新的切片
-                               if no, _ := strconv.Atoi(m4s_link[:len(m4s_link)-4]); lastNo >= no {
-                                       continue
+                               // 1min后重新启用
+                               t.log.L("W: ", fmt.Sprintf("服务器 %s 发生故障 %v", F.ParseHost(v.Url), err))
+                               v.DisableAuto()
+                               if t.common.ValidLive() == nil {
+                                       e = errors.New("全部切片服务器发生故障")
+                                       break
                                }
                        }
-
-                       //将切片添加到返回切片数组
-                       p := t.getM4s()
-                       p.SerUuid = v.Uuid
-                       p.Url = F.ResolveReferenceLast(v.Url, m4s_link+"?trid="+F.ParseQuery(v.Url, "trid="))
-                       p.Base = m4s_link
-                       p.isHeader = isHeader
-                       p.createdTime = time.Now()
-                       m4s_links = append(m4s_links, p)
+                       continue
+               } else {
+                       for m4sLinkI := range rg {
+                               //将切片添加到返回切片数组
+                               p := t.getM4s()
+                               p.SerUuid = v.Uuid
+                               p.Url = F.ResolveReferenceLast(v.Url, m4sLinkI.M4sLink()+"?trid="+F.ParseQuery(v.Url, "trid="))
+                               p.Base = m4sLinkI.M4sLink()
+                               p.isHeader = m4sLinkI.IsHeader()
+                               p.createdTime = time.Now()
+                               m4s_links = append(m4s_links, p)
+                       }
                }
 
                if len(m4s_links) == 0 {