%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/vacivi36/ava/lib/editor/tiny/plugins/recordrtc/amd/build/
Upload File :
Create Path :
Current File : /home/vacivi36/ava/lib/editor/tiny/plugins/recordrtc/amd/build/base_recorder.min.js.map

{"version":3,"file":"base_recorder.min.js","sources":["../src/base_recorder.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.\n//\n\n/**\n * Tiny Record RTC type.\n *\n * @module      tiny_recordrtc/recording/base\n * @copyright   2022 Stevani Andolo <stevani@hotmail.com.au>\n * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {get_string as getString, get_strings as getStrings} from 'core/str';\nimport {component} from './common';\nimport Pending from 'core/pending';\nimport {getData} from './options';\nimport uploadFile from 'editor_tiny/uploader';\nimport {add as addToast} from 'core/toast';\nimport * as ModalEvents from 'core/modal_events';\nimport * as ModalFactory from 'core/modal_factory';\nimport * as Templates from 'core/templates';\nimport {saveCancelPromise} from 'core/notification';\nimport {prefetchStrings, prefetchTemplates} from 'core/prefetch';\n\n/**\n * The RecordRTC base class for audio, video, and any other future types\n */\nexport default class {\n\n    stopRequested = false;\n\n    /**\n     * Constructor for the RecordRTC class\n     *\n     * @param {TinyMCE} editor The Editor to which the content will be inserted\n     * @param {Modal} modal The Moodle Modal that contains the interface used for recording\n     */\n    constructor(editor, modal) {\n        this.ready = false;\n\n        if (!this.checkAndWarnAboutBrowserCompatibility()) {\n            return;\n        }\n\n        this.editor = editor;\n        this.config = getData(editor).params;\n        this.modal = modal;\n        this.modalRoot = modal.getRoot()[0];\n        this.startStopButton = this.modalRoot.querySelector('button[data-action=\"startstop\"]');\n        this.uploadButton = this.modalRoot.querySelector('button[data-action=\"upload\"]');\n\n        // Disable the record button untilt he stream is acquired.\n        this.setRecordButtonState(false);\n\n        this.player = this.configurePlayer();\n        this.registerEventListeners();\n        this.ready = true;\n\n        this.captureUserMedia();\n        this.prefetchContent();\n    }\n\n    /**\n     * Check whether the browser is compatible.\n     *\n     * @returns {boolean}\n     */\n    isReady() {\n        return this.ready;\n    }\n\n    // Disable eslint's valid-jsdoc rule as the following methods are abstract and mnust be overridden by the child class.\n\n    /* eslint-disable valid-jsdoc, no-unused-vars */\n\n    /**\n     * Get the Player element for this type.\n     *\n     * @returns {HTMLElement} The player element, typically an audio or video tag.\n     */\n    configurePlayer() {\n        throw new Error(`configurePlayer() must be implemented in ${this.constructor.name}`);\n    }\n\n    /**\n     * Get the list of supported mimetypes for this recorder.\n     * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/isTypeSupported}\n     *\n     * @returns {string[]} The list of supported mimetypes.\n     */\n    getSupportedTypes() {\n        throw new Error(`getSupportedTypes() must be implemented in ${this.constructor.name}`);\n    }\n\n    /**\n     * Get any recording options passed into the MediaRecorder.\n     * Please note that the mimeType will be fetched from {@link getSupportedTypes()}.\n     *\n     * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder#options}\n     * @returns {Object}\n     */\n    getRecordingOptions() {\n        throw new Error(`getRecordingOptions() must be implemented in ${this.constructor.name}`);\n    }\n\n    /**\n     * Get a filename for the generated file.\n     *\n     * Typically this function will take a prefix and add a type-specific suffix such as the extension to it.\n     *\n     * @param {string} prefix The prefix for the filename generated by the recorder.\n     * @returns {string}\n     */\n    getFileName(prefix) {\n        throw new Error(`getFileName() must be implemented in ${this.constructor.name}`);\n    }\n\n    /**\n     * Get a list of constraints as required by the getUserMedia() function.\n     * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#constraints}\n     *\n     * @returns {Object}\n     */\n    getMediaConstraints() {\n        throw new Error(`getMediaConstraints() must be implemented in ${this.constructor.name}`);\n    }\n\n    /**\n     * Whether to start playing the recording as it is captured.\n     * @returns {boolean} Whether to start playing the recording as it is captured.\n     */\n    playOnCapture() {\n        return false;\n    }\n\n    /**\n     * Get the time limit for this recording type.\n     *\n     * @returns {number} The time limit in seconds.\n     */\n    getTimeLimit() {\n        throw new Error(`getTimeLimit() must be implemented in ${this.constructor.name}`);\n    }\n\n    /**\n     * Get the name of the template used when embedding the URL in the editor content.\n     *\n     * @returns {string}\n     */\n    getEmbedTemplateName() {\n        throw new Error(`getEmbedTemplateName() must be implemented in ${this.constructor.name}`);\n    }\n\n    /**\n     * Fetch the Class of the Modal to be displayed.\n     *\n     * @returns {Modal}\n     */\n    static getModalClass() {\n        throw new Error(`getModalClass() must be implemented in ${this.constructor.name}`);\n    }\n\n    /* eslint-enable valid-jsdoc, no-unused-vars */\n\n    /**\n     * Get the options for the MediaRecorder.\n     *\n     * @returns {object} The options for the MediaRecorder instance.\n     */\n    getParsedRecordingOptions() {\n        const types = this.getSupportedTypes();\n        const options = this.getParsedRecordingOptions();\n        const compatTypes = types.filter((type) => window.MediaRecorder.isTypeSupported(type));\n\n        if (compatTypes.length !== 0) {\n            options.mimeType = compatTypes[0];\n        }\n\n        return options;\n    }\n\n    /**\n     * Start capturing the User Media and handle success or failure of the capture.\n     */\n    async captureUserMedia() {\n        try {\n            const stream = await navigator.mediaDevices.getUserMedia(this.getMediaConstraints());\n            this.handleCaptureSuccess(stream);\n        } catch (error) {\n            this.handleCaptureFailure(error);\n        }\n    }\n\n    /**\n     * Prefetch some of the content that will be used in the UI.\n     *\n     * Note: not all of the strings used are pre-fetched.\n     * Some of the strings will be fetched because their template is used.\n     */\n    prefetchContent() {\n        prefetchStrings(component, [\n            'uploading',\n            'recordagain_title',\n            'recordagain_desc',\n            'discard_title',\n            'discard_desc',\n            'confirm_yes',\n            'recordinguploaded',\n            'maxfilesizehit',\n            'maxfilesizehit_title',\n            'uploadfailed',\n        ]);\n\n        prefetchTemplates([\n            this.getEmbedTemplateName(),\n            'tiny_recordrtc/timeremaining',\n        ]);\n    }\n\n    /**\n     * Display an error message to the user.\n     *\n     * @param {Promise<string>} title The error title\n     * @param {Promise<string>} content The error message\n     * @returns {Promise<Modal>}\n     */\n    async displayAlert(title, content) {\n        const pendingPromise = new Pending('core/confirm:alert');\n        const ModalFactory = await import('core/modal_factory');\n        const modal = await ModalFactory.create({\n            type: ModalFactory.types.ALERT,\n            title: title,\n            body: content,\n            removeOnClose: true,\n        });\n\n        modal.show();\n        pendingPromise.resolve();\n\n        return modal;\n    }\n\n    /**\n     * Handle successful capture of the User Media.\n     *\n     * @param {MediaStream} stream The stream as captured by the User Media.\n     */\n    handleCaptureSuccess(stream) {\n        // Set audio player source to microphone stream.\n        this.player.srcObject = stream;\n\n        if (this.playOnCapture()) {\n            // Mute audio, distracting while recording.\n            this.player.muted = true;\n\n            this.player.play();\n        }\n\n        this.stream = stream;\n        this.setupPlayerSource();\n        this.setRecordButtonState(true);\n    }\n\n    /**\n     * Setup the player to use the stream as a source.\n     */\n    setupPlayerSource() {\n        if (!this.player.srcObject) {\n            this.player.srcObject = this.stream;\n\n            // Mute audio, distracting while recording.\n            this.player.muted = true;\n\n            this.player.play();\n        }\n    }\n\n    /**\n     * Enable the record button.\n     *\n     * @param {boolean|null} enabled Set the button state\n     */\n    setRecordButtonState(enabled) {\n        this.startStopButton.disabled = !enabled;\n    }\n\n    /**\n     * Configure button visibility for the record button.\n     *\n     * @param {boolean} visible Set the visibility of the button.\n     */\n    setRecordButtonVisibility(visible) {\n        const container = this.getButtonContainer('start-stop');\n        container.classList.toggle('hide', !visible);\n    }\n\n    /**\n     * Enable the upload button.\n     *\n     * @param {boolean|null} enabled Set the button state\n     */\n    setUploadButtonState(enabled) {\n        this.uploadButton.disabled = !enabled;\n    }\n\n    /**\n     * Configure button visibility for the upload button.\n     *\n     * @param {boolean} visible Set the visibility of the button.\n     */\n    setUploadButtonVisibility(visible) {\n        const container = this.getButtonContainer('upload');\n        container.classList.toggle('hide', !visible);\n    }\n    /**\n     * Handle failure to capture the User Media.\n     *\n     * @param {Error} error\n     */\n    handleCaptureFailure(error) {\n        // Changes 'CertainError' -> 'gumcertain' to match language string names.\n        var subject = `gum${error.name.replace('Error', '').toLowerCase()}`;\n        this.displayAlert(\n            getString(`${subject}_title`, component),\n            getString(subject, component)\n        );\n    }\n\n    /**\n     * Close the modal and stop recording.\n     */\n    close() {\n        // Closing the modal will destroy it and remove it from the DOM.\n        // It will also stop the recording via the hidden Modal Event.\n        this.modal.hide();\n    }\n\n    /**\n     * Register event listeners for the modal.\n     */\n    registerEventListeners() {\n        this.modalRoot.addEventListener('click', this.handleModalClick.bind(this));\n        this.modal.getRoot().on(ModalEvents.outsideClick, this.outsideClickHandler.bind(this));\n        this.modal.getRoot().on(ModalEvents.hidden, () => {\n            this.cleanupStream();\n            this.requestRecordingStop();\n        });\n    }\n\n    /**\n     * Prevent the Modal from closing when recording is on process.\n     *\n     * @param {MouseEvent} event The click event\n     */\n    async outsideClickHandler(event) {\n        if (this.isRecording()) {\n            // The user is recording.\n            // Do not distract with a confirmation, just prevent closing.\n            event.preventDefault();\n        } else if (this.hasData()) {\n            // If there is a blobsize then there is data that may be lost.\n            // Ask the user to confirm they want to close the modal.\n            // We prevent default here, and then close the modal if they confirm.\n            event.preventDefault();\n\n            try {\n                await saveCancelPromise(\n                    await getString(\"discard_title\", component),\n                    await getString(\"discard_desc\", component),\n                    await getString(\"confirm_yes\", component),\n                );\n                this.modal.hide();\n            } catch (error) {\n                // Do nothing, the modal will not close.\n            }\n        }\n    }\n\n    /**\n     * Handle a click within the Modal.\n     *\n     * @param {MouseEvent} event The click event\n     */\n    handleModalClick(event) {\n        const button = event.target.closest('button');\n        if (button && button.dataset.action) {\n            const action = button.dataset.action;\n            if (action === 'startstop') {\n                this.handleRecordingStartStopRequested();\n            }\n\n            if (action === 'upload') {\n                this.uploadRecording();\n            }\n        }\n    }\n\n    /**\n     * Handle the click event for the recording start/stop button.\n     */\n    handleRecordingStartStopRequested() {\n        if (this.mediaRecorder?.state === 'recording') {\n            this.requestRecordingStop();\n        } else {\n            this.startRecording();\n        }\n    }\n\n    /**\n     * Handle the media stream after it has finished.\n     */\n    async onMediaStopped() {\n        // Set source of audio player.\n        this.blob = new Blob(this.data.chunks, {\n            type: this.mediaRecorder.mimeType\n        });\n        this.player.srcObject = null;\n        this.player.src = URL.createObjectURL(this.blob);\n\n        // Change the label to \"Record again\".\n        this.setRecordButtonTextFromString('recordagain');\n\n        // Show audio player with controls enabled, and unmute.\n        this.player.muted = false;\n        this.player.controls = true;\n        this.getButtonContainer('player')?.classList.toggle('hide', false);\n\n        // Show upload button.\n        this.setUploadButtonVisibility(true);\n        this.setUploadButtonState(true);\n    }\n\n    /**\n     * Upload the recording and insert it into the editor content.\n     */\n    async uploadRecording() {\n        // Trigger error if no recording has been made.\n        if (this.data.chunks.length === 0) {\n            this.displayAlert('norecordingfound');\n            return;\n        }\n\n        const fileName = this.getFileName((Math.random() * 1000).toString().replace('.', ''));\n\n        // Upload recording to server.\n        try {\n            // Once uploading starts, do not allow any further changes to the recording.\n            this.setRecordButtonVisibility(false);\n\n            // Disable the upload button.\n            this.setUploadButtonState(false);\n\n            // Upload the recording.\n            const fileURL = await uploadFile(this.editor, 'media', this.blob, fileName, (progress) => {\n                this.setUploadButtonTextProgress(progress);\n            });\n            this.insertMedia(fileURL);\n            this.close();\n            addToast(await getString('recordinguploaded', component));\n        } catch (error) {\n            // Show a toast and unhide the button.\n            this.setUploadButtonState(true);\n\n            addToast(await getString('uploadfailed', component, {error}), {\n                type: 'error',\n            });\n\n        }\n    }\n\n    /**\n     * Helper to get the container that a button is in.\n     *\n     * @param {string} purpose The button purpose\n     * @returns {HTMLElement}\n     */\n    getButtonContainer(purpose) {\n        return this.modalRoot.querySelector(`[data-purpose=\"${purpose}-container\"]`);\n    }\n\n    /**\n     * Check whether the browser is compatible with capturing media.\n     *\n     * @returns {boolean}\n     */\n    static isBrowserCompatible() {\n        return this.checkSecure() && this.hasUserMedia();\n    }\n\n    static async display(editor) {\n        const ModalClass = this.getModalClass();\n        const modal = await ModalFactory.create({\n            type: ModalClass.TYPE,\n            templateContext: {},\n            large: true,\n        });\n\n        // Set up the VideoRecorder.\n        const recorder = new this(editor, modal);\n        if (recorder.isReady()) {\n            modal.show();\n        }\n        return modal;\n    }\n\n    /**\n     * Check whether the browser is compatible with capturing media, and display a warning if not.\n     *\n     * @returns {boolean}\n     */\n    checkAndWarnAboutBrowserCompatibility() {\n        if (!this.constructor.checkSecure()) {\n            getStrings(['insecurealert_title', 'insecurealert'].map((key) => ({key, component})))\n                .then(([title, message]) => addToast(message, {title, type: 'error'}))\n                .catch();\n            return false;\n        }\n\n        if (!this.constructor.hasUserMedia) {\n            getStrings(['nowebrtc_title', 'nowebrtc'].map((key) => ({key, component})))\n                .then(([title, message]) => addToast(message, {title, type: 'error'}))\n                .catch();\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * Check whether the browser supports WebRTC.\n     *\n     * @returns {boolean}\n     */\n    static hasUserMedia() {\n        return (navigator.mediaDevices && window.MediaRecorder);\n    }\n\n    /**\n     * Check whether the hostname is either hosted over SSL, or from a valid localhost hostname.\n     *\n     * The UserMedia API can only be used in secure contexts as noted.\n     * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#privacy_and_security}\n     *\n     * @returns {boolean} Whether the plugin can be loaded.\n     */\n    static checkSecure() {\n        // Note: We can now use window.isSecureContext.\n        // https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#feature_detection\n        // https://developer.mozilla.org/en-US/docs/Web/API/isSecureContext\n        return window.isSecureContext;\n    }\n\n    /**\n     * Update the content of the stop recording button timer.\n     */\n    async setStopRecordingButton() {\n        const {html, js} = await Templates.renderForPromise('tiny_recordrtc/timeremaining', this.getTimeRemaining());\n        Templates.replaceNodeContents(this.startStopButton, html, js);\n        this.buttonTimer = setInterval(this.updateRecordButtonTime.bind(this), 500);\n    }\n\n    /**\n     * Update the time on the stop recording button.\n     */\n    updateRecordButtonTime() {\n        const {remaining, minutes, seconds} = this.getTimeRemaining();\n        if (remaining < 0) {\n            this.requestRecordingStop();\n        } else {\n            this.startStopButton.querySelector('[data-type=\"minutes\"]').textContent = minutes;\n            this.startStopButton.querySelector('[data-type=\"seconds\"]').textContent = seconds;\n        }\n    }\n\n    /**\n     * Set the text of the record button using a language string.\n     *\n     * @param {string} string The string identifier\n     */\n    async setRecordButtonTextFromString(string) {\n        this.startStopButton.textContent = await getString(string, component);\n    }\n\n    /**\n     * Set the upload button text progress.\n     *\n     * @param {number} progress The progress\n     */\n    async setUploadButtonTextProgress(progress) {\n        this.uploadButton.textContent = await getString('uploading', component, {\n            progress: Math.round(progress * 100) / 100,\n        });\n    }\n\n    async resetUploadButtonText() {\n        this.uploadButton.textContent = await getString('upload', component);\n    }\n\n    /**\n     * Clear the timer for the stop recording button.\n     */\n    clearButtonTimer() {\n        if (this.buttonTimer) {\n            clearInterval(this.buttonTimer);\n        }\n        this.buttonTimer = null;\n    }\n\n    /**\n     * Get the time remaining for the recording.\n     *\n     * @returns {Object} The minutes and seconds remaining.\n     */\n    getTimeRemaining() {\n        // All times are in milliseconds\n        const now = new Date().getTime();\n        const remaining = Math.floor(this.getTimeLimit() - ((now - this.startTime) / 1000));\n\n        const formatter = new Intl.NumberFormat(navigator.language, {minimumIntegerDigits: 2});\n        const seconds = formatter.format(remaining % 60);\n        const minutes = formatter.format(Math.floor((remaining - seconds) / 60));\n        return {\n            remaining,\n            minutes,\n            seconds,\n        };\n    }\n\n    /**\n     * Get the maximum file size that can be uploaded.\n     *\n     * @returns {number} The max byte size\n     */\n    getMaxUploadSize() {\n        return this.config.maxrecsize;\n    }\n\n    /**\n     * Stop the recording.\n     * Please note that this should only stop the recording.\n     * Anything related to processing the recording should be handled by the\n     * mediaRecorder's stopped event handler which is processed after it has stopped.\n     */\n    requestRecordingStop() {\n        if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {\n            this.stopRequested = true;\n        } else {\n            // There is no recording to stop, but the stream must still be cleaned up.\n            this.cleanupStream();\n        }\n    }\n\n    stopRecorder() {\n        this.mediaRecorder.stop();\n\n        // Unmute the player so that the audio is heard during playback.\n        this.player.muted = false;\n    }\n\n    /**\n     * Clean up the stream.\n     *\n     * This involves stopping any track which is still active.\n     */\n    cleanupStream() {\n        if (this.stream) {\n            this.stream.getTracks()\n                .filter((track) => track.readyState !== 'ended')\n                .forEach((track) => track.stop());\n        }\n    }\n\n    /**\n     * Handle the mediaRecorder `stop` event.\n     */\n    handleStopped() {\n        // Handle the stream data.\n        this.onMediaStopped();\n\n        // Clear the button timer.\n        this.clearButtonTimer();\n    }\n\n    /**\n     * Handle the mediaRecorder `start` event.\n     *\n     * This event is called when the recording starts.\n     */\n    handleStarted() {\n        this.startTime = new Date().getTime();\n        this.setStopRecordingButton();\n    }\n\n    /**\n     * Handle the mediaRecorder `dataavailable` event.\n     *\n     * @param {Event} event\n     */\n    handleDataAvailable(event) {\n        if (this.isRecording()) {\n            const newSize = this.data.blobSize + event.data.size;\n            // Recording stops when either the maximum upload size is reached, or the time limit expires.\n            // The time limit is checked in the `updateButtonTime` function.\n            if (newSize >= this.getMaxUploadSize()) {\n                this.stopRecorder();\n                this.displayFileLimitHitMessage();\n            } else {\n                // Push recording slice to array.\n                this.data.chunks.push(event.data);\n\n                // Size of all recorded data so far.\n                this.data.blobSize = newSize;\n\n                if (this.stopRequested) {\n                    this.stopRecorder();\n                }\n            }\n        }\n    }\n\n    async displayFileLimitHitMessage() {\n        addToast(await getString('maxfilesizehit', component), {\n            title: await getString('maxfilesizehit_title', component),\n            type: 'error',\n        });\n    }\n\n    /**\n     * Check whether the recording is in progress.\n     *\n     * @returns {boolean}\n     */\n    isRecording() {\n        return this.mediaRecorder?.state === 'recording';\n    }\n\n    /**\n     * Whether any data has been recorded.\n     *\n     * @returns {boolean}\n     */\n    hasData() {\n        return !!this.data?.blobSize;\n    }\n\n    /**\n     * Start the recording\n     */\n    async startRecording() {\n        if (this.mediaRecorder) {\n            // Stop the existing recorder if it exists.\n            if (this.isRecording()) {\n                this.mediaRecorder.stop();\n            }\n\n            if (this.hasData()) {\n                const resetRecording = await this.recordAgainConfirmation();\n                if (!resetRecording) {\n                    // User cancelled at the confirmation to reset the data, so exit early.\n                    return;\n                }\n                this.setUploadButtonVisibility(false);\n            }\n\n            this.mediaRecorder = null;\n        }\n\n        // The options for the recording codecs and bitrates.\n        this.mediaRecorder = new MediaRecorder(this.stream, this.getParsedRecordingOptions());\n\n        this.mediaRecorder.addEventListener('dataavailable', this.handleDataAvailable.bind(this));\n        this.mediaRecorder.addEventListener('stop', this.handleStopped.bind(this));\n        this.mediaRecorder.addEventListener('start', this.handleStarted.bind(this));\n\n        this.data = {\n            chunks: [],\n            blobSize: 0\n        };\n        this.setupPlayerSource();\n        this.stopRequested = false;\n\n        // Capture in 50ms chunks.\n        this.mediaRecorder.start(50);\n    }\n\n    /**\n     * Confirm whether the user wants to reset the existing recoring.\n     *\n     * @returns {Promise<boolean>} Whether the user confirmed the reset.\n     */\n    async recordAgainConfirmation() {\n        try {\n            await saveCancelPromise(\n                await getString(\"recordagain_title\", component),\n                await getString(\"recordagain_desc\", component),\n                await getString(\"confirm_yes\", component)\n            );\n            return true;\n        } catch {\n            return false;\n        }\n    }\n\n    /**\n     * Insert the HTML to embed the recording into the editor content.\n     *\n     * @param {string} source The URL to view the media.\n     */\n    async insertMedia(source) {\n        const {html} = await Templates.renderForPromise(\n            this.getEmbedTemplateName(),\n            this.getEmbedTemplateContext({\n                source,\n            })\n        );\n        this.editor.insertContent(html);\n    }\n\n    /**\n     * Add or modify the template parameters for the specified type.\n     *\n     * @param {Object} templateContext The Tempalte context to use\n     * @returns {Object} The finalised template context\n     */\n    getEmbedTemplateContext(templateContext) {\n        return templateContext;\n    }\n}\n"],"names":["constructor","editor","modal","ready","this","checkAndWarnAboutBrowserCompatibility","config","params","modalRoot","getRoot","startStopButton","querySelector","uploadButton","setRecordButtonState","player","configurePlayer","registerEventListeners","captureUserMedia","prefetchContent","isReady","Error","name","getSupportedTypes","getRecordingOptions","getFileName","prefix","getMediaConstraints","playOnCapture","getTimeLimit","getEmbedTemplateName","getParsedRecordingOptions","types","options","compatTypes","filter","type","window","MediaRecorder","isTypeSupported","length","mimeType","stream","navigator","mediaDevices","getUserMedia","handleCaptureSuccess","error","handleCaptureFailure","component","title","content","pendingPromise","Pending","ModalFactory","create","ALERT","body","removeOnClose","show","resolve","srcObject","muted","play","setupPlayerSource","enabled","disabled","setRecordButtonVisibility","visible","getButtonContainer","classList","toggle","setUploadButtonState","setUploadButtonVisibility","subject","replace","toLowerCase","displayAlert","close","hide","addEventListener","handleModalClick","bind","on","ModalEvents","outsideClick","outsideClickHandler","hidden","cleanupStream","requestRecordingStop","event","isRecording","preventDefault","hasData","button","target","closest","dataset","action","handleRecordingStartStopRequested","uploadRecording","mediaRecorder","state","startRecording","blob","Blob","data","chunks","src","URL","createObjectURL","setRecordButtonTextFromString","controls","fileName","Math","random","toString","fileURL","progress","setUploadButtonTextProgress","insertMedia","purpose","checkSecure","hasUserMedia","ModalClass","getModalClass","TYPE","templateContext","large","map","key","then","_ref2","message","catch","_ref","isSecureContext","html","js","Templates","renderForPromise","getTimeRemaining","replaceNodeContents","buttonTimer","setInterval","updateRecordButtonTime","remaining","minutes","seconds","textContent","string","round","clearButtonTimer","clearInterval","now","Date","getTime","floor","startTime","formatter","Intl","NumberFormat","language","minimumIntegerDigits","format","getMaxUploadSize","maxrecsize","stopRequested","stopRecorder","stop","getTracks","track","readyState","forEach","handleStopped","onMediaStopped","handleStarted","setStopRecordingButton","handleDataAvailable","newSize","blobSize","size","displayFileLimitHitMessage","push","_this$data","recordAgainConfirmation","start","source","getEmbedTemplateContext","insertContent"],"mappings":"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"}

Zerion Mini Shell 1.0