DEV Community

Cover image for How to Receive Video Position Feedback from MadMapper using Node.js OSC
Gustavo Henrique
Gustavo Henrique

Posted on

How to Receive Video Position Feedback from MadMapper using Node.js OSC

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

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
Enter fullscreen mode Exit fullscreen mode

How OSC Feedback Works

  1. You create a manual OSC control in MadMapper
  2. You map this control to a video parameter (like position)
  3. MadMapper sends feedback to your OSC address when the parameter changes
  4. 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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

MadMapper Configuration

Step 1: OSC Preferences

  1. Open MadMapper Preferences
  2. Go to OSC section
  3. Set:
    • OSC Input Port: 8010
    • Feedback Mode: Auto or Manual
    • Feedback IP: Auto (or your computer's IP)
    • Feedback Port: 8011

Step 2: Create Manual OSC Controls

This is crucial for feedback to work!

  1. Open Window → Controls
  2. Click "+" to add new control
  3. Select OSC type
  4. 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

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.

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");
});
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Manual control creation is essential for OSC feedback
  2. Position values range from 0.0 to 1.0
  3. Registration with MadMapper enables feedback in Auto mode
  4. 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)