Consumers Know Only the Root
Oriel watches very different things. A capture card behind a game console. A USB webcam with a motorized gimbal. A phone used as a camera body. A network camera that another family app, Haven, can reach. A synthetic test pattern. If every consumer (sessions, looks, the page, the gate) had to know which of these it was talking to, every new kind of device would mean editing every consumer. The family has a standing rule against exactly that shape: Dad calls a non-object-oriented architecture a sin, and the fix he keeps pointing at is a canonical root class whose children pay their own tax.
In Oriel that root is WatchSource, in backend/sources.py. Its docstring opens with the whole contract: consumers know only WatchSource; a child says how its device is opened, recorded and peeked at. The root declares six abstract methods (modes, present, start, stop, state, peek) and one shared one, describe, which the page reads.
Four Children, Each Paying Its Own Tax
| Child | Kind | What it is | Its tax |
|---|---|---|---|
UvcCaptureSource | uvc | any camera-class device AVFoundation shows: capture cards, webcams, a Continuity Camera | asks the recorder through the file channel; modes come from the device |
TestPatternSource | test | ffmpeg's test pattern | records itself with ffmpeg in exactly the same segment and sidecar layout; needs no grant |
CordeliaSource | cordelia | a camera of a device body on a phone or tablet | stills on request through the body's own wire; refuses to record |
HavenCameraSource | Haven's kind | a camera Haven reaches over the network | stills on request through Haven, which asks Oriel's gate itself; refuses to record |
Look at the last two. A phone body and a network camera cannot keep a continuous recording on Oriel's disk, and they say so in the only honest way: their start raises "takes stills on request; it does not record". The root did not grow a can_record flag for them. The children refuse what they cannot do, and the consumers handle the refusal the way they handle any other.
Notice also where those two children live: not in sources.py, but in cordelia.py and haven_cameras.py, next to the code that knows phones and Haven. The root registers every child automatically through __init_subclass__, so adding a kind is a new class in its own module, and the root file is not edited.
The Test Pattern Is a Real Child
The humblest child is the most useful. TestPatternSource renders ffmpeg's testsrc2 and records it into the same fragmented MP4 segments, with the same JSON sidecars, that the recorder writes for a real card. Because it is a full child, not a mock, everything downstream runs against it unchanged: the frame index, every look, every budget test, a session's start and stop, an engine restart mid-session. The suite exercises the real recording format without a single camera, and when a device is missing in development, the test pattern stands in for it.
That is the payoff of a root class done properly. A test double that obeys the same contract is not a special path through the code; it is just another child.
Devices Are Rows; Classes Are Kinds
One more design choice keeps this clean. An input in Oriel's database is a row: an id, a kind, a unique ID, a name, a role. WatchSource.for_row looks the kind up in the registry and builds the right child for that row. So the code never names a device. Two identical capture cards are two rows of kind uvc; a camera that moves to another Mac is a row whose unique ID carries that Mac's name. The class says how a kind of device behaves. The row says which device this is.