AlexxIT/go2rtc · error

onvif: unsupported service

Error message

onvif: unsupported service

What it means

Request in pkg/onvif/client.go is the low-level SOAP dispatcher for all ONVIF service calls (GetProfile, GetVideoSourceConfiguration, GetStreamUri, GetSnapshotUri, GetServiceCapabilities, DeviceRequest). Each service method resolves a service URL (device/media/etc.); if the resolved URL is an empty string — meaning the device did not expose that service or the capability was never discovered — Request refuses to send and returns errors.New("onvif: unsupported service").

Solutions

  1. Check which services the camera exposes (DeviceService/GetServices or GetCapabilities) and only call operations for services that exist
  2. Verify discovery populated the service URLs — re-probe the device and confirm mediaURL/deviceURL are non-empty before use
  3. Use a camera/ONVIF-conformance tool (ONVIF Device Manager) to confirm whether the media service is implemented at all
  4. Upgrade the camera firmware or choose a device with full ONVIF Profile S support if you need streaming URIs
  5. Guard your code: call GetServiceCapabilities/GetStreamUri only when the corresponding URL field is non-empty

Example fix

// before
uri, err := cam.GetStreamUri(...) // panics-free but errors: unsupported service
// after
if cam.GetStreamUriURL() == "" { // media service not exposed
    return nil, errors.New("camera does not support ONVIF media service")
}
uri, err := cam.GetStreamUri(...)
Defensive patterns

Strategy: type-guard

Validate before calling

if c.mediaURL == "" {
    return errors.New("device does not expose the ONVIF media service")
}

Type guard

func supportsMedia(c *onvif.Client) bool { return c.MediaURL() != "" }
func supportsDevice(c *onvif.Client) bool { return c.DeviceURL() != "" }

Try / catch

uri, err := cam.GetStreamUri(ctx, tok)
if err != nil {
    if err.Error() == "onvif: unsupported service" {
        return nil, fmt.Errorf("camera %s lacks ONVIF media service; streaming unavailable", camID)
    }
    return err
}

Prevention

When it happens

Trigger: Calling any ONVIF operation when the corresponding service URL is empty: the camera does not implement the media service (no GetStreamUri/GetSnapshotUri/GetProfile), or the device/PTZ/etc. service was not found during capability discovery.

Common situations: Cheap/IP cameras that omit ONVIF media or analytics services entirely; a camera that reports services at discovery time but whose URLs weren't populated (device returned no XAddr for the service); calling snapshot/stream helpers against a doorbell or encoder that only implements device service.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07). Data as JSON: /api/errors/7e0ae9066e812aa0. Report an issue: GitHub.

Appendix: source

Thrown at pkg/onvif/client.go:179

	switch operation {
	case DeviceGetServices:
		operation = `<tds:GetServices><tds:IncludeCapability>true</tds:IncludeCapability></tds:GetServices>`
	case DeviceGetCapabilities:
		operation = `<tds:GetCapabilities><tds:Category>All</tds:Category></tds:GetCapabilities>`
	default:
		operation = `<tds:` + operation + `/>`
	}
	return c.Request(c.deviceURL, operation)
}

func (c *Client) MediaRequest(operation string) ([]byte, error) {
	operation = `<trt:` + operation + `/>`
	return c.Request(c.mediaURL, operation)
}

func (c *Client) Request(url, body string) ([]byte, error) {
	if url == "" {
		return nil, errors.New("onvif: unsupported service")
	}

	e := NewEnvelopeWithUser(c.url.User)
	e.Append(body)

	client := &http.Client{Timeout: time.Second * 5000}
	res, err := client.Post(url, `application/soap+xml;charset=utf-8`, bytes.NewReader(e.Bytes()))
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		return nil, errors.New("onvif: wrong response " + res.Status)
	}

	return io.ReadAll(res.Body)
}

View on GitHub (pinned to c245815e75)