diff --git a/cmd/streamcli/commands/commands.go b/cmd/streamcli/commands/commands.go index 5633618..2ac96f8 100644 --- a/cmd/streamcli/commands/commands.go +++ b/cmd/streamcli/commands/commands.go @@ -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) } } diff --git a/cmd/streamctl/commands/commands.go b/cmd/streamctl/commands/commands.go index 0c1c504..64943ca 100644 --- a/cmd/streamctl/commands/commands.go +++ b/cmd/streamctl/commands/commands.go @@ -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) diff --git a/cmd/streamd/main.go b/cmd/streamd/main.go index b01d908..cae0a97 100644 --- a/cmd/streamd/main.go +++ b/cmd/streamd/main.go @@ -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") diff --git a/cmd/streamd/ui/ui.go b/cmd/streamd/ui/ui.go index 03c0121..5933384 100644 --- a/cmd/streamd/ui/ui.go +++ b/cmd/streamd/ui/ui.go @@ -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) diff --git a/cmd/streampanel/flag.go b/cmd/streampanel/flag.go index b8aae17..6dfa5db 100644 --- a/cmd/streampanel/flag.go +++ b/cmd/streampanel/flag.go @@ -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 diff --git a/cmd/streampanel/flag_android.go b/cmd/streampanel/flag_android.go index 4762687..d34fb87 100644 --- a/cmd/streampanel/flag_android.go +++ b/cmd/streampanel/flag_android.go @@ -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, + ) } diff --git a/cmd/streampanel/fork.go b/cmd/streampanel/fork.go index edcf7f2..389111d 100644 --- a/cmd/streampanel/fork.go +++ b/cmd/streampanel/fork.go @@ -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 }, diff --git a/cmd/streampanel/main.go b/cmd/streampanel/main.go index 955e375..33ce3b1 100644 --- a/cmd/streampanel/main.go +++ b/cmd/streampanel/main.go @@ -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( diff --git a/cmd/streampanel/runtime.go b/cmd/streampanel/runtime.go index c407fc1..b4e94be 100644 --- a/cmd/streampanel/runtime.go +++ b/cmd/streampanel/runtime.go @@ -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)) diff --git a/cmd/streampanel/signal_handler.go b/cmd/streampanel/signal_handler.go index 0d7c921..81c440c 100644 --- a/cmd/streampanel/signal_handler.go +++ b/cmd/streampanel/signal_handler.go @@ -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 diff --git a/cmd/streampanel/streamd.go b/cmd/streampanel/streamd.go index acf85ee..b67c371 100644 --- a/cmd/streampanel/streamd.go +++ b/cmd/streampanel/streamd.go @@ -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") diff --git a/pkg/mainprocess/main_process.go b/pkg/mainprocess/main_process.go index 2e92a0c..7ad7359 100644 --- a/pkg/mainprocess/main_process.go +++ b/pkg/mainprocess/main_process.go @@ -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,23 +453,37 @@ func (m *Manager) waitForReadyProcess( } for { - conn, ch, isReady := xsync.DoR3(ctx, &m.connsLocker, func() (net.Conn, chan struct{}, bool) { - readyMap := m.childReadyFor[name] - isReady := false - if readyMap != nil { - if _, ok := readyMap[msgType]; ok { - isReady = true + conn, ch, isReady := xsync.DoR3( + ctx, + &m.connsLocker, + func() (net.Conn, chan struct{}, bool) { + readyMap := m.childReadyFor[name] + isReady := false + if readyMap != nil { + if _, ok := readyMap[msgType]; ok { + isReady = true + } } - } - return m.conns[name], m.connsChanged, isReady - }) + 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 { diff --git a/pkg/mainprocess/unit_test.go b/pkg/mainprocess/unit_test.go index 4ac97a8..221a1aa 100644 --- a/pkg/mainprocess/unit_test.go +++ b/pkg/mainprocess/unit_test.go @@ -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 { - handleCall("child0", content) - return nil - }) + 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 { - handleCall("child1", content) - return nil - }) + 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()) diff --git a/pkg/oauthhandler/oauth2_handler.go b/pkg/oauthhandler/oauth2_handler.go index 0013274..1d91871 100644 --- a/pkg/oauthhandler/oauth2_handler.go +++ b/pkg/oauthhandler/oauth2_handler.go @@ -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, + ) }), } diff --git a/pkg/observability/error_monitor_hook.go b/pkg/observability/error_monitor_hook.go index b6068ac..e5fb915 100644 --- a/pkg/observability/error_monitor_hook.go +++ b/pkg/observability/error_monitor_hook.go @@ -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, diff --git a/pkg/player/cmd/player/main.go b/pkg/player/cmd/player/main.go index a33b4f6..2fe10ba 100644 --- a/pkg/player/cmd/player/main.go +++ b/pkg/player/cmd/player/main.go @@ -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) diff --git a/pkg/player/mpv.go b/pkg/player/mpv.go index aa3e3db..91d86eb 100644 --- a/pkg/player/mpv.go +++ b/pkg/player/mpv.go @@ -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() diff --git a/pkg/recoder/.wip/livego/recoder.go b/pkg/recoder/.wip/livego/recoder.go index 1501b5f..e1d3053 100644 --- a/pkg/recoder/.wip/livego/recoder.go +++ b/pkg/recoder/.wip/livego/recoder.go @@ -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 diff --git a/pkg/recoder/libav/recoder/output.go b/pkg/recoder/libav/recoder/output.go index 5751861..95db080 100644 --- a/pkg/recoder/libav/recoder/output.go +++ b/pkg/recoder/libav/recoder/output.go @@ -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)) } diff --git a/pkg/recoder/libav/recoder/recoder.go b/pkg/recoder/libav/recoder/recoder.go index f2a14d0..bfdd0e1 100644 --- a/pkg/recoder/libav/recoder/recoder.go +++ b/pkg/recoder/libav/recoder/recoder.go @@ -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 } diff --git a/pkg/recoder/libav/saferecoder/process/server/server.go b/pkg/recoder/libav/saferecoder/process/server/server.go index 25bba8d..622f756 100644 --- a/pkg/recoder/libav/saferecoder/process/server/server.go +++ b/pkg/recoder/libav/saferecoder/process/server/server.go @@ -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 { diff --git a/pkg/recoder/xaionaro-go-rtmp/input.go b/pkg/recoder/xaionaro-go-rtmp/input.go index a24b330..00afb4f 100644 --- a/pkg/recoder/xaionaro-go-rtmp/input.go +++ b/pkg/recoder/xaionaro-go-rtmp/input.go @@ -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") diff --git a/pkg/recoder/xaionaro-go-rtmp/new_rtmp_client.go b/pkg/recoder/xaionaro-go-rtmp/new_rtmp_client.go index 66367d9..da1bc24 100644 --- a/pkg/recoder/xaionaro-go-rtmp/new_rtmp_client.go +++ b/pkg/recoder/xaionaro-go-rtmp/new_rtmp_client.go @@ -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()) diff --git a/pkg/recoder/xaionaro-go-rtmp/recoder.go b/pkg/recoder/xaionaro-go-rtmp/recoder.go index 082a34f..bda13bf 100644 --- a/pkg/recoder/xaionaro-go-rtmp/recoder.go +++ b/pkg/recoder/xaionaro-go-rtmp/recoder.go @@ -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) } diff --git a/pkg/repository/git.go b/pkg/repository/git.go index 582f222..9a79bf0 100644 --- a/pkg/repository/git.go +++ b/pkg/repository/git.go @@ -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{ - All: true, - AllowEmptyCommits: false, - Author: signature, - Committer: signature, - }) + 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) diff --git a/pkg/screenshot/screenshot.go b/pkg/screenshot/screenshot.go index 7fc090e..b42634d 100644 --- a/pkg/screenshot/screenshot.go +++ b/pkg/screenshot/screenshot.go @@ -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)) diff --git a/pkg/streamcontrol/config.go b/pkg/streamcontrol/config.go index 6e9f55e..fbb416b 100644 --- a/pkg/streamcontrol/config.go +++ b/pkg/streamcontrol/config.go @@ -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 diff --git a/pkg/streamcontrol/error.go b/pkg/streamcontrol/error.go index 4111b70..561fb20 100644 --- a/pkg/streamcontrol/error.go +++ b/pkg/streamcontrol/error.go @@ -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 { diff --git a/pkg/streamcontrol/obs/obs.go b/pkg/streamcontrol/obs/obs.go index 637c0f2..c520116 100644 --- a/pkg/streamcontrol/obs/obs.go +++ b/pkg/streamcontrol/obs/obs.go @@ -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{ diff --git a/pkg/streamcontrol/obs/types/action/action.go b/pkg/streamcontrol/obs/types/action/action.go index d2dab32..e522a90 100644 --- a/pkg/streamcontrol/obs/types/action/action.go +++ b/pkg/streamcontrol/obs/types/action/action.go @@ -12,16 +12,16 @@ type Action interface { type ValueExpression string type ElementShowHide struct { - ElementName *string `yaml:"element_name,omitempty" json:"element_name,omitempty"` - ElementUUID *string `yaml:"element_uuid,omitempty" json:"element_uuid,omitempty"` + ElementName *string `yaml:"element_name,omitempty" json:"element_name,omitempty"` + ElementUUID *string `yaml:"element_uuid,omitempty" json:"element_uuid,omitempty"` ValueExpression ValueExpression `yaml:"value_expression,omitempty" json:"value_expression,omitempty"` } func (ElementShowHide) isAction() {} type WindowCaptureSetSource struct { - ElementName *string `yaml:"element_name,omitempty" json:"element_name,omitempty"` - ElementUUID *string `yaml:"element_uuid,omitempty" json:"element_uuid,omitempty"` + ElementName *string `yaml:"element_name,omitempty" json:"element_name,omitempty"` + ElementUUID *string `yaml:"element_uuid,omitempty" json:"element_uuid,omitempty"` ValueExpression ValueExpression `yaml:"value_expression,omitempty" json:"value_expression,omitempty"` } diff --git a/pkg/streamcontrol/obs/types/scene_rule.go b/pkg/streamcontrol/obs/types/scene_rule.go index 3c261bd..60ec6fc 100644 --- a/pkg/streamcontrol/obs/types/scene_rule.go +++ b/pkg/streamcontrol/obs/types/scene_rule.go @@ -14,8 +14,8 @@ type SceneRules []SceneRule type SceneRule struct { Description string `yaml:"description,omitempty" json:"description,omitempty"` - TriggerQuery trigger.Query `yaml:"trigger" json:"trigger"` - Action action.Action `yaml:"action" json:"action"` + TriggerQuery trigger.Query `yaml:"trigger" json:"trigger"` + Action action.Action `yaml:"action" json:"action"` } func (sr *SceneRule) UnmarshalYAML(b []byte) (_err error) { @@ -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) @@ -105,6 +123,6 @@ func (sr SceneRule) MarshalYAML() (b []byte, _err error) { type serializableSceneRule struct { Description string `yaml:"description,omitempty" json:"description,omitempty"` - Trigger map[string]any `yaml:"trigger" json:"trigger"` - Action map[string]any `yaml:"action" json:"action"` + Trigger map[string]any `yaml:"trigger" json:"trigger"` + Action map[string]any `yaml:"action" json:"action"` } diff --git a/pkg/streamcontrol/obs/types/trigger/query.go b/pkg/streamcontrol/obs/types/trigger/query.go index 508113c..339aa0f 100644 --- a/pkg/streamcontrol/obs/types/trigger/query.go +++ b/pkg/streamcontrol/obs/types/trigger/query.go @@ -14,10 +14,10 @@ type Not struct { } type WindowFocusChange struct { - WindowID *uint64 `yaml:"window_id,omitempty" json:"window_id,omitempty"` - WindowTitle *string `yaml:"window_title,omitempty" json:"window_title,omitempty"` + WindowID *uint64 `yaml:"window_id,omitempty" json:"window_id,omitempty"` + WindowTitle *string `yaml:"window_title,omitempty" json:"window_title,omitempty"` WindowTitlePartial *string `yaml:"window_title_partial,omitempty" json:"window_title_partial,omitempty"` - UserID *uint64 `yaml:"user_id,omitempty" json:"user_id,omitempty"` + UserID *uint64 `yaml:"user_id,omitempty" json:"user_id,omitempty"` //lint:ignore U1000 this field is used by reflection uiComment struct{} `uicomment:"This action will also add field .IsFocused to the event."` diff --git a/pkg/streamcontrol/stream_control.go b/pkg/streamcontrol/stream_control.go index c22a6ed..3de4e86 100644 --- a/pkg/streamcontrol/stream_control.go +++ b/pkg/streamcontrol/stream_control.go @@ -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 { diff --git a/pkg/streamcontrol/twitch/twitch.go b/pkg/streamcontrol/twitch/twitch.go index 4e34d95..8d8bc47 100644 --- a/pkg/streamcontrol/twitch/twitch.go +++ b/pkg/streamcontrol/twitch/twitch.go @@ -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") diff --git a/pkg/streamcontrol/youtube/youtube.go b/pkg/streamcontrol/youtube/youtube.go index 6ee44c8..f897bce 100644 --- a/pkg/streamcontrol/youtube/youtube.go +++ b/pkg/streamcontrol/youtube/youtube.go @@ -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 { diff --git a/pkg/streamd/api/streamd.go b/pkg/streamd/api/streamd.go index f53737b..55cb460 100644 --- a/pkg/streamd/api/streamd.go +++ b/pkg/streamd/api/streamd.go @@ -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) } diff --git a/pkg/streamd/config/config.go b/pkg/streamd/config/config.go index a97e025..8e28e79 100644 --- a/pkg/streamd/config/config.go +++ b/pkg/streamd/config/config.go @@ -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 } diff --git a/pkg/streamd/config/monitor_filter.go b/pkg/streamd/config/monitor_filter.go index 6d84834..f01cfc9 100644 --- a/pkg/streamd/config/monitor_filter.go +++ b/pkg/streamd/config/monitor_filter.go @@ -31,7 +31,7 @@ type Filter interface { type FilterColor struct { Brightness float64 `yaml:"brightness" json:"brightness"` - Opacity float64 `yaml:"opacity" json:"opacity"` + Opacity float64 `yaml:"opacity" json:"opacity"` } func (f *FilterColor) Filter( @@ -88,7 +88,7 @@ func (f *FilterColor) MonitorFilterType() MonitorFilterType { } type serializableFilter struct { - Type MonitorFilterType `yaml:"type" json:"type"` + Type MonitorFilterType `yaml:"type" json:"type"` Config map[string]any `yaml:"config,omitempty" json:"config"` } diff --git a/pkg/streamd/config/monitor_source.go b/pkg/streamd/config/monitor_source.go index c9bc04d..67b6ac6 100644 --- a/pkg/streamd/config/monitor_source.go +++ b/pkg/streamd/config/monitor_source.go @@ -89,10 +89,10 @@ func (d *Duration) UnmarshalJSON(b []byte) error { } type MonitorSourceOBSVideo struct { - Name string `yaml:"name" json:"name"` - Width float64 `yaml:"width" json:"width"` - Height float64 `yaml:"height" json:"height"` - ImageFormat ImageFormat `yaml:"image_format" json:"image_format"` + Name string `yaml:"name" json:"name"` + Width float64 `yaml:"width" json:"width"` + Height float64 `yaml:"height" json:"height"` + ImageFormat ImageFormat `yaml:"image_format" json:"image_format"` UpdateInterval Duration `yaml:"update_interval" json:"update_interval"` } @@ -166,24 +166,40 @@ 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 } type MonitorSourceOBSVolume struct { - Name string `yaml:"name" json:"name"` + Name string `yaml:"name" json:"name"` UpdateInterval Duration `yaml:"update_interval" json:"update_interval"` - ColorActive string `yaml:"color_active" json:"color_active"` - ColorPassive string `yaml:"color_passive" json:"color_passive"` + ColorActive string `yaml:"color_active" json:"color_active"` + ColorPassive string `yaml:"color_passive" json:"color_passive"` } var _ Source = (*MonitorSourceOBSVolume)(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() diff --git a/pkg/streamd/config/read.go b/pkg/streamd/config/read.go index 8e7b5a3..bde70ea 100644 --- a/pkg/streamd/config/read.go +++ b/pkg/streamd/config/read.go @@ -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) } diff --git a/pkg/streamd/git_storage.go b/pkg/streamd/git_storage.go index 068cd3b..9093319 100644 --- a/pkg/streamd/git_storage.go +++ b/pkg/streamd/git_storage.go @@ -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) diff --git a/pkg/streamd/memoize/memoize.go b/pkg/streamd/memoize/memoize.go index bd1677d..3892ffc 100644 --- a/pkg/streamd/memoize/memoize.go +++ b/pkg/streamd/memoize/memoize.go @@ -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) diff --git a/pkg/streamd/server/grpc.go b/pkg/streamd/server/grpc.go index 3f6446f..8fb187d 100644 --- a/pkg/streamd/server/grpc.go +++ b/pkg/streamd/server/grpc.go @@ -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( @@ -921,9 +952,13 @@ func (grpc *GRPCServer) AddStreamForward( cfg.Enabled, api.StreamForwardingQuirks{ RestartUntilYoutubeRecognizesStream: api.RestartUntilYoutubeRecognizesStream{ - Enabled: cfg.Quirks.RestartUntilYoutubeRecognizesStream.Enabled, - StartTimeout: sec2dur(cfg.Quirks.RestartUntilYoutubeRecognizesStream.StartTimeout), - StopStartDelay: sec2dur(cfg.Quirks.RestartUntilYoutubeRecognizesStream.StopStartDelay), + Enabled: cfg.Quirks.RestartUntilYoutubeRecognizesStream.Enabled, + StartTimeout: sec2dur( + cfg.Quirks.RestartUntilYoutubeRecognizesStream.StartTimeout, + ), + StopStartDelay: sec2dur( + cfg.Quirks.RestartUntilYoutubeRecognizesStream.StopStartDelay, + ), }, StartAfterYoutubeRecognizedStream: api.StartAfterYoutubeRecognizedStream{ Enabled: cfg.Quirks.StartAfterYoutubeRecognizedStream.Enabled, @@ -948,9 +983,13 @@ func (grpc *GRPCServer) UpdateStreamForward( cfg.Enabled, api.StreamForwardingQuirks{ RestartUntilYoutubeRecognizesStream: api.RestartUntilYoutubeRecognizesStream{ - Enabled: cfg.Quirks.RestartUntilYoutubeRecognizesStream.Enabled, - StartTimeout: sec2dur(cfg.Quirks.RestartUntilYoutubeRecognizesStream.StartTimeout), - StopStartDelay: sec2dur(cfg.Quirks.RestartUntilYoutubeRecognizesStream.StopStartDelay), + Enabled: cfg.Quirks.RestartUntilYoutubeRecognizesStream.Enabled, + 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 } diff --git a/pkg/streamd/stream_controller.go b/pkg/streamd/stream_controller.go index e1f3719..cc6ac42 100644 --- a/pkg/streamd/stream_controller.go +++ b/pkg/streamd/stream_controller.go @@ -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) diff --git a/pkg/streamd/stream_forward.go b/pkg/streamd/stream_forward.go index c4064e9..9315450 100644 --- a/pkg/streamd/stream_forward.go +++ b/pkg/streamd/stream_forward.go @@ -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) } diff --git a/pkg/streamd/streamd.go b/pkg/streamd/streamd.go index 6bf53be..37963c3 100644 --- a/pkg/streamd/streamd.go +++ b/pkg/streamd/streamd.go @@ -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 diff --git a/pkg/streampanel/error.go b/pkg/streampanel/error.go index b05e1dc..3cdd622 100644 --- a/pkg/streampanel/error.go +++ b/pkg/streampanel/error.go @@ -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() diff --git a/pkg/streampanel/git_storage.go b/pkg/streampanel/git_storage.go index 4cc7932..78168dd 100644 --- a/pkg/streampanel/git_storage.go +++ b/pkg/streampanel/git_storage.go @@ -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( diff --git a/pkg/streampanel/monitor.go b/pkg/streampanel/monitor.go index 94ae78f..e39d3ce 100644 --- a/pkg/streampanel/monitor.go +++ b/pkg/streampanel/monitor.go @@ -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, diff --git a/pkg/streampanel/panel.go b/pkg/streampanel/panel.go index 930b4a2..e8267b6 100644 --- a/pkg/streampanel/panel.go +++ b/pkg/streampanel/panel.go @@ -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/)") + channelField.SetPlaceHolder( + "channel ID (copy&paste it from the browser: https://www.twitch.tv/)", + ) 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.onSetupStreamButton(ctx) - }) + p.setupStreamButton = widget.NewButtonWithIcon( + setupStreamString(), + theme.SettingsIcon(), + func() { + p.onSetupStreamButton(ctx) + }, + ) p.setupStreamButton.Disable() - p.startStopButton = widget.NewButtonWithIcon(startStreamString(), theme.MediaRecordIcon(), func() { - p.onStartStopButton(ctx) - }) + 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() { - additionalButtonInfo.Callback(t, selectedTagsOrdered()) - }) + 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() { - twitchCategory.OnSubmitted(catName) - }) + 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() { - selectedTwitchCategoryBox.Remove(selectedTwitchCategoryContainer) - twitchProfile.CategoryName = nil - }) + 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() { - youtubeTemplate.OnSubmitted(recName) - }) + 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() { - selectedYoutubeBroadcastBox.Remove(selectedYoutubeBroadcastContainer) - youtubeProfile.TemplateBroadcastIDs = youtubeProfile.TemplateBroadcastIDs[:0] - }) + 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,18 +3121,21 @@ 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) { - switch s { - case "ignore": - youtubeProfile.TemplateTags = youtube.TemplateTagsIgnore - case "use as primary": - youtubeProfile.TemplateTags = youtube.TemplateTagsUseAsPrimary - case "use as additional": - youtubeProfile.TemplateTags = youtube.TemplateTagsUseAsAdditional - default: - p.DisplayError(fmt.Errorf("unexpected new value of 'template tags': '%s'", s)) - } - }) + templateTags := widget.NewSelect( + []string{"ignore", "use as primary", "use as additional"}, + func(s string) { + switch s { + case "ignore": + youtubeProfile.TemplateTags = youtube.TemplateTagsIgnore + case "use as primary": + youtubeProfile.TemplateTags = youtube.TemplateTagsUseAsPrimary + case "use as additional": + youtubeProfile.TemplateTags = youtube.TemplateTagsUseAsAdditional + 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:")) diff --git a/pkg/streampanel/restream.go b/pkg/streampanel/restream.go index 7ced96f..85816a8 100644 --- a/pkg/streampanel/restream.go +++ b/pkg/streampanel/restream.go @@ -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} diff --git a/pkg/streampanel/timers.go b/pkg/streampanel/timers.go index f0173db..f0a1bf8 100644 --- a/pkg/streampanel/timers.go +++ b/pkg/streampanel/timers.go @@ -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")) diff --git a/pkg/streampanel/update_timer_handler.go b/pkg/streampanel/update_timer_handler.go index 154660c..3ae07b5 100644 --- a/pkg/streampanel/update_timer_handler.go +++ b/pkg/streampanel/update_timer_handler.go @@ -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, diff --git a/pkg/streampanel/widget_hint.go b/pkg/streampanel/widget_hint.go index 713e916..9d37335 100644 --- a/pkg/streampanel/widget_hint.go +++ b/pkg/streampanel/widget_hint.go @@ -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 } diff --git a/pkg/streamplayer/cmd/streamplayer/dummy_platforms_controller.go b/pkg/streamplayer/cmd/streamplayer/dummy_platforms_controller.go index b4c6931..8cff333 100644 --- a/pkg/streamplayer/cmd/streamplayer/dummy_platforms_controller.go +++ b/pkg/streamplayer/cmd/streamplayer/dummy_platforms_controller.go @@ -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 } diff --git a/pkg/streamplayer/cmd/streamplayer/main.go b/pkg/streamplayer/cmd/streamplayer/main.go index ba3994c..6e69b83 100644 --- a/pkg/streamplayer/cmd/streamplayer/main.go +++ b/pkg/streamplayer/cmd/streamplayer/main.go @@ -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() diff --git a/pkg/streamplayer/stream_player.go b/pkg/streamplayer/stream_player.go index c08f1d7..48327f5 100644 --- a/pkg/streamplayer/stream_player.go +++ b/pkg/streamplayer/stream_player.go @@ -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 { - r := map[streamtypes.StreamID]*StreamPlayerHandler{} - for k, v := range sp.StreamPlayers { - r[k] = v - } - return r - }) + 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) diff --git a/pkg/streamserver/implementations/.wip/go2rtc/streamserver/server/rtmp/rtmp_server.go b/pkg/streamserver/implementations/.wip/go2rtc/streamserver/server/rtmp/rtmp_server.go index 0774e61..9e0c9de 100644 --- a/pkg/streamserver/implementations/.wip/go2rtc/streamserver/server/rtmp/rtmp_server.go +++ b/pkg/streamserver/implementations/.wip/go2rtc/streamserver/server/rtmp/rtmp_server.go @@ -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 { diff --git a/pkg/streamserver/implementations/.wip/go2rtc/streamserver/stream_server.go b/pkg/streamserver/implementations/.wip/go2rtc/streamserver/stream_server.go index 0a0e316..6b4934c 100644 --- a/pkg/streamserver/implementations/.wip/go2rtc/streamserver/stream_server.go +++ b/pkg/streamserver/implementations/.wip/go2rtc/streamserver/stream_server.go @@ -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, + ) } diff --git a/pkg/streamserver/implementations/.wip/go2rtc/streamserver/streams/dot.go b/pkg/streamserver/implementations/.wip/go2rtc/streamserver/streams/dot.go index c54a733..9de8b44 100644 --- a/pkg/streamserver/implementations/.wip/go2rtc/streamserver/streams/dot.go +++ b/pkg/streamserver/implementations/.wip/go2rtc/streamserver/streams/dot.go @@ -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 { diff --git a/pkg/streamserver/implementations/.wip/go2rtc/streamserver/streams/handlers.go b/pkg/streamserver/implementations/.wip/go2rtc/streamserver/streams/handlers.go index 36af426..0f74c27 100644 --- a/pkg/streamserver/implementations/.wip/go2rtc/streamserver/streams/handlers.go +++ b/pkg/streamserver/implementations/.wip/go2rtc/streamserver/streams/handlers.go @@ -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] diff --git a/pkg/streamserver/implementations/.wip/livego/streamserver/stream_server.go b/pkg/streamserver/implementations/.wip/livego/streamserver/stream_server.go index 4db2a73..3ea6a6e 100644 --- a/pkg/streamserver/implementations/.wip/livego/streamserver/stream_server.go +++ b/pkg/streamserver/implementations/.wip/livego/streamserver/stream_server.go @@ -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( diff --git a/pkg/streamserver/implementations/mediamtx/streamserver/rtmp_server.go b/pkg/streamserver/implementations/mediamtx/streamserver/rtmp_server.go index 38bbb7a..0ea1011 100644 --- a/pkg/streamserver/implementations/mediamtx/streamserver/rtmp_server.go +++ b/pkg/streamserver/implementations/mediamtx/streamserver/rtmp_server.go @@ -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() diff --git a/pkg/streamserver/implementations/mediamtx/streamserver/stream_server.go b/pkg/streamserver/implementations/mediamtx/streamserver/stream_server.go index 88a0aa7..408b0d1 100644 --- a/pkg/streamserver/implementations/mediamtx/streamserver/stream_server.go +++ b/pkg/streamserver/implementations/mediamtx/streamserver/stream_server.go @@ -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()) diff --git a/pkg/streamserver/implementations/xaionaro-go-rtmp/handler.go b/pkg/streamserver/implementations/xaionaro-go-rtmp/handler.go index abaebf6..40497fe 100644 --- a/pkg/streamserver/implementations/xaionaro-go-rtmp/handler.go +++ b/pkg/streamserver/implementations/xaionaro-go-rtmp/handler.go @@ -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") } diff --git a/pkg/streamserver/implementations/xaionaro-go-rtmp/pubsub.go b/pkg/streamserver/implementations/xaionaro-go-rtmp/pubsub.go index fa3c07a..db22df0 100644 --- a/pkg/streamserver/implementations/xaionaro-go-rtmp/pubsub.go +++ b/pkg/streamserver/implementations/xaionaro-go-rtmp/pubsub.go @@ -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) }) diff --git a/pkg/streamserver/implementations/xaionaro-go-rtmp/streamforward/stream_forwards.go b/pkg/streamserver/implementations/xaionaro-go-rtmp/streamforward/stream_forwards.go index 957b167..45f4e23 100644 --- a/pkg/streamserver/implementations/xaionaro-go-rtmp/streamforward/stream_forwards.go +++ b/pkg/streamserver/implementations/xaionaro-go-rtmp/streamforward/stream_forwards.go @@ -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, + ) } diff --git a/pkg/streamserver/implementations/xaionaro-go-rtmp/streamserver/stream_server.go b/pkg/streamserver/implementations/xaionaro-go-rtmp/streamserver/stream_server.go index 25c64a1..6f777c0 100644 --- a/pkg/streamserver/implementations/xaionaro-go-rtmp/streamserver/stream_server.go +++ b/pkg/streamserver/implementations/xaionaro-go-rtmp/streamserver/stream_server.go @@ -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) } }) diff --git a/pkg/streamserver/streamforward/stream_forward.go b/pkg/streamserver/streamforward/stream_forward.go index 432f009..0f535d3 100644 --- a/pkg/streamserver/streamforward/stream_forward.go +++ b/pkg/streamserver/streamforward/stream_forward.go @@ -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) } diff --git a/pkg/streamserver/streamforward/stream_forwards.go b/pkg/streamserver/streamforward/stream_forwards.go index 3ed37d9..633256d 100644 --- a/pkg/streamserver/streamforward/stream_forwards.go +++ b/pkg/streamserver/streamforward/stream_forwards.go @@ -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 true - }) + 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 !dstID.IsSet() || dstID.Get() == destID - }) + 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 { - return true - }) + 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) { diff --git a/pkg/streamserver/streamplayers/stream_players.go b/pkg/streamserver/streamplayers/stream_players.go index 53da3e1..a09c0d1 100644 --- a/pkg/streamserver/streamplayers/stream_players.go +++ b/pkg/streamserver/streamplayers/stream_players.go @@ -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 diff --git a/pkg/streamserver/types/platform_controller.go b/pkg/streamserver/types/platform_controller.go index ad1d5fc..2ac76fe 100644 --- a/pkg/streamserver/types/platform_controller.go +++ b/pkg/streamserver/types/platform_controller.go @@ -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) } diff --git a/pkg/streamtypes/video_convert_config.go b/pkg/streamtypes/video_convert_config.go index 3567f44..48c9cd5 100644 --- a/pkg/streamtypes/video_convert_config.go +++ b/pkg/streamtypes/video_convert_config.go @@ -20,12 +20,12 @@ type VideoTrackConfig struct { } type RecodeVideoConfig struct { - Enable bool `json:"enable,omitempty" yaml:"enable,omitempty"` - FlipV bool `json:"flip_v,omitempty" yaml:"flip_v,omitempty"` - FlipH bool `json:"flip_h,omitempty" yaml:"flip_h,omitempty"` - Crop image.Rectangle `json:"crop,omitempty" yaml:"crop,omitempty"` - Scale image.Point `json:"scale,omitempty" yaml:"scale,omitempty"` - Codec VideoCodec `json:"codec,omitempty" yaml:"codec,omitempty"` + Enable bool `json:"enable,omitempty" yaml:"enable,omitempty"` + FlipV bool `json:"flip_v,omitempty" yaml:"flip_v,omitempty"` + FlipH bool `json:"flip_h,omitempty" yaml:"flip_h,omitempty"` + Crop image.Rectangle `json:"crop,omitempty" yaml:"crop,omitempty"` + Scale image.Point `json:"scale,omitempty" yaml:"scale,omitempty"` + Codec VideoCodec `json:"codec,omitempty" yaml:"codec,omitempty"` Quality VideoQuality `json:"quality,omitempty" yaml:"quality,omitempty"` } @@ -65,8 +65,8 @@ type AudioTrackConfig struct { } type RecodeAudioConfig struct { - Enable bool `json:"enable,omitempty" yaml:"enable,omitempty"` - Codec AudioCodec `json:"codec,omitempty" yaml:"codec,omitempty"` + Enable bool `json:"enable,omitempty" yaml:"enable,omitempty"` + Codec AudioCodec `json:"codec,omitempty" yaml:"codec,omitempty"` Quality AudioQuality `json:"quality,omitempty" yaml:"quality,omitempty"` }