Playjector API

Game capture and control bridge for communicating with the native C++ Playjector application via TCP sockets

Game Capture

Direct communication with native C++ game capture engine

TCP Socket Bridge

Reliable TCP communication with automatic reconnection

Process Management

Automatic spawning and lifecycle management of Playjector process

Quick Start

Get started with the Playjector connector:

import { makePlayjectorConnector } from '@playcastdotio/PlayjectorConnector';

const connector = makePlayjectorConnector(7555, {
  callbacks: {
    message: (message) => {
      console.log('Received from Playjector:', message);
      handlePlayjectorMessage(message);
    },
    ready: () => {
      console.log('Playjector connected and ready');
      // Start sending commands
    },
    error: (error) => {
      console.error('Playjector error:', error);
    },
    close: () => {
      console.log('Playjector connection closed');
    }
  },
  spawn: {
    path: 'C:\\Program Files (x86)\\PlayCast\\PlayJector',
    args: ['-default_port', '7555'],
    spawnWaitTime: 1000
  },
  autoReconnect: {
    enabled: true,
    timeout: 3000
  }
});

// Connect to Playjector
await connector.connect();
Prerequisites: The Playjector executable must be installed on the system. The connector will attempt to find it in standard installation paths or use the provided path.

Connector Configuration

Configure the Playjector connector with callbacks, spawning options, and connection settings.

makePlayjectorConnector(port: number, config: PlayjectorConfig)

Parameters

Parameter Type Required Description
port number Required TCP port for communication with Playjector
config PlayjectorConfig Required Configuration object with callbacks and options

Configuration Sections

Callbacks

Event handlers for Playjector communication and lifecycle events.

  • message(message) - Handle incoming messages from Playjector
  • ready() - Called when connection is established
  • refused() - Called when connection is refused
  • reset() - Called when connection is reset
  • error(message) - Handle connection errors
  • close() - Called when connection closes
  • playjectorClose(code) - Called when Playjector process exits
  • disconnected() - Called when connector disconnects
const config = {
  callbacks: {
    message: (message) => {
      // Validate and process message
      if (validators.validatePlaycastMessage(message)) {
        handleValidMessage(message);
      }
    },
    ready: () => {
      // Connection established, send initialization commands
      connector.sendMessage(initializationMessage);
    },
    error: (errorMsg) => {
      // Log error and notify user
      logger.error('Playjector error:', errorMsg);
      notifyUser('Connection error occurred');
    },
    playjectorClose: (exitCode) => {
      // Handle process exit
      if (exitCode !== 0) {
        logger.warn('Playjector exited with code:', exitCode);
      }
    }
  }
};

Process Spawning

Configure automatic spawning of the Playjector process.

Option Type Default Description
path string Auto-detected Path to Playjector executable directory
args string[] [] Command line arguments for Playjector
spawnWaitTime number 500 Time to wait after spawning process (ms)
logging.port number undefined Port for Playjector logging output
const config = {
  spawn: {
    path: 'C:\\Program Files (x86)\\PlayCast\\PlayJector',
    args: [
      '-default_port', '7555',
      '-quality', 'high',
      '-fps', '60'
    ],
    spawnWaitTime: 1500,  // Wait longer for slower systems
    logging: {
      port: 7556  // Enable logging on separate port
    }
  }
};

Auto-Reconnect

Configure automatic reconnection behavior when connection is lost.

Option Type Default Description
enabled boolean false Enable automatic reconnection
timeout number 3000 Time between reconnection attempts (ms)
const config = {
  autoReconnect: {
    enabled: true,
    timeout: 5000  // Wait 5 seconds between attempts
  },
  maxConnectionAttempts: 10  // Limit total attempts
};

Additional Options

Option Type Default Description
disableMessageVerification boolean false Skip message validation for performance
hostIpOverride string '127.0.0.1' Override connection IP address
maxConnectionAttempts number -1 (unlimited) Maximum number of connection attempts
logging boolean false Enable detailed logging

Connection Management

Managing the TCP connection to the Playjector process.

Connection Methods

connect(): Promise<void>

Establish connection to Playjector. Will spawn process if configured and needed.

disconnect(): void

Close connection and terminate Playjector process if spawned.

// Connect with error handling
try {
  await connector.connect();
  console.log('Successfully connected to Playjector');
} catch (error) {
  console.error('Failed to connect:', error);
  // Handle connection failure
  handleConnectionError(error);
}

// Disconnect cleanly
connector.disconnect();
console.log('Disconnected from Playjector');

Connection States and Events

const connector = makePlayjectorConnector(7555, {
  callbacks: {
    ready: () => {
      // Connection established successfully
      updateConnectionStatus('connected');
      enableGameControls();
    },
    refused: () => {
      // Connection refused (Playjector not listening)
      updateConnectionStatus('refused');
      showRetryButton();
    },
    reset: () => {
      // Connection was reset unexpectedly
      updateConnectionStatus('reset');
      attemptReconnection();
    },
    close: () => {
      // Connection closed normally
      updateConnectionStatus('closed');
      disableGameControls();
    },
    error: (errorMsg) => {
      // Connection error occurred
      updateConnectionStatus('error');
      showErrorMessage(errorMsg);
    }
  }
});
Important: Always handle connection events properly to provide good user experience during network issues or process failures.

Message Handling

Sending and receiving messages through the Playjector bridge.

Sending Messages

sendMessage(message: PlaycastMessage<PlaycastMessageTarget>): void
import { generators } from '@playcastdotio/Messaging';

// Send gamepad input
const gamepadMessage = generators.gamepad.createButtonEvent({
  userId: 'player123',
  button: 'A',
  pressed: true,
  timestamp: Date.now()
});

connector.sendMessage(gamepadMessage);

// Send mouse input
const mouseMessage = generators.mouse.createMoveEvent({
  userId: 'player123',
  x: 100,
  y: 200,
  deltaX: 5,
  deltaY: -3,
  timestamp: Date.now()
});

connector.sendMessage(mouseMessage);

// Send stream quality command
const qualityMessage = generators.stream.createQualityChange({
  userId: 'player123',
  preset: 'high',
  bitrate: 5000000,
  timestamp: Date.now()
});

connector.sendMessage(qualityMessage);

Receiving Messages

const connector = makePlayjectorConnector(7555, {
  callbacks: {
    message: (message) => {
      // Messages are automatically validated unless disabled
      console.log('Message type:', message.header.target);
      console.log('Action:', message.header.action);

      // Route message based on type
      switch (message.header.target) {
        case 'stream':
          handleStreamMessage(message);
          break;
        case 'system':
          handleSystemMessage(message);
          break;
        case 'encoder':
          handleEncoderMessage(message);
          break;
        default:
          console.log('Unknown message type:', message.header.target);
      }
    }
  }
});

function handleStreamMessage(message) {
  switch (message.header.action) {
    case 'qualityChanged':
      updateQualityIndicator(message.body.message.quality);
      break;
    case 'frameRateChanged':
      updateFrameRateDisplay(message.body.message.frameRate);
      break;
    case 'bitrateChanged':
      updateBitrateDisplay(message.body.message.bitrate);
      break;
  }
}

Message Capture and Debugging

const connector = makePlayjectorConnector(7555, {
  captureMessages: {
    enabled: true,
    maximumMessagesToCapture: 1000,
    outgoing: [],
    incoming: [],
    combinedState: [],
    onMessageSent: (message) => {
      console.log('Sent to Playjector:', message.header.target);
    },
    onMessageReceived: (message) => {
      console.log('Received from Playjector:', message.header.target);
    }
  }
});

// Access captured messages for debugging
console.log('Outgoing messages:', connector.config.captureMessages.outgoing);
console.log('Incoming messages:', connector.config.captureMessages.incoming);
console.log('All messages:', connector.config.captureMessages.combinedState);

Process Spawning

Automatic management of the Playjector process lifecycle.

Process Discovery

The connector automatically searches for the Playjector executable in standard installation paths:

  • C:\Program Files (x86)\PlayCast\PlayJector
  • Custom path specified in configuration
const connector = makePlayjectorConnector(7555, {
  spawn: {
    // Custom installation path
    path: 'D:\\Games\\PlayCast\\PlayJector',
    args: [
      '-default_port', '7555',
      '-quality', 'ultra',
      '-fps', '120',
      '-resolution', '1920x1080'
    ],
    spawnWaitTime: 2000  // Wait 2 seconds for startup
  },
  callbacks: {
    playjectorClose: (exitCode) => {
      if (exitCode === 0) {
        console.log('Playjector exited normally');
      } else {
        console.error('Playjector crashed with code:', exitCode);
        // Handle crash recovery
        handlePlayjectorCrash(exitCode);
      }
    }
  }
});

Process Arguments

Argument Description Example
-default_port TCP port for communication '-default_port', '7555'
-logging_port Port for logging output '-logging_port', '7556'
-quality Initial quality preset '-quality', 'high'
-fps Target frame rate '-fps', '60'
-resolution Capture resolution '-resolution', '1920x1080'
Note: The connector will automatically add required port arguments if not specified in the args array.

Auto-Reconnect

Robust reconnection handling for network issues and process failures.

Reconnection Scenarios

  • Connection Refused - Playjector not running or port busy
  • Connection Reset - Network issue or Playjector restart
  • Connection Closed - Normal disconnection with reconnect enabled
  • Process Exit - Playjector process crashed or was terminated
const connector = makePlayjectorConnector(7555, {
  autoReconnect: {
    enabled: true,
    timeout: 3000  // 3 second delay between attempts
  },
  maxConnectionAttempts: 5,  // Limit attempts to prevent infinite loop

  callbacks: {
    refused: () => {
      console.log('Connection refused, will retry...');
      updateConnectionStatus('reconnecting');
    },
    reset: () => {
      console.log('Connection reset, attempting reconnection...');
      updateConnectionStatus('reconnecting');
    },
    close: () => {
      if (autoReconnectEnabled) {
        console.log('Connection closed, will reconnect...');
        updateConnectionStatus('reconnecting');
      } else {
        updateConnectionStatus('disconnected');
      }
    },
    ready: () => {
      console.log('Reconnection successful');
      updateConnectionStatus('connected');
      resetConnectionAttemptCounter();
    }
  }
});

Manual Reconnection Control

// Disable auto-reconnect temporarily
connector.config.autoReconnect.enabled = false;

// Manual reconnection with custom logic
async function manualReconnect() {
  const maxRetries = 3;
  let retryCount = 0;

  while (retryCount < maxRetries) {
    try {
      await connector.connect();
      console.log('Manual reconnection successful');
      return true;
    } catch (error) {
      retryCount++;
      console.log(`Manual reconnection attempt ${retryCount} failed`);

      if (retryCount < maxRetries) {
        // Progressive backoff
        const delay = Math.pow(2, retryCount) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }

  console.error('Manual reconnection failed after all attempts');
  return false;
}

// Re-enable auto-reconnect
connector.config.autoReconnect.enabled = true;
Connection Limits: Set reasonable limits on connection attempts to prevent resource exhaustion during persistent failures.

Error Handling and Troubleshooting

Common issues and solutions when working with the Playjector connector.

Common Errors

Playjector Path Not Found

// Error: "Playjector path does not exist"
// Solution: Specify correct path or install Playjector

const connector = makePlayjectorConnector(7555, {
  spawn: {
    // Check if path exists before using
    path: process.env.PLAYJECTOR_PATH ||
          'C:\\Program Files (x86)\\PlayCast\\PlayJector'
  },
  callbacks: {
    error: (errorMsg) => {
      if (errorMsg.includes('path does not exist')) {
        showPlayjectorInstallationPrompt();
      }
    }
  }
});

Port Already in Use

// Error: "EADDRINUSE" - Port already in use
// Solution: Use different port or kill existing process

const connector = makePlayjectorConnector(7555, {
  callbacks: {
    refused: () => {
      // Try alternative port
      tryAlternativePort();
    }
  }
});

async function tryAlternativePort() {
  const alternatePorts = [7556, 7557, 7558];

  for (const port of alternatePorts) {
    try {
      const newConnector = makePlayjectorConnector(port, config);
      await newConnector.connect();
      console.log(`Connected on alternative port: ${port}`);
      return newConnector;
    } catch (error) {
      continue;
    }
  }

  throw new Error('No available ports found');
}

Message Validation Failures

// Error: "Message from PlayJector failed validation"
// Solution: Enable logging to debug message format

const connector = makePlayjectorConnector(7555, {
  logging: true,  // Enable detailed logging
  disableMessageVerification: false,  // Keep validation enabled
  callbacks: {
    message: (message) => {
      try {
        // Additional validation
        if (typeof message === 'string') {
          console.log('Raw message:', message);
          message = JSON.parse(message);
        }

        if (message && message.header && message.body) {
          handleValidMessage(message);
        } else {
          console.warn('Invalid message structure:', message);
        }
      } catch (error) {
        console.error('Message processing error:', error);
      }
    }
  }
});