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

  1. Only call Start() on producer-mode Conns (ModeActiveProducer or ModePassiveProducer); use the consumer API path for consumer Conns.
  2. Check c.mode before calling Start and branch to the correct start/play method for that mode.
  3. 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

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


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)