mirror of https://github.com/gwuhaolin/livego.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
162 lines
3.8 KiB
162 lines
3.8 KiB
package hls |
|
|
|
import ( |
|
"fmt" |
|
"github.com/gwuhaolin/livego/configure" |
|
"net" |
|
"net/http" |
|
"path" |
|
"strconv" |
|
"strings" |
|
"time" |
|
|
|
"github.com/gwuhaolin/livego/av" |
|
|
|
cmap "github.com/orcaman/concurrent-map" |
|
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(`<?xml version="1.0" ?> |
|
<cross-domain-policy> |
|
<allow-access-from domain="*" /> |
|
<allow-http-request-headers-from domain="*" headers="*"/> |
|
</cross-domain-policy>`) |
|
|
|
type Server struct { |
|
listener net.Listener |
|
conns cmap.ConcurrentMap |
|
} |
|
|
|
func NewServer() *Server { |
|
ret := &Server{ |
|
conns: cmap.New(), |
|
} |
|
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 |
|
ok := server.conns.Has(info.Key) |
|
if !ok { |
|
log.Debug("new hls source") |
|
s = NewSource(info) |
|
server.conns.Set(info.Key, s) |
|
} else { |
|
v, _ := server.conns.Get(info.Key) |
|
s = v.(*Source) |
|
} |
|
return s |
|
} |
|
|
|
func (server *Server) getConn(key string) *Source { |
|
v, ok := server.conns.Get(key) |
|
if !ok { |
|
return nil |
|
} |
|
return v.(*Source) |
|
} |
|
|
|
func (server *Server) checkStop() { |
|
for { |
|
<-time.After(5 * time.Second) |
|
for item := range server.conns.IterBuffered() { |
|
v := item.Val.(*Source) |
|
if !v.Alive() && !configure.Config.GetBool("hls_keep_after_end") { |
|
log.Debug("check stop and remove: ", v.Info()) |
|
server.conns.Remove(item.Key) |
|
} |
|
} |
|
} |
|
} |
|
|
|
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 |
|
}
|
|
|