mirror of
https://github.com/xaionaro-go/streamctl.git
synced 2025-10-19 05:44:39 +08:00
Add the boilerplate code for Kick
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
kick "github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick/types"
|
||||||
obs "github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs/types"
|
obs "github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs/types"
|
||||||
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
|
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
|
||||||
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
|
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
|
||||||
@@ -216,7 +217,7 @@ func streamStatus(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
result := map[streamcontrol.PlatformName]*streamcontrol.StreamStatus{}
|
result := map[streamcontrol.PlatformName]*streamcontrol.StreamStatus{}
|
||||||
for _, platID := range []streamcontrol.PlatformName{
|
for _, platID := range []streamcontrol.PlatformName{
|
||||||
obs.ID, twitch.ID, youtube.ID,
|
obs.ID, twitch.ID, youtube.ID, kick.ID,
|
||||||
} {
|
} {
|
||||||
isEnabled, err := streamD.IsBackendEnabled(ctx, platID)
|
isEnabled, err := streamD.IsBackendEnabled(ctx, platID)
|
||||||
assertNoError(ctx, err)
|
assertNoError(ctx, err)
|
||||||
|
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/xsync"
|
"github.com/xaionaro-go/streamctl/pkg/xsync"
|
||||||
@@ -138,12 +139,14 @@ func expandPath(rawPath string) string {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
idTwitch = twitch.ID
|
idTwitch = twitch.ID
|
||||||
|
idKick = kick.ID
|
||||||
idYoutube = youtube.ID
|
idYoutube = youtube.ID
|
||||||
)
|
)
|
||||||
|
|
||||||
func newConfig() streamcontrol.Config {
|
func newConfig() streamcontrol.Config {
|
||||||
cfg := streamcontrol.Config{}
|
cfg := streamcontrol.Config{}
|
||||||
twitch.InitConfig(cfg)
|
twitch.InitConfig(cfg)
|
||||||
|
kick.InitConfig(cfg)
|
||||||
youtube.InitConfig(cfg)
|
youtube.InitConfig(cfg)
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
@@ -157,6 +160,9 @@ func generateConfig(cmd *cobra.Command, args []string) {
|
|||||||
cfg[idTwitch].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
|
cfg[idTwitch].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
|
||||||
"some_profile": twitch.StreamProfile{},
|
"some_profile": twitch.StreamProfile{},
|
||||||
}
|
}
|
||||||
|
cfg[idKick].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
|
||||||
|
"some_profile": kick.StreamProfile{},
|
||||||
|
}
|
||||||
cfg[idYoutube].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
|
cfg[idYoutube].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
|
||||||
"some_profile": youtube.StreamProfile{},
|
"some_profile": youtube.StreamProfile{},
|
||||||
}
|
}
|
||||||
@@ -218,13 +224,24 @@ func readConfigFromPath(
|
|||||||
logger.Debugf(ctx, "final stream profiles of twitch: %#+v", (*cfg)[idTwitch].StreamProfiles)
|
logger.Debugf(ctx, "final stream profiles of twitch: %#+v", (*cfg)[idTwitch].StreamProfiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (*cfg)[idKick] != nil {
|
||||||
|
err = streamcontrol.ConvertStreamProfiles[kick.StreamProfile](
|
||||||
|
ctx,
|
||||||
|
(*cfg)[idKick].StreamProfiles,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to convert stream profiles of kick: %w: <%s>", err, b)
|
||||||
|
}
|
||||||
|
logger.Debugf(ctx, "final stream profiles of kick: %#+v", (*cfg)[idKick].StreamProfiles)
|
||||||
|
}
|
||||||
|
|
||||||
if (*cfg)[idYoutube] != nil {
|
if (*cfg)[idYoutube] != nil {
|
||||||
err = streamcontrol.ConvertStreamProfiles[youtube.StreamProfile](
|
err = streamcontrol.ConvertStreamProfiles[youtube.StreamProfile](
|
||||||
ctx,
|
ctx,
|
||||||
(*cfg)[idYoutube].StreamProfiles,
|
(*cfg)[idYoutube].StreamProfiles,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to convert stream profiles of twitch: %w: <%s>", err, b)
|
return fmt.Errorf("unable to convert stream profiles of youtube: %w: <%s>", err, b)
|
||||||
}
|
}
|
||||||
logger.Debugf(
|
logger.Debugf(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -283,6 +300,34 @@ func getTwitchStreamController(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getKickStreamController(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg streamcontrol.Config,
|
||||||
|
) (*kick.Kick, error) {
|
||||||
|
platCfg := streamcontrol.GetPlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile](
|
||||||
|
ctx,
|
||||||
|
cfg,
|
||||||
|
idKick,
|
||||||
|
)
|
||||||
|
if platCfg == nil {
|
||||||
|
logger.Infof(ctx, "kick config was not found")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debugf(ctx, "kick config: %#+v", platCfg)
|
||||||
|
return kick.New(ctx, *platCfg,
|
||||||
|
func(c kick.Config) error {
|
||||||
|
return xsync.DoR1(ctx, &saveConfigLock, func() error {
|
||||||
|
cfg[idKick] = &streamcontrol.AbstractPlatformConfig{
|
||||||
|
Config: c.Config,
|
||||||
|
StreamProfiles: streamcontrol.ToAbstractStreamProfiles(c.StreamProfiles),
|
||||||
|
}
|
||||||
|
return saveConfig(ctx, cfg)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func getYouTubeStreamController(
|
func getYouTubeStreamController(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg streamcontrol.Config,
|
cfg streamcontrol.Config,
|
||||||
@@ -325,6 +370,14 @@ func getStreamControllers(
|
|||||||
result = append(result, streamcontrol.ToAbstract(twitch))
|
result = append(result, streamcontrol.ToAbstract(twitch))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kick, err := getKickStreamController(ctx, cfg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Panic(ctx, err)
|
||||||
|
}
|
||||||
|
if kick != nil {
|
||||||
|
result = append(result, streamcontrol.ToAbstract(kick))
|
||||||
|
}
|
||||||
|
|
||||||
youtube, err := getYouTubeStreamController(ctx, cfg)
|
youtube, err := getYouTubeStreamController(ctx, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Panic(ctx, err)
|
logger.Panic(ctx, err)
|
||||||
|
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/xaionaro-go/streamctl/cmd/streamd/ui"
|
"github.com/xaionaro-go/streamctl/cmd/streamd/ui"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
||||||
@@ -265,6 +266,9 @@ func main() {
|
|||||||
func(ctx context.Context, cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile]) (bool, error) {
|
func(ctx context.Context, cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile]) (bool, error) {
|
||||||
return false, streamd.ErrSkipBackend
|
return false, streamd.ErrSkipBackend
|
||||||
},
|
},
|
||||||
|
func(ctx context.Context, cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile]) (bool, error) {
|
||||||
|
return false, streamd.ErrSkipBackend
|
||||||
|
},
|
||||||
func(ctx context.Context, cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile]) (bool, error) {
|
func(ctx context.Context, cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile]) (bool, error) {
|
||||||
return false, streamd.ErrSkipBackend
|
return false, streamd.ErrSkipBackend
|
||||||
},
|
},
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/facebookincubator/go-belt/tool/logger"
|
"github.com/facebookincubator/go-belt/tool/logger"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/oauthhandler"
|
"github.com/xaionaro-go/streamctl/pkg/oauthhandler"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
obs "github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs/types"
|
obs "github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs/types"
|
||||||
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
|
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
|
||||||
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
|
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
|
||||||
@@ -28,6 +29,10 @@ type UI struct {
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile],
|
cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile],
|
||||||
) (bool, error)
|
) (bool, error)
|
||||||
|
InputKickUserInfoFn func(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile],
|
||||||
|
) (bool, error)
|
||||||
InputYouTubeUserInfoFn func(
|
InputYouTubeUserInfoFn func(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile],
|
cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile],
|
||||||
@@ -50,6 +55,10 @@ func NewUI(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile],
|
cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile],
|
||||||
) (bool, error),
|
) (bool, error),
|
||||||
|
inputKickUserInfoFn func(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile],
|
||||||
|
) (bool, error),
|
||||||
inputYouTubeUserInfoFn func(
|
inputYouTubeUserInfoFn func(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile],
|
cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile],
|
||||||
@@ -68,6 +77,7 @@ func NewUI(
|
|||||||
CodeChMapLocker: xsync.RWMutex{},
|
CodeChMapLocker: xsync.RWMutex{},
|
||||||
SetLoggingLevelFn: setLoggingLevel,
|
SetLoggingLevelFn: setLoggingLevel,
|
||||||
InputTwitchUserInfoFn: inputTwitchUserInfoFn,
|
InputTwitchUserInfoFn: inputTwitchUserInfoFn,
|
||||||
|
InputKickUserInfoFn: inputKickUserInfoFn,
|
||||||
InputYouTubeUserInfoFn: inputYouTubeUserInfoFn,
|
InputYouTubeUserInfoFn: inputYouTubeUserInfoFn,
|
||||||
InputOBSConnectInfoFn: inputOBSConnectInfoFn,
|
InputOBSConnectInfoFn: inputOBSConnectInfoFn,
|
||||||
}
|
}
|
||||||
@@ -220,6 +230,13 @@ func (ui *UI) OAuthHandlerTwitch(
|
|||||||
return ui.oauth2Handler(ctx, twitch.ID, arg)
|
return ui.oauth2Handler(ctx, twitch.ID, arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ui *UI) OAuthHandlerKick(
|
||||||
|
ctx context.Context,
|
||||||
|
arg oauthhandler.OAuthHandlerArgument,
|
||||||
|
) error {
|
||||||
|
return ui.oauth2Handler(ctx, kick.ID, arg)
|
||||||
|
}
|
||||||
|
|
||||||
func (ui *UI) OAuthHandlerYouTube(
|
func (ui *UI) OAuthHandlerYouTube(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
arg oauthhandler.OAuthHandlerArgument,
|
arg oauthhandler.OAuthHandlerArgument,
|
||||||
@@ -234,6 +251,13 @@ func (ui *UI) InputTwitchUserInfo(
|
|||||||
return ui.InputTwitchUserInfoFn(ctx, cfg)
|
return ui.InputTwitchUserInfoFn(ctx, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ui *UI) InputKickUserInfo(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile],
|
||||||
|
) (bool, error) {
|
||||||
|
return ui.InputKickUserInfoFn(ctx, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
func (ui *UI) InputYouTubeUserInfo(
|
func (ui *UI) InputYouTubeUserInfo(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile],
|
cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile],
|
||||||
|
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/xaionaro-go/streamctl/pkg/mainprocess"
|
"github.com/xaionaro-go/streamctl/pkg/mainprocess"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
||||||
@@ -147,6 +148,12 @@ func runStreamd(
|
|||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
return false, streamd.ErrSkipBackend
|
return false, streamd.ErrSkipBackend
|
||||||
},
|
},
|
||||||
|
func(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile],
|
||||||
|
) (bool, error) {
|
||||||
|
return false, streamd.ErrSkipBackend
|
||||||
|
},
|
||||||
func(
|
func(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile],
|
cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile],
|
||||||
|
@@ -19,26 +19,35 @@ type Kick struct {
|
|||||||
Channel *kickcom.ChannelV1
|
Channel *kickcom.ChannelV1
|
||||||
Client Client
|
Client Client
|
||||||
ChatHandler *ChatHandler
|
ChatHandler *ChatHandler
|
||||||
|
SaveCfgFn func(Config) error
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ streamcontrol.StreamController[StreamProfile] = (*Kick)(nil)
|
var _ streamcontrol.StreamController[StreamProfile] = (*Kick)(nil)
|
||||||
|
|
||||||
func New(channelSlug string) (*Kick, error) {
|
func New(
|
||||||
ctx := context.TODO()
|
ctx context.Context,
|
||||||
|
cfg Config,
|
||||||
|
saveCfgFn func(Config) error,
|
||||||
|
) (*Kick, error) {
|
||||||
|
|
||||||
|
if cfg.Config.Channel == "" {
|
||||||
|
return nil, fmt.Errorf("channel is not set")
|
||||||
|
}
|
||||||
|
|
||||||
client, err := kickcom.New()
|
client, err := kickcom.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to initialize a client to Kick: %w", err)
|
return nil, fmt.Errorf("unable to initialize a client to Kick: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
channel, err := client.GetChannelV1(ctx, channelSlug)
|
channel, err := client.GetChannelV1(ctx, cfg.Config.Channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to obtain channel info: %w", err)
|
return nil, fmt.Errorf("unable to obtain channel info: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
k := &Kick{
|
k := &Kick{
|
||||||
Client: client,
|
Client: client,
|
||||||
Channel: channel,
|
Channel: channel,
|
||||||
|
SaveCfgFn: saveCfgFn,
|
||||||
}
|
}
|
||||||
|
|
||||||
chatHandler, err := k.newChatHandler(ctx, channel.ID)
|
chatHandler, err := k.newChatHandler(ctx, channel.ID)
|
||||||
|
@@ -298,6 +298,9 @@ type BackendDataTwitch struct {
|
|||||||
Cache cache.Twitch
|
Cache cache.Twitch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BackendDataKick struct {
|
||||||
|
}
|
||||||
|
|
||||||
type BackendDataYouTube struct {
|
type BackendDataYouTube struct {
|
||||||
Cache cache.YouTube
|
Cache cache.YouTube
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/xaionaro-go/streamctl/pkg/player"
|
"github.com/xaionaro-go/streamctl/pkg/player"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/player/protobuf/go/player_grpc"
|
"github.com/xaionaro-go/streamctl/pkg/player/protobuf/go/player_grpc"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
obs "github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs/types"
|
obs "github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs/types"
|
||||||
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
|
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
|
||||||
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
|
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
|
||||||
@@ -802,6 +803,13 @@ func (c *Client) GetBackendData(
|
|||||||
&_data,
|
&_data,
|
||||||
)
|
)
|
||||||
data = _data
|
data = _data
|
||||||
|
case kick.ID:
|
||||||
|
_data := api.BackendDataKick{}
|
||||||
|
err = json.Unmarshal(
|
||||||
|
[]byte(reply.GetData()),
|
||||||
|
&_data,
|
||||||
|
)
|
||||||
|
data = _data
|
||||||
case youtube.ID:
|
case youtube.ID:
|
||||||
_data := api.BackendDataYouTube{}
|
_data := api.BackendDataYouTube{}
|
||||||
err = json.Unmarshal(
|
err = json.Unmarshal(
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/facebookincubator/go-belt/tool/logger"
|
"github.com/facebookincubator/go-belt/tool/logger"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
||||||
@@ -35,6 +36,7 @@ func NewConfig() Config {
|
|||||||
cfg := streamcontrol.Config{}
|
cfg := streamcontrol.Config{}
|
||||||
obs.InitConfig(cfg)
|
obs.InitConfig(cfg)
|
||||||
twitch.InitConfig(cfg)
|
twitch.InitConfig(cfg)
|
||||||
|
kick.InitConfig(cfg)
|
||||||
youtube.InitConfig(cfg)
|
youtube.InitConfig(cfg)
|
||||||
return Config{
|
return Config{
|
||||||
Backends: cfg,
|
Backends: cfg,
|
||||||
@@ -54,6 +56,9 @@ func NewSampleConfig() Config {
|
|||||||
cfg.Backends[twitch.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
|
cfg.Backends[twitch.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
|
||||||
"some_profile": twitch.StreamProfile{},
|
"some_profile": twitch.StreamProfile{},
|
||||||
}
|
}
|
||||||
|
cfg.Backends[kick.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
|
||||||
|
"some_profile": kick.StreamProfile{},
|
||||||
|
}
|
||||||
cfg.Backends[youtube.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
|
cfg.Backends[youtube.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
|
||||||
"some_profile": youtube.StreamProfile{},
|
"some_profile": youtube.StreamProfile{},
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
||||||
@@ -89,6 +90,16 @@ func (cfg *Config) UnmarshalYAML(b []byte) (_err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.Backends[kick.ID] != nil {
|
||||||
|
err = streamcontrol.ConvertStreamProfiles[kick.StreamProfile](
|
||||||
|
context.Background(),
|
||||||
|
cfg.Backends[kick.ID].StreamProfiles,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to convert stream profiles of kick: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.Backends[youtube.ID] != nil {
|
if cfg.Backends[youtube.ID] != nil {
|
||||||
err = streamcontrol.ConvertStreamProfiles[youtube.StreamProfile](
|
err = streamcontrol.ConvertStreamProfiles[youtube.StreamProfile](
|
||||||
context.Background(),
|
context.Background(),
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
|
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
||||||
@@ -22,6 +23,8 @@ func ProfileGRPC2Go(
|
|||||||
profile = &obs.StreamProfile{}
|
profile = &obs.StreamProfile{}
|
||||||
case twitch.ID:
|
case twitch.ID:
|
||||||
profile = &twitch.StreamProfile{}
|
profile = &twitch.StreamProfile{}
|
||||||
|
case kick.ID:
|
||||||
|
profile = &kick.StreamProfile{}
|
||||||
case youtube.ID:
|
case youtube.ID:
|
||||||
profile = &youtube.StreamProfile{}
|
profile = &youtube.StreamProfile{}
|
||||||
default:
|
default:
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
||||||
@@ -15,6 +16,8 @@ func NewStreamProfile(
|
|||||||
switch platID {
|
switch platID {
|
||||||
case twitch.ID:
|
case twitch.ID:
|
||||||
return &twitch.StreamProfile{}, nil
|
return &twitch.StreamProfile{}, nil
|
||||||
|
case kick.ID:
|
||||||
|
return &kick.StreamProfile{}, nil
|
||||||
case youtube.ID:
|
case youtube.ID:
|
||||||
return &youtube.StreamProfile{}, nil
|
return &youtube.StreamProfile{}, nil
|
||||||
case obs.ID:
|
case obs.ID:
|
||||||
|
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/facebookincubator/go-belt/tool/logger"
|
"github.com/facebookincubator/go-belt/tool/logger"
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
||||||
@@ -37,6 +38,8 @@ func (d *StreamD) EXPERIMENTAL_ReinitStreamControllers(ctx context.Context) erro
|
|||||||
err = d.initOBSBackend(ctx)
|
err = d.initOBSBackend(ctx)
|
||||||
case strings.ToLower(string(twitch.ID)):
|
case strings.ToLower(string(twitch.ID)):
|
||||||
err = d.initTwitchBackend(ctx)
|
err = d.initTwitchBackend(ctx)
|
||||||
|
case strings.ToLower(string(kick.ID)):
|
||||||
|
err = d.initKickBackend(ctx)
|
||||||
case strings.ToLower(string(youtube.ID)):
|
case strings.ToLower(string(youtube.ID)):
|
||||||
err = d.initYouTubeBackend(ctx)
|
err = d.initYouTubeBackend(ctx)
|
||||||
}
|
}
|
||||||
@@ -196,6 +199,47 @@ func newTwitch(
|
|||||||
return twitch, nil
|
return twitch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newKick(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg *streamcontrol.AbstractPlatformConfig,
|
||||||
|
setUserData func(context.Context, *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile]) (bool, error),
|
||||||
|
saveCfgFunc func(*streamcontrol.AbstractPlatformConfig) error,
|
||||||
|
customOAuthHandler kick.OAuthHandler,
|
||||||
|
getOAuthListenPorts func() []uint16,
|
||||||
|
) (
|
||||||
|
*kick.Kick,
|
||||||
|
error,
|
||||||
|
) {
|
||||||
|
platCfg := streamcontrol.ConvertPlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile](
|
||||||
|
ctx,
|
||||||
|
cfg,
|
||||||
|
)
|
||||||
|
if platCfg == nil {
|
||||||
|
return nil, fmt.Errorf("kick config was not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Enable != nil && !*cfg.Enable {
|
||||||
|
return nil, ErrSkipBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debugf(ctx, "kick config: %#+v", platCfg)
|
||||||
|
cfg = streamcontrol.ToAbstractPlatformConfig(ctx, platCfg)
|
||||||
|
kick, err := kick.New(ctx, *platCfg,
|
||||||
|
func(c kick.Config) error {
|
||||||
|
return saveCfgFunc(&streamcontrol.AbstractPlatformConfig{
|
||||||
|
Enable: c.Enable,
|
||||||
|
Config: c.Config,
|
||||||
|
StreamProfiles: streamcontrol.ToAbstractStreamProfiles(c.StreamProfiles),
|
||||||
|
Custom: c.Custom,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to initialize Kick client: %w", err)
|
||||||
|
}
|
||||||
|
return kick, nil
|
||||||
|
}
|
||||||
|
|
||||||
func newYouTube(
|
func newYouTube(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg *streamcontrol.AbstractPlatformConfig,
|
cfg *streamcontrol.AbstractPlatformConfig,
|
||||||
@@ -365,6 +409,24 @@ func (d *StreamD) initTwitchBackend(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *StreamD) initKickBackend(ctx context.Context) error {
|
||||||
|
kick, err := newKick(
|
||||||
|
ctx,
|
||||||
|
d.Config.Backends[kick.ID],
|
||||||
|
d.UI.InputKickUserInfo,
|
||||||
|
func(cfg *streamcontrol.AbstractPlatformConfig) error {
|
||||||
|
return d.setPlatformConfig(ctx, kick.ID, cfg)
|
||||||
|
},
|
||||||
|
d.UI.OAuthHandlerKick,
|
||||||
|
d.GetOAuthListenPorts,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.StreamControllers.Kick = kick
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *StreamD) initYouTubeBackend(ctx context.Context) error {
|
func (d *StreamD) initYouTubeBackend(ctx context.Context) error {
|
||||||
youTube, err := newYouTube(
|
youTube, err := newYouTube(
|
||||||
ctx,
|
ctx,
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/facebookincubator/go-belt/tool/logger"
|
"github.com/facebookincubator/go-belt/tool/logger"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
|
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
|
||||||
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
|
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamd/api"
|
"github.com/xaionaro-go/streamctl/pkg/streamd/api"
|
||||||
@@ -35,6 +36,8 @@ func (a *platformsControllerAdapter) CheckStreamStartedByURL(
|
|||||||
platID = youtube.ID
|
platID = youtube.ID
|
||||||
case strings.Contains(destination.Hostname(), "twitch"):
|
case strings.Contains(destination.Hostname(), "twitch"):
|
||||||
platID = twitch.ID
|
platID = twitch.ID
|
||||||
|
case strings.Contains(destination.Hostname(), "global-contribute.live-video.net"):
|
||||||
|
platID = kick.ID
|
||||||
default:
|
default:
|
||||||
return false, fmt.Errorf(
|
return false, fmt.Errorf(
|
||||||
"do not know how to check if the stream started for '%s'",
|
"do not know how to check if the stream started for '%s'",
|
||||||
|
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/xaionaro-go/streamctl/pkg/player"
|
"github.com/xaionaro-go/streamctl/pkg/player"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/repository"
|
"github.com/xaionaro-go/streamctl/pkg/repository"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
||||||
@@ -42,6 +43,7 @@ import (
|
|||||||
type StreamControllers struct {
|
type StreamControllers struct {
|
||||||
OBS *obs.OBS
|
OBS *obs.OBS
|
||||||
Twitch *twitch.Twitch
|
Twitch *twitch.Twitch
|
||||||
|
Kick *kick.Kick
|
||||||
YouTube *youtube.YouTube
|
YouTube *youtube.YouTube
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,6 +452,8 @@ func (d *StreamD) IsBackendEnabled(
|
|||||||
return d.StreamControllers.OBS != nil, nil
|
return d.StreamControllers.OBS != nil, nil
|
||||||
case twitch.ID:
|
case twitch.ID:
|
||||||
return d.StreamControllers.Twitch != nil, nil
|
return d.StreamControllers.Twitch != nil, nil
|
||||||
|
case kick.ID:
|
||||||
|
return d.StreamControllers.Kick != nil, nil
|
||||||
case youtube.ID:
|
case youtube.ID:
|
||||||
return d.StreamControllers.YouTube != nil, nil
|
return d.StreamControllers.YouTube != nil, nil
|
||||||
default:
|
default:
|
||||||
@@ -518,6 +522,21 @@ func (d *StreamD) StartStream(
|
|||||||
return fmt.Errorf("unable to start the stream on Twitch: %w", err)
|
return fmt.Errorf("unable to start the stream on Twitch: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
case kick.ID:
|
||||||
|
profile, err := streamcontrol.GetStreamProfile[kick.StreamProfile](ctx, profile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to convert the profile into Twitch profile: %w", err)
|
||||||
|
}
|
||||||
|
err = d.StreamControllers.Kick.StartStream(
|
||||||
|
d.ctxForController(ctx),
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
*profile,
|
||||||
|
customArgs...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to start the stream on Twitch: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
case youtube.ID:
|
case youtube.ID:
|
||||||
profile, err := streamcontrol.GetStreamProfile[youtube.StreamProfile](ctx, profile)
|
profile, err := streamcontrol.GetStreamProfile[youtube.StreamProfile](ctx, profile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -617,6 +636,8 @@ func (d *StreamD) GetBackendData(
|
|||||||
return api.BackendDataOBS{}, nil
|
return api.BackendDataOBS{}, nil
|
||||||
case twitch.ID:
|
case twitch.ID:
|
||||||
return api.BackendDataTwitch{Cache: d.Cache.Twitch}, nil
|
return api.BackendDataTwitch{Cache: d.Cache.Twitch}, nil
|
||||||
|
case kick.ID:
|
||||||
|
return api.BackendDataKick{}, nil
|
||||||
case youtube.ID:
|
case youtube.ID:
|
||||||
return api.BackendDataYouTube{Cache: d.Cache.Youtube}, nil
|
return api.BackendDataYouTube{Cache: d.Cache.Youtube}, nil
|
||||||
default:
|
default:
|
||||||
@@ -644,6 +665,21 @@ func (d *StreamD) tryConnectTwitch(
|
|||||||
errmon.ObserveErrorCtx(ctx, err)
|
errmon.ObserveErrorCtx(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *StreamD) tryConnectKick(
|
||||||
|
ctx context.Context,
|
||||||
|
) {
|
||||||
|
if d.StreamControllers.Kick != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := d.Config.Backends[kick.ID]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := d.initKickBackend(ctx)
|
||||||
|
errmon.ObserveErrorCtx(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *StreamD) tryConnectYouTube(
|
func (d *StreamD) tryConnectYouTube(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) {
|
) {
|
||||||
@@ -676,6 +712,13 @@ func (d *StreamD) streamController(
|
|||||||
if d.StreamControllers.Twitch != nil {
|
if d.StreamControllers.Twitch != nil {
|
||||||
result = streamcontrol.ToAbstract(d.StreamControllers.Twitch)
|
result = streamcontrol.ToAbstract(d.StreamControllers.Twitch)
|
||||||
}
|
}
|
||||||
|
case kick.ID:
|
||||||
|
if d.StreamControllers.Kick == nil {
|
||||||
|
d.tryConnectKick(ctx)
|
||||||
|
}
|
||||||
|
if d.StreamControllers.Kick != nil {
|
||||||
|
result = streamcontrol.ToAbstract(d.StreamControllers.Kick)
|
||||||
|
}
|
||||||
case youtube.ID:
|
case youtube.ID:
|
||||||
if d.StreamControllers.YouTube == nil {
|
if d.StreamControllers.YouTube == nil {
|
||||||
d.tryConnectYouTube(ctx)
|
d.tryConnectYouTube(ctx)
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/facebookincubator/go-belt/tool/logger"
|
"github.com/facebookincubator/go-belt/tool/logger"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/oauthhandler"
|
"github.com/xaionaro-go/streamctl/pkg/oauthhandler"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
obs "github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs/types"
|
obs "github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs/types"
|
||||||
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
|
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
|
||||||
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
|
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
|
||||||
@@ -19,12 +20,17 @@ type UI interface {
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) (bool, string, []byte, error)
|
) (bool, string, []byte, error)
|
||||||
OAuthHandlerTwitch(ctx context.Context, arg oauthhandler.OAuthHandlerArgument) error
|
OAuthHandlerTwitch(ctx context.Context, arg oauthhandler.OAuthHandlerArgument) error
|
||||||
|
OAuthHandlerKick(ctx context.Context, arg oauthhandler.OAuthHandlerArgument) error
|
||||||
OAuthHandlerYouTube(ctx context.Context, arg oauthhandler.OAuthHandlerArgument) error
|
OAuthHandlerYouTube(ctx context.Context, arg oauthhandler.OAuthHandlerArgument) error
|
||||||
OpenBrowser(ctx context.Context, url string) error
|
OpenBrowser(ctx context.Context, url string) error
|
||||||
InputTwitchUserInfo(
|
InputTwitchUserInfo(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile],
|
cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile],
|
||||||
) (bool, error)
|
) (bool, error)
|
||||||
|
InputKickUserInfo(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile],
|
||||||
|
) (bool, error)
|
||||||
InputYouTubeUserInfo(
|
InputYouTubeUserInfo(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile],
|
cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile],
|
||||||
|
@@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/xaionaro-go/streamctl/pkg/colorx"
|
"github.com/xaionaro-go/streamctl/pkg/colorx"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
||||||
@@ -278,6 +279,7 @@ func (p *Panel) updateMonitorPageStreamStatus(
|
|||||||
obs.ID,
|
obs.ID,
|
||||||
youtube.ID,
|
youtube.ID,
|
||||||
twitch.ID,
|
twitch.ID,
|
||||||
|
kick.ID,
|
||||||
} {
|
} {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
observability.Go(ctx, func() {
|
observability.Go(ctx, func() {
|
||||||
|
@@ -35,6 +35,7 @@ import (
|
|||||||
"github.com/xaionaro-go/streamctl/pkg/screenshot"
|
"github.com/xaionaro-go/streamctl/pkg/screenshot"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/screenshoter"
|
"github.com/xaionaro-go/streamctl/pkg/screenshoter"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube"
|
||||||
@@ -107,6 +108,7 @@ type Panel struct {
|
|||||||
|
|
||||||
youtubeCheck *widget.Check
|
youtubeCheck *widget.Check
|
||||||
twitchCheck *widget.Check
|
twitchCheck *widget.Check
|
||||||
|
kickCheck *widget.Check
|
||||||
|
|
||||||
configPath string
|
configPath string
|
||||||
configCache *streamdconfig.Config
|
configCache *streamdconfig.Config
|
||||||
@@ -662,6 +664,15 @@ func (p *Panel) OAuthHandlerTwitch(
|
|||||||
return p.oauthHandler(ctx, twitch.ID, arg)
|
return p.oauthHandler(ctx, twitch.ID, arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Panel) OAuthHandlerKick(
|
||||||
|
ctx context.Context,
|
||||||
|
arg oauthhandler.OAuthHandlerArgument,
|
||||||
|
) error {
|
||||||
|
logger.Infof(ctx, "OAuthHandlerKick: %#+v", arg)
|
||||||
|
defer logger.Infof(ctx, "/OAuthHandlerKick")
|
||||||
|
return p.oauthHandler(ctx, kick.ID, arg)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Panel) OAuthHandlerYouTube(
|
func (p *Panel) OAuthHandlerYouTube(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
arg oauthhandler.OAuthHandlerArgument,
|
arg oauthhandler.OAuthHandlerArgument,
|
||||||
@@ -857,6 +868,13 @@ func (p *Panel) InputTwitchUserInfo(
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Panel) InputKickUserInfo(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg *streamcontrol.PlatformConfig[kick.PlatformSpecificConfig, kick.StreamProfile],
|
||||||
|
) (bool, error) {
|
||||||
|
return false, fmt.Errorf("not implemented, yet")
|
||||||
|
}
|
||||||
|
|
||||||
var youtubeCredentialsCreateLink, _ = url.Parse(
|
var youtubeCredentialsCreateLink, _ = url.Parse(
|
||||||
"https://console.cloud.google.com/apis/credentials/oauthclient",
|
"https://console.cloud.google.com/apis/credentials/oauthclient",
|
||||||
)
|
)
|
||||||
@@ -1105,6 +1123,7 @@ func (p *Panel) refilterProfiles(ctx context.Context) {
|
|||||||
subValueMatch = true
|
subValueMatch = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case kick.StreamProfile:
|
||||||
case youtube.StreamProfile:
|
case youtube.StreamProfile:
|
||||||
if containTagSubstringCI(prof.Tags, filterValue) {
|
if containTagSubstringCI(prof.Tags, filterValue) {
|
||||||
subValueMatch = true
|
subValueMatch = true
|
||||||
@@ -1214,6 +1233,7 @@ func (p *Panel) openSettingsWindow(ctx context.Context) error {
|
|||||||
for _, backendID := range []streamcontrol.PlatformName{
|
for _, backendID := range []streamcontrol.PlatformName{
|
||||||
obs.ID,
|
obs.ID,
|
||||||
twitch.ID,
|
twitch.ID,
|
||||||
|
kick.ID,
|
||||||
youtube.ID,
|
youtube.ID,
|
||||||
} {
|
} {
|
||||||
isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID)
|
isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID)
|
||||||
@@ -1319,6 +1339,13 @@ func (p *Panel) openSettingsWindow(ctx context.Context) error {
|
|||||||
twitchAlreadyLoggedIn.SetText("(already logged in)")
|
twitchAlreadyLoggedIn.SetText("(already logged in)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kickAlreadyLoggedIn := widget.NewLabel("")
|
||||||
|
if !backendEnabled[kick.ID] {
|
||||||
|
kickAlreadyLoggedIn.SetText("(not logged in)")
|
||||||
|
} else {
|
||||||
|
kickAlreadyLoggedIn.SetText("(already logged in)")
|
||||||
|
}
|
||||||
|
|
||||||
youtubeAlreadyLoggedIn := widget.NewLabel("")
|
youtubeAlreadyLoggedIn := widget.NewLabel("")
|
||||||
if !backendEnabled[youtube.ID] {
|
if !backendEnabled[youtube.ID] {
|
||||||
youtubeAlreadyLoggedIn.SetText("(not logged in)")
|
youtubeAlreadyLoggedIn.SetText("(not logged in)")
|
||||||
@@ -1432,6 +1459,27 @@ func (p *Panel) openSettingsWindow(ctx context.Context) error {
|
|||||||
}),
|
}),
|
||||||
twitchAlreadyLoggedIn,
|
twitchAlreadyLoggedIn,
|
||||||
),
|
),
|
||||||
|
container.NewHBox(
|
||||||
|
widget.NewButtonWithIcon("(Re-)login in Kick", theme.LoginIcon(), func() {
|
||||||
|
if cfg.Backends[kick.ID] == nil {
|
||||||
|
kick.InitConfig(cfg.Backends)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Backends[kick.ID].Enable = nil
|
||||||
|
cfg.Backends[kick.ID].Config = kick.PlatformSpecificConfig{}
|
||||||
|
|
||||||
|
if err := p.StreamD.SetConfig(ctx, cfg); err != nil {
|
||||||
|
p.DisplayError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.StreamD.EXPERIMENTAL_ReinitStreamControllers(ctx); err != nil {
|
||||||
|
p.DisplayError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
kickAlreadyLoggedIn,
|
||||||
|
),
|
||||||
container.NewHBox(
|
container.NewHBox(
|
||||||
widget.NewButtonWithIcon("(Re-)login in YouTube", theme.LoginIcon(), func() {
|
widget.NewButtonWithIcon("(Re-)login in YouTube", theme.LoginIcon(), func() {
|
||||||
if cfg.Backends[youtube.ID] == nil {
|
if cfg.Backends[youtube.ID] == nil {
|
||||||
@@ -1603,6 +1651,7 @@ func (p *Panel) getUpdatedStatus_backends_noLock(ctx context.Context) {
|
|||||||
for _, backendID := range []streamcontrol.PlatformName{
|
for _, backendID := range []streamcontrol.PlatformName{
|
||||||
obs.ID,
|
obs.ID,
|
||||||
twitch.ID,
|
twitch.ID,
|
||||||
|
kick.ID,
|
||||||
youtube.ID,
|
youtube.ID,
|
||||||
} {
|
} {
|
||||||
isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID)
|
isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID)
|
||||||
@@ -1616,6 +1665,9 @@ func (p *Panel) getUpdatedStatus_backends_noLock(ctx context.Context) {
|
|||||||
if backendEnabled[twitch.ID] {
|
if backendEnabled[twitch.ID] {
|
||||||
p.twitchCheck.Enable()
|
p.twitchCheck.Enable()
|
||||||
}
|
}
|
||||||
|
if backendEnabled[kick.ID] {
|
||||||
|
p.kickCheck.Enable()
|
||||||
|
}
|
||||||
if backendEnabled[youtube.ID] {
|
if backendEnabled[youtube.ID] {
|
||||||
p.youtubeCheck.Enable()
|
p.youtubeCheck.Enable()
|
||||||
}
|
}
|
||||||
@@ -1856,6 +1908,10 @@ func (p *Panel) initMainWindow(
|
|||||||
p.twitchCheck.SetChecked(true)
|
p.twitchCheck.SetChecked(true)
|
||||||
p.twitchCheck.Disable()
|
p.twitchCheck.Disable()
|
||||||
|
|
||||||
|
p.kickCheck = widget.NewCheck("Kick", nil)
|
||||||
|
p.kickCheck.SetChecked(true)
|
||||||
|
p.kickCheck.Disable()
|
||||||
|
|
||||||
p.youtubeCheck = widget.NewCheck("YouTube", nil)
|
p.youtubeCheck = widget.NewCheck("YouTube", nil)
|
||||||
p.youtubeCheck.SetChecked(true)
|
p.youtubeCheck.SetChecked(true)
|
||||||
p.youtubeCheck.Disable()
|
p.youtubeCheck.Disable()
|
||||||
@@ -1892,6 +1948,9 @@ func (p *Panel) initMainWindow(
|
|||||||
twLabel := widget.NewLabel("TW:")
|
twLabel := widget.NewLabel("TW:")
|
||||||
twLabel.Importance = widget.HighImportance
|
twLabel.Importance = widget.HighImportance
|
||||||
p.streamStatus[twitch.ID] = widget.NewLabel("")
|
p.streamStatus[twitch.ID] = widget.NewLabel("")
|
||||||
|
kcLabel := widget.NewLabel("Kc:")
|
||||||
|
kcLabel.Importance = widget.HighImportance
|
||||||
|
p.streamStatus[kick.ID] = widget.NewLabel("")
|
||||||
ytLabel := widget.NewLabel("YT:")
|
ytLabel := widget.NewLabel("YT:")
|
||||||
ytLabel.Importance = widget.HighImportance
|
ytLabel.Importance = widget.HighImportance
|
||||||
p.streamStatus[youtube.ID] = widget.NewLabel("")
|
p.streamStatus[youtube.ID] = widget.NewLabel("")
|
||||||
@@ -1903,6 +1962,7 @@ func (p *Panel) initMainWindow(
|
|||||||
}
|
}
|
||||||
streamInfoItems.Add(container.NewHBox(layout.NewSpacer(), obsLabel, p.streamStatus[obs.ID]))
|
streamInfoItems.Add(container.NewHBox(layout.NewSpacer(), obsLabel, p.streamStatus[obs.ID]))
|
||||||
streamInfoItems.Add(container.NewHBox(layout.NewSpacer(), twLabel, p.streamStatus[twitch.ID]))
|
streamInfoItems.Add(container.NewHBox(layout.NewSpacer(), twLabel, p.streamStatus[twitch.ID]))
|
||||||
|
streamInfoItems.Add(container.NewHBox(layout.NewSpacer(), kcLabel, p.streamStatus[kick.ID]))
|
||||||
streamInfoItems.Add(container.NewHBox(layout.NewSpacer(), ytLabel, p.streamStatus[youtube.ID]))
|
streamInfoItems.Add(container.NewHBox(layout.NewSpacer(), ytLabel, p.streamStatus[youtube.ID]))
|
||||||
streamInfoContainer := container.NewBorder(
|
streamInfoContainer := container.NewBorder(
|
||||||
nil,
|
nil,
|
||||||
@@ -2243,6 +2303,7 @@ func (p *Panel) setupStreamNoLock(ctx context.Context) {
|
|||||||
for _, backendID := range []streamcontrol.PlatformName{
|
for _, backendID := range []streamcontrol.PlatformName{
|
||||||
obs.ID,
|
obs.ID,
|
||||||
twitch.ID,
|
twitch.ID,
|
||||||
|
kick.ID,
|
||||||
youtube.ID,
|
youtube.ID,
|
||||||
} {
|
} {
|
||||||
isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID)
|
isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID)
|
||||||
@@ -2278,6 +2339,19 @@ func (p *Panel) setupStreamNoLock(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.kickCheck.Checked && backendEnabled[kick.ID] {
|
||||||
|
err := p.StreamD.StartStream(
|
||||||
|
ctx,
|
||||||
|
kick.ID,
|
||||||
|
p.streamTitleField.Text,
|
||||||
|
p.streamDescriptionField.Text,
|
||||||
|
profile.PerPlatform[kick.ID],
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
p.DisplayError(fmt.Errorf("unable to setup the stream on Twitch: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if p.youtubeCheck.Checked && backendEnabled[youtube.ID] {
|
if p.youtubeCheck.Checked && backendEnabled[youtube.ID] {
|
||||||
if p.streamIsRunning(ctx, youtube.ID) {
|
if p.streamIsRunning(ctx, youtube.ID) {
|
||||||
logger.Debugf(ctx, "updating the stream info at YouTube")
|
logger.Debugf(ctx, "updating the stream info at YouTube")
|
||||||
@@ -2435,6 +2509,9 @@ func (p *Panel) stopStreamNoLock(ctx context.Context) {
|
|||||||
if backendEnabled[twitch.ID] {
|
if backendEnabled[twitch.ID] {
|
||||||
p.twitchCheck.Enable()
|
p.twitchCheck.Enable()
|
||||||
}
|
}
|
||||||
|
if backendEnabled[kick.ID] {
|
||||||
|
p.kickCheck.Enable()
|
||||||
|
}
|
||||||
if backendEnabled[youtube.ID] {
|
if backendEnabled[youtube.ID] {
|
||||||
p.youtubeCheck.Enable()
|
p.youtubeCheck.Enable()
|
||||||
}
|
}
|
||||||
@@ -2845,6 +2922,7 @@ func (p *Panel) profileWindow(
|
|||||||
var (
|
var (
|
||||||
obsProfile *obs.StreamProfile
|
obsProfile *obs.StreamProfile
|
||||||
twitchProfile *twitch.StreamProfile
|
twitchProfile *twitch.StreamProfile
|
||||||
|
kickProfile *kick.StreamProfile
|
||||||
youtubeProfile *youtube.StreamProfile
|
youtubeProfile *youtube.StreamProfile
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2870,6 +2948,7 @@ func (p *Panel) profileWindow(
|
|||||||
for _, backendID := range []streamcontrol.PlatformName{
|
for _, backendID := range []streamcontrol.PlatformName{
|
||||||
obs.ID,
|
obs.ID,
|
||||||
twitch.ID,
|
twitch.ID,
|
||||||
|
kick.ID,
|
||||||
youtube.ID,
|
youtube.ID,
|
||||||
} {
|
} {
|
||||||
isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID)
|
isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID)
|
||||||
@@ -2893,6 +2972,8 @@ func (p *Panel) profileWindow(
|
|||||||
}
|
}
|
||||||
_ = backendData[obs.ID].(api.BackendDataOBS)
|
_ = backendData[obs.ID].(api.BackendDataOBS)
|
||||||
dataTwitch := backendData[twitch.ID].(api.BackendDataTwitch)
|
dataTwitch := backendData[twitch.ID].(api.BackendDataTwitch)
|
||||||
|
dataKick := backendData[kick.ID].(api.BackendDataKick)
|
||||||
|
_ = dataKick // TODO: delete me!
|
||||||
dataYouTube := backendData[youtube.ID].(api.BackendDataYouTube)
|
dataYouTube := backendData[youtube.ID].(api.BackendDataYouTube)
|
||||||
|
|
||||||
var bottomContent []fyne.CanvasObject
|
var bottomContent []fyne.CanvasObject
|
||||||
@@ -3026,6 +3107,14 @@ func (p *Panel) profileWindow(
|
|||||||
bottomContent = append(bottomContent, widget.NewLabel("Twitch is disabled"))
|
bottomContent = append(bottomContent, widget.NewLabel("Twitch is disabled"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bottomContent = append(bottomContent, widget.NewSeparator())
|
||||||
|
bottomContent = append(bottomContent, widget.NewRichTextFromMarkdown("# Kick:"))
|
||||||
|
if backendEnabled[kick.ID] {
|
||||||
|
bottomContent = append(bottomContent, widget.NewLabel("Kick configuration is not implemented, yet"))
|
||||||
|
} else {
|
||||||
|
bottomContent = append(bottomContent, widget.NewLabel("Kick is disabled"))
|
||||||
|
}
|
||||||
|
|
||||||
var getYoutubeTags func() []string
|
var getYoutubeTags func() []string
|
||||||
bottomContent = append(bottomContent, widget.NewSeparator())
|
bottomContent = append(bottomContent, widget.NewSeparator())
|
||||||
bottomContent = append(bottomContent, widget.NewRichTextFromMarkdown("# YouTube:"))
|
bottomContent = append(bottomContent, widget.NewRichTextFromMarkdown("# YouTube:"))
|
||||||
@@ -3228,6 +3317,9 @@ func (p *Panel) profileWindow(
|
|||||||
}
|
}
|
||||||
profile.PerPlatform[twitch.ID] = twitchProfile
|
profile.PerPlatform[twitch.ID] = twitchProfile
|
||||||
}
|
}
|
||||||
|
if kickProfile != nil {
|
||||||
|
profile.PerPlatform[kick.ID] = kickProfile
|
||||||
|
}
|
||||||
if youtubeProfile != nil {
|
if youtubeProfile != nil {
|
||||||
if getYoutubeTags != nil {
|
if getYoutubeTags != nil {
|
||||||
youtubeProfile.Tags = sanitizeTags(getYoutubeTags())
|
youtubeProfile.Tags = sanitizeTags(getYoutubeTags())
|
||||||
|
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||||
obs "github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs/types"
|
obs "github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs/types"
|
||||||
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
|
twitch "github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch/types"
|
||||||
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
|
youtube "github.com/xaionaro-go/streamctl/pkg/streamcontrol/youtube/types"
|
||||||
@@ -250,6 +251,7 @@ func (ui *timersUI) kickOffRemotely(
|
|||||||
for _, platID := range []streamcontrol.PlatformName{
|
for _, platID := range []streamcontrol.PlatformName{
|
||||||
youtube.ID,
|
youtube.ID,
|
||||||
twitch.ID,
|
twitch.ID,
|
||||||
|
kick.ID,
|
||||||
obs.ID,
|
obs.ID,
|
||||||
} {
|
} {
|
||||||
_, err := streamD.AddTimer(ctx, deadline, &action.EndStream{
|
_, err := streamD.AddTimer(ctx, deadline, &action.EndStream{
|
||||||
|
Reference in New Issue
Block a user