200 lines
5.4 KiB
Go
200 lines
5.4 KiB
Go
package m3u8
|
||
|
||
import (
|
||
"bytes"
|
||
"fmt"
|
||
"io"
|
||
"strings"
|
||
"videoweb/pub/common/enum/redisconst"
|
||
"videoweb/pub/common/errors"
|
||
"videoweb/pub/common/http"
|
||
"videoweb/pub/common/log"
|
||
"videoweb/pub/common/utils"
|
||
"videoweb/pub/redis"
|
||
"videoweb/pub/storage/proxy"
|
||
|
||
"github.com/grafov/m3u8"
|
||
)
|
||
|
||
const (
|
||
keyUri = "/enkey"
|
||
webkeyUri = "/api/web/enkey"
|
||
h5KeyUri = "/enkey"
|
||
AuthKey = "fT5xSg4hltHpzVy6aV9rVECDJ1J1pN"
|
||
keyUriLsj = "/lsjenkey"
|
||
keyUriWebLsj = "/api/web/lsjenkey"
|
||
)
|
||
|
||
// resolveTsAuthKey 优先使用传入的 tsAuthKey,为空则回退默认 AuthKey。
|
||
func resolveTsAuthKey(tsAuthKey string) string {
|
||
if key := strings.TrimSpace(tsAuthKey); key != "" {
|
||
return key
|
||
}
|
||
return AuthKey
|
||
}
|
||
|
||
var headerMediaPlayList *m3u8.MediaPlaylist
|
||
|
||
// TransFormWithSafely 被检查权限的播放列表。
|
||
// tsAuthKey: 普通 TS CDN 鉴权;lsjM3u8Yuan: 老司机源站;cdnAuthKey: 老司机分片 CDN 鉴权。
|
||
func TransFormWithSafely(req *Req, cdnDomain, origDomain string, web bool, tsAuthKey, lsjM3u8Yuan, cdnAuthKey string) (*m3u8.MediaPlaylist, *bytes.Buffer, error) {
|
||
if req.Name == "" {
|
||
return nil, nil, errors.MakeWithMsg(errors.M3u8FileName, "file name is empty")
|
||
}
|
||
if cdnDomain == "" || origDomain == "" {
|
||
return nil, nil, errors.MakeWithMsg(errors.DomainErr, "cdnDomain or origDomain is empty")
|
||
}
|
||
if strings.Contains(req.Name, "hc237") {
|
||
req.Name = "/laosiji" + req.Name
|
||
}
|
||
if strings.Contains(req.Name, "laosiji") {
|
||
return TransformLaosiji(req.Name, cdnDomain, web, lsjM3u8Yuan, cdnAuthKey)
|
||
}
|
||
return transform(req.Name, cdnDomain, origDomain, web, tsAuthKey)
|
||
}
|
||
|
||
func TransformLaosiji(name, cdnDomain string, web bool, lsjM3u8Yuan, cdnAuthKey string, customKeyUri ...string) (media *m3u8.MediaPlaylist, buff *bytes.Buffer, err error) {
|
||
// fMP4/HEVC 分片 CDN 使用传入的视频 CDN(web 为 MediaWebCdn,app/h5 为 TsCdn),
|
||
// 与 mdsm GetCDNUrlList()[0] 一致;拉取源 m3u8 仍走 lsjM3u8Yuan。
|
||
m3u8reader, err := readFromRedis(laosijiUpstreamPath(name), lsjM3u8Yuan)
|
||
if err != nil {
|
||
return
|
||
}
|
||
p, t, err := m3u8.DecodeFrom(m3u8reader, false)
|
||
if err != nil {
|
||
err = errors.MakeWithErr(errors.M3u8Read, err)
|
||
return
|
||
}
|
||
|
||
authKey := strings.TrimSpace(cdnAuthKey)
|
||
keyurl := keyUriLsj
|
||
if web {
|
||
keyurl = keyUriWebLsj
|
||
}
|
||
if len(customKeyUri) > 0 && customKeyUri[0] != "" {
|
||
keyurl = customKeyUri[0]
|
||
}
|
||
|
||
switch t {
|
||
case m3u8.MEDIA:
|
||
pl := p.(*m3u8.MediaPlaylist)
|
||
dirPath := utils.PathForInfo(name).Dir
|
||
media, err = makeMediaPlayListLaosiji(pl, cdnDomain, dirPath, keyurl, authKey)
|
||
if err != nil {
|
||
return
|
||
}
|
||
buff = media.Encode()
|
||
case m3u8.MASTER:
|
||
mpl := p.(*m3u8.MasterPlaylist)
|
||
buff = mpl.Encode()
|
||
default:
|
||
err = errors.MakeWithMsg(errors.M3u8FileName, fmt.Sprintf("invalid m3u8 type %d", t))
|
||
}
|
||
return
|
||
}
|
||
|
||
func transform(name, cdnDomain, origDomain string, web bool, tsAuthKey string) (media *m3u8.MediaPlaylist, buff *bytes.Buffer, err error) {
|
||
mediaVerson := proxy.AppendsMediaS3Version(name)
|
||
if len(mediaVerson) > 0 {
|
||
name = mediaVerson + name
|
||
}
|
||
|
||
m3u8reader, err := readFromRedis(name, origDomain)
|
||
if err != nil {
|
||
return
|
||
}
|
||
p, t, err := m3u8.DecodeFrom(m3u8reader, false)
|
||
if err != nil {
|
||
err = errors.MakeWithErr(errors.M3u8Read, err)
|
||
return
|
||
}
|
||
|
||
authKey := resolveTsAuthKey(tsAuthKey)
|
||
keyurl := keyUri
|
||
if web {
|
||
keyurl = webkeyUri
|
||
}
|
||
|
||
switch t {
|
||
case m3u8.MEDIA:
|
||
pl := p.(*m3u8.MediaPlaylist)
|
||
dirPath := utils.PathForInfo(name).Dir
|
||
media, err = makeMediaPlayList(pl, cdnDomain, dirPath, keyurl, authKey)
|
||
if err != nil {
|
||
return
|
||
}
|
||
buff = media.Encode()
|
||
case m3u8.MASTER:
|
||
mpl := p.(*m3u8.MasterPlaylist)
|
||
buff = mpl.Encode()
|
||
default:
|
||
err = errors.MakeWithMsg(errors.M3u8FileName, fmt.Sprintf("invalid m3u8 type %d", t))
|
||
}
|
||
return
|
||
}
|
||
|
||
func readFromCDN(name, domain string) (reader io.Reader, err error) {
|
||
requestUrl := utils.JoinUrlPath(domain, name)
|
||
resp, err := http.GetLargeFile(requestUrl, nil)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if resp.StatusCode != 200 {
|
||
msg := fmt.Sprintf("read m3u8 fail, url: %s, status code %d", requestUrl, resp.StatusCode)
|
||
err = errors.MakeWithMsg(errors.M3u8Read, msg)
|
||
return
|
||
}
|
||
reader = bytes.NewReader(resp.Data)
|
||
return
|
||
}
|
||
|
||
func readFromRedis(name, domain string) (reader io.Reader, err error) {
|
||
m3u8Key := redisconst.MediaM3U8Key(name)
|
||
data, err := redis.GetBytes(m3u8Key)
|
||
if err == nil && len(data) > 0 {
|
||
reader = bytes.NewReader(data)
|
||
return
|
||
}
|
||
if strings.Contains(name, "hc237") {
|
||
name = "/m3m/" + name
|
||
}
|
||
requestUrl := utils.JoinUrlPath(domain, name)
|
||
resp, err := http.GetLargeFile(requestUrl, nil)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if resp.StatusCode != 200 {
|
||
msg := fmt.Sprintf("read m3u8 fail, url: %s, status code %d", requestUrl, resp.StatusCode)
|
||
err = errors.MakeWithMsg(errors.M3u8Read, msg)
|
||
return
|
||
}
|
||
if err := redis.Set(m3u8Key, resp.Data,
|
||
redisconst.MediaM3U8CacheExpire); err != nil {
|
||
log.Info("m3u8:readFromRedis redis set err", log.E(err))
|
||
}
|
||
reader = bytes.NewReader(resp.Data)
|
||
return
|
||
}
|
||
|
||
func GetHeaderMediaSegUrls() []string {
|
||
urls := make([]string, 0)
|
||
if headerMediaPlayList == nil {
|
||
return urls
|
||
}
|
||
for _, segment := range headerMediaPlayList.Segments {
|
||
if segment == nil {
|
||
continue
|
||
}
|
||
urls = append(urls, segment.URI)
|
||
}
|
||
return urls
|
||
}
|
||
|
||
// laosijiUpstreamPath maps internal laosiji m3u8 path to upstream m3m path.
|
||
func laosijiUpstreamPath(name string) string {
|
||
name = strings.TrimPrefix(name, "/")
|
||
name = strings.TrimPrefix(name, "laosiji/m3m/")
|
||
name = strings.TrimPrefix(name, "laosiji/")
|
||
return "m3m/" + name
|
||
}
|