1.1.3 • Published 3 months ago

electron-window-rtc v1.1.3

Weekly downloads
-
License
MIT
Repository
github
Last release
3 months ago

electron-window-rtc

Inspired by electron-peer-connection, electron-window-rtc is a zero-dependency package that allows sharing medias between Electron windows through WebRTC with (almost) zero-latency, depending on application configuration.

It works by creating a main process events hub that acts as a signaling server for the windows through Electron's IPC. Once windows are registered, each renderer process creates a WindowRTCPeerConnection to another window and begins to send/receive media streams.

This package was primarily released to handle video/canvas manipulation before sending it to a window rendered offscreen passed to the Syphon framework with same author's node-syphon package.

Donate

If you find this package useful, contribute to the author's open source work by donating here! Thank you!

paypal

Table of Contents

Install

npm i -s electron-window-rtc
yarn add electron-window-rtc

Usage

See Electron+Vue example for a complete integration example.

Main process

// WindowRTCMain is a singleton.
import { WindowRTCMain } from 'electron-window-rtc';

const senderWindow: BrowserWindow = createWindow(); // Left to application.
const receiverWindow: BrowserWindow = createWindow();

// Note that windows can both send and receive by creating two connection (see below in 'renderer process' section).

WindowRTCMain.register('sender', senderWindow);
WindowRTCMain.register('receiver', receiverWindow);

Renderer process

Sender

import {
  WindowRTCPeerConnection,
  defineIpc,
} from 'electron-window-rtc/renderer';

// Important: define early how to access to IPC object, according to application 'preload' script.
// The IPC object must at least expose `on`, `removeListener`, `send` and `invoke` methods (see IPCObject below).
defineIpc(window.electron.ipcRenderer);

document.addEventListener('DOMContentLoaded', (event) => {
  const windowConnection = new WindowRTCPeerConnection('receiver');

  // Canvas for example...
  const canvas: HTMLCanvasElement = document.getElementById('canvas');

  // Add track from canvas: this will create an 'offer' for 'receiver' window.
  // Note the '240' fps framerate: leaving it empty creates latency in the receiver.
  windowConnection.addStream(canvas.captureStream(240));
});

Receiver

import {
  WindowRTCPeerConnection,
  defineIpc,
} from 'electron-window-rtc/renderer';

// Important: define early how to access to IPC object, according to application 'preload' script.
// The IPC object must at least expose `on`, `removeListener`, `send` and `invoke` methods (see IPCObject below).
defineIpc(window.electron.ipcRenderer);

document.addEventListener('DOMContentLoaded', (event) => {
  const windowConnection = new WindowRTCPeerConnection('sender');

  // Listen to 'track' added by 'sender' window.
  windowConnection.on('track', (event: EventManagerDTO) => {
    const video: HTMLVideoElement = document.getElementById('video');

    const trackEvent: RTCTrackEvent = event.payload;
    const streams = trackEvent.streams;
    for (const stream of streams) {
      // For the sake of this example, keep only one stream, we could also get `streams[0]`.
      video.srcObject = null;
      video.srcObject = stream;
    }
  });
});

API

Main process

Import the singleton.

import { WindowRTCMain } from 'electron-window-rtc';

WindowRTCMain.register

Registers a BrowserWindow for message passing with a unique name.

Parameters

NameTypeDefaultOptional
namestringundefinedfalse
windowBrowserWindowundefinedfalse

Returns void

Throws Error if a window with this name or this window has already been registered.

WindowRTCMain.unregister

Unregister a BrowserWindow from message passing. Fails silently if window can not be found.

Parameters

NameTypeDefaultOptional
namestringundefinedfalse

Returns void

WindowRTCMain.dispose

Dispose the WindowRTCMain singleton by unregistering windows and removing IpcMain listeners.

Returns void

Renderer process

import { WindowRTCPeerConnection, defineIpc } from 'electron-window-rtc/renderer';

defineIpc

Define the global IpcObject to use for thiw window for communicating with main process.

Parameters

NameTypeDefaultOptional
ipcIpcObjectundefinedfalse

Returns void

WindowRTCPeerConnection.with

Create a WindowRTCPeerConnection with window of given name.

Parameters

NameTypeDefaultOptional
namestringundefinedfalse

Returns Promise<WindowRTCPeerConnection> An instance of WindowRTCPeerConnection.

Throws Error if IpcObject was not defined with defineIpc, or if the window with name was not registered, or if this window was not registered.

windowPeerConnectionInstance.addStream

Add a stream to send to connected window and create an offer sent to receiving window.

Parameters

NameTypeDefaultOptional
streamMediaStreamundefinedfalse
maxBitrate{ audio: number; video: number; }5000 (Kbps) for bothtrue

Returns Promise<void>

windowPeerConnectionInstance.requestOffer

Request the peer window to send an offer.

Returns Promise<void>

windowPeerConnectionInstance.on

Register a listener to WindowRTCPeerConnection instance's events.

Parameters

NameTypeDefaultOptional
channelWindowRTCEventChannelundefinedfalse
listener(event: WindowRTCEvent) => voidundefinedfalse

Returns void

windowPeerConnectionInstance.off

Unregister a listener or a whole channel from WindowRTCPeerConnection instance's events. If listener is left undefined, unregisters all listeners for this channel.

Parameters

NameTypeDefaultOptional
channelWindowRTCEventChannelundefinedfalse
listener?(event: WindowRTCEvent) => voidundefinedtrue

Returns void

windowPeerConnectionInstance.dispose

Close the connection with the other window and remove all listeners.

Returns void

Events

WindowRTCEvent (see below) sent by WindowRTCPeerConnection with different payloads according to the event emitted.

ChannelPayload TypeEmitted
*anyFor each event.
icecandidateRTCIceCandidateOn icecandidate RTCPeerConnection event.
iceconnectionstatechangeEventOn iceconnectionstatechange RTCPeerConnection event.
icecandidateerrorRTCPeerConnectionIceErrorEventOn icecandidateerror RTCPeerConnection event.
icegatheringstatechangeEventOn icegatheringstatechange RTCPeerConnection event.
negotiationneededEventOn negotiationneeded RTCPeerConnection event.
signalingstatechangeEventOn signalingstatechange RTCPeerConnection event.
trackRTCTrackEventOn track RTCPeerConnection event.
leaveundefined | ErrorWhen local window leaves.
peer-leftundefined | ErrorWhen remote window leaves.
request-offerundefinedWhen a window requests an offer from its peer window.
sent-offerRTCSessionDescriptionInitWhen a window has sent an offer.
received-offer{ offer: SdpObject, answer: RTCSessionDescriptionInit }When a window has received an offer and sent an answer.
received-answerSdpObjectWhen a window has received an answer.
received-candidateRTCIceCandidateWhen a window has received an ICE candidate.
errorErrorWhen an error occurred in IPC communication.

Types

IpcObject

Describes the IPC object used by electron-window-rtc

interface IpcObject {
  on: (
    channel: string,
    callback: (event: IpcRendererEvent, ...args: any[]) => void
  ) => void;
  removeListener: (
    channel: string,
    callback: (event: IpcRendererEvent, ...args: any[]) => void
  ) => void;
  send: (channel: string, ...args: any[]) => void;
  invoke: (channel: string, ...args: any[]) => Promise<any>;
}

WindowRTCEvent

Describes the generic event data sent and received by WindowRTCPeerConnection.

interface WindowRTCEvent {
  /**
   * This window name the event was emitted from.
   */
  local: string;
  /**
   * Peer window name.
   */
  remote: string;
  payload: any;
}

// Example.
windowConnection.on('track', (event: WindowRTCEvent) => {
  const local: string = event.local;
  const receiveremoter: string = event.remote;
  const trackEvent: RTCTrackEvent = event.payload;
});

Using canvas

When using canvas to get an image to send to other windows, application should set the frameRequestRate parameter of canvas.captureStream to a high framerate to avoid latency on the receiver side.

Setting frameRequestRate

alt good_performance

Fig. 1: const stream = canvas.captureStream(240); in Sender window doesn't induce latency. Look at the result of performance.now() sent by the Sender.

Without setting frameRequestRate

alt bad_performance

Fig. 2: Without setting frameRequestRate: a latency of ~30ms is induced.

Using WebAudio API

See example's SenderWindow and ReceiverWindow for the setup of the final stream.

alt with_audio

Fig. 3: Sending audio for visualization with or without playing it in the Sender window.

Known Issues

  • Electron example doesn't handle very well reloading windows and introduces latency. We may explore recreating stream on reload.
  • Reloading Sender window takes a lot of time for Receiver window to reconnect, whereas the requestOffer method allows reconnecting quickly on Receiver window's reload.
  • Closing and opening again windows has not been tested: it may involve some logic in the main process to be integrated in WindowRTCMain.
  • Latency differs when using the example in development mode or production mode.

License

MIT

1.1.3

3 months ago

1.1.2

3 months ago

1.1.1

3 months ago

1.1.0

3 months ago

1.0.8

3 months ago

1.0.7

4 months ago

1.0.6

4 months ago

1.0.5

4 months ago

1.0.4

4 months ago

1.0.3

4 months ago

1.0.2

4 months ago

1.0.1

4 months ago

1.0.0

4 months ago