package hls import ( "fmt" "net" "net/http" "path" "strconv" "strings" "sync" "time" "github.com/gwuhaolin/livego/configure" "github.com/gwuhaolin/livego/av" log "github.com/sirupsen/logrus" ) const ( duration = 3000 ) var ( ErrNoPublisher = fmt.Errorf("no publisher") ErrInvalidReq = fmt.Errorf("invalid req url path") ErrNoSupportVideoCodec = fmt.Errorf("no support video codec") ErrNoSupportAudioCodec = fmt.Errorf("no support audio codec") ) var crossdomainxml = []byte(` `) type Server struct { listener net.Listener conns *sync.Map } func NewServer() *Server { ret := &Server{ conns: &sync.Map{}, } go ret.checkStop() return ret } func (server *Server) Serve(listener net.Listener) error { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { server.handle(w, r) }) server.listener = listener http.Serve(listener, mux) return nil } func (server *Server) GetWriter(info av.Info) av.WriteCloser { var s *Source v, ok := server.conns.Load(info.Key) if !ok { log.Debug("new hls source") s = NewSource(info) server.conns.Store(info.Key, s) } else { s = v.(*Source) } return s } func (server *Server) getConn(key string) *Source { v, ok := server.conns.Load(key) if !ok { return nil } return v.(*Source) } func (server *Server) checkStop() { for { <-time.After(5 * time.Second) server.conns.Range(func(key, val interface{}) bool { v := val.(*Source) if !v.Alive() && !configure.Config.GetBool("hls_keep_after_end") { log.Debug("check stop and remove: ", v.Info()) server.conns.Delete(key) } return true }) } } func (server *Server) handle(w http.ResponseWriter, r *http.Request) { if path.Base(r.URL.Path) == "crossdomain.xml" { w.Header().Set("Content-Type", "application/xml") w.Write(crossdomainxml) return } switch path.Ext(r.URL.Path) { case ".m3u8": key, _ := server.parseM3u8(r.URL.Path) conn := server.getConn(key) if conn == nil { http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden) return } tsCache := conn.GetCacheInc() if tsCache == nil { http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden) return } body, err := tsCache.GenM3U8PlayList() if err != nil { log.Debug("GenM3U8PlayList error: ", err) http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Content-Type", "application/x-mpegURL") w.Header().Set("Content-Length", strconv.Itoa(len(body))) w.Write(body) case ".ts": key, _ := server.parseTs(r.URL.Path) conn := server.getConn(key) if conn == nil { http.Error(w, ErrNoPublisher.Error(), http.StatusForbidden) return } tsCache := conn.GetCacheInc() item, err := tsCache.GetItem(r.URL.Path) if err != nil { log.Debug("GetItem error: ", err) http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Content-Type", "video/mp2ts") w.Header().Set("Content-Length", strconv.Itoa(len(item.Data))) w.Write(item.Data) } } func (server *Server) parseM3u8(pathstr string) (key string, err error) { pathstr = strings.TrimLeft(pathstr, "/") key = strings.Split(pathstr, path.Ext(pathstr))[0] return } func (server *Server) parseTs(pathstr string) (key string, err error) { pathstr = strings.TrimLeft(pathstr, "/") paths := strings.SplitN(pathstr, "/", 3) if len(paths) != 3 { err = fmt.Errorf("invalid path=%s", pathstr) return } key = paths[0] + "/" + paths[1] return }