Создаём расширение google chrome для записи экрана и камеры

В данной статье рассмотрим основные моменты при создании расширения google chrome для записи экрана и камеры. Оно может записывать целый экран, отдельное окно или вкладку. В режиме записи экрана можно вставлять окно с фронтальной камерой внутрь страницы на которой активен плагин, либо вне браузера. Также можно включить запись микрофона или звук системы. Ну и дополнительно можно осуществлять запись только с камеры.

В расширении также будет использоваться интернационализация (i18next). Для управления состоянием будет использоваться библиотека onChange. Для перемещения встраиваемой камеры по странице jquery-ui. И для стилизации видеоплеера библиотека plyr-player.

Ссылка на исходный код расширения

Первым делом нужно настроить главный файл расширения google chrome — manifest.json

"action": {
    "default_popup": "html/popup.html"
 },

default_popup: файл HTML, который будет отображаться в выпадающем меню, когда пользователь нажимает на значок действия. В нашем случае это будет loader, после загрузки которого мы будем вставлять iframe на страницу пользователя.

"background": {
    "service_worker": "js/background.js",
    "type": "module"
 },

Файл background.js обязателен для всех расширений google chrome. Он может использоваться для выполнения различных задач в фоновом режиме, таких как обновление данных, отправка запросов на сервер, мониторинг веб-страниц и т.д. Это позволяет расширению работать в фоновом режиме, даже если пользователь не активен в браузере. В нашем случаем в нем нет необходимости, поэтому просто создадим его и оставим пустым.

"content_scripts": [
    {
      "matches": [
        "https://*/*",
        "http://*/*"
      ],
      "js": [
        "js/content-script.js"
      ]
    }
  ],

Пожалуй, главный файл нашего расширения — это content-script.js. Он может использоваться для изменения внешнего вида и поведения веб-страницы, добавления или удаления элементов на странице, взаимодействия с содержимым страницы и т.д. matches: URL-адреса веб-страниц, на которых должен быть запущен сценарий.

"web_accessible_resources": [
    {
      "matches": [
        ""
      ],
      "resources": [
        "html/popup.html",
        "html/iframe.html",
        "js/renderContent/camera.js",
        "js/renderContent/iframe.js",
        "libs/jquery-3.6.0.min.js",
        "libs/jquery-ui.min.js"
      ]
    }
  ],

Файл «web_accessible_resources» в Google Chrome Extension используется для определения ресурсов, которые могут быть доступны на веб-странице, даже если они находятся за пределами расширения.

"permissions": [
    "tabs",
    "storage",
    "downloads",
    "activeTab",
    "scripting"
  ]

Ну и «permissions» является важным элементом в разработке расширений Google Chrome, позволяющим расширению запросить необходимые разрешения у пользователя, чтобы выполнить необходимые действия. Без этого файла расширение не сможет получить доступ к некоторым функциям браузера, что может ограничить его функциональность.

вид popup

setTimeout(() => {
    chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
      chrome.tabs.sendMessage(tabs[0].id, "open")
      window.close()
    })
 }, 1500)

Итак в выпадающем окне popup у нас отображается loader, после загрузки которого с помощью таймера передается сообщение в content-script и закрывается наш popup с помощью window.close ()

chrome.runtime.onMessage.addListener((msg, _, sendResponse) => {
  const iframePlugin = document.querySelector("#record_plugin")

  switch (msg) {
    case "close": {
      iframePlugin?.remove()
      break
    }
    case "open": {
      ;(async () => {
        const src = chrome.runtime.getURL("js/renderContent/iframe.js")
        const contentScript = await import(src)
        contentScript.renderIframe(iframePlugin)
      })()
      break
    }

В content-script мы слушаем сообщения, которые нам приходят из popup и из нашего iframe, который мы встраиваем тут же.

iframe после встраивания на страницу

Внутри iframe будет происходить основная логика расширения. За доступ к содержимому экрана, звуку системы, микрофону и камере будет отвечать класс Media с помощью методов navigator.mediaDevices.getDisplayMedia и navigator.mediaDevices.getUserMedia.

получение видеопотока с камерыа тут получение доступа к видеопотоку экрана

if (state.mode === "screen") {
        combine = new MediaStream([
          ...this.screenStream.getTracks(),
          ...this.voiceStream.getTracks(),
        ])
      } else {
        combine = new MediaStream([
          ...this.cameraStream.getTracks(),
          ...this.voiceStream.getTracks(),
        ])
      }

Так создается поток медиа данных который включает в себя видео и аудио треки в зависимости от режима (запись экрана или запись с камеры).

let blobData = new Blob(data, { type: "video/mp4" })
// Convert the blob data to a url
let url = URL.createObjectURL(blobData)
// Assign the url to the output video tag and anchor
output.src = url
downloadTitle.href = url

Обработка готового потока медиа данных и их помещение внутрь проигрывателя и ссылки (для скачивания видеофайла).

const player = new Plyr("#video", {
    controls: [
      "play-large",
      "play",
      "progress",
      "current-time",
      "volume",
      "captions",
      "settings",
      "fullscreen",
    ],
  })
  const newMedia = new Media()
  const defaultLanguage = lStorage.get("language_plugin") || "en"
  newMedia.getFlowCamera()

  const i18nInstance = i18next.createInstance()
  await i18nInstance.init({
    lng: defaultLanguage,
    debug: false,
    resources,
  })

Итак внутри iframe сперва создаем экземпляр плеера, затем экземпляр Media для работы с потоками видео и аудио, запрашиваем доступ к камере, достаем из localStorage текущий язык и инициализируем тексты.

const initialState = {
    //активность записи
    recording: false,
    language: defaultLanguage,
    // полный экран для плеера
    fullscreen: false,
    // проверка на то, что запись не пустая
    emptyRecord: true,
    // режим - screen или camera
    mode: "screen",
    UIState: {
      wiewIframe: "control",
      switch: {
        microphone: false,
        camera: false,
        cameraLocal: false,
        audio: false,
      },
    },
  }

Создаем стейт iframe, состояние которого будем отслеживать с помощью библиотеки onChange и в следствие этих изменений перерисовывать наш UI.

Также создаем объект со всеми необходимыми элементами, на которые навешиваем обработчики, внутри которых будет меняться наш стейт.

вид UI после записи экрана

Таким образом, в данной статье мы в общих чертах рассмотрели создание расширения google chrome для записи экрана и камеры. Исходный код — для более детального ознакомления с расширением.

© Habrahabr.ru