Tutorial: Electron uses TRTC Web SDK for screen sharing

Electron uses TRTC Web SDK for screen sharing

This article describes how to do screen share when using the TRTC Web SDK in Electron.

Introduction

Electron makes it easy to create windows and load web pages for use. Because Electron does not implement the browser-supported WebRTC standard getDisplayMedia interface, if a web page contains WebRTC screen share-related logic, it will not be possible to use the TRTC Web SDK for screen share in Electron as a normal browser.

The problem is that Electron does not implement the getDisplayMedia interface for WebRTC, which is supported by the browser. This makes it impossible to use TRTC Web SDK for screen share directly in Electron.

How do I do screen share in Electron using the TRTC Web SDK?

Solutions

The preferred recommendation is to use the TRTC Electron SDK, and if you only want to use the TRTC Web SDK, the options are as follows.

To implement screen share, you need to use Electron's API:desktopCapturer.getSources({ types: ['window', 'screen'] })

  1. The main process main.js listens for the page to finish loading, gets the list of screen share sources via desktopCapturer.getSources, and sends the list of screen share sources to the rendering process.
  2. The rendering process listens to the main process events to get a list of screen share sources.
  3. Create screen share streams and push them through the TRTC Web SDK.
    1. Get the screen-sharing MediaStream from the system API via navigator.mediaDevices.getUserMedia().
    2. Create a screen share stream to be published from the videoTrack obtained from the screen stream via the custom capture function of TRTC.createStream().
    3. Publish screen share stream
// 【1】main.js Master process gets list of screen share sources
const { app, BrowserWindow, desktopCapturer, systemPreferences } = require('electron');
function createWindow () {
  const win = new BrowserWindow({
    // ……
  });
  win.loadFile('./src/index.html');
  win.webContents.on('did-finish-load', async () => {
    if (win) {
      desktopCapturer.getSources({ types: ['window', 'screen'] }).then(async sources => {
        win.webContents.send('SEND_SCREEN_SHARE_SOURCES', sources);
      });
    }
  });
}
// 【2】Render process listens to main process events to get a list of screen share sources
const { ipcRenderer } = require('electron');
let shareSourceList = [];
ipcRenderer.on('SEND_SCREEN_SHARE_SOURCES', async (event, sources) => {
  const selectContainer = window.document.getElementById('screen-share-select');
  shareSourceList = sources;
  sources.forEach(obj => {
    const optionElement = document.createElement('option');
    optionElement.innerText = `${obj.name}`;
    selectContainer.appendChild(optionElement);
  });
})
// 【3】Rendering process push screen share
async function startScreenShare() {
  const selectContainer = document.getElementById('screen-share-select');
  const selectValue = selectContainer.options[selectContainer.selectedIndex].value;
  const [ source ] = shareSourceList.filter(obj => obj.name === `${selectValue}`);
  try {
    const stream = await navigator.mediaDevices.getUserMedia({
      audio: false,
      video: {
        mandatory: {
          chromeMediaSource: 'desktop',
          chromeMediaSourceId: source.id, // screen share source id
          minWidth: 1280,
          maxWidth: 1280,
          minHeight: 720,
          maxHeight: 720
        }
      }
    });
    const shareClient = TRTC.createClient({
      // ……
    });
    const shareStream = TRTC.createStream({
      videoSource: stream.getVideoTracks()[0]
    });
    await shareStream.initialize();
    await shareClient.join({ roomId: parseInt(roomId) });
    shareStream && await shareClient.publish(shareStream);
  } catch (error) {
    console.error('start screen share error = ', error)
  }
}

Common Problems

  • macOS Monterey(12.2.1), device permissions need to be requested in the master process
    async function checkAndApplyDeviceAccessPrivilege() {
      const cameraPrivilege = systemPreferences.getMediaAccessStatus('camera');
      console.log(
        `checkAndApplyDeviceAccessPrivilege before apply cameraPrivilege: ${cameraPrivilege}`
      );
      if (cameraPrivilege !== 'granted') {
        await systemPreferences.askForMediaAccess('camera');
      }
      const micPrivilege = systemPreferences.getMediaAccessStatus('microphone');
      console.log(
        `checkAndApplyDeviceAccessPrivilege before apply micPrivilege: ${micPrivilege}`
      );
      if (micPrivilege !== 'granted') {
        await systemPreferences.askForMediaAccess('microphone');
      }
      const screenPrivilege = systemPreferences.getMediaAccessStatus('screen');
      console.log(
        `checkAndApplyDeviceAccessPrivilege before apply screenPrivilege: ${screenPrivilege}`
      );
    }