123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 |
- package fllcc
- import (
- "context"
- "encoding/base64"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "net/http"
- "strings"
- "sync"
- "time"
- "gogs.daxia.dev/huanan/pkg.daxia.dev.git/fllcc/cache"
- "github.com/beevik/guid"
- "github.com/grafov/m3u8"
- )
- const (
- TYPE_VIDEO = "video"
- TYPE_KEY = "key"
- TYPE_TS = "ts"
- VERSION = "0.1"
- )
- var port = 4000
- var host = "127.0.0.1"
- var playUrlMap = map[string]string{}
- var playUrlMapLock = sync.Mutex{}
- var activeUrlMap = map[string]int64{}
- var activeMasterUrlMap = map[string]string{}
- var activeUrlMapLock = sync.Mutex{}
- var envMode = "prod"
- var srv *http.Server = nil
- var srvLock = sync.Mutex{}
- var fileCache = &cache.FileCache{}
- var client = http.Client{
- Timeout: 15 * time.Second,
- }
- var clientShortTimeout = http.Client{
- Timeout: 3 * time.Second,
- }
- //启动内部服务器
- func startup(dir string, cacheDir string, size int) {
- if cacheDir == "" || cacheDir == "/" {
- outputLog("启动失败")
- return
- }
- http.HandleFunc("/video/", videoHandler)
- http.HandleFunc("/key/", keyHandler)
- http.HandleFunc("/ts/", tsHandler)
- http.HandleFunc("/ping", pingHandler)
- http.HandleFunc("/version", versionHandler)
- fileCache = cache.NewFileCache(cacheDir, size)
- //这里有权限控制,没事
- _ = fileCache.ClearCache()
- if dir != "" {
- http.Handle("/v/",
- http.StripPrefix("/v/", http.FileServer(http.Dir(dir))))
- //打印一下目录内容
- //获取文件或目录相关信息
- fileInfoList, err := ioutil.ReadDir(dir)
- if err != nil {
- log.Println(err)
- } else {
- fmt.Println(len(fileInfoList))
- for i := range fileInfoList {
- log.Println(fileInfoList[i].Name()) //打印当前文件或目录下的文件或目录名
- }
- }
- }
- log.Println("run mode:" + envMode)
- //选取4000 - 5000的端口
- freePort, err := getFreePort()
- if err != nil {
- outputLog(err)
- return
- }
- port = freePort
- startThread()
- d := time.Second * 5
- t := time.NewTicker(d)
- defer t.Stop()
- for {
- <-t.C
- //检测一下是否真的停止了
- pingUrl := fmt.Sprintf("http://%s:%d/ping", host, port)
- resp, err := client.Get(pingUrl)
- if err == nil {
- resp.Body.Close()
- continue
- }
- outputLog("重启服务器")
- startThread()
- }
- }
- func serverLoop(srv *http.Server) {
- defer func() {
- if p := recover(); p != nil {
- outputLog("pinic:", p)
- }
- }()
- srvLock.Lock()
- srvTmp := &http.Server{Addr: fmt.Sprintf("%s:%d", host, port)}
- srv = srvTmp
- srvLock.Unlock()
- err := srvTmp.ListenAndServe()
- if err != nil {
- outputLog(err)
- }
- }
- func startThread() {
- srvLock.Lock()
- defer srvLock.Unlock()
- if srv != nil {
- //出错的时候才停止
- _ = srv.Shutdown(context.Background())
- }
- //启动服务器,但重启的时候,会退出
- go serverLoop(srv)
- }
- func getPlayUrl(oriPlayUrl string) string {
- if strings.HasPrefix(oriPlayUrl, "file://") {
- oriPlayUrl = strings.TrimPrefix(oriPlayUrl, "file://")
- oriPlayUrl = fmt.Sprintf("http://%s:%d/v/%s", host, port, oriPlayUrl)
- }
- playUrlMapLock.Lock()
- defer playUrlMapLock.Unlock()
- randUrl := getRandomUrl(TYPE_VIDEO)
- playUrlMap[randUrl] = oriPlayUrl
- activeUrlMapLock.Lock()
- defer activeUrlMapLock.Unlock()
- md5 := getMD5Hash(oriPlayUrl)
- activeUrlMap[md5] = time.Now().Unix()
- return randUrl
- }
- //处理ts,有缓存使用缓存,没有就获取数据,写入缓存,返回
- func tsHandler(w http.ResponseWriter, r *http.Request) {
- defer func() {
- _ = recover()
- }()
- curPath := r.URL.String()
- //提取ts的key
- pathList := strings.Split(curPath, "/")
- key := pathList[len(pathList)-1]
- data, err := fileCache.GetData(key)
- if err == nil {
- _, _ = w.Write(data)
- return
- }
- oriPath := ""
- //获取原路径
- func() {
- playUrlMapLock.Lock()
- defer playUrlMapLock.Unlock()
- oriPath = playUrlMap[curPath]
- }()
- if oriPath == "" {
- outputLog("获取原路径失败:" + curPath)
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- resp, err := client.Get(oriPath)
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- defer resp.Body.Close()
- bodyBuf := make([]byte, 0)
- const bufLen = 4096
- for {
- buf := make([]byte, bufLen)
- n, err := resp.Body.Read(buf)
- if n > 0 {
- //调整缓冲去大小
- buf = buf[:n]
- bodyBuf = append(bodyBuf, buf...)
- _, _ = w.Write(buf)
- continue
- }
- if err == io.EOF {
- break
- }
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- }
- _ = fileCache.SetData(key, bodyBuf)
- }
- //获取原来的key,解密内容后,返回正常的内容
- func keyHandler(w http.ResponseWriter, r *http.Request) {
- curPath := r.URL.String()
- oriPath := ""
- //校验key的有效性
- pathList := strings.Split(curPath, "_")
- if len(pathList) != 2 {
- outputLog("路径格式不对")
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- token := r.Header.Get("token")
- if token != "C203561DD73AEDC699358DFA92217E57" && envMode != "dev" {
- outputLog("token不对")
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- err := func() error {
- activeUrlMapLock.Lock()
- defer activeUrlMapLock.Unlock()
- md5Url := pathList[1]
- md5Map := activeMasterUrlMap[md5Url]
- if md5Map != "" {
- md5Url = md5Map
- }
- //urlActiveTime := activeUrlMap[md5Url]
- //超过5秒,没有心跳,停止播放
- //if time.Now().Unix()-urlActiveTime > 5 && envMode != "dev" {
- // return errors.New("心跳错误")
- //}
- return nil
- }()
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- func() {
- playUrlMapLock.Lock()
- defer playUrlMapLock.Unlock()
- oriPath = playUrlMap[curPath]
- }()
- if oriPath == "" {
- outputLog("获取原路径失败:" + curPath)
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- resp, err := client.Get(oriPath)
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- bodyStr := string(body)
- //解密key数据
- bodyStr = strings.TrimSpace(bodyStr)
- outputLog("key数据:" + bodyStr)
- //判断是不是试播地址,尝试解密
- tryKey := func() string {
- data, err := base64.StdEncoding.DecodeString(bodyStr)
- dataStr := string(data)
- outputLog("解码的key:" + dataStr)
- suffix := "123456"
- if err == nil && strings.HasSuffix(dataStr, suffix) {
- //这个就是试播
- dataStr = strings.TrimSuffix(dataStr, suffix)
- outputLog("试播key:" + dataStr)
- //_, _ = w.Write([]byte(dataStr))
- return dataStr
- }
- outputLog("不是试播key:" + dataStr)
- return ""
- }()
- outputLog("try key:" + tryKey)
- if tryKey != "" {
- outputLog("返回key" + tryKey)
- w.WriteHeader(http.StatusOK)
- _, _ = w.Write([]byte(tryKey))
- return
- }
- outputLog("进入非试播路径")
- descData, err := descData(bodyStr, "24a1d6ff39d8")
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- data, err := base64.StdEncoding.DecodeString(descData)
- if err != nil {
- outputLog(err.Error())
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- _, _ = w.Write(data)
- }
- func videoHandler(w http.ResponseWriter, r *http.Request) {
- path := r.URL.String()
- pathList := strings.Split(path, "/")
- id := pathList[len(pathList)-1]
- //获取完整请求地址
- randPath := getUrlByID(TYPE_VIDEO, id)
- oriPath := ""
- err := func() error {
- playUrlMapLock.Lock()
- defer playUrlMapLock.Unlock()
- oriPath = playUrlMap[randPath]
- if oriPath == "" {
- return errors.New("not exists")
- }
- return nil
- }()
- if err != nil {
- w.WriteHeader(http.StatusNotFound)
- return
- }
- resp, err := client.Get(oriPath)
- if err != nil {
- outputLog("get err:" + err.Error())
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- bodyStr := string(body)
- newM3U8Str, err := replaceUrl(oriPath, bodyStr)
- if err != nil {
- outputLog("解析内容失败:" + bodyStr)
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- _, _ = w.Write([]byte(newM3U8Str))
- }
- //ping 测试
- func pingHandler(w http.ResponseWriter, r *http.Request) {
- _, _ = w.Write([]byte("pong"))
- }
- //version 版本
- func versionHandler(w http.ResponseWriter, r *http.Request) {
- _, _ = w.Write([]byte(VERSION))
- }
- func getRandomUrl(typeName string) string {
- guid := guid.New().String()
- randUrl := fmt.Sprintf("http://%s:%d/%s/%s", host, port, typeName, guid)
- if typeName == TYPE_VIDEO {
- randUrl += ".m3u8"
- }
- return randUrl
- }
- func replaceUrl(baseUrl string, m3u8Str string) (string, error) {
- playUrlMapLock.Lock()
- defer playUrlMapLock.Unlock()
- urlMd5 := getMD5Hash(baseUrl)
- //解析m3u8内容,替换一下地址,加密key,用内部的,ts文件,用绝对地址
- p, listType, err := m3u8.DecodeFrom(strings.NewReader(m3u8Str), true)
- if err != nil {
- outputLog(err.Error())
- return "", err
- }
- if listType == m3u8.MASTER {
- masterPlayList := p.(*m3u8.MasterPlaylist)
- for _, item := range masterPlayList.Variants {
- masterUrl := item.URI
- oriUrl, _ := urlToABS(baseUrl, masterUrl)
- randUrl := getRandomUrl(TYPE_VIDEO)
- item.URI = randUrl
- playUrlMap[randUrl] = oriUrl
- func() {
- activeUrlMapLock.Lock()
- defer activeUrlMapLock.Unlock()
- activeMasterUrlMap[getMD5Hash(oriUrl)] = urlMd5
- }()
- }
- return masterPlayList.String(), nil
- }
- if listType != m3u8.MEDIA {
- return "", errors.New("解析失败")
- }
- mediaPlayList := p.(*m3u8.MediaPlaylist)
- if mediaPlayList.Key != nil {
- oriUrl, _ := urlToABS(baseUrl, mediaPlayList.Key.URI)
- randUrl := getRandomUrlRelative(TYPE_KEY) + "_" + urlMd5
- mediaPlayList.Key.URI = fmt.Sprintf("http://%s:%d%s", host, port, randUrl)
- playUrlMap[randUrl] = oriUrl
- }
- for _, item := range mediaPlayList.Segments {
- if item == nil {
- continue
- }
- //ts路径,用全路径
- oriUrl, _ := urlToABS(baseUrl, item.URI)
- randUrl := fmt.Sprintf("/ts/%s", getMD5Hash(oriUrl))
- playUrlMap[randUrl] = oriUrl
- item.URI = fmt.Sprintf("http://%s:%d/%s", host, port, randUrl)
- if item.Key == nil {
- continue
- }
- {
- oriUrl, _ := urlToABS(baseUrl, item.Key.URI)
- randUrl := getRandomUrlRelative(TYPE_KEY) + "_" + urlMd5
- item.Key.URI = fmt.Sprintf("http://%s:%d%s", host, port, randUrl)
- playUrlMap[randUrl] = oriUrl
- }
- }
- return mediaPlayList.String(), nil
- }
- func getRandomUrlRelative(typeName string) string {
- guid := guid.New().String()
- return fmt.Sprintf("/%s/%s", typeName, guid)
- }
- func geRandomUrlAbs(typeName string) string {
- guid := guid.New().String()
- return fmt.Sprintf("http://%s:%d/%s/%s", host, port, typeName, guid)
- }
- func getUrlByID(typeName string, id string) string {
- return fmt.Sprintf("http://%s:%d/%s/%s", host, port, typeName, id)
- }
|