Skip to content

Commit 157f647

Browse files
committed
Audio Update
1 parent ce95a08 commit 157f647

File tree

2 files changed

+97
-49
lines changed

2 files changed

+97
-49
lines changed

features/video-recorder/popup.html

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,18 @@
11
<div class="ReactModalPortal STE-ReactModalPortal">
2-
<div
3-
class="ReactModal__Overlay ReactModal__Overlay--after-open modal_modal-overlay_1Lcbx"
4-
>
2+
<div class="ReactModal__Overlay ReactModal__Overlay--after-open modal_modal-overlay_1Lcbx">
53
<div
64
class="ReactModal__Content ReactModal__Content--after-open modal_modal-content_1h3ll prompt_modal-content_1BfWj"
7-
tabindex="-1"
8-
role="dialog"
9-
aria-label="Rename Variable"
10-
>
11-
<div
12-
class="box_box_2jjDp"
13-
dir="ltr"
14-
style="flex-direction: column; flex-grow: 1"
15-
>
5+
tabindex="-1" role="dialog" aria-label="Rename Variable">
6+
<div class="box_box_2jjDp" dir="ltr" style="flex-direction: column; flex-grow: 1">
167
<div class="modal_header_1h7ps">
178
<div class="modal_header-item_2zQTd modal_header-item-title_tLOU5">
189
Video Recording
1910
</div>
2011
<div class="modal_header-item_2zQTd modal_header-item-close_2XDeL">
21-
<div
22-
aria-label="Close"
23-
class="close-button_close-button_lOp2G close-button_large_2oadS"
24-
role="button"
25-
tabindex="0"
26-
>
27-
<img
28-
class="close-button_close-icon_HBCuO"
29-
src="data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3LjQ4IDcuNDgiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO3N0cm9rZTojZmZmO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MnB4O308L3N0eWxlPjwvZGVmcz48dGl0bGU+aWNvbi0tYWRkPC90aXRsZT48bGluZSBjbGFzcz0iY2xzLTEiIHgxPSIzLjc0IiB5MT0iNi40OCIgeDI9IjMuNzQiIHkyPSIxIi8+PGxpbmUgY2xhc3M9ImNscy0xIiB4MT0iMSIgeTE9IjMuNzQiIHgyPSI2LjQ4IiB5Mj0iMy43NCIvPjwvc3ZnPg=="
30-
/>
12+
<div aria-label="Close" class="close-button_close-button_lOp2G close-button_large_2oadS" role="button"
13+
tabindex="0">
14+
<img class="close-button_close-icon_HBCuO"
15+
src="data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3LjQ4IDcuNDgiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO3N0cm9rZTojZmZmO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MnB4O308L3N0eWxlPjwvZGVmcz48dGl0bGU+aWNvbi0tYWRkPC90aXRsZT48bGluZSBjbGFzcz0iY2xzLTEiIHgxPSIzLjc0IiB5MT0iNi40OCIgeDI9IjMuNzQiIHkyPSIxIi8+PGxpbmUgY2xhc3M9ImNscy0xIiB4MT0iMSIgeTE9IjMuNzQiIHgyPSI2LjQ4IiB5Mj0iMy43NCIvPjwvc3ZnPg==" />
3116
</div>
3217
</div>
3318
</div>
@@ -43,21 +28,23 @@
4328
<span>Start Recording</span>
4429
</button>
4530
<br /><br />
31+
Microphone: <input type="checkbox" class="microphoneCheckbox">
32+
<br />
33+
Desktop sound: <input type="checkbox" class="desktopSoundCheckbox" checked>
34+
<br />
4635
<select class="video-format-select">
4736
<option value="mp4">mp4</option>
4837
<option value="webm">webm</option>
4938
</select>
5039
<br /><br />
5140
Preview: <br />
5241
<video class="STE-recorded-video"></video>
53-
<button
54-
class="prompt_ok-button_3QFdD downloadButton scratchtoolsTag"
55-
>
42+
<button class="prompt_ok-button_3QFdD downloadButton scratchtoolsTag">
5643
<span>Download Video</span>
5744
</button>
5845
</div>
5946
</div>
6047
</div>
6148
</div>
6249
</div>
63-
</div>
50+
</div>

features/video-recorder/video-recorder.js

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default async function ({ feature, console }) {
1111
})
1212

1313
let openPopup = document.createElement("button");
14-
14+
1515
ScratchTools.waitForElements(".preview .inner .flex-row.action-buttons", async function (row) {
1616
if (row.querySelector(".ste-video-recorder-open")) return;
1717
openPopup = document.createElement("button");
@@ -20,8 +20,9 @@ export default async function ({ feature, console }) {
2020
row.insertAdjacentElement("afterbegin", openPopup);
2121
openPopup.addEventListener('click', () => {
2222
document.body.append(popup)
23-
})
23+
})
2424
})
25+
2526
ScratchTools.waitForElements(".menu-bar_account-info-group_MeJZP", async function (row) {
2627
if (row.querySelector(".ste-video-recorder-open")) return;
2728
openPopup = document.createElement("div");
@@ -32,7 +33,7 @@ export default async function ({ feature, console }) {
3233
row.insertAdjacentElement("afterbegin", openPopup);
3334
openPopup.addEventListener('click', () => {
3435
document.body.append(popup)
35-
})
36+
})
3637
})
3738

3839
let popup = document.createElement("div");
@@ -45,7 +46,8 @@ export default async function ({ feature, console }) {
4546
let downloadButton = popup.querySelector(".downloadButton");
4647
let lastDownloadFunction = () => { }
4748
let mimeType = popup.querySelector("select");
48-
49+
let microphoneCheckbox = popup.querySelector(".microphoneCheckbox");
50+
let desktopSoundCheckbox = popup.querySelector(".desktopSoundCheckbox");
4951

5052
closeButton.addEventListener('click', () => {
5153
document.querySelector(".STE-ReactModalPortal").remove()
@@ -58,20 +60,79 @@ export default async function ({ feature, console }) {
5860

5961
const canvas = feature.traps.vm.renderer.canvas;
6062
const preview = popup.querySelector("video")
61-
const projectTitle = document.querySelector("input.inplace-input") || document.querySelector("input.project-title-input_title-field_en5Gd")
62-
// document.querySelector(".menu-bar_account-info-group_MeJZP").append(preview)
63+
64+
await new Promise(async (resolve, reject) => {
65+
(async () => {
66+
const rem = await ScratchTools.waitForElement("input.inplace-input")
67+
resolve(rem);
68+
})();
69+
(async () => {
70+
const rem = await ScratchTools.waitForElement("input.project-title-input_title-field_en5Gd")
71+
resolve(rem);
72+
})();
73+
(async () => {
74+
const rem = await ScratchTools.waitForElement(".project-title")
75+
resolve(rem);
76+
})();
77+
})
78+
79+
let projectTitle = document.querySelector("input.inplace-input") || document.querySelector("input.project-title-input_title-field_en5Gd") || document.querySelector(".project-title");
80+
81+
ScratchTools.waitForElements("input.inplace-input", async function (_projectTitle) {
82+
projectTitle = _projectTitle
83+
})
84+
85+
ScratchTools.waitForElements("input.project-title-input_title-field_en5Gd", async function (_projectTitle) {
86+
projectTitle = _projectTitle
87+
})
88+
89+
ScratchTools.waitForElements(".project-title", async function (_projectTitle) {
90+
projectTitle = _projectTitle
91+
})
92+
6393

6494
let mediaRecorder;
6595
let recordedChunks = [];
66-
// console.log(startButton)
67-
// Start recording
68-
startButton.addEventListener('click', () => {
96+
97+
startButton.addEventListener('click', async () => {
6998
startButton.classList.add("STE-hide-button");
7099
stopButton.classList.remove("STE-hide-button");
71100

72101
// Capture the canvas element as a stream
73-
const stream = canvas.captureStream(30); // 30 FPS
74-
mediaRecorder = new MediaRecorder(stream);
102+
const canvasStream = canvas.captureStream(30); // 30 FPS
103+
104+
// Get the audio context from the Scratch VM
105+
const audioContext = feature.traps.vm.runtime.audioEngine.audioContext;
106+
const audioDestination = audioContext.createMediaStreamDestination();
107+
108+
if (microphoneCheckbox.checked) {
109+
// Capture the microphone audio
110+
let micStream;
111+
try {
112+
micStream = await navigator.mediaDevices.getUserMedia({ audio: true });
113+
} catch (err) {
114+
console.error("Error capturing microphone audio:", err);
115+
}
116+
117+
if (micStream) {
118+
const micSource = audioContext.createMediaStreamSource(micStream);
119+
micSource.connect(audioDestination);
120+
}
121+
}
122+
123+
// Connect the audio engine's output
124+
if (desktopSoundCheckbox.checked) {
125+
feature.traps.vm.runtime.audioEngine.inputNode.connect(audioDestination);
126+
}
127+
128+
// Combine the canvas video track and audio tracks
129+
const combinedStream = new MediaStream();
130+
canvasStream.getVideoTracks().forEach(track => combinedStream.addTrack(track));
131+
if (microphoneCheckbox.checked || desktopSoundCheckbox.checked) {
132+
audioDestination.stream.getAudioTracks().forEach(track => combinedStream.addTrack(track));
133+
}
134+
135+
mediaRecorder = new MediaRecorder(combinedStream);
75136

76137
mediaRecorder.ondataavailable = function (event) {
77138
if (event.data.size > 0) {
@@ -85,19 +146,20 @@ export default async function ({ feature, console }) {
85146
});
86147
preview.src = URL.createObjectURL(blob);
87148
preview.controls = true;
88-
preview.download = `${projectTitle.value}.${mimeType.value}`
89-
downloadButton.removeEventListener("click", lastDownloadFunction)
149+
// console.log(projectTitle)
150+
preview.download = `${projectTitle.value}.${mimeType.value}`;
151+
downloadButton.removeEventListener("click", lastDownloadFunction);
90152
lastDownloadFunction = async () => {
91-
const url = URL.createObjectURL(blob)
92-
const a = document.createElement('a')
93-
a.href = url
94-
a.download = `${projectTitle.value}.${mimeType.value}`
95-
document.body.appendChild(a)
96-
a.click()
97-
document.body.removeChild(a)
98-
URL.revokeObjectURL(url)
153+
const url = URL.createObjectURL(blob);
154+
const a = document.createElement('a');
155+
a.href = url;
156+
a.download = `${projectTitle.value}.${mimeType.value}`;
157+
document.body.appendChild(a);
158+
a.click();
159+
document.body.removeChild(a);
160+
URL.revokeObjectURL(url);
99161
}
100-
downloadButton.addEventListener("click", lastDownloadFunction)
162+
downloadButton.addEventListener("click", lastDownloadFunction);
101163
recordedChunks = [];
102164
};
103165

@@ -106,7 +168,6 @@ export default async function ({ feature, console }) {
106168
stopButton.disabled = false;
107169
});
108170

109-
// Stop recording
110171
stopButton.addEventListener('click', () => {
111172
mediaRecorder.stop();
112173
startButton.disabled = false;

0 commit comments

Comments
 (0)