package m3u8 import ( "bytes" "crypto/md5" "encoding/hex" "fmt" "strconv" "strings" "time" "videoweb/pub/common/errors" "videoweb/pub/common/utils" "videoweb/pub/pubdef" "videoweb/pub/storage/proxy" "github.com/grafov/m3u8" ) // TransformEx m3u8 文件转换。 // apiType(1:app,2:h5app,3:web) // authKey: TS CDN 鉴权(空则回退默认 AuthKey) // lsjM3u8Yuan / cdnAuthKey: 老司机源站与分片鉴权 // customKeyUri: 非空时直接使用,用于将鉴权参数嵌入 EXT-X-KEY URI func TransformEx(name, cdnDomain, origDomain string, apiType int, authKey, lsjM3u8Yuan, cdnAuthKey string, customKeyUri ...string) ( media *m3u8.MediaPlaylist, buff *bytes.Buffer, err error) { return transformEx(name, cdnDomain, origDomain, apiType, authKey, lsjM3u8Yuan, cdnAuthKey, true, customKeyUri...) } // TransformExOriginalPath 按数据库中的原始路径读取 m3u8,不自动追加 v1/v2/v3 前缀。 // 短剧水印未完成时仍播放原片,原片路径如 k12/...,需要保留原路径。 func TransformExOriginalPath(name, cdnDomain, origDomain string, apiType int, authKey, lsjM3u8Yuan, cdnAuthKey string, customKeyUri ...string) ( media *m3u8.MediaPlaylist, buff *bytes.Buffer, err error) { return transformEx(name, cdnDomain, origDomain, apiType, authKey, lsjM3u8Yuan, cdnAuthKey, false, customKeyUri...) } func transformEx(name, cdnDomain, origDomain string, apiType int, authKey, lsjM3u8Yuan, cdnAuthKey string, appendMediaVersion bool, customKeyUri ...string) ( media *m3u8.MediaPlaylist, buff *bytes.Buffer, err error) { if name == "" { err = errors.MakeWithMsg(errors.M3u8FileName, "m3u8 file name is empty") return } if cdnDomain == "" || origDomain == "" { err = errors.MakeWithMsg(errors.DomainErr, "cdnDomain or origDomain is empty") return } if strings.Contains(name, "hc237") { name = "/laosiji" + name } if strings.Contains(name, "laosiji") { return TransformLaosiji(name, cdnDomain, false, lsjM3u8Yuan, cdnAuthKey, customKeyUri...) } if appendMediaVersion { mediaVerson := proxy.AppendsMediaS3Version(name) if len(mediaVerson) > 0 { name = mediaVerson + name } } authKey = resolveTsAuthKey(authKey) 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 } apiPath := keyUri if len(customKeyUri) > 0 && customKeyUri[0] != "" { apiPath = customKeyUri[0] } else if apiType == 2 { apiPath = h5KeyUri } else if apiType == 3 { apiPath = webkeyUri } switch t { case m3u8.MEDIA: pl := p.(*m3u8.MediaPlaylist) dirPath := utils.PathForInfo(name).Dir media, err = makeTSList(pl, cdnDomain, dirPath, apiPath, 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 makeTSList(from *m3u8.MediaPlaylist, cdnDomain string, dirPath string, apiPath string, authKey string) ( media *m3u8.MediaPlaylist, err error) { media, err = m3u8.NewMediaPlaylist(from.WinSize(), from.Count()) if err != nil { return } now := time.Now() base := utils.JoinUrlPath(cdnDomain, dirPath) for _, segment := range from.Segments { if segment == nil { continue } sg := new(m3u8.MediaSegment) sg.URI = utils.JoinUrlPath(base, segment.URI) if len(authKey) > 0 { urlPath := utils.JoinUrlPath(dirPath, segment.URI) urlAuth := generateUrlAuth(now, urlPath, authKey) sg.URI = sg.URI + urlAuth } sg.Title = segment.Title sg.Duration = segment.Duration if err = media.AppendSegment(sg); err != nil { return } } _ = media.SetDefaultKey("AES-128", apiPath, "", "", "") media.Close() return } func generateUrlAuth(now time.Time, path string, authKey string) string { timestamp := now.Unix() signStr := fmt.Sprintf("%s-%d-0-0-%s", path, timestamp, authKey) md5Str := getMD5Sign(signStr) // TS CDN 鉴权参数后追加应用标识,便于流量统计。 urlAuth := fmt.Sprintf("?md=%d-0-0-%s&c=%d", timestamp, md5Str, pubdef.AppId) return urlAuth } func generateUrlAuthQiniu(now time.Time, path string, authKey string) string { timestamp := strconv.FormatInt(now.Add(2*time.Hour).Unix(), 16) base64 := path signStr := authKey + base64 + timestamp md5Str := getMD5Sign(signStr) urlAuth := fmt.Sprintf("?sign=%s&t=%s", md5Str, timestamp) return urlAuth } func getMD5Sign(buf string) string { md5Ctx := md5.New() md5Ctx.Write([]byte(buf)) cipherStr := md5Ctx.Sum(nil) nsign := hex.EncodeToString(cipherStr) return nsign }