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  }