A comprehensive guide to receiving real-time video playback position feedback from MadMapper using Node.js and OSC (Open Sound Control) protocol.
Table of Contents
- Introduction
- Prerequisites
- Understanding OSC Feedback in MadMapper
- Setting Up the Node.js Monitor
- MadMapper Configuration
- Complete Example
- Troubleshooting
- Advanced Use Cases
Introduction
This guide demonstrates how to receive real-time video position feedback from MadMapper using Node.js and OSC. You'll learn to capture video playback progress and trigger actions when videos reach specific points (like the end). This is particularly useful for interactive installations, live performances, or automated content management systems.
Why this guide exists:
While researching MadMapper OSC integration, I found scattered information in forums and documentation about OSC feedback, but nothing comprehensive about implementing this with Node.js. Most existing resources focus on dedicated OSC applications like TouchOSC, Chataigne, or QLab - software that handles OSC natively. This guide fills that gap by showing how to build custom Node.js applications that can receive and process MadMapper's video feedback.
What you'll learn:
- How OSC feedback works in MadMapper
- Setting up Node.js to receive OSC feedback from MadMapper
- Capturing video position updates in real-time
- Processing feedback data to trigger automated actions
Prerequisites
- MadMapper (any recent version)
- Node.js installed on your system
- Basic understanding of JavaScript
- Videos loaded in MadMapper
Understanding OSC Feedback in MadMapper
Key Concept: Manual Control Mapping
Important: MadMapper only sends OSC feedback for manually created controls, not for predefined OSC channels.
❌ Won't work: Direct feedback from /surfaces/Quad-1/opacity
✅ Will work: Manual control mapping /myVideo → /medias/video.mp4/position
How OSC Feedback Works
- You create a manual OSC control in MadMapper
- You map this control to a video parameter (like position)
- MadMapper sends feedback to your OSC address when the parameter changes
- Your Node.js app receives and processes this feedback
Setting Up Node.js to Receive OSC Feedback
Installation
First, install the required OSC library:
npm install node-osc
Basic OSC Server Setup
const osc = require("node-osc");
// Configuration
const MADMAPPER_IP = "127.0.0.1"; // MadMapper's IP
const MADMAPPER_PORT = 8010; // MadMapper's OSC input port
const FEEDBACK_PORT = 8011; // Port to receive feedback
// Create OSC server to receive feedback
const server = new osc.Server(FEEDBACK_PORT, "0.0.0.0");
console.log(`OSC Server listening on port ${FEEDBACK_PORT}`);
// Create OSC client to send commands
const client = new osc.Client(MADMAPPER_IP, MADMAPPER_PORT);
Receiving Video Position Updates
// Track video states
const videoStates = {
"/video/intro": {
position: 0,
hasFinished: false,
displayName: "Intro Video"
},
"/video/main": {
position: 0,
hasFinished: false,
displayName: "Main Content"
}
};
// Handle incoming OSC messages
server.on("message", (msg, rinfo) => {
const [address, ...args] = msg;
const position = args[0]; // 0.0 to 1.0
console.log(`VIDEO ${address}: ${(position * 100).toFixed(1)}%`);
// Check if this is a monitored video
if (videoStates[address]) {
const oldPosition = videoStates[address].position;
videoStates[address].position = position;
// Detect video end (99.5% or more)
// Note: We use 0.995 instead of 1.0 because video position
// rarely reaches exactly 100% in practice
if (position >= 0.995 && !videoStates[address].hasFinished) {
console.log(`VIDEO ${videoStates[address].displayName} finished!`);
videoStates[address].hasFinished = true;
// Trigger your action here
triggerNextAction(address);
}
// Reset when video restarts
if (position < 0.1 && videoStates[address].hasFinished) {
videoStates[address].hasFinished = false;
console.log(`VIDEO ${videoStates[address].displayName} restarted`);
}
}
});
function triggerNextAction(videoAddress) {
// Example: Start another video or trigger an effect
console.log("Triggering next action...");
client.send("/nextVideo", 1);
}
MadMapper Configuration
Step 1: OSC Preferences
- Open MadMapper Preferences
- Go to OSC section
- Set:
-
OSC Input Port:
8010
-
Feedback Mode:
Auto
orManual
-
Feedback IP:
Auto
(or your computer's IP) -
Feedback Port:
8011
-
OSC Input Port:
Step 2: Create Manual OSC Controls
This is crucial for feedback to work!
- Open Window → Controls
- Click "+" to add new control
- Select OSC type
- Configure:
-
OSC Address:
/video/intro
(your custom address) - Target: Click "Learn" and move the video position slider
-
OR manually type:
/medias/your-video-file.mp4/position
- Handle Feedback: checked
-
OSC Address:
Screenshot of MadMapper Controls window showing multiple video position controls configured with OSC addresses (/medias/videoX.mp4/position
) and the Enable Feedback option checked. This is the essential setup for receiving real-time position updates from video playback.
Step 3: Register Your Application
Send an initial message to register with MadMapper:
// Register with MadMapper (Auto mode)
client.send("/ping", 1, () => {
console.log("Registered with MadMapper");
});
Complete Example
Here's a complete working example:
const osc = require("node-osc");
class VideoMonitor {
constructor() {
this.MADMAPPER_IP = "127.0.0.1";
this.MADMAPPER_PORT = 8010;
this.FEEDBACK_PORT = 8011;
this.setupOSC();
this.setupVideoStates();
this.registerWithMadMapper();
}
setupOSC() {
// OSC server for receiving feedback
this.server = new osc.Server(this.FEEDBACK_PORT, "0.0.0.0");
this.server.on("message", this.handleOSCMessage.bind(this));
// OSC client for sending commands
this.client = new osc.Client(this.MADMAPPER_IP, this.MADMAPPER_PORT);
console.log(`OSC Monitor ready on port ${this.FEEDBACK_PORT}`);
}
setupVideoStates() {
this.videos = {
"/video/intro": { position: 0, finished: false, name: "Intro" },
"/video/main": { position: 0, finished: false, name: "Main Content" },
"/video/outro": { position: 0, finished: false, name: "Outro" }
};
}
registerWithMadMapper() {
setTimeout(() => {
this.client.send("/ping", 1);
console.log("Registered with MadMapper");
}, 1000);
}
handleOSCMessage(msg, rinfo) {
const [address, position] = msg;
if (this.videos[address]) {
this.updateVideoState(address, position);
}
}
updateVideoState(address, position) {
const video = this.videos[address];
const oldPosition = video.position;
video.position = position;
const percent = (position * 100).toFixed(1);
console.log(`VIDEO ${video.name}: ${percent}%`);
// Check for video completion (99.5% threshold)
// Using 0.995 instead of 1.0 because videos rarely reach exactly 100%
if (position >= 0.995 && !video.finished) {
this.onVideoFinished(address, video);
}
// Reset on restart
if (position < 0.1 && video.finished) {
video.finished = false;
console.log(`VIDEO ${video.name} restarted`);
}
}
onVideoFinished(address, video) {
console.log(`VIDEO ${video.name} FINISHED!`);
video.finished = true;
// Add your custom logic here
switch(address) {
case "/video/intro":
this.startMainVideo();
break;
case "/video/main":
this.startOutroVideo();
break;
case "/video/outro":
this.loopToIntro();
break;
}
}
startMainVideo() {
console.log("Starting main video...");
this.client.send("/video/main", 1);
}
startOutroVideo() {
console.log("Starting outro video...");
this.client.send("/video/outro", 1);
}
loopToIntro() {
console.log("Looping back to intro...");
setTimeout(() => {
this.client.send("/video/intro", 1);
}, 2000);
}
// Status reporting
printStatus() {
console.log("\nVIDEO STATUS:");
Object.entries(this.videos).forEach(([addr, video]) => {
const percent = (video.position * 100).toFixed(1);
const status = video.finished ? "Finished" : "Playing";
console.log(` ${video.name}: ${percent}% - ${status}`);
});
console.log("");
}
}
// Start the monitor
const monitor = new VideoMonitor();
// Show status every 10 seconds
setInterval(() => monitor.printStatus(), 10000);
// Graceful shutdown
process.on('SIGINT', () => {
console.log('\nShutting down video monitor...');
process.exit(0);
});
Important Notes
Why 99.5% Instead of 100%?
In practice, video position values in MadMapper rarely reach exactly 1.0
(100%). This is common in many video playback systems due to:
- Frame timing precision: Video frames don't align perfectly with percentage calculations
- Buffering behavior: The last few milliseconds might not trigger position updates
- Codec specifics: Different video formats handle end-of-stream differently
Using 0.995
(99.5%) as the threshold ensures reliable detection of video completion while still being close enough to the actual end.
Troubleshooting
Common Issues
Not receiving any OSC messages
- Verify MadMapper OSC preferences (IP addresses and port numbers)
- Ensure you created manual OSC controls with "Handle Feedback" checked
- Send a ping message to register with MadMapper (required for Auto mode)
- Check firewall settings that might block OSC communication
Receiving only 0 or 1 values (not gradual position)
- Your OSC control is likely mapped to play/stop instead of position
- Re-map the control target to
/medias/[filename]/position
- Use the "Learn" function while manually moving the video position slider
- Verify the control is mapped to the timeline position, not play button
Feedback stops working after MadMapper restart
- Re-send the ping message to re-register your application
- Check if "Auto" feedback mode lost your application in its registry
- Verify OSC preferences weren't reset during restart
Debug Tips
// Add detailed debugging
server.on("message", (msg, rinfo) => {
console.log(`OSC Debug:`);
console.log(` From: ${rinfo.address}:${rinfo.port}`);
console.log(` Address: "${msg[0]}"`);
console.log(` Value: ${msg[1]} (${typeof msg[1]})`);
console.log(` Raw:`, msg);
});
Advanced Use Cases
Multiple Video Synchronization
Monitor multiple videos and ensure they stay synchronized:
// Check if videos are in sync (within 5% tolerance)
const tolerance = 0.05;
const positions = Object.values(videos).map(v => v.position);
const minPos = Math.min(...positions);
const maxPos = Math.max(...positions);
if (maxPos - minPos > tolerance) {
console.log("Videos out of sync!");
// Implement sync logic
}
Dynamic Video Switching
Create playlists that automatically advance:
const playlist = ["/video/intro", "/video/main", "/video/outro"];
let currentIndex = 0;
function playNext() {
if (currentIndex < playlist.length - 1) {
currentIndex++;
client.send(playlist[currentIndex], 1);
} else {
// Loop back to start
currentIndex = 0;
client.send(playlist[currentIndex], 1);
}
}
Performance Analytics
Track video performance metrics:
const analytics = {
completionRates: {},
averageWatchTime: {},
skipPoints: {}
};
// Track when users skip videos
function trackSkip(address, position) {
if (!analytics.skipPoints[address]) {
analytics.skipPoints[address] = [];
}
analytics.skipPoints[address].push(position);
}
Conclusion
This guide bridges the gap between MadMapper's OSC capabilities and Node.js development, providing a practical foundation for receiving video position feedback. While most OSC resources focus on dedicated applications, this approach enables custom JavaScript solutions for unique project requirements.
The key insights are:
- Manual control creation is essential for OSC feedback
- Position values range from 0.0 to 1.0
- Registration with MadMapper enables feedback in Auto mode
- Real-time feedback enables reactive content systems
With these techniques, you can create sophisticated interactive video installations, automated content management systems, and responsive multimedia experiences that go beyond what traditional OSC applications offer.
Top comments (0)