AlexxIT/go2rtc · warning

nest: tried to stop rtsp stream without a project or device…

Error message

nest: tried to stop rtsp stream without a project or device ID

What it means

StopRTSPStream (pkg/nest/api.go:389) refuses to run when the API struct has no StreamProjectID or StreamDeviceID. These fields are only populated by a successful GenerateRtspStream, so this error means you are trying to stop a stream that was never generated through this API instance.

Solutions

  1. Ensure Stop is called on the same *API instance that successfully ran GenerateRtspStream.
  2. Guard the stop call: skip StopRTSPStream if the stream was never generated or generation returned an error.
  3. Persist StreamProjectID/StreamDeviceID/StreamExtensionToken if stop must survive a restart, and restore them into the API struct.
  4. If the stream came from the WebRTC path, use the corresponding WebRTC teardown instead of StopRTSPStream.

Example fix

// before: unconditional cleanup
api.GenerateRtspStream(projectID, deviceID)
defer api.Stop()
// after
if err := api.GenerateRtspStream(projectID, deviceID); err != nil { return err }
defer func() {
    if api.StreamProjectID != "" && api.StreamDeviceID != "" {
        api.Stop()
    }
}()
Defensive patterns

Strategy: type-guard

Validate before calling

if api.StreamProjectID == "" || api.StreamDeviceID == "" {
    return nil // or skip: no RTSP stream was generated on this instance
}

Type guard

func hasActiveStream(a *nest.API) bool {
    return a.StreamProjectID != "" && a.StreamDeviceID != "" && a.StreamExtensionToken != ""
}

Try / catch

if hasActiveStream(api) {
    if err := api.Stop(); err != nil { log.Printf("stop rtsp stream: %v", err) }
}

Prevention

When it happens

Trigger: Calling Stop/StopRTSPStream on a fresh API instance, on a second API object separate from the one that generated the stream, or after a process restart that lost the in-memory StreamProjectID/StreamDeviceID.

Common situations: Defer-based cleanup running when generation failed earlier; constructing a new *API for the stop call instead of reusing the original; calling Stop when the stream was established via WebRTC instead of RTSP; service restart between generate and stop.

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/3960f632562115b5. Report an issue: GitHub.

Appendix: source

Thrown at pkg/nest/api.go:389

		return "", err
	}

	if _, ok := resv.Results.StreamURLs["rtspUrl"]; !ok {
		return "", errors.New("nest: failed to generate rtsp url")
	}

	a.StreamProjectID = projectID
	a.StreamDeviceID = deviceID
	a.StreamToken = resv.Results.StreamToken
	a.StreamExtensionToken = resv.Results.StreamExtensionToken
	a.StreamExpiresAt = resv.Results.ExpiresAt

	return resv.Results.StreamURLs["rtspUrl"], nil
}

func (a *API) StopRTSPStream() error {
	if a.StreamProjectID == "" || a.StreamDeviceID == "" {
		return errors.New("nest: tried to stop rtsp stream without a project or device ID")
	}

	var reqv struct {
		Command string `json:"command"`
		Params  struct {
			StreamExtensionToken string `json:"streamExtensionToken"`
		} `json:"params"`
	}
	reqv.Command = "sdm.devices.commands.CameraLiveStream.StopRtspStream"
	reqv.Params.StreamExtensionToken = a.StreamExtensionToken

	b, err := json.Marshal(reqv)
	if err != nil {
		return err
	}

	uri := "https://smartdevicemanagement.googleapis.com/v1/enterprises/" +
		a.StreamProjectID + "/devices/" + a.StreamDeviceID + ":executeCommand"

View on GitHub (pinned to c245815e75)