github.com/cnotch/ipchub@v1.1.0/av/format/hls/playlist.go (about) 1 // Copyright (c) 2019,CAOHONGJU All rights reserved. 2 // Use of this source code is governed by a MIT-style 3 // license that can be found in the LICENSE file. 4 5 package hls 6 7 import ( 8 "bytes" 9 "errors" 10 "fmt" 11 "io" 12 "sync" 13 "sync/atomic" 14 "time" 15 16 "github.com/cnotch/scheduler" 17 ) 18 19 const hlsRemainSegments = 3 20 21 // Playlist the HLS playlist(m3u8 and ts files). 22 type Playlist struct { 23 // m3u8 segments 24 l sync.RWMutex 25 segments []*segment 26 27 // last http access time 28 lastAccessTime int64 29 } 30 31 // NewPlaylist . 32 func NewPlaylist() *Playlist { 33 return &Playlist{ 34 lastAccessTime: time.Now().UnixNano(), 35 } 36 37 } 38 39 var m3u8Pool = sync.Pool{ 40 New: func() interface{} { 41 return bytes.NewBuffer(make([]byte, 0, 512)) 42 }, 43 } 44 45 // M3u8 获取 m3u8 播放列表 46 func (pl *Playlist) M3u8(token string) ([]byte, error) { 47 atomic.StoreInt64(&pl.lastAccessTime, time.Now().UnixNano()) 48 w := m3u8Pool.Get().(*bytes.Buffer) 49 w.Reset() 50 defer m3u8Pool.Put(w) 51 52 pl.l.RLock() 53 defer pl.l.RUnlock() 54 segments := pl.segments 55 56 if len(segments) < hlsRemainSegments { 57 return nil, errors.New("playlist is not enough,maybe the HLS stream just started") 58 } 59 60 seq := segments[0].sequenceNo 61 var maxDuration float64 62 for _, seg := range segments { 63 if seg.duration > maxDuration { 64 maxDuration = seg.duration 65 } 66 } 67 duration := int32(maxDuration + 1) 68 // 描述部分 69 fmt.Fprintf(w, 70 "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-ALLOW-CACHE:NO\n#EXT-X-TARGETDURATION:%d\n#EXT-X-MEDIA-SEQUENCE:%d\n\n", 71 duration, seq) 72 73 // 列表部分 74 for _, seg := range segments { 75 if seg.isSequenceHeader { 76 // #EXT-X-DISCONTINUITY\n 77 fmt.Fprint(w, "#EXT-X-DISCONTINUITY\n") 78 } 79 80 if len(token) > 0 { 81 fmt.Fprintf(w, "#EXTINF:%.3f,\n%s?token=%s\n", 82 seg.duration, 83 seg.uri, token) 84 } else { 85 fmt.Fprintf(w, "#EXTINF:%.3f,\n%s\n", 86 seg.duration, 87 seg.uri) 88 } 89 } 90 91 return w.Bytes(), nil 92 } 93 94 // Segment 获取 segment 95 func (pl *Playlist) Segment(seq int) (io.Reader, int, error) { 96 atomic.StoreInt64(&pl.lastAccessTime, time.Now().UnixNano()) 97 pl.l.RLock() 98 defer pl.l.RUnlock() 99 100 for _, seg := range pl.segments { 101 if seg.sequenceNo == seq { 102 return seg.file.get() 103 } 104 } 105 return nil, 0, errors.New("Not found TSFile") 106 } 107 108 // LastAccessTime 最后hls访问时间 109 func (pl *Playlist) LastAccessTime() time.Time { 110 lastAccessTime := atomic.LoadInt64(&pl.lastAccessTime) 111 return time.Unix(0, lastAccessTime) 112 } 113 114 // Close . 115 func (pl *Playlist) Close() error { 116 pl.l.Lock() 117 defer pl.l.Unlock() 118 pl.clearSegments(0) 119 120 return nil 121 } 122 123 func (pl *Playlist) addSegment(seg *segment) { 124 pl.l.Lock() 125 defer pl.l.Unlock() 126 pl.segments = append(pl.segments, seg) 127 128 pl.clearSegments(hlsRemainSegments) 129 } 130 131 func (pl *Playlist) clearSegments(remain int) { 132 if len(pl.segments) > remain { 133 for i := 0; i < len(pl.segments)-remain; i++ { 134 if err := pl.segments[i].file.delete(); err != nil { 135 // 延时异步删除 136 file := pl.segments[i].file 137 duration := time.Duration(pl.segments[i].duration * float64(time.Second)) 138 uri := pl.segments[i].uri 139 scheduler.AfterFunc(duration, func() { 140 file.delete() 141 }, "delete "+uri) 142 } 143 pl.segments[i] = nil 144 } 145 copy(pl.segments[:remain], pl.segments[len(pl.segments)-remain:]) 146 pl.segments = pl.segments[:remain] 147 } 148 return 149 }