{"record":{"id":"3960f632562115b5","repo":"AlexxIT/go2rtc","slug":"nest-tried-to-stop-rtsp-stream-without-a-project","errorCode":null,"errorMessage":"nest: tried to stop rtsp stream without a project or device ID","messagePattern":"nest: tried to stop rtsp stream without a project or device ID","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/nest/api.go","lineNumber":389,"sourceCode":"\t\treturn \"\", err\n\t}\n\n\tif _, ok := resv.Results.StreamURLs[\"rtspUrl\"]; !ok {\n\t\treturn \"\", errors.New(\"nest: failed to generate rtsp url\")\n\t}\n\n\ta.StreamProjectID = projectID\n\ta.StreamDeviceID = deviceID\n\ta.StreamToken = resv.Results.StreamToken\n\ta.StreamExtensionToken = resv.Results.StreamExtensionToken\n\ta.StreamExpiresAt = resv.Results.ExpiresAt\n\n\treturn resv.Results.StreamURLs[\"rtspUrl\"], nil\n}\n\nfunc (a *API) StopRTSPStream() error {\n\tif a.StreamProjectID == \"\" || a.StreamDeviceID == \"\" {\n\t\treturn errors.New(\"nest: tried to stop rtsp stream without a project or device ID\")\n\t}\n\n\tvar reqv struct {\n\t\tCommand string `json:\"command\"`\n\t\tParams  struct {\n\t\t\tStreamExtensionToken string `json:\"streamExtensionToken\"`\n\t\t} `json:\"params\"`\n\t}\n\treqv.Command = \"sdm.devices.commands.CameraLiveStream.StopRtspStream\"\n\treqv.Params.StreamExtensionToken = a.StreamExtensionToken\n\n\tb, err := json.Marshal(reqv)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\turi := \"https://smartdevicemanagement.googleapis.com/v1/enterprises/\" +\n\t\ta.StreamProjectID + \"/devices/\" + a.StreamDeviceID + \":executeCommand\"","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/AlexxIT/go2rtc/blob/c245815e75e2a5fd60b4290f12bfc04e55a984d3/pkg/nest/api.go#L371-L407","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: unconditional cleanup\napi.GenerateRtspStream(projectID, deviceID)\ndefer api.Stop()\n// after\nif err := api.GenerateRtspStream(projectID, deviceID); err != nil { return err }\ndefer func() {\n    if api.StreamProjectID != \"\" && api.StreamDeviceID != \"\" {\n        api.Stop()\n    }\n}()","handlingStrategy":"type-guard","validationCode":"if api.StreamProjectID == \"\" || api.StreamDeviceID == \"\" {\n    return nil // or skip: no RTSP stream was generated on this instance\n}","typeGuard":"func hasActiveStream(a *nest.API) bool {\n    return a.StreamProjectID != \"\" && a.StreamDeviceID != \"\" && a.StreamExtensionToken != \"\"\n}","tryCatchPattern":"if hasActiveStream(api) {\n    if err := api.Stop(); err != nil { log.Printf(\"stop rtsp stream: %v\", err) }\n}","preventionTips":["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."],"tags":["state","streaming","rtsp","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"c245815e75e2a5fd60b4290f12bfc04e55a984d3","analyzedAt":"2026-09-07T11:47:02.965Z","contentChangedAt":"2026-09-07T11:47:02.965Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}