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.
117 lines
4.3 KiB
117 lines
4.3 KiB
package live
|
|
|
|
import (
|
|
"crypto/md5"
|
|
cryptorand "crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
type DisabledAdapter struct{}
|
|
|
|
func (DisabledAdapter) Provider() string { return "disabled" }
|
|
func (DisabledAdapter) CreateStream(StreamRequest) (StreamCredentials, error) {
|
|
return StreamCredentials{}, ErrDisabled
|
|
}
|
|
func (DisabledAdapter) CreatePlayURLs(PlayRequest) (PlayURLs, error) { return PlayURLs{}, ErrDisabled }
|
|
func (DisabledAdapter) QueryOnline(string) (OnlineStatus, error) { return OnlineStatus{}, ErrDisabled }
|
|
|
|
// FakeAdapter is a local provider that never contacts a cloud service.
|
|
type FakeAdapter struct {
|
|
PlayDomain string
|
|
PushDomain string
|
|
AppName string
|
|
}
|
|
|
|
func (f FakeAdapter) Provider() string { return "fake" }
|
|
func (f FakeAdapter) CreateStream(req StreamRequest) (StreamCredentials, error) {
|
|
return StreamCredentials{
|
|
Provider: f.Provider(),
|
|
PushURL: "fake://" + f.PushDomain + "/" + f.AppName + "/" + req.StreamName,
|
|
ExpiresAt: req.ExpiresAt,
|
|
}, nil
|
|
}
|
|
func (f FakeAdapter) CreatePlayURLs(req PlayRequest) (PlayURLs, error) {
|
|
return PlayURLs{
|
|
HLS: "fake://" + f.PlayDomain + "/" + f.AppName + "/" + req.StreamName + ".m3u8",
|
|
ExpiresAt: req.ExpiresAt,
|
|
}, nil
|
|
}
|
|
func (f FakeAdapter) QueryOnline(string) (OnlineStatus, error) {
|
|
return OnlineStatus{Online: true}, nil
|
|
}
|
|
|
|
type AliyunAdapter struct {
|
|
cfg Config
|
|
client *aliyunLiveClient
|
|
}
|
|
|
|
func NewAliyunAdapter(cfg Config) (Adapter, error) {
|
|
if !cfg.AllowRealCloud || cfg.PushDomain == "" || cfg.PlayDomain == "" || cfg.AppName == "" || cfg.AuthKey == "" || cfg.PlayAuthKey == "" || cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" || cfg.RegionID == "" {
|
|
return nil, ErrInvalidConfig
|
|
}
|
|
if cfg.Protocol == "" {
|
|
cfg.Protocol = "srt"
|
|
}
|
|
if cfg.Protocol != "srt" && cfg.Protocol != "rtmps" {
|
|
return nil, ErrInvalidConfig
|
|
}
|
|
if cfg.SRTPort <= 0 {
|
|
cfg.SRTPort = 1105
|
|
}
|
|
client, err := newAliyunLiveClient(cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return AliyunAdapter{cfg: cfg, client: client}, nil
|
|
}
|
|
|
|
func (a AliyunAdapter) Provider() string { return "aliyun" }
|
|
func (a AliyunAdapter) CreateStream(req StreamRequest) (StreamCredentials, error) {
|
|
return StreamCredentials{Provider: a.Provider(), PushURL: a.pushURL(req.StreamName, req.ExpiresAt), ExpiresAt: req.ExpiresAt}, nil
|
|
}
|
|
func (a AliyunAdapter) CreatePlayURLs(req PlayRequest) (PlayURLs, error) {
|
|
expires := req.ExpiresAt
|
|
path := "/" + a.cfg.AppName + "/" + req.StreamName
|
|
// Alibaba Live authenticates the complete playback path. HLS and FLV
|
|
// therefore need independent signatures because their file extensions are
|
|
// part of the URI used by the CDN auth check.
|
|
hlsPath := path + ".m3u8"
|
|
flvPath := path + ".flv"
|
|
hlsSignature := sign(hlsPath, expires.Unix(), a.cfg.PlayAuthKey)
|
|
flvSignature := sign(flvPath, expires.Unix(), a.cfg.PlayAuthKey)
|
|
base := "http://" + a.cfg.PlayDomain + "/" + a.cfg.AppName + "/" + req.StreamName
|
|
rtmpPath := path
|
|
rtmpSignature := sign(rtmpPath, expires.Unix(), a.cfg.PlayAuthKey)
|
|
rtmp := "rtmp://" + a.cfg.PlayDomain + "/" + a.cfg.AppName + "/" + req.StreamName + "?auth_key=" + rtmpSignature
|
|
return PlayURLs{
|
|
HLS: base + ".m3u8?auth_key=" + hlsSignature,
|
|
FLV: base + ".flv?auth_key=" + flvSignature,
|
|
RTMP: rtmp,
|
|
ExpiresAt: expires,
|
|
}, nil
|
|
}
|
|
func (a AliyunAdapter) QueryOnline(streamName string) (OnlineStatus, error) {
|
|
return a.client.queryOnline(streamName)
|
|
}
|
|
|
|
func sign(path string, expiresAt int64, key string) string {
|
|
var random [4]byte
|
|
if _, err := cryptorand.Read(random[:]); err != nil {
|
|
panic(fmt.Sprintf("generate live URL nonce: %v", err))
|
|
}
|
|
rand := strconv.FormatUint(uint64(random[0])<<24|uint64(random[1])<<16|uint64(random[2])<<8|uint64(random[3]), 10)
|
|
sum := md5.Sum([]byte(fmt.Sprintf("%s-%d-%s-0-%s", path, expiresAt, rand, key)))
|
|
return fmt.Sprintf("%d-%s-0-%s", expiresAt, rand, hex.EncodeToString(sum[:]))
|
|
}
|
|
|
|
func (a AliyunAdapter) pushURL(streamName string, expires time.Time) string {
|
|
path := "/" + a.cfg.AppName + "/" + streamName
|
|
auth := sign(path, expires.Unix(), a.cfg.AuthKey)
|
|
if a.cfg.Protocol == "srt" {
|
|
return "srt://" + a.cfg.PushDomain + ":" + strconv.Itoa(a.cfg.SRTPort) + "?streamid=#!::h=" + a.cfg.PushDomain + ",r=" + path + "?auth_key=" + auth + ",m=publish"
|
|
}
|
|
return "rtmps://" + a.cfg.PushDomain + "/" + a.cfg.AppName + "/" + streamName + "?auth_key=" + auth
|
|
}
|
|
|