Control Video Playback from TouchDesigner over OSC
TouchDesigner thinks in channels — continuous streams of numbers flowing every frame. OSC is the protocol that matches that way of working. HTTP provides validation and queue-admission feedback. OSC fits when you want a live CHOP value to become the player’s volume or playback position in real time. This guide covers both, using DearScenario Player as the remote player.
OSC or HTTP? Pick by the shape of the control
DearScenario Player accepts both. The HTTP guide covers discrete commands with validation and queue-admission feedback. Reach for OSC when the control is continuous and real-time:
| HTTP | OSC | |
|---|---|---|
| Best for | Discrete cues: load, play, pause | Continuous values: volume, seek |
| Rate | A few per second | Tens of times per second, per frame |
| Confirmation | Validated and queued: {"ok": true}; playback observed separately | No success acknowledgement |
| TouchDesigner source | Web DAT / Python | OSC Out DAT, driven by callbacks or CHOP values |
Use an OSC Out DAT for both cue changes and live values. The Player accepts single OSC messages and rejects bundles, so explicitly send unbundled messages.
Setup
- Install and launch DearScenario Player on the playback machine (see HTTP node setup guide)
- Put both machines on the same network
- Set
network.enable_osctotruein the player'sconfig.json, then restart the player. OSC uses UDP port18290.
Discrete cues with an OSC Out DAT
Use an OSC Out DAT for one-shot commands — the things that happen at a moment, not continuously. Set its Network Address to the DearScenario Player IP and Port to 18290, then send messages with sendOSC():
n = op('oscout1')
# Load and play in one message (string + cue + loop)
n.sendOSC('/player/play_media', ['C:\\media\\scene_a.mp4', 0.0, 0], asBundle=False)
# Or prepare first; send play from a later callback after HTTP status shows ready
n.sendOSC('/player/prepare', ['C:\\media\\scene_a.mp4', 0.0, 0], asBundle=False)
# In the ready callback: n.sendOSC('/player/play', [], asBundle=False)
# Later
n.sendOSC('/player/pause', [], asBundle=False)Drive these from any callback — a CHOP Execute DAT’s onValueChange when a sensor crosses a threshold, a Timer CHOP at a timeline point, or a button’s panel callback.
Continuous control from CHOP channels
Build volume or seek values as CHOP channels, then use a CHOP Execute DAT to send individual messages through the OSC Out DAT. This gives explicit control over the message format required by the Player.
- Scale volume to 0–1 or seek position to non-negative seconds.
- Name the channels
volumeandseek_sec. - Point a CHOP Execute DAT at that CHOP and enable Value Change.
- Configure an OSC Out DAT named
oscout1in the same network with the Player IP and port18290. - Use this callback:
def onValueChange(channel, sampleIndex, val, prev):
if channel.name == 'volume':
address = '/player/volume'
value = max(0.0, min(1.0, float(val)))
elif channel.name == 'seek_sec':
address = '/player/seek_sec'
value = max(0.0, float(val))
else:
return
op('oscout1').sendOSC(
address, [value], asBundle=False,
useNonStandardTypes=False, use64BitPrecision=False)
returnUse the OSC Out DAT sendOSC() options
to keep messages unbundled with 32-bit numeric arguments. An OSC Out CHOP path
must meet the same packet rules; a bundle or a 64-bit value will be rejected.
Limit the update rate to what the installation needs.
OSC address reference
DearScenario Player listens for OSC on port 18290. These are the addresses you will use most from TouchDesigner:
| OSC Address | Type | Action |
|---|---|---|
/player/play_media | s, sfi, or sii | Load and play (atomic cue) |
/player/prepare | s, sfi, or sii | Load and leave ready/paused |
/player/play | — | Start or resume playback |
/player/pause | — | Pause playback |
/player/cue | — | Return to cue point while keeping media loaded |
/player/stop | — | Stop and remove media |
/player/seek_sec | float or int | Seek to time in seconds |
/player/volume | float or int | Set volume (float 0–1; int 0 or 1) |
/player/mute | int | Mute (1) or unmute (0) |
/player/loop | int | Enable (1) or disable (0) looping |
Real-time patterns
- Audio-reactive volume: An Audio Analysis CHOP envelope, scaled to 0–1, sent to
/player/volumethrough the value-change callback so playback level tracks the room or a live source - Scrub by interaction: A slider drives
player/seek_sec - Sensor-gated cue + live value: An OSC Out DAT fires
/player/play_mediawhen a sensor trips, while a CHOP Execute DAT sends volume updates through the OSC Out DAT - Multi-node control: Send the same absolute value through OSC Out DATs aimed at different node IPs; this does not guarantee frame-synchronized playback
Reliability: OSC is fire-and-forget
OSC has no success acknowledgement. Later absolute-value updates can replace a dropped volume or seek message. HTTP {"ok": true} confirms validation and queue admission only; it does not prove a file loaded or playback started. If the show depends on that state, inspect state, source, and error through GET /api/status. Immediate OSC errors are sent to the sender’s UDP source port, not an arbitrary OSC In DAT port, and do not report later playback failures. A common split:
- OSC for everything continuous and for low-stakes triggers
- HTTP plus status observation for cues that require verified playback state, sent from a script (see the HTTP guide)
Where DearScenario Player fits
DearScenario Player is a playback API node: it runs headless on a Windows PC or Raspberry Pi, exposes the same command model over HTTP and OSC, and does not try to be the creative brain. TouchDesigner holds the logic and the live data; DearScenario Player turns it into video on a remote screen. OSC is simply the channel-native way to connect the two.
Next step: If your production also runs QLab, see how to trigger DearScenario Player from QLab over OSC, or put a low-cost Raspberry Pi node behind each screen.

