AlexxIT/go2rtc · error
start from wrong mode
Error message
start from wrong mode: ${c.mode.String()} What it means
Inside Start(), when the Conn is in StateSetup it checks c.mode before sending PLAY. Only ModeActiveProducer (client PLAY) and ModePassiveProducer (nothing to do) are valid for a starting producer; any other mode is a programmer/lifecycle error, and the error includes the actual mode name via c.mode.String(). Start() even asserts this precondition at the top (core.Assert), so hitting this error means the assert was bypassed or mode changed after construction.
Solutions
- Only call Start() on producer-mode Conns (ModeActiveProducer or ModePassiveProducer); use the consumer API path for consumer Conns.
- Check c.mode before calling Start and branch to the correct start/play method for that mode.
- Fix Conn construction so the mode matches the intended role; do not mutate mode after setup.
Example fix
// before
err := conn.Start() // works only for producer modes
// after
if conn.Mode == core.ModeActiveProducer || conn.Mode == core.ModePassiveProducer {
err := conn.Start()
} else {
// consumer path: use consumer start API instead
} Defensive patterns
Strategy: validation
Validate before calling
if conn.Mode != core.ModeActiveProducer && conn.Mode != core.ModePassiveProducer {
return fmt.Errorf("Start requires producer mode, got %s", conn.Mode.String())
} Type guard
func isProducerMode(mode core.Mode) bool {
return mode == core.ModeActiveProducer || mode == core.ModePassiveProducer
} Try / catch
if err := conn.Start(); err != nil {
if strings.Contains(err.Error(), "start from wrong mode") {
// route to the consumer start path for this Conn
}
} Prevention
- Separate consumer and producer code paths; never share Start() across both.
- Assert the mode right after Conn creation and fail early.
- Avoid mutating c.mode after setup; make it effectively immutable.
- Document expected mode per public method in doc comments.
When it happens
Trigger: Calling Start() on a Conn in StateSetup whose mode is neither ModeActiveProducer nor ModePassiveProducer — e.g. ModePassiveConsumer or ModeActiveConsumer connections driven through the producer Start path; or c.mode mutated after the Conn was created.
Common situations: Generic streaming code that calls Start() on both consumer and producer Conns without branching; a Conn built as a consumer (backchannel/pulling side) but fed into a producer pipeline; copy-pasted server-side code that reused the producer Start on a consumer Conn.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- rtsp: wrong mode for GetTrack
- start from CONN state
- exec: rtsp module disabled
- exec: timeout
- producer without tracks
AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07).
Data as JSON: /api/errors/fd2dd670c5ac1f41.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/rtsp/producer.go:72
core.Assert(c.mode == core.ModeActiveProducer || c.mode == core.ModePassiveProducer)
for {
ok := false
c.stateMu.Lock()
switch c.state {
case StateNone:
err = nil
case StateConn:
err = errors.New("start from CONN state")
case StateSetup:
switch c.mode {
case core.ModeActiveProducer:
err = c.Play()
case core.ModePassiveProducer:
err = nil
default:
err = errors.New("start from wrong mode: " + c.mode.String())
}
if err == nil {
c.state = StatePlay
ok = true
}
}
c.stateMu.Unlock()
if !ok {
return
}
// Handler can return different states:
// 1. None after PLAY should exit without error
// 2. Play after PLAY should exit from Start with error
// 3. Setup after PLAY should Play once again
err = c.Handle()View on GitHub (pinned to c245815e75)