Skip to content

Commit 9964e00

Browse files
author
hoang.tran12
committed
WIP
1 parent d194336 commit 9964e00

File tree

5 files changed

+305
-30
lines changed

5 files changed

+305
-30
lines changed

manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"downloads",
2323
"bookmarks",
2424
"scripting",
25+
"tabCapture",
2526
"contextMenus",
2627
"webNavigation",
2728
"notifications",

scripts/_test.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Document</title>
8+
9+
<script src="_test_main.js"></script>
10+
</head>
11+
12+
<body>
13+
</body>
14+
15+
</html>

scripts/_test.js

Lines changed: 226 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { getCurrentTabId, runScriptInTab } from "./helpers/utils.js";
2+
13
export default {
24
icon: "",
35
name: {
@@ -11,9 +13,65 @@ export default {
1113

1214
// whiteList: ["https://www.google.com/*"],
1315

14-
onClick: async () => {
16+
onClickExtension: async () => {
17+
// https://developer.chrome.com/docs/extensions/reference/api/tabCapture#preserving-system-audio
18+
try {
19+
const currentTabID = await getCurrentTabId();
20+
const newTab = await chrome.windows.create({
21+
url: "/scripts/_test.html",
22+
type: "popup",
23+
width: 800,
24+
height: 600,
25+
});
26+
27+
const streamId = await chrome.tabCapture.getMediaStreamId({
28+
consumerTabId: newTab.id,
29+
targetTabId: currentTabID,
30+
});
31+
32+
alert(streamId);
33+
34+
runScriptInTab({
35+
func: (streamId) => {
36+
window.setStreamId?.(streamId);
37+
},
38+
args: [streamId],
39+
tabId: newTab.id,
40+
});
41+
} catch (e) {
42+
console.error(e);
43+
}
44+
},
45+
46+
onClick_: async () => {
1547
//https://www.youtube.com/watch?v=uk96O7N1Yo0
48+
// https://www.skilldrick.co.uk/fft/
49+
// https://stackoverflow.com/a/61301293/23648002
50+
// https://www.renderforest.com/music-visualisations
51+
1652
javascript: (function () {
53+
var ctx;
54+
var width = 1000;
55+
var fftHeight = 250;
56+
var height = fftHeight + 20;
57+
var fftSize = 2048; // number of samples used to generate each FFT
58+
var frequencyBins = fftSize / 2; // number of frequency bins in FFT
59+
var video;
60+
61+
function requestPIPCanvas(canvas) {
62+
const stream = canvas.captureStream();
63+
if (!video) {
64+
video = document.createElement("video");
65+
video.autoplay = true;
66+
video.style.display = "none";
67+
}
68+
video.srcObject = stream;
69+
document.body.appendChild(video);
70+
setTimeout(() => {
71+
video.requestPictureInPicture?.();
72+
}, 500);
73+
}
74+
1775
function draggable(ele) {
1876
// Variables to store the position of the canvas
1977
var offsetX, offsetY;
@@ -27,7 +85,7 @@ export default {
2785
});
2886

2987
// Function to handle mouse move event
30-
ele.addEventListener("mousemove", function (event) {
88+
document.addEventListener("mousemove", function (event) {
3189
if (!isDragging) return;
3290
var x = event.clientX - offsetX;
3391
var y = event.clientY - offsetY;
@@ -36,7 +94,7 @@ export default {
3694
});
3795

3896
// Function to handle mouse up event
39-
ele.addEventListener("mouseup", function () {
97+
document.addEventListener("mouseup", function () {
4098
isDragging = false;
4199
});
42100
}
@@ -47,32 +105,172 @@ export default {
47105
);
48106
}
49107

108+
function applyLog(fftArray) {}
109+
110+
function smoothFFT(fftArray, smoothingFactor = 0.8) {
111+
let smoothedFFT = [];
112+
smoothedFFT[0] = fftArray[0];
113+
for (let i = 1; i < fftArray.length; i++) {
114+
smoothedFFT[i] =
115+
fftArray[i] * smoothingFactor +
116+
smoothedFFT[i - 1] * (1 - smoothingFactor);
117+
}
118+
return smoothedFFT;
119+
}
120+
121+
function highlightBass(
122+
fftArray,
123+
samplingRate = 44100,
124+
bassRange = [20, 200]
125+
) {
126+
const fftSize = fftArray.length;
127+
const threshold = 0.5; // Adjust threshold value as needed (0 for hard removal)
128+
129+
for (let i = 0; i < fftSize; i++) {
130+
const freq = (i * samplingRate) / fftSize;
131+
if (freq < bassRange[0] || freq > bassRange[1]) {
132+
fftArray[i] *= threshold; // Apply threshold instead of hard removal
133+
}
134+
}
135+
136+
return fftArray;
137+
}
138+
139+
function logScale(fftArray, minDecibels = -60, maxDecibels = 0) {
140+
let minAmplitude = Math.pow(10, minDecibels / 10);
141+
let maxAmplitude = Math.pow(10, maxDecibels / 10);
142+
143+
const scale = (val) => {
144+
const scaledValue =
145+
10 * Math.log10(Math.max(val, minAmplitude)) -
146+
10 * Math.log10(minAmplitude);
147+
return Math.min(scaledValue, maxDecibels); // Cap the output at maxDecibels
148+
};
149+
150+
return fftArray.map((val) => scale(val));
151+
}
152+
153+
function drawLinearFFT(dataArray, canvasCtx) {
154+
canvasCtx.clearRect(0, 0, width, height);
155+
canvasCtx.beginPath();
156+
157+
var sliceLength = width / frequencyBins;
158+
159+
for (var i = 0; i < frequencyBins; i++) {
160+
var x = i * sliceLength;
161+
var y = fftHeight - (dataArray[i] * fftHeight) / 256;
162+
canvasCtx.lineTo(x, y);
163+
}
164+
165+
canvasCtx.stroke();
166+
}
167+
168+
function drawLogarithmicFFT(dataArray, canvasCtx) {
169+
canvasCtx.clearRect(0, 0, width, height);
170+
canvasCtx.beginPath();
171+
172+
var scale = Math.log(frequencyBins - 1) / width;
173+
var binWidthFreq = ctx.sampleRate / (frequencyBins * 2);
174+
var firstBinWidthPixels = Math.log(2) / scale;
175+
176+
for (var i = 1; i < frequencyBins; i++) {
177+
var x = Math.log(i) / scale;
178+
var y = fftHeight - (dataArray[i] * fftHeight) / 256;
179+
canvasCtx.lineTo(x, y);
180+
}
181+
182+
canvasCtx.stroke();
183+
}
184+
185+
function drawLinearScale(canvasCtx) {
186+
canvasCtx.save();
187+
canvasCtx.fillStyle = "black";
188+
189+
for (var x = 0; x < width; x += 100) {
190+
canvasCtx.beginPath();
191+
canvasCtx.moveTo(x, fftHeight);
192+
canvasCtx.lineTo(x, fftHeight + 4);
193+
canvasCtx.stroke();
194+
canvasCtx.fillText(
195+
Math.floor(((ctx.sampleRate / 2) * x) / width),
196+
x,
197+
height
198+
);
199+
}
200+
201+
canvasCtx.restore();
202+
}
203+
204+
function drawLogarithmicScale(canvasCtx) {
205+
canvasCtx.save();
206+
canvasCtx.fillStyle = "black";
207+
208+
var scale = Math.log(frequencyBins - 1) / width;
209+
var binWidthInHz = ctx.sampleRate / (frequencyBins * 2);
210+
var firstBinWidthInPx = Math.log(2) / scale;
211+
212+
for (
213+
var x = 0, freq = binWidthInHz;
214+
x < width;
215+
x += firstBinWidthInPx, freq *= 2
216+
) {
217+
canvasCtx.beginPath();
218+
canvasCtx.moveTo(x, fftHeight);
219+
canvasCtx.lineTo(x, fftHeight + 4);
220+
canvasCtx.stroke();
221+
canvasCtx.fillText(Math.floor(freq), Math.floor(x), height);
222+
}
223+
224+
canvasCtx.restore();
225+
}
226+
50227
function createAudioContext() {
51228
const audioContext = new (window.AudioContext ||
52229
window.webkitAudioContext)();
230+
ctx = audioContext;
53231
const analyser = audioContext.createAnalyser();
54-
analyser.fftSize = 2048;
232+
analyser.fftSize = fftSize;
55233
const bufferLength = analyser.frequencyBinCount;
56234
const dataArray = new Uint8Array(bufferLength);
57235

58236
const canvas = document.createElement("canvas");
237+
canvas.width = width;
238+
canvas.height = height;
59239
canvas.style.cssText =
60-
"position: fixed; top: 0; left: 0; width: 600px; height: 400px; z-index: 2147483647;";
240+
"position: fixed; top: 0; left: 0; z-index: 2147483647; background: #333a;";
61241
document.body.appendChild(canvas);
62-
const ctx = canvas.getContext("2d");
242+
const canvasCtx = canvas.getContext("2d");
63243
draggable(canvas);
64244

245+
canvas.onclick = function () {
246+
requestPIPCanvas(canvas);
247+
};
248+
65249
function draw() {
66250
analyser.getByteFrequencyData(dataArray);
67-
ctx.clearRect(0, 0, canvas.width, canvas.height);
68-
const barWidth = ~~(bufferLength / canvas.width);
69-
for (let x = 0; x < canvas.width; x++) {
70-
let i = x * barWidth;
71-
let item = dataArray[i];
72-
const barHeight = map(item, 0, 255, 0, canvas.height);
73-
ctx.fillStyle = `rgba(255, 255, 255, ${map(item, 0, 255, 0, 1)})`;
74-
ctx.fillRect(x, canvas.height - barHeight, 1, barHeight);
75-
}
251+
252+
canvasCtx.strokeStyle = "rgba(255, 255, 255, 0.9)";
253+
drawLogarithmicFFT(dataArray, canvasCtx);
254+
255+
// canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
256+
// const barWidth = ~~(bufferLength / canvas.width);
257+
258+
// const arr = highlightBass(dataArray, audioContext.sampleRate);
259+
// canvasCtx.beginPath();
260+
// canvasCtx.strokeStyle = "rgba(255, 255, 255, 0.9)";
261+
262+
// for (let x = 0; x < canvas.width; x++) {
263+
// let i = x * barWidth;
264+
// let item = arr[i];
265+
// const barHeight = map(item, 0, 255, 0, canvas.height);
266+
267+
// // line
268+
// canvasCtx.lineTo(x, canvas.height - barHeight);
269+
270+
// // canvasCtx.fillStyle = `rgba(255, 255, 255, ${map(item, 0, 255, 0, 1)})`;
271+
// // canvasCtx.fillRect(x, canvas.height - barHeight, 1, barHeight);
272+
// }
273+
// canvasCtx.stroke();
76274
requestAnimationFrame(draw);
77275
}
78276

@@ -103,19 +301,19 @@ export default {
103301
});
104302

105303
// Keep checking for new videos on the page
106-
setInterval(() => {
107-
const newVideos = document.querySelectorAll("video");
108-
newVideos.forEach((videoElement) => {
109-
const exists = contexts.some(
110-
(context) => context.videoElement === videoElement
111-
);
112-
if (!exists) {
113-
const { handleVideoAudio, canvas } = createAudioContext();
114-
handleVideoAudio(videoElement);
115-
contexts.push({ canvas, videoElement });
116-
}
117-
});
118-
}, 2000);
304+
// setInterval(() => {
305+
// const newVideos = document.querySelectorAll("video");
306+
// newVideos.forEach((videoElement) => {
307+
// const exists = contexts.some(
308+
// (context) => context.videoElement === videoElement
309+
// );
310+
// if (!exists) {
311+
// const { handleVideoAudio, canvas } = createAudioContext();
312+
// handleVideoAudio(videoElement);
313+
// contexts.push({ canvas, videoElement });
314+
// }
315+
// });
316+
// }, 2000);
119317
}
120318

121319
startAudioAnalysis();

scripts/_test_main.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
function setStreamId(streamId) {
2+
alert(streamId);
3+
navigator.mediaDevices
4+
.getUserMedia({
5+
audio: {
6+
mandatory: {
7+
chromeMediaSource: "tab",
8+
chromeMediaSourceId: streamId,
9+
},
10+
},
11+
})
12+
.then((tabStream) => {
13+
// at this point the sound of the tab becomes muted with no way to unmute it
14+
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
15+
const source = audioCtx.createMediaStreamSource(tabStream);
16+
const analyser = audioCtx.createAnalyser();
17+
analyser.fftSize = 2048;
18+
const bufferLength = analyser.frequencyBinCount;
19+
const dataArray = new Uint8Array(bufferLength);
20+
source.connect(analyser);
21+
analyser.connect(audioCtx.destination);
22+
23+
const canvas = document.createElement("canvas");
24+
canvas.width = 800;
25+
canvas.height = 200;
26+
canvas.style.cssText =
27+
"position: fixed; top: 0; left: 0; z-index: 2147483647; background: #333a;";
28+
document.body.appendChild(canvas);
29+
const canvasCtx = canvas.getContext("2d");
30+
31+
function draw() {
32+
analyser.getByteFrequencyData(dataArray);
33+
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
34+
canvasCtx.beginPath();
35+
const barWidth = ~~(bufferLength / canvas.width);
36+
for (let x = 0; x < canvas.width; x++) {
37+
let i = x * barWidth;
38+
let item = dataArray[i];
39+
const barHeight = map(item, 0, 255, 0, canvas.height);
40+
canvasCtx.lineTo(x, canvas.height - barHeight);
41+
}
42+
canvasCtx.strokeStyle = "rgba(255, 255, 255, 0.9)";
43+
canvasCtx.stroke();
44+
requestAnimationFrame(draw);
45+
}
46+
47+
function map(x, in_min, in_max, out_min, out_max) {
48+
return (
49+
((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
50+
);
51+
}
52+
53+
requestAnimationFrame(draw);
54+
})
55+
.catch((e) => {
56+
console.log("ERROR", e);
57+
});
58+
}

0 commit comments

Comments
 (0)