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
- Ensure Stop is called on the same *API instance that successfully ran GenerateRtspStream.
- Guard the stop call: skip StopRTSPStream if the stream was never generated or generation returned an error.
- Persist StreamProjectID/StreamDeviceID/StreamExtensionToken if stop must survive a restart, and restore them into the API struct.
- 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
- Only call Stop on the same *API instance that ran GenerateRtspStream successfully.
- Skip teardown when stream generation errored — wrap generate+defer so stop only runs after success.
- Persist StreamProjectID/StreamDeviceID/StreamExtensionToken if stop may occur after a restart.
- Treat stop failures as non-fatal in cleanup paths; log and continue.
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)