Inserting a custom media stream into a video meeting, can be difficult. Let’s think of showing a video, live data or a stream from a security camera, dronefeed or bodycam.

With eyeson API and eyeson-js you only need a few lines of code to stream any media directly into a meeting.

You can achive this by drawing something on a canvas and capture its stream, for example a drawing board, or an online game that is visualized on the canvas element. Any other media stream is also possible, like a screencast, or capture a video source. The media stream content is up to you.

There are two functions in eyeson-js to make it work (where stream is your own media stream)

  • eyeson.start(<access_key>, { stream })
  • eyeson.send({ type: ‘replace_stream’, stream })

eyeson.start

Usually one would join a meeting using eyeson.start(<access_key>, { audio: true, video: true }) which would automatically open a media stream from a microphone (audio) and camera (video). Even if you set audio: false, the library would need to access a microphone, but mutes it.

With eyeson.start(<access_key>, { stream }), devices are not accessed and instead the given media stream is used.

Good to know: The given media stream is never touched! It will never get closed or stopped.

const canvas = document.getElementById('stream-canvas');
const context = canvas.getContext('2d');
// Firefox needs a kick before captureStream
context.fillRect(0, 0, canvas.width, canvas.height);

const canvasStream = canvas.captureStream();

eyeson.start(<access_key>, { stream: canvasStream });

Pro tip: Use the sendOnly option if you don’t plan to receive and show the meeting video and to save bandwidth. eyeson.start(<access_key>, { stream, sendOnly: true })

replace_stream event

The replace_stream event can be used at any time to set a new custom media stream.

When the meeting was joined with { audio: true, video: true } where eyeson accesses microphone and camera, it would release the access with the replace_stream event.

Whenever you want to return to mic and cam usage, you can simply call the start_stream event.

eyeson.start(<access_key>, { audio: true, video: true });

// […]
const canvasStream = canvas.captureStream();
eyeson.send({ type: 'replace_stream', stream: canvasStream });

// […]
eyeson.send({ type: 'start_stream', audio: true, video: true });
const firstCanvasStream = firstCanvas.captureStream();
const secondCanvasStream = secondCanvas.captureStream();

eyeson.start(<access_key>, { stream: firstCanvasStream });

// […]
eyeson.send({ type: 'replace_stream', stream: secondCanvasStream });

Mute video/audio

To mute the video and/or audio track in your custom media stream so that it is also hidden in the meeting, you can use the change_stream event from the eyeson-js library.

Heads-up! This will set track.enabled = false in your media stream, so it becomes black in your preview.

let showVideo = true;

function toggleVideo() {
  showVideo = !showVideo;
  eyeson.send({ type: 'change_stream', audio: true, video: showVideo });
}

Notes

It is possible to have a custom media stream with only one audio track or just one video track.

The first audio track with readyState = "live" and the first video track with readyState = "live" are used.

Troubleshoot

Sometimes it seems like the captured canvas stream is not shown in the eyeson meeting. In this case it would help to kick the canvas in order to let your browser know that frames should be captured and sent over.

const canvas = document.getElementById('stream-canvas');
const context = canvas.getContext('2d');
// Firefox needs a kick before captureStream
context.fillRect(0, 0, canvas.width, canvas.height);

const canvasStream = canvas.captureStream();

eyeson.start(<access_key>, { stream: canvasStream, sendOnly: true });

function triggerCanvas() {
  const imgData = context.createImageData(1, 1);
  context.putImageData(imgData, 0, 0);
  setTimeout(triggerCanvas, 1000);
}

setTimeout(triggerCanvas, 1000);