PageSourceSearch

https://plato-philosophy.org/wp-content/plugins/voice-widgets-pro/…?ver=97e7e78c3d32dcdc2ef09ee53fdaac07

js plato-philosophy.org collected 2026-10-02 05:13:50 UTC 37,961 bytes, 1,003 lines download raw bytes

1"use strict";
2
3jQuery( document ).ready( function( $ ) {
4
5    //console.log('wroking')
6
7    let gumStream; // Stream from getUserMedia()
8    let rec; // Recorder.js object
9    let recorder; // Recorder.js object
10    let input; // MediaStreamAudioSourceNode we'll be recording
11    let audioContext;
12    let audioBlob;
13    let drawVisual;
14    let draw;
15    let timerInterval;
16    let countdownInterval;
17    let isTimerPaused = false;
18    let isCountdownPaused = false;
19    let form;
20    let sampleRate;
21    let encodeAfterRecord = true;
22
23    var record_object = {};
24    record_object.record_id = [];
25    record_object.type = 'qccf7wpvoicemessage_gravityforms';
26
27    /** Open popup. */
28    let $fbutton = $('#vmwpmdp-wpvoicemessage_gravityforms-fbutton');
29    if ($fbutton.length) {
30        $fbutton.on('click', openPopup);
31    }
32
33    /** Close modal by overlay click. */
34    let $overlay = $('.vmwpmdp-wpvoicemessage_gravityforms-fbutton-overlay');
35    if ($overlay.length) {
36        $overlay.on('click', closePopup);
37
38        /** Close modal by close button click. */
39        $overlay.find('.vmwpmdp-wpvoicemessage_gravityforms-fbutton-modal .vmwpmdp-wpvoicemessage_gravityforms-close').on('click', closePopup);
40    }
41
42    /**
43     * Do not close popup by click on window
44     * and stop event propagation to overlay.
45     **/
46    let $popup = $('.vmwpmdp-wpvoicemessage_gravityforms-fbutton-modal');
47    if ($popup.length) {
48        $popup.on('click', (event) => { event.stopPropagation(); });
49    }
50
51    /** Add Events to buttons using event delegation for dynamic content. */
52    // Attaching a listener to a static parent (.vmwpmdp-wpvoicemessage_gravityforms-form-box)
53    // or document.body is much more robust than looping through elements,
54    // especially if you are loading them via AJAX.
55    $(document).on('click', '.vmwpmdp-wpvoicemessage_gravityforms-form-box .vmwpmdp-wpvoicemessage_gravityforms-start-btn', function(e) {
56        const $cForm = $(this).closest('.vmwpmdp-wpvoicemessage_gravityforms-form-box');
57        const parent_id = $(this).closest('[id]').attr('id');
58        startRecordingButtonClick($cForm, e, parent_id);
59    });
60    
61    $(document).on('click', '.vmwpmdp-wpvoicemessage_gravityforms-form-box .vmwpmdp-reset-rec-btn', function() {
62        const $cForm = $(this).closest('.vmwpmdp-wpvoicemessage_gravityforms-form-box');
63        if ($(this).closest('.vmwpmdp-speak-now-btns').length) {
64            resetButtonClick($cForm);
65        } else if ($(this).closest('.vmwpmdp-send-btns').length) {
66            resetSendButtonClick($cForm);
67        }
68    });
69    
70    $(document).on('click', '.vmwpmdp-wpvoicemessage_gravityforms-form-box .vmwpmdp-stop-rec-btn', function(e) {
71        const $cForm = $(this).closest('.vmwpmdp-wpvoicemessage_gravityforms-form-box');
72        const parent_id = $(this).closest('[id]').attr('id');
73        stopButtonClick($cForm, e, parent_id);
74    });
75    
76    $(document).on('click', '.vmwpmdp-wpvoicemessage_gravityforms-thanks-box .vmwpmdp-restart', function() {
77        const $cForm = $(this).closest('.vmwpmdp-wpvoicemessage_gravityforms-form-box');
78        yesResetButtonClick($cForm);
79    });
80
81    $(document).on('click', '.vmwpmdp-wpvoicemessage_gravityforms-send-box .vmwpmdp-send-rec-btn', function() {
82        const $cForm = $(this).closest('.vmwpmdp-wpvoicemessage_gravityforms-form-box');
83        sendButtonClick($cForm);
84    });
85    
86    $(document).on('click', '.vmwpmdp-wpvoicemessage_gravityforms-form-box .vmwpmdp-reset-rec-back', function() {
87        const $cForm = $(this).closest('.vmwpmdp-wpvoicemessage_gravityforms-form-box');
88        backButtonClick($cForm);
89    });
90
91    $(document).on('click', '.vmwpmdp-wpvoicemessage_gravityforms-form-box .vmwpmdp-reset-rec-yes', function() {
92        const $cForm = $(this).closest('.vmwpmdp-wpvoicemessage_gravityforms-form-box');
93        yesResetButtonClick($cForm);
94    });
95
96    $(document).on('click', '.vmwpmdp-wpvoicemessage_gravityforms-form-box .vmwpmdp-reset-rec-no', function(e) {
97        const $cForm = $(this).closest('.vmwpmdp-wpvoicemessage_gravityforms-form-box');
98        noResetButtonClick($(e.target), $cForm);
99    });
100
101    // Handle form submission using AJAX.
102    $('.vmwpmdp-wpvoicemessage_gravityforms-send-box form').on('submit', function(e) {
103        e.preventDefault();
104    });
105
106    /** Try again button after microphone access error. */
107    $('.vmwpmdp-mic-access-err-reload-btn').on('click', () => { location.reload(); });
108
109
110    /**
111     * Open Popup.
112     **/
113    function openPopup() {
114        let $root = $('html');
115        let $overlay = $('.vmwpmdp-wpvoicemessage_gravityforms-fbutton-overlay');
116        $overlay.show();
117        $root.addClass('vmwpmdp-wpvoicemessage_gravityforms-modal-opened');
118    }
119
120    /**
121     * Close Popup.
122     **/
123    function closePopup() {
124        let $root = $('html');
125        let $overlay = $('.vmwpmdp-wpvoicemessage_gravityforms-fbutton-overlay');
126
127        /** Reverse animation */
128        $root.addClass('vmwpmdp-wpvoicemessage_gravityforms-reverse');
129
130        /** Hide all */
131        setTimeout(function() {
132            $overlay.hide();
133            $root.removeClass('vmwpmdp-wpvoicemessage_gravityforms-modal-opened');
134            $root.removeClass('vmwpmdp-wpvoicemessage_gravityforms-reverse');
135
136            /** Show Start Recording step. */
137            let $pop_cForm = $('.vmwpmdp-wpvoicemessage_gravityforms-fbutton-modal .vmwpmdp-wpvoicemessage_gravityforms-form-box');
138            showStartRecordingStep($pop_cForm);
139
140            /** Stop all media stream. */
141            stopAllStreams();
142
143        }, 600);
144    }
145
146    /**
147     * No button on Reset view.
148     **/
149    function noResetButtonClick($target, $cForm) {
150        if ('showSendStep' === $target.attr('show-step')) {
151            /** Show Send view. */
152            showSendStep($cForm);
153            $target.attr('show-step', '');
154        } else {
155            /** Resume Recording. */
156            rec.record();
157            log('Recording resumed.');
158
159            /** Resume timer. */
160            isTimerPaused = false;
161
162            /** Resume Countdown. */
163            isCountdownPaused = false;
164
165            /** Resume animation. */
166            drawVisual = window.requestAnimationFrame(draw);
167
168            /** Show Speak Now view. */
169            showSpeakNowStep($cForm);
170        }
171    }
172
173
174    /**
175     * Handles the click of the "Send" button from the "Send" view.
176     */
177    function sendButtonClick($cForm) {
178       // console.log('Send button clicked for form:', $cForm);
179
180        fillCurrentURL($cForm);
181        fillUserDetails($cForm);
182
183        // Show animation
184        $cForm.addClass('vmwpmdp-busy');
185
186        // Get additional fields form
187        const $additionalForm = $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-send-box form');
188        
189        let formData;
190
191        if ($additionalForm.length) {
192            // Validate additional fields
193            if (!$additionalForm.get(0).checkValidity()) {
194                $additionalForm.get(0).reportValidity();
195                $cForm.removeClass('vmwpmdp-busy');
196                return;
197            }
198            // Get form data from the form
199            formData = new FormData($additionalForm.get(0));
200        } else {
201            // Create a new FormData object if no form exists
202            formData = new FormData();
203        }
204
205        // Add audio data
206        formData.append('vmwpmdp-wpvoicemessage_gravityforms-audio', audioBlob);
207
208        // Add audio Sample Rate
209        formData.append('vmwpmdp-wpvoicemessage_gravityforms-audio-sample-rate', sampleRate);
210
211        // Add Action to process on WP side
212        formData.append('action', 'wpvoicemessage_gravityforms_send');
213
214        // Add nonce to security
215        formData.append('nonce', vmwpmdpContacterWP.nonce);
216
217        // Add cform id
218        formData.append('cform-id', $cForm.attr('cform-id'));
219
220        // Send data to WP using jQuery's $.ajax
221        $.ajax({
222            url: vmwpmdpContacterWP.ajaxurl,
223            type: 'POST',
224            data: formData,
225            processData: false, // Prevents jQuery from trying to convert the FormData object
226            contentType: false, // Prevents jQuery from setting a default content type
227            success: function(response) {
228                // Hide animation
229                $cForm.removeClass('vmwpmdp-busy');
230                
231                // Check for specific Gravityforms wrapper classes
232                if ($cForm.closest('.gform_body').length || $cForm.hasClass('vmwpmdp-wpvoicemessage_gravityforms-form-box')) {
233
234                    try {
235                        const responseJson = response;
236                        //console.log(response)
237                        if (responseJson.status == 'ok' && responseJson.post_id > 0) {
238                            
239                            const $urlField = $cForm.find('.clr-wpvoicemessage_gravityforms-field').first();
240                            if ($urlField.length) {
241                                const records_ids = record_object.record_id;
242                                records_ids.pop();
243                                records_ids.push(responseJson.post_id);
244                                record_object.type = 'clrwpvoicemessage_gravityforms';
245                                $urlField.val(responseJson.audio_file_path);
246
247                                var record_input_val = $cForm.closest('.ginput_complex').find('.qcld_audio_record_input input').first();
248                                if (record_input_val.length === 0) {
249                                    record_input_val = $cForm.closest('.gfield').find('.qcld_audio_record_input input').first();
250                                }
251                                if (record_input_val.length === 0) {
252                                    record_input_val = $cForm.closest('.gform_body').find('.qcld_audio_record_input input').first();
253                                }
254                                record_input_val.val( responseJson.audio_file_path );
255                            }
256                            showThanksStep($cForm);
257                        } else {
258                            // Show error message
259                            showErrorStep($cForm);
260                        }
261                    } catch (e) {
262                        //console.error('Failed to parse JSON response:', e);
263                        showErrorStep($cForm);
264                    }
265                } else {
266                    showThanksStep($cForm);
267                }
268            },
269            error: function(jqXHR, textStatus, errorThrown) {
270                //console.error('AJAX request failed:', textStatus, errorThrown);
271                $cForm.removeClass('vmwpmdp-busy');
272                showErrorStep($cForm);
273            }
274        });
275    }
276
277
278    // Helper functions (replace with your actual implementation)
279     function startRecordingButtonClick($cForm, e, parent_id) {
280        //console.log('startRecordingButtonClick triggered on', $cForm, 'with event', e, 'and parent ID', parent_id);
281
282        // Using browser API to get microphone access
283        navigator.mediaDevices.getUserMedia({ audio: true })
284            .then(function(stream) {
285                //console.log('getUserMedia() success, stream created, beginning recording');
286
287                // Store the stream globally (or pass it along)
288                gumStream = stream;
289
290                // Use the stream
291                audioContext = new AudioContext();
292                input = audioContext.createMediaStreamSource(stream);
293
294                // Create the Recorder.js object
295                // var rec = new Recorder(input, {
296                //     numChannels: 1
297                // });
298
299                // // Start the recording
300                // rec.record();
301                // console.log('Recording started');
302
303                var numChannels = vmwpmdpContacterWP.convert_recorded_audio == 'mp3' ? 2 : 1;
304
305                rec = new WebAudioRecorder(input, {
306                    workerDir: vmwpmdpContacterWP.QC_VOICEWIDGET_ASSETS_URL, // must end with slash
307                    encoding: vmwpmdpContacterWP.convert_recorded_audio,
308                    numChannels:numChannels, //2 is the default, mp3 encoding supports only 2
309                    onEncoderLoading: function(recorder, encoding) {
310                    // show "loading encoder..." display
311                    // __log("Loading "+encoding+" encoder...");
312                    },
313                    onEncoderLoaded: function(recorder, encoding) {
314                    // hide "loading encoder..." display
315                    //__log(encoding+" encoder loaded");
316                    }
317                });
318
319                rec.onComplete = function(recorder, blob) { 
320                    //__log("Encoding complete");
321                    // createDownloadLink(blob,recorder.encoding);
322                    // createPayer( blob, cForm )
323                    createPayer( blob, $cForm )
324                    // encodingTypeSelect.disabled = false;
325                }
326
327                let maxDurations = parseInt( $cForm.attr( 'max-duration' ) );
328
329                //console.log( e );
330
331                rec.setOptions({
332                    timeLimit: maxDurations,
333                    encodeAfterRecord:encodeAfterRecord,
334                    ogg: {quality: 1},
335                    mp3: {bitRate: parseInt(vmwpmdpContacterWP.voice_bit_rate)}
336                });
337
338                // start the recording process
339                rec.startRecording();
340
341                // --- UI Updates with jQuery ---
342                // Hide the initial start box
343                $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-start-btn-box').hide();
344                // Show the "speak now" box
345                $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-recording-box').show();
346
347                // Start any visual timers or animations
348                startTimer($cForm); 
349                startVisuals($cForm);
350                
351            })
352            .catch(function(err) {
353                //console.error('Recording failed:', err);
354                // --- UI Updates with jQuery on failure ---
355                // Hide the current step
356                $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-start-btn-box').hide();
357                // Show the microphone access error box
358                $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-mic-access-err-box').show();
359            });
360    }
361
362
363    /**
364     * Creates an audio player and visual waveform display for a recorded audio blob.
365     * 
366     * @param {Blob} blob The recorded audio blob.
367     * @param {jQuery} $cForm The jQuery-wrapped form element.
368     */
369    function createPayer(blob, $cForm) {
370        const url = URL.createObjectURL(blob);
371        audioBlob = blob;
372        
373        // Create the audio element with jQuery
374        const $audioEl = $('<audio controls></audio>').attr('src', url);
375        
376        // Add audio element to the player box, first clearing old content
377        const $playerBox = $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-send-box .vmwpmdp-wpvoicemessage_gravityforms-player-box');
378        //$playerBox.empty().append($audioEl);
379        $playerBox.empty();
380
381        // Set Download Link if it exists
382        const $downloadBtn = $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-download-l
382ink-box > a');
383        if ($downloadBtn.length) {
384            $downloadBtn.attr({
385                'href': url,
386                'download': 'record.wav'
387            });
388        }
389        
390        // Create the play button
391        const $playButton = $('<a>')
392            .html('<span class="dashicons dashicons-controls-play"></span>')
393            .addClass('qc_playpause_wavesurfer');
394            
395        const $bodyPlayButton = $cForm.find('.waveform');
396        const containerElement = $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-player-box').get(0); // Native element for Wavesurfer
397
398        // Clear and append elements
399        $bodyPlayButton.empty().append($playButton).append($playerBox);
400        
401        let wavesurfer;
402        if ((typeof wpvoicevar !== "undefined") && (wpvoicevar.wave_style === 'bar-style')) {
403            wavesurfer = WaveSurfer.create({
404                container: containerElement,
405                waveColor: '#38ada9',
406                progressColor: '#079992',
407                barWidth: 2,
408                barHeight: 1,
409                barGap: null,
410                height: 50,
411                responsive: true,
412                normalize: true,
413                scrollParent: true,
414                hideScrollbar: true
415            });
416        } else {
417            wavesurfer = WaveSurfer.create({
418                container: containerElement,
419                waveColor: '#38ada9',
420                progressColor: '#079992',
421                barGap: null,
422                height: 50,
423                responsive: true,
424                normalize: true,
425                scrollParent: true,
426                hideScrollbar: true
427            });
428        }
429        
430        wavesurfer.load(url);
431        
432        // Attach event listener using jQuery's .on()
433        $playButton.on("click", function() {
434            wavesurfer.on('pause', function() {
435                $playButton.html('<span class="dashicons dashicons-controls-play"></span>');
436            });
437            wavesurfer.on('play', function() {
438                $playButton.html('<span class="dashicons dashicons-controls-pause"></span>');
439            });
440            wavesurfer.playPause();
441        });
442    }
443
444
445    // Placeholder functions that you would also need to define
446    // A new global variable to store the timer's current time in seconds
447    let timerSeconds = 0; 
448    // The timer display element
449    let $timerDisplay;
450
451    /**
452     * Starts the recording timer.
453     */
454    function startTimer($cForm) {
455        // Check if the timer is already running
456        if (timerInterval) {
457            //console.log('Timer is already running.');
458            return;
459        }
460
461        // Find the timer display element within the active form
462        $timerDisplay = $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-recording-countdown');
463
464        // Make sure we have a timer display element
465        if (!$timerDisplay.length) {
466            //console.error('Timer display element not found.');
467            return;
468        }
469
470        let maxDuration = parseInt( $cForm.attr( 'max-duration' ) );
471
472        if ( maxDuration === 0 ) { return; } // If maxDuration set to unlimited, countdown is impossible.
473
474        let countdown = maxDuration;
475
476        isTimerPaused = false;
477
478        // Use setInterval to update the timer every second
479        timerInterval = setInterval(() => {
480            if (!isTimerPaused) {
481                timerSeconds++;
482                const minutes = Math.floor(timerSeconds / 60);
483                const seconds = timerSeconds % 60;
484
485                countdown--;
486                /** If timer lower than 0 Stop recording. */
487                if ( maxDuration !== 0 && countdown < 0 ) {
488                    $cForm.find( '.vmwpmdp-speak-now-btns .vmwpmdp-stop-rec-btn' ).click();
489                }
490                
491                // Format the time with leading zeros
492                const formattedMinutes = minutes < 10 ? '0' + minutes : minutes;
493                const formattedSeconds = seconds < 10 ? '0' + seconds : seconds;
494                
495                // Update the display
496                $timerDisplay.text(`${formattedMinutes}:${formattedSeconds}`);
497            }
498        }, 1000);
499    }
500
501    /**
502     * Pauses the timer without resetting.
503     */
504    function pauseTimer() {
505        isTimerPaused = true;
506        //console.log('Timer paused.');
507    }
508
509    /**
510     * Stops and resets the timer.
511     */
512    function stopTimer() {
513        clearInterval(timerInterval);
514        timerInterval = null;
515        timerSeconds = 0;
516        isTimerPaused = false;
517        if ($timerDisplay) {
518            $timerDisplay.text('00:00'); // Reset display
519        }
520        //console.log('Timer stopped and reset.');
521    }
522
523    
524    // Global variable to hold the AnalyserNode and data array
525    let analyser;
526    let dataArray;
527    let canvasContext;
528    let canvas;
529
530    // Remove global canvas and canvasContext variables from the top.
531    // The context will now be accessed from the form's data.
532
533    /**
534     * Starts the visual feedback for the audio recording.
535     */
536    function startVisuals($cForm) {
537        //console.log('Starting audio visualizer.');
538
539        /**
540         * Create Analyser to extract data from audio source.
541         * The AnalyserNode interface represents a node able to provide real-time frequency and time-domain analysis information.
542         **/
543        let analyser = audioContext.createAnalyser();
544
545        /** Connect analyser to audio source. */
546        input.connect( analyser );
547
548        /** Array to receive the data from audio source. */
549        analyser.fftSize = 2048;
550        let bufferLength = analyser.frequencyBinCount;
551        let dataArray = new Uint8Array( bufferLength );
552
553        /** Canvas for animation. */
554        let animation = $cForm.find( '.vmwpmdp-wpvoicemessage_gravityforms-recording-animation canvas' )[0];
555
556        let animationCtx = animation.getContext( "2d" );
557
558        /** Clear the canvas. */
559        animationCtx.clearRect( 0, 0, animation.width, animation.height );
560
561        draw = function() {
562
563            /** Using requestAnimationFrame() to keep looping the drawing function once it has been started. */
564            drawVisual = requestAnimationFrame( draw );
565
566            /** Grab the time domain data and copy it into our array. */
567            analyser.getByteTimeDomainData( dataArray );
568
569            /** Fill the canvas with a solid colour to start. */
570            animationCtx.clearRect( 0, 0, animation.width, animation.height ); // Clear the canvas.
571            animationCtx.fillStyle = 'rgba( 255, 255, 255, 0.01 )'; // Almost transparent
572            animationCtx.fillRect( 0, 0, animation.width, animation.height );
573
574            /** Set a line width and stroke colour for the wave we will draw, then begin drawing a path. */
575            animationCtx.lineWidth = 2;
576
577            let startColor = vmwpmdpContacterWP.accentColor.replace(/[^,]+(?=\))/, '0');
578            let endColor = vmwpmdpContacterWP.accentColor.replace(/[^,]+(?=\))/, '1');
579
580            const gradient = animationCtx.createLinearGradient(0, 0, 384, 0);
581            gradient.addColorStop( 0, startColor );
582            gradient.addColorStop( .25 , endColor );
583            gradient.addColorStop( .75 , endColor );
584            gradient.addColorStop( 1, startColor );
585            animationCtx.strokeStyle = gradient;
586
587            animationCtx.beginPath();
588
589            /**
590             * Determine the width of each segment of the line to be drawn
591             * by dividing the canvas width by the array length (equal to the FrequencyBinCount, as defined earlier on),
592             * then define an x variable to define the position to move to for drawing each segment of the line.
593             **/
594            let sliceWidth = animation.width * 1.0 / bufferLength;
595            let x = 0;
596
597            /**
598             * Run through a loop, defining the position of a small segment of the wave
599             * for each point in the buffer at a certain height based on the data point value form the array,
600             * then moving the line across to the place where the next wave segment should be drawn.
601             **/
602            for ( let i = 0; i < bufferLength; i++ ) {
603
604                let v = dataArray[i] / 128.0;
605                let y = v * animation.height/2;
606
607                if ( i === 0 ) {
608                    animationCtx.moveTo( x, y );
609                } else {
610                    animationCtx.lineTo( x, y );
611                }
612
613                x += sliceWidth;
614            }
615
616            /**
617             * Finish the line in the middle of the right hand side of the canvas,
618             * then draw the stroke we've defined.
619             **/
620            animationCtx.lineTo( animation.width, animation.height/2 );
621            animationCtx.stroke();
622        };
623
624        /** Call the draw() function to start off the whole process. */
625        draw();
626
627        // Call the draw function with the form object
628        //draw($cForm);
629    }
630
631
632    
633
634    /**
635     * Stops the visual feedback loop.
636     */
637    function stopVisuals() {
638        if (drawVisual) {
639            cancelAnimationFrame(drawVisual);
640        }
641        // Clean up analyser and canvas context if necessary
642        if (analyser) {
643          analyser.disconnect();
644        }
645    }
646
647
648
649    /**
650     * Yes button on Reset view.
651     **/
652    function yesResetButtonClick($cForm) {
653
654        /** Stop the recording. */
655        if (rec) {
656            rec.finishRecording();
657        }
658
659        hideAllViews( $cForm );
660
661        /** Stop microphone access. */
662        if (gumStream) {
663            gumStream.getAudioTracks()[0].stop();
664        }
665
666        /** Show Start Recording step. */
667        showStartRecordingStep($cForm);
668    }
669
670    function resetButtonClick($cForm) {
671        // This function is likely triggered from the "Speak Now" view.
672        //console.log('Reset button clicked for form:', $cForm);
673
674        // Stop the recording (using a global variable or object)
675        if (rec) {
676            rec.finishRecording();
677            //console.log('Recording stopped.');
678        }
679        
680        // Reset any UI timers or visuals
681        stopTimer();
682        stopVisuals();
683
684        // Show the initial "Start Recording" step
685        showStartRecordingStep($cForm);
686        
687        // Potentially stop the media stream as well, if that's part of the reset flow
688        if (gumStream) {
689            gumStream.getAudioTracks()[0].stop();
690           // console.log('Microphone access stopped.');
691        }
692
693        // Since this is a "reset", you might also want to clear any recording-related data
694        audioBlob = null;
695        rec = null;
696        
697        // You could also manually reset form fields if needed
698        // $cForm.find('input, textarea, select').val('');
699        // 
700        showSpeakNowStep($cForm)
701    }
702
703
704    /**
705     * Handles the "Stop" button click.
706     */
707    function stopButtonClick($cForm, e, parent_id) {
708       // console.log('stopButtonClick triggered for form:', $cForm);
709
710        // Defensive check: Ensure the recorder object exists before trying to use it.
711        if (!rec) {
712            //console.warn('Recorder object not available. Was recording started correctly?');
713            
714            // Handle the UI gracefully, e.g., show an error message or just hide the controls.
715            // For example, if recording failed, you might show the mic access error box again.
716            $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-speak-now-box').hide();
717            $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-mic-access-err-box').show();
718            
719            // Stop any active streams, just in case
720            stopAllStreams();
721            
722            return;
723        }
724
725        // Stop the recording
726        rec.finishRecording();
727        //console.log('Recording stopped.');
728
729        // Stop all media streams
730        stopAllStreams();
731        
732        // Stop timers and visuals
733        stopTimer();
734        stopVisuals();
735
736        //createPayer(blob, $cForm);
737
738        // --- UI Updates with jQuery ---
739        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-recording-box').hide();
740        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-send-box').show();
741
742    }
743
744
745   /**
746     * Handles the click of the "Reset" button from the "Send" view.
747     */
748    function resetSendButtonClick($cForm) {
749        //console.log('Reset button clicked from the send view for form:', $cForm);
750
751        // 1. Clear any stored recording data
752        audioBlob = null;
753        rec = null;
754        
755        // 2. Stop any media stream that might still be active (unlikely, but safe)
756        if (gumStream) {
757            gumStream.getAudioTracks().stop();
758           // console.log('Microphone access stopped.');
759        }
760        
761        // 3. Reset any UI timers or visuals (if they were still running)
762        stopTimer();
763        stopVisuals();
764
765        // 4. Remove the dynamically created audio player
766        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-send-box .vmwpmdp-wpvoicemessage_gravityforms-player-box').empty();
767        
768        // 5. Reset the form's input fields to their default state
769        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-send-box .clr-wpvoicemessage_gravityforms-field').val('');
770
771        // 6. Show the initial "Start Recording" step
772        showStartRecordingStep($cForm);
773
774        //console.log('Form fully reset. Ready for a new recording.');
775    }
776
777    /**
778     * Resets and shows the initial "Start Recording" step for a specific form.
779     */
780    function showStartRecordingStep($cForm) {
781        //console.log('Showing the start recording step for form:', $cForm);
782
783        // Hide all other views within the form
784        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-recording-box').hide();
785        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-send-box').hide();
786        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-thanks-box').hide();
787        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-error-box').hide();
788
789        // Show the "Start Recording" view
790        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-start-btn-box').show();
791
792        // Optional: Hide any busy or error animations
793        $cForm.removeClass('vmwpmdp-busy');
794
795        // Optional: Clear any previous data or timers for a clean slate
796        stopTimer();
797        stopVisuals();
798        audioBlob = null;
799        rec = null;
800    }
801
802    /**
803     * Stops all tracks on the active media stream.
804     */
805    function stopAllStreams() {
806        // Check if the stream object exists
807        if (gumStream) {
808            // Get all tracks from the stream
809            const tracks = gumStream.getTracks();
810            
811            // Iterate over all tracks and stop each one individually
812            tracks.forEach(track => {
813                track.stop();
814                //console.log('Stopped media track:', track.kind);
815            });
816            
817            // Optional: Disconnect the input from the audio context
818            if (input) {
819                input.disconnect();
820            }
821
822            // Reset the global stream variable
823            gumStream = null;
824            //console.log('All media streams stopped and resources released.');
825        } else {
826            //console.log('No active media streams to stop.');
827        }
828    }
829    /**
830     * Shows the "Send" step and hides all other views for a specific form.
831     */
832    function showSendStep($cForm) {
833        //console.log('Showing the send step for form:', $cForm);
834
835        // Hide all other views within the form
836        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-start-btn-box').hide();
837        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-recording-box').hide();
838        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-thanks-box').hide();
839        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-error-box').hide();
840
841        // Show the "Send" view
842        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-send-box').show();
843
844
845        // Optional: Hide any busy or error animations
846        $cForm.removeClass('vmwpmdp-busy');
847    }
848
849
850    const LOG_LEVEL = 'debug'; // Change this to 'info', 'warn', etc.
851    const LOG_LEVELS = {
852        'debug': 0,
853        'info': 1,
854        'warn': 2,
855        'error': 3
856    };
857
858    function log(level, message) {
859        if (LOG_LEVELS[level] >= LOG_LEVELS[LOG_LEVEL]) {
860            const timestamp = new Date().toISOString();
861            console.log(`[${timestamp}] [${level.toUpperCase()}]`, message);
862        }
863    }
864
865    /**
866     * Shows the "Speak Now" step and hides other views for a specific form.
867     */
868    function showSpeakNowStep($cForm) {
869        //console.log('Showing the "Speak Now" step for form:', $cForm);
870
871        // Hide other views
872        hideAllViews( $cForm );
873
874        // Show the "Speak Now" view
875        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-recording-box').show();
876
877        // Optional: Hide any busy or error animations
878        $cForm.removeClass('vmwpmdp-busy');
879    }
880    /**
881     * Fills a form input with the current page's URL.
882     */
883    function fillCurrentURL($cForm) {
884        // 1. Get the current page's full URL
885        const currentUrl = window.location.href;
886        //console.log('Current URL is:', currentUrl);
887        
888        // 2. Find the input field within the specific form and set its value
889        // Assuming you have a hidden input field for this purpose, like:
890        // <input type="hidden" name="page_url" class="vmwpmdp-page-url">
891        const $urlInput = $cForm.find('#vmwpmdpCurrentURL');
892        
893        if ($urlInput.length) {
894            $urlInput.val(currentUrl);
895            //console.log('Form field for URL populated.');
896        } else {
897           // console.warn('URL input field not found. Skipping population.');
898        }
899    }
900
901    /**
902     * Populates form fields with user details retrieved from the server.
903     */
904    function fillUserDetails($cForm) {
905        //console.log('Attempting to fill user details for form:', $cForm);
906
907        // Assume an endpoint exists to fetch user data. In WordPress,
908        // this would be a custom REST API endpoint or an AJAX action.
909        //const userApiEndpoint = 'your-wordpress-api-endpoint/get-user-data'; 
910
911        let vmwpmdpUserDetails = $cForm.find( '#vmwpmdpUserDetails' )[0];
912        if ( 'undefined' === typeof vmwpmdpUserDetails ) { return; }
913
914        let login = $cForm.find( '.vmwpmdp-wpvoicemessage_gravityforms-additional-fields' )[0].attr( 'user-login' );
915        let ip = $cForm.find( '.vmwpmdp-wpvoicemessage_gravityforms-additional-fields' )[0].attr( 'user-ip' );
916
917        if ( ! login ) {
918            login = 'Guest';
919        }
920
921        vmwpmdpUserDetails.val( login + ' (' + ip + ')' );
922
923    }
924
925    /**
926     * Handles the "Back" button click, returning to the "Speak Now" step.
927     */
928    function backButtonClick($cForm) {
929        //console.log('"Back" button clicked for form:', $cForm);
930
931        // Show the "Speak Now" step
932        //showSpeakNowStep($cForm);
933
934        hideAllViews( $cForm ); // Hide all steps.
935
936        // Since the recording was paused (not stopped) before showing the reset view,
937        // we need to resume it here.
938        if (rec) {
939            rec.finishRecording();
940            //console.log('Recording resumed.');
941        }
942
943        // Resume timer and visuals
944        isTimerPaused = false;
945        isCountdownPaused = false;
946        drawVisual = window.requestAnimationFrame(draw);
947
948        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-send-box').show();
949
950        // Optional: Hide any busy or error animations
951        $cForm.removeClass('vmwpmdp-busy');
952    }
953
954
955    /**
956     * Shows the "Thanks" message and hides all other views for a specific form.
957     */
958    function showThanksStep($cForm) {
959        //console.log('Showing the thanks step for form:', $cForm);
960
961        // Hide all other views within the form
962        hideAllViews( $cForm ); // Hide all steps.
963
964        // Show the "Thanks" view
965        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-thanks-box').show();
966        
967
968        // Optional: Hide any busy or error animations
969        $cForm.removeClass('vmwpmdp-busy');
970    }
971
972    function showErrorStep( $cForm ) {
973        hideAllViews( $cForm ); // Hide all steps.
974        $cForm.addClass('vmwpmdp-step-error');
975
976        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-error-box').show();
977        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-reset-box').show();
978    }
979
980    /**
981     * Converted hideAllViews function.
982     * This assumes your views have a common class, e.g., '.vmwpmdp-step'.
983     */
984    function hideAllViews($cForm) {
985        // Assuming all steps have a common class like '.vmwpmdp-step'
986
987        // Hide all other views within the form
988        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-start-btn-box').hide();
989        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-allow-access-box').hide();
990        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-recording-box').hide();
991        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-reset-box').hide();
992        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-thanks-box').hide();
993        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-send-box').hide();
994
995        // Show the "Send" view
996        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-error-box').hide();
997        $cForm.find('.vmwpmdp-wpvoicemessage_gravityforms-reset-box').hide();
998
999    }
1000
1001
1002
1003});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.