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) }