golines --max-len=60

This commit is contained in:
Dmitrii Okunev
2024-10-16 22:58:55 +01:00
parent df42a4da66
commit fb33738f1c
73 changed files with 1812 additions and 433 deletions

View File

@@ -147,12 +147,24 @@ func streamSetup(cmd *cobra.Command, args []string) {
assertNoError(ctx, err)
if isEnabled[youtube.ID] {
err := streamD.StartStream(ctx, youtube.ID, title, description, cfg.Backends[youtube.ID].StreamProfiles[profileName])
err := streamD.StartStream(
ctx,
youtube.ID,
title,
description,
cfg.Backends[youtube.ID].StreamProfiles[profileName],
)
assertNoError(ctx, err)
}
if isEnabled[twitch.ID] {
err := streamD.StartStream(ctx, twitch.ID, title, description, cfg.Backends[twitch.ID].StreamProfiles[profileName])
err := streamD.StartStream(
ctx,
twitch.ID,
title,
description,
cfg.Backends[twitch.ID].StreamProfiles[profileName],
)
assertNoError(ctx, err)
}
}

View File

@@ -97,7 +97,8 @@ func init() {
StreamStart.PersistentFlags().String("title", "", "stream title")
StreamStart.PersistentFlags().String("description", "", "stream description")
StreamStart.PersistentFlags().String("profile", "", "profile")
StreamStart.PersistentFlags().StringArray("youtube-templates", nil, "the list of templates used to create streams; if nothing is provided, then a stream won't be created")
StreamStart.PersistentFlags().
StringArray("youtube-templates", nil, "the list of templates used to create streams; if nothing is provided, then a stream won't be created")
Root.AddCommand(GenerateConfig)
Root.AddCommand(SetTitle)
@@ -153,8 +154,12 @@ func generateConfig(cmd *cobra.Command, args []string) {
logger.Panicf(cmd.Context(), "file '%s' already exists", cfgPath)
}
cfg := newConfig()
cfg[idTwitch].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{"some_profile": twitch.StreamProfile{}}
cfg[idYoutube].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{"some_profile": youtube.StreamProfile{}}
cfg[idTwitch].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
"some_profile": twitch.StreamProfile{},
}
cfg[idYoutube].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
"some_profile": youtube.StreamProfile{},
}
err := writeConfigToPath(cmd.Context(), cfgPath, cfg)
if err != nil {
logger.Panic(cmd.Context(), err)
@@ -203,7 +208,10 @@ func readConfigFromPath(
}
if (*cfg)[idTwitch] != nil {
err = streamcontrol.ConvertStreamProfiles[twitch.StreamProfile](ctx, (*cfg)[idTwitch].StreamProfiles)
err = streamcontrol.ConvertStreamProfiles[twitch.StreamProfile](
ctx,
(*cfg)[idTwitch].StreamProfiles,
)
if err != nil {
return fmt.Errorf("unable to convert stream profiles of twitch: %w: <%s>", err, b)
}
@@ -211,11 +219,18 @@ func readConfigFromPath(
}
if (*cfg)[idYoutube] != nil {
err = streamcontrol.ConvertStreamProfiles[youtube.StreamProfile](ctx, (*cfg)[idYoutube].StreamProfiles)
err = streamcontrol.ConvertStreamProfiles[youtube.StreamProfile](
ctx,
(*cfg)[idYoutube].StreamProfiles,
)
if err != nil {
return fmt.Errorf("unable to convert stream profiles of twitch: %w: <%s>", err, b)
}
logger.Debugf(ctx, "final stream profiles of youtube: %#+v", (*cfg)[idYoutube].StreamProfiles)
logger.Debugf(
ctx,
"final stream profiles of youtube: %#+v",
(*cfg)[idYoutube].StreamProfiles,
)
}
return nil
@@ -244,7 +259,11 @@ func getTwitchStreamController(
ctx context.Context,
cfg streamcontrol.Config,
) (*twitch.Twitch, error) {
platCfg := streamcontrol.GetPlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile](ctx, cfg, idTwitch)
platCfg := streamcontrol.GetPlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile](
ctx,
cfg,
idTwitch,
)
if platCfg == nil {
logger.Infof(ctx, "twitch config was not found")
return nil, nil
@@ -268,7 +287,11 @@ func getYouTubeStreamController(
ctx context.Context,
cfg streamcontrol.Config,
) (*youtube.YouTube, error) {
platCfg := streamcontrol.GetPlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile](ctx, cfg, idYoutube)
platCfg := streamcontrol.GetPlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile](
ctx,
cfg,
idYoutube,
)
if platCfg == nil {
logger.Infof(ctx, "youtube config was not found")
return nil, nil
@@ -288,7 +311,10 @@ func getYouTubeStreamController(
)
}
func getStreamControllers(ctx context.Context, cfg streamcontrol.Config) streamcontrol.StreamControllers {
func getStreamControllers(
ctx context.Context,
cfg streamcontrol.Config,
) streamcontrol.StreamControllers {
var result streamcontrol.StreamControllers
twitch, err := getTwitchStreamController(ctx, cfg)

View File

@@ -39,9 +39,21 @@ const forceNetPProfOnAndroid = true
func main() {
loggerLevel := logger.LevelWarning
pflag.Var(&loggerLevel, "log-level", "Log level")
listenAddr := pflag.String("listen-addr", ":3594", "the address to listen for incoming connections to")
configPath := pflag.String("config-path", "/etc/streamd/streamd.yaml", "the path to the config file")
netPprofAddr := pflag.String("go-net-pprof-addr", "", "address to listen to for net/pprof requests")
listenAddr := pflag.String(
"listen-addr",
":3594",
"the address to listen for incoming connections to",
)
configPath := pflag.String(
"config-path",
"/etc/streamd/streamd.yaml",
"the path to the config file",
)
netPprofAddr := pflag.String(
"go-net-pprof-addr",
"",
"address to listen to for net/pprof requests",
)
cpuProfile := pflag.String("go-profile-cpu", "", "file to write cpu profile to")
heapProfile := pflag.String("go-profile-heap", "", "file to write memory profile to")
sentryDSN := pflag.String("sentry-dsn", "", "DSN of a Sentry instance to send error reports")
@@ -210,7 +222,13 @@ func main() {
},
func(listenPort uint16, platID streamcontrol.PlatformName, authURL string) bool {
logger.Debugf(ctx, "streamd.UI.OpenOAuthURL(%d, %s, '%s')", listenPort, platID, authURL)
defer logger.Debugf(ctx, "/streamd.UI.OpenOAuthURL(%d, %s, '%s')", listenPort, platID, authURL)
defer logger.Debugf(
ctx,
"/streamd.UI.OpenOAuthURL(%d, %s, '%s')",
listenPort,
platID,
authURL,
)
grpcLocker.Lock()
logger.Debugf(ctx, "streamdGRPCLocker.Lock()-ed")

View File

@@ -159,7 +159,13 @@ func (ui *UI) oauth2Handler(
defer removeReceiver()
}
logger.Debugf(ctx, "asking to open the URL '%s' using listen port %d for platform '%s'", arg.AuthURL, arg.ListenPort, platID)
logger.Debugf(
ctx,
"asking to open the URL '%s' using listen port %d for platform '%s'",
arg.AuthURL,
arg.ListenPort,
platID,
)
ui.OAuthURLOpenFn(arg.ListenPort, platID, arg.AuthURL)
t := time.NewTicker(time.Hour)

View File

@@ -63,23 +63,71 @@ func parseFlags() Flags {
defaultLogFile = ""
}
pflag.Var(&loggerLevelValue, "log-level", "Log level")
listenAddr := pflag.String("listen-addr", "", "the address to listen for incoming connections to")
remoteAddr := pflag.String("remote-addr", "", "the address (for example 127.0.0.1:3594) of streamd to connect to, instead of running the stream controllers locally")
listenAddr := pflag.String(
"listen-addr",
"",
"the address to listen for incoming connections to",
)
remoteAddr := pflag.String(
"remote-addr",
"",
"the address (for example 127.0.0.1:3594) of streamd to connect to, instead of running the stream controllers locally",
)
configPath := pflag.String("config-path", "~/.streampanel.yaml", "the path to the config file")
netPprofAddrMain := pflag.String("go-net-pprof-addr-main", "", "address to listen to for net/pprof requests by the main process")
netPprofAddrUI := pflag.String("go-net-pprof-addr-ui", "", "address to listen to for net/pprof requests by the UI process")
netPprofAddrStreamD := pflag.String("go-net-pprof-addr-streamd", "", "address to listen to for net/pprof requests by the streamd process")
netPprofAddrMain := pflag.String(
"go-net-pprof-addr-main",
"",
"address to listen to for net/pprof requests by the main process",
)
netPprofAddrUI := pflag.String(
"go-net-pprof-addr-ui",
"",
"address to listen to for net/pprof requests by the UI process",
)
netPprofAddrStreamD := pflag.String(
"go-net-pprof-addr-streamd",
"",
"address to listen to for net/pprof requests by the streamd process",
)
cpuProfile := pflag.String("go-profile-cpu", "", "file to write cpu profile to")
heapProfile := pflag.String("go-profile-heap", "", "file to write memory profile to")
logstashAddr := pflag.String("logstash-addr", "", "the address of logstash to send logs to (for example: 'tcp://192.168.0.2:5044')")
logstashAddr := pflag.String(
"logstash-addr",
"",
"the address of logstash to send logs to (for example: 'tcp://192.168.0.2:5044')",
)
sentryDSN := pflag.String("sentry-dsn", "", "DSN of a Sentry instance to send error reports")
page := pflag.String("page", string(consts.PageControl), "DSN of a Sentry instance to send error reports")
page := pflag.String(
"page",
string(consts.PageControl),
"DSN of a Sentry instance to send error reports",
)
logFile := pflag.String("log-file", defaultLogFile, "log file to write logs into")
subprocess := pflag.String("subprocess", "", "[internal use flag] run a specific sub-process (format: processName:addressToConnect)")
splitProcess := pflag.Bool("split-process", !isMobile(), "split the process into multiple processes for better stability")
lockTimeout := pflag.Duration("lock-timeout", 2*time.Minute, "[debug option] change the timeout for locking, before reporting it as a deadlock")
oauthListenPortTwitch := pflag.Uint16("oauth-listen-port-twitch", 8091, "the port that is used for OAuth callbacks while authenticating in Twitch")
oauthListenPortYouTube := pflag.Uint16("oauth-listen-port-youtube", 8092, "the port that is used for OAuth callbacks while authenticating in YouTube")
subprocess := pflag.String(
"subprocess",
"",
"[internal use flag] run a specific sub-process (format: processName:addressToConnect)",
)
splitProcess := pflag.Bool(
"split-process",
!isMobile(),
"split the process into multiple processes for better stability",
)
lockTimeout := pflag.Duration(
"lock-timeout",
2*time.Minute,
"[debug option] change the timeout for locking, before reporting it as a deadlock",
)
oauthListenPortTwitch := pflag.Uint16(
"oauth-listen-port-twitch",
8091,
"the port that is used for OAuth callbacks while authenticating in Twitch",
)
oauthListenPortYouTube := pflag.Uint16(
"oauth-listen-port-youtube",
8092,
"the port that is used for OAuth callbacks while authenticating in YouTube",
)
pflag.Parse()
@@ -130,7 +178,11 @@ func getFlags(
func(ctx context.Context, source mainprocess.ProcessName, content any) error {
result, ok := content.(GetFlagsResult)
if !ok {
return fmt.Errorf("got unexpected type '%T' instead of %T", content, GetFlagsResult{})
return fmt.Errorf(
"got unexpected type '%T' instead of %T",
content,
GetFlagsResult{},
)
}
flags = result.Flags
return nil

View File

@@ -103,5 +103,11 @@ func getFlagsAndroidFromFiles(flags *Flags) {
logger.Errorf(ctx, "unable to unserialize '%s': %v", flagsSerialized, err)
}
logger.Debugf(ctx, "successfully parsed file '%s' with content '%s'; now the flags == %#+v", flagsFilePath, flagsSerialized, *flags)
logger.Debugf(
ctx,
"successfully parsed file '%s' with content '%s'; now the flags == %#+v",
flagsFilePath,
flagsSerialized,
*flags,
)
}

View File

@@ -63,7 +63,10 @@ func runSubprocess(
) {
parts := strings.SplitN(subprocessFlag, ":", 2)
if len(parts) != 2 {
logger.Panicf(belt.WithField(preCtx, "process", ""), "expected 2 parts in --subprocess: name and address, separated via a colon")
logger.Panicf(
belt.WithField(preCtx, "process", ""),
"expected 2 parts in --subprocess: name and address, separated via a colon",
)
}
procName := ProcessName(parts[0])
addr := parts[1]
@@ -123,7 +126,12 @@ func runSplitProcesses(
case ProcessNameStreamd:
err := m.SendMessage(ctx, ProcessNameUI, StreamDDied{})
if err != nil {
logger.Errorf(ctx, "failed to send a StreamDDied message to '%s': %v", ProcessNameUI, err)
logger.Errorf(
ctx,
"failed to send a StreamDDied message to '%s': %v",
ProcessNameUI,
err,
)
}
}
}
@@ -189,11 +197,25 @@ func runFork(
ctx, cancelFn := context.WithCancel(ctx)
os.Setenv(EnvPassword, password)
args := []string{execPath, "--sentry-dsn=" + flags.SentryDSN, "--log-level=" + logger.Level(flags.LoggerLevel).String(), "--subprocess=" + string(procName) + ":" + addr, "--logstash-addr=" + flags.LogstashAddr}
args := []string{
execPath,
"--sentry-dsn=" + flags.SentryDSN,
"--log-level=" + logger.Level(flags.LoggerLevel).String(),
"--subprocess=" + string(procName) + ":" + addr,
"--logstash-addr=" + flags.LogstashAddr,
}
logger.Infof(ctx, "running '%s %s'", args[0], strings.Join(args[1:], " "))
cmd := exec.Command(args[0], args[1:]...)
cmd.Stderr = logwriter.NewLogWriter(ctx, logger.FromCtx(ctx).WithField("log_writer_target", "split").WithField("output_type", "stderr"))
cmd.Stdout = logwriter.NewLogWriter(ctx, logger.FromCtx(ctx).WithField("log_writer_target", "split"))
cmd.Stderr = logwriter.NewLogWriter(
ctx,
logger.FromCtx(ctx).
WithField("log_writer_target", "split").
WithField("output_type", "stderr"),
)
cmd.Stdout = logwriter.NewLogWriter(
ctx,
logger.FromCtx(ctx).WithField("log_writer_target", "split"),
)
cmd.Stdin = os.Stdin
err = child_process_manager.ConfigureCommand(cmd)
if err != nil {
@@ -213,7 +235,13 @@ func runFork(
err := cmd.Wait()
cancelFn()
if err != nil {
logger.Errorf(ctx, "error running '%s %s': %v", args[0], strings.Join(args[1:], " "), err)
logger.Errorf(
ctx,
"error running '%s %s': %v",
args[0],
strings.Join(args[1:], " "),
err,
)
}
})
return nil
@@ -251,7 +279,11 @@ func setReadyFor(
func(ctx context.Context, source ProcessName, content any) error {
_, ok := content.(mainprocess.MessageReadyConfirmed)
if !ok {
return fmt.Errorf("got unexpected type '%T' instead of %T", content, mainprocess.MessageReadyConfirmed{})
return fmt.Errorf(
"got unexpected type '%T' instead of %T",
content,
mainprocess.MessageReadyConfirmed{},
)
}
return nil
},

View File

@@ -104,7 +104,11 @@ func runPanel(
}
assert(ctx, panel.StreamD != nil)
listener, grpcServer, streamdGRPC, _ := initGRPCServers(ctx, panel.StreamD, flags.ListenAddr)
listener, grpcServer, streamdGRPC, _ := initGRPCServers(
ctx,
panel.StreamD,
flags.ListenAddr,
)
// to erase an oauth request answered locally from "UnansweredOAuthRequests" in the GRPC server:
panel.OnInternallySubmittedOAuthCode = func(

View File

@@ -86,7 +86,10 @@ func initRuntime(
if netPprofAddr != "" {
observability.Go(ctx, func() {
http.Handle("/metrics", promhttp.Handler()) // TODO: either split this from pprof argument, or rename the argument (and re-describe it)
http.Handle(
"/metrics",
promhttp.Handler(),
) // TODO: either split this from pprof argument, or rename the argument (and re-describe it)
l.Infof("starting to listen for net/pprof requests at '%s'", netPprofAddr)
l.Error(http.ListenAndServe(netPprofAddr, nil))

View File

@@ -31,7 +31,12 @@ func mainProcessSignalHandler(
logger.Debugf(ctx, "interrupting '%s'", name)
err := f.Process.Signal(os.Interrupt)
if err != nil {
logger.Errorf(ctx, "unable to send Interrupt to '%s': %v", name, err)
logger.Errorf(
ctx,
"unable to send Interrupt to '%s': %v",
name,
err,
)
logger.Debugf(ctx, "killing '%s'", name)
f.Process.Kill()
return

View File

@@ -115,7 +115,13 @@ func runStreamd(
},
func(listenPort uint16, platID streamcontrol.PlatformName, authURL string) bool {
logger.Debugf(ctx, "streamd.UI.OpenOAuthURL(%d, %s, '%s')", listenPort, platID, authURL)
defer logger.Debugf(ctx, "/streamd.UI.OpenOAuthURL(%d, %s, '%s')", listenPort, platID, authURL)
defer logger.Debugf(
ctx,
"/streamd.UI.OpenOAuthURL(%d, %s, '%s')",
listenPort,
platID,
authURL,
)
streamdGRPCLocker.Lock()
logger.Debugf(ctx, "streamdGRPCLocker.Lock()-ed")

View File

@@ -210,7 +210,11 @@ func (m *Manager) handleConnection(
m.unregisterConnection(ctx, sourceName)
}(regMessage.Source)
if err := encoder.Encode(RegistrationResult{}); err != nil {
err = fmt.Errorf("unable to encode&send the registration result to '%s': %w", regMessage.Source, err)
err = fmt.Errorf(
"unable to encode&send the registration result to '%s': %w",
regMessage.Source,
err,
)
logger.Error(ctx, err)
return
}
@@ -237,7 +241,13 @@ func (m *Manager) handleConnection(
logger.Tracef(ctx, "waiting for a message from '%s'", regMessage.Source)
decoder := gob.NewDecoder(conn)
err := decoder.Decode(&message)
logger.Tracef(ctx, "getting a message from '%s': %#+v %#+v", regMessage.Source, message, err)
logger.Tracef(
ctx,
"getting a message from '%s': %#+v %#+v",
regMessage.Source,
message,
err,
)
select {
case <-ctx.Done():
logger.Tracef(ctx, "context was closed")
@@ -305,7 +315,12 @@ func (m *Manager) processMessage(
close(errCh)
return err.ErrorOrNil()
case ProcessNameMain:
logger.Tracef(ctx, "got a message to the main process from '%s': %#+v", source, message.Content)
logger.Tracef(
ctx,
"got a message to the main process from '%s': %#+v",
source,
message.Content,
)
switch content := message.Content.(type) {
case MessageReady:
var result *multierror.Error
@@ -318,7 +333,13 @@ func (m *Manager) processMessage(
return onReceivedMessage(ctx, source, message.Content)
}
default:
logger.Tracef(ctx, "got a message to '%s' from '%s': %#+v", message.Destination, source, message.Content)
logger.Tracef(
ctx,
"got a message to '%s' from '%s': %#+v",
message.Destination,
source,
message.Content,
)
return m.sendMessage(ctx, source, message.Destination, message.Content)
}
}
@@ -349,7 +370,14 @@ func (m *Manager) sendMessage(
) (_ret error) {
logger.Tracef(ctx, "sending message %#+v from '%s' to '%s'", content, source, destination)
defer func() {
logger.Tracef(ctx, "/sending message %#+v from '%s' to '%s': %v", content, source, destination, _ret)
logger.Tracef(
ctx,
"/sending message %#+v from '%s' to '%s': %v",
content,
source,
destination,
_ret,
)
}()
if !m.isExpectedProcess(destination) {
@@ -359,7 +387,11 @@ func (m *Manager) sendMessage(
observability.Go(ctx, func() {
conn, err := m.waitForReadyProcess(ctx, destination, reflect.TypeOf(content))
if err != nil {
logger.Errorf(ctx, "%v", fmt.Errorf("unable to wait for process '%s': %w", destination, err))
logger.Errorf(
ctx,
"%v",
fmt.Errorf("unable to wait for process '%s': %w", destination, err),
)
return
}
@@ -372,7 +404,9 @@ func (m *Manager) sendMessage(
h := m.connLocker.Lock(context.Background(), destination)
defer h.Unlock()
defer time.Sleep(100 * time.Millisecond) // TODO: Delete this horrible hack (that is introduced to avoid erasing messages in the buffer)
defer time.Sleep(
100 * time.Millisecond,
) // TODO: Delete this horrible hack (that is introduced to avoid erasing messages in the buffer)
err = gob.NewEncoder(conn).Encode(message)
if err != nil {
logger.Errorf(ctx, "%v", fmt.Errorf("unable to encode&send message: %w", err))
@@ -419,7 +453,10 @@ func (m *Manager) waitForReadyProcess(
}
for {
conn, ch, isReady := xsync.DoR3(ctx, &m.connsLocker, func() (net.Conn, chan struct{}, bool) {
conn, ch, isReady := xsync.DoR3(
ctx,
&m.connsLocker,
func() (net.Conn, chan struct{}, bool) {
readyMap := m.childReadyFor[name]
isReady := false
if readyMap != nil {
@@ -428,14 +465,25 @@ func (m *Manager) waitForReadyProcess(
}
}
return m.conns[name], m.connsChanged, isReady
})
},
)
if conn != nil && isReady {
logger.Debugf(ctx, "waitForReadyProcess(ctx, '%s', '%s'): waiting is complete", name, msgType)
logger.Debugf(
ctx,
"waitForReadyProcess(ctx, '%s', '%s'): waiting is complete",
name,
msgType,
)
return conn, nil
}
logger.Debugf(ctx, "waitForReadyProcess(ctx, '%s', '%s'): waiting for a change in connections", name, msgType)
logger.Debugf(
ctx,
"waitForReadyProcess(ctx, '%s', '%s'): waiting for a change in connections",
name,
msgType,
)
<-ch
}
}
@@ -452,7 +500,12 @@ func (m *Manager) registerConnection(
conn net.Conn,
) error {
logger.Debugf(ctx, "registerConnection(ctx, '%s', %s)", sourceName, conn.RemoteAddr().String())
defer logger.Debugf(ctx, "/registerConnection(ctx, '%s', %s)", sourceName, conn.RemoteAddr().String())
defer logger.Debugf(
ctx,
"/registerConnection(ctx, '%s', %s)",
sourceName,
conn.RemoteAddr().String(),
)
if !m.isExpectedProcess(sourceName) {
return fmt.Errorf("process '%s' is not ever expected", sourceName)
}
@@ -550,7 +603,9 @@ func (m *Manager) SendMessagePreReady(
}
h := m.connLocker.Lock(context.Background(), dst)
defer h.Unlock()
defer time.Sleep(100 * time.Millisecond) // TODO: Delete this horrible hack (that is introduced to avoid erasing messages in the buffer)
defer time.Sleep(
100 * time.Millisecond,
) // TODO: Delete this horrible hack (that is introduced to avoid erasing messages in the buffer)
err = encoder.Encode(msg)
logger.Tracef(ctx, "sending message %#+v: %v", msg, err)
if err != nil {

View File

@@ -66,19 +66,25 @@ func Test(t *testing.T) {
c0, err := NewClient("child0", m.Addr().String(), m.Password())
require.NoError(t, err)
defer c0.Close()
go c0.Serve(belt.WithField(ctx, "process", "child0"), func(ctx context.Context, source ProcessName, content any) error {
go c0.Serve(
belt.WithField(ctx, "process", "child0"),
func(ctx context.Context, source ProcessName, content any) error {
handleCall("child0", content)
return nil
})
},
)
c0.SendMessage(ctx, "main", MessageReady{})
c1, err := NewClient("child1", m.Addr().String(), m.Password())
require.NoError(t, err)
defer c1.Close()
go c1.Serve(belt.WithField(ctx, "process", "child1"), func(ctx context.Context, source ProcessName, content any) error {
go c1.Serve(
belt.WithField(ctx, "process", "child1"),
func(ctx context.Context, source ProcessName, content any) error {
handleCall("child1", content)
return nil
})
},
)
c1.SendMessage(ctx, "main", MessageReady{})
_, err = NewClient("child2", m.Addr().String(), m.Password())

View File

@@ -49,7 +49,10 @@ func OAuth2HandlerViaBrowser(ctx context.Context, arg OAuthHandlerArgument) erro
return err
}
fmt.Printf("Your browser has been launched (URL: %s).\nPlease approve the permissions.\n", arg.AuthURL)
fmt.Printf(
"Your browser has been launched (URL: %s).\nPlease approve the permissions.\n",
arg.AuthURL,
)
// Wait for the web server to get the code.
code := <-codeCh
@@ -75,7 +78,11 @@ func NewCodeReceiver(
fmt.Fprintf(w, "No code received :(\r\n\r\nYou can close this browser window.")
return
}
fmt.Fprintf(w, "Received code: %v\r\n\r\nYou can now safely close this browser window.", code)
fmt.Fprintf(
w,
"Received code: %v\r\n\r\nYou can now safely close this browser window.",
code,
)
}),
}

View File

@@ -75,7 +75,11 @@ func NewErrorMonitorLoggerHook(
var _ loggertypes.PreHook = (*ErrorMonitorLoggerHook)(nil)
func (h *ErrorMonitorLoggerHook) ProcessInput(traceIDs belt.TraceIDs, level loggertypes.Level, args ...any) loggertypes.PreHookResult {
func (h *ErrorMonitorLoggerHook) ProcessInput(
traceIDs belt.TraceIDs,
level loggertypes.Level,
args ...any,
) loggertypes.PreHookResult {
if level > loggertypes.LevelWarning {
return loggertypes.PreHookResult{
Skip: false,
@@ -93,7 +97,12 @@ func (h *ErrorMonitorLoggerHook) ProcessInput(traceIDs belt.TraceIDs, level logg
}
}
func (h *ErrorMonitorLoggerHook) ProcessInputf(traceIDs belt.TraceIDs, level loggertypes.Level, format string, args ...any) loggertypes.PreHookResult {
func (h *ErrorMonitorLoggerHook) ProcessInputf(
traceIDs belt.TraceIDs,
level loggertypes.Level,
format string,
args ...any,
) loggertypes.PreHookResult {
if level > loggertypes.LevelWarning {
return loggertypes.PreHookResult{
Skip: false,
@@ -110,7 +119,13 @@ func (h *ErrorMonitorLoggerHook) ProcessInputf(traceIDs belt.TraceIDs, level log
Skip: false,
}
}
func (h *ErrorMonitorLoggerHook) ProcessInputFields(traceIDs belt.TraceIDs, level loggertypes.Level, message string, fields field.AbstractFields) loggertypes.PreHookResult {
func (h *ErrorMonitorLoggerHook) ProcessInputFields(
traceIDs belt.TraceIDs,
level loggertypes.Level,
message string,
fields field.AbstractFields,
) loggertypes.PreHookResult {
if level > loggertypes.LevelWarning {
return loggertypes.PreHookResult{
Skip: false,

View File

@@ -41,7 +41,11 @@ func main() {
loggerLevel := logger.LevelInfo
pflag.Var(&loggerLevel, "log-level", "Log level")
mpvPath := pflag.String("mpv", "mpv", "path to mpv")
backend := pflag.String("backend", backends[0], "player backend, supported values: "+strings.Join(backends, ", "))
backend := pflag.String(
"backend",
backends[0],
"player backend, supported values: "+strings.Join(backends, ", "),
)
pflag.Parse()
l := logrus.Default().WithLevel(loggerLevel)

View File

@@ -119,7 +119,18 @@ func (p *MPV) execMPV(ctx context.Context) (_err error) {
err := os.Remove(socketPath)
logger.Debugf(ctx, "socket deletion result: '%s': %v", socketPath, err)
args := []string{p.PathToMPV, "--idle", "--keep-open=always", "--keep-open-pause=no", "--no-hidpi-window-scale", "--no-osc", "--no-osd-bar", "--window-scale=1", "--input-ipc-server=" + socketPath, fmt.Sprintf("--title=%s", p.Title)}
args := []string{
p.PathToMPV,
"--idle",
"--keep-open=always",
"--keep-open-pause=no",
"--no-hidpi-window-scale",
"--no-osc",
"--no-osd-bar",
"--window-scale=1",
"--input-ipc-server=" + socketPath,
fmt.Sprintf("--title=%s", p.Title),
}
switch observability.LogLevelFilter.GetLevel() {
case logger.LevelPanic, logger.LevelFatal:
args = append(args, "--msg-level=all=no")
@@ -135,8 +146,18 @@ func (p *MPV) execMPV(ctx context.Context) (_err error) {
logger.Debugf(ctx, "running command '%s %s'", args[0], strings.Join(args[1:], " "))
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = logwriter.NewLogWriter(ctx, logger.FromCtx(ctx).WithField("log_writer_target", "mpv").WithField("output_type", "stdout"))
cmd.Stderr = logwriter.NewLogWriter(ctx, logger.FromCtx(ctx).WithField("log_writer_target", "mpv").WithField("output_type", "stderr"))
cmd.Stdout = logwriter.NewLogWriter(
ctx,
logger.FromCtx(ctx).
WithField("log_writer_target", "mpv").
WithField("output_type", "stdout"),
)
cmd.Stderr = logwriter.NewLogWriter(
ctx,
logger.FromCtx(ctx).
WithField("log_writer_target", "mpv").
WithField("output_type", "stderr"),
)
err = child_process_manager.ConfigureCommand(cmd)
errmon.ObserveErrorCtx(ctx, err)
err = cmd.Start()

View File

@@ -34,7 +34,12 @@ func (r *Recoder) StartRecoding(
return xsync.DoR1(ctx, &r.Locker, func() error {
relay := rtmprelay.NewRtmpRelay(&input.URL, &output.URL)
if err := relay.Start(); err != nil {
return fmt.Errorf("unable to start RTMP relay from '%s' to '%s': %w", input.URL, output.URL, err)
return fmt.Errorf(
"unable to start RTMP relay from '%s' to '%s': %w",
input.URL,
output.URL,
err,
)
}
r.Relay = relay

View File

@@ -55,7 +55,11 @@ func NewOutputFromURL(
Closer: astikit.NewCloser(),
}
formatContext, err := astiav.AllocOutputFormatContext(nil, formatFromScheme(url.Scheme), url.String())
formatContext, err := astiav.AllocOutputFormatContext(
nil,
formatFromScheme(url.Scheme),
url.String(),
)
if err != nil {
return nil, fmt.Errorf("allocating output format context failed: %w", err)
}
@@ -69,7 +73,10 @@ func NewOutputFromURL(
// if output is a file:
if !output.FormatContext.OutputFormat().Flags().Has(astiav.IOFormatFlagNofile) {
logger.Tracef(ctx, "destination '%s' is a file", url.String())
ioContext, err := astiav.OpenIOContext(url.String(), astiav.NewIOContextFlags(astiav.IOContextFlagWrite))
ioContext, err := astiav.OpenIOContext(
url.String(),
astiav.NewIOContextFlags(astiav.IOContextFlagWrite),
)
if err != nil {
log.Fatal(fmt.Errorf("main: opening io context failed: %w", err))
}

View File

@@ -35,7 +35,9 @@ func New(
WaiterChan: make(chan struct{}),
RecoderConfig: cfg,
}
close(result.WaiterChan) // to prevent Wait() from blocking when the process is not started, yet.
close(
result.WaiterChan,
) // to prevent Wait() from blocking when the process is not started, yet.
return result
}

View File

@@ -109,7 +109,11 @@ func (srv *GRPCServer) newInputByURL(
config := recoder.InputConfig{}
input, err := recoder.NewInputFromURL(ctx, path.Url.Url, path.Url.AuthKey, config)
if err != nil {
return nil, fmt.Errorf("unable to initialize an input using URL '%s' and config %#+v", path.Url, config)
return nil, fmt.Errorf(
"unable to initialize an input using URL '%s' and config %#+v",
path.Url,
config,
)
}
inputID := xsync.DoR1(ctx, &srv.InputLocker, func() InputID {
@@ -163,7 +167,11 @@ func (srv *GRPCServer) newOutputByURL(
config := recoder.OutputConfig{}
output, err := recoder.NewOutputFromURL(ctx, path.Url.Url, path.Url.AuthKey, config)
if err != nil {
return nil, fmt.Errorf("unable to initialize an output using URL '%s' and config %#+v", path.Url, config)
return nil, fmt.Errorf(
"unable to initialize an output using URL '%s' and config %#+v",
path.Url,
config,
)
}
outputID := xsync.DoR1(ctx, &srv.OutputLocker, func() OutputID {

View File

@@ -41,7 +41,11 @@ func (r *Recoder) NewInputFromPublisher(
) (recoder.Input, error) {
publisher, ok := publisherIface.(*xaionarogortmp.Pubsub)
if !ok {
return nil, fmt.Errorf("expected a publisher or type %T, but received %T", publisherIface, publisher)
return nil, fmt.Errorf(
"expected a publisher or type %T, but received %T",
publisherIface,
publisher,
)
}
return &Input{
@@ -85,7 +89,11 @@ func (r *Recoder) NewInputFromURL(
},
})
if err != nil {
return nil, fmt.Errorf("got an error on command 'Connect' to the input endpoint '%s': %w", urlString, err)
return nil, fmt.Errorf(
"got an error on command 'Connect' to the input endpoint '%s': %w",
urlString,
err,
)
}
return nil, fmt.Errorf("not implemented, yet")

View File

@@ -49,7 +49,12 @@ func newRTMPClient(
dialFunc = rtmp.Dial
case "rtmps":
dialFunc = func(protocol, addr string, config *rtmp.ConnConfig) (*rtmp.ClientConn, error) {
return rtmp.TLSDial(protocol, addr, config, http.DefaultTransport.(*http.Transport).TLSClientConfig)
return rtmp.TLSDial(
protocol,
addr,
config,
http.DefaultTransport.(*http.Transport).TLSClientConfig,
)
}
default:
return nil, fmt.Errorf("unexpected scheme '%s' in URL '%s'", url.Scheme, url.String())

View File

@@ -73,7 +73,11 @@ func (r *Recoder) StartRecoding(
return fmt.Errorf("recoding is already running")
}
stream, err := output.Client.CreateStream(ctx, &rtmpmsg.NetConnectionCreateStream{}, chunkSize)
stream, err := output.Client.CreateStream(
ctx,
&rtmpmsg.NetConnectionCreateStream{},
chunkSize,
)
if err != nil {
return fmt.Errorf("unable to create a stream on the remote side: %w", err)
}

View File

@@ -176,7 +176,8 @@ func (g *GIT) push(
if err == git.NoErrAlreadyUpToDate {
return nil
}
if err != nil && strings.Contains(err.Error(), "is at") && strings.Contains(err.Error(), "but expected") {
if err != nil && strings.Contains(err.Error(), "is at") &&
strings.Contains(err.Error(), "but expected") {
return ErrNeedsRebase{Err: err}
}
if err != nil {
@@ -200,7 +201,11 @@ func (g *GIT) Read() ([]byte, error) {
b, err := io.ReadAll(f)
f.Close()
if err != nil {
return nil, fmt.Errorf("unable to read the content of file '%s' from the virtual git repository: %w", g.FilePath, err)
return nil, fmt.Errorf(
"unable to read the content of file '%s' from the virtual git repository: %w",
g.FilePath,
err,
)
}
return b, nil
@@ -271,11 +276,21 @@ func (g *GIT) Pull(
logger.Debugf(ctx, "git is already in sync: %s == %s", lastKnownCommitHash, newCommitHash)
return nil
}
logger.Debugf(ctx, "got a different commit from git: %s != %s", lastKnownCommitHash, newCommitHash)
logger.Debugf(
ctx,
"got a different commit from git: %s != %s",
lastKnownCommitHash,
newCommitHash,
)
oldCommit, _ := g.Repo.CommitObject(newCommitHash)
if oldCommit != nil {
logger.Debugf(ctx, "we already have this commit in the history on our side, skipping it: %s: %#+v", newCommitHash, oldCommit)
logger.Debugf(
ctx,
"we already have this commit in the history on our side, skipping it: %s: %#+v",
newCommitHash,
oldCommit,
)
return nil
}
@@ -301,7 +316,11 @@ func (g *GIT) CommitAndPush(
_, err := worktree.Add(g.FilePath)
if err != nil {
return plumbing.Hash{}, fmt.Errorf("unable to add file '%s' to the git's worktree: %w", g.FilePath, err)
return plumbing.Hash{}, fmt.Errorf(
"unable to add file '%s' to the git's worktree: %w",
g.FilePath,
err,
)
}
now := time.Now()
@@ -312,12 +331,15 @@ func (g *GIT) CommitAndPush(
return plumbing.Hash{}, fmt.Errorf("unable to determine the host name: %w", err)
}
hash, err := worktree.Commit(fmt.Sprintf("Update from '%s' at %s", host, ts), &git.CommitOptions{
hash, err := worktree.Commit(
fmt.Sprintf("Update from '%s' at %s", host, ts),
&git.CommitOptions{
All: true,
AllowEmptyCommits: false,
Author: signature,
Committer: signature,
})
},
)
if err != nil {
return hash, fmt.Errorf("unable to commit the new config to the git repo: %w", err)
}
@@ -387,7 +409,11 @@ func (g *GIT) Write(
)
logger.Debugf(ctx, "file open result: %v", err)
if err != nil && !os.IsNotExist(err) {
return plumbing.Hash{}, fmt.Errorf("unable to open file '%s' for reading: %w", g.FilePath, err)
return plumbing.Hash{}, fmt.Errorf(
"unable to open file '%s' for reading: %w",
g.FilePath,
err,
)
}
var sha1SumBefore [sha1.Size]byte
@@ -420,12 +446,19 @@ func (g *GIT) Write(
0644,
)
if err != nil {
return plumbing.Hash{}, fmt.Errorf("unable to open file '%s' for writing: %w", g.FilePath, err)
return plumbing.Hash{}, fmt.Errorf(
"unable to open file '%s' for writing: %w",
g.FilePath,
err,
)
}
_, err = io.Copy(f, bytes.NewReader(b))
f.Close()
if err != nil {
return plumbing.Hash{}, fmt.Errorf("unable to write the config into virtual git repo: %w", err)
return plumbing.Hash{}, fmt.Errorf(
"unable to write the config into virtual git repo: %w",
err,
)
}
hash, err := g.CommitAndPush(ctx, worktree, ref)

View File

@@ -10,7 +10,11 @@ import (
func Screenshot(cfg Config) (*image.RGBA, error) {
activeDisplays := screenshot.NumActiveDisplays()
if cfg.DisplayID >= uint(activeDisplays) {
return nil, fmt.Errorf("display ID %d is out of range (max: %d)", cfg.DisplayID, activeDisplays-1)
return nil, fmt.Errorf(
"display ID %d is out of range (max: %d)",
cfg.DisplayID,
activeDisplays-1,
)
}
rgbaFull, err := screenshot.CaptureDisplay(int(cfg.DisplayID))

View File

@@ -130,10 +130,14 @@ var _ yaml.BytesUnmarshaler = (*RawMessage)(nil)
var _ yaml.BytesMarshaler = (*RawMessage)(nil)
func (RawMessage) GetParent() (ProfileName, bool) {
panic("the value is not parsed; don't use the platform config directly, and use function GetPlatformConfig instead")
panic(
"the value is not parsed; don't use the platform config directly, and use function GetPlatformConfig instead",
)
}
func (RawMessage) GetOrder() int {
panic("the value is not parsed; don't use the platform config directly, and use function GetPlatformConfig instead")
panic(
"the value is not parsed; don't use the platform config directly, and use function GetPlatformConfig instead",
)
}
func (m *RawMessage) UnmarshalJSON(b []byte) error {
@@ -192,7 +196,11 @@ func (cfg *Config) UnmarshalYAML(b []byte) (_err error) {
t := map[PlatformName]*unparsedPlatformConfig{}
err := yaml.Unmarshal(b, &t)
if err != nil {
return fmt.Errorf("unable to unmarshal YAML of the root of the config: %w; config: <%s>", err, b)
return fmt.Errorf(
"unable to unmarshal YAML of the root of the config: %w; config: <%s>",
err,
b,
)
}
if *cfg == nil {
@@ -316,7 +324,9 @@ func GetPlatformSpecificConfig[T any](
}
}
func GetStreamProfiles[S StreamProfile](streamProfiles map[ProfileName]AbstractStreamProfile) StreamProfiles[S] {
func GetStreamProfiles[S StreamProfile](
streamProfiles map[ProfileName]AbstractStreamProfile,
) StreamProfiles[S] {
s := make(map[ProfileName]S, len(streamProfiles))
for k, p := range streamProfiles {
switch p := p.(type) {
@@ -345,7 +355,9 @@ func InitConfig[T any, S StreamProfile](cfg Config, id PlatformName, platCfg Pla
}
}
func ToAbstractStreamProfiles[S StreamProfile](in map[ProfileName]S) map[ProfileName]AbstractStreamProfile {
func ToAbstractStreamProfiles[S StreamProfile](
in map[ProfileName]S,
) map[ProfileName]AbstractStreamProfile {
m := make(map[ProfileName]AbstractStreamProfile, len(in))
for k, v := range in {
m[k] = v

View File

@@ -10,7 +10,11 @@ type ErrInvalidStreamProfileType struct {
var _ error = ErrInvalidStreamProfileType{}
func (e ErrInvalidStreamProfileType) Error() string {
return fmt.Sprintf("received an invalid stream profile type: expected:%T, received:%T", e.Expected, e.Received)
return fmt.Sprintf(
"received an invalid stream profile type: expected:%T, received:%T",
e.Expected,
e.Received,
)
}
type ErrNoStreamControllerForProfile struct {

View File

@@ -213,7 +213,9 @@ func (obs *OBS) GetStreamStatus(
var startedAt *time.Time
if streamStatus.OutputActive {
startedAt = ptr(time.Now().Add(-time.Duration(streamStatus.OutputDuration * float64(time.Millisecond))))
startedAt = ptr(
time.Now().Add(-time.Duration(streamStatus.OutputDuration * float64(time.Millisecond))),
)
}
return &streamcontrol.StreamStatus{

View File

@@ -71,26 +71,44 @@ func (sr SceneRule) MarshalYAML() (b []byte, _err error) {
triggerBytes, err := yaml.Marshal(sr.TriggerQuery)
if err != nil {
return nil, fmt.Errorf("unable to serialize the trigger %T:%#+v: %w", sr.TriggerQuery, sr.TriggerQuery, err)
return nil, fmt.Errorf(
"unable to serialize the trigger %T:%#+v: %w",
sr.TriggerQuery,
sr.TriggerQuery,
err,
)
}
triggerMap := map[string]any{}
err = yaml.Unmarshal(triggerBytes, &triggerMap)
if err != nil {
return nil, fmt.Errorf("unable to unserialize the trigger '%s' into a map: %w", triggerBytes, err)
return nil, fmt.Errorf(
"unable to unserialize the trigger '%s' into a map: %w",
triggerBytes,
err,
)
}
triggerMap["type"] = registry.ToTypeName(sr.TriggerQuery)
actionBytes, err := yaml.Marshal(sr.Action)
if err != nil {
return nil, fmt.Errorf("unable to serialize the action %T:%#+v: %w", sr.Action, sr.Action, err)
return nil, fmt.Errorf(
"unable to serialize the action %T:%#+v: %w",
sr.Action,
sr.Action,
err,
)
}
actionMap := map[string]any{}
err = yaml.Unmarshal(actionBytes, &actionMap)
if err != nil {
return nil, fmt.Errorf("unable to unserialize the action '%s' into a map: %w", actionBytes, err)
return nil, fmt.Errorf(
"unable to unserialize the action '%s' into a map: %w",
actionBytes,
err,
)
}
actionMap["type"] = registry.ToTypeName(sr.Action)

View File

@@ -120,7 +120,13 @@ type StreamController[ProfileType StreamProfile] interface {
StreamControllerCommons
ApplyProfile(ctx context.Context, profile ProfileType, customArgs ...any) error
StartStream(ctx context.Context, title string, description string, profile ProfileType, customArgs ...any) error
StartStream(
ctx context.Context,
title string,
description string,
profile ProfileType,
customArgs ...any,
) error
}
type AbstractStreamController interface {

View File

@@ -42,7 +42,9 @@ func New(
return nil, fmt.Errorf("'channel' is not set")
}
if cfg.Config.ClientID == "" || cfg.Config.ClientSecret == "" {
return nil, fmt.Errorf("'clientid' or/and 'clientsecret' is/are not set; go to https://dev.twitch.tv/console/apps/create and create an app if it not created, yet")
return nil, fmt.Errorf(
"'clientid' or/and 'clientsecret' is/are not set; go to https://dev.twitch.tv/console/apps/create and create an app if it not created, yet",
)
}
getPortsFn := cfg.Config.GetOAuthListenPorts
@@ -92,7 +94,10 @@ func New(
errmon.ObserveErrorCtx(ctx, err)
now := time.Now()
if now.Sub(prevTokenUpdate) < time.Second*30 {
logger.Errorf(ctx, "updating the token too often, most likely it won't help, so asking to re-authenticate")
logger.Errorf(
ctx,
"updating the token too often, most likely it won't help, so asking to re-authenticate",
)
t.prepareLocker.Do(ctx, func() {
t.client.SetAppAccessToken("")
t.client.SetUserAccessToken("")
@@ -121,7 +126,10 @@ func getUserID(
return "", fmt.Errorf("unable to query user info: %w", err)
}
if len(resp.Data.Users) != 1 {
return "", fmt.Errorf("expected 1 user with login, but received %d users", len(resp.Data.Users))
return "", fmt.Errorf(
"expected 1 user with login, but received %d users",
len(resp.Data.Users),
)
}
return resp.Data.Users[0].ID, nil
}
@@ -146,7 +154,12 @@ func (t *Twitch) prepareNoLock(ctx context.Context) error {
if err != nil {
return
}
logger.Debugf(ctx, "broadcaster_id: %s (login: %s)", t.broadcasterID, t.config.Config.Channel)
logger.Debugf(
ctx,
"broadcaster_id: %s (login: %s)",
t.broadcasterID,
t.config.Config.Channel,
)
})
return err
}
@@ -171,7 +184,13 @@ func (t *Twitch) editChannelInfo(
return fmt.Errorf("unable to update the channel info (%#+v): %w", *params, err)
}
if resp.ErrorStatus != 0 {
return fmt.Errorf("unable to update the channel info (%#+v), the response reported an error: %d %v: %v", *params, resp.ErrorStatus, resp.Error, resp.ErrorMessage)
return fmt.Errorf(
"unable to update the channel info (%#+v), the response reported an error: %d %v: %v",
*params,
resp.ErrorStatus,
resp.Error,
resp.ErrorMessage,
)
}
logger.Debugf(ctx, "success")
return nil
@@ -219,7 +238,10 @@ func (t *Twitch) ApplyProfile(
if profile.CategoryName != nil {
if profile.CategoryID != nil {
logger.Warnf(ctx, "both category name and ID are set; these are contradicting stream profile settings; prioritizing the name")
logger.Warnf(
ctx,
"both category name and ID are set; these are contradicting stream profile settings; prioritizing the name",
)
}
categoryID, err := t.getCategoryID(ctx, *profile.CategoryName)
if err == nil {
@@ -237,7 +259,10 @@ func (t *Twitch) ApplyProfile(
if tag == "" {
continue
}
tag = truncateStringByByteLength(tag, 25) // see also: https://github.com/twitchdev/issues/issues/789
tag = truncateStringByByteLength(
tag,
25,
) // see also: https://github.com/twitchdev/issues/issues/789
tags = append(tags, tag)
}
@@ -247,7 +272,10 @@ func (t *Twitch) ApplyProfile(
if tags != nil {
logger.Debugf(ctx, "has tags")
if len(tags) == 0 {
logger.Warnf(ctx, "unfortunately, there is a bug in the helix lib, which does not allow to set zero tags, so adding tag 'stream' to the list of tags as a placeholder")
logger.Warnf(
ctx,
"unfortunately, there is a bug in the helix lib, which does not allow to set zero tags, so adding tag 'stream' to the list of tags as a placeholder",
)
params.Tags = []string{"English"}
} else {
params.Tags = tags
@@ -294,7 +322,11 @@ func (t *Twitch) getCategoryID(
Names: []string{categoryName},
})
if err != nil {
return "", fmt.Errorf("unable to query the category info (of name '%s'): %w", categoryName, err)
return "", fmt.Errorf(
"unable to query the category info (of name '%s'): %w",
categoryName,
err,
)
}
if len(resp.Data.Games) != 1 {
@@ -360,7 +392,10 @@ func (t *Twitch) StartStream(
result = multierror.Append(result, fmt.Errorf("unable to set description: %w", err))
}
if err := t.ApplyProfile(ctx, profile, customArgs...); err != nil {
result = multierror.Append(result, fmt.Errorf("unable to apply the stream-specific profile: %w", err))
result = multierror.Append(
result,
fmt.Errorf("unable to apply the stream-specific profile: %w", err),
)
}
return multierror.Append(result).ErrorOrNil()
}
@@ -614,7 +649,12 @@ func (t *Twitch) getNewTokenByUser(
return fmt.Errorf("unable to get user access token: %w", err)
}
if resp.ErrorStatus != 0 {
return fmt.Errorf("unable to query: %d %v: %v", resp.ErrorStatus, resp.Error, resp.ErrorMessage)
return fmt.Errorf(
"unable to query: %d %v: %v",
resp.ErrorStatus,
resp.Error,
resp.ErrorMessage,
)
}
t.client.SetUserAccessToken(resp.Data.AccessToken)
t.client.SetRefreshToken(resp.Data.RefreshToken)
@@ -637,7 +677,12 @@ func (t *Twitch) getNewTokenByApp(
return fmt.Errorf("unable to get app access token: %w", err)
}
if resp.ErrorStatus != 0 {
return fmt.Errorf("unable to get app access token (the response contains an error): %d %v: %v", resp.ErrorStatus, resp.Error, resp.ErrorMessage)
return fmt.Errorf(
"unable to get app access token (the response contains an error): %d %v: %v",
resp.ErrorStatus,
resp.Error,
resp.ErrorMessage,
)
}
logger.Debugf(ctx, "setting the app access token")
t.client.SetAppAccessToken(resp.Data.AccessToken)
@@ -735,7 +780,8 @@ func (t *Twitch) GetAllCategories(
}
pagination = &resp.Data.Pagination
logger.FromCtx(ctx).Tracef("I have %d categories now; new categories: %d", len(categoriesMap), newCategoriesCount)
logger.FromCtx(ctx).
Tracef("I have %d categories now; new categories: %d", len(categoriesMap), newCategoriesCount)
}
logger.FromCtx(ctx).Tracef("%d categories in total")

View File

@@ -53,7 +53,9 @@ func New(
saveCfgFn func(Config) error,
) (*YouTube, error) {
if cfg.Config.ClientID == "" || cfg.Config.ClientSecret == "" {
return nil, fmt.Errorf("'clientid' or/and 'clientsecret' is/are not set; go to https://console.cloud.google.com/apis/credentials and create an app if it not created, yet")
return nil, fmt.Errorf(
"'clientid' or/and 'clientsecret' is/are not set; go to https://console.cloud.google.com/apis/credentials and create an app if it not created, yet",
)
}
ctx, cancelFn := context.WithCancel(ctx)
@@ -430,7 +432,10 @@ func (yt *YouTube) ApplyProfile(
) error {
return yt.updateActiveBroadcasts(ctx, func(broadcast *youtube.LiveBroadcast) error {
if broadcast.Snippet == nil {
return fmt.Errorf("YouTube have not provided the current snippet of broadcast %v", broadcast.Id)
return fmt.Errorf(
"YouTube have not provided the current snippet of broadcast %v",
broadcast.Id,
)
}
setProfile(broadcast, profile)
return nil
@@ -443,7 +448,10 @@ func (yt *YouTube) SetTitle(
) error {
return yt.updateActiveBroadcasts(ctx, func(broadcast *youtube.LiveBroadcast) error {
if broadcast.Snippet == nil {
return fmt.Errorf("YouTube have not provided the current snippet of broadcast %v", broadcast.Id)
return fmt.Errorf(
"YouTube have not provided the current snippet of broadcast %v",
broadcast.Id,
)
}
setTitle(broadcast, title)
return nil
@@ -456,7 +464,10 @@ func (yt *YouTube) SetDescription(
) error {
return yt.updateActiveBroadcasts(ctx, func(broadcast *youtube.LiveBroadcast) error {
if broadcast.Snippet == nil {
return fmt.Errorf("YouTube have not provided the current snippet of broadcast %v", broadcast.Id)
return fmt.Errorf(
"YouTube have not provided the current snippet of broadcast %v",
broadcast.Id,
)
}
setDescription(broadcast, description)
return nil
@@ -595,7 +606,12 @@ func (yt *YouTube) StartStream(
logger.Debugf(ctx, "profile == %#+v", profile)
templateBroadcastIDs = append(templateBroadcastIDs, profile.TemplateBroadcastIDs...)
logger.Debugf(ctx, "templateBroadcastIDs == %v; customArgs == %v", templateBroadcastIDs, customArgs)
logger.Debugf(
ctx,
"templateBroadcastIDs == %v; customArgs == %v",
templateBroadcastIDs,
customArgs,
)
templateBroadcastIDMap := map[string]struct{}{}
for _, broadcastID := range templateBroadcastIDs {
@@ -629,7 +645,11 @@ func (yt *YouTube) StartStream(
return fmt.Errorf("unable to get the list of active broadcasts: %w", err)
}
if len(response.Items) != len(templateBroadcastIDs) {
return fmt.Errorf("expected %d broadcasts, but found %d", len(templateBroadcastIDs), len(response.Items))
return fmt.Errorf(
"expected %d broadcasts, but found %d",
len(templateBroadcastIDs),
len(response.Items),
)
}
broadcasts = append(broadcasts, response.Items...)
}
@@ -637,18 +657,29 @@ func (yt *YouTube) StartStream(
{
logger.Debugf(ctx, "getting video info of %v", templateBroadcastIDs)
response, err := yt.YouTubeService.Videos.List(videoParts).Id(templateBroadcastIDs...).Context(ctx).Do()
response, err := yt.YouTubeService.Videos.List(videoParts).
Id(templateBroadcastIDs...).
Context(ctx).
Do()
logger.Debugf(ctx, "YouTube.Video result: %v", err)
if err != nil {
return fmt.Errorf("unable to get the list of active broadcasts: %w", err)
}
if len(response.Items) != len(templateBroadcastIDs) {
return fmt.Errorf("expected %d videos, but found %d", len(templateBroadcastIDs), len(response.Items))
return fmt.Errorf(
"expected %d videos, but found %d",
len(templateBroadcastIDs),
len(response.Items),
)
}
videos = append(videos, response.Items...)
}
playlistsResponse, err := yt.YouTubeService.Playlists.List(playlistParts).MaxResults(1000).Mine(true).Context(ctx).Do()
playlistsResponse, err := yt.YouTubeService.Playlists.List(playlistParts).
MaxResults(1000).
Mine(true).
Context(ctx).
Do()
logger.Debugf(ctx, "YouTube.Playlists result: %v", err)
if err != nil {
return fmt.Errorf("unable to get the list of playlists: %w", err)
@@ -659,7 +690,12 @@ func (yt *YouTube) StartStream(
logger.Debugf(ctx, "getting playlist items for %s", templateBroadcastID)
for _, playlist := range playlistsResponse.Items {
playlistItemsResponse, err := yt.YouTubeService.PlaylistItems.List(playlistItemParts).MaxResults(1000).PlaylistId(playlist.Id).VideoId(templateBroadcastID).Context(ctx).Do()
playlistItemsResponse, err := yt.YouTubeService.PlaylistItems.List(playlistItemParts).
MaxResults(1000).
PlaylistId(playlist.Id).
VideoId(templateBroadcastID).
Context(ctx).
Do()
logger.Debugf(ctx, "YouTube.PlaylistItems result: %v", err)
if err != nil {
return fmt.Errorf("unable to get the list of playlist items: %w", err)
@@ -676,15 +712,28 @@ func (yt *YouTube) StartStream(
}
}
logger.Debugf(ctx, "found %d playlists for %s", len(playlistIDMap[templateBroadcastID]), templateBroadcastID)
logger.Debugf(
ctx,
"found %d playlists for %s",
len(playlistIDMap[templateBroadcastID]),
templateBroadcastID,
)
}
var highestStreamNum uint64
if profile.AutoNumerate {
resp, err := yt.YouTubeService.LiveBroadcasts.List(liveBroadcastParts).Context(ctx).Mine(true).MaxResults(100).Fields().Do(googleapi.QueryParameter("order", "date"))
resp, err := yt.YouTubeService.LiveBroadcasts.List(liveBroadcastParts).
Context(ctx).
Mine(true).
MaxResults(100).
Fields().
Do(googleapi.QueryParameter("order", "date"))
logger.Debugf(ctx, "YouTube.LiveBroadcasts result: %v", err)
if err != nil {
return fmt.Errorf("unable to request previous streams to figure out the next stream number for auto-numeration: %w", err)
return fmt.Errorf(
"unable to request previous streams to figure out the next stream number for auto-numeration: %w",
err,
)
}
for _, b := range resp.Items {
@@ -709,7 +758,11 @@ func (yt *YouTube) StartStream(
templateBroadcastID := broadcast.Id
if video.Id != broadcast.Id {
return fmt.Errorf("internal error: the orders of videos and broadcasts do not match: %s != %s", video.Id, broadcast.Id)
return fmt.Errorf(
"internal error: the orders of videos and broadcasts do not match: %s != %s",
video.Id,
broadcast.Id,
)
}
now := time.Now().UTC()
broadcast.Id = ""
@@ -720,7 +773,9 @@ func (yt *YouTube) StartStream(
broadcast.ContentDetails.MonitorStream = nil
broadcast.ContentDetails.ForceSendFields = []string{"EnableAutoStop"}
broadcast.Snippet.ScheduledStartTime = now.Format("2006-01-02T15:04:05") + ".00Z"
broadcast.Snippet.ScheduledEndTime = now.Add(time.Hour*12).Format("2006-01-02T15:04:05") + ".00Z"
broadcast.Snippet.ScheduledEndTime = now.Add(time.Hour*12).
Format("2006-01-02T15:04:05") +
".00Z"
broadcast.Snippet.LiveChatId = ""
broadcast.Status.SelfDeclaredMadeForKids = broadcast.Status.MadeForKids
broadcast.Status.ForceSendFields = []string{"SelfDeclaredMadeForKids"}
@@ -747,7 +802,10 @@ func (yt *YouTube) StartStream(
logger.Debugf(ctx, "YouTube.LiveBroadcasts result: %v", err)
if err != nil {
if strings.Contains(err.Error(), "invalidScheduledStartTime") {
logger.Debugf(ctx, "it seems the local system clock is off, trying to fix the schedule time")
logger.Debugf(
ctx,
"it seems the local system clock is off, trying to fix the schedule time",
)
now, err = timeapiio.Now()
if err != nil {
@@ -758,7 +816,9 @@ func (yt *YouTube) StartStream(
now = time.Now().Add(time.Hour)
}
broadcast.Snippet.ScheduledStartTime = now.Format("2006-01-02T15:04:05") + ".00Z"
broadcast.Snippet.ScheduledEndTime = now.Add(time.Hour*12).Format("2006-01-02T15:04:05") + ".00Z"
broadcast.Snippet.ScheduledEndTime = now.Add(time.Hour*12).
Format("2006-01-02T15:04:05") +
".00Z"
newBroadcast, err = yt.YouTubeService.LiveBroadcasts.Insert(
[]string{"snippet", "contentDetails", "monetizationDetails", "status"},
broadcast,
@@ -789,13 +849,22 @@ func (yt *YouTube) StartStream(
video.Snippet.Tags = append(video.Snippet.Tags, profile.Tags...)
video.Snippet.Tags = append(video.Snippet.Tags, templateTags...)
default:
logger.Errorf(ctx, "unexpected value of the 'TemplateTags' setting: '%v'", profile.TemplateTags)
logger.Errorf(
ctx,
"unexpected value of the 'TemplateTags' setting: '%v'",
profile.TemplateTags,
)
video.Snippet.Tags = profile.Tags
}
video.Snippet.Tags = deduplicate(video.Snippet.Tags)
tagsTruncated := TruncateTags(video.Snippet.Tags)
if len(tagsTruncated) != len(video.Snippet.Tags) {
logger.Infof(ctx, "YouTube tags were truncated, the amount was reduced from %d to %d to satisfy the 500 characters limit", len(video.Snippet.Tags), len(tagsTruncated))
logger.Infof(
ctx,
"YouTube tags were truncated, the amount was reduced from %d to %d to satisfy the 500 characters limit",
len(video.Snippet.Tags),
len(tagsTruncated),
)
video.Snippet.Tags = tagsTruncated
}
b, err = yaml.Marshal(video)
@@ -832,7 +901,9 @@ func (yt *YouTube) StartStream(
logger.Debugf(ctx, "adding the video to playlist %#+v", newPlaylistItem)
}
_, err = yt.YouTubeService.PlaylistItems.Insert(playlistItemParts, newPlaylistItem).Context(ctx).Do()
_, err = yt.YouTubeService.PlaylistItems.Insert(playlistItemParts, newPlaylistItem).
Context(ctx).
Do()
logger.Debugf(ctx, "YouTube.PlaylistItems result: %v", err)
if err != nil {
return fmt.Errorf("unable to add video to playlist %#+v: %w", playlistID, err)
@@ -843,16 +914,25 @@ func (yt *YouTube) StartStream(
logger.Debugf(ctx, "downloading the thumbnail")
resp, err := http.Get(broadcast.Snippet.Thumbnails.Standard.Url)
if err != nil {
return fmt.Errorf("unable to download the thumbnail from the template video: %w", err)
return fmt.Errorf(
"unable to download the thumbnail from the template video: %w",
err,
)
}
logger.Debugf(ctx, "reading the thumbnail")
thumbnail, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return fmt.Errorf("unable to read the thumbnail from the response from the template video: %w", err)
return fmt.Errorf(
"unable to read the thumbnail from the response from the template video: %w",
err,
)
}
logger.Debugf(ctx, "setting the thumbnail")
_, err = yt.YouTubeService.Thumbnails.Set(newBroadcast.Id).Media(bytes.NewReader(thumbnail)).Context(ctx).Do()
_, err = yt.YouTubeService.Thumbnails.Set(newBroadcast.Id).
Media(bytes.NewReader(thumbnail)).
Context(ctx).
Do()
logger.Debugf(ctx, "YouTube.Thumbnails result: %v", err)
if err != nil {
return fmt.Errorf("unable to set the thumbnail: %w", err)
@@ -907,7 +987,13 @@ func (yt *YouTube) GetStreamStatus(
if err != nil {
_startedAt, err = time.Parse(timeLayoutFallback, ts)
if err != nil {
return fmt.Errorf("unable to parse '%s' with layouts '%s' and '%s': %w", ts, timeLayout, timeLayoutFallback, err)
return fmt.Errorf(
"unable to parse '%s' with layouts '%s' and '%s': %w",
ts,
timeLayout,
timeLayoutFallback,
err,
)
}
}
startedAt = &_startedAt
@@ -939,7 +1025,12 @@ func (yt *YouTube) GetStreamStatus(
Streams: streams,
}
if observability.LogLevelFilter.GetLevel() >= logger.LevelTrace {
logger.Tracef(ctx, "len(customData.UpcomingBroadcasts) == %d; len(customData.Streams) == %d", len(customData.UpcomingBroadcasts), len(customData.Streams))
logger.Tracef(
ctx,
"len(customData.UpcomingBroadcasts) == %d; len(customData.Streams) == %d",
len(customData.UpcomingBroadcasts),
len(customData.Streams),
)
for idx, broadcast := range customData.UpcomingBroadcasts {
b, err := json.Marshal(broadcast)
if err != nil {
@@ -948,7 +1039,11 @@ func (yt *YouTube) GetStreamStatus(
logger.Tracef(ctx, "UpcomingBroadcasts[%3d] == %s", idx, b)
}
}
logger.Tracef(ctx, "len(customData.ActiveBroadcasts) == %d", len(customData.ActiveBroadcasts))
logger.Tracef(
ctx,
"len(customData.ActiveBroadcasts) == %d",
len(customData.ActiveBroadcasts),
)
for idx, bc := range customData.ActiveBroadcasts {
b, err := json.Marshal(bc)
if err != nil {

View File

@@ -48,7 +48,12 @@ type StreamD interface {
) error
SetTitle(ctx context.Context, platID streamcontrol.PlatformName, title string) error
SetDescription(ctx context.Context, platID streamcontrol.PlatformName, description string) error
ApplyProfile(ctx context.Context, platID streamcontrol.PlatformName, profile streamcontrol.AbstractStreamProfile, customArgs ...any) error
ApplyProfile(
ctx context.Context,
platID streamcontrol.PlatformName,
profile streamcontrol.AbstractStreamProfile,
customArgs ...any,
) error
OBSOLETE_GitRelogin(ctx context.Context) error
GetBackendData(ctx context.Context, platID streamcontrol.PlatformName) (any, error)
Restart(ctx context.Context) error
@@ -189,7 +194,12 @@ type StreamD interface {
ListTimers(ctx context.Context) ([]Timer, error)
AddOBSSceneRule(ctx context.Context, sceneName SceneName, sceneRule SceneRule) error
UpdateOBSSceneRule(ctx context.Context, sceneName SceneName, idx uint64, sceneRule SceneRule) error
UpdateOBSSceneRule(
ctx context.Context,
sceneName SceneName,
idx uint64,
sceneRule SceneRule,
) error
RemoveOBSSceneRule(ctx context.Context, sceneName SceneName, idx uint64) error
ListOBSSceneRules(ctx context.Context, sceneName SceneName) (SceneRules, error)
}

View File

@@ -47,9 +47,15 @@ func NewConfig() Config {
func NewSampleConfig() Config {
cfg := NewConfig()
cfg.Backends[obs.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{"some_profile": obs.StreamProfile{}}
cfg.Backends[twitch.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{"some_profile": twitch.StreamProfile{}}
cfg.Backends[youtube.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{"some_profile": youtube.StreamProfile{}}
cfg.Backends[obs.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
"some_profile": obs.StreamProfile{},
}
cfg.Backends[twitch.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
"some_profile": twitch.StreamProfile{},
}
cfg.Backends[youtube.ID].StreamProfiles = map[streamcontrol.ProfileName]streamcontrol.AbstractStreamProfile{
"some_profile": youtube.StreamProfile{},
}
return cfg
}

View File

@@ -166,16 +166,32 @@ func (s *MonitorSourceOBSVideo) GetImageBytes(
}
resp, err := obsServer.GetSourceScreenshot(ctx, req)
if err != nil {
return nil, "", time.Now().Add(time.Second), fmt.Errorf("unable to get a screenshot of '%s': %w", s.Name, err)
return nil, "", time.Now().
Add(time.Second),
fmt.Errorf(
"unable to get a screenshot of '%s': %w",
s.Name,
err,
)
}
imgB64 := resp.GetImageData()
imgBytes, mimeType, err := imgb64.Decode(string(imgB64))
if err != nil {
return nil, "", time.Time{}, fmt.Errorf("unable to decode the screenshot of '%s': %w", s.Name, err)
return nil, "", time.Time{}, fmt.Errorf(
"unable to decode the screenshot of '%s': %w",
s.Name,
err,
)
}
logger.Tracef(ctx, "the decoded image is of format '%s' (expected format: '%s') and size %d", mimeType, s.ImageFormat, len(imgBytes))
logger.Tracef(
ctx,
"the decoded image is of format '%s' (expected format: '%s') and size %d",
mimeType,
s.ImageFormat,
len(imgBytes),
)
return imgBytes, mimeType, time.Now().Add(time.Duration(s.UpdateInterval)), nil
}
@@ -224,11 +240,19 @@ func (s *MonitorSourceOBSVolume) GetImage(
colorActive, err := colorx.Parse(s.ColorActive)
if err != nil {
return nil, time.Time{}, fmt.Errorf("unable to parse the `color_active` value '%s': %w", s.ColorActive, err)
return nil, time.Time{}, fmt.Errorf(
"unable to parse the `color_active` value '%s': %w",
s.ColorActive,
err,
)
}
colorPassive, err := colorx.Parse(s.ColorPassive)
if err != nil {
return nil, time.Time{}, fmt.Errorf("unable to parse the `color_passive` value '%s': %w", s.ColorPassive, err)
return nil, time.Time{}, fmt.Errorf(
"unable to parse the `color_passive` value '%s': %w",
s.ColorPassive,
err,
)
}
size := img.Bounds().Size()

View File

@@ -66,21 +66,30 @@ func (cfg *Config) UnmarshalYAML(b []byte) (_err error) {
}
if cfg.Backends[obs.ID] != nil {
err = streamcontrol.ConvertStreamProfiles[obs.StreamProfile](context.Background(), cfg.Backends[obs.ID].StreamProfiles)
err = streamcontrol.ConvertStreamProfiles[obs.StreamProfile](
context.Background(),
cfg.Backends[obs.ID].StreamProfiles,
)
if err != nil {
return fmt.Errorf("unable to convert stream profiles of OBS: %w", err)
}
}
if cfg.Backends[twitch.ID] != nil {
err = streamcontrol.ConvertStreamProfiles[twitch.StreamProfile](context.Background(), cfg.Backends[twitch.ID].StreamProfiles)
err = streamcontrol.ConvertStreamProfiles[twitch.StreamProfile](
context.Background(),
cfg.Backends[twitch.ID].StreamProfiles,
)
if err != nil {
return fmt.Errorf("unable to convert stream profiles of twitch: %w", err)
}
}
if cfg.Backends[youtube.ID] != nil {
err = streamcontrol.ConvertStreamProfiles[youtube.StreamProfile](context.Background(), cfg.Backends[youtube.ID].StreamProfiles)
err = streamcontrol.ConvertStreamProfiles[youtube.StreamProfile](
context.Background(),
cfg.Backends[youtube.ID].StreamProfiles,
)
if err != nil {
return fmt.Errorf("unable to convert stream profiles of youtube: %w", err)
}

View File

@@ -99,7 +99,10 @@ func (d *StreamD) onConfigUpdateViaGIT(ctx context.Context, cfg *config.Config)
d.UI.DisplayError(fmt.Errorf("unable to save data: %w", err))
}
if d.GitInitialized {
d.UI.Restart(ctx, "Received an updated config from another device, please restart the application")
d.UI.Restart(
ctx,
"Received an updated config from another device, please restart the application",
)
}
}
@@ -203,7 +206,9 @@ func (d *StreamD) startPeriodicGitSyncer(ctx context.Context) {
observability.Go(ctx, func() {
err := d.sendConfigViaGIT(ctx)
if err != nil {
d.UI.DisplayError(fmt.Errorf("unable to send the config to the remote git repository: %w", err))
d.UI.DisplayError(
fmt.Errorf("unable to send the config to the remote git repository: %w", err),
)
}
ticker := time.NewTicker(time.Minute)

View File

@@ -88,7 +88,12 @@ func Memoize[REQ any, REPLY any, T func(context.Context, REQ) (REPLY, error)](
logger.Tracef(ctx, "using the cached value")
return v.Reply, v.Error
}
logger.Tracef(ctx, "the cached value expired: %s < %s", v.SavedAt.Format(timeFormat), cutoffTS.Format(timeFormat))
logger.Tracef(
ctx,
"the cached value expired: %s < %s",
v.SavedAt.Format(timeFormat),
cutoffTS.Format(timeFormat),
)
delete(cache, key)
} else {
logger.Errorf(ctx, "cache-failure: expected type %T, but got %T", (*cacheItem)(nil), cachedResult)

View File

@@ -243,7 +243,11 @@ func (grpc *GRPCServer) IsBackendEnabled(
) (*streamd_grpc.IsBackendEnabledReply, error) {
enabled, err := grpc.StreamD.IsBackendEnabled(ctx, streamcontrol.PlatformName(req.GetPlatID()))
if err != nil {
return nil, fmt.Errorf("unable to check if backend '%s' is enabled: %w", req.GetPlatID(), err)
return nil, fmt.Errorf(
"unable to check if backend '%s' is enabled: %w",
req.GetPlatID(),
err,
)
}
return &streamd_grpc.IsBackendEnabledReply{
IsInitialized: enabled,
@@ -564,7 +568,10 @@ func (grpc *GRPCServer) openBrowser(
count++
err := handler.Sender.Send(&req)
if err != nil {
err = multierror.Append(resultErr, fmt.Errorf("unable to send oauth request: %w", err))
err = multierror.Append(
resultErr,
fmt.Errorf("unable to send oauth request: %w", err),
)
}
}
}
@@ -583,10 +590,25 @@ func (grpc *GRPCServer) OpenOAuthURL(
) (_ret error) {
logger.Debugf(ctx, "OpenOAuthURL(ctx, %d, '%s', '%s')", listenPort, platID, authURL)
defer func() {
logger.Debugf(ctx, "/OpenOAuthURL(ctx, %d, '%s', '%s'): %v", listenPort, platID, authURL, _ret)
logger.Debugf(
ctx,
"/OpenOAuthURL(ctx, %d, '%s', '%s'): %v",
listenPort,
platID,
authURL,
_ret,
)
}()
return xsync.DoA4R1(ctx, &grpc.OAuthURLHandlerLocker, grpc.openOAuthURL, ctx, listenPort, platID, authURL)
return xsync.DoA4R1(
ctx,
&grpc.OAuthURLHandlerLocker,
grpc.openOAuthURL,
ctx,
listenPort,
platID,
authURL,
)
}
func (grpc *GRPCServer) openOAuthURL(
@@ -703,7 +725,12 @@ func (grpc *GRPCServer) ListStreamServers(
var result []*streamd_grpc.StreamServerWithStatistics
for _, srv := range servers {
srvGRPC, err := goconv.StreamServerConfigGo2GRPC(ctx, srv.Type, srv.ListenAddr, srv.Options())
srvGRPC, err := goconv.StreamServerConfigGo2GRPC(
ctx,
srv.Type,
srv.ListenAddr,
srv.Options(),
)
if err != nil {
return nil, fmt.Errorf("unable to convert the server server value: %w", err)
}
@@ -727,7 +754,11 @@ func (grpc *GRPCServer) StartStreamServer(
) (*streamd_grpc.StartStreamServerReply, error) {
srvType, addr, opts, err := goconv.StreamServerConfigGRPC2Go(ctx, req.GetConfig())
if err != nil {
return nil, fmt.Errorf("unable to convert the stream server config %#+v: %w", req.GetConfig(), err)
return nil, fmt.Errorf(
"unable to convert the stream server config %#+v: %w",
req.GetConfig(),
err,
)
}
err = grpc.StreamD.StartStreamServer(
@@ -922,8 +953,12 @@ func (grpc *GRPCServer) AddStreamForward(
api.StreamForwardingQuirks{
RestartUntilYoutubeRecognizesStream: api.RestartUntilYoutubeRecognizesStream{
Enabled: cfg.Quirks.RestartUntilYoutubeRecognizesStream.Enabled,
StartTimeout: sec2dur(cfg.Quirks.RestartUntilYoutubeRecognizesStream.StartTimeout),
StopStartDelay: sec2dur(cfg.Quirks.RestartUntilYoutubeRecognizesStream.StopStartDelay),
StartTimeout: sec2dur(
cfg.Quirks.RestartUntilYoutubeRecognizesStream.StartTimeout,
),
StopStartDelay: sec2dur(
cfg.Quirks.RestartUntilYoutubeRecognizesStream.StopStartDelay,
),
},
StartAfterYoutubeRecognizedStream: api.StartAfterYoutubeRecognizedStream{
Enabled: cfg.Quirks.StartAfterYoutubeRecognizedStream.Enabled,
@@ -949,8 +984,12 @@ func (grpc *GRPCServer) UpdateStreamForward(
api.StreamForwardingQuirks{
RestartUntilYoutubeRecognizesStream: api.RestartUntilYoutubeRecognizesStream{
Enabled: cfg.Quirks.RestartUntilYoutubeRecognizesStream.Enabled,
StartTimeout: sec2dur(cfg.Quirks.RestartUntilYoutubeRecognizesStream.StartTimeout),
StopStartDelay: sec2dur(cfg.Quirks.RestartUntilYoutubeRecognizesStream.StopStartDelay),
StartTimeout: sec2dur(
cfg.Quirks.RestartUntilYoutubeRecognizesStream.StartTimeout,
),
StopStartDelay: sec2dur(
cfg.Quirks.RestartUntilYoutubeRecognizesStream.StopStartDelay,
),
},
StartAfterYoutubeRecognizedStream: api.StartAfterYoutubeRecognizedStream{
Enabled: cfg.Quirks.StartAfterYoutubeRecognizedStream.Enabled,
@@ -1113,7 +1152,11 @@ func (grpc *GRPCServer) StreamPlayerOpen(
ctx context.Context,
req *streamd_grpc.StreamPlayerOpenRequest,
) (*streamd_grpc.StreamPlayerOpenReply, error) {
err := grpc.StreamD.StreamPlayerOpenURL(ctx, streamtypes.StreamID(req.GetStreamID()), req.GetRequest().GetLink())
err := grpc.StreamD.StreamPlayerOpenURL(
ctx,
streamtypes.StreamID(req.GetStreamID()),
req.GetRequest().GetLink(),
)
if err != nil {
return nil, err
}
@@ -1125,7 +1168,10 @@ func (grpc *GRPCServer) StreamPlayerProcessTitle(
ctx context.Context,
req *streamd_grpc.StreamPlayerProcessTitleRequest,
) (*streamd_grpc.StreamPlayerProcessTitleReply, error) {
title, err := grpc.StreamD.StreamPlayerProcessTitle(ctx, streamtypes.StreamID(req.GetStreamID()))
title, err := grpc.StreamD.StreamPlayerProcessTitle(
ctx,
streamtypes.StreamID(req.GetStreamID()),
)
if err != nil {
return nil, err
}
@@ -1214,7 +1260,11 @@ func (grpc *GRPCServer) StreamPlayerSetSpeed(
ctx context.Context,
req *streamd_grpc.StreamPlayerSetSpeedRequest,
) (*streamd_grpc.StreamPlayerSetSpeedReply, error) {
err := grpc.StreamD.StreamPlayerSetSpeed(ctx, streamtypes.StreamID(req.GetStreamID()), req.GetRequest().Speed)
err := grpc.StreamD.StreamPlayerSetSpeed(
ctx,
streamtypes.StreamID(req.GetStreamID()),
req.GetRequest().Speed,
)
if err != nil {
return nil, err
}
@@ -1226,7 +1276,11 @@ func (grpc *GRPCServer) StreamPlayerSetPause(
ctx context.Context,
req *streamd_grpc.StreamPlayerSetPauseRequest,
) (*streamd_grpc.StreamPlayerSetPauseReply, error) {
err := grpc.StreamD.StreamPlayerSetPause(ctx, streamtypes.StreamID(req.GetStreamID()), req.GetRequest().IsPaused)
err := grpc.StreamD.StreamPlayerSetPause(
ctx,
streamtypes.StreamID(req.GetStreamID()),
req.GetRequest().IsPaused,
)
if err != nil {
return nil, err
}

View File

@@ -45,7 +45,10 @@ func (d *StreamD) EXPERIMENTAL_ReinitStreamControllers(ctx context.Context) erro
continue
}
if err != nil {
result = multierror.Append(result, fmt.Errorf("unable to initialize '%s': %w", platName, err))
result = multierror.Append(
result,
fmt.Errorf("unable to initialize '%s': %w", platName, err),
)
}
}
return result.ErrorOrNil()
@@ -128,7 +131,8 @@ func newTwitch(
error,
) {
platCfg := streamcontrol.ConvertPlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile](
ctx, cfg,
ctx,
cfg,
)
if platCfg == nil {
return nil, fmt.Errorf("twitch config was not found")
@@ -139,7 +143,8 @@ func newTwitch(
}
hadSetNewUserData := false
if platCfg.Config.Channel == "" || platCfg.Config.ClientID == "" || platCfg.Config.ClientSecret == "" {
if platCfg.Config.Channel == "" || platCfg.Config.ClientID == "" ||
platCfg.Config.ClientSecret == "" {
ok, err := setUserData(ctx, platCfg)
if !ok {
err := saveCfgFunc(&streamcontrol.AbstractPlatformConfig{
@@ -197,7 +202,8 @@ func newYouTube(
error,
) {
platCfg := streamcontrol.ConvertPlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile](
ctx, cfg,
ctx,
cfg,
)
if platCfg == nil {
return nil, fmt.Errorf("youtube config was not found")
@@ -295,7 +301,9 @@ func (d *StreamD) listenOBSEvents(
default:
}
client, err := o.GetClient(obs.GetClientOption(goobs.WithEventSubscriptions(subscriptions.InputVolumeMeters)))
client, err := o.GetClient(
obs.GetClientOption(goobs.WithEventSubscriptions(subscriptions.InputVolumeMeters)),
)
if err != nil {
logger.Errorf(ctx, "unable to get an OBS client: %v", err)
time.Sleep(time.Second)

View File

@@ -36,7 +36,10 @@ func (a *platformsControllerAdapter) CheckStreamStartedByURL(
case strings.Contains(destination.Hostname(), "twitch"):
platID = twitch.ID
default:
return false, fmt.Errorf("do not know how to check if the stream started for '%s'", destination.String())
return false, fmt.Errorf(
"do not know how to check if the stream started for '%s'",
destination.String(),
)
}
return a.CheckStreamStartedByPlatformID(ctx, platID)
}

View File

@@ -183,7 +183,12 @@ func getOBSImageBytes(
) ([]byte, time.Time, error) {
img, nextUpdateAt, err := el.Source.GetImage(ctx, obsServer, el, obsState)
if err != nil {
return nil, time.Now().Add(time.Second), fmt.Errorf("unable to get the image from the source: %w", err)
return nil, time.Now().
Add(time.Second),
fmt.Errorf(
"unable to get the image from the source: %w",
err,
)
}
for _, filter := range el.Filters {
@@ -292,7 +297,10 @@ func (d *StreamD) initStreamServer(ctx context.Context) (_err error) {
)
assert(d.StreamServer != nil)
defer d.notifyAboutChange(ctx, events.StreamServersChange)
return d.StreamServer.Init(ctx, sstypes.InitOptionDefaultStreamPlayerOptions(d.streamPlayerOptions()))
return d.StreamServer.Init(
ctx,
sstypes.InitOptionDefaultStreamPlayerOptions(d.streamPlayerOptions()),
)
}
func (d *StreamD) streamPlayerOptions() sptypes.Options {
@@ -526,7 +534,9 @@ func (d *StreamD) SaveConfig(ctx context.Context) error {
if d.GitStorage != nil {
err = d.sendConfigViaGIT(ctx)
if err != nil {
d.UI.DisplayError(fmt.Errorf("unable to send the config to the remote git repository: %w", err))
d.UI.DisplayError(
fmt.Errorf("unable to send the config to the remote git repository: %w", err),
)
}
}
})
@@ -550,7 +560,10 @@ func (d *StreamD) SetConfig(ctx context.Context, cfg *config.Config) error {
return nil
}
func (d *StreamD) IsBackendEnabled(ctx context.Context, id streamcontrol.PlatformName) (bool, error) {
func (d *StreamD) IsBackendEnabled(
ctx context.Context,
id streamcontrol.PlatformName,
) (bool, error) {
return xsync.RDoR2(ctx, &d.ControllersLocker, func() (bool, error) {
switch id {
case obs.ID:
@@ -600,7 +613,12 @@ func (d *StreamD) StartStream(
if err != nil {
return fmt.Errorf("unable to convert the profile into OBS profile: %w", err)
}
err = d.StreamControllers.OBS.StartStream(d.ctxForController(ctx), title, description, *profile, customArgs...)
err = d.StreamControllers.OBS.StartStream(
d.ctxForController(ctx),
title,
description,
*profile,
customArgs...)
if err != nil {
return fmt.Errorf("unable to start the stream on OBS: %w", err)
}
@@ -610,7 +628,12 @@ func (d *StreamD) StartStream(
if err != nil {
return fmt.Errorf("unable to convert the profile into Twitch profile: %w", err)
}
err = d.StreamControllers.Twitch.StartStream(d.ctxForController(ctx), title, description, *profile, customArgs...)
err = d.StreamControllers.Twitch.StartStream(
d.ctxForController(ctx),
title,
description,
*profile,
customArgs...)
if err != nil {
return fmt.Errorf("unable to start the stream on Twitch: %w", err)
}
@@ -620,7 +643,12 @@ func (d *StreamD) StartStream(
if err != nil {
return fmt.Errorf("unable to convert the profile into YouTube profile: %w", err)
}
err = d.StreamControllers.YouTube.StartStream(d.ctxForController(ctx), title, description, *profile, customArgs...)
err = d.StreamControllers.YouTube.StartStream(
d.ctxForController(ctx),
title,
description,
*profile,
customArgs...)
if err != nil {
return fmt.Errorf("unable to start the stream on YouTube: %w", err)
}
@@ -1492,9 +1520,24 @@ func (d *StreamD) UpdateStreamPlayer(
disabled bool,
streamPlaybackConfig sptypes.Config,
) (_err error) {
logger.Debugf(ctx, "UpdateStreamPlayer(ctx, '%s', '%s', %v, %#+v)", streamID, playerType, disabled, streamPlaybackConfig)
logger.Debugf(
ctx,
"UpdateStreamPlayer(ctx, '%s', '%s', %v, %#+v)",
streamID,
playerType,
disabled,
streamPlaybackConfig,
)
defer func() {
logger.Debugf(ctx, "/UpdateStreamPlayer(ctx, '%s', '%s', %v, %#+v): %v", streamID, playerType, disabled, streamPlaybackConfig, _err)
logger.Debugf(
ctx,
"/UpdateStreamPlayer(ctx, '%s', '%s', %v, %#+v): %v",
streamID,
playerType,
disabled,
streamPlaybackConfig,
_err,
)
}()
defer d.notifyAboutChange(ctx, events.StreamPlayersChange)
var result *multierror.Error

View File

@@ -74,7 +74,11 @@ func (p *Panel) ShowErrorReports() {
content := container.NewVBox()
if len(reports) == 0 {
content.Add(widget.NewRichTextWithText("No significant errors were reported since the application was started, yet..."))
content.Add(
widget.NewRichTextWithText(
"No significant errors were reported since the application was started, yet...",
),
)
} else {
for _, report := range reports {
errLabel := report.Error.Error()

View File

@@ -52,7 +52,9 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=
-----END OPENSSH PRIVATE KEY-----`)
gitInstruction := widget.NewRichTextFromMarkdown("We can sync the configuration among all of your devices via a git repository. To get a git repository you may, for example, use GitHub; but never use public repositories, always use private ones (because the repository will contain all the access credentials to YouTube/Twitch/whatever).")
gitInstruction := widget.NewRichTextFromMarkdown(
"We can sync the configuration among all of your devices via a git repository. To get a git repository you may, for example, use GitHub; but never use public repositories, always use private ones (because the repository will contain all the access credentials to YouTube/Twitch/whatever).",
)
gitInstruction.Wrapping = fyne.TextWrapWord
w.SetContent(container.NewBorder(

View File

@@ -149,7 +149,14 @@ func (p *Panel) updateMonitorPageImagesNoLock(
if !changed && lastWinSize == winSize && lastOrientation == orientation {
return
}
logger.Tracef(ctx, "updating the image '%s': %v %#+v %#+v", el.ElementName, changed, lastWinSize, winSize)
logger.Tracef(
ctx,
"updating the image '%s': %v %#+v %#+v",
el.ElementName,
changed,
lastWinSize,
winSize,
)
imgSize := image.Point{
X: int(winSize.Width * float32(el.Width) / 100),
Y: int(winSize.Height * float32(el.Height) / 100),
@@ -192,7 +199,13 @@ func (p *Panel) updateMonitorPageImagesNoLock(
if !changed && lastWinSize == winSize && lastOrientation == orientation {
return
}
logger.Tracef(ctx, "updating the screenshot image: %v %#+v %#+v", changed, lastWinSize, winSize)
logger.Tracef(
ctx,
"updating the screenshot image: %v %#+v %#+v",
changed,
lastWinSize,
winSize,
)
winSize := image.Point{X: int(winSize.Width), Y: int(winSize.Height)}
img = imgFillTo(
ctx,
@@ -366,7 +379,10 @@ func (p *Panel) newMonitorSettingsWindow(ctx context.Context) {
deleteButton := widget.NewButtonWithIcon("", theme.DeleteIcon(), func() {
w := dialog.NewConfirm(
fmt.Sprintf("Delete monitor element '%s'?", name),
fmt.Sprintf("Are you sure you want to delete the element '%s' from the Monitor page?", name),
fmt.Sprintf(
"Are you sure you want to delete the element '%s' from the Monitor page?",
name,
),
func(b bool) {
if !b {
return
@@ -484,7 +500,9 @@ func (p *Panel) editMonitorElementWindow(
SceneName: &sceneName,
})
if err != nil {
p.DisplayError(fmt.Errorf("unable to get the list of items of scene '%s': %w", sceneName, err))
p.DisplayError(
fmt.Errorf("unable to get the list of items of scene '%s': %w", sceneName, err),
)
return
}
for _, item := range resp.SceneItems {
@@ -649,7 +667,9 @@ func (p *Panel) editMonitorElementWindow(
brightness.SetText(fmt.Sprintf("%f", brightnessValue))
obsVideoUpdateInterval := xfyne.NewNumericalEntry()
obsVideoUpdateInterval.SetText(fmt.Sprintf("%v", time.Duration(obsVideoSource.UpdateInterval).Seconds()))
obsVideoUpdateInterval.SetText(
fmt.Sprintf("%v", time.Duration(obsVideoSource.UpdateInterval).Seconds()),
)
obsVideoUpdateInterval.OnChanged = func(s string) {
if s == "" || s == "-" {
s = "0.2"
@@ -767,7 +787,15 @@ func (p *Panel) editMonitorElementWindow(
widget.NewLabel("Source:"),
sourceOBSVideoSelect,
widget.NewLabel("Source image size (use '0' for preserving the original size or ratio):"),
container.NewHBox(widget.NewLabel("X:"), sourceWidth, widget.NewLabel(`px`), widget.NewSeparator(), widget.NewLabel("Y:"), sourceHeight, widget.NewLabel(`px`)),
container.NewHBox(
widget.NewLabel("X:"),
sourceWidth,
widget.NewLabel(`px`),
widget.NewSeparator(),
widget.NewLabel("Y:"),
sourceHeight,
widget.NewLabel(`px`),
),
widget.NewLabel("Format:"),
imageFormatSelect,
widget.NewLabel("Update interval:"),
@@ -779,7 +807,9 @@ func (p *Panel) editMonitorElementWindow(
})
obsVolumeUpdateInterval := xfyne.NewNumericalEntry()
obsVolumeUpdateInterval.SetText(fmt.Sprintf("%v", time.Duration(obsVideoSource.UpdateInterval).Seconds()))
obsVolumeUpdateInterval.SetText(
fmt.Sprintf("%v", time.Duration(obsVideoSource.UpdateInterval).Seconds()),
)
obsVolumeUpdateInterval.OnChanged = func(s string) {
if s == "" || s == "-" {
s = "0.2"
@@ -796,7 +826,11 @@ func (p *Panel) editMonitorElementWindow(
if volumeColorActiveParsed, err = colorx.Parse(obsVolumeSource.ColorActive); err != nil {
volumeColorActiveParsed = color.RGBA{R: 0, G: 255, B: 0, A: 255}
}
volumeColorActive := colorpicker.NewColorSelectModalRect(w, fyne.NewSize(30, 20), volumeColorActiveParsed)
volumeColorActive := colorpicker.NewColorSelectModalRect(
w,
fyne.NewSize(30, 20),
volumeColorActiveParsed,
)
volumeColorActive.SetOnChange(func(c color.Color) {
r32, g32, b32, a32 := c.RGBA()
r8, g8, b8, a8 := uint8(r32>>8), uint8(g32>>8), uint8(b32>>8), uint8(a32>>8)
@@ -807,7 +841,11 @@ func (p *Panel) editMonitorElementWindow(
if volumeColorPassiveParsed, err = colorx.Parse(obsVolumeSource.ColorPassive); err != nil {
volumeColorPassiveParsed = color.RGBA{R: 0, G: 0, B: 0, A: 0}
}
volumeColorPassive := colorpicker.NewColorSelectModalRect(w, fyne.NewSize(30, 20), volumeColorPassiveParsed)
volumeColorPassive := colorpicker.NewColorSelectModalRect(
w,
fyne.NewSize(30, 20),
volumeColorPassiveParsed,
)
volumeColorPassive.SetOnChange(func(c color.Color) {
r32, g32, b32, a32 := c.RGBA()
r8, g8, b8, a8 := uint8(r32>>8), uint8(g32>>8), uint8(b32>>8), uint8(a32>>8)
@@ -884,11 +922,33 @@ func (p *Panel) editMonitorElementWindow(
widget.NewLabel("Z-Index / layer:"),
zIndex,
widget.NewLabel("Display size:"),
container.NewHBox(widget.NewLabel("X:"), displayWidth, widget.NewLabel(`%`), widget.NewSeparator(), widget.NewLabel("Y:"), displayHeight, widget.NewLabel(`%`)),
container.NewHBox(
widget.NewLabel("X:"),
displayWidth,
widget.NewLabel(`%`),
widget.NewSeparator(),
widget.NewLabel("Y:"),
displayHeight,
widget.NewLabel(`%`),
),
widget.NewLabel("Align:"),
container.NewHBox(widget.NewLabel("X:"), alignX, widget.NewSeparator(), widget.NewLabel("Y:"), alignY),
container.NewHBox(
widget.NewLabel("X:"),
alignX,
widget.NewSeparator(),
widget.NewLabel("Y:"),
alignY,
),
widget.NewLabel("Offset:"),
container.NewHBox(widget.NewLabel("X:"), offsetX, widget.NewLabel(`%`), widget.NewSeparator(), widget.NewLabel("Y:"), offsetY, widget.NewLabel(`%`)),
container.NewHBox(
widget.NewLabel("X:"),
offsetX,
widget.NewLabel(`%`),
widget.NewSeparator(),
widget.NewLabel("Y:"),
offsetY,
widget.NewLabel(`%`),
),
widget.NewLabel("Quality:"),
isLossless,
imageQuality,

View File

@@ -248,7 +248,11 @@ func (p *Panel) LazyInitStreamD(ctx context.Context) (_err error) {
if p.Config.RemoteStreamDAddr != "" {
if err := p.initRemoteStreamD(ctx); err != nil {
return fmt.Errorf("unable to initialize the remote stream controller '%s': %w", p.Config.RemoteStreamDAddr, err)
return fmt.Errorf(
"unable to initialize the remote stream controller '%s': %w",
p.Config.RemoteStreamDAddr,
err,
)
}
} else {
if err := p.initBuiltinStreamD(ctx); err != nil {
@@ -312,7 +316,12 @@ func (p *Panel) Loop(ctx context.Context, opts ...LoopOption) error {
p.setStatusFunc("Connecting...")
err := p.startOAuthListenerForRemoteStreamD(ctx, streamD)
if err != nil {
p.setStatusFunc(fmt.Sprintf("Connection failed, please restart the application.\n\nError: %v", err))
p.setStatusFunc(
fmt.Sprintf(
"Connection failed, please restart the application.\n\nError: %v",
err,
),
)
<-ctx.Done()
}
closeLoadingWindow()
@@ -341,7 +350,9 @@ func (p *Panel) Loop(ctx context.Context, opts ...LoopOption) error {
p.initMainWindow(ctx, initCfg.StartingPage)
if streamDRunErr != nil {
p.DisplayError(fmt.Errorf("unable to initialize the streaming controllers: %w", streamDRunErr))
p.DisplayError(
fmt.Errorf("unable to initialize the streaming controllers: %w", streamDRunErr),
)
}
logger.Tracef(ctx, "p.rearrangeProfiles")
@@ -367,7 +378,10 @@ func (p *Panel) startOAuthListenerForRemoteStreamD(
streamD *client.Client,
) error {
ctx, cancelFn := context.WithCancel(ctx)
receiver, listenPort, err := oauthhandler.NewCodeReceiver(ctx, p.Config.OAuth.ListenPorts.Twitch)
receiver, listenPort, err := oauthhandler.NewCodeReceiver(
ctx,
p.Config.OAuth.ListenPorts.Twitch,
)
if err != nil {
cancelFn()
return fmt.Errorf("unable to start listener for OAuth responses: %w", err)
@@ -401,7 +415,13 @@ func (p *Panel) startOAuthListenerForRemoteStreamD(
}
if err := p.openBrowser(ctx, req.GetAuthURL(), "It is required to confirm access in Twitch/YouTube using browser"); err != nil {
p.DisplayError(fmt.Errorf("unable to open browser with URL '%s': %w", req.GetAuthURL(), err))
p.DisplayError(
fmt.Errorf(
"unable to open browser with URL '%s': %w",
req.GetAuthURL(),
err,
),
)
continue
}
@@ -420,7 +440,13 @@ func (p *Panel) startOAuthListenerForRemoteStreamD(
Code: code,
})
if err != nil {
p.DisplayError(fmt.Errorf("unable to submit the oauth code of '%s': %w", req.GetPlatID(), err))
p.DisplayError(
fmt.Errorf(
"unable to submit the oauth code of '%s': %w",
req.GetPlatID(),
err,
),
)
continue
}
}
@@ -626,13 +652,19 @@ func (p *Panel) OnSubmittedOAuthCode(
return nil
}
func (p *Panel) OAuthHandlerTwitch(ctx context.Context, arg oauthhandler.OAuthHandlerArgument) error {
func (p *Panel) OAuthHandlerTwitch(
ctx context.Context,
arg oauthhandler.OAuthHandlerArgument,
) error {
logger.Infof(ctx, "OAuthHandlerTwitch: %#+v", arg)
defer logger.Infof(ctx, "/OAuthHandlerTwitch")
return p.oauthHandler(ctx, twitch.ID, arg)
}
func (p *Panel) OAuthHandlerYouTube(ctx context.Context, arg oauthhandler.OAuthHandlerArgument) error {
func (p *Panel) OAuthHandlerYouTube(
ctx context.Context,
arg oauthhandler.OAuthHandlerArgument,
) error {
logger.Infof(ctx, "OAuthHandlerYouTube: %#+v", arg)
defer logger.Infof(ctx, "/OAuthHandlerYouTube")
return p.oauthHandler(ctx, youtube.ID, arg)
@@ -657,7 +689,11 @@ func (p *Panel) oauthHandler(
return fmt.Errorf("unable to open browser with URL '%s': %w", arg.AuthURL, err)
}
logger.Infof(ctx, "Your browser has been launched (URL: %s).\nPlease approve the permissions.\n", arg.AuthURL)
logger.Infof(
ctx,
"Your browser has been launched (URL: %s).\nPlease approve the permissions.\n",
arg.AuthURL,
)
// Wait for the web server to get the code.
code := <-codeCh
@@ -685,7 +721,12 @@ func (p *Panel) openBrowser(
if p.Config.Browser.Command != "" {
args := []string{p.Config.Browser.Command, url}
logger.Debugf(ctx, "the browser command is configured to be '%s', so running '%s'", p.Config.Browser.Command, strings.Join(args, " "))
logger.Debugf(
ctx,
"the browser command is configured to be '%s', so running '%s'",
p.Config.Browser.Command,
strings.Join(args, " "),
)
return exec.Command(args[0], args[1:]...).Start()
}
@@ -759,7 +800,9 @@ func (p *Panel) InputTwitchUserInfo(
resizeWindow(w, fyne.NewSize(600, 200))
channelField := widget.NewEntry()
channelField.SetPlaceHolder("channel ID (copy&paste it from the browser: https://www.twitch.tv/<the channel ID is here>)")
channelField.SetPlaceHolder(
"channel ID (copy&paste it from the browser: https://www.twitch.tv/<the channel ID is here>)",
)
clientIDField := widget.NewEntry()
clientIDField.SetPlaceHolder("client ID")
clientSecretField := widget.NewEntry()
@@ -767,7 +810,10 @@ func (p *Panel) InputTwitchUserInfo(
instructionText := widget.NewRichText(
&widget.TextSegment{Text: "Go to\n", Style: widget.RichTextStyle{Inline: true}},
&widget.HyperlinkSegment{Text: twitchAppsCreateLink.String(), URL: twitchAppsCreateLink},
&widget.TextSegment{Text: `,` + "\n" + `create an application (enter "http://localhost:8091/" as the "OAuth Redirect URLs" value), then click "Manage" then "New Secret", and copy&paste client ID and client secret.`, Style: widget.RichTextStyle{Inline: true}},
&widget.TextSegment{
Text: `,` + "\n" + `create an application (enter "http://localhost:8091/" as the "OAuth Redirect URLs" value), then click "Manage" then "New Secret", and copy&paste client ID and client secret.`,
Style: widget.RichTextStyle{Inline: true},
},
)
instructionText.Wrapping = fyne.TextWrapWord
@@ -810,7 +856,9 @@ func (p *Panel) InputTwitchUserInfo(
return true, nil
}
var youtubeCredentialsCreateLink, _ = url.Parse("https://console.cloud.google.com/apis/credentials/oauthclient")
var youtubeCredentialsCreateLink, _ = url.Parse(
"https://console.cloud.google.com/apis/credentials/oauthclient",
)
func (p *Panel) InputYouTubeUserInfo(
ctx context.Context,
@@ -825,10 +873,22 @@ func (p *Panel) InputYouTubeUserInfo(
clientSecretField.SetPlaceHolder("client secret")
instructionText := widget.NewRichText(
&widget.TextSegment{Text: "Go to\n", Style: widget.RichTextStyle{Inline: true}},
&widget.HyperlinkSegment{Text: youtubeCredentialsCreateLink.String(), URL: youtubeCredentialsCreateLink},
&widget.TextSegment{Text: `,` + "\n" + `configure "consent screen" (note: you may add yourself into Test Users to avoid problems further on, and don't forget to add "YouTube Data API v3" scopes) and go back to` + "\n", Style: widget.RichTextStyle{Inline: true}},
&widget.HyperlinkSegment{Text: youtubeCredentialsCreateLink.String(), URL: youtubeCredentialsCreateLink},
&widget.TextSegment{Text: `,` + "\n" + `choose "Desktop app", confirm and copy&paste client ID and client secret.`, Style: widget.RichTextStyle{Inline: true}},
&widget.HyperlinkSegment{
Text: youtubeCredentialsCreateLink.String(),
URL: youtubeCredentialsCreateLink,
},
&widget.TextSegment{
Text: `,` + "\n" + `configure "consent screen" (note: you may add yourself into Test Users to avoid problems further on, and don't forget to add "YouTube Data API v3" scopes) and go back to` + "\n",
Style: widget.RichTextStyle{Inline: true},
},
&widget.HyperlinkSegment{
Text: youtubeCredentialsCreateLink.String(),
URL: youtubeCredentialsCreateLink,
},
&widget.TextSegment{
Text: `,` + "\n" + `choose "Desktop app", confirm and copy&paste client ID and client secret.`,
Style: widget.RichTextStyle{Inline: true},
},
)
instructionText.Wrapping = fyne.TextWrapWord
@@ -879,11 +939,23 @@ func (p *Panel) profileCreateOrUpdate(ctx context.Context, profile Profile) erro
continue
}
cfg.Backends[platformName].StreamProfiles[profile.Name] = platformProfile
logger.Tracef(ctx, "profileCreateOrUpdate(%s): cfg.Backends[%s].StreamProfiles[%s] = %#+v", profile.Name, platformName, profile.Name, platformProfile)
logger.Tracef(
ctx,
"profileCreateOrUpdate(%s): cfg.Backends[%s].StreamProfiles[%s] = %#+v",
profile.Name,
platformName,
profile.Name,
platformProfile,
)
}
cfg.ProfileMetadata[profile.Name] = profile.ProfileMetadata
logger.Tracef(ctx, "profileCreateOrUpdate(%s): cfg.Backends == %#+v", profile.Name, cfg.Backends)
logger.Tracef(
ctx,
"profileCreateOrUpdate(%s): cfg.Backends == %#+v",
profile.Name,
cfg.Backends,
)
err = p.StreamD.SetConfig(ctx, cfg)
if err != nil {
@@ -1002,7 +1074,11 @@ func (p *Panel) refilterProfiles(ctx context.Context) {
if p.filterValue == "" {
p.profilesOrderFiltered = p.profilesOrderFiltered[:len(p.profilesOrder)]
copy(p.profilesOrderFiltered, p.profilesOrder)
logger.Tracef(ctx, "refilterProfiles(): profilesOrderFiltered <- p.profilesOrder: %#+v", p.profilesOrder)
logger.Tracef(
ctx,
"refilterProfiles(): profilesOrderFiltered <- p.profilesOrder: %#+v",
p.profilesOrder,
)
logger.Tracef(ctx, "refilterProfiles(): p.profilesListWidget.Refresh()")
p.profilesListWidget.Refresh()
return
@@ -1037,7 +1113,12 @@ func (p *Panel) refilterProfiles(ctx context.Context) {
}
if titleMatch || subValueMatch {
logger.Tracef(ctx, "refilterProfiles(): profilesOrderFiltered[%3d] = %s", len(p.profilesOrderFiltered), profileName)
logger.Tracef(
ctx,
"refilterProfiles(): profilesOrderFiltered[%3d] = %s",
len(p.profilesOrderFiltered),
profileName,
)
p.profilesOrderFiltered = append(p.profilesOrderFiltered, profileName)
}
}
@@ -1153,10 +1234,18 @@ func (p *Panel) openSettingsWindow(ctx context.Context) error {
logger.Debugf(ctx, "current OBS config: %#+v", obsCfg)
}
cmdBeforeStartStream, _ := cfg.Backends[obs.ID].GetCustomString(config.CustomConfigKeyBeforeStreamStart)
cmdBeforeStopStream, _ := cfg.Backends[obs.ID].GetCustomString(config.CustomConfigKeyBeforeStreamStop)
cmdAfterStartStream, _ := cfg.Backends[obs.ID].GetCustomString(config.CustomConfigKeyAfterStreamStart)
cmdAfterStopStream, _ := cfg.Backends[obs.ID].GetCustomString(config.CustomConfigKeyAfterStreamStop)
cmdBeforeStartStream, _ := cfg.Backends[obs.ID].GetCustomString(
config.CustomConfigKeyBeforeStreamStart,
)
cmdBeforeStopStream, _ := cfg.Backends[obs.ID].GetCustomString(
config.CustomConfigKeyBeforeStreamStop,
)
cmdAfterStartStream, _ := cfg.Backends[obs.ID].GetCustomString(
config.CustomConfigKeyAfterStreamStart,
)
cmdAfterStopStream, _ := cfg.Backends[obs.ID].GetCustomString(
config.CustomConfigKeyAfterStreamStop,
)
beforeStartStreamCommandEntry := widget.NewEntry()
beforeStartStreamCommandEntry.SetText(cmdBeforeStartStream)
@@ -1210,7 +1299,9 @@ func (p *Panel) openSettingsWindow(ctx context.Context) error {
w.Close()
})
templateInstruction := widget.NewRichTextFromMarkdown("Commands support [Go templates](https://pkg.go.dev/text/template) with two custom functions predefined:\n* `devnull` nullifies any inputs\n* `httpGET` makes an HTTP GET request and inserts the response body")
templateInstruction := widget.NewRichTextFromMarkdown(
"Commands support [Go templates](https://pkg.go.dev/text/template) with two custom functions predefined:\n* `devnull` nullifies any inputs\n* `httpGET` makes an HTTP GET request and inserts the response body",
)
templateInstruction.Wrapping = fyne.TextWrapWord
obsAlreadyLoggedIn := widget.NewLabel("")
@@ -1368,7 +1459,10 @@ func (p *Panel) openSettingsWindow(ctx context.Context) error {
displayIDSelector,
widget.NewLabel("Crop to:"),
container.NewHBox(
screenshotCropXEntry, screenshotCropYEntry, screenshotCropWEntry, screenshotCropHEntry,
screenshotCropXEntry,
screenshotCropYEntry,
screenshotCropWEntry,
screenshotCropHEntry,
),
widget.NewSeparator(),
widget.NewSeparator(),
@@ -1512,7 +1606,9 @@ func (p *Panel) getUpdatedStatus_backends_noLock(ctx context.Context) {
} {
isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID)
if err != nil {
p.ReportError(fmt.Errorf("unable to get info if backend '%s' is enabled: %w", backendID, err))
p.ReportError(
fmt.Errorf("unable to get info if backend '%s' is enabled: %w", backendID, err),
)
}
backendEnabled[backendID] = isEnabled
}
@@ -1629,7 +1725,12 @@ func (p *Panel) getUpdatedStatus_startStopStreamButton_noLock(ctx context.Contex
logger.Tracef(ctx, "ytStreamStatus == %#+v", ytStreamStatus)
if d, ok := ytStreamStatus.CustomData.(youtube.StreamStatusCustomData); ok {
logger.Tracef(ctx, "len(d.UpcomingBroadcasts) == %d; len(d.Streams) == %d", len(d.UpcomingBroadcasts), len(d.Streams))
logger.Tracef(
ctx,
"len(d.UpcomingBroadcasts) == %d; len(d.Streams) == %d",
len(d.UpcomingBroadcasts),
len(d.Streams),
)
if len(d.UpcomingBroadcasts) != 0 {
p.startStopButton.Enable()
}
@@ -1694,18 +1795,30 @@ func (p *Panel) initMainWindow(
profileControl.Add(button)
}
p.setupStreamButton = widget.NewButtonWithIcon(setupStreamString(), theme.SettingsIcon(), func() {
p.setupStreamButton = widget.NewButtonWithIcon(
setupStreamString(),
theme.SettingsIcon(),
func() {
p.onSetupStreamButton(ctx)
})
},
)
p.setupStreamButton.Disable()
p.startStopButton = widget.NewButtonWithIcon(startStreamString(), theme.MediaRecordIcon(), func() {
p.startStopButton = widget.NewButtonWithIcon(
startStreamString(),
theme.MediaRecordIcon(),
func() {
p.onStartStopButton(ctx)
})
},
)
p.startStopButton.Importance = widget.SuccessImportance
p.startStopButton.Disable()
profilesList := widget.NewList(p.profilesListLength, p.profilesListItemCreate, p.profilesListItemUpdate)
profilesList := widget.NewList(
p.profilesListLength,
p.profilesListItemCreate,
p.profilesListItemUpdate,
)
profilesList.OnSelected = func(id widget.ListItemID) {
p.onProfilesListSelect(id)
for _, button := range selectedProfileButtons {
@@ -2116,7 +2229,9 @@ func (p *Panel) setupStreamNoLock(ctx context.Context) {
} {
isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID)
if err != nil {
p.DisplayError(fmt.Errorf("unable to get info if backend '%s' is enabled: %w", backendID, err))
p.DisplayError(
fmt.Errorf("unable to get info if backend '%s' is enabled: %w", backendID, err),
)
return
}
backendEnabled[backendID] = isEnabled
@@ -2268,7 +2383,9 @@ func (p *Panel) stopStreamNoLock(ctx context.Context) {
} {
isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID)
if err != nil {
p.DisplayError(fmt.Errorf("unable to get info if backend '%s' is enabled: %w", backendID, err))
p.DisplayError(
fmt.Errorf("unable to get info if backend '%s' is enabled: %w", backendID, err),
)
return
}
backendEnabled[backendID] = isEnabled
@@ -2627,9 +2744,13 @@ func newTagsEditor(
tagsControlsContainer.Add(widget.NewSeparator())
tagsControlsContainer.Add(widget.NewSeparator())
for _, additionalButtonInfo := range additionalButtons {
button := widget.NewButtonWithIcon(additionalButtonInfo.Label, additionalButtonInfo.Icon, func() {
button := widget.NewButtonWithIcon(
additionalButtonInfo.Label,
additionalButtonInfo.Icon,
func() {
additionalButtonInfo.Callback(t, selectedTagsOrdered())
})
},
)
tagsControlsContainer.Add(button)
}
@@ -2655,7 +2776,11 @@ func newTagsEditor(
tagLabel := tagName
overflown := false
for {
size := fyne.MeasureText(tagLabel, fyne.CurrentApp().Settings().Theme().Size("text"), fyne.TextStyle{})
size := fyne.MeasureText(
tagLabel,
fyne.CurrentApp().Settings().Theme().Size("text"),
fyne.TextStyle{},
)
if size.Width < 100 {
break
}
@@ -2732,7 +2857,9 @@ func (p *Panel) profileWindow(
isEnabled, err := p.StreamD.IsBackendEnabled(ctx, backendID)
if err != nil {
w.Close()
p.DisplayError(fmt.Errorf("unable to get info if backend '%s' is enabled: %w", backendID, err))
p.DisplayError(
fmt.Errorf("unable to get info if backend '%s' is enabled: %w", backendID, err),
)
return nil
}
backendEnabled[backendID] = isEnabled
@@ -2756,7 +2883,9 @@ func (p *Panel) profileWindow(
bottomContent = append(bottomContent, widget.NewRichTextFromMarkdown("# OBS:"))
if backendEnabled[obs.ID] {
if platProfile := values.PerPlatform[obs.ID]; platProfile != nil {
obsProfile = ptr(streamcontrol.GetPlatformSpecificConfig[obs.StreamProfile](ctx, platProfile))
obsProfile = ptr(
streamcontrol.GetPlatformSpecificConfig[obs.StreamProfile](ctx, platProfile),
)
} else {
obsProfile = &obs.StreamProfile{}
}
@@ -2778,7 +2907,9 @@ func (p *Panel) profileWindow(
}
if platProfile := values.PerPlatform[twitch.ID]; platProfile != nil {
twitchProfile = ptr(streamcontrol.GetPlatformSpecificConfig[twitch.StreamProfile](ctx, platProfile))
twitchProfile = ptr(
streamcontrol.GetPlatformSpecificConfig[twitch.StreamProfile](ctx, platProfile),
)
for _, tag := range twitchProfile.Tags {
addTag(tag)
}
@@ -2802,9 +2933,13 @@ func (p *Panel) profileWindow(
if strings.Contains(cleanTwitchCategoryName(cat.Name), text) {
selectedTwitchCategoryContainer := container.NewHBox()
catName := cat.Name
tagContainerRemoveButton := widget.NewButtonWithIcon(catName, theme.ContentAddIcon(), func() {
tagContainerRemoveButton := widget.NewButtonWithIcon(
catName,
theme.ContentAddIcon(),
func() {
twitchCategory.OnSubmitted(catName)
})
},
)
selectedTwitchCategoryContainer.Add(tagContainerRemoveButton)
selectTwitchCategoryBox.Add(selectedTwitchCategoryContainer)
count++
@@ -2821,10 +2956,14 @@ func (p *Panel) profileWindow(
setSelectedTwitchCategory := func(catName string) {
selectedTwitchCategoryBox.RemoveAll()
selectedTwitchCategoryContainer := container.NewHBox()
tagContainerRemoveButton := widget.NewButtonWithIcon(catName, theme.ContentClearIcon(), func() {
tagContainerRemoveButton := widget.NewButtonWithIcon(
catName,
theme.ContentClearIcon(),
func() {
selectedTwitchCategoryBox.Remove(selectedTwitchCategoryContainer)
twitchProfile.CategoryName = nil
})
},
)
selectedTwitchCategoryContainer.Add(tagContainerRemoveButton)
selectedTwitchCategoryBox.Add(selectedTwitchCategoryContainer)
twitchProfile.CategoryName = &catName
@@ -2878,7 +3017,9 @@ func (p *Panel) profileWindow(
youtubeTags = append(youtubeTags, tagName)
}
if platProfile := values.PerPlatform[youtube.ID]; platProfile != nil {
youtubeProfile = ptr(streamcontrol.GetPlatformSpecificConfig[youtube.StreamProfile](ctx, platProfile))
youtubeProfile = ptr(
streamcontrol.GetPlatformSpecificConfig[youtube.StreamProfile](ctx, platProfile),
)
for _, tag := range youtubeProfile.Tags {
addTag(tag)
}
@@ -2890,8 +3031,14 @@ func (p *Panel) profileWindow(
youtubeProfile.AutoNumerate = b
})
autoNumerateCheck.SetChecked(youtubeProfile.AutoNumerate)
autoNumerateHint := NewHintWidget(w, "When enabled, it adds the number of the stream to the stream's title.\n\nFor example 'Watching presidential debate' -> 'Watching presidential debate [#52]'.")
bottomContent = append(bottomContent, container.NewHBox(autoNumerateCheck, autoNumerateHint))
autoNumerateHint := NewHintWidget(
w,
"When enabled, it adds the number of the stream to the stream's title.\n\nFor example 'Watching presidential debate' -> 'Watching presidential debate [#52]'.",
)
bottomContent = append(
bottomContent,
container.NewHBox(autoNumerateCheck, autoNumerateHint),
)
youtubeTemplate := widget.NewEntry()
youtubeTemplate.SetPlaceHolder("youtube live recording template")
@@ -2909,9 +3056,13 @@ func (p *Panel) profileWindow(
if strings.Contains(cleanYoutubeRecordingName(bc.Snippet.Title), text) {
selectedYoutubeRecordingsContainer := container.NewHBox()
recName := bc.Snippet.Title
tagContainerRemoveButton := widget.NewButtonWithIcon(recName, theme.ContentAddIcon(), func() {
tagContainerRemoveButton := widget.NewButtonWithIcon(
recName,
theme.ContentAddIcon(),
func() {
youtubeTemplate.OnSubmitted(recName)
})
},
)
selectedYoutubeRecordingsContainer.Add(tagContainerRemoveButton)
selectYoutubeTemplateBox.Add(selectedYoutubeRecordingsContainer)
count++
@@ -2929,10 +3080,14 @@ func (p *Panel) profileWindow(
selectedYoutubeBroadcastBox.RemoveAll()
selectedYoutubeBroadcastContainer := container.NewHBox()
recName := bc.Snippet.Title
tagContainerRemoveButton := widget.NewButtonWithIcon(recName, theme.ContentClearIcon(), func() {
tagContainerRemoveButton := widget.NewButtonWithIcon(
recName,
theme.ContentClearIcon(),
func() {
selectedYoutubeBroadcastBox.Remove(selectedYoutubeBroadcastContainer)
youtubeProfile.TemplateBroadcastIDs = youtubeProfile.TemplateBroadcastIDs[:0]
})
},
)
selectedYoutubeBroadcastContainer.Add(tagContainerRemoveButton)
selectedYoutubeBroadcastBox.Add(selectedYoutubeBroadcastContainer)
youtubeProfile.TemplateBroadcastIDs = []string{bc.Id}
@@ -2966,7 +3121,9 @@ func (p *Panel) profileWindow(
bottomContent = append(bottomContent, youtubeTemplate)
templateTagsLabel := widget.NewLabel("Template tags:")
templateTags := widget.NewSelect([]string{"ignore", "use as primary", "use as additional"}, func(s string) {
templateTags := widget.NewSelect(
[]string{"ignore", "use as primary", "use as additional"},
func(s string) {
switch s {
case "ignore":
youtubeProfile.TemplateTags = youtube.TemplateTagsIgnore
@@ -2977,7 +3134,8 @@ func (p *Panel) profileWindow(
default:
p.DisplayError(fmt.Errorf("unexpected new value of 'template tags': '%s'", s))
}
})
},
)
switch youtubeProfile.TemplateTags {
case youtube.TemplateTagsUndefined, youtube.TemplateTagsIgnore:
templateTags.SetSelected("ignore")
@@ -2986,11 +3144,22 @@ func (p *Panel) profileWindow(
case youtube.TemplateTagsUseAsAdditional:
templateTags.SetSelected("use as additional")
default:
p.DisplayError(fmt.Errorf("unexpected current value of 'template tags': '%s'", youtubeProfile.TemplateTags))
p.DisplayError(
fmt.Errorf(
"unexpected current value of 'template tags': '%s'",
youtubeProfile.TemplateTags,
),
)
}
templateTags.SetSelected(youtubeProfile.TemplateTags.String())
templateTagsHint := NewHintWidget(w, "'ignore' will ignore the tags set in the template; 'use as primary' will put the tags of the template first and then add the profile tags; 'use as additional' will put the tags of the profile first and then add the template tags")
bottomContent = append(bottomContent, container.NewHBox(templateTagsLabel, templateTags, templateTagsHint))
templateTagsHint := NewHintWidget(
w,
"'ignore' will ignore the tags set in the template; 'use as primary' will put the tags of the template first and then add the profile tags; 'use as additional' will put the tags of the profile first and then add the template tags",
)
bottomContent = append(
bottomContent,
container.NewHBox(templateTagsLabel, templateTags, templateTagsHint),
)
youtubeTagsEditor := newTagsEditor(youtubeTags, 0)
bottomContent = append(bottomContent, widget.NewLabel("Tags:"))

View File

@@ -288,7 +288,14 @@ func (p *Panel) displayStreamServers(
p.previousNumBytesLocker.Do(ctx, func() {
prevNumBytes := p.previousNumBytes[key]
now := time.Now()
bwStr := bwString(srv.NumBytesProducerRead, prevNumBytes[0], srv.NumBytesConsumerWrote, prevNumBytes[1], now, p.previousNumBytesTS[key])
bwStr := bwString(
srv.NumBytesProducerRead,
prevNumBytes[0],
srv.NumBytesConsumerWrote,
prevNumBytes[1],
now,
p.previousNumBytesTS[key],
)
bwText := widget.NewRichTextWithText(bwStr)
hasDynamicValue = hasDynamicValue || bwStr != ""
p.previousNumBytes[key] = [4]uint64{srv.NumBytesProducerRead, srv.NumBytesConsumerWrote}
@@ -571,7 +578,14 @@ func (p *Panel) displayStreamDestinations(
}
func (p *Panel) openAddPlayerWindow(ctx context.Context) {
p.openAddOrEditPlayerWindow(ctx, "Add player", false, sptypes.DefaultConfig(ctx), nil, p.StreamD.AddStreamPlayer)
p.openAddOrEditPlayerWindow(
ctx,
"Add player",
false,
sptypes.DefaultConfig(ctx),
nil,
p.StreamD.AddStreamPlayer,
)
}
func (p *Panel) openEditPlayerWindow(
@@ -593,7 +607,14 @@ func (p *Panel) openEditPlayerWindow(
p.DisplayError(fmt.Errorf("unable to find a stream player for '%s'", streamID))
return
}
p.openAddOrEditPlayerWindow(ctx, "Edit player", !playerCfg.Disabled, playerCfg.StreamPlayback, &streamID, p.StreamD.UpdateStreamPlayer)
p.openAddOrEditPlayerWindow(
ctx,
"Edit player",
!playerCfg.Disabled,
playerCfg.StreamPlayback,
&streamID,
p.StreamD.UpdateStreamPlayer,
)
}
func (p *Panel) openAddOrEditPlayerWindow(
@@ -779,14 +800,28 @@ func (p *Panel) displayStreamPlayers(
c := container.NewHBox()
deleteButton := widget.NewButtonWithIcon("", theme.DeleteIcon(), func() {
w := dialog.NewConfirm(
fmt.Sprintf("Delete player for stream '%s' (%s) ?", player.StreamID, player.PlayerType),
fmt.Sprintf(
"Delete player for stream '%s' (%s) ?",
player.StreamID,
player.PlayerType,
),
"",
func(b bool) {
if !b {
return
}
logger.Debugf(ctx, "remove player '%s' (%s)", player.StreamID, player.PlayerType)
defer logger.Debugf(ctx, "/remove player '%s' (%s)", player.StreamID, player.PlayerType)
logger.Debugf(
ctx,
"remove player '%s' (%s)",
player.StreamID,
player.PlayerType,
)
defer logger.Debugf(
ctx,
"/remove player '%s' (%s)",
player.StreamID,
player.PlayerType,
)
err := p.StreamD.RemoveStreamPlayer(ctx, player.StreamID)
if err != nil {
p.DisplayError(err)
@@ -816,8 +851,22 @@ func (p *Panel) displayStreamPlayers(
if !b {
return
}
logger.Debugf(ctx, "stop/start player %s on '%s': disabled:%v->%v", player.PlayerType, player.StreamID, player.Disabled, !player.Disabled)
defer logger.Debugf(ctx, "/stop/start player %s on '%s': disabled:%v->%v", player.PlayerType, player.StreamID, player.Disabled, !player.Disabled)
logger.Debugf(
ctx,
"stop/start player %s on '%s': disabled:%v->%v",
player.PlayerType,
player.StreamID,
player.Disabled,
!player.Disabled,
)
defer logger.Debugf(
ctx,
"/stop/start player %s on '%s': disabled:%v->%v",
player.PlayerType,
player.StreamID,
player.Disabled,
!player.Disabled,
)
err := p.StreamD.UpdateStreamPlayer(
xcontext.DetachDone(ctx),
player.StreamID,
@@ -843,7 +892,12 @@ func (p *Panel) displayStreamPlayers(
if !player.Disabled {
pos, err := p.StreamD.StreamPlayerGetPosition(ctx, player.StreamID)
if err != nil {
logger.Errorf(ctx, "unable to get the current position at player '%s': %v", player.StreamID, err)
logger.Errorf(
ctx,
"unable to get the current position at player '%s': %v",
player.StreamID,
err,
)
} else {
c.Add(widget.NewSeparator())
posStr := pos.String()
@@ -932,8 +986,22 @@ func (p *Panel) openAddOrEditRestreamWindow(
quirks sstypes.ForwardingQuirks,
) error,
) {
logger.Debugf(ctx, "openAddOrEditRestreamWindow(ctx, '%s', '%s', '%s', %#+v)", title, streamID, dstID, fwd)
defer logger.Debugf(ctx, "/openAddOrEditRestreamWindow(ctx, '%s', '%s', '%s', %#+v)", title, streamID, dstID, fwd)
logger.Debugf(
ctx,
"openAddOrEditRestreamWindow(ctx, '%s', '%s', '%s', %#+v)",
title,
streamID,
dstID,
fwd,
)
defer logger.Debugf(
ctx,
"/openAddOrEditRestreamWindow(ctx, '%s', '%s', '%s', %#+v)",
title,
streamID,
dstID,
fwd,
)
w := p.app.NewWindow(AppName + ": " + title)
resizeWindow(w, fyne.NewSize(400, 300))
@@ -1146,7 +1214,11 @@ func (p *Panel) displayStreamForwards(
c := container.NewHBox()
deleteButton := widget.NewButtonWithIcon("", theme.DeleteIcon(), func() {
w := dialog.NewConfirm(
fmt.Sprintf("Delete restreaming (stream forwarding) %s -> %s ?", fwd.StreamID, fwd.DestinationID),
fmt.Sprintf(
"Delete restreaming (stream forwarding) %s -> %s ?",
fwd.StreamID,
fwd.DestinationID,
),
"",
func(b bool) {
if !b {
@@ -1183,8 +1255,18 @@ func (p *Panel) displayStreamForwards(
if !b {
return
}
logger.Debugf(ctx, "pause/unpause restreaming (stream forwarding): enabled:%v->%v", fwd.Enabled, !fwd.Enabled)
defer logger.Debugf(ctx, "/pause/unpause restreaming (stream forwarding): enabled:%v->%v", !fwd.Enabled, fwd.Enabled)
logger.Debugf(
ctx,
"pause/unpause restreaming (stream forwarding): enabled:%v->%v",
fwd.Enabled,
!fwd.Enabled,
)
defer logger.Debugf(
ctx,
"/pause/unpause restreaming (stream forwarding): enabled:%v->%v",
!fwd.Enabled,
fwd.Enabled,
)
err := p.StreamD.UpdateStreamForward(
ctx,
fwd.StreamID,
@@ -1229,7 +1311,14 @@ func (p *Panel) displayStreamForwards(
now := time.Now()
p.previousNumBytesLocker.Do(ctx, func() {
prevNumBytes := p.previousNumBytes[key]
bwStr := bwString(fwd.NumBytesRead, prevNumBytes[0], fwd.NumBytesWrote, prevNumBytes[1], now, p.previousNumBytesTS[key])
bwStr := bwString(
fwd.NumBytesRead,
prevNumBytes[0],
fwd.NumBytesWrote,
prevNumBytes[1],
now,
p.previousNumBytesTS[key],
)
bwText := widget.NewRichTextWithText(bwStr)
hasDynamicValue = hasDynamicValue || bwStr != ""
p.previousNumBytes[key] = [4]uint64{fwd.NumBytesRead, fwd.NumBytesWrote}

View File

@@ -224,7 +224,13 @@ func (ui *timersUI) start(
}
}
duration := time.Hour*time.Duration(hours) + time.Minute*time.Duration(mins) + time.Second*time.Duration(secs)
duration := time.Hour*time.Duration(
hours,
) + time.Minute*time.Duration(
mins,
) + time.Second*time.Duration(
secs,
)
if duration == 0 {
ui.panel.DisplayError(fmt.Errorf("the time is not set for the timer"))

View File

@@ -15,7 +15,10 @@ type updateTimerHandler struct {
startTS time.Time
}
func newUpdateTimerHandler(startStopButton *widget.Button, startedAt time.Time) *updateTimerHandler {
func newUpdateTimerHandler(
startStopButton *widget.Button,
startedAt time.Time,
) *updateTimerHandler {
ctx, cancelFn := context.WithCancel(context.Background())
h := &updateTimerHandler{
ctx: ctx,

View File

@@ -121,7 +121,8 @@ func getMousePos(window fyne.Window) fyne.Position {
func (w *HintWidget) isHovering(mousePos fyne.Position) bool {
pos0 := GetAbsolutePosition(w, w.Window.Canvas().Content())
pos1 := pos0.Add(w.Label.Size())
if mousePos.X >= pos0.X && mousePos.Y >= pos0.Y && mousePos.X <= pos1.X && mousePos.Y <= pos1.Y {
if mousePos.X >= pos0.X && mousePos.Y >= pos0.Y && mousePos.X <= pos1.X &&
mousePos.Y <= pos1.Y {
return true
}

View File

@@ -12,10 +12,16 @@ type dummyPlatformsController struct{}
var _ streamservertypes.PlatformsController = (*dummyPlatformsController)(nil)
func (dummyPlatformsController) CheckStreamStartedByURL(ctx context.Context, destination *url.URL) (bool, error) {
func (dummyPlatformsController) CheckStreamStartedByURL(
ctx context.Context,
destination *url.URL,
) (bool, error) {
return true, nil
}
func (dummyPlatformsController) CheckStreamStartedByPlatformID(ctx context.Context, platID streamcontrol.PlatformName) (bool, error) {
func (dummyPlatformsController) CheckStreamStartedByPlatformID(
ctx context.Context,
platID streamcontrol.PlatformName,
) (bool, error) {
return true, nil
}

View File

@@ -37,8 +37,16 @@ func assertNoError(ctx context.Context, err error) {
func main() {
loggerLevel := logger.LevelWarning
pflag.Var(&loggerLevel, "log-level", "Log level")
rtmpListenAddr := pflag.String("rtmp-listen-addr", "127.0.0.1:1935", "the TCP port to serve an RTMP server on")
streamID := pflag.String("stream-id", "test/test", "the path of the stream in rtmp://address/path")
rtmpListenAddr := pflag.String(
"rtmp-listen-addr",
"127.0.0.1:1935",
"the TCP port to serve an RTMP server on",
)
streamID := pflag.String(
"stream-id",
"test/test",
"the path of the stream in rtmp://address/path",
)
mpvPath := pflag.String("mpv", "mpv", "path to mpv")
pflag.Parse()

View File

@@ -96,7 +96,14 @@ func (sp *StreamPlayers) Create(
) (_ret *StreamPlayerHandler, _err error) {
logger.Debugf(ctx, "StreamPlayers.Create(ctx, '%s', %#+v)", streamID, opts)
defer func() {
logger.Debugf(ctx, "/StreamPlayers.Create(ctx, '%s', %#+v): (%v, %v)", streamID, opts, _ret, _err)
logger.Debugf(
ctx,
"/StreamPlayers.Create(ctx, '%s', %#+v): (%v, %v)",
streamID,
opts,
_ret,
_err,
)
}()
ctx, cancel := context.WithCancel(ctx)
@@ -114,11 +121,18 @@ func (sp *StreamPlayers) Create(
}
if p.Config.CatchupMaxSpeedFactor <= 1 {
return nil, fmt.Errorf("MaxCatchupSpeedFactor should be higher than 1, but it is %v", p.Config.CatchupMaxSpeedFactor)
return nil, fmt.Errorf(
"MaxCatchupSpeedFactor should be higher than 1, but it is %v",
p.Config.CatchupMaxSpeedFactor,
)
}
if p.Config.MaxCatchupAtLag <= p.Config.JitterBufDuration {
return nil, fmt.Errorf("MaxCatchupAtLag (%v) should be higher than JitterBufDuration (%v)", p.Config.MaxCatchupAtLag, p.Config.JitterBufDuration)
return nil, fmt.Errorf(
"MaxCatchupAtLag (%v) should be higher than JitterBufDuration (%v)",
p.Config.MaxCatchupAtLag,
p.Config.JitterBufDuration,
)
}
if err := p.startU(ctx); err != nil {
@@ -157,13 +171,17 @@ func (sp *StreamPlayers) Get(streamID streamtypes.StreamID) *StreamPlayerHandler
func (sp *StreamPlayers) GetAll() map[streamtypes.StreamID]*StreamPlayerHandler {
ctx := context.TODO()
return xsync.DoR1(ctx, &sp.StreamPlayersLocker, func() map[streamtypes.StreamID]*StreamPlayerHandler {
return xsync.DoR1(
ctx,
&sp.StreamPlayersLocker,
func() map[streamtypes.StreamID]*StreamPlayerHandler {
r := map[streamtypes.StreamID]*StreamPlayerHandler{}
for k, v := range sp.StreamPlayers {
r[k] = v
}
return r
})
},
)
}
const (
@@ -570,7 +588,10 @@ func (p *StreamPlayerHandler) controllerLoop(
errmon.ObserveErrorCtx(ctx, p.Close())
return
case <-getRestartChan:
logger.Debugf(ctx, "received a notification that the player should be restarted immediately")
logger.Debugf(
ctx,
"received a notification that the player should be restarted immediately",
)
restart()
return
case <-t.C:
@@ -580,9 +601,17 @@ func (p *StreamPlayerHandler) controllerLoop(
now := time.Now()
l, err := player.GetLength(ctx)
if err != nil {
logger.Errorf(ctx, "StreamPlayer[%s].controllerLoop: unable to get the current length: %v", p.StreamID, err)
logger.Errorf(
ctx,
"StreamPlayer[%s].controllerLoop: unable to get the current length: %v",
p.StreamID,
err,
)
if prevLength != 0 {
logger.Debugf(ctx, "previously GetLength worked, so it seems like the player died or something, restarting")
logger.Debugf(
ctx,
"previously GetLength worked, so it seems like the player died or something, restarting",
)
restart()
return
}
@@ -593,11 +622,25 @@ func (p *StreamPlayerHandler) controllerLoop(
pos, err := player.GetPosition(ctx)
if err != nil {
logger.Errorf(ctx, "StreamPlayer[%s].controllerLoop: unable to get the current position: %v", p.StreamID, err)
logger.Errorf(
ctx,
"StreamPlayer[%s].controllerLoop: unable to get the current position: %v",
p.StreamID,
err,
)
time.Sleep(time.Second)
return
}
logger.Tracef(ctx, "StreamPlayer[%s].controllerLoop: now == %v, posUpdatedAt == %v, len == %v; pos == %v; readTimeout == %v", p.StreamID, now, posUpdatedAt, l, pos, p.Config.ReadTimeout)
logger.Tracef(
ctx,
"StreamPlayer[%s].controllerLoop: now == %v, posUpdatedAt == %v, len == %v; pos == %v; readTimeout == %v",
p.StreamID,
now,
posUpdatedAt,
l,
pos,
p.Config.ReadTimeout,
)
if pos < 0 {
logger.Debugf(ctx, "negative position: %v", pos)
@@ -628,7 +671,11 @@ func (p *StreamPlayerHandler) controllerLoop(
if curSpeed == 1 {
return
}
logger.Debugf(ctx, "StreamPlayer[%s].controllerLoop: resetting the speed to 1", p.StreamID)
logger.Debugf(
ctx,
"StreamPlayer[%s].controllerLoop: resetting the speed to 1",
p.StreamID,
)
err := player.SetSpeed(ctx, 1)
if err != nil {
logger.Errorf(ctx, "unable to reset the speed to 1: %v", err)
@@ -643,23 +690,34 @@ func (p *StreamPlayerHandler) controllerLoop(
(lag.Seconds()-p.Config.JitterBufDuration.Seconds())/
(p.Config.MaxCatchupAtLag.Seconds()-p.Config.JitterBufDuration.Seconds())
speed = float64(uint(speed*10)) / 10 // to avoid flickering (for example between 1.0001 and 1.0)
speed = float64(
uint(speed*10),
) / 10 // to avoid flickering (for example between 1.0001 and 1.0)
if speed > p.Config.CatchupMaxSpeedFactor {
logger.Warnf(
ctx,
"speed is calculated higher than the maximum: %v > %v: (%v-1)*(%v-%v)/(%v-%v); lag calculation: %v - %v",
speed, p.Config.CatchupMaxSpeedFactor,
speed,
p.Config.CatchupMaxSpeedFactor,
lag.Seconds(), p.Config.JitterBufDuration.Seconds(),
p.Config.MaxCatchupAtLag.Seconds(), p.Config.JitterBufDuration.Seconds(),
l, pos,
p.Config.CatchupMaxSpeedFactor,
lag.Seconds(),
p.Config.JitterBufDuration.Seconds(),
p.Config.MaxCatchupAtLag.Seconds(),
p.Config.JitterBufDuration.Seconds(),
l,
pos,
)
speed = p.Config.CatchupMaxSpeedFactor
}
if speed != curSpeed {
logger.Debugf(ctx, "StreamPlayer[%s].controllerLoop: setting the speed to %v", p.StreamID, speed)
logger.Debugf(
ctx,
"StreamPlayer[%s].controllerLoop: setting the speed to %v",
p.StreamID,
speed,
)
err = player.SetSpeed(ctx, speed)
if err != nil {
logger.Errorf(ctx, "unable to set the speed to %v: %v", speed, err)

View File

@@ -184,7 +184,9 @@ func StreamsHandle(url string) (core.Producer, error) {
return rtmp.DialPlay(url)
}
func StreamsConsumerHandle(url string) (core.Consumer, types.NumBytesReaderWroter, func(context.Context) error, error) {
func StreamsConsumerHandle(
url string,
) (core.Consumer, types.NumBytesReaderWroter, func(context.Context) error, error) {
cons := flv.NewConsumer()
trafficCounter := &types.TrafficCounter{}
run := func(ctx context.Context) error {

View File

@@ -74,7 +74,12 @@ func (s *StreamServer) initNoLock(ctx context.Context) error {
for dstID, dstCfg := range cfg.Destinations {
err := s.addStreamDestination(ctx, dstID, dstCfg.URL)
if err != nil {
return fmt.Errorf("unable to initialize stream destination '%s' to %#+v: %w", dstID, dstCfg, err)
return fmt.Errorf(
"unable to initialize stream destination '%s' to %#+v: %w",
dstID,
dstCfg,
err,
)
}
}
@@ -88,7 +93,12 @@ func (s *StreamServer) initNoLock(ctx context.Context) error {
if !fwd.Disabled {
err := s.addStreamForward(ctx, streamID, dstID)
if err != nil {
return fmt.Errorf("unable to launch stream forward from '%s' to '%s': %w", streamID, dstID, err)
return fmt.Errorf(
"unable to launch stream forward from '%s' to '%s': %w",
streamID,
dstID,
err,
)
}
}
}
@@ -315,7 +325,11 @@ func (s *StreamServer) addStreamForward(
) error {
streamSrc := s.StreamHandler.Get(string(streamID))
if streamSrc == nil {
return fmt.Errorf("unable to find stream ID '%s', available stream IDs: %s", streamID, strings.Join(s.StreamHandler.GetAll(), ", "))
return fmt.Errorf(
"unable to find stream ID '%s', available stream IDs: %s",
streamID,
strings.Join(s.StreamHandler.GetAll(), ", "),
)
}
dst, err := s.findStreamDestinationByID(ctx, destinationID)
if err != nil {
@@ -418,7 +432,11 @@ func (s *StreamServer) listStreamForwards(
streamIDSrc := types.StreamID(name)
streamDst, err := s.findStreamDestinationByURL(ctx, fwd.URL)
if err != nil {
return nil, fmt.Errorf("unable to convert URL '%s' to a stream ID: %w", fwd.URL, err)
return nil, fmt.Errorf(
"unable to convert URL '%s' to a stream ID: %w",
fwd.URL,
err,
)
}
result = append(result, StreamForward{
StreamID: streamIDSrc,
@@ -467,7 +485,13 @@ func (s *StreamServer) removeStreamForward(
err = fwd.Close()
if err != nil {
return fmt.Errorf("unable to close forwarding from %s to %s (%s): %w", streamID, dstID, fwd.URL, err)
return fmt.Errorf(
"unable to close forwarding from %s to %s (%s): %w",
streamID,
dstID,
fwd.URL,
err,
)
}
stream.Cleanup()
return nil
@@ -562,7 +586,10 @@ func (s *StreamServer) findStreamDestinationByURL(
return dst, nil
}
}
return types.StreamDestination{}, fmt.Errorf("unable to find a stream destination by URL '%s'", url)
return types.StreamDestination{}, fmt.Errorf(
"unable to find a stream destination by URL '%s'",
url,
)
}
func (s *StreamServer) findStreamDestinationByID(
@@ -574,5 +601,8 @@ func (s *StreamServer) findStreamDestinationByID(
return dst, nil
}
}
return types.StreamDestination{}, fmt.Errorf("unable to find a stream destination by StreamID '%s'", destinationID)
return types.StreamDestination{}, fmt.Errorf(
"unable to find a stream destination by StreamID '%s'",
destinationID,
)
}

View File

@@ -112,7 +112,14 @@ type conn struct {
func (c *conn) appendDOT(dot []byte, group string) []byte {
host := c.host()
dot = fmt.Appendf(dot, "%s [group=host];\n", host)
dot = fmt.Appendf(dot, "%d [group=%s, label=%q, title=%q];\n", c.ID, group, c.FormatName, c.label())
dot = fmt.Appendf(
dot,
"%d [group=%s, label=%q, title=%q];\n",
c.ID,
group,
c.FormatName,
c.label(),
)
if group == "producer" {
dot = fmt.Appendf(dot, "%s -> %d [label=%q];\n", host, c.ID, humanBytes(c.BytesRecv))
} else {

View File

@@ -89,7 +89,9 @@ func (s *StreamHandler) HandleConsumerFunc(scheme string, handler ConsumerHandle
s.consumerHandlers[scheme] = handler
}
func (s *StreamHandler) GetConsumer(url string) (core.Consumer, types.NumBytesReaderWroter, func(context.Context) error, error) {
func (s *StreamHandler) GetConsumer(
url string,
) (core.Consumer, types.NumBytesReaderWroter, func(context.Context) error, error) {
if i := strings.IndexByte(url, ':'); i > 0 {
scheme := url[:i]

View File

@@ -65,7 +65,12 @@ func (s *StreamServer) init(ctx context.Context) error {
for dstID, dstCfg := range cfg.Destinations {
err := s.addStreamDestination(ctx, dstID, dstCfg.URL)
if err != nil {
return fmt.Errorf("unable to initialize stream destination '%s' to %#+v: %w", dstID, dstCfg, err)
return fmt.Errorf(
"unable to initialize stream destination '%s' to %#+v: %w",
dstID,
dstCfg,
err,
)
}
}
@@ -79,7 +84,12 @@ func (s *StreamServer) init(ctx context.Context) error {
if !fwd.Disabled {
_, err := s.addStreamForward(ctx, streamID, dstID, fwd.Quirks)
if err != nil {
return fmt.Errorf("unable to launch stream forward from '%s' to '%s': %w", streamID, dstID, err)
return fmt.Errorf(
"unable to launch stream forward from '%s' to '%s': %w",
streamID,
dstID,
err,
)
}
}
}
@@ -143,7 +153,11 @@ func (s *StreamServer) startServer(
observability.Go(ctx, func() {
err = portServer.Server.Serve(listener)
if err != nil {
err = fmt.Errorf("unable to start serving RTMP at '%s': %w", listener.Addr().String(), err)
err = fmt.Errorf(
"unable to start serving RTMP at '%s': %w",
listener.Addr().String(),
err,
)
logger.Error(ctx, err)
}
})
@@ -371,7 +385,15 @@ func (s *StreamServer) addStreamForward(
ctx = belt.WithField(ctx, "stream_forward", fmt.Sprintf("%s->%s", streamID, destinationID))
if actFwd, ok := s.ActiveStreamForwardings[destinationID]; ok {
return buildStreamForward(streamID, destinationID, cfg, actFwd), fmt.Errorf("there is already an active stream forwarding to '%s'", destinationID)
return buildStreamForward(
streamID,
destinationID,
cfg,
actFwd,
), fmt.Errorf(
"there is already an active stream forwarding to '%s'",
destinationID,
)
}
dst, err := s.findStreamDestinationByID(ctx, destinationID)
@@ -633,7 +655,10 @@ func (s *StreamServer) findStreamDestinationByID(
return dst, nil
}
}
return types.StreamDestination{}, fmt.Errorf("unable to find a stream destination by StreamID '%s'", destinationID)
return types.StreamDestination{}, fmt.Errorf(
"unable to find a stream destination by StreamID '%s'",
destinationID,
)
}
func (s *StreamServer) AddStreamPlayer(

View File

@@ -73,7 +73,11 @@ func (srv *RTMPServer) init(
}
}()
if (cfg.ServerCert != nil) != (cfg.ServerKey != nil) {
return fmt.Errorf("fields 'ServerCert' and 'ServerKey' should be used together (cur values: ServerCert == %#+v; ServerKey == %#+v)", cfg.ServerCert, cfg.ServerKey)
return fmt.Errorf(
"fields 'ServerCert' and 'ServerKey' should be used together (cur values: ServerCert == %#+v; ServerKey == %#+v)",
cfg.ServerCert,
cfg.ServerKey,
)
}
if cfg.ServerCert != nil && cfg.ServerKey != nil {
@@ -84,7 +88,10 @@ func (srv *RTMPServer) init(
}
if cfg.IsTLS && (srv.ServerCert == "" || srv.ServerKey == "") {
ctx := context.TODO()
logger.Warnf(ctx, "TLS is enabled, but no certificate is supplied, generating an ephemeral self-signed certificate") // TODO: implement the support of providing the certificates in the UI
logger.Warnf(
ctx,
"TLS is enabled, but no certificate is supplied, generating an ephemeral self-signed certificate",
) // TODO: implement the support of providing the certificates in the UI
err := srv.generateServerCertificate()
if err != nil {
return fmt.Errorf("unable to set the TLS certificate: %w", err)
@@ -173,7 +180,11 @@ func (srv *RTMPServer) setServerCertificate(
Bytes: cert.Raw,
}
if err := pem.Encode(certFile, certPEM); err != nil {
return fmt.Errorf("unable to write the server certificate to file '%s' in PEM format: %w", certFile.Name(), err)
return fmt.Errorf(
"unable to write the server certificate to file '%s' in PEM format: %w",
certFile.Name(),
err,
)
}
keyFile, err = os.CreateTemp("", "rtmps-server-certkey-*.pem")
@@ -192,7 +203,11 @@ func (srv *RTMPServer) setServerCertificate(
Bytes: keyBytes,
}
if err := pem.Encode(keyFile, privatePem); err != nil {
return fmt.Errorf("unable to write the server certificate to file '%s' in PEM format: %w", certFile.Name(), err)
return fmt.Errorf(
"unable to write the server certificate to file '%s' in PEM format: %w",
certFile.Name(),
err,
)
}
srv.ServerCert = certFile.Name()

View File

@@ -117,7 +117,13 @@ func (s *StreamServer) init(
s.mutex.Do(ctx, func() {
_, err := s.startServer(ctx, srv.Type, srv.Listen, srv.Options()...)
if err != nil {
logger.Errorf(ctx, "unable to initialize %s server at %s: %w", srv.Type, srv.Listen, err)
logger.Errorf(
ctx,
"unable to initialize %s server at %s: %w",
srv.Type,
srv.Listen,
err,
)
}
})
})
@@ -145,7 +151,11 @@ func (s *StreamServer) PathReady(path defs.Path) {
s.streamsStatusLocker.Do(context.Background(), func() {
publisher := s.publishers[appKey]
if publisher != nil {
logger.Errorf(ctx, "double-registration of a publisher for '%s' (this is an internal error in the code): %w", appKey)
logger.Errorf(
ctx,
"double-registration of a publisher for '%s' (this is an internal error in the code): %w",
appKey,
)
return
}
s.publishers[appKey] = newPublisherClosedNotifier()
@@ -386,7 +396,13 @@ func (s *StreamServer) WaitPublisherChan(
},
)
logger.Debugf(ctx, "WaitPublisherChan('%s', %v): publisher==%#+v", appKey, waitForNext, publisher)
logger.Debugf(
ctx,
"WaitPublisherChan('%s', %v): publisher==%#+v",
appKey,
waitForNext,
publisher,
)
if publisher != nil && publisher != curPublisher {
ch <- publisher
@@ -399,7 +415,12 @@ func (s *StreamServer) WaitPublisherChan(
logger.Debugf(ctx, "WaitPublisherChan('%s', %v): cancelled", appKey, waitForNext)
return
case <-waitCh:
logger.Debugf(ctx, "WaitPublisherChan('%s', %v): an event happened, rechecking", appKey, waitForNext)
logger.Debugf(
ctx,
"WaitPublisherChan('%s', %v): an event happened, rechecking",
appKey,
waitForNext,
)
}
}
})
@@ -433,13 +454,23 @@ func (s *StreamServer) startServer(
for _, portSrv := range s.serverHandlers {
if portSrv.ListenAddr() == listenAddr {
return nil, fmt.Errorf("we already have an port server %#+v instance at '%s'", portSrv, listenAddr)
return nil, fmt.Errorf(
"we already have an port server %#+v instance at '%s'",
portSrv,
listenAddr,
)
}
}
portSrv, err := s.newServer(ctx, serverType, listenAddr, opts...)
if err != nil {
return nil, fmt.Errorf("unable to initialize a new instance of a port server %s at %s with options %v: %w", serverType, listenAddr, opts, err)
return nil, fmt.Errorf(
"unable to initialize a new instance of a port server %s at %s with options %v: %w",
serverType,
listenAddr,
opts,
err,
)
}
logger.Tracef(ctx, "adding serverHandler %#+v %#+v", portSrv, portSrv.Config())

View File

@@ -66,7 +66,11 @@ func (h *Handler) OnCreateStream(timestamp uint32, cmd *rtmpmsg.NetConnectionCre
return nil
}
func (h *Handler) OnPublish(_ *rtmp.StreamContext, timestamp uint32, cmd *rtmpmsg.NetStreamPublish) error {
func (h *Handler) OnPublish(
_ *rtmp.StreamContext,
timestamp uint32,
cmd *rtmpmsg.NetStreamPublish,
) error {
log.Printf("OnPublish: %#v", cmd)
if h.sub != nil {
@@ -94,7 +98,11 @@ func (h *Handler) OnPublish(_ *rtmp.StreamContext, timestamp uint32, cmd *rtmpms
})
}
func (h *Handler) OnPlay(rtmpctx *rtmp.StreamContext, timestamp uint32, cmd *rtmpmsg.NetStreamPlay) error {
func (h *Handler) OnPlay(
rtmpctx *rtmp.StreamContext,
timestamp uint32,
cmd *rtmpmsg.NetStreamPlay,
) error {
if h.sub != nil {
return errors.New("Cannot play on this stream")
}

View File

@@ -93,7 +93,10 @@ func (pb *Pubsub) ClosedChan() <-chan struct{} {
const sendQueueLength = 2 * 60 * 10 // presumably it will give about 10 seconds of queue: 2 tracks * 60FPS * 30 seconds
func (pb *Pubsub) Sub(connCloser io.Closer, eventCallback func(context.Context, *flvtag.FlvTag) error) *Sub {
func (pb *Pubsub) Sub(
connCloser io.Closer,
eventCallback func(context.Context, *flvtag.FlvTag) error,
) *Sub {
ctx := context.TODO()
return xsync.DoR1(ctx, &pb.m, func() *Sub {
subID := pb.nextSubID
@@ -303,7 +306,11 @@ func (s *Sub) senderLoop(
case tag, ok := <-s.sendQueue:
if !ok {
logger.Debugf(ctx, "Sub[%d].senderLoop: the queue is closed; closing the client", s.subID)
logger.Debugf(
ctx,
"Sub[%d].senderLoop: the queue is closed; closing the client",
s.subID,
)
s.Close()
return
}
@@ -313,7 +320,12 @@ func (s *Sub) senderLoop(
err := s.onEvent(ctx, tag)
if err != nil {
metrics.FromCtx(ctx).Count("submit_process_error").Add(1)
logger.Errorf(ctx, "Sub[%d].senderLoop: unable to send an FLV tag: %v", s.subID, err)
logger.Errorf(
ctx,
"Sub[%d].senderLoop: unable to send an FLV tag: %v",
s.subID,
err,
)
} else {
metrics.FromCtx(ctx).Count("submit_process_success").Add(1)
}
@@ -331,7 +343,11 @@ func (s *Sub) Submit(
case s.sendQueue <- flv:
metrics.FromCtx(ctx).Count("submit_pushed").Add(1)
default:
logger.Errorf(ctx, "subscriber #%d queue is full, cannot send a tag; closing the connection, because cannot restore from this", s.subID)
logger.Errorf(
ctx,
"subscriber #%d queue is full, cannot send a tag; closing the connection, because cannot restore from this",
s.subID,
)
observability.Go(ctx, func() {
s.CloseOrLog(ctx)
})

View File

@@ -12,5 +12,9 @@ func NewStreamForwards(
s StreamServer,
platformsController types.PlatformsController,
) *StreamForwards {
return streamforward.NewStreamForwards(s, xaionarogortmp.NewRecoderFactory(), platformsController)
return streamforward.NewStreamForwards(
s,
xaionarogortmp.NewRecoderFactory(),
platformsController,
)
}

View File

@@ -88,7 +88,13 @@ func (s *StreamServer) init(
observability.Go(ctx, func() {
_, err := s.startServer(ctx, srv.Type, srv.Listen)
if err != nil {
logger.Errorf(ctx, "unable to initialize %s server at %s: %w", srv.Type, srv.Listen, err)
logger.Errorf(
ctx,
"unable to initialize %s server at %s: %w",
srv.Type,
srv.Listen,
err,
)
}
})
}
@@ -210,7 +216,11 @@ func (s *StreamServer) startServer(
OnConnect: func(conn net.Conn) (io.ReadWriteCloser, *rtmp.ConnConfig) {
ctx := belt.WithField(ctx, "client", conn.RemoteAddr().String())
h := yutoppgortmp.NewHandler(s.RelayService)
wrcc := types.NewReaderWriterCloseCounter(conn, &portSrv.ReadCount, &portSrv.WriteCount)
wrcc := types.NewReaderWriterCloseCounter(
conn,
&portSrv.ReadCount,
&portSrv.WriteCount,
)
return wrcc, &rtmp.ConnConfig{
Handler: h,
ControlState: rtmp.StreamControlStateConfig{
@@ -223,7 +233,11 @@ func (s *StreamServer) startServer(
observability.Go(ctx, func() {
err = portSrv.Serve(listener)
if err != nil {
err = fmt.Errorf("unable to start serving RTMP at '%s': %w", listener.Addr().String(), err)
err = fmt.Errorf(
"unable to start serving RTMP at '%s': %w",
listener.Addr().String(),
err,
)
logger.Error(ctx, err)
}
})

View File

@@ -52,9 +52,21 @@ func (fwds *StreamForwards) NewActiveStreamForward(
pauseFunc func(ctx context.Context, fwd *ActiveStreamForwarding),
opts ...Option,
) (_ret *ActiveStreamForwarding, _err error) {
logger.Debugf(ctx, "NewActiveStreamForward(ctx, '%s', '%s', relayService, pauseFunc)", streamID, urlString)
logger.Debugf(
ctx,
"NewActiveStreamForward(ctx, '%s', '%s', relayService, pauseFunc)",
streamID,
urlString,
)
defer func() {
logger.Debugf(ctx, "/NewActiveStreamForward(ctx, '%s', '%s', relayService, pauseFunc): %#+v %v", streamID, urlString, _ret, _err)
logger.Debugf(
ctx,
"/NewActiveStreamForward(ctx, '%s', '%s', relayService, pauseFunc): %#+v %v",
streamID,
urlString,
_ret,
_err,
)
}()
urlParsed, err := url.Parse(urlString)
@@ -176,7 +188,10 @@ func (fwd *ActiveStreamForwarding) waitForPublisherAndStart(
})
logger.Debugf(ctx, "DestinationStreamingLocker.Lock(ctx, '%s')", fwd.DestinationURL)
destinationUnlocker := fwd.StreamForwards.DestinationStreamingLocker.Lock(ctx, fwd.DestinationURL)
destinationUnlocker := fwd.StreamForwards.DestinationStreamingLocker.Lock(
ctx,
fwd.DestinationURL,
)
defer func() {
if destinationUnlocker != nil { // if ctx was cancelled before we locked then the unlocker is nil
destinationUnlocker.Unlock()
@@ -337,7 +352,12 @@ func (fwd *ActiveStreamForwarding) openOutputFor(
ctx context.Context,
recoderInstance recoder.Recoder,
) (recoder.Output, error) {
output, err := recoderInstance.NewOutputFromURL(ctx, fwd.DestinationURL.String(), fwd.DestinationStreamKey, recoder.OutputConfig{})
output, err := recoderInstance.NewOutputFromURL(
ctx,
fwd.DestinationURL.String(),
fwd.DestinationStreamKey,
recoder.OutputConfig{},
)
if err != nil {
return nil, fmt.Errorf("unable to open '%s' as the output: %w", fwd.DestinationURL, err)
}

View File

@@ -72,7 +72,12 @@ func (s *StreamForwards) init(
for dstID, dstCfg := range cfg.Destinations {
err := s.addActiveStreamDestination(ctx, dstID, dstCfg.URL, dstCfg.StreamKey)
if err != nil {
_ret = fmt.Errorf("unable to initialize stream destination '%s' to %#+v: %w", dstID, dstCfg, err)
_ret = fmt.Errorf(
"unable to initialize stream destination '%s' to %#+v: %w",
dstID,
dstCfg,
err,
)
return
}
}
@@ -84,7 +89,12 @@ func (s *StreamForwards) init(
}
_, err := s.newActiveStreamForward(ctx, streamID, dstID, fwd.Quirks)
if err != nil {
_ret = fmt.Errorf("unable to launch stream forward from '%s' to '%s': %w", streamID, dstID, err)
_ret = fmt.Errorf(
"unable to launch stream forward from '%s' to '%s': %w",
streamID,
dstID,
err,
)
return
}
}
@@ -187,7 +197,10 @@ func (s *StreamForwards) newActiveStreamForward(
DestinationID: destinationID,
}
if _, ok := s.ActiveStreamForwardings[key]; ok {
return nil, fmt.Errorf("there is already an active stream forwarding to '%s'", destinationID)
return nil, fmt.Errorf(
"there is already an active stream forwarding to '%s'",
destinationID,
)
}
dst, err := s.findStreamDestinationByID(ctx, destinationID)
@@ -227,7 +240,10 @@ func (s *StreamForwards) newActiveStreamForward(
) {
if quirks.StartAfterYoutubeRecognizedStream.Enabled {
if quirks.RestartUntilYoutubeRecognizesStream.Enabled {
logger.Errorf(ctx, "StartAfterYoutubeRecognizedStream should not be used together with RestartUntilYoutubeRecognizesStream")
logger.Errorf(
ctx,
"StartAfterYoutubeRecognizedStream should not be used together with RestartUntilYoutubeRecognizesStream",
)
} else {
logger.Debugf(ctx, "fwd %s->%s is waiting for YouTube to recognize the stream", streamID, destinationID)
started, err := s.PlatformsController.CheckStreamStartedByPlatformID(
@@ -284,13 +300,21 @@ func (s *StreamForwards) restartUntilYoutubeRecognizesStream(
cfg types.RestartUntilYoutubeRecognizesStream,
) {
ctx = belt.WithField(ctx, "module", "restartUntilYoutubeRecognizesStream")
ctx = belt.WithField(ctx, "stream_forward", fmt.Sprintf("%s->%s", fwd.StreamID, fwd.DestinationID))
ctx = belt.WithField(
ctx,
"stream_forward",
fmt.Sprintf("%s->%s", fwd.StreamID, fwd.DestinationID),
)
logger.Debugf(ctx, "restartUntilYoutubeRecognizesStream(ctx, %#+v, %#+v)", fwd, cfg)
defer func() { logger.Debugf(ctx, "restartUntilYoutubeRecognizesStream(ctx, %#+v, %#+v)", fwd, cfg) }()
if !cfg.Enabled {
logger.Errorf(ctx, "an attempt to start restartUntilYoutubeRecognizesStream when the hack is disabled for this stream forwarder: %#+v", cfg)
logger.Errorf(
ctx,
"an attempt to start restartUntilYoutubeRecognizesStream when the hack is disabled for this stream forwarder: %#+v",
cfg,
)
return
}
@@ -316,21 +340,39 @@ func (s *StreamForwards) restartUntilYoutubeRecognizesStream(
return
case <-time.After(cfg.StartTimeout):
}
logger.Debugf(ctx, "waited %v, checking if the remote platform accepted the stream", cfg.StartTimeout)
logger.Debugf(
ctx,
"waited %v, checking if the remote platform accepted the stream",
cfg.StartTimeout,
)
for {
streamOK, err := s.PlatformsController.CheckStreamStartedByPlatformID(
memoize.SetNoCache(ctx, true),
youtube.ID,
)
logger.Debugf(ctx, "the result of checking the stream on the remote platform: %v %v", streamOK, err)
logger.Debugf(
ctx,
"the result of checking the stream on the remote platform: %v %v",
streamOK,
err,
)
if err != nil {
logger.Errorf(ctx, "unable to check if the stream with URL '%s' is started: %v", fwd.ActiveForwarding.DestinationURL, err)
logger.Errorf(
ctx,
"unable to check if the stream with URL '%s' is started: %v",
fwd.ActiveForwarding.DestinationURL,
err,
)
time.Sleep(time.Second)
continue
}
if streamOK {
logger.Debugf(ctx, "waiting %v to recheck if the stream will be still OK", cfg.StopStartDelay)
logger.Debugf(
ctx,
"waiting %v to recheck if the stream will be still OK",
cfg.StopStartDelay,
)
select {
case <-ctx.Done():
return
@@ -340,9 +382,19 @@ func (s *StreamForwards) restartUntilYoutubeRecognizesStream(
memoize.SetNoCache(ctx, true),
youtube.ID,
)
logger.Debugf(ctx, "the result of checking the stream on the remote platform: %v %v", streamOK, err)
logger.Debugf(
ctx,
"the result of checking the stream on the remote platform: %v %v",
streamOK,
err,
)
if err != nil {
logger.Errorf(ctx, "unable to check if the stream with URL '%s' is started: %v", fwd.ActiveForwarding.DestinationURL, err)
logger.Errorf(
ctx,
"unable to check if the stream with URL '%s' is started: %v",
fwd.ActiveForwarding.DestinationURL,
err,
)
time.Sleep(time.Second)
continue
}
@@ -353,7 +405,10 @@ func (s *StreamForwards) restartUntilYoutubeRecognizesStream(
break
}
logger.Infof(ctx, "the remote platform still does not see the stream, restarting the stream forwarding: stopping...")
logger.Infof(
ctx,
"the remote platform still does not see the stream, restarting the stream forwarding: stopping...",
)
err := fwd.ActiveForwarding.Stop()
if err != nil {
@@ -366,7 +421,10 @@ func (s *StreamForwards) restartUntilYoutubeRecognizesStream(
case <-time.After(cfg.StopStartDelay):
}
logger.Infof(ctx, "the remote platform still does not see the stream, restarting the stream forwarding: starting...")
logger.Infof(
ctx,
"the remote platform still does not see the stream, restarting the stream forwarding: starting...",
)
err = fwd.ActiveForwarding.Start(ctx)
if err != nil {
@@ -447,9 +505,12 @@ func (s *StreamForwards) ListStreamForwards(
}()
return xsync.DoR2(ctx, &s.Mutex, func() ([]StreamForward, error) {
return s.getStreamForwards(ctx, func(si types.StreamID, di ordered.Optional[types.DestinationID]) bool {
return s.getStreamForwards(
ctx,
func(si types.StreamID, di ordered.Optional[types.DestinationID]) bool {
return true
})
},
)
})
}
@@ -486,7 +547,12 @@ func (s *StreamForwards) getStreamForwards(
if !filterFunc(streamID, ordered.Optional[types.DestinationID]{}) {
continue
}
logger.Tracef(ctx, "len(s.Config.Streams[%s].Forwardings) == %d", streamID, len(stream.Forwardings))
logger.Tracef(
ctx,
"len(s.Config.Streams[%s].Forwardings) == %d",
streamID,
len(stream.Forwardings),
)
for dstID, cfg := range stream.Forwardings {
if !filterFunc(streamID, ordered.Opt(dstID)) {
continue
@@ -589,9 +655,12 @@ func (s *StreamForwards) GetStreamForwardsByDestination(
}()
return xsync.DoR2(ctx, &s.Mutex, func() ([]StreamForward, error) {
return s.getStreamForwards(ctx, func(streamID types.StreamID, dstID ordered.Optional[types.DestinationID]) bool {
return s.getStreamForwards(
ctx,
func(streamID types.StreamID, dstID ordered.Optional[types.DestinationID]) bool {
return !dstID.IsSet() || dstID.Get() == destID
})
},
)
})
}
@@ -647,7 +716,15 @@ func (s *StreamForwards) UpdateStreamDestination(
streamKey string,
) error {
ctx = belt.WithField(ctx, "module", "StreamServer")
return xsync.DoA4R1(ctx, &s.Mutex, s.updateStreamDestination, ctx, destinationID, url, streamKey)
return xsync.DoA4R1(
ctx,
&s.Mutex,
s.updateStreamDestination,
ctx,
destinationID,
url,
streamKey,
)
}
func (s *StreamForwards) updateStreamDestination(
@@ -659,14 +736,20 @@ func (s *StreamForwards) updateStreamDestination(
s.WithConfig(ctx, func(ctx context.Context, cfg *types.Config) {
for key := range s.ActiveStreamForwardings {
if key.DestinationID == destinationID {
_ret = fmt.Errorf("there is already an active stream forwarding to '%s'", destinationID)
_ret = fmt.Errorf(
"there is already an active stream forwarding to '%s'",
destinationID,
)
return
}
}
err := s.removeActiveStreamDestination(ctx, destinationID)
if err != nil {
_ret = fmt.Errorf("unable to remove (to then re-add) the active stream destination: %w", err)
_ret = fmt.Errorf(
"unable to remove (to then re-add) the active stream destination: %w",
err,
)
return
}
@@ -725,9 +808,12 @@ func (s *StreamForwards) removeActiveStreamDestination(
ctx context.Context,
destinationID types.DestinationID,
) error {
streamForwards, err := s.getStreamForwards(ctx, func(si types.StreamID, di ordered.Optional[types.DestinationID]) bool {
streamForwards, err := s.getStreamForwards(
ctx,
func(si types.StreamID, di ordered.Optional[types.DestinationID]) bool {
return true
})
},
)
if err != nil {
return fmt.Errorf("unable to list stream forwardings: %w", err)
}
@@ -756,7 +842,10 @@ func (s *StreamForwards) findStreamDestinationByID(
return dst, nil
}
}
return types.StreamDestination{}, fmt.Errorf("unable to find a stream destination by StreamID '%s'", destinationID)
return types.StreamDestination{}, fmt.Errorf(
"unable to find a stream destination by StreamID '%s'",
destinationID,
)
}
func (s *StreamForwards) getLocalhostEndpoint(ctx context.Context) (*url.URL, error) {

View File

@@ -362,7 +362,10 @@ func (s *StreamPlayers) setStreamPlayer(
var opts SetupOptions
if playerCfg.DefaultStreamPlayerOptions != nil {
opts = append(opts, SetupOptionDefaultStreamPlayerOptions(playerCfg.DefaultStreamPlayerOptions))
opts = append(
opts,
SetupOptionDefaultStreamPlayerOptions(playerCfg.DefaultStreamPlayerOptions),
)
}
var streamCfg map[types.StreamID]*types.StreamConfig

View File

@@ -9,5 +9,8 @@ import (
type PlatformsController interface {
CheckStreamStartedByURL(ctx context.Context, destination *url.URL) (bool, error)
CheckStreamStartedByPlatformID(ctx context.Context, platID streamcontrol.PlatformName) (bool, error)
CheckStreamStartedByPlatformID(
ctx context.Context,
platID streamcontrol.PlatformName,
) (bool, error)
}