Search code examples
google-chrome-extension

Chrome Extension MV3, Use mediaDevice.getUserMedia() in Contest-script


i'm building a chrome extension that is used for screen and video capturing. Curently I'm having issuing with capturing both user's camera and screen at the same time. I had to capture the user's camera and display it to them while the recording of screen is going on.

I had initilized the request from the offscreen.js but I couldnt get it to display it on the screen because I couldn't use document.querySelector from there as it return null

Second I have tried initializing the request from content-script but it would appear with the name of the hosting site.

My question now is:

  1. Is there a way I could initialize the request from content-script to still appear with the extension name
  2. How could I capture the video and get the stream displayed to the user from inside offscreen js

.. Any better way of getting this to work is welcomed. This implementation is already in loom chrome extension. But I couldnt figure out how they got that to work


Solution

  • Finally, I got it working. first, you need to update your Chrome browser to the latest version at the time of my question I was using version 117, but upon updating to version 123, most of the bottlenecks gave way;

    1. I could initialize navigator.getUserMedia() and its counterpart navigator.getDisplayMedia() from the offscreen script. Then to handle my issue of accessing both camera, display and audio all at once and still showing a small preview screen to the user: I initialized the request from the offscreen page so I can access the camera and screen from any webpage the user is visiting. Then upon initializing, I sent a message to the content script which houses the code for initialising the front camera to have it appear at the bottom of the users' scene. the message could only be sent through the background script.
    // background.js 
    
    const OFFSCREEN_DOCUMENT_PATH = 'src/offscreen/index.html'
    let creating:any = null
    
    async function hasDocument() {
      const offscreenUrl = chrome.runtime.getURL(OFFSCREEN_DOCUMENT_PATH)
      const existingContexts = await chrome.runtime.getContexts({
        contextTypes: ['OFFSCREEN_DOCUMENT'],
        documentUrls: [offscreenUrl],
      })
    
      // console.log(existingContexts)
      if (existingContexts.length > 0) {
        return true
      }
      return false
    }
    
    
    async function setupOffscreenDocument(path) {
      if (!(await hasDocument())) {
        if (creating) {
          await creating
        } else {
          creating = chrome.offscreen.createDocument({
            url: chrome.runtime.getURL(path),
            reasons: ['USER_MEDIA', 'BLOBS', 'DISPLAY_MEDIA', 'WEB_RTC'],
            justification: 'Recording user media ',
          })
          await creating
          // write code to claim offscreen as a client
    
          creating = null
        }
      }
    
      const offscreenUrl = chrome.runtime.getURL(path)
      const existingContexts = await chrome.runtime.getContexts({
        contextTypes: ['OFFSCREEN_DOCUMENT'],
        documentUrls: [offscreenUrl],
      })
    
      return true
    }
    
    async function closeOffscreenDocument() {
      if (!(await hasDocument())) {
        return
      }
    
      await chrome.offscreen.closeDocument()
    }
    
    async function startRecording(message) {
      await setupOffscreenDocument(OFFSCREEN_DOCUMENT_PATH)
    
      chrome.runtime.sendMessage({
        target: 'offscreen',
        type: 'get-user-recording',
        data: ''
      })
    }
    
    ........................ 
    
    // offscreen.js script
    chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
      if (message.type == 'get-user-recording' && message.target == 'offscreen') {
        handleMessage(message.data)
        sendResponse()
      } else if (
        message.type == 'stop-recording' &&
        message.target == 'offscreen'
      ) {
        //stopRecording()
        recorder.stop()
        sendResponse()
      }
    })
    
    
    // content script
    
    chrome.runtime.onMessage.addListener(async (msg) => {
      if (msg.type == 'notice' && msg.target == 'start_camera') {
    
    .......
    
      } else if (msg.type == 'notice' && msg.target == 'stop_camera') {
    ......
      }
    })
    
    

    To end the camera, the message is also related from offscreen to the content script through the background script to request the small camera be stopped.

    1. Initialize the request from the content script to still appear with the extension name. When a content script is enclosed inside an iframe, the media permission and request will bear the name of the extension rather than the name of the webpage the camera is appearing on. I learnt this from a post on Github: https://github.com/GoogleChrome/chrome-extensions-samples/issues/821