.

iT邦幫忙

2

JavaScript - 做個錄音錄影功能ㄅ

Ares 2021-08-02 00:30:522822 瀏覽
  • 分享至 

  • xImage
  •  

大家好!今天這篇主要是實作瀏覽器上的錄音與錄影功能,這邊先列出幾個會做到的目標

  1. 顯示視訊畫面與聲音
  2. 顯示螢幕分享畫面與聲音
  3. 截圖與下載
  4. 錄製畫面與聲音

那麼在開始之前先來認識一下這次會使用到的 JavaScript API,重點有三個,分別為 mediaDevicesMediaStreamMediaRecorder,以下開始介紹~

mediaDevices

mediaDevices 主要用來獲取我們連接的媒體設備,因為媒體設備需要較高的安全性,所以有幾點限制

  • 需要使用者同意才可以使用
  • 只有在 https 與本地端才可以使用,在 http 無法正常運作
  • 如果想在 http 網域下使用須開啟瀏覽器設定

getUserMedia

getUserMedia 可以擷取麥克風與視訊鏡頭的 MediaStream 供顯示,詳細的設定值可以參考這裡

const constraints = { audio: true, video: true }
navigator.mediaDevices.getUserMedia(constraints).then(stream => {
  // do something...
}).catch(err => {
  // do something...
})

getDisplayMedia

getDisplayMedia 可以擷取螢幕畫面的 MediaStream 供顯示,可選擇分享螢幕畫面、應用程式或是網頁分頁,詳細的設定值可以參考這裡

const constraints = { audio: true, video: true }
navigator.mediaDevices.getDisplayMedia(constraints).then(stream => {
  // do something...
}).catch(err => {
  // do something...
})

enumerateDevices

enumerateDevices 會列出目前可使用的設備資訊

navigator.mediaDevices.enumerateDevices().then(devices => {
  // do something...
}).catch(err => {
  // do something...
})

getSupportedConstraints

getSupportedConstraints 會列出所有支援的設定屬性

navigator.mediaDevices.getSupportedConstraints().then(constraints => {
  // do something...
}).catch(err => {
  // do something...
})

MediaStream

MediaStream 是一個影片或者音訊的 Stream,可以藉由我們剛剛介紹的 mediaDevices 來擷取,其中包含了多個 Track,例如視訊鏡頭擷取了影像與聲音,那就會有兩個 Track,獲取 Track 的方法如下

mediaStream.getTracks().forEach(track => {
  // do something...
})

如果要動態改變設定或是監聽某些事件基本上都是對 Track 進行操作

MediaRecorder

MediaRecorder 是一個可以記錄 MediaStream 的方法,使用方式如下

const constraints = { audio: true, video: true }
navigator.mediaDevices.getUserMedia(constraints).then(stream => {
  const options = {
    audioBitsPerSecond: 128000,
    videoBitsPerSecond: 2500000,
    mimeType: 'video/webm'
  }
  const mediaRecorder = new MediaRecorder(stream, options)
  mediaRecorder.addEventListener('dataavailable', e => {
    // do something...
  })
  mediaRecorder.start()
  setTimeout(() => {
    mediaRecorder.stop()
  }, 50000)
}).catch(err => {
  // do something...
})

在停止錄製後會觸發 dataavailable 事件,並將 Blob 檔案夾在事件內,而前面的 MIME 類型比較麻煩,可以使用 MediaRecorder.isTypeSupported(mimeType) 來檢查支援度

實作開始

首先我們先把需要用到的幾個按鈕跟顯示區塊給做出來,video 用來顯示視訊畫面與螢幕分享畫面,而 imgcanvas 則是用來截圖與顯示截圖

<div>
  <button id="camera">視訊鏡頭</button>
  <button id="screen">螢幕畫面</button>
  <button id="screenshot">截圖</button>
  <button id="screenshot-download">下載截圖</button>
  <button id="video-start">開始錄影</button>
  <button id="video-download">停止錄影並下載</button>
  <button id="recording-start">開始錄音</button>
  <button id="recording-download">停止錄音並下載</button>
  <button id="stop">停止</button>
</div>

<div>
  <video id="video"></video>
  <img id="img">
  <canvas id="canvas" style="display: none;"></canvas>
</div>

目標一 - 顯示視訊畫面與聲音

這邊使用到我們用剛剛介紹到的 getUserMedia 來擷取視訊畫面

let cameraStream
const camera = document.querySelector('#camera')
const stop = document.querySelector('#stop')
const video = document.querySelector('#video')

const constraints = { audio: true, video: true }
camera.addEventListener('click', () => {
  navigator.mediaDevices.getUserMedia(constraints).then(stream => {
    cameraStream = stream
    video.srcObject = stream
    video.play()
  })
})

stop.addEventListener('click', () => {
  if (cameraStream) {
    cameraStream.getTracks().forEach(track => {
      track.stop()
    })
    cameraStream = null
  }
})

完成結果如下
視訊畫面

目標二 - 顯示螢幕分享畫面與聲音

一樣使用剛剛介紹到的 getDisplayMedia 來擷取螢幕分享畫面

let screenStream
const screen = document.querySelector('#screen')
const stop = document.querySelector('#stop')
const video = document.querySelector('#video')

const constraints = { audio: true, video: true }
screen.addEventListener('click', () => {
  navigator.mediaDevices.getDisplayMedia(constraints).then(stream => {
    screenStream = stream
    video.srcObject = stream
    video.play()
  })
})

stop.addEventListener('click', () => {
  if (screenStream) {
    screenStream.getTracks().forEach(track => {
      track.stop()
    })
    screenStream = null
  }
})

完成結果如下
分享分頁畫面

因為分享螢幕畫面或使用視訊鏡頭時要將其他設備停止運作,所以我們把共用的邏輯拆出來

let cameraStream
let screenStream
const camera = document.querySelector('#camera')
const screen = document.querySelector('#screen')
const stop = document.querySelector('#stop')
const video = document.querySelector('#video')

function stopAllStream () {
  if (cameraStream) {
    cameraStream.getTracks().forEach(track => {
      track.stop()
    })
    cameraStream = null
  }
  if (screenStream) {
    screenStream.getTracks().forEach(track => {
      track.stop()
    })
    screenStream = null
  }
}

const constraints = { audio: true, video: true }
camera.addEventListener('click', () => {
  navigator.mediaDevices.getUserMedia(constraints).then(stream => {
    stopAllStream()
    cameraStream = stream
    video.srcObject = stream
    video.play()
  })
})

screen.addEventListener('click', () => {
  navigator.mediaDevices.getDisplayMedia(constraints).then(stream => {
    stopAllStream()
    screenStream = stream
    video.srcObject = stream
    video.play()
  })
})

stop.addEventListener('click', stopAllStream)

目標三 - 截圖與下載

截圖會用到一些轉換的技巧,可以參考之前小弟寫的 圖片轉換處理,這邊我就不多做解釋了~

let screenshotBlobUrl
const screenshot = document.querySelector('#screenshot')
const screenshotDownload = document.querySelector('#screenshot-download')
const video = document.querySelector('#video')
const img = document.querySelector('#img')
const canvas = document.querySelector('#canvas')
const ctx = canvas.getContext('2d')

screenshot.addEventListener('click', () => {
  if (!cameraStream && !screenStream) return
  const width = video.offsetWidth
  const height = video.offsetHeight
  canvas.width = width
  canvas.height = height
  ctx.drawImage(video, 0, 0, width, height)
  canvas.toBlob(blob => {
    screenshotBlobUrl = window.URL.createObjectURL(blob)
    img.src = screenshotBlobUrl
  })
})

screenshotDownload.addEventListener('click', () => {
  if (!screenshotBlobUrl) return
  const downloadLink = document.createElement('a')
  downloadLink.href = screenshotBlobUrl
  downloadLink.download = 'fileName'
  downloadLink.click()
})

現在不管使用分享螢幕還是視訊鏡頭都可以截圖也可以下載囉!
截圖與下載

目標四 - 錄製畫面與聲音

接著一樣使用剛剛介紹到的 MediaRecorder 來錄製影片與聲音

let videoMediaRecorder
let recordingMediaRecorder
const videoStart = document.querySelector('#video-start')
const videoDownload = document.querySelector('#video-download')
const recordingStart = document.querySelector('#recording-start')
const recordingDownload = document.querySelector('#recording-download')

videoStart.addEventListener('click', () => {
  if (!cameraStream && !screenStream) return
  const currentStream = cameraStream || screenStream
  const options = {
    audioBitsPerSecond: 128000,
    videoBitsPerSecond: 2500000,
    mimeType: 'video/webm'
  }
  const mediaRecorder = new MediaRecorder(currentStream, options)
  videoMediaRecorder = mediaRecorder
  mediaRecorder.addEventListener('dataavailable', e => {
    const blob = new Blob([e.data], { type: 'video/mp4' })
    const downloadLink = document.createElement('a')
    downloadLink.href = window.URL.createObjectURL(blob)
    downloadLink.download = 'videoName'
    downloadLink.click()
  })
  mediaRecorder.start()
})

videoDownload.addEventListener('click', () => {
  if (!videoMediaRecorder) return
  videoMediaRecorder.stop()
})

recordingStart.addEventListener('click', () => {
  if (!cameraStream && !screenStream) return
  const currentStream = cameraStream || screenStream
  const options = {
    audioBitsPerSecond: 128000,
    mimeType: 'audio/webm'
  }
  const mediaRecorder = new MediaRecorder(currentStream, options)
  recordingMediaRecorder = mediaRecorder
  mediaRecorder.addEventListener('dataavailable', e => {
    const blob = new Blob([e.data], { type: 'audio/mp4' })
    const downloadLink = document.createElement('a')
    downloadLink.href = window.URL.createObjectURL(blob)
    downloadLink.download = 'recordingName'
    downloadLink.click()
  })
  mediaRecorder.start()
})

recordingDownload.addEventListener('click', () => {
  if (!recordingMediaRecorder) return
  recordingMediaRecorder.stop()
})

結語

現在影音的 API 弄得相當簡單可以上手,如果不是用舊版的瀏覽器基本上都不太會有問題(有需要支援的幫QQ),做這種有畫面的東西還是相當有趣的,各位有空有可以動手玩看看啦~


.
圖片
  直播研討會

尚未有邦友留言

立即登入留言