package live import ( "crypto/md5" "encoding/hex" "errors" "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 } func NewAliyunAdapter(cfg Config) (Adapter, error) { if !cfg.AllowRealCloud || cfg.PushDomain == "" || cfg.PlayDomain == "" || cfg.AppName == "" || cfg.AuthKey == "" || cfg.PlayAuthKey == "" { return nil, ErrInvalidConfig } if cfg.Protocol == "" { cfg.Protocol = "srt" } if cfg.SRTPort <= 0 { cfg.SRTPort = 1105 } return AliyunAdapter{cfg: cfg}, 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 signature := sign(path, expires.Unix(), a.cfg.PlayAuthKey) base := "https://" + a.cfg.PlayDomain + "/" + a.cfg.AppName + "/" + req.StreamName return PlayURLs{HLS: base + ".m3u8?auth_key=" + signature, FLV: base + ".flv?auth_key=" + signature, RTMP: "rtmp://" + a.cfg.PlayDomain + "/" + a.cfg.AppName + "/" + req.StreamName, ExpiresAt: expires}, nil } func (a AliyunAdapter) QueryOnline(string) (OnlineStatus, error) { return OnlineStatus{}, errors.New("aliyun online query is not configured") } func sign(path string, expiresAt int64, key string) string { rand := strconv.FormatInt(expiresAt%10000, 10) sum := md5.Sum([]byte(fmt.Sprintf("%s-%d-%s-%s-%s", path, expiresAt, rand, "0", 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 }