Proyectos de Subversion Moodle

Rev

Rev 1 | Mostrar el archivo completo | | | Autoría | Ultima modificación | Ver Log |

Rev 1 Rev 1441
Línea 23... Línea 23...
23
 */
23
 */
Línea 24... Línea 24...
24
 
24
 
25
import {getString, getStrings} from 'core/str';
25
import {getString, getStrings} from 'core/str';
26
import {component} from './common';
26
import {component} from './common';
27
import Pending from 'core/pending';
27
import Pending from 'core/pending';
28
import {getData} from './options';
28
import {getData, isPausingAllowed} from './options';
29
import uploadFile from 'editor_tiny/uploader';
29
import uploadFile from 'editor_tiny/uploader';
30
import {add as addToast} from 'core/toast';
30
import {add as addToast} from 'core/toast';
31
import * as ModalEvents from 'core/modal_events';
31
import * as ModalEvents from 'core/modal_events';
32
import * as Templates from 'core/templates';
32
import * as Templates from 'core/templates';
Línea 38... Línea 38...
38
 * The RecordRTC base class for audio, video, and any other future types
38
 * The RecordRTC base class for audio, video, and any other future types
39
 */
39
 */
40
export default class {
40
export default class {
Línea 41... Línea 41...
41
 
41
 
-
 
42
    stopRequested = false;
-
 
43
    buttonTimer = null;
-
 
44
    pauseTime = null;
Línea 42... Línea 45...
42
    stopRequested = false;
45
    startTime = null;
43
 
46
 
44
    /**
47
    /**
45
     * Constructor for the RecordRTC class
48
     * Constructor for the RecordRTC class
Línea 58... Línea 61...
58
        this.config = getData(editor).params;
61
        this.config = getData(editor).params;
59
        this.modal = modal;
62
        this.modal = modal;
60
        this.modalRoot = modal.getRoot()[0];
63
        this.modalRoot = modal.getRoot()[0];
61
        this.startStopButton = this.modalRoot.querySelector('button[data-action="startstop"]');
64
        this.startStopButton = this.modalRoot.querySelector('button[data-action="startstop"]');
62
        this.uploadButton = this.modalRoot.querySelector('button[data-action="upload"]');
65
        this.uploadButton = this.modalRoot.querySelector('button[data-action="upload"]');
-
 
66
        this.pauseResumeButton = this.modalRoot.querySelector('button[data-action="pauseresume"]');
Línea 63... Línea 67...
63
 
67
 
64
        // Disable the record button untilt he stream is acquired.
68
        // Disable the record button untilt he stream is acquired.
Línea 65... Línea 69...
65
        this.setRecordButtonState(false);
69
        this.setRecordButtonState(false);
Línea 231... Línea 235...
231
            'confirm_yes',
235
            'confirm_yes',
232
            'recordinguploaded',
236
            'recordinguploaded',
233
            'maxfilesizehit',
237
            'maxfilesizehit',
234
            'maxfilesizehit_title',
238
            'maxfilesizehit_title',
235
            'uploadfailed',
239
            'uploadfailed',
-
 
240
            'pause',
-
 
241
            'resume',
236
        ]);
242
        ]);
Línea 237... Línea 243...
237
 
243
 
238
        prefetchTemplates([
244
        prefetchTemplates([
239
            this.getEmbedTemplateName(),
245
            this.getEmbedTemplateName(),
Línea 315... Línea 321...
315
        const container = this.getButtonContainer('start-stop');
321
        const container = this.getButtonContainer('start-stop');
316
        container.classList.toggle('hide', !visible);
322
        container.classList.toggle('hide', !visible);
317
    }
323
    }
Línea 318... Línea 324...
318
 
324
 
-
 
325
    /**
-
 
326
     * Configure button visibility for the pause button.
-
 
327
     *
-
 
328
     * @param {boolean} visible Set the visibility of the button.
-
 
329
     */
-
 
330
    setPauseButtonVisibility(visible) {
-
 
331
        if (this.pauseResumeButton) {
-
 
332
            this.pauseResumeButton.classList.toggle('hidden', !visible);
-
 
333
        }
-
 
334
    }
-
 
335
 
319
    /**
336
    /**
320
     * Enable the upload button.
337
     * Enable the upload button.
321
     *
338
     *
322
     * @param {boolean|null} enabled Set the button state
339
     * @param {boolean|null} enabled Set the button state
323
     */
340
     */
Línea 332... Línea 349...
332
     */
349
     */
333
    setUploadButtonVisibility(visible) {
350
    setUploadButtonVisibility(visible) {
334
        const container = this.getButtonContainer('upload');
351
        const container = this.getButtonContainer('upload');
335
        container.classList.toggle('hide', !visible);
352
        container.classList.toggle('hide', !visible);
336
    }
353
    }
-
 
354
 
-
 
355
    /**
-
 
356
     * Sets the state of the audio player, including visibility, muting, and controls.
-
 
357
     *
-
 
358
     * @param {boolean} state A boolean indicating the audio player state.
-
 
359
     */
-
 
360
    setPlayerState(state) {
-
 
361
        // Mute or unmute the audio player and show or hide controls.
-
 
362
        this.player.muted = !state;
-
 
363
        this.player.controls = state;
-
 
364
        // Toggle the 'hide' class on the player button container based on state.
-
 
365
        this.getButtonContainer('player')?.classList.toggle('hide', !state);
-
 
366
    }
-
 
367
 
337
    /**
368
    /**
338
     * Handle failure to capture the User Media.
369
     * Handle failure to capture the User Media.
339
     *
370
     *
340
     * @param {Error} error
371
     * @param {Error} error
341
     */
372
     */
Línea 365... Línea 396...
365
        this.modal.getRoot().on(ModalEvents.outsideClick, this.outsideClickHandler.bind(this));
396
        this.modal.getRoot().on(ModalEvents.outsideClick, this.outsideClickHandler.bind(this));
366
        this.modal.getRoot().on(ModalEvents.hidden, () => {
397
        this.modal.getRoot().on(ModalEvents.hidden, () => {
367
            this.cleanupStream();
398
            this.cleanupStream();
368
            this.requestRecordingStop();
399
            this.requestRecordingStop();
369
        });
400
        });
-
 
401
        this.player.addEventListener('error', this.handlePlayerError.bind(this));
-
 
402
        this.player.addEventListener('loadedmetadata', this.handlePlayerLoadedMetadata.bind(this));
-
 
403
    }
-
 
404
 
-
 
405
    /**
-
 
406
     * Handle the player `error` event.
-
 
407
     *
-
 
408
     * This event is called when the player throws an error.
-
 
409
     */
-
 
410
    handlePlayerError() {
-
 
411
        const error = this.player.error;
-
 
412
        if (error) {
-
 
413
            const message = `An error occurred: ${error.message || 'Unknown error'}. Please try again.`;
-
 
414
            addToast(message, {type: error});
-
 
415
            // Disable the upload button.
-
 
416
            this.setUploadButtonState(false);
-
 
417
        }
-
 
418
    }
-
 
419
 
-
 
420
    /**
-
 
421
     * Handles the event when the player's metadata has been loaded.
-
 
422
     */
-
 
423
    handlePlayerLoadedMetadata() {
-
 
424
        if (isFinite(this.player.duration)) {
-
 
425
            // Note: In Chrome, you need to seek to activate the error listener
-
 
426
            // if an issue arises after inserting the recorded audio into the player source.
-
 
427
            this.player.currentTime = 0.1;
-
 
428
        }
370
    }
429
    }
Línea 371... Línea 430...
371
 
430
 
372
    /**
431
    /**
373
     * Prevent the Modal from closing when recording is on process.
432
     * Prevent the Modal from closing when recording is on process.
374
     *
433
     *
375
     * @param {MouseEvent} event The click event
434
     * @param {MouseEvent} event The click event
376
     */
435
     */
377
    async outsideClickHandler(event) {
436
    async outsideClickHandler(event) {
378
        if (this.isRecording()) {
437
        if (this.isRecording() || this.isPaused()) {
379
            // The user is recording.
438
            // The user is recording.
380
            // Do not distract with a confirmation, just prevent closing.
439
            // Do not distract with a confirmation, just prevent closing.
381
            event.preventDefault();
440
            event.preventDefault();
382
        } else if (this.hasData()) {
441
        } else if (this.hasData()) {
Línea 412... Línea 471...
412
            }
471
            }
Línea 413... Línea 472...
413
 
472
 
414
            if (action === 'upload') {
473
            if (action === 'upload') {
415
                this.uploadRecording();
474
                this.uploadRecording();
-
 
475
            }
-
 
476
 
-
 
477
            if (action === 'pauseresume') {
-
 
478
                this.handleRecordingPauseResumeRequested();
416
            }
479
            }
417
        }
480
        }
Línea 418... Línea 481...
418
    }
481
    }
419
 
482
 
420
    /**
483
    /**
421
     * Handle the click event for the recording start/stop button.
484
     * Handle the click event for the recording start/stop button.
422
     */
485
     */
423
    handleRecordingStartStopRequested() {
486
    handleRecordingStartStopRequested() {
424
        if (this.mediaRecorder?.state === 'recording') {
487
        if (this.isRecording() || this.isPaused()) {
425
            this.requestRecordingStop();
488
            this.requestRecordingStop();
426
        } else {
489
        } else {
427
            this.startRecording();
490
            this.startRecording();
Línea 428... Línea 491...
428
        }
491
        }
-
 
492
    }
-
 
493
 
-
 
494
    /**
-
 
495
     * Handle the click event for the recording pause/resume button.
-
 
496
     */
-
 
497
    handleRecordingPauseResumeRequested() {
-
 
498
        if (this.isRecording()) {
-
 
499
            // Pause recording.
-
 
500
            this.mediaRecorder.pause();
-
 
501
        } else if (this.isPaused()) {
-
 
502
            // Resume recording.
-
 
503
            this.mediaRecorder.resume();
-
 
504
        }
429
    }
505
    }
430
 
506
 
431
    /**
507
    /**
432
     * Handle the media stream after it has finished.
508
     * Handle the media stream after it has finished.
433
     */
509
     */
Línea 440... Línea 516...
440
        this.player.src = URL.createObjectURL(this.blob);
516
        this.player.src = URL.createObjectURL(this.blob);
Línea 441... Línea 517...
441
 
517
 
442
        // Change the label to "Record again".
518
        // Change the label to "Record again".
Línea 443... Línea -...
443
        this.setRecordButtonTextFromString('recordagain');
-
 
444
 
-
 
445
        // Show audio player with controls enabled, and unmute.
-
 
446
        this.player.muted = false;
-
 
447
        this.player.controls = true;
-
 
448
        this.getButtonContainer('player')?.classList.toggle('hide', false);
519
        this.setRecordButtonTextFromString('recordagain');
449
 
520
 
-
 
521
        // Show upload button.
450
        // Show upload button.
522
        this.setUploadButtonVisibility(true);
-
 
523
        this.setPlayerState(true);
-
 
524
        this.setUploadButtonState(true);
-
 
525
 
-
 
526
        // Hide the pause button.
-
 
527
        this.setPauseButtonVisibility(false);
-
 
528
        if (this.mediaRecorder.state === 'inactive') {
451
        this.setUploadButtonVisibility(true);
529
            this.setPauseButtonTextFromString('pause');
Línea 452... Línea 530...
452
        this.setUploadButtonState(true);
530
        }
453
    }
531
    }
454
 
532
 
Línea 510... Línea 588...
510
    }
588
    }
Línea 511... Línea 589...
511
 
589
 
512
    static async display(editor) {
590
    static async display(editor) {
513
        const ModalClass = this.getModalClass();
591
        const ModalClass = this.getModalClass();
514
        const modal = await ModalClass.create({
592
        const modal = await ModalClass.create({
-
 
593
            templateContext: {
-
 
594
                isallowedpausing: isPausingAllowed(editor),
515
            templateContext: {},
595
            },
516
            large: true,
596
            large: true,
517
            removeOnClose: true,
597
            removeOnClose: true,
Línea 518... Línea 598...
518
        });
598
        });
Línea 576... Línea 656...
576
     * Update the content of the stop recording button timer.
656
     * Update the content of the stop recording button timer.
577
     */
657
     */
578
    async setStopRecordingButton() {
658
    async setStopRecordingButton() {
579
        const {html, js} = await Templates.renderForPromise('tiny_recordrtc/timeremaining', this.getTimeRemaining());
659
        const {html, js} = await Templates.renderForPromise('tiny_recordrtc/timeremaining', this.getTimeRemaining());
580
        Templates.replaceNodeContents(this.startStopButton, html, js);
660
        Templates.replaceNodeContents(this.startStopButton, html, js);
581
        this.buttonTimer = setInterval(this.updateRecordButtonTime.bind(this), 500);
661
        this.startButtonTimer();
582
    }
662
    }
Línea 583... Línea 663...
583
 
663
 
584
    /**
664
    /**
585
     * Update the time on the stop recording button.
665
     * Update the time on the stop recording button.
Línea 602... Línea 682...
602
    async setRecordButtonTextFromString(string) {
682
    async setRecordButtonTextFromString(string) {
603
        this.startStopButton.textContent = await getString(string, component);
683
        this.startStopButton.textContent = await getString(string, component);
604
    }
684
    }
Línea 605... Línea 685...
605
 
685
 
-
 
686
    /**
-
 
687
     * Set the text of the pause button using a language string.
-
 
688
     *
-
 
689
     * @param {string} string The string identifier
-
 
690
     */
-
 
691
    async setPauseButtonTextFromString(string) {
-
 
692
        if (this.pauseResumeButton) {
-
 
693
            this.pauseResumeButton.textContent = await getString(string, component);
-
 
694
        }
-
 
695
    }
-
 
696
 
606
    /**
697
    /**
607
     * Set the upload button text progress.
698
     * Set the upload button text progress.
608
     *
699
     *
609
     * @param {number} progress The progress
700
     * @param {number} progress The progress
610
     */
701
     */
Línea 624... Línea 715...
624
    clearButtonTimer() {
715
    clearButtonTimer() {
625
        if (this.buttonTimer) {
716
        if (this.buttonTimer) {
626
            clearInterval(this.buttonTimer);
717
            clearInterval(this.buttonTimer);
627
        }
718
        }
628
        this.buttonTimer = null;
719
        this.buttonTimer = null;
-
 
720
        this.pauseTime = null;
-
 
721
        this.startTime = null;
-
 
722
    }
-
 
723
 
-
 
724
    /**
-
 
725
     * Pause the timer for the stop recording button.
-
 
726
     */
-
 
727
    pauseButtonTimer() {
-
 
728
        // Stop the countdown timer.
-
 
729
        this.pauseTime = new Date().getTime(); // Store pause time.
-
 
730
        if (this.buttonTimer) {
-
 
731
            clearInterval(this.buttonTimer);
-
 
732
        }
-
 
733
    }
-
 
734
 
-
 
735
    /**
-
 
736
     * Start the timer for the start recording button.
-
 
737
     * If the recording was paused, the timer will resume from the pause time.
-
 
738
     */
-
 
739
    startButtonTimer() {
-
 
740
        if (this.pauseTime !== null) {
-
 
741
            // Resume from pause.
-
 
742
            const pauseDuration = new Date().getTime() - this.pauseTime;
-
 
743
            // Adjust start time by pause duration.
-
 
744
            this.startTime += pauseDuration;
-
 
745
            this.pauseTime = null;
-
 
746
        }
-
 
747
        this.buttonTimer = setInterval(this.updateRecordButtonTime.bind(this), 500);
629
    }
748
    }
Línea 630... Línea 749...
630
 
749
 
631
    /**
750
    /**
632
     * Get the time remaining for the recording.
751
     * Get the time remaining for the recording.
633
     *
752
     *
634
     * @returns {Object} The minutes and seconds remaining.
753
     * @returns {Object} The minutes and seconds remaining.
635
     */
754
     */
636
    getTimeRemaining() {
755
    getTimeRemaining() {
637
        // All times are in milliseconds
756
        // All times are in milliseconds.
-
 
757
        let now = new Date().getTime();
-
 
758
        if (this.pauseTime !== null) {
-
 
759
            // If paused, use pauseTime instead of current time.
-
 
760
            now = this.pauseTime;
638
        const now = new Date().getTime();
761
        }
Línea 639... Línea 762...
639
        const remaining = Math.floor(this.getTimeLimit() - ((now - this.startTime) / 1000));
762
        const remaining = Math.floor(this.getTimeLimit() - ((now - this.startTime) / 1000));
640
 
763
 
641
        const formatter = new Intl.NumberFormat(navigator.language, {minimumIntegerDigits: 2});
764
        const formatter = new Intl.NumberFormat(navigator.language, {minimumIntegerDigits: 2});
Línea 664... Línea 787...
664
     * mediaRecorder's stopped event handler which is processed after it has stopped.
787
     * mediaRecorder's stopped event handler which is processed after it has stopped.
665
     */
788
     */
666
    requestRecordingStop() {
789
    requestRecordingStop() {
667
        if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
790
        if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
668
            this.stopRequested = true;
791
            this.stopRequested = true;
-
 
792
            if (this.isPaused()) {
-
 
793
                this.stopRecorder();
-
 
794
            }
669
        } else {
795
        } else {
670
            // There is no recording to stop, but the stream must still be cleaned up.
796
            // There is no recording to stop, but the stream must still be cleaned up.
671
            this.cleanupStream();
797
            this.cleanupStream();
672
        }
798
        }
673
    }
799
    }
Línea 674... Línea 800...
674
 
800
 
-
 
801
    stopRecorder() {
-
 
802
        if (this.isPaused()) {
-
 
803
            this.pauseTime = null;
675
    stopRecorder() {
804
        }
Línea 676... Línea 805...
676
        this.mediaRecorder.stop();
805
        this.mediaRecorder.stop();
677
 
806
 
678
        // Unmute the player so that the audio is heard during playback.
807
        // Unmute the player so that the audio is heard during playback.
Línea 708... Línea 837...
708
     *
837
     *
709
     * This event is called when the recording starts.
838
     * This event is called when the recording starts.
710
     */
839
     */
711
    handleStarted() {
840
    handleStarted() {
712
        this.startTime = new Date().getTime();
841
        this.startTime = new Date().getTime();
-
 
842
        if (isPausingAllowed(this.editor) && !this.isPaused()) {
-
 
843
            this.setPauseButtonVisibility(true);
-
 
844
        }
713
        this.setStopRecordingButton();
845
        this.setStopRecordingButton();
714
    }
846
    }
Línea 715... Línea 847...
715
 
847
 
-
 
848
    /**
-
 
849
     * Handle the mediaRecorder `pause` event.
-
 
850
     *
-
 
851
     * This event is called when the recording pauses.
-
 
852
     */
-
 
853
    handlePaused() {
-
 
854
        this.pauseButtonTimer();
-
 
855
        this.setPauseButtonTextFromString('resume');
-
 
856
    }
-
 
857
 
-
 
858
    /**
-
 
859
     * Handle the mediaRecorder `resume` event.
-
 
860
     *
-
 
861
     * This event is called when the recording resumes.
-
 
862
     */
-
 
863
    handleResume() {
-
 
864
        this.startButtonTimer();
-
 
865
        this.setPauseButtonTextFromString('pause');
-
 
866
    }
-
 
867
 
716
    /**
868
    /**
717
     * Handle the mediaRecorder `dataavailable` event.
869
     * Handle the mediaRecorder `dataavailable` event.
718
     *
870
     *
719
     * @param {Event} event
871
     * @param {Event} event
720
     */
872
     */
721
    handleDataAvailable(event) {
873
    handleDataAvailable(event) {
722
        if (this.isRecording()) {
874
        if (this.isRecording() || this.isPaused()) {
723
            const newSize = this.data.blobSize + event.data.size;
875
            const newSize = this.data.blobSize + event.data.size;
724
            // Recording stops when either the maximum upload size is reached, or the time limit expires.
876
            // Recording stops when either the maximum upload size is reached, or the time limit expires.
725
            // The time limit is checked in the `updateButtonTime` function.
877
            // The time limit is checked in the `updateButtonTime` function.
726
            if (newSize >= this.getMaxUploadSize()) {
878
            if (newSize >= this.getMaxUploadSize()) {
Línea 755... Línea 907...
755
    isRecording() {
907
    isRecording() {
756
        return this.mediaRecorder?.state === 'recording';
908
        return this.mediaRecorder?.state === 'recording';
757
    }
909
    }
Línea 758... Línea 910...
758
 
910
 
-
 
911
    /**
-
 
912
     * Check whether the recording is paused.
-
 
913
     *
-
 
914
     * @returns {boolean}
-
 
915
     */
-
 
916
    isPaused() {
-
 
917
        return this.mediaRecorder?.state === 'paused';
-
 
918
    }
-
 
919
 
759
    /**
920
    /**
760
     * Whether any data has been recorded.
921
     * Whether any data has been recorded.
761
     *
922
     *
762
     * @returns {boolean}
923
     * @returns {boolean}
763
     */
924
     */
Línea 769... Línea 930...
769
     * Start the recording
930
     * Start the recording
770
     */
931
     */
771
    async startRecording() {
932
    async startRecording() {
772
        if (this.mediaRecorder) {
933
        if (this.mediaRecorder) {
773
            // Stop the existing recorder if it exists.
934
            // Stop the existing recorder if it exists.
774
            if (this.isRecording()) {
935
            if (this.isRecording() || this.isPaused()) {
775
                this.mediaRecorder.stop();
936
                this.mediaRecorder.stop();
776
            }
937
            }
Línea 777... Línea 938...
777
 
938
 
778
            if (this.hasData()) {
939
            if (this.hasData()) {
779
                const resetRecording = await this.recordAgainConfirmation();
940
                const resetRecording = await this.recordAgainConfirmation();
780
                if (!resetRecording) {
941
                if (!resetRecording) {
781
                    // User cancelled at the confirmation to reset the data, so exit early.
942
                    // User cancelled at the confirmation to reset the data, so exit early.
782
                    return;
943
                    return;
783
                }
944
                }
-
 
945
                this.setUploadButtonVisibility(false);
-
 
946
                this.setPlayerState(false);
-
 
947
                if (!this.stream.active) {
-
 
948
                    await this.captureUserMedia();
784
                this.setUploadButtonVisibility(false);
949
                }
Línea 785... Línea 950...
785
            }
950
            }
786
 
951
 
Línea 791... Línea 956...
791
        this.mediaRecorder = new MediaRecorder(this.stream, this.getParsedRecordingOptions());
956
        this.mediaRecorder = new MediaRecorder(this.stream, this.getParsedRecordingOptions());
Línea 792... Línea 957...
792
 
957
 
793
        this.mediaRecorder.addEventListener('dataavailable', this.handleDataAvailable.bind(this));
958
        this.mediaRecorder.addEventListener('dataavailable', this.handleDataAvailable.bind(this));
794
        this.mediaRecorder.addEventListener('stop', this.handleStopped.bind(this));
959
        this.mediaRecorder.addEventListener('stop', this.handleStopped.bind(this));
-
 
960
        this.mediaRecorder.addEventListener('start', this.handleStarted.bind(this));
-
 
961
        this.mediaRecorder.addEventListener('pause', this.handlePaused.bind(this));
Línea 795... Línea 962...
795
        this.mediaRecorder.addEventListener('start', this.handleStarted.bind(this));
962
        this.mediaRecorder.addEventListener('resume', this.handleResume.bind(this));
796
 
963
 
797
        this.data = {
964
        this.data = {
798
            chunks: [],
965
            chunks: [],