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.
120 lines
2.9 KiB
120 lines
2.9 KiB
// Package liveurls
|
|
// @Time:2023/02/17 16:32
|
|
// @File:youtube.go
|
|
// @SoftWare:Goland
|
|
// @Author:Popeye
|
|
// @Contact:TG@popeyelau
|
|
|
|
package liveurls
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"github.com/tidwall/gjson"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/etherlabsio/go-m3u8/m3u8"
|
|
)
|
|
|
|
var streamCachedMap sync.Map
|
|
|
|
type Youtube struct {
|
|
//https://www.youtube.com/watch?v=cK4LemjoFd0
|
|
//Rid: cK4LemjoFd0
|
|
Rid string
|
|
Quality string
|
|
}
|
|
|
|
func (y *Youtube) GetLiveUrl() any {
|
|
if cached, ok := getKey(y.Rid); ok {
|
|
return cached
|
|
}
|
|
//proxyUrl, err := url.Parse("http://127.0.0.1:8888")
|
|
client := &http.Client{
|
|
Timeout: time.Second * 5,
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
//Transport: &http.Transport{Proxy: http.ProxyURL(proxyUrl)},
|
|
}
|
|
|
|
json := []byte(fmt.Sprintf(`{"context": {"client": {"hl": "zh","clientVersion": "2.20201021.03.00","clientName": "WEB"}},"videoId": "%s"}`, y.Rid))
|
|
reqBody := bytes.NewBuffer(json)
|
|
r, _ := http.NewRequest("POST", "https://www.youtube.com/youtubei/v1/player", reqBody)
|
|
r.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36")
|
|
resp, err := client.Do(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
body, _ := io.ReadAll(resp.Body)
|
|
str := string(body)
|
|
|
|
stream := gjson.Get(str, "streamingData.hlsManifestUrl")
|
|
if stream.Exists() && len(stream.String()) > 0 {
|
|
quality := y.getResolution(stream.String())
|
|
if quality != nil {
|
|
return *quality
|
|
}
|
|
return stream
|
|
}
|
|
|
|
formats := gjson.Get(str, "streamingData.formats")
|
|
if formats.Exists() && formats.IsArray() {
|
|
arr := formats.Array()
|
|
playback := arr[len(arr)-1].Get("url").String()
|
|
return playback
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (y *Youtube) getResolution(liveurl string) *string {
|
|
client := &http.Client{Timeout: time.Second * 5}
|
|
r, _ := http.NewRequest("GET", liveurl, nil)
|
|
r.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36")
|
|
resp, err := client.Do(r)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
playlist, err := m3u8.Read(resp.Body)
|
|
defer resp.Body.Close()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
playlists := playlist.Playlists()
|
|
|
|
if len(playlists) < 1 {
|
|
return nil
|
|
}
|
|
|
|
mapping := map[string]string{}
|
|
for _, item := range playlists {
|
|
mapping[strconv.Itoa(item.Resolution.Height)] = item.URI
|
|
}
|
|
|
|
if stream, ok := mapping[y.Quality]; ok {
|
|
setKey(y.Rid, stream, 600)
|
|
return &stream
|
|
}
|
|
|
|
stream := playlists[len(playlists)-1].URI
|
|
setKey(y.Rid, stream, 600)
|
|
return &stream
|
|
}
|
|
|
|
func setKey(key string, data interface{}, timeout int) {
|
|
streamCachedMap.Store(key, data)
|
|
time.AfterFunc(time.Second*time.Duration(timeout), func() {
|
|
streamCachedMap.Delete(key)
|
|
})
|
|
}
|
|
|
|
func getKey(key string) (interface{}, bool) {
|
|
return streamCachedMap.Load(key)
|
|
}
|
|
|