Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
// This file is part of Moodle - http://moodle.org/
2
//
3
// Moodle is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// Moodle is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
 
16
/**
17
 * Tiny Record RTC - Screen recorder configuration.
18
 *
19
 * @module      tiny_recordrtc/screen_recorder
20
 * @copyright   2024 The Open University
21
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 */
23
 
24
import BaseClass from './base_recorder';
25
import Modal from 'tiny_recordrtc/modal';
26
import {component} from 'tiny_recordrtc/common';
27
import {getString} from 'core/str';
28
 
29
export default class Screen extends BaseClass {
30
    configurePlayer() {
31
        return this.modalRoot.querySelector('video');
32
    }
33
 
34
    getSupportedTypes() {
35
        return [
36
            // Support webm as a preference.
37
            // This container supports both vp9, and vp8.
38
            // It does not support AVC1/h264 at all.
39
            // It is supported by Chromium, and Firefox browsers, but not Safari.
40
            'video/webm;codecs=vp9,opus',
41
            'video/webm;codecs=vp8,opus',
42
 
43
            // Fall back to mp4 if webm is not available.
44
            // The mp4 container supports v9, and h264 but neither of these are supported for recording on other
45
            // browsers.
46
            // In addition to this, we can record in v9, but VideoJS does not support a mp4 container with v9 codec
47
            // for playback. We leave it as a final option as a just-in-case.
48
            'video/mp4;codecs=h264,opus',
49
            'video/mp4;codecs=h264,wav',
50
            'video/mp4;codecs=v9,opus',
51
        ];
52
 
53
    }
54
 
55
    getRecordingOptions() {
56
        return {
57
            videoBitsPerSecond: parseInt(this.config.screenbitrate),
58
            videoWidth: parseInt(this.config.videoscreenwidth),
59
            videoHeight: parseInt(this.config.videoscreenheight),
60
        };
61
    }
62
 
63
    getMediaConstraints() {
64
        return {
65
            audio: true,
66
            systemAudio: 'exclude',
67
            video: {
68
                displaySurface: 'monitor',
69
                frameRate: {ideal: 24},
70
                width: {
71
                    max: parseInt(this.config.videoscreenwidth),
72
                },
73
                height: {
74
                    max: parseInt(this.config.videoscreenheight),
75
                },
76
            },
77
        };
78
    }
79
 
80
    playOnCapture() {
81
        // Play the recording back on capture.
82
        return true;
83
    }
84
 
85
    getRecordingType() {
86
        return 'screen';
87
    }
88
 
89
    getTimeLimit() {
90
        return this.config.screentimelimit;
91
    }
92
 
93
    getEmbedTemplateName() {
94
        return 'tiny_recordrtc/embed_screen';
95
    }
96
 
97
    getFileName(prefix) {
98
        return `${prefix}-video.${this.getFileExtension()}`;
99
    }
100
 
101
    getFileExtension() {
102
        if (window.MediaRecorder.isTypeSupported('audio/webm')) {
103
            return 'webm';
104
        } else if (window.MediaRecorder.isTypeSupported('audio/mp4')) {
105
            return 'mp4';
106
        }
107
 
108
        window.console.warn(`Unknown file type for MediaRecorder API`);
109
        return '';
110
    }
111
 
112
    async captureUserMedia() {
113
        // Screen recording requires both audio and the screen, and we need to get them both together.
114
        const audioPromise = navigator.mediaDevices.getUserMedia({audio: true});
115
        const screenPromise = navigator.mediaDevices.getDisplayMedia(this.getMediaConstraints());
116
        // If the audioPromise is "rejected" (indicating that the user does not want to share their voice),
117
        // we will proceed to record their screen without audio.
118
        // Therefore, we will use Promise.allSettled instead of Promise.all.
119
        await Promise.allSettled([audioPromise, screenPromise]).then(this.combineAudioAndScreenRecording.bind(this));
120
    }
121
 
122
    /**
123
     * For starting screen recording, once we have both audio and video, combine them.
124
     *
125
     * @param {Object[]} results from the above Promise.allSettled call.
126
     */
127
    combineAudioAndScreenRecording(results) {
128
        const [audioData, screenData] = results;
129
        if (screenData.status !== 'fulfilled') {
130
            // If the user does not grant screen permission show warning popup.
131
            this.handleCaptureFailure(screenData.reason);
132
            return;
133
        }
134
 
135
        const screenStream = screenData.value;
136
        // Prepare to handle if the user clicks the browser's "Stop Sharing Screen" button.
137
        screenStream.getVideoTracks()[0].addEventListener('ended', this.handleStopScreenSharing.bind(this));
138
 
139
        // Handle microphone.
140
        if (audioData.status !== 'fulfilled') {
141
            // We could not get audio. In this case, we just continue without audio.
142
            this.handleCaptureSuccess(screenStream);
143
            return;
144
        }
145
        const audioStream = audioData.value;
146
        // Merge the video track from the media stream with the audio track from the microphone stream
147
        // and stop any unnecessary tracks to ensure that the recorded video has microphone sound.
148
        const composedStream = new MediaStream();
149
        screenStream.getTracks().forEach(function(track) {
150
            if (track.kind === 'video') {
151
                composedStream.addTrack(track);
152
            } else {
153
                track.stop();
154
            }
155
        });
156
        audioStream.getAudioTracks().forEach(function(micTrack) {
157
            composedStream.addTrack(micTrack);
158
        });
159
 
160
        this.handleCaptureSuccess(composedStream);
161
    }
162
 
163
    /**
164
     * Callback that is called by the user clicking Stop screen sharing on the browser.
165
     */
166
    handleStopScreenSharing() {
167
        if (this.isRecording() || this.isPaused()) {
168
            this.requestRecordingStop();
169
            this.cleanupStream();
170
        } else {
171
            this.setRecordButtonState(false);
172
            this.displayAlert(
173
                getString('screensharingstopped_title', component),
174
                getString('screensharingstopped', component)
175
            );
176
        }
177
    }
178
 
179
    handleRecordingStartStopRequested() {
180
        if (this.isRecording() || this.isPaused()) {
181
            this.requestRecordingStop();
182
            this.cleanupStream();
183
        } else {
184
            this.startRecording();
185
        }
186
    }
187
 
188
    static getModalClass() {
189
        return class extends Modal {
190
            static TYPE = `${component}/screen_recorder`;
191
            static TEMPLATE = `${component}/screen_recorder`;
192
        };
193
    }
194
}