Two capabilities a camera driver can opt into from its manifest: building
its own control panel at runtime, and handing over stream and snapshot
URLs on demand instead of declaring them once. Both are driven entirely
with proxy notifications. Extras are not specific to cameras and are worth
knowing wherever a driver needs controls its proxy does not define.
Proxy
camera
Binding
5001
Opt in via
<capabilities>
Payloads
XML strings
The model
A camera driver has always been able to declare fixed URLs and a fixed
feature set in its manifest. These capabilities invert that. The driver
publishes its controls and its URLs at runtime, as XML, over the proxy
binding it already owns.
Two independent mechanisms share one opt in style, and a driver may
adopt either without the other.
Extras are driver defined controls rendered in the
camera interface. The driver sends a document describing sections and
objects. Each object names the command the proxy will send back when a
viewer manipulates it, so the driver defines both the control and its
callback in one place. Nothing about an extra is declared in the
manifest, which means the control set can differ per device, per model,
or per firmware revision.
Dynamic URLs replace static stream and snapshot
addresses. Rather than resolving a URL once at setup, the driver answers
with a current list whenever one is needed, and can invalidate the
previous answer at any time. This suits devices whose addresses depend
on how they are attached, on a credential that rotates, or on a
recorder that fronts them.
Both mechanisms are pure notification traffic. There are no new bindings
and no new proxies. Everything travels over the existing camera proxy
binding, which is conventionally 5001.
Extras are not a camera concept. The same notifications
and the same document format are used by drivers across many proxy
types, including thermostats, lights, fireplaces and keypads. Only the
dynamic URL half is specific to cameras. Read the extras sections below
as general proxy technique that happens to be documented here against a
camera.
Opting in
Three flags inside the manifest's <capabilities>
block. They gate the behavior: without them, the notifications below are
ignored.
Capability flags
Flag
Effect
has_extras
The driver supplies its own control panel. Enables the extras notifications.
requires_dynamic_stream_urls
Stream addresses come from the driver at runtime rather than from static configuration.
The stream and snapshot flags are independent. A device that serves
stills from a stable path but streams from a rotating one sets only the
stream flag.
Call flow
Almost all traffic runs driver to proxy. The single inbound arrow is
the command the driver itself named when it defined the control,
which is what makes the extras surface self describing.
Conventions
Everything is a notification
Each message below is an ordinary proxy notification on the camera
binding. There is no response and no return value. The driver announces
state, and the interface catches up.
C4:SendToProxy(CAMERA_BINDING, "EXTRAS_SETUP_CHANGED", { XML = document }, "NOTIFY")
Payloads are XML strings, not tables
The documents travel as a single string parameter, conventionally
XML, built by the driver. This is unlike most proxy
traffic, where parameters are a flat table of scalars. Escape any value
that could contain a quote or an angle bracket, since device supplied
names frequently do.
Identifiers are yours, and they are load bearing
An object's id is chosen by the driver. It is the handle
used to update that control's value later, so it must stay stable for as
long as the control exists. The command attribute is
likewise arbitrary and is simply the command name the proxy will send
back. Deriving one from the other keeps the two ends aligned.
Library conventions are not the contract
The examples here dispatch inbound commands through a table looked up by
name, which is a common driver idiom rather than a requirement. What is
fixed is the notification names, the parameter names
(XML, KEY, URLS), the element and
attribute names in the documents, and the fact that the inbound command
is whatever the driver put in the command attribute.
A driver may receive that command in a plain
ReceivedFromProxy with a chain of comparisons and behave
identically.
Driver notifications
Sent by the driver on the camera proxy binding. All are fire and forget.
EXTRAS_SETUP_CHANGEDExtras
Publishes the entire control panel. Send it once the driver knows what
the device supports, and again whenever the shape of the panel
changes, such as when a capability probe finishes or a viewer changes
how the device is attached.
Parameters
Name
Type
Notes
XML
string req
An extras_setup document. Replaces the previous panel entirely.
This is a replacement, not a merge. Any control absent from the new
document is gone.
EXTRAS_STATE_CHANGEDExtras
Updates the value of controls already published, without resending the
panel. Use it when the device reports a change that did not originate
in the interface, and to confirm a change that did.
Parameters
Name
Type
Notes
XML
string req
An extras_state document carrying one or more id and value pairs.
An id that does not match a published object has no effect. There is
no error.
DYNAMIC_CAPABILITIES_CHANGEDCapabilities
Announces the movement related capability set, which is carried as an
ordinary parameter table rather than a document. Send it alongside the
extras panel whenever the driver has finished working out what the
device can physically do.
STREAM_URLS_READYURLs
Delivers the current stream list. Required when the driver declares
requires_dynamic_stream_urls.
Parameters
Name
Type
Notes
KEY
string
Correlates this answer with the request that prompted it. Echoed into the document's own key attribute.
URLS
string req
A streams document.
The key appears in two places, as a parameter and as an attribute on
the document root. Set both to the same value.
SNAPSHOT_URLS_READYURLs
The still image counterpart, carrying a snapshots
document. Same parameters and same key handling.
DYNAMIC_URLS_CHANGEDURLs
Invalidates whatever was published previously. Takes no parameters.
Send it when an address, port, credential, or attachment method
changes, so that anything holding an old URL discards it.
This is the mechanism that makes the whole dynamic URL arrangement
worthwhile. A driver that never invalidates has gained nothing over
static configuration.
Proxy commands
Inbound commands arrive on the camera binding and are handled like any
other proxy traffic.
the command you namedExtras
When a viewer operates a control, the proxy sends the command string
from that object's command attribute. The driver chose
the name, so there is no fixed vocabulary here, only the promise that
what the driver declared is what it will receive.
The new value arrives in the command parameters. Apply it to the
device, then confirm with EXTRAS_STATE_CHANGED rather
than assuming the interface and the device now agree. A control whose
write fails should be pushed back to its old value the same way.
Momentary controls carry no meaningful value. Treat the arrival of the
command as the whole event.
Connection and addressingStandard
The usual camera proxy commands still arrive and still matter here,
because each one can invalidate a previously published URL. A driver
using dynamic URLs should treat every one of these as a trigger to
send DYNAMIC_URLS_CHANGED.
Commands that can invalidate a URL
Command
Carries
SET_ADDRESS
ADDRESS
SET_HTTP_PORT
PORT
SET_HTTPS_PORT
PORT
SET_RTSP_PORT
PORT
SET_USERNAME
credential
SET_PASSWORD
credential
SET_USE_HTTPS
scheme selection
SET_AUTHENTICATION_REQUIRED
auth policy
SET_AUTHENTICATION_TYPE
auth policy
SET_PUBLICLY_ACCESSIBLE
reachability
On an address change the driver also emits
ADDRESS_CHANGED back to the proxy carrying the new
ADDRESS.
Payload shapes
extras_setup
Sections group controls under a heading. Objects are the controls. Four
object types exist: list, slider,
switch, and button.
Both wrap a flat list. Order is meaningful: the first entry is treated as
the primary, higher quality source, and later entries as progressively
lighter alternates.
The key attribute is omitted when the publication was not
prompted by a request. Snapshot documents follow the same pattern with
snapshots and snapshot element names.
Behavior notes
The panel is replaced, never merged
Every setup document supersedes the last. Rebuild the whole thing
from current state rather than trying to send a difference.
State updates do not resize the panel
A value update can change what a control reads, not what controls
exist, and not the items inside a list. Changing the options
themselves means sending a fresh setup document.
Nothing is acknowledged
These are notifications, so there is no delivery confirmation and no
error path. A malformed document fails silently, which makes an
empty or stale panel the normal symptom of a construction bug.
Confirm writes rather than assuming them
The interface shows the value the driver last published. If a write
to the device fails and the driver stays quiet, the control keeps
displaying a setting that was never applied.
Capability flags gate everything
Without the manifest flags the notifications are ignored. A panel
that never appears is worth checking against the manifest before
the code.
Invalidate on every addressing change
Address, ports, credentials, scheme, and reachability all feed URL
construction. Any of them changing without an invalidation leaves
consumers holding an address that no longer resolves.
The two halves have very different adoption
Extras are widely used across many proxy types and can be treated as
well exercised. The dynamic URL flags are set by far fewer drivers,
so that half is thinner and its edge cases are more likely to be
untested.
Integration recipe
The minimum for a driver that wants both halves. Helper names here are
placeholders for whatever the driver already has.
-- 1. publish the panel once capabilities are known
local function PublishExtras()
local doc = {}
doc[#doc + 1] = '<extras_setup><extra>'
doc[#doc + 1] = '<section label="Illumination">'
doc[#doc + 1] = string.format(
'<object type="slider" id="%s" label="%s" command="%s"'
.. ' value="%d" min="1" max="100"/>',
"beam_level", "Brightness", "SET_BEAM_LEVEL", DeviceState.level)
doc[#doc + 1] = '</section></extra></extras_setup>'
C4:SendToProxy(CAMERA_BINDING, "EXTRAS_SETUP_CHANGED", {
XML = table.concat(doc),
}, "NOTIFY")
end
-- 2. handle the command the control declared
local function OnBeamLevel(tParams)
local level = tonumber(tParams.LEVEL or tParams.VALUE)
if not WriteLevelToDevice(level) then
level = DeviceState.level -- push the old value back
else
DeviceState.level = level
end
C4:SendToProxy(CAMERA_BINDING, "EXTRAS_STATE_CHANGED", {
XML = string.format(
'<extras_state><extra><object id="%s" value="%s"/></extra></extras_state>',
"beam_level", tostring(level)),
}, "NOTIFY")
end
-- 3. publish stream URLs
local function PublishStreams(key)
local doc = {}
if key then
doc[#doc + 1] = string.format('<streams key="%s">', key)
else
doc[#doc + 1] = '<streams>'
end
for _, s in ipairs(BuildStreamList()) do
doc[#doc + 1] = string.format(
'<stream url="%s" codec="%s" resolution="%s"/>', s.url, s.codec, s.resolution)
end
doc[#doc + 1] = '</streams>'
C4:SendToProxy(CAMERA_BINDING, "STREAM_URLS_READY", {
KEY = key,
URLS = table.concat(doc),
}, "NOTIFY")
end
-- 4. invalidate whenever addressing changes
local function OnAddressingChanged()
C4:SendToProxy(CAMERA_BINDING, "DYNAMIC_URLS_CHANGED", {}, "NOTIFY")
end
Escaping is omitted above for brevity. Device supplied labels and
stream names routinely contain characters that will break a document
built by concatenation, so run every interpolated value through an XML
escape before it reaches the string.