@@ -5,42 +5,39 @@ import { useClamp } from '@vueuse/math';
55import { AudioMotionAnalyzer } from ' audiomotion-analyzer'
66import ControlRotary from ' ./ControlRotary.vue' ;
77
8- let ctx, tempCanvas, tempCtx, audio
8+ let canvas, ctx, tempCanvas, tempCtx, audio
99
1010const screen = ref ()
11- const canvasElement = ref ()
1211const video = ref ()
1312
14- const { width , height } = useWindowSize ()
15- const { toggle } = useFullscreen (screen )
13+ const { toggle , isSupported } = useFullscreen (screen )
1614
1715const paused = ref (false )
1816const recording = ref (false )
17+ const videoRecording = ref (false )
1918const recordedWidth = ref (0 )
2019const showVideo = ref (false )
2120const initiated = ref (false )
2221const vertical = useStorage (' vertical' , false )
23- const invert = useStorage (' vertical' , false )
22+ const invert = useStorage (' inverted' , false )
23+
2424
25- watch ([ width, height], ([ w , h ]) => {
26- if ( ! canvasElement . value && ! tempCanvas) return
27- canvasElement . value .width = tempCanvas .width = w
28- canvasElement . value .height = tempCanvas .height = h
25+ const { width , height } = useWindowSize ()
26+ const setSize = ( w , h ) => {
27+ canvas .width = tempCanvas .width = w
28+ canvas .height = tempCanvas .height = h
2929 clear ()
30- })
30+ }
31+ watch ([width, height], ([w , h ]) => setSize (w, h))
3132
3233onMounted (() => {
33- ctx = canvasElement .value .getContext (' 2d' )
34+ canvas = document .createElement (' canvas' )
35+ ctx = canvas .getContext (' 2d' )
3436 tempCanvas = document .createElement (' canvas' )
3537 tempCtx = tempCanvas .getContext (' 2d' )
36- tempCanvas .width = width .value
37- tempCanvas .height = height .value
38- clear ()
39- const videostream = canvasElement .value .captureStream ();
38+ setSize (width .value , height .value )
39+ const videostream = canvas .captureStream ();
4040 video .value .srcObject = videostream;
41- video .value .play ()
42- .then (() => video .value ? .requestPictureInPicture ? .())
43- .catch (error => console .error (error));
4441});
4542
4643const smoothing = useClamp (useStorage (' smoothing' , 0.5 ), 0 , 0.9 )
@@ -75,11 +72,49 @@ function initiate() {
7572 const micStream = audio .audioCtx .createMediaStreamSource (stream)
7673 audio .connectInput (micStream)
7774 initiated .value = true
75+ video .value .play ()
7876 }).catch ((e ) => {
7977 console .log (' mic denied' , e)
8078 })
8179}
8280
81+ let recorder
82+
83+ const startVideo = () => {
84+ console .log (' hello' )
85+ videoRecording .value = Date .now ()
86+
87+ recorder = new MediaRecorder (video .value .srcObject )
88+
89+ recorder .ondataavailable = (event ) => {
90+ const blob = event .data ;
91+ const url = URL .createObjectURL (blob);
92+
93+ // Create a new window to display the video
94+ const newWindow = window .open (' ' , ' _blank' , ` width=${ width .value } ,height=${ height .value + 1 } ` );
95+ newWindow .document .write (`
96+ <html style="overscroll-behavior: none;"><body style="margin:0; background: black; position: relative">
97+ <button onclick="saveVideo()" style="position: absolute; top: 1em; left: 1em; font-size: 3em;">Download video</button>
98+ <video controls autoplay >
99+ <source src="${ url} " type="video/mp4">
100+ </video>
101+ </body></html>
102+ ` );
103+
104+ // Function to handle the "Save Video" button click
105+ newWindow .saveVideo = () => {
106+ const a = newWindow .document .createElement (' a' );
107+ a .href = url;
108+ a .download = ' recorded_video.mp4' ;
109+ a .click ();
110+ URL .revokeObjectURL (url);
111+ };
112+ };
113+ recorder .start ()
114+ }
115+
116+ const stopVideo = () => { videoRecording .value = false ; recorder? .stop () }
117+
83118let offscreenCanvas, offscreenCtx
84119
85120const startRecording = () => {
@@ -94,15 +129,23 @@ const startRecording = () => {
94129
95130const stopRecording = () => {
96131 recording .value = false ;
97-
98- // const link = document.createElement('a');
99- // link.download = 'canvas_recording.png';
100- // link.href = offscreenCanvas.toDataURL();
101- // link.click();
102-
132+ const filename = ` spectrogram_${ new Date ().toISOString ().slice (0 , 19 ).replace (/ T/ , ' _' )} .png` ;
103133 offscreenCanvas .toBlob ((blob ) => {
104134 const blobUrl = window .URL .createObjectURL (blob);
105- window .open (blobUrl, ' _blank' );
135+ // window.open(blobUrl, '_blank');
136+ const newWindow = window .open (undefined , ' _blank' );
137+ if (newWindow) {
138+ newWindow .document .write (`
139+ <html>
140+ <head>
141+ <title>${ filename} </title>
142+ </head>
143+ <body style="margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #222222;">
144+ <img src="${ blobUrl} " alt="${ filename} " style="cursor: pointer; max-width: 100%; max-height: 100vh; object-fit: contain;" onclick="const a = document.createElement('a'); a.href = '${ blobUrl} '; a.download = '${ filename} '; document.body.appendChild(a); a.click();">
145+ </body>
146+ </html>
147+ ` );
148+ }
106149 }, ' image/png' );
107150};
108151
@@ -124,7 +167,7 @@ const recordFrame = () => {
124167 offscreenCtx .drawImage (recTemp, 0 , 0 )
125168
126169 offscreenCtx .drawImage (
127- canvasElement . value , // Source canvas
170+ canvas , // Source canvas
128171 width .value - speed .value , 0 , // Source area (rightmost line)
129172 speed .value , height .value , // Size of the copied area
130173 recordedWidth .value , 0 , // Destination (append at the right)
@@ -141,7 +184,7 @@ const sigmoid = (value) => 1 / (1 + Math.exp(-steepness.value * (value - midpoin
141184const onCanvasDraw = (instance ) => {
142185 if (paused .value ) return ;
143186
144- tempCtx .drawImage (canvasElement . value , 0 , 0 , width .value , height .value , 0 , 0 , width .value , height .value );
187+ tempCtx .drawImage (canvas , 0 , 0 , width .value , height .value , 0 , 0 , width .value , height .value );
145188
146189 const bars = instance .getBars ().map (bar => colorFreq (bar .freq , sigmoid (bar .value [0 ])));
147190
@@ -180,11 +223,7 @@ onKeyStroke('Enter', (e) => { e.preventDefault(); clear(); })
180223< template lang= " pug" >
181224.flex .flex - col .justify - center .bg - black .relative
182225 .fullscreen - container#screen (ref= " screen" )
183- canvas#spectrogram .max - w- full (
184- ref= " canvasElement"
185- : width= " width"
186- : height= " height"
187- )
226+ video .max - w- full (ref= " video" @click= " paused = !paused" )
188227 button .absolute .m - auto .top - 0 .w - full .h - full .text - white .text - 2xl (
189228 title= " Press anywhere to start"
190229 v- if = " !initiated"
@@ -199,12 +238,23 @@ onKeyStroke('Enter', (e) => { e.preventDefault(); clear(); })
199238 .i - la- arrow- down (v- else )
200239 button .text - xl .select - none .cursor - pointer (@pointerdown= " clear()" )
201240 .i - la- trash- alt
241+ button .text - xl .select - none .cursor - pointer (@pointerdown= " toggle()" )
242+ .i - la- expand
243+ button .text - xl .select - none .cursor - pointer .transition (
244+ v- if = " video?.requestPictureInPicture"
245+ @pointerdown= " video?.requestPictureInPicture?.()" )
246+ .i - la- external- link- square- alt
202247 button .text - xl .select - none .cursor - pointer .flex .items - center .gap - 1 (
203248 : class = " { 'text-red': recording }"
204249 @pointerdown= " recording ? stopRecording() : startRecording()" )
205250 .i - la- circle (v- if = " !recording" )
206251 .i - la- dot- circle (v- else )
207252 .p - 0 .text - sm .font - mono (v - if = " recording && recordedWidth" ) {{ recordedWidth }}px ({{ ((time - recording) / 1000 ).toFixed (1 ) }}s)
253+ button .text - xl .select - none .cursor - pointer .flex .items - center .gap - 1 (
254+ : class = " { 'text-red': videoRecording }"
255+ @pointerdown= " !videoRecording ? startVideo() : stopVideo()" )
256+ .i - la- video
257+ .p - 0 .text - sm .font - mono (v - if = " videoRecording" ) {{ ((time - videoRecording) / 1000 ).toFixed () }}s
208258
209259
210260 .absolute .top - 14 .left - 2 .flex .flex - col .text - white .items - center .overscroll - none .overflow - x- hidden .overflow - y- scroll .bg - dark- 900 .bg - op- 20 .backdrop - blur .op - 40 .hover - op- 100 .transition (v- show= " initiated" )
@@ -214,18 +264,7 @@ onKeyStroke('Enter', (e) => { e.preventDefault(); clear(); })
214264 ControlRotary (v- model= " steepness" : min= " 3" : max= " 30" : step= " 0.0001" : fixed= " 2" param= " CONTRAST" )
215265 ControlRotary (v- model= " midpoint" : min= " 0" : max= " 1" : step= " .0001" param= " MIDPOINT" : fixed= " 2" )
216266 ControlRotary (v- model= " smoothing" : min= " 0" : max= " 1" : step= " .0001" param= " SMOOTH" : fixed= " 2" )
217- .flex - 1
218267
219- button .top - 4 .right - 4 .text - xl .select - none .cursor - pointer .transition (
220- : style= " { opacity: showVideo ? 1 : 0.2 }"
221- @pointerdown= " showVideo = !showVideo" )
222- .i - la- external- link- square- alt
223- button .text - xl .select - none .cursor - pointer (@pointerdown= " toggle()" )
224- .i - la- expand
225- .fixed .overflow - clip .text - white .transition .bottom - 4 .left - 18 .rounded - xl .overflow - hidden (v- show= " showVideo" )
226- .relative
227- .absolute .p - 2 .opacity - 70 .touch - none .select - none .text - md Right click here to enter Picture- In- Picture mode
228- video .max - h- 50 .max - w- full (ref= " video" )
229268
230269< / template>
231270
0 commit comments