Шпаргалка по работе с медиа в браузере

от автора

Привет, друзья!

В данной шпаргалке представлены все основные интерфейсы и методы по работе с медиа в браузере, описываемые в следующих спецификациях:

Шпаргалка представлена в форме вопрос-ответ.

Туториалы по теме:

Если вам это интересно, прошу под кат.

Содержание:

1. Как получить список медиаустройств пользователя?

Для получения списка медиаустройств пользователя предназначен метод enumerateDevices интерфейса MediaDevices объекта Navigator:

const devices = await navigator.mediaDevices.enumerateDevices()

Список моих устройств:

Свойство kind может использоваться для формирования требований (constraints) к медиапотоку (MediaStream) (далее — поток) (см. ниже), поэтому имеет смысл временно сохранять в браузере информацию о доступных устройствах пользователя:

const STORAGE_KEY = 'user_media_devices'  export async function enumerateDevices() {   try {     const devices = sessionStorage.getItem(STORAGE_KEY)       ? JSON.parse(sessionStorage.getItem(STORAGE_KEY))       : await navigator.mediaDevices.enumerateDevices()      if (!sessionStorage.getItem(STORAGE_KEY)) {       sessionStorage.setItem(STORAGE_KEY, JSON.stringify(devices))     }      return { devices }   } catch (error) {     return { error }   } }

Обработчик:

const stringify = (data) => JSON.stringify(data, null, 2)  const handleError = (e) => {   console.error(e) }  // <button id="enumerateDevicesBtn">Enumerate devices</button> enumerateDevicesBtn.onclick = async () => {   const { devices, error } = await enumerateDevices()   if (error) return handleError(error)    // <pre id="logBox"></pre>   logBox.textContent = stringify(devices) }

2. Как получить список требований к потоку, поддерживаемых браузером?

Для получения списка требований к потоку, поддерживаемых браузером, предназначен метод getSupportedConstraints:

const constraints = await navigator.mediaDevices.getSupportedConstraints()

Список требований, поддерживаемых моим браузером (последняя версия Chrome):

Обратите внимание: в данном списке представлены не все требования, которые можно применять к потоку. Некоторые требования из списка относятся к категории «продвинутых» (advanced) и применяются несколько иначе, чем обычные. Многие требования являются экспериментальными и на сегодняшний день поддерживаются не в полной мере.

export async function getSupportedConstraints() {   try {     const constraints = await navigator.mediaDevices.getSupportedConstraints()     return { constraints }   } catch (error) {     return { error }   } }

Обработчик:

// <button id="getSupportedConstraintsBtn">Get supported constraints</button> getSupportedConstraintsBtn.onclick = async () => {   const { constraints, error } = await getSupportedConstraints()   if (error) return handleError(error)    logBox.textContent = stringify(constraints) }

3. Как захватить поток с устройств пользователя?

Для захвата потока с устройств пользователя используется метод getUserMedia:

const stream = await navigator.mediaDevices.getUserMedia(constraints?)

Данный метод принимает опциональные требования к потоку:

Дефолтные требования:

{ audio: true, video: true }

Пример кастомных требований:

export const DEFAULT_AUDIO_CONSTRAINTS = {   echoCancellation: true,   autoGainControl: true,   noiseSuppression: true }  export const DEFAULT_VIDEO_CONSTRAINTS = {   width: 1920,   height: 1080,   frameRate: 60 }

getUserMedia возвращает поток с устройств пользователя:

Поток представляет собой коллекцию медиатреков (MediaStreamTrack) (далее — трек):

Поток предоставляет несколько методов для работы с треками:

  • getTracks — возвращает список медиатреков;
  • getAudioTracks — возвращает список аудиотреков;
  • getVideoTracks — возвращает список видеотреков;
  • addTrack — добавляет трек в поток;
  • removeTrack — удаляет трек из потока и др.

Обратите внимание: захваченный поток должен быть «одиночкой» (singleton). Это позволяет избежать повторного захвата и правильно останавливать захват.

let mediaStream  export async function getUserMedia(   constraints = {     audio: DEFAULT_AUDIO_CONSTRAINTS,     video: DEFAULT_VIDEO_CONSTRAINTS   } ) {   try {     const stream = mediaStream       ? mediaStream       : (mediaStream = await navigator.mediaDevices.getUserMedia(constraints))      const tracks = stream.getTracks()     const audioTracks = stream.getAudioTracks()     const videoTracks = stream.getVideoTracks()      return { stream, tracks, audioTracks, videoTracks }   } catch (error) {     return { error }   } }

Для прямой передачи потока в приемник (например, DOM-элемент video) используется свойство srcObject. Приемник должен иметь атрибуты autoplay и muted:

// <video id="streamReceiver" controls autoplay muted></video> streamReceiver.srcObject = stream

Трек предоставляет такие методы, как:

  • getCapabilities — возвращает список возможностей (настроек), поддерживаемых треком;
  • getConstraints — возвращает список требований, примененных к треку;
  • getSettings — возвращает список требований и настроек, примененных к треку;
  • applyConstraints — применяет требования к треку;
  • stop — останавливает получение данных из источника трека и др.

Функция для получения потока и треков с применением к потоку поддерживаемых требований, передачей потока в приемник, получением информации о первом треке и отображением этой информации на экране может выглядеть следующим образом:

// <button id="getUserMediaBtn">Get user media</button> getUserMediaBtn.onclick = async () => {   const { devices, error: devicesError } = await enumerateDevices()   if (devicesError) return handleError(devicesError)    let constraints   if (devices.some((device) => device.kind === 'audioinput')) {     constraints = { audio: DEFAULT_AUDIO_CONSTRAINTS }   }   if (devices.some((device) => device.kind === 'videoinput')) {     constraints = { ...constraints, video: DEFAULT_VIDEO_CONSTRAINTS }   }   if (!constraints) {     return handleError('User has no devices to capture.')   }    const { stream, tracks, error: mediaError } = await getUserMedia(constraints)   if (mediaError) return handleError(mediaError)   console.log('@stream', stream)    streamReceiver.srcObject = stream    const [firstTrack] = tracks   console.log('@first track', firstTrack)    const trackCapabilities = firstTrack.getCapabilities()   const trackConstraints = firstTrack.getConstraints()   const trackSettings = firstTrack.getSettings()    logBox.textContent = stringify({     trackCapabilities,     trackConstraints,     trackSettings   }) }

Пример захваченного потока и первого трека:

Пример информации о треке:

4. Как захватить поток с экрана пользователя?

Для захвата потока с экрана пользователя предназначен метод getDisplayMedia:

const stream = await navigator.mediaDevices.getDisplayMedia(constraints?)

В целом, данный метод аналогичен методу getUserMedia, но поддерживает несколько дополнительных требований к потоку:

Пример дополнительных требований:

export const ADDITIONAL_VIDEO_CONSTRAINTS = {   displaySurface: 'window',   cursor: 'motion' }

Обратите внимание: на сегодняшний день аудио при захвате экрана не поддерживается.

Функция для захвата экрана:

// поток должен быть одиночкой let displayStream  export async function getDisplayMedia(   constraints = {     video: { ...DEFAULT_VIDEO_CONSTRAINTS, ...ADDITIONAL_VIDEO_CONSTRAINTS }   } ) {   try {     const stream = displayStream       ? displayStream       : (displayStream = await navigator.mediaDevices.getDisplayMedia(constraints))      const [tracks, audioTracks, videoTracks] = [       stream.getTracks(),       stream.getAudioTracks(),       stream.getVideoTracks()     ]      return { stream, tracks, audioTracks, videoTracks }   } catch (error) {     return { error }   } }

Соответствующий обработчик:

// <button id="getDisplayMediaBtn">Get display media</button> getDisplayMediaBtn.onclick = async () => {   const { stream, tracks, error } = await getDisplayMedia()   if (error) return handleError(error)   console.log('@display stream', stream)    streamReceiver.srcObject = stream    const [firstTrack] = tracks   console.log('@display first track', firstTrack)    const [trackCapabilities, trackConstraints, trackSettings] = [     firstTrack.getCapabilities(),     firstTrack.getConstraints(),     firstTrack.getSettings()   ]    logBox.textContent = stringify({     trackCapabilities,     trackConstraints,     trackSettings   }) }

Пример захваченного потока и первого трека:

Пример информации о треке:

5. Как захватить поток из DOM-элемента?

Для захвата потока из таких DOM-элементов, как audio, video и canvas используется метод captureStream интерфейса HTMLMediaElement или, соответственно, HTMLCanvasElement:

const stream = await mediaElement.captureStream()

При захвате потока из медиаэлемента имеет смысл проверять, во-первых, что переданный аргумент является медиаэлементом, во-вторых, готовность медиа к воспроизведению до конца без прерываний с помощью свойства readyState:

if (!(mediaElement instanceof HTMLMediaElement)) {   throw new Error('Argument must be an instance of HTMLMediaElement.') } if (mediaElement.readyState !== 4) {   throw new Error(     'Media element has not enough data to be played through the end without interruption.'   ) }

В случае с DOM-элементами может одновременно захватываться несколько потоков.

Функция для захвата потока из медиаэлемента:

let mediaElementStreams = []  export async function captureStreamFromMediaElement(mediaElement) {   if (!(mediaElement instanceof HTMLMediaElement)) {     throw new Error('Argument must be an instance of HTMLMediaElement.')   }   if (mediaElement.readyState !== 4) {     throw new Error(       'Media element has not enough data to be played through the end without interruption.'     )   }   try {     const stream = await mediaElement.captureStream()     mediaElementStreams.push(stream)      const [tracks, audioTracks, videoTracks] = [       stream.getTracks(),       stream.getAudioTracks(),       stream.getVideoTracks()     ]      return { stream, tracks, audioTracks, videoTracks }   } catch (error) {     return { error }   } }

Соответствующий обработчик:

// <button id="getStreamFromMediaElementBtn">Get stream from media element</button> getStreamFromMediaElementBtn.onclick = async () => {   // <video id="videoEl" src="./assets/forest.mp4" controls></video>   const { stream, tracks, error } = await captureStreamFromMediaElement(videoEl)   if (error) return handleError(error)   console.log('@media element stream', stream)    streamReceiver.srcObject = stream    const [firstTrack] = tracks   console.log('@media element first track', firstTrack)    const [trackCapabilities, trackConstraints, trackSettings] = [     firstTrack.getCapabilities(),     firstTrack.getConstraints(),     firstTrack.getSettings()   ]    logBox.textContent = stringify({     trackCapabilities,     trackConstraints,     trackSettings   }) }

Пример захваченного потока и первого трека:

Пример информации о треке:

6. Как остановить захват потока?

Для остановки захвата потока необходимо прекратить получение данных из каждого его источника, т.е. трека. Для этого следует вызвать метод stop каждого трека:

export function stopTracks() {   mediaStream?.getTracks().forEach((track) => {     track.stop()   })   displayStream?.getTracks().forEach((track) => {     track.stop()   })   for (const stream of mediaElementStreams) {     stream?.getTracks().forEach((track) => {       track.stop()     })   }   mediaStream = null   displayStream = null   mediaElementStreams = [] }

7. Как захватить изображение из видеотрека?

Для захвата изображения из видеотрека (или кадра из холста) предназначен метод takePhoto интерфейса ImageCapture:

const imageCapture = new ImageCapture(videoTrack) const blob = await imageCapture.takePhoto(photoSettings?)

Данный метод принимает опциональные настройки для фото:

Пример настроек для фото:

export const DEFAULT_PHOTO_SETTINGS = {   imageHeight: 480,   imageWidth: 640 }

К видеотреку можно применять дополнительные требования, связанные с захватом изображения:

Эти требования являются продвинутыми и применяются с помощью метода applyConstraints:

const advancedConstraints = {   name: value } await videoTrack.applyConstraints({   advanced: [advancedConstraints] })

Для того, чтобы иметь возможность применять к видеотреку некоторые продвинутые требования при захвате потока с устройств пользователя к потоку должны применяться следующие требования:

// эти требования относятся к видео export const DEFAULT_PHOTO_CONSTRAINTS = {   pan: true,   tilt: true,   zoom: true }

Метод takePhoto возвращает объект Blob:

Экземпляр ImageCapture предоставляет следующие методы для получения списка возможностей и настроек для фото:

  • getPhotoCapabilities — возвращает список возможностей для фото;
  • getPhotoSettings — возвращает список настроек для фото.

Функция для получения возможностей и настроек для фото:

export async function getPhotoCapabilitiesAndSettings(videoTrack) {   const imageCapture = new ImageCapture(videoTrack)   console.log('@image capture', imageCapture)    try {     const [photoCapabilities, photoSettings] = await Promise.all([       imageCapture.getPhotoCapabilities(),       imageCapture.getPhotoSettings()     ])      return { photoCapabilities, photoSettings }   } catch (error) {     return { error }   } }

Соответствующий обработчик:

// <button id="getPhotoCapabilitiesAndSettingsBtn">Get photo capabilities and settings</button> getPhotoCapabilitiesAndSettingsBtn.onclick = async () => {   const { videoTracks, error: mediaError } = await getUserMedia()   if (mediaError) return handleError(mediaError)    const [firstVideoTrack] = videoTracks    const {     photoCapabilities,     photoSettings,     error: photoError   } = await getPhotoCapabilitiesAndSettings(firstVideoTrack)   if (photoError) return handleError(photoError)    logBox.textContent = stringify({     photoCapabilities,     photoSettings   }) }

Пример возможностей и настроек для фото:

Функция для захвата изображения из видеотрека:

export async function takePhoto({   videoTrack,   photoSettings = DEFAULT_PHOTO_SETTINGS }) {   const imageCapture = new ImageCapture(videoTrack)    try {     const blob = await imageCapture.takePhoto(photoSettings)     return { blob }   } catch (error) {     return { error }   } }

Соответствующий обработчик:

// <button id="takePhotoBtn">Take photo</button> takePhotoBtn.onclick = async () => {   const { videoTracks, error: mediaError } = await getUserMedia({     video: { ...DEFAULT_VIDEO_CONSTRAINTS, ...DEFAULT_PHOTO_CONSTRAINTS }   })   if (mediaError) return handleError(mediaError)    const [videoTrack] = videoTracks    // здесь мы можем применять к треку дополнительные требования   // await videoTrack.applyConstraints({   //   advanced: [advancedConstraints]   // })    const { blob, error: photoError } = await takePhoto({ videoTrack })   if (photoError) return handleError(photoError)    // <img id="imgBox" alt="" />   const imgSrc = URL.createObjectURL(blob)   imgBox.src = imgSrc   // imgBox.addEventListener(   //   'load',   //   () => {   //     URL.revokeObjectURL(imgSrc)   //   },   //   { once: true }   // ) }

Ссылка на источник изображения формируется с помощью метода URL.createObjectURL. Метод URL.revokeObjectURL должен вызываться во избежание утечек памяти, но при его вызове после загрузки изображения, как в приведенном примере, изображение невозможно будет скачать.

8. Как записать поток?

Для записи потока предназначен интерфейс MediaRecorder:

const mediaRecorder = new MediaRecorder(mediaStream, options?)

Конструктор MediaRecorder принимает поток и опциональный объект с настройками, наиболее важной из которых является настройка mimeType — тип создаваемой записи.

Экземпляр MediaRecorder предоставляет следующие методы для управления записью:

  • start(timeslice?) — запускает запись. Данный метод принимает опциональный параметр timeslice — время вызова события dataavailable (см. ниже);
  • pause — приостанавливает запись;
  • resume — продолжает запись;
  • stop — останавливает запись.

В процессе записи возникает ряд событий, наиболее важным из которых является dataavailable. Обработчик этого события принимает объект, содержащий свойство data, в котором находятся части (chunks) записанных данных в виде Blob:

let mediaDataChunks = []  mediaRecorder.ondatavailable = ({ data }) => {   mediaDataChunks.push(data) }

Интерфейс MediaRecorder позволяет проверять поддержку типа создаваемой записи с помощью метода isTypeSupported.

Предположим, что мы хотим записать экран пользователя со звуком. Поток экрана будет содержать только видео. Поэтому нам необходимо получить видеотреки экрана и аудиотреки микрофона и объединить их в один поток. Это можно сделать при помощи конструктора MediaStream:

export const createNewStream = (tracks) => new MediaStream(tracks)

Данный конструктор принимает треки в виде массива.

Функция для начала записи:

const DEFAULT_RECORD_MIME_TYPE = 'video/webm' const DEFAULT_RECORD_TIMESLICE = 250  // лучше, чтобы `mediaRecorder` был одиночкой let mediaRecorder let mediaDataChunks = []  export async function startRecording({   mediaStream,   mimeType,   timeslice = DEFAULT_RECORD_TIMESLICE,   ...restOptions }) {   if (mediaRecorder) return    mediaRecorder = new MediaRecorder(mediaStream, {     mimeType: MediaRecorder.isTypeSupported(mimeType)       ? mimeType       : DEFAULT_RECORD_MIME_TYPE,     ...restOptions   })   console.log('@media recorder', mediaRecorder)    mediaRecorder.onerror = ({ error }) => {     return error   }    mediaRecorder.ondataavailable = ({ data }) => {     mediaDataChunks.push(data)   }    mediaRecorder.start(timeslice) }

Соответствующий обработчик:

// <button id="startRecordingBtn">Start recording</button> startRecordingBtn.onclick = async () => {   const { devices, error: devicesError } = await enumerateDevices()   if (devicesError) return handleError(devicesError)    // мы готовы записывать экран без звука   let _audioTracks = []   if (devices.some(({ kind }) => kind === 'audioinput')) {     const { audioTracks, error: mediaError } = await getUserMedia()     if (mediaError) return handleError(mediaError)      _audioTracks = audioTracks   }    const { videoTracks, error: displayError } = await getDisplayMedia()   if (displayError) return handleError(displayError)    const mediaStream = createNewStream([..._audioTracks, ...videoTracks])   streamReceiver.srcObject = mediaStream    // ждем возможную ошибку   const recordError = await startRecording({ mediaStream })   if (recordError) return handleError(recordError) }

Пример «записывателя»:

Функция приостановки/продолжения записи:

// в таких случаях удобно использовать `IIFE` и замыкание export const pauseOrResumeRecording = (function () {   let paused = false    return function () {     if (!mediaRecorder) return      paused ? mediaRecorder.resume() : mediaRecorder.pause()     paused = !paused      return paused   } })()

Обработчик:

// <button id="pauseOrResumeRecordingBtn">Pause/Resume recording</button> pauseOrResumeRecordingBtn.onclick = () => {   const paused = pauseOrResumeRecording()   console.log('@recording paused', paused) }

Функция остановки записи:

export function stopRecording() {   if (!mediaRecorder) return    mediaRecorder.stop()    const _mediaDataChunks = mediaDataChunks   console.log('@media data chunks', _mediaDataChunks)    // очитка   // Явное удаление обработчика события `dataavailable`   // обеспечивает возможность повторной записи   mediaRecorder.ondataavailable = null   mediaRecorder = null   mediaDataChunks = []    return _mediaDataChunks }

Обработчик:

// <button id="stopRecordingBtn">Stop recording</button> stopRecordingBtn.onclick = () => {   const chunks = stopRecording()    const blob = new Blob(chunks, {     type: DEFAULT_RECORD_MIME_TYPE   })    // если необходимо создать файл, например, для передачи на сервер   // https://w3c.github.io/FileAPI/#file-section   // const file = new File(   //   chunks,   //   `new-record-${Date.now()}.${DEFAULT_RECORD_MIME_TYPE.split('/').at(-1)}`,   //   {   //     type: DEFAULT_RECORD_MIME_TYPE   //   }   // )    // <video id="recordBox" controls></video>   recordBox.src = URL.createObjectURL(blob)   // в данном случае проблем со скачиванием файла не возникает   URL.revokeObjectURL(blob)    stopTracks() }

Пример частей данных:

9. Как преобразовать текст в речь?

Для преобразования текста в речь предназначен интерфейс SpeechSynthesis:

Данный интерфейс является свойством глобального объекта window (window.speechSynthesis).

Для озвучивания текста применяются голоса (voices), доступные в браузере. Для получения их списка используется метод getVoices:

const voices = speechSynthesis.getVoices()

Этот метод на сегодняшний день работает не совсем обычно. При первом озвучивании его приходится вызывать дважды, повторно запрашивая список голосов в обработчике события voicechanged:

let voices = speechSynthesis.getVoices()  speechSynthesis.onvoiceschanged = () => {   voices = speechSynthesis.getVoices() }

speechSynthesis предоставляет следующие методы для озвучивания текста:

  • start(utterance) — запуск озвучивания;
  • pause — приостановка озвучивания;
  • resume — продолжение озвучивания;
  • cancel — отмена (остановка) озвучивания.

Метод start принимает экземпляр SpeechSynthesisUtterance:

const utterance = new SpeechSynthesisUtterance(text?)

Данный конструктор принимает опциональный текст для озвучивания.

utterance имеет несколько сеттеров для настройки озвучивания:

  • text — текст для озвучивания;
  • lang — язык озвучивания;
  • voice — голос для озвучивания и др.

Предположим, что у нас имеется такой текст:

<textarea id="textToSpeech" rows="4"> Мы — источник веселья и скорби рудник. Мы — вместилище скверны и чистый родник. Человек, словно в зеркале мир, — многолик. Он ничтожен — и он же безмерно велик! </textarea>

Функция для озвучивания этого текста голосом от Google:

// голос для озвучивания let voiceFromGoogle // индикатор начала озвучивания let speakingStarted  export function startSpeechSynthesis() {   if (voiceFromGoogle) return speak()    speechSynthesis.getVoices()    speechSynthesis.onvoiceschanged = () => {     const voices = speechSynthesis.getVoices()     console.log('@voices', voices)      voiceFromGoogle = voices.find((voice) => voice.name === 'Google русский')      speak()   } }  function speak() {   const trimmedText = textToSpeech.value.trim()   if (!trimmedText) return    const utterance = new SpeechSynthesisUtterance(trimmedText)   utterance.lang = 'ru-RU'   utterance.voice = voiceFromGoogle   console.log('@utterance', utterance)    speechSynthesis.speak(utterance)   speakingStarted = true    utterance.onend = () => {     speakingStarted = false   } }

Соответствующий обработчик:

// <button id="startSpeechSynthesisBtn">Start speech synthesis</button> startSpeechSynthesisBtn.onclick = () => {   startSpeechSynthesis() }

Пример списка голосов:

Пример «высказывания»:

Функция для приостановки/продолжения озвучивания:

// индикатор озвучивания `speechSynthesis.speaking` в настоящее время работает некорректно export const pauseOrResumeSpeaking = (function () {   let paused = false    return function () {     if (!speakingStarted) return      paused ? speechSynthesis.resume() : speechSynthesis.pause()     paused = !paused      return paused   } })()

Обработчик:

// <button id="pauseOrResumeSpeakingBtn">Pause/resume speaking</button> pauseOrResumeSpeakingBtn.onclick = () => {   const paused = pauseOrResumeSpeaking()   console.log('@speaking paused', paused) }

Функция для остановки озвучивания:

export function stopSpeaking() {   speechSynthesis.cancel() }

Обработчик:

// <button id="stopSpeakingBtn">Stop speaking</button> stopSpeakingBtn.onclick = () => {   stopSpeaking() }

10. Как преобразовать речь в текст?

Для преобразования речи в текст предназначен интерфейс SpeechRecognition:

// рекомендованный подход const speechRecognition =   window.SpeechRecognition || window.webkitSpeechRecognition const recognition = new speechRecognition()

recognition имеет несколько сеттеров для настройки распознавания речи:

  • lang — язык для распознавания;
  • continuous — определяет, продолжается ли распознавание после получения первого «финального» результата;
  • interimResults — определяет, обрабатываются ли «промежуточные» результаты распознавания;
  • maxAlternatives — определяет максимальное количество вариантов распознанного слова, возвращаемых браузером. Варианты возвращаются в виде массива, первым элементом которого является наиболее подходящее с точки зрения браузера слово.

Методы для управления распознаванием, предоставляемые recognition:

  • start — запуск распознавания;
  • stop — остановка распознавания;
  • abort — прекращение распознавания.

При распознавании речи браузером происходит следующее:

  • при вызове метода start браузер начинает нас «слушать»;
  • каждое сказанное слово регистрируется как отдельная сущность — массив, содержащий несколько (в зависимости от настройки maxAlternatives) вариантов этого слова;
  • регистрация слова приводит к возникновению события result;
  • регистрируемые сущности являются промежуточными (interim) результатами распознавания;
  • по истечении некоторого времени (определяемого браузером) после того, как мы замолчали, промежуточный результат переводится в статус финального (final);
  • снова возникает событие result: значением свойства isFinal результата является true;
  • после регистрации финального результата возникает событие end;
  • если настройка continuous имеет значение false, распознавание завершится после регистрации первого слова;
  • если настройка interimResults имеет значение false, результаты будут сразу регистрироваться как финальные;
  • событие result имеет свойство resultIndex, значением которого является индекс последнего обработанного результата.

Обратите внимание: браузер не умеет работать с пунктуацией: он понимает слова, но не знаки препинания. Также обратите внимание, что выполняемое браузером редактирование результатов распознавания является минимальным и почти всегда оказывается недостаточным.

Предположим, что у нас имеется инпут для промежуточных результатов и текстовый блок для финальных результатов распознавания речи:

<div class="speech-to-text-wrapper">   <input type="text" id="interimTranscriptBox" />   <textarea id="finalTranscriptBox" rows="4"></textarea> </div>

Для решения проблемы, связанной с пунктуацией, нам потребуется такой словарь:

const DICTIONARY = {   точка: '.',   запятая: ',',   вопрос: '?',   восклицание: '!',   двоеточие: ':',   тире: '-',   абзац: '\n',   отступ: '\t' }

А для решения проблемы, связанной с редактированием, такие функции:

// заменяем слова на знаки препинания const editInterim = (s) => s   .split(' ')   .map((word) => {     word = word.trim()     return DICTIONARY[word.toLowerCase()]       ? DICTIONARY[word.toLowerCase()]       : word   })   .join(' ')  // удаляем лишние пробелы const editFinal = (s) => s.replace(/\s{1,}([\.+,?!:-])/g, '$1')

Функция для распознавания речи:

// экземпляр "распознавателя" let recognition // индикатор начала распознавания let recognitionStarted // финальный результат let finalTranscript  // функция очистки function resetRecognition() {   recognition = null   recognitionStarted = false   finalTranscript = ''   interimTranscriptBox.value = ''   finalTranscriptBox.value = '' }  export function startSpeechRecognition() {   resetRecognition()    recognition = new speechRecognition()   // настройки распознавания   recognition.continuous = true   recognition.interimResults = true   recognition.maxAlternatives = 3   recognition.lang = 'ru-RU'   console.log('@recognition', recognition)    recognition.start()   recognitionStarted = true    recognition.onend = () => {     // Повторно запускаем распознавание, если     // соответствующий индикатор имеет значение `true`     if (!recognitionStarted) return     recognition.start()   }    recognition.onresult = (e) => {     // Промежуточные результаты обновляются на каждом цикле распознавания     let interimTranscript = ''     // Перебираем результаты с того места, на котором остановились в прошлый раз     for (let i = e.resultIndex; i < e.results.length; i++) {       // Атрибут `isFinal` является индикатором того, что речь закончилась (мы перестали говорить)       if (e.results[i].isFinal) {         // Редактируем промежуточный результат         const interimResult = editInterim(e.results[i][0].transcript)         // и добавляем его к финальному         finalTranscript += interimResult       } else {         // В противном случае, записываем распознанное слово в промежуточный результат         interimTranscript += e.results[i][0].transcript       }     }     // Записываем промежуточный результат в `input`     interimTranscriptBox.value = interimTranscript     // Редактируем финальный результат     finalTranscript = editFinal(finalTranscript)     // и записываем его в `textarea`     finalTranscriptBox.value = finalTranscript   } }

Соответствующий обработчик:

// <button id="startSpeechRecognitionBtn">Start speech synthesis</button> startSpeechRecognitionBtn.onclick = () => {   startSpeechRecognition() }

Пример «распознавателя»:

Функция остановки распознавания:

export function stopRecognition() {   if (!recognition) return   recognition.stop()   recognitionStarted = false }

Обработчик:

// <button id="stopRecognitionBtn">Stop recognition</button> stopRecognitionBtn.onclick = () => {   stopRecognition() }

Функция прекращения распознавания:

export function abortRecognition() {   if (!recognition) return   recognition.abort()   resetRecognition() }

Обработчик:

// <button id="abortRecognitionBtn">Abort recognition</button> abortRecognitionBtn.onclick = () => {   abortRecognition() }

11. Как определить поддержку возможностей по работе с медиа браузером?

Функция для определения возможностей браузера по работе с медиа, рассмотренных в данной шпаргалке:

export function verifySupport() {   const unsupportedFeatures = []    if (!('mediaDevices' in navigator)) {     unsupportedFeatures.push('mediaDevices')   }    if (     !('captureStream' in HTMLAudioElement.prototype) &&     !('mozCaptureStream' in HTMLAudioElement.prototype)   ) {     unsupportedFeatures.push('captureStream')   }    ;['MediaStream', 'MediaRecorder', 'Blob', 'File', 'ImageCapture'].forEach(     (f) => {       if (!(f in window)) {         unsupportedFeatures.push(f)       }     }   )    if (!('speechSynthesis' in window)) {     unsupportedFeatures.push('speechSynthesis')   }    if (     !('SpeechRecognition' in window) &&     !('webkitSpeechRecognition' in window)   ) {     unsupportedFeatures.push('SpeechRecognition')   }    return unsupportedFeatures }

Пример использования этой функции:

const unsupportedFeatures = verifySupport() if (unsupportedFeatures.length) {   console.error(unsupportedFeatures) }

Таким образом, мы рассмотрели все основные интерфейсы и методы по работе с медиа, описываемые в указанных в начале шпаргалки спецификациях.

Следует отметить, что существует еще два интерфейса, предоставляемых браузером для работы с медиа, которые мы оставили без внимания в силу их сложности и специфичности:

Что касается последнего, вот парочка материалов, с которых можно начать изучение данного интерфейса:

Благодарю за внимание и happy coding!



ссылка на оригинал статьи https://habr.com/ru/company/timeweb/blog/667148/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *