95 строки
3.1 KiB
JavaScript
95 строки
3.1 KiB
JavaScript
|
// Copyright 2023 Google LLC
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
chrome.runtime.onMessage.addListener(async (message) => {
|
||
|
if (message.target === 'offscreen') {
|
||
|
switch (message.type) {
|
||
|
case 'start-recording':
|
||
|
startRecording(message.data);
|
||
|
break;
|
||
|
case 'stop-recording':
|
||
|
stopRecording();
|
||
|
break;
|
||
|
default:
|
||
|
throw new Error('Unrecognized message:', message.type);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
let recorder;
|
||
|
let data = [];
|
||
|
|
||
|
async function startRecording(streamId) {
|
||
|
if (recorder?.state === 'recording') {
|
||
|
throw new Error('Called startRecording while recording is in progress.');
|
||
|
}
|
||
|
|
||
|
const media = await navigator.mediaDevices.getUserMedia({
|
||
|
audio: {
|
||
|
mandatory: {
|
||
|
chromeMediaSource: 'tab',
|
||
|
chromeMediaSourceId: streamId
|
||
|
}
|
||
|
},
|
||
|
video: {
|
||
|
mandatory: {
|
||
|
chromeMediaSource: 'tab',
|
||
|
chromeMediaSourceId: streamId
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Continue to play the captured audio to the user.
|
||
|
const output = new AudioContext();
|
||
|
const source = output.createMediaStreamSource(media);
|
||
|
source.connect(output.destination);
|
||
|
|
||
|
// Start recording.
|
||
|
recorder = new MediaRecorder(media, { mimeType: 'video/webm' });
|
||
|
recorder.ondataavailable = (event) => data.push(event.data);
|
||
|
recorder.onstop = () => {
|
||
|
const blob = new Blob(data, { type: 'video/webm' });
|
||
|
window.open(URL.createObjectURL(blob), '_blank');
|
||
|
|
||
|
// Clear state ready for next recording
|
||
|
recorder = undefined;
|
||
|
data = [];
|
||
|
};
|
||
|
recorder.start();
|
||
|
|
||
|
// Record the current state in the URL. This provides a very low-bandwidth
|
||
|
// way of communicating with the service worker (the service worker can check
|
||
|
// the URL of the document and see the current recording state). We can't
|
||
|
// store that directly in the service worker as it may be terminated while
|
||
|
// recording is in progress. We could write it to storage but that slightly
|
||
|
// increases the risk of things getting out of sync.
|
||
|
window.location.hash = 'recording';
|
||
|
}
|
||
|
|
||
|
async function stopRecording() {
|
||
|
recorder.stop();
|
||
|
|
||
|
// Stopping the tracks makes sure the recording icon in the tab is removed.
|
||
|
recorder.stream.getTracks().forEach((t) => t.stop());
|
||
|
|
||
|
// Update current state in URL
|
||
|
window.location.hash = '';
|
||
|
|
||
|
// Note: In a real extension, you would want to write the recording to a more
|
||
|
// permanent location (e.g IndexedDB) and then close the offscreen document,
|
||
|
// to avoid keeping a document around unnecessarily. Here we avoid that to
|
||
|
// make sure the browser keeps the Object URL we create (see above) and to
|
||
|
// keep the sample fairly simple to follow.
|
||
|
}
|