From 1b8ced3b9a84f40684d561a88b74c61eae6103de Mon Sep 17 00:00:00 2001 From: Stepan Mikhailiuk Date: Tue, 19 Sep 2023 14:29:30 -0700 Subject: [PATCH] feat(*): add recording example --- .travis.yml | 65 ++++++++ .../video-recording/css/main.css | 27 +++ .../video-recording/index.html | 89 ++++++++++ .../video-recording/js/main.js | 154 ++++++++++++++++++ .../video-recording/js/worker.js | 44 +++++ 5 files changed, 379 insertions(+) create mode 100644 .travis.yml create mode 100644 src/content/insertable-streams/video-recording/css/main.css create mode 100644 src/content/insertable-streams/video-recording/index.html create mode 100644 src/content/insertable-streams/video-recording/js/main.js create mode 100644 src/content/insertable-streams/video-recording/js/worker.js diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..7b57cb790c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,65 @@ +sudo: false +language: node_js +dist: trusty +node_js: +- "7" + +env: + - CXX=g++-4.8 +matrix: + include: + - os: linux + sudo: false + env: BROWSER=chrome BVER=stable + - os: linux + sudo: false + env: BROWSER=chrome BVER=beta + - os: linux + sudo: false + env: BROWSER=chrome BVER=unstable + - os: linux + sudo: false + env: BROWSER=firefox BVER=stable + - os: linux + sudo: false + env: BROWSER=firefox BVER=beta + - os: linux + sudo: false + env: BROWSER=firefox BVER=unstable + - os: osx + sudo: required + osx_image: xcode9.4 + env: BROWSER=safari BVER=stable + - os: osx + sudo: required + osx_image: xcode11.2 + env: BROWSER=safari BVER=unstable + + fast_finish: true + + allow_failures: + - os: linux + sudo: false + env: BROWSER=chrome BVER=unstable + - os: linux + sudo: false + env: BROWSER=firefox BVER=unstable + +before_script: + - ./node_modules/travis-multirunner/setup.sh + - export DISPLAY=:99.0 + - if [ -f /etc/init.d/xvfb ]; then sh -e /etc/init.d/xvfb start; fi + +after_failure: + - for file in *.log; do echo $file; echo "======================"; cat $file; done || true + +notifications: + email: + - + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 diff --git a/src/content/insertable-streams/video-recording/css/main.css b/src/content/insertable-streams/video-recording/css/main.css new file mode 100644 index 0000000000..6921cdb645 --- /dev/null +++ b/src/content/insertable-streams/video-recording/css/main.css @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +button { + margin: 20px 10px 0 0; + min-width: 100px; +} + +div#buttons { + margin: 0 0 20px 0; +} + +div#status { + height: 2em; + margin: 1em 0 0 0; +} + +video { + --width: 30%; + width: var(--width); + height: calc(var(--width) * 0.75); +} diff --git a/src/content/insertable-streams/video-recording/index.html b/src/content/insertable-streams/video-recording/index.html new file mode 100644 index 0000000000..2a030b62d7 --- /dev/null +++ b/src/content/insertable-streams/video-recording/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + Insertable Streams - Mirror in a worker vs Mirror in main thread + + + + + + + + + + +
+

WebRTC samples + Breakout Box crop

+ +

This sample shows how to perform mirroring of a video stream using the experimental + mediacapture-transform API + in a Worker. + + It also provides comparison between mirroring in the main thread using canvas and mirroring in a worker. +

+ +
+ Original video +
+ + +
+
+ +
+ Mirrored With Canvas +
+ + +
+
+ +
+ Mirrored in a worker +
+ + +
+
+ +
+ + + +
+ +

+ Note: This sample is using an experimental API that has not yet been standardized. As + of 2022-11-21, this API is available in the latest version of Chrome based browsers. +

+ View source on GitHub + +
+ + + + + + + diff --git a/src/content/insertable-streams/video-recording/js/main.js b/src/content/insertable-streams/video-recording/js/main.js new file mode 100644 index 0000000000..3f40fa5356 --- /dev/null +++ b/src/content/insertable-streams/video-recording/js/main.js @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +'use strict'; + +/* global MediaStreamTrackProcessor, MediaStreamTrackGenerator */ +if (typeof MediaStreamTrackProcessor === 'undefined' || + typeof MediaStreamTrackGenerator === 'undefined') { + alert( + 'Your browser does not support the experimental MediaStreamTrack API ' + + 'for Insertable Streams of Media. See the note at the bottom of the ' + + 'page.'); +} + +const startButton = document.getElementById('startButton'); +const slowDownButton = document.getElementById('slowDownButton'); +const stopButton = document.getElementById('stopButton'); +const originalVideo = document.getElementById('originalVideo'); +const recordedOriginalVideo = document.getElementById('recordedOriginalVideo'); +const mirroredWithCanvasVideo = document.getElementById('mirroredWithCanvasVideo'); +const recordedMirroredWithCanvasVideo = document.getElementById('recordedMirroredWithCanvasVideo'); +const mirroredInWebWorkerVideo = document.getElementById('mirroredInWebWorkerVideo'); +const recordedMirroredInWorkerVideo = document.getElementById('recordedMirroredInWorkerVideo'); + +const worker = new Worker('./js/worker.js', { name: 'Crop worker' }); + + +class VideoRecorder { + constructor(stream, outputVideoElement) { + this.videoElement = outputVideoElement; + this.mediaRecorder = new MediaRecorder(stream); + + this.mediaRecorder.ondataavailable = (event) => { + if (event.data.size > 0) { + this.recordedBlob.push(event.data); + } + }; + + this.mediaRecorder.onerror = (e) => { + throw e; + }; + + this.recordedBlob = []; + } + + start() { + this.mediaRecorder.start(1000); + } + + stop() { + this.mediaRecorder.stop(); + console.log('stopped'); + const blob = new Blob(this.recordedBlob, { type: 'video/webm' }); + const url = URL.createObjectURL(blob); + this.videoElement.src = url; + } +} + +let recorders = []; + +startButton.addEventListener('click', async () => { + const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 1280, height: 720 } }); + originalVideo.srcObject = stream; + + const [track] = stream.getTracks(); + const processor = new MediaStreamTrackProcessor({ track }); + const { readable } = processor; + + const generator = new MediaStreamTrackGenerator({ kind: 'video' }); + const { writable } = generator; + + const mediaStream = new MediaStream([generator]); + mirroredInWebWorkerVideo.srcObject = mediaStream; + + const mirroredWithCanvasVideoStream = createMirroredCanvasStream(stream); + mirroredWithCanvasVideo.srcObject = mirroredWithCanvasVideoStream; + + recorders.push(new VideoRecorder(stream, recordedOriginalVideo)); + recorders.push(new VideoRecorder(mediaStream, recordedMirroredInWorkerVideo)); + recorders.push(new VideoRecorder(mirroredWithCanvasVideoStream, recordedMirroredWithCanvasVideo)); + + + recorders.forEach(recorder => recorder.start()); + + worker.postMessage({ + operation: 'mirror', + readable, + writable, + }, [readable, writable]); +}); + +stopButton.addEventListener('click', () => { + recorders.forEach(recorder => recorder.stop()); + recorders = []; +}); + +slowDownButton.addEventListener('click', () => { + console.time('slowDownButton'); + let str = ''; + for (let i = 0; i < 100000; i++) { + str += i.toString(); + if (str[str.length - 1] === '0') { + str += '1'; + } + } + console.timeEnd('slowDownButton'); +}); + + +function createMirroredCanvasStream(stream) { + const videoElement = document.createElement('video'); + videoElement.playsInline = true; + videoElement.autoplay = true; // required in order for to successfully capture