Skip to content

Commit ae5a687

Browse files
author
hoang.tran12
committed
share audio to other tab
1 parent 3b33e5c commit ae5a687

File tree

4 files changed

+95
-246
lines changed

4 files changed

+95
-246
lines changed

public/music-visualizer/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<meta charset="UTF-8">
66
<meta name="viewport" content="width=device-width, initial-scale=1.0">
77
<title>Document</title>
8-
<script src="https://cdn.jsdelivr.net/npm/p5@1.9.3/lib/p5.js"></script>
8+
<!-- <script src="https://cdn.jsdelivr.net/npm/p5@1.9.3/lib/p5.js"></script> -->
99
<script src="./main.js"></script>
1010
</head>
1111

public/music-visualizer/main.js

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,34 @@ function start(streamId) {
2121
video: false,
2222
},
2323
function (stream) {
24-
draw(stream);
24+
drawVisualizer(stream);
2525
},
2626
function (error) {
2727
console.error(error);
2828
}
2929
);
3030
}
3131

32-
function draw(stream) {
32+
function drawVisualizer(stream) {
3333
// at this point the sound of the tab becomes muted with no way to unmute it
3434
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
3535
const source = audioCtx.createMediaStreamSource(stream);
3636
const analyser = audioCtx.createAnalyser();
3737
analyser.fftSize = 2048;
38+
source.connect(analyser);
39+
// analyser.connect(audioCtx.destination); // play stream to speaker - we dont need it, original tab already play it
40+
3841
const bufferLength = analyser.frequencyBinCount;
3942
const dataArray = new Uint8Array(bufferLength);
40-
source.connect(analyser);
41-
analyser.connect(audioCtx.destination);
4243

4344
const canvas = document.createElement("canvas");
44-
canvas.width = 800;
45-
canvas.height = 200;
45+
canvas.width = window.innerWidth;
46+
canvas.height = window.innerHeight;
47+
window.onresize = () => {
48+
canvas.width = window.innerWidth;
49+
canvas.height = window.innerHeight;
50+
};
51+
4652
canvas.style.cssText =
4753
"position: fixed; top: 0; left: 0; z-index: 2147483647; background: #333a;";
4854
document.body.appendChild(canvas);
@@ -52,12 +58,14 @@ function draw(stream) {
5258
analyser.getByteFrequencyData(dataArray);
5359
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
5460
canvasCtx.beginPath();
55-
const barWidth = ~~(bufferLength / canvas.width);
61+
62+
const data = smoothFFT(dataArray);
63+
const barWidth = bufferLength / canvas.width;
5664
for (let x = 0; x < canvas.width; x++) {
57-
let i = x * barWidth;
58-
let item = dataArray[i];
59-
const barHeight = map(item, 0, 255, 0, canvas.height);
60-
canvasCtx.lineTo(x, canvas.height - barHeight);
65+
let i = ~~(x * barWidth);
66+
let item = data[i];
67+
const barHeight = map(item, 0, 255, 0, canvas.height / 2);
68+
canvasCtx.lineTo(x, canvas.height / 2 - barHeight);
6169
}
6270
canvasCtx.strokeStyle = "rgba(255, 255, 255, 0.9)";
6371
canvasCtx.stroke();
@@ -70,3 +78,42 @@ function draw(stream) {
7078

7179
requestAnimationFrame(draw);
7280
}
81+
82+
function smoothFFT(fftArray, smoothingFactor = 0.8) {
83+
let smoothedFFT = [];
84+
smoothedFFT[0] = fftArray[0];
85+
for (let i = 1; i < fftArray.length; i++) {
86+
smoothedFFT[i] =
87+
fftArray[i] * smoothingFactor +
88+
smoothedFFT[i - 1] * (1 - smoothingFactor);
89+
}
90+
return smoothedFFT;
91+
}
92+
93+
function highlightBass(fftArray, samplingRate = 44100, bassRange = [20, 200]) {
94+
const fftSize = fftArray.length;
95+
const threshold = 0.5; // Adjust threshold value as needed (0 for hard removal)
96+
97+
for (let i = 0; i < fftSize; i++) {
98+
const freq = (i * samplingRate) / fftSize;
99+
if (freq < bassRange[0] || freq > bassRange[1]) {
100+
fftArray[i] *= threshold; // Apply threshold instead of hard removal
101+
}
102+
}
103+
104+
return fftArray;
105+
}
106+
107+
function logScale(fftArray, minDecibels = -60, maxDecibels = 0) {
108+
let minAmplitude = Math.pow(10, minDecibels / 10);
109+
let maxAmplitude = Math.pow(10, maxDecibels / 10);
110+
111+
const scale = (val) => {
112+
const scaledValue =
113+
10 * Math.log10(Math.max(val, minAmplitude)) -
114+
10 * Math.log10(minAmplitude);
115+
return Math.min(scaledValue, maxDecibels); // Cap the output at maxDecibels
116+
};
117+
118+
return fftArray.map((val) => scale(val));
119+
}

scripts/_test.js

Lines changed: 32 additions & 229 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,38 @@ export default {
2626
// https://groups.google.com/a/chromium.org/g/chromium-extensions/c/ffI0iNd79oo
2727
// https://github.dev/GoogleChrome/chrome-extensions-samples/api-samples/tabCapture
2828

29-
onClickExtension: async () => {
29+
onClick: async () => {
30+
// https://developer.chrome.com/docs/extensions/how-to/web-platform/screen-capture
31+
const stream = await navigator.mediaDevices.getDisplayMedia({
32+
audio: true,
33+
video: true,
34+
});
35+
36+
drawVisualizer(stream);
37+
38+
// const streamId = await UfsGlobal.Extension.runInBackground(
39+
// "chrome.tabCapture.getMediaStreamId"
40+
// );
41+
// navigator.webkitGetUserMedia(
42+
// {
43+
// audio: {
44+
// mandatory: {
45+
// chromeMediaSource: "tab", // The media source must be 'tab' here.
46+
// chromeMediaSourceId: streamId,
47+
// },
48+
// },
49+
// video: false,
50+
// },
51+
// function (stream) {
52+
// console.log(stream);
53+
// },
54+
// function (error) {
55+
// console.error(error);
56+
// }
57+
// );
58+
},
59+
60+
_onClickExtension: async () => {
3061
try {
3162
// const url = "http://127.0.0.1:5500/public/music-visualizer/index.html";
3263
const url = await chrome.runtime.getURL(
@@ -86,232 +117,4 @@ export default {
86117
console.log(e);
87118
}
88119
},
89-
90-
onClick_: async () => {
91-
javascript: (function () {
92-
var ctx;
93-
var width = 1000;
94-
var fftHeight = 250;
95-
var height = fftHeight + 20;
96-
var fftSize = 2048; // number of samples used to generate each FFT
97-
var frequencyBins = fftSize / 2; // number of frequency bins in FFT
98-
var video;
99-
100-
function requestPIPCanvas(canvas) {
101-
const stream = canvas.captureStream();
102-
if (!video) {
103-
video = document.createElement("video");
104-
video.autoplay = true;
105-
video.style.display = "none";
106-
}
107-
video.srcObject = stream;
108-
document.body.appendChild(video);
109-
setTimeout(() => {
110-
video.requestPictureInPicture?.();
111-
}, 500);
112-
}
113-
114-
function draggable(ele) {
115-
// Variables to store the position of the canvas
116-
var offsetX, offsetY;
117-
var isDragging = false;
118-
119-
// Function to handle mouse down event
120-
ele.addEventListener("mousedown", function (event) {
121-
isDragging = true;
122-
offsetX = event.clientX - ele.offsetLeft;
123-
offsetY = event.clientY - ele.offsetTop;
124-
});
125-
126-
// Function to handle mouse move event
127-
document.addEventListener("mousemove", function (event) {
128-
if (!isDragging) return;
129-
var x = event.clientX - offsetX;
130-
var y = event.clientY - offsetY;
131-
ele.style.left = x + "px";
132-
ele.style.top = y + "px";
133-
});
134-
135-
// Function to handle mouse up event
136-
document.addEventListener("mouseup", function () {
137-
isDragging = false;
138-
});
139-
}
140-
141-
function map(x, in_min, in_max, out_min, out_max) {
142-
return (
143-
((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
144-
);
145-
}
146-
147-
function smoothFFT(fftArray, smoothingFactor = 0.8) {
148-
let smoothedFFT = [];
149-
smoothedFFT[0] = fftArray[0];
150-
for (let i = 1; i < fftArray.length; i++) {
151-
smoothedFFT[i] =
152-
fftArray[i] * smoothingFactor +
153-
smoothedFFT[i - 1] * (1 - smoothingFactor);
154-
}
155-
return smoothedFFT;
156-
}
157-
158-
function highlightBass(
159-
fftArray,
160-
samplingRate = 44100,
161-
bassRange = [20, 200]
162-
) {
163-
const fftSize = fftArray.length;
164-
const threshold = 0.5; // Adjust threshold value as needed (0 for hard removal)
165-
166-
for (let i = 0; i < fftSize; i++) {
167-
const freq = (i * samplingRate) / fftSize;
168-
if (freq < bassRange[0] || freq > bassRange[1]) {
169-
fftArray[i] *= threshold; // Apply threshold instead of hard removal
170-
}
171-
}
172-
173-
return fftArray;
174-
}
175-
176-
function logScale(fftArray, minDecibels = -60, maxDecibels = 0) {
177-
let minAmplitude = Math.pow(10, minDecibels / 10);
178-
let maxAmplitude = Math.pow(10, maxDecibels / 10);
179-
180-
const scale = (val) => {
181-
const scaledValue =
182-
10 * Math.log10(Math.max(val, minAmplitude)) -
183-
10 * Math.log10(minAmplitude);
184-
return Math.min(scaledValue, maxDecibels); // Cap the output at maxDecibels
185-
};
186-
187-
return fftArray.map((val) => scale(val));
188-
}
189-
190-
function drawLinearFFT(dataArray, canvasCtx) {
191-
canvasCtx.clearRect(0, 0, width, height);
192-
canvasCtx.beginPath();
193-
194-
var sliceLength = width / frequencyBins;
195-
196-
for (var i = 0; i < frequencyBins; i++) {
197-
var x = i * sliceLength;
198-
var y = fftHeight - (dataArray[i] * fftHeight) / 256;
199-
canvasCtx.lineTo(x, y);
200-
}
201-
202-
canvasCtx.stroke();
203-
}
204-
205-
function drawLogarithmicFFT(dataArray, canvasCtx) {
206-
canvasCtx.clearRect(0, 0, width, height);
207-
canvasCtx.beginPath();
208-
209-
var scale = Math.log(frequencyBins - 1) / width;
210-
var binWidthFreq = ctx.sampleRate / (frequencyBins * 2);
211-
var firstBinWidthPixels = Math.log(2) / scale;
212-
213-
for (var i = 1; i < frequencyBins; i++) {
214-
var x = Math.log(i) / scale;
215-
var y = fftHeight - (dataArray[i] * fftHeight) / 256;
216-
canvasCtx.lineTo(x, y);
217-
}
218-
219-
canvasCtx.stroke();
220-
}
221-
222-
function createAudioContext() {
223-
const audioContext = new (window.AudioContext ||
224-
window.webkitAudioContext)();
225-
ctx = audioContext;
226-
const analyser = audioContext.createAnalyser();
227-
analyser.fftSize = fftSize;
228-
const bufferLength = analyser.frequencyBinCount;
229-
const dataArray = new Uint8Array(bufferLength);
230-
231-
const canvas = document.createElement("canvas");
232-
canvas.width = width;
233-
canvas.height = height;
234-
canvas.style.cssText =
235-
"position: fixed; top: 0; left: 0; z-index: 2147483647; background: #333a;";
236-
document.body.appendChild(canvas);
237-
const canvasCtx = canvas.getContext("2d");
238-
draggable(canvas);
239-
240-
canvas.onclick = function () {
241-
requestPIPCanvas(canvas);
242-
};
243-
244-
function draw() {
245-
analyser.getByteFrequencyData(dataArray);
246-
247-
canvasCtx.strokeStyle = "rgba(255, 255, 255, 0.9)";
248-
drawLogarithmicFFT(dataArray, canvasCtx);
249-
250-
// canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
251-
// const barWidth = ~~(bufferLength / canvas.width);
252-
253-
// const arr = highlightBass(dataArray, audioContext.sampleRate);
254-
// canvasCtx.beginPath();
255-
// canvasCtx.strokeStyle = "rgba(255, 255, 255, 0.9)";
256-
257-
// for (let x = 0; x < canvas.width; x++) {
258-
// let i = x * barWidth;
259-
// let item = arr[i];
260-
// const barHeight = map(item, 0, 255, 0, canvas.height);
261-
262-
// // line
263-
// canvasCtx.lineTo(x, canvas.height - barHeight);
264-
265-
// // canvasCtx.fillStyle = `rgba(255, 255, 255, ${map(item, 0, 255, 0, 1)})`;
266-
// // canvasCtx.fillRect(x, canvas.height - barHeight, 1, barHeight);
267-
// }
268-
// canvasCtx.stroke();
269-
requestAnimationFrame(draw);
270-
}
271-
272-
draw();
273-
274-
function handleVideoAudio(videoElement) {
275-
const source = audioContext.createMediaElementSource(videoElement);
276-
source.connect(analyser);
277-
analyser.connect(audioContext.destination);
278-
}
279-
280-
return { handleVideoAudio, canvas };
281-
}
282-
283-
function startAudioAnalysis() {
284-
if (!window.AudioContext) {
285-
alert("Your browser doesn't support Web Audio API");
286-
return;
287-
}
288-
289-
const videoElements = document.querySelectorAll("video");
290-
const contexts = [];
291-
292-
videoElements.forEach((videoElement) => {
293-
const { handleVideoAudio, canvas } = createAudioContext();
294-
handleVideoAudio(videoElement);
295-
contexts.push({ canvas, videoElement });
296-
});
297-
298-
// Keep checking for new videos on the page
299-
// setInterval(() => {
300-
// const newVideos = document.querySelectorAll("video");
301-
// newVideos.forEach((videoElement) => {
302-
// const exists = contexts.some(
303-
// (context) => context.videoElement === videoElement
304-
// );
305-
// if (!exists) {
306-
// const { handleVideoAudio, canvas } = createAudioContext();
307-
// handleVideoAudio(videoElement);
308-
// contexts.push({ canvas, videoElement });
309-
// }
310-
// });
311-
// }, 2000);
312-
}
313-
314-
startAudioAnalysis();
315-
})();
316-
},
317120
};

working_note.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@
6363

6464
- [ ] Thêm change logs cho từng scripts
6565

66-
- [ ] chụp ảnh website bằng chrome.tabs.captureVisibleTab
67-
68-
- [ ] chrome.tabCapture [link](https://developer.chrome.com/docs/extensions/reference/api/tabCapture) => access MediaStream of current tab
69-
70-
- [ ] screen capture [here](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSupportedConstraints/suppressLocalAudioPlayback)
66+
- [ ] Xem google extension howto [Link](https://developer.chrome.com/docs/extensions/how-to)
67+
- [ ] record audio and video from another tab [link](https://developer.chrome.com/docs/extensions/how-to/web-platform/screen-capture) => **TIỀM NĂNG làm web visualize music**
68+
- [x] chụp ảnh website bằng chrome.tabs.captureVisibleTab => bỏ, không có tiềm năng
69+
- [x] chrome.tabCapture [link](https://developer.chrome.com/docs/extensions/reference/api/tabCapture) => access MediaStream of current tab => phải là 1 page trong extension mới truy cập được => không tiềm năng

0 commit comments

Comments
 (0)