Web_Advance
  • 本書簡介
  • Node.js 部分
    • Node 版本管理
    • 使用NPM
      • Yarn
    • 開始Node
    • Worker Thread
    • REPL
    • TCP
    • path
    • Cluster and Child_process
    • assert (自訂拋出的錯誤)
    • Stream(流)
    • util (工具類)
    • EventEmitter
    • fs 文件操作
    • Buffer
      • Binary Diff
      • 查看 Binary 檔案內容
    • Process (進程)
    • 錯誤處理
  • OS
  • Async Hook
  • TCP
  • HTTP
    • 有關爬蟲
    • HTTP/2
    • HTTP Protocal
  • HTTPS
    • HTTPS 流程
    • SSL pinning
    • HTTPS 封包解密
    • 建立自簽發 HTTPS 證書
    • 幫網站加上HTTPS
    • HTTPS原理
  • Crypto加密
  • 有關繼承
  • JS 基本
    • JavaScript 迴圈與異步處理
  • 使用 Express
    • 上傳檔案
    • 圖片伺服器
    • 簡單範例
  • 使用 Nest.js
  • 使用MongoDB
    • 設置帳戶登入權限
    • Mongoose 框架
    • 進階Mongo
    • 基本環境操作
    • MongoDB Sharding
  • 使用MySQL
    • Schema 架構設計
    • SQL 語法
    • SQL Procedure
    • Node.js 操作 MySQL
    • 使用 Sequelize
      • DB Migration
      • function
  • 使用PostgreSQL
    • 常見問題
    • replica
    • 基本指令
    • 使用Node.js操控pg
    • SQL 基礎
  • 使用TypeORM
  • RethinkDB
  • CSS 深度探討
    • Width, Height
  • React
    • 第三方組件
      • Formik
    • styled component
    • propTypes
    • React webpack 部署
    • React util
    • 寫component並且publish
    • create-react-app
    • Context API
    • i18n
    • Server side render
    • Next.js教學
    • Higher order component 與 Recompose
    • component 間 互相存取
    • React hook
  • React router
    • 自己寫一個Router
  • Redux
    • Redux Toolkit
    • 小技巧
    • Redux sagas
    • compose
  • React Native
    • adb
    • InApp Billing
    • Icon
    • SVG
    • Firebase
      • Phone Auth
    • 自動化測試
    • Splash screen
    • Websocket
    • Googla OAuth
      • iOS
      • Android
    • Facebook OAuth
      • iOS
      • Android
    • IOS
    • 第三方組件
      • Auth Code Input
      • Country Code Picker
      • onboarding screen
      • Toast
    • ESlint
    • Push notification
    • Android 上架步驟
    • Expo
    • router
      • react-navigation套件
    • 原生組件
      • RefreshControl
      • Modal
      • Alert
      • button
      • KeyboardAvoidingView
      • Drawer
      • Image
    • 限制螢幕垂直與水平
    • NativeBase UI
    • Debug
    • 常見問題
    • Network
    • 硬體操作
      • 隱藏鍵盤
      • 地理位置
      • 相機與圖片庫存取
    • Async Storage
    • Animation
    • Admob
  • JS 模組化
  • 使用 Webpack
  • 使用 Babel
  • JWT Token
  • ES6 ES7 ES8
    • Array method
    • ES8 Async
    • ES6 Proxy
    • ES6 Object
    • ES6 Arrow function
    • ES6 Promise
    • ES6 Symbol
    • ES6 Generator
    • ES6 Set,Map
    • ES6 Class
  • 模板引擎
    • Mustache
    • Handlebars.js
    • EJS
  • ESLint
  • 部屬到OpenShift
  • OpenStack
  • OAuth
    • Twitter OAuth
    • Google authenticator
    • facebook oauth
      • facebook like ,share
    • google oauth
  • Redis
  • 做一個簡單的markdown editor
  • Websocket
    • WebSocket 相關 Protocol
  • Sublime 安裝套件
  • Google api
    • Cloud Run
    • speech api
    • place autocomplete
    • Geocode
    • Map
      • React map
    • vision api
    • Google-recaptcha
    • Google sheet
  • Instagram API
  • Markdown 與 code pretty js
  • HTML5
    • IntersectionObserver
    • HTML5 audio
    • HTML5 Video 與 WebRTC
      • WebRTC 進階
      • WebEX API
    • HTML5 IndexedDB
  • Google Cloud Platform (GCP)
    • Cloud Storage
    • Cloud storage 串接 Cloud CDN
  • Vim 編輯器
  • 使用nginx
    • config
  • Unix 實用指令
    • 新 VPS 安裝流程
    • Ubuntu 22 安裝
    • Shell Script 教學
  • Git 實用指令
    • Git hook
    • 加上 SSH-key 到 GitHub
    • GPG簽名
  • SSH 實用指令
  • 有關Fetch與axios與跨域請求
  • 圖片上傳相關
    • imgur API
  • JS 格式轉換
  • js trick
  • AWS
    • AWS EBS
    • AWS HTTPS 憑證
    • AWS Cloudfront、ELB、ACL
    • AWS Athena
    • AWS CloudWatch、SNS
    • AWS RDS
    • AWS lambda
      • 範例
      • 加上權限控管
    • AWS S3
    • AWS DynamoDB
      • 結合Lambda
    • 快速把 EC2 串上 AWS Cloudfront CDN
    • AWS 證照相關
  • 有關日期Date
  • VS code 編輯器
    • VSCode 外掛 Plugin
  • CI with Gitlab&Jenkins
  • API 測試
    • Postman
      • 設置 Postman 環境變數
    • API Blueprint
    • swagger
      • 註解寫在Code內生成swagger UI
  • Javascript 實用Lib
  • 遠端寫程式
  • Quicktime錄影注意事項
  • Web開發進階Bug
  • Web壓力測試
  • LineBot
  • PM2部署
  • i18n
  • VPN
  • Protocol Buffers
  • Docker教學
    • LXC LXD
    • Docker Compose
    • Docker 原理
    • Docker 指令
  • E2E Testing
    • Cypress
    • PlayWright
    • Puppeteer 與其他 UI 測試工具
  • Unit Test (Jest & enzyme)
    • React Testing Library
    • mocha
  • Cassandra
    • cluster
  • Distribute Web
    • Dat project
    • IPFS project
  • Cluster and Child_process
  • 打包應用程式
  • Java
    • 使用gradle結合docker
  • Debug 頁面
  • Proxy
  • Chrome extension
  • 消息系統
    • RabbitMQ
  • 金流串接
    • Paypal
    • spgateway智富通
    • Stripe 串接
  • 有關Log
  • 設定 feature flag
  • Azure
    • Face API
    • Image Analyze API
    • Azure Serverless
    • Cosmos DB
      • 使用 SDK
      • 以 RESTful 操作 DB
      • 一致性策略與 DB replicate
  • NodeBB 筆記
  • 瀏覽器快取與緩存(Etag, If-None-Match)
  • 瀏覽器快取與緩存(Expires, Last-modified, Cache-Control)
  • Node.js 第三方模組
    • OpenCV
  • Kubernetes
    • 本地測試 MiniKube
  • Ngrok 使用
  • Telegram MiniAPP 開發
  • Firebase 教學
  • 演算法筆記
  • 圖表
    • Echart
    • TradingView 圖表
    • D3
    • 熱力圖 heatmap
  • 後端緩存 Cache
  • 資料一致性
  • Web 安全機制
    • Cookie 與 LocalStorage
  • Vue
    • Element UI
    • Devtool
    • Vuex
    • Vue router
  • 相關網路協議
    • 網路 IP 基礎
    • Google Search 技巧
    • 網路診斷工具
    • IP
    • DNS
  • GitLab 與 Drone
  • SMTP、POP、IMAP
    • SendGrid
  • IPC
  • 串流服務
    • Twilio
    • Agora
  • 其他資源
  • GraphQL
  • Typescript
  • UI 相關資源
  • FFmpeg
  • Unity 遊戲開發筆記
  • Influx DB
  • Windows 相關
  • DALL·E 3
  • Coap
  • Slack API
  • 資訊安全
    • 破解 ZIP 密碼
Powered by GitBook
On this page
  • HTML5 Video
  • 1.有關canvas串流
  • 2.從HTML5錄製影片並下載
  • 3. getUserMedia streaming with WebSocket
  • 4. 之後有了mediaSource API
  • MediaSource 串流範例
  • WebRTC串流
  • WebRTC 可用範例:
  • Debug
  • 名詞:
  • WebRTC 流程:
  • 範例:
  • video.src vs srcObject
  • 可能錯誤
  • Video 屬性
  • WebRTC 錄製
  • React Native WebRTC

Was this helpful?

Edit on GitHub
  1. HTML5

HTML5 Video 與 WebRTC

PreviousHTML5 audioNextWebRTC 進階

Last updated 4 years ago

Was this helpful?

HTML5 Video

1.有關canvas串流

參考此

但其原理為使用canvas擷取影像,並使用websocket傳遞canvas資料,所以只有影像,這種做法client端用來顯示的 dataURL會持續改變,所以雖然是串流,但畫面會有閃爍問題

2.從HTML5錄製影片並下載

教學

Source code

其原理為使用 navigator.mediaDevices.getUserMedia存取網頁攝影機後使用new MediaRecorder錄製

而MediaRecorder接到資料後要存入blob

const haveLoadedMetadata = stream => {
      const video = document.querySelector("#localVideo");
      video.srcObject = stream;
      video.play();
      return new Promise(resolve => video.onloadedmetadata = () => resolve(stream)); 
    };
    var constraints = { audio: true, video: { width: 400, height: 200 } };

    navigator.mediaDevices
      .getUserMedia(constraints)
      .then(mediaStream => haveLoadedMetadata(mediaStream))
      .then((mediaStream) => {
          var options = { mimeType: "video/webm; codecs=vp9" };
          const recorder = new MediaRecorder(mediaStream, options);
          recorder.ondataavailable = (e) => {
            console.log(e) //這裡記得要呼叫 recorder.stop() 才會有  ondataavailable
          }
          recorder.start();
      setTimeout(() => {
        recorder.stop()
      }, 2000);
     })
    .catch(function (err) {
      console.log(err.name + ": " + err.message);
    });
    
    
//或是可以用第三方模組 msr
import MediaStreamRecorder from "msr";
var multiStreamRecorder = new MediaStreamRecorder.MultiStreamRecorder(
   mediaStream // from getUserMedia
);
multiStreamRecorder.ondataavailable = function (blob) {
  // POST/PUT "Blob" using FormData/XHR2
  ws.send(blob.video);
};
multiStreamRecorder.start(3000);

之後再把blob轉格式

var superBuffer = new Blob(blob1, {type: 'video/webm'});

最後轉為可用在url的型態

window.URL.createObjectURL(superBuffer)

把他放到video的src即可

可參考mediaSource 模式:

3. getUserMedia streaming with WebSocket

後來想到可以使用將影片擷取10秒一格並分開連續傳送給client達到串流的效果,但一樣因為最後要在前端將video.srcObject 改為blob,只要更改video src都會造成畫面閃爍

client

import React, { useEffect } from "react";
import MediaStreamRecorder from "msr";
import "./App.css";

const ws = new WebSocket("ws://localhost:3003");

function App() {
  useEffect(() => {
    const video = document.querySelector("#clientVideo");
    video.onloadedmetadata = function (e) {
      video.play();
    };
    ws.onopen = () => {
      console.log("open");
    };
    ws.onmessage = (msg) => {
      if (msg.data instanceof Blob) {
        const video = document.querySelector("#clientVideo");
        video.src = window.URL.createObjectURL(msg.data);
      }
    };
  });
  const send = () => {
    var constraints = { audio: true, video: { width: 400, height: 200 } };

    navigator.mediaDevices
      .getUserMedia(constraints)
      .then((mediaStream) => {
        const video = document.querySelector("#localVideo");
        video.srcObject = mediaStream;
        video.onloadedmetadata = function (e) {
          video.play();
        };

        var multiStreamRecorder = new MediaStreamRecorder.MultiStreamRecorder(
          mediaStream
        );
        multiStreamRecorder.ondataavailable = function (blob) {
          // POST/PUT "Blob" using FormData/XHR2
          ws.send(blob.video);
        };
        multiStreamRecorder.start(7000);
      })
      .catch(function (err) {
        console.log(err.name + ": " + err.message);
      });
  };
  return (
    <div className="App">
      <video id="localVideo"></video>
      <button onClick={() => send()}>send stream</button>
      <div style={{ width: 400, border: "1px solid black", margin: "0 auto" }}>
        <div>client</div>
        <video id="clientVideo" autoPlay></video>
      </div>
    </div>
  );
}

export default App;

server

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 3003 });

wss.on('connection', function connection(ws) {
  ws.on('message', (message) => {
    try {
      console.log(message);
      console.log(Buffer.isBuffer(message))
      ws.send(message);
    } catch(err) {
      console.log(err)
    }
  });
  ws.send('something');
});

但因為video src 每次更新後畫面會閃爍

4. 之後有了mediaSource API

MediaSource 串流範例

video.src = URL.createObjectURL(mediaSource); 後mediaSource.addEventListener("sourceopen") 才會觸發

Async & 加上 websocket 版本:

socket.io 版本參照bitbucket

import React, { useEffect } from "react";
import "./App.css";
const ws = new WebSocket("ws://localhost:3003");

function App() {
  const start = () => {
    ws.onopen = () => {
      console.log("open");
    };
    ws.onmessage = async (msg) => {
      console.log(msg.data)
      const remoteBuf = await msg.data.arrayBuffer();
      // TODO receive remoteBuf
    };
    (async function() {
      const devices = await navigator.mediaDevices.enumerateDevices();
      console.table(devices);

      const stream = await navigator.mediaDevices.getUserMedia({
        video: true,
        audio: true,
      });

      const rec = new MediaRecorder(stream, {
        mimeType: 'video/webm; codecs="opus,vp8"',
      });

      const ms = new MediaSource();

      const video = document.querySelector("#video");

      //video.srcObject = ms;
      video.src = URL.createObjectURL(ms);
      video.volume = 0;
      video.controls = true;
      video.autoplay = true;

      await new Promise((resolve, reject) => {
        ms.addEventListener("sourceopen", () => resolve(), { once: true });
      });

      const sb = ms.addSourceBuffer(rec.mimeType);

      rec.ondataavailable = ({ data }) => {
        (async function () {
          try {
            if (data.size === 0) {
              console.warn("empty recorder data");
              throw new Error("empty recorder data");
            }

            const buf = await data.arrayBuffer();
            sb.appendBuffer(buf);
            ws.send(buf);

            if (video.buffered.length > 1) {
              console.warn("MSE buffered has a gap!");
              throw new Error("MSE buffered has a gap!");
            }
          } catch (err) {
            console.error(err);
          }
        })();
      };

      rec.start(1000);
      console.info("start");
    })();
  };

  return (
    <div id="container">
      <video id="video"></video>
      <button onClick={() => start()}>start</button>
    </div>
  );
}

export default App;

WebRTC串流

WebRTC 可用範例:

Debug

chrome://webrtc-internals

名詞:

WebRTC 流程:

範例:

以上兩個為很好且簡單的範例,上面是文章下面是程式碼。

Client 過程

1. 初始化連線: 
```
new RTCPeerConnection({
  iceServers: [
    { urls: "stun:stun.stunprotocol.org:3478" },
    { urls: "stun:stun.l.google.com:19302" },
  ]
})
```
並加入本地影像 
```
peerConnection.addStream(localStream);
```

2. 發起人利用 createOffer 產生 description 後設定 setLocalDescription 後傳送 SDP 給其他人: 
```
peerConnection.createOffer().then(createdDescription);
```

```
function createdDescription(description) {
  peerConnection
    .setLocalDescription(description)
    .then(function () {
      serverConnection.send(
        JSON.stringify({ sdp: peerConnection.localDescription, uuid: uuid })
      );
    })
}
```

3. 其他人收到對方的 sdp 後呼叫 setRemoteDescription,
之後用 createAnswer 產生 description 後也用 createdDescription 傳給發起人
```
  if (signal.sdp) {
    peerConnection
      .setRemoteDescription(new RTCSessionDescription(signal.sdp))
      .then(function () {
        if (signal.sdp.type === "offer") {
          peerConnection
            .createAnswer()
            .then(createdDescription)
        }
      })
  }
```  

4. 在本地蒐集到ice後傳送給另一個peer: onicecandidate 並且傳送 serverConnection.send
(ICE candidate可能接收到多個)
https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate
```
peerConnection.onicecandidate = gotIceCandidate;
serverConnection.send(JSON.stringify({ ice: event.candidate, uuid: uuid }));
```

4. 另一個peer接到ice後把ice加入: peerConnection.addIceCandidate
```
if (signal.ice) {
    peerConnection
      .addIceCandidate(new RTCIceCandidate(signal.ice))
}
```

5. 接收到視訊:peerConnection.ontrack = gotRemoteStream

6. 顯示遠端視訊: 
```
function gotRemoteStream(event) {
  remoteVideo.srcObject = event.streams[0];
}
```

Server

單純廣播所有接收到的訊息給連線的client

const wss = new WebSocketServer({server: httpsServer});

wss.on('connection', function(ws) {
  ws.on('message', function(message) {
    // Broadcast any received message to all clients
    console.log('received: %s', message);
    wss.broadcast(message);
  });
});

wss.broadcast = function(data) {
  this.clients.forEach(function(client) {
    if(client.readyState === WebSocket.OPEN) {
      client.send(data);
    }
  });
};

video.src vs srcObject

可能錯誤

  1. Failed to execute 'appendBuffer' on 'SourceBuffer': This SourceBuffer has been removed from the parent media source

解法:將recorder.start(2000); 錄影間隔毫秒縮短即可

Video 屬性

  1. video.autoplay = true; 等同於

    video.onloadedmetadata = function (e) {
      video.play();
    };

WebRTC 錄製

React Native WebRTC

0.6 版本以後需要配置如下,不然 Android 啟動後會 crash

Older versions of the Media Source specification required using to create an object URL then setting to that URL. Now you can just set srcObject to the directly.

https://github.com/EasonWang01/Node.js-stream-video/tree/master/Desktop/Node.js-stream-video-master
https://developers.google.com/web/updates/2016/01/mediarecorder
https://github.com/webrtc/samples/blob/gh-pages/src/content/getusermedia/record/js/main.js
https://github.com/webrtc/samples/blob/gh-pages/src/content/getusermedia/record/js/main.js
https://developer.mozilla.org/zh-TW/docs/Web/API/MediaSource
https://stackoverflow.com/a/52379544
https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling
https://shanetully.com/2014/09/a-dead-simple-webrtc-example/
https://github.com/shanet/WebRTC-Example
createObjectURL()
src
MediaStream
https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject
https://stackoverflow.com/questions/16571044/how-to-record-webcam-and-audio-using-webrtc-and-a-server-based-peer-connection
https://github.com/react-native-webrtc/react-native-webrtc
https://github.com/react-native-webrtc/react-native-webrtc/issues/885#issuecomment-723116643
WebRTC samples
https://blog.mozilla.com.tw/posts/3261/webrtc-相關縮寫名詞簡介blog.mozilla.com.tw
GitHub - muaz-khan/RecordRTC: RecordRTC is WebRTC JavaScript library for audio/video as well as screen activity recording. It supports Chrome, Firefox, Opera, Android, and Microsoft Edge. Platforms: Linux, Mac and Windows.GitHub
Logo
Logo