{"record":{"id":"c9d9e3dfe8287c5d","repo":"AlexxIT/go2rtc","slug":"speaker-wait-failed-w","errorCode":null,"errorMessage":"speaker wait failed: %w","messagePattern":"speaker wait failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/tuya/mqtt.go","lineNumber":263,"sourceCode":"\t}\n\n\treturn c.sendMqttMessage(\"resolution\", 312, \"\", ResolutionFrame{\n\t\tMode:  \"webrtc\",\n\t\tValue: resolution, // 0: HD, 1: SD\n\t})\n}\n\nfunc (c *TuyaMqttClient) SendSpeaker(speaker int) error {\n\tif err := c.sendMqttMessage(\"speaker\", 312, \"\", SpeakerFrame{\n\t\tMode:  \"webrtc\",\n\t\tValue: speaker, // 0: off, 1: on\n\t}); err != nil {\n\t\treturn err\n\t}\n\n\t// Wait for camera response\n\tif err := c.speakerWaiter.Wait(); err != nil {\n\t\treturn fmt.Errorf(\"speaker wait failed: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (c *TuyaMqttClient) SendDisconnect() error {\n\treturn c.sendMqttMessage(\"disconnect\", 302, \"\", DisconnectFrame{\n\t\tMode: \"webrtc\",\n\t})\n}\n\nfunc (c *TuyaMqttClient) onConnect(client mqtt.Client) {\n\tif token := client.Subscribe(c.subscribeTopic, 1, c.onMessage); token.Wait() && token.Error() != nil {\n\t\tc.waiter.Done(token.Error())\n\t\treturn\n\t}\n\n\tc.waiter.Done(nil)","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/AlexxIT/go2rtc/blob/c245815e75e2a5fd60b4290f12bfc04e55a984d3/pkg/tuya/mqtt.go#L245-L281","documentation":"SendSpeaker publishes a speaker protocol message and then blocks on speakerWaiter.Wait(), which is completed by onMqttSpeaker when the camera responds. This error wraps any error the waiter was completed with — i.e. the camera replied with a non-zero resCode (delivered via error 316) or the wait timed out / was cancelled by the waiter implementation. It's the user-facing signal that the speaker command failed at the device level.","triggerScenarios":"Calling SendSpeaker when the camera responds with resCode != 0, or the waiter times out because no speaker response arrives (camera offline, wrong dps payload rejected), or the wait context is cancelled.","commonSituations":"Camera busy or in low-power sleep and not answering within the wait window, unsupported audio parameters in the speaker request, or MQTT responses arriving on a topic the client isn't subscribed to.","solutions":["Unwrap the error to see the underlying cause (resCode value vs timeout) and handle each distinctly","Increase/verify the waiter timeout to accommodate camera wake-up latency","Ensure the camera is awake (call WakeUp first) and subscribed to response topics","Check the speaker request payload parameters against what the device model supports"],"exampleFix":"// before\nerr := client.SendSpeaker(sdp, res, streamType, hevc)\n// after\nerr := client.SendSpeaker(sdp, res, streamType, hevc)\nif err != nil && strings.Contains(err.Error(), \"speaker wait failed\") {\n    if strings.Contains(err.Error(), \"resCode\") { return err }\n    // timeout path: retry once after waking the device\n    _ = client.WakeUp(deviceID)\n    err = client.SendSpeaker(sdp, res, streamType, hevc)\n}","handlingStrategy":"try-catch","validationCode":"// ensure device is awake before speaker commands\nif !deviceAwake { if err := client.WakeUp(deviceID); err != nil { return err } }","typeGuard":null,"tryCatchPattern":"err := client.SendSpeaker(...)\nif err != nil {\n    if strings.Contains(err.Error(), \"resCode\") { return fmt.Errorf(\"device rejected: %v\", err) }\n    // else treat as timeout: retry once\n}","preventionTips":["Set waiter timeouts longer than camera wake-up latency","Serialize speaker commands with a mutex","Wake the device explicitly before SendSpeaker"],"tags":["go","mqtt","waiter","timeout","speaker"],"backgroundTag":"request-timeout","analyzedSha":"c245815e75e2a5fd60b4290f12bfc04e55a984d3","analyzedAt":"2026-09-07T11:47:02.965Z","contentChangedAt":"2026-09-07T11:47:02.965Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}