From 0cd3b75164261ef12c0dd2718fe72250c095abd1 Mon Sep 17 00:00:00 2001 From: Stevani Andolo Date: Wed, 31 Aug 2022 17:37:52 +0800 Subject: [PATCH] MDL-75259 tiny_recordrtc: Implement Record RTC plugin for TinyMCE Part of MDL-75966 Co-authored by Andrew Lyons AMOS BEGIN CPY [audioandvideo,atto_recordrtc],[audioandvideo,tiny_recordrtc] CPY [audiobitrate,atto_recordrtc],[audiobitrate,tiny_recordrtc] CPY [attachrecording,atto_recordrtc],[attachrecording,tiny_recordrtc] CPY [allowedtypes,atto_recordrtc],[allowedtypes,tiny_recordrtc] CPY [allowedtypes_desc,atto_recordrtc],[allowedtypes_desc,tiny_recordrtc] CPY [audiobitrate,atto_recordrtc],[audiobitrate,tiny_recordrtc] CPY [audiobitrate_desc,atto_recordrtc],[audiobitrate_desc,tiny_recordrtc] CPY [audiotimelimit,atto_recordrtc],[audiotimelimit,tiny_recordrtc] CPY [audiotimelimit_desc,atto_recordrtc],[audiotimelimit_desc,tiny_recordrtc] CPY [confirm_yes,atto_recordrtc],[yes,core] CPY [gumabort,atto_recordrtc],[gumabort,tiny_recordrtc] CPY [gumabort_title,atto_recordrtc],[gumabort_title,tiny_recordrtc] CPY [gumnotallowed,atto_recordrtc],[gumnotallowed,tiny_recordrtc] CPY [gumnotallowed_title,atto_recordrtc],[gumnotallowed_title,tiny_recordrtc] CPY [gumnotfound,atto_recordrtc],[gumnotfound,tiny_recordrtc] CPY [gumnotfound_title,atto_recordrtc],[gumnotfound_title,tiny_recordrtc] CPY [gumnotreadable,atto_recordrtc],[gumnotreadable,tiny_recordrtc] CPY [gumnotreadable_title,atto_recordrtc],[gumnotreadable_title,tiny_recordrtc] CPY [gumnotsupported,atto_recordrtc],[gumnotsupported,tiny_recordrtc] CPY [gumnotsupported_title,atto_recordrtc],[gumnotsupported_title,tiny_recordrtc] CPY [gumoverconstrained,atto_recordrtc],[gumoverconstrained,tiny_recordrtc] CPY [gumoverconstrained_title,atto_recordrtc],[gumoverconstrained_title,tiny_recordrtc] CPY [gumsecurity,atto_recordrtc],[gumsecurity,tiny_recordrtc] CPY [gumsecurity_title,atto_recordrtc],[gumsecurity_title,tiny_recordrtc] CPY [gumtype,atto_recordrtc],[gumtype,tiny_recordrtc] CPY [gumtype_title,atto_recordrtc],[gumtype_title,tiny_recordrtc] CPY [insecurealert,atto_recordrtc],[insecurealert,tiny_recordrtc] CPY [insecurealert_title,atto_recordrtc],[insecurealert_title,tiny_recordrtc] CPY [insecurealerttitle,atto_recordrtc],[insecurealerttitle,tiny_recordrtc] CPY [insecurealert,atto_recordrtc],[insecurealert,tiny_recordrtc] CPY [nearingmaxsize,atto_recordrtc],[maxfilesizehit,tiny_recordrtc] CPY [nearingmaxsize_title,atto_recordrtc],[maxfilesizehit_title,tiny_recordrtc] CPY [norecordingfound,atto_recordrtc],[norecordingfound,tiny_recordrtc] CPY [norecordingfound_title,atto_recordrtc],[norecordingfound_title,tiny_recordrtc] CPY [nowebrtc,atto_recordrtc],[nowebrtc,tiny_recordrtc] CPY [nowebrtc_title,atto_recordrtc],[nowebrtc_title,tiny_recordrtc] CPY [onlyaudio,atto_recordrtc],[onlyaudio,tiny_recordrtc] CPY [onlyvideo,atto_recordrtc],[onlyvideo,tiny_recordrtc] CPY [recordagain,atto_recordrtc],[recordagain,tiny_recordrtc] CPY [recordinguploaded,atto_recordrtc],[recordinguploaded,tiny_recordrtc] CPY [recordingfailed,atto_recordrtc],[recordingfailed,tiny_recordrtc] CPY [recordrtc:recordaudio,atto_recordrtc],[recordrtc:recordaudio,tiny_recordrtc] CPY [recordrtc:recordvideo,atto_recordrtc],[recordrtc:recordvideo,tiny_recordrtc] CPY [startrecording,atto_recordrtc],[startrecording,tiny_recordrtc] CPY [stoprecording,atto_recordrtc],[stoprecording,tiny_recordrtc] CPY [timelimitwarning,atto_recordrtc],[timelimitwarning,tiny_recordrtc] CPY [uploadaborted,atto_recordrtc],[uploadaborted,tiny_recordrtc] CPY [uploadprogress,atto_recordrtc],[uploadprogress,tiny_recordrtc] CPY [videobitrate,atto_recordrtc],[videobitrate,tiny_recordrtc] CPY [videobitrate_desc,atto_recordrtc],[videobitrate_desc,tiny_recordrtc] CPY [videobuttontitle,atto_recordrtc],[videobuttontitle,tiny_recordrtc] CPY [videotimelimit,atto_recordrtc],[videotimelimit,tiny_recordrtc] CPY [videotimelimit_desc,atto_recordrtc],[videotimelimit_desc,tiny_recordrtc] AMOS END --- lib/classes/plugin_manager.php | 1 + .../recordrtc/amd/build/audio_recorder.min.js | 3 + .../amd/build/audio_recorder.min.js.map | 1 + .../recordrtc/amd/build/base_recorder.min.js | 3 + .../amd/build/base_recorder.min.js.map | 1 + .../recordrtc/amd/build/commands_audio.min.js | 10 + .../amd/build/commands_audio.min.js.map | 1 + .../recordrtc/amd/build/commands_video.min.js | 10 + .../amd/build/commands_video.min.js.map | 1 + .../plugins/recordrtc/amd/build/common.min.js | 3 + .../recordrtc/amd/build/common.min.js.map | 1 + .../recordrtc/amd/build/configuration.min.js | 11 + .../amd/build/configuration.min.js.map | 1 + .../plugins/recordrtc/amd/build/modal.min.js | 10 + .../recordrtc/amd/build/modal.min.js.map | 1 + .../recordrtc/amd/build/options.min.js | 11 + .../recordrtc/amd/build/options.min.js.map | 1 + .../plugins/recordrtc/amd/build/plugin.min.js | 10 + .../recordrtc/amd/build/plugin.min.js.map | 1 + .../recordrtc/amd/build/video_recorder.min.js | 3 + .../amd/build/video_recorder.min.js.map | 1 + .../recordrtc/amd/src/audio_recorder.js | 84 ++ .../recordrtc/amd/src/base_recorder.js | 840 ++++++++++++++++++ .../recordrtc/amd/src/commands_audio.js | 62 ++ .../recordrtc/amd/src/commands_video.js | 62 ++ .../tiny/plugins/recordrtc/amd/src/common.js | 29 + .../recordrtc/amd/src/configuration.js | 84 ++ .../tiny/plugins/recordrtc/amd/src/modal.js | 39 + .../tiny/plugins/recordrtc/amd/src/options.js | 36 + .../tiny/plugins/recordrtc/amd/src/plugin.js | 62 ++ .../recordrtc/amd/src/video_recorder.js | 100 +++ .../plugins/recordrtc/classes/plugininfo.php | 122 +++ .../recordrtc/classes/privacy/provider.php | 30 + .../tiny/plugins/recordrtc/db/access.php | 44 + .../recordrtc/lang/en/tiny_recordrtc.php | 87 ++ .../tiny/plugins/recordrtc/pix/audio.svg | 15 + .../tiny/plugins/recordrtc/pix/icon.png | Bin 0 -> 2802 bytes .../tiny/plugins/recordrtc/pix/video.svg | 46 + .../tiny/plugins/recordrtc/settings.php | 97 ++ lib/editor/tiny/plugins/recordrtc/styles.css | 43 + .../templates/audio_recorder.mustache | 78 ++ .../recordrtc/templates/embed_audio.mustache | 29 + .../recordrtc/templates/embed_video.mustache | 29 + .../templates/insert_recording.mustache | 70 ++ .../templates/timeremaining.mustache | 28 + .../templates/video_recorder.mustache | 78 ++ lib/editor/tiny/plugins/recordrtc/version.php | 29 + 47 files changed, 2308 insertions(+) create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js.map create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/base_recorder.min.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/base_recorder.min.js.map create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/commands_audio.min.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/commands_audio.min.js.map create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/commands_video.min.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/commands_video.min.js.map create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/common.min.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/common.min.js.map create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/configuration.min.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/configuration.min.js.map create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/modal.min.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/modal.min.js.map create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/options.min.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/options.min.js.map create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/plugin.min.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/plugin.min.js.map create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js.map create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/src/audio_recorder.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/src/base_recorder.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/src/commands_audio.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/src/commands_video.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/src/common.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/src/configuration.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/src/modal.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/src/options.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/src/plugin.js create mode 100644 lib/editor/tiny/plugins/recordrtc/amd/src/video_recorder.js create mode 100644 lib/editor/tiny/plugins/recordrtc/classes/plugininfo.php create mode 100644 lib/editor/tiny/plugins/recordrtc/classes/privacy/provider.php create mode 100644 lib/editor/tiny/plugins/recordrtc/db/access.php create mode 100644 lib/editor/tiny/plugins/recordrtc/lang/en/tiny_recordrtc.php create mode 100644 lib/editor/tiny/plugins/recordrtc/pix/audio.svg create mode 100644 lib/editor/tiny/plugins/recordrtc/pix/icon.png create mode 100644 lib/editor/tiny/plugins/recordrtc/pix/video.svg create mode 100644 lib/editor/tiny/plugins/recordrtc/settings.php create mode 100644 lib/editor/tiny/plugins/recordrtc/styles.css create mode 100644 lib/editor/tiny/plugins/recordrtc/templates/audio_recorder.mustache create mode 100644 lib/editor/tiny/plugins/recordrtc/templates/embed_audio.mustache create mode 100644 lib/editor/tiny/plugins/recordrtc/templates/embed_video.mustache create mode 100644 lib/editor/tiny/plugins/recordrtc/templates/insert_recording.mustache create mode 100644 lib/editor/tiny/plugins/recordrtc/templates/timeremaining.mustache create mode 100644 lib/editor/tiny/plugins/recordrtc/templates/video_recorder.mustache create mode 100644 lib/editor/tiny/plugins/recordrtc/version.php diff --git a/lib/classes/plugin_manager.php b/lib/classes/plugin_manager.php index 7d3a77a36d8..ca1a97208a2 100644 --- a/lib/classes/plugin_manager.php +++ b/lib/classes/plugin_manager.php @@ -2032,6 +2032,7 @@ class core_plugin_manager { 'autosave', 'h5p', 'media', + 'recordrtc', ], 'tinymce' => array( diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js b/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js new file mode 100644 index 00000000000..914faba5ac5 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js @@ -0,0 +1,3 @@ +define("tiny_recordrtc/audio_recorder",["exports","./base_recorder","./modal","core/modal_registry","tiny_recordrtc/common"],(function(_exports,_base_recorder,_modal,_modal_registry,_common){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_base_recorder=_interopRequireDefault(_base_recorder),_modal=_interopRequireDefault(_modal),_modal_registry=_interopRequireDefault(_modal_registry);class Audio extends _base_recorder.default{configurePlayer(){return this.modalRoot.querySelector("audio")}getSupportedTypes(){return["audio/webm;codecs=opus","audio/ogg;codecs=opus"]}getParsedRecordingOptions(){return{audioBitsPerSecond:parseInt(this.config.audiobitrate)}}getMediaConstraints(){return{audio:!0}}getRecordingType(){return"audio"}getTimeLimit(){return this.config.audiotimelimit}getEmbedTemplateName(){return"tiny_recordrtc/embed_audio"}getFileName(prefix){return"".concat(prefix,"-audio.ogg")}static getModalClass(){var _class;const modalType="".concat(_common.component,"/audio_recorder"),registration=_modal_registry.default.get(modalType);if(registration)return registration.module;const AudioModal=(_defineProperty(_class=class extends _modal.default{},"TYPE",modalType),_defineProperty(_class,"TEMPLATE","".concat(_common.component,"/audio_recorder")),_class);return _modal_registry.default.register(AudioModal.TYPE,AudioModal,AudioModal.TEMPLATE),AudioModal}}return _exports.default=Audio,_exports.default})); + +//# sourceMappingURL=audio_recorder.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js.map b/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js.map new file mode 100644 index 00000000000..3d5b29e413b --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/audio_recorder.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"audio_recorder.min.js","sources":["../src/audio_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 .\n\n/**\n * Tiny Record RTC - audio recorder configuration.\n *\n * @module tiny_recordrtc/audio\n * @copyright 2022 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseClass from './base_recorder';\nimport Modal from './modal';\nimport ModalRegistry from 'core/modal_registry';\nimport {component} from 'tiny_recordrtc/common';\n\nexport default class Audio extends BaseClass {\n configurePlayer() {\n return this.modalRoot.querySelector('audio');\n }\n\n getSupportedTypes() {\n return [\n 'audio/webm;codecs=opus',\n 'audio/ogg;codecs=opus',\n ];\n }\n\n getParsedRecordingOptions() {\n return {\n audioBitsPerSecond: parseInt(this.config.audiobitrate),\n };\n }\n\n getMediaConstraints() {\n return {\n audio: true,\n };\n }\n\n getRecordingType() {\n return 'audio';\n }\n\n getTimeLimit() {\n return this.config.audiotimelimit;\n }\n\n getEmbedTemplateName() {\n return 'tiny_recordrtc/embed_audio';\n }\n\n getFileName(prefix) {\n return `${prefix}-audio.ogg`;\n }\n\n static getModalClass() {\n const modalType = `${component}/audio_recorder`;\n const registration = ModalRegistry.get(modalType);\n if (registration) {\n return registration.module;\n }\n\n const AudioModal = class extends Modal {\n static TYPE = modalType;\n static TEMPLATE = `${component}/audio_recorder`;\n };\n\n ModalRegistry.register(AudioModal.TYPE, AudioModal, AudioModal.TEMPLATE);\n return AudioModal;\n }\n}\n"],"names":["Audio","BaseClass","configurePlayer","this","modalRoot","querySelector","getSupportedTypes","getParsedRecordingOptions","audioBitsPerSecond","parseInt","config","audiobitrate","getMediaConstraints","audio","getRecordingType","getTimeLimit","audiotimelimit","getEmbedTemplateName","getFileName","prefix","modalType","component","registration","ModalRegistry","get","module","AudioModal","Modal","register","TYPE","TEMPLATE"],"mappings":"+pBA4BqBA,cAAcC,uBAC/BC,yBACWC,KAAKC,UAAUC,cAAc,SAGxCC,0BACW,CACH,yBACA,yBAIRC,kCACW,CACHC,mBAAoBC,SAASN,KAAKO,OAAOC,eAIjDC,4BACW,CACHC,OAAO,GAIfC,yBACW,QAGXC,sBACWZ,KAAKO,OAAOM,eAGvBC,6BACW,6BAGXC,YAAYC,wBACEA,6DAIJC,oBAAeC,qCACfC,aAAeC,wBAAcC,IAAIJ,cACnCE,oBACOA,aAAaG,aAGlBC,mCAAa,cAAcC,wBACfP,uDACOC,6EAGXO,SAASF,WAAWG,KAAMH,WAAYA,WAAWI,UACxDJ"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/base_recorder.min.js b/lib/editor/tiny/plugins/recordrtc/amd/build/base_recorder.min.js new file mode 100644 index 00000000000..704a4fca015 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/base_recorder.min.js @@ -0,0 +1,3 @@ +define("tiny_recordrtc/base_recorder",["exports","core/str","./common","core/pending","./options","editor_tiny/uploader","core/toast","core/modal_events","core/modal_factory","core/templates","core/notification","core/prefetch"],(function(_exports,_str,_common,_pending,_options,_uploader,_toast,ModalEvents,ModalFactory,Templates,_notification,_prefetch){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_pending=_interopRequireDefault(_pending),_uploader=_interopRequireDefault(_uploader),ModalEvents=_interopRequireWildcard(ModalEvents),ModalFactory=_interopRequireWildcard(ModalFactory),Templates=_interopRequireWildcard(Templates);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}return _exports.default=class{constructor(editor,modal){var obj,key,value;value=!1,(key="stopRequested")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,this.ready=!1,this.checkAndWarnAboutBrowserCompatibility()&&(this.editor=editor,this.config=(0,_options.getData)(editor).params,this.modal=modal,this.modalRoot=modal.getRoot()[0],this.startStopButton=this.modalRoot.querySelector('button[data-action="startstop"]'),this.uploadButton=this.modalRoot.querySelector('button[data-action="upload"]'),this.setRecordButtonState(!1),this.player=this.configurePlayer(),this.registerEventListeners(),this.ready=!0,this.captureUserMedia(),this.prefetchContent())}isReady(){return this.ready}configurePlayer(){throw new Error("configurePlayer() must be implemented in ".concat(this.constructor.name))}getSupportedTypes(){throw new Error("getSupportedTypes() must be implemented in ".concat(this.constructor.name))}getRecordingOptions(){throw new Error("getRecordingOptions() must be implemented in ".concat(this.constructor.name))}getFileName(prefix){throw new Error("getFileName() must be implemented in ".concat(this.constructor.name))}getMediaConstraints(){throw new Error("getMediaConstraints() must be implemented in ".concat(this.constructor.name))}playOnCapture(){return!1}getTimeLimit(){throw new Error("getTimeLimit() must be implemented in ".concat(this.constructor.name))}getEmbedTemplateName(){throw new Error("getEmbedTemplateName() must be implemented in ".concat(this.constructor.name))}static getModalClass(){throw new Error("getModalClass() must be implemented in ".concat(this.constructor.name))}getParsedRecordingOptions(){const types=this.getSupportedTypes(),options=this.getParsedRecordingOptions(),compatTypes=types.filter((type=>window.MediaRecorder.isTypeSupported(type)));return 0!==compatTypes.length&&(options.mimeType=compatTypes[0]),options}async captureUserMedia(){try{const stream=await navigator.mediaDevices.getUserMedia(this.getMediaConstraints());this.handleCaptureSuccess(stream)}catch(error){this.handleCaptureFailure(error)}}prefetchContent(){(0,_prefetch.prefetchStrings)(_common.component,["uploading","recordagain_title","recordagain_desc","discard_title","discard_desc","confirm_yes","recordinguploaded","maxfilesizehit","maxfilesizehit_title","uploadfailed"]),(0,_prefetch.prefetchTemplates)([this.getEmbedTemplateName(),"tiny_recordrtc/timeremaining"])}async displayAlert(title,content){const pendingPromise=new _pending.default("core/confirm:alert"),ModalFactory=await("function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require(["core/modal_factory"],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require("core/modal_factory")):Promise.resolve(_systemImportTransformerGlobalIdentifier["core/modal_factory"])),modal=await ModalFactory.create({type:ModalFactory.types.ALERT,title:title,body:content,removeOnClose:!0});return modal.show(),pendingPromise.resolve(),modal}handleCaptureSuccess(stream){this.player.srcObject=stream,this.playOnCapture()&&(this.player.muted=!0,this.player.play()),this.stream=stream,this.setupPlayerSource(),this.setRecordButtonState(!0)}setupPlayerSource(){this.player.srcObject||(this.player.srcObject=this.stream,this.player.muted=!0,this.player.play())}setRecordButtonState(enabled){this.startStopButton.disabled=!enabled}setRecordButtonVisibility(visible){this.getButtonContainer("start-stop").classList.toggle("hide",!visible)}setUploadButtonState(enabled){this.uploadButton.disabled=!enabled}setUploadButtonVisibility(visible){this.getButtonContainer("upload").classList.toggle("hide",!visible)}handleCaptureFailure(error){var subject="gum".concat(error.name.replace("Error","").toLowerCase());this.displayAlert((0,_str.get_string)("".concat(subject,"_title"),_common.component),(0,_str.get_string)(subject,_common.component))}close(){this.modal.hide()}registerEventListeners(){this.modalRoot.addEventListener("click",this.handleModalClick.bind(this)),this.modal.getRoot().on(ModalEvents.outsideClick,this.outsideClickHandler.bind(this)),this.modal.getRoot().on(ModalEvents.hidden,(()=>{this.cleanupStream(),this.requestRecordingStop()}))}async outsideClickHandler(event){if(this.isRecording())event.preventDefault();else if(this.hasData()){event.preventDefault();try{await(0,_notification.saveCancelPromise)(await(0,_str.get_string)("discard_title",_common.component),await(0,_str.get_string)("discard_desc",_common.component),await(0,_str.get_string)("confirm_yes",_common.component)),this.modal.hide()}catch(error){}}}handleModalClick(event){const button=event.target.closest("button");if(button&&button.dataset.action){const action=button.dataset.action;"startstop"===action&&this.handleRecordingStartStopRequested(),"upload"===action&&this.uploadRecording()}}handleRecordingStartStopRequested(){var _this$mediaRecorder;"recording"===(null===(_this$mediaRecorder=this.mediaRecorder)||void 0===_this$mediaRecorder?void 0:_this$mediaRecorder.state)?this.requestRecordingStop():this.startRecording()}async onMediaStopped(){var _this$getButtonContai;this.blob=new Blob(this.data.chunks,{type:this.mediaRecorder.mimeType}),this.player.srcObject=null,this.player.src=URL.createObjectURL(this.blob),this.setRecordButtonTextFromString("recordagain"),this.player.muted=!1,this.player.controls=!0,null===(_this$getButtonContai=this.getButtonContainer("player"))||void 0===_this$getButtonContai||_this$getButtonContai.classList.toggle("hide",!1),this.setUploadButtonVisibility(!0),this.setUploadButtonState(!0)}async uploadRecording(){if(0===this.data.chunks.length)return void this.displayAlert("norecordingfound");const fileName=this.getFileName((1e3*Math.random()).toString().replace(".",""));try{this.setRecordButtonVisibility(!1),this.setUploadButtonState(!1);const fileURL=await(0,_uploader.default)(this.editor,"media",this.blob,fileName,(progress=>{this.setUploadButtonTextProgress(progress)}));this.insertMedia(fileURL),this.close(),(0,_toast.add)(await(0,_str.get_string)("recordinguploaded",_common.component))}catch(error){this.setUploadButtonState(!0),(0,_toast.add)(await(0,_str.get_string)("uploadfailed",_common.component,{error:error}),{type:"error"})}}getButtonContainer(purpose){return this.modalRoot.querySelector('[data-purpose="'.concat(purpose,'-container"]'))}static isBrowserCompatible(){return this.checkSecure()&&this.hasUserMedia()}static async display(editor){const ModalClass=this.getModalClass(),modal=await ModalFactory.create({type:ModalClass.TYPE,templateContext:{},large:!0});return new this(editor,modal).isReady()&&modal.show(),modal}checkAndWarnAboutBrowserCompatibility(){return this.constructor.checkSecure()?!!this.constructor.hasUserMedia||((0,_str.get_strings)(["nowebrtc_title","nowebrtc"].map((key=>({key:key,component:_common.component})))).then((_ref2=>{let[title,message]=_ref2;return(0,_toast.add)(message,{title:title,type:"error"})})).catch(),!1):((0,_str.get_strings)(["insecurealert_title","insecurealert"].map((key=>({key:key,component:_common.component})))).then((_ref=>{let[title,message]=_ref;return(0,_toast.add)(message,{title:title,type:"error"})})).catch(),!1)}static hasUserMedia(){return navigator.mediaDevices&&window.MediaRecorder}static checkSecure(){return window.isSecureContext}async setStopRecordingButton(){const{html:html,js:js}=await Templates.renderForPromise("tiny_recordrtc/timeremaining",this.getTimeRemaining());Templates.replaceNodeContents(this.startStopButton,html,js),this.buttonTimer=setInterval(this.updateRecordButtonTime.bind(this),500)}updateRecordButtonTime(){const{remaining:remaining,minutes:minutes,seconds:seconds}=this.getTimeRemaining();remaining<0?this.requestRecordingStop():(this.startStopButton.querySelector('[data-type="minutes"]').textContent=minutes,this.startStopButton.querySelector('[data-type="seconds"]').textContent=seconds)}async setRecordButtonTextFromString(string){this.startStopButton.textContent=await(0,_str.get_string)(string,_common.component)}async setUploadButtonTextProgress(progress){this.uploadButton.textContent=await(0,_str.get_string)("uploading",_common.component,{progress:Math.round(100*progress)/100})}async resetUploadButtonText(){this.uploadButton.textContent=await(0,_str.get_string)("upload",_common.component)}clearButtonTimer(){this.buttonTimer&&clearInterval(this.buttonTimer),this.buttonTimer=null}getTimeRemaining(){const now=(new Date).getTime(),remaining=Math.floor(this.getTimeLimit()-(now-this.startTime)/1e3),formatter=new Intl.NumberFormat(navigator.language,{minimumIntegerDigits:2}),seconds=formatter.format(remaining%60);return{remaining:remaining,minutes:formatter.format(Math.floor((remaining-seconds)/60)),seconds:seconds}}getMaxUploadSize(){return this.config.maxrecsize}requestRecordingStop(){this.mediaRecorder&&"inactive"!==this.mediaRecorder.state?this.stopRequested=!0:this.cleanupStream()}stopRecorder(){this.mediaRecorder.stop(),this.player.muted=!1}cleanupStream(){this.stream&&this.stream.getTracks().filter((track=>"ended"!==track.readyState)).forEach((track=>track.stop()))}handleStopped(){this.onMediaStopped(),this.clearButtonTimer()}handleStarted(){this.startTime=(new Date).getTime(),this.setStopRecordingButton()}handleDataAvailable(event){if(this.isRecording()){const newSize=this.data.blobSize+event.data.size;newSize>=this.getMaxUploadSize()?(this.stopRecorder(),this.displayFileLimitHitMessage()):(this.data.chunks.push(event.data),this.data.blobSize=newSize,this.stopRequested&&this.stopRecorder())}}async displayFileLimitHitMessage(){(0,_toast.add)(await(0,_str.get_string)("maxfilesizehit",_common.component),{title:await(0,_str.get_string)("maxfilesizehit_title",_common.component),type:"error"})}isRecording(){var _this$mediaRecorder2;return"recording"===(null===(_this$mediaRecorder2=this.mediaRecorder)||void 0===_this$mediaRecorder2?void 0:_this$mediaRecorder2.state)}hasData(){var _this$data;return!(null===(_this$data=this.data)||void 0===_this$data||!_this$data.blobSize)}async startRecording(){if(this.mediaRecorder){if(this.isRecording()&&this.mediaRecorder.stop(),this.hasData()){if(!await this.recordAgainConfirmation())return;this.setUploadButtonVisibility(!1)}this.mediaRecorder=null}this.mediaRecorder=new MediaRecorder(this.stream,this.getParsedRecordingOptions()),this.mediaRecorder.addEventListener("dataavailable",this.handleDataAvailable.bind(this)),this.mediaRecorder.addEventListener("stop",this.handleStopped.bind(this)),this.mediaRecorder.addEventListener("start",this.handleStarted.bind(this)),this.data={chunks:[],blobSize:0},this.setupPlayerSource(),this.stopRequested=!1,this.mediaRecorder.start(50)}async recordAgainConfirmation(){try{return await(0,_notification.saveCancelPromise)(await(0,_str.get_string)("recordagain_title",_common.component),await(0,_str.get_string)("recordagain_desc",_common.component),await(0,_str.get_string)("confirm_yes",_common.component)),!0}catch{return!1}}async insertMedia(source){const{html:html}=await Templates.renderForPromise(this.getEmbedTemplateName(),this.getEmbedTemplateContext({source:source}));this.editor.insertContent(html)}getEmbedTemplateContext(templateContext){return templateContext}},_exports.default})); + +//# sourceMappingURL=base_recorder.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/base_recorder.min.js.map b/lib/editor/tiny/plugins/recordrtc/amd/build/base_recorder.min.js.map new file mode 100644 index 00000000000..82dfe5618d5 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/base_recorder.min.js.map @@ -0,0 +1 @@ +{"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 .\n//\n\n/**\n * Tiny Record RTC type.\n *\n * @module tiny_recordrtc/recording/base\n * @copyright 2022 Stevani Andolo \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} title The error title\n * @param {Promise} content The error message\n * @returns {Promise}\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} 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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"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/commands_audio.min.js b/lib/editor/tiny/plugins/recordrtc/amd/build/commands_audio.min.js new file mode 100644 index 00000000000..fd739eeced7 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/commands_audio.min.js @@ -0,0 +1,10 @@ +define("tiny_recordrtc/commands_audio",["exports","core/str","editor_tiny/utils","./common","./audio_recorder"],(function(_exports,_str,_utils,_common,_audio_recorder){var obj; +/** + * Tiny Record RTC - record audio command. + * + * @module tiny_recordrtc/recordAudioCommands + * @copyright 2022, Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_audio_recorder=(obj=_audio_recorder)&&obj.__esModule?obj:{default:obj};return _exports.default=async()=>{if(!_audio_recorder.default.isBrowserCompatible())return()=>!1;const[audioButtonTitle,audio]=await Promise.all([(0,_str.get_string)("audiobuttontitle",_common.component),(0,_utils.getButtonImage)("audio",_common.component)]);return editor=>{editor.ui.registry.addIcon("audio",audio.html),editor.ui.registry.addButton(_common.audioButtonName,{icon:"audio",tooltip:audioButtonTitle,onAction:()=>_audio_recorder.default.display(editor)}),editor.ui.registry.addMenuItem(_common.audioButtonName,{icon:"audio",text:audioButtonTitle,onAction:()=>_audio_recorder.default.display(editor)})}},_exports.default})); + +//# sourceMappingURL=commands_audio.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/commands_audio.min.js.map b/lib/editor/tiny/plugins/recordrtc/amd/build/commands_audio.min.js.map new file mode 100644 index 00000000000..00c5f1797db --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/commands_audio.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"commands_audio.min.js","sources":["../src/commands_audio.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 .\n\n/**\n * Tiny Record RTC - record audio command.\n *\n * @module tiny_recordrtc/recordAudioCommands\n * @copyright 2022, Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {get_string as getString} from 'core/str';\nimport {getButtonImage} from 'editor_tiny/utils';\nimport {\n audioButtonName,\n component\n} from './common';\nimport Recorder from './audio_recorder';\n\nexport default async() => {\n if (!Recorder.isBrowserCompatible()) {\n // The browser doesn't support the plugin, so just don't show it.\n return () => false;\n }\n\n const [\n audioButtonTitle,\n audio,\n ] = await Promise.all([\n getString('audiobuttontitle', component),\n getButtonImage('audio', component),\n ]);\n\n return (editor) => {\n const icon = 'audio';\n editor.ui.registry.addIcon(icon, audio.html);\n\n editor.ui.registry.addButton(audioButtonName, {\n icon,\n tooltip: audioButtonTitle,\n onAction: () => Recorder.display(editor),\n });\n\n editor.ui.registry.addMenuItem(audioButtonName, {\n icon,\n text: audioButtonTitle,\n onAction: () => Recorder.display(editor),\n });\n };\n};\n"],"names":["async","Recorder","isBrowserCompatible","audioButtonTitle","audio","Promise","all","component","editor","ui","registry","addIcon","html","addButton","audioButtonName","icon","tooltip","onAction","display","addMenuItem","text"],"mappings":";;;;;;;qLA+BeA,cACNC,wBAASC,4BAEH,KAAM,QAIbC,iBACAC,aACMC,QAAQC,IAAI,EAClB,mBAAU,mBAAoBC,oBAC9B,yBAAe,QAASA,4BAGpBC,SAEJA,OAAOC,GAAGC,SAASC,QADN,QACoBP,MAAMQ,MAEvCJ,OAAOC,GAAGC,SAASG,UAAUC,wBAAiB,CAC1CC,KAJS,QAKTC,QAASb,iBACTc,SAAU,IAAMhB,wBAASiB,QAAQV,UAGrCA,OAAOC,GAAGC,SAASS,YAAYL,wBAAiB,CAC5CC,KAVS,QAWTK,KAAMjB,iBACNc,SAAU,IAAMhB,wBAASiB,QAAQV"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/commands_video.min.js b/lib/editor/tiny/plugins/recordrtc/amd/build/commands_video.min.js new file mode 100644 index 00000000000..75d38339c0a --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/commands_video.min.js @@ -0,0 +1,10 @@ +define("tiny_recordrtc/commands_video",["exports","core/str","editor_tiny/utils","./common","./video_recorder"],(function(_exports,_str,_utils,_common,_video_recorder){var obj; +/** + * Tiny Record RTC - record video command. + * + * @module tiny_recordrtc/recordVideoCommands + * @copyright 2022, Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_video_recorder=(obj=_video_recorder)&&obj.__esModule?obj:{default:obj};return _exports.default=async()=>{if(!_video_recorder.default.isBrowserCompatible())return()=>!1;const[videoButtonTitle,buttonImage]=await Promise.all([(0,_str.get_string)("videobuttontitle",_common.component),(0,_utils.getButtonImage)("video",_common.component)]);return editor=>{editor.ui.registry.addIcon("video",buttonImage.html),editor.ui.registry.addButton(_common.videoButtonName,{icon:"video",tooltip:videoButtonTitle,onAction:()=>_video_recorder.default.display(editor)}),editor.ui.registry.addMenuItem(_common.videoButtonName,{icon:"video",text:videoButtonTitle,onAction:()=>_video_recorder.default.display(editor)})}},_exports.default})); + +//# sourceMappingURL=commands_video.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/commands_video.min.js.map b/lib/editor/tiny/plugins/recordrtc/amd/build/commands_video.min.js.map new file mode 100644 index 00000000000..a48cced9e4b --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/commands_video.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"commands_video.min.js","sources":["../src/commands_video.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 .\n\n/**\n * Tiny Record RTC - record video command.\n *\n * @module tiny_recordrtc/recordVideoCommands\n * @copyright 2022, Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {get_string as getString} from 'core/str';\nimport {getButtonImage as getVideoIcon} from 'editor_tiny/utils';\nimport {\n videoButtonName,\n component\n} from './common';\nimport Recorder from './video_recorder';\n\nexport default async() => {\n if (!Recorder.isBrowserCompatible()) {\n // The browser doesn't support the plugin, so just don't show it.\n return () => false;\n }\n\n const [\n videoButtonTitle,\n buttonImage,\n ] = await Promise.all([\n getString('videobuttontitle', component),\n getVideoIcon('video', component),\n ]);\n\n return (editor) => {\n let icon = 'video';\n editor.ui.registry.addIcon(icon, buttonImage.html);\n\n editor.ui.registry.addButton(videoButtonName, {\n icon,\n tooltip: videoButtonTitle,\n onAction: () => Recorder.display(editor),\n });\n\n editor.ui.registry.addMenuItem(videoButtonName, {\n icon,\n text: videoButtonTitle,\n onAction: () => Recorder.display(editor),\n });\n };\n};\n"],"names":["async","Recorder","isBrowserCompatible","videoButtonTitle","buttonImage","Promise","all","component","editor","ui","registry","addIcon","html","addButton","videoButtonName","icon","tooltip","onAction","display","addMenuItem","text"],"mappings":";;;;;;;qLA+BeA,cACNC,wBAASC,4BAEH,KAAM,QAIbC,iBACAC,mBACMC,QAAQC,IAAI,EAClB,mBAAU,mBAAoBC,oBAC9B,yBAAa,QAASA,4BAGlBC,SAEJA,OAAOC,GAAGC,SAASC,QADR,QACsBP,YAAYQ,MAE7CJ,OAAOC,GAAGC,SAASG,UAAUC,wBAAiB,CAC1CC,KAJO,QAKPC,QAASb,iBACTc,SAAU,IAAMhB,wBAASiB,QAAQV,UAGrCA,OAAOC,GAAGC,SAASS,YAAYL,wBAAiB,CAC5CC,KAVO,QAWPK,KAAMjB,iBACNc,SAAU,IAAMhB,wBAASiB,QAAQV"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/common.min.js b/lib/editor/tiny/plugins/recordrtc/amd/build/common.min.js new file mode 100644 index 00000000000..6cb4fe455a3 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/common.min.js @@ -0,0 +1,3 @@ +define("tiny_recordrtc/common",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={pluginName:"tiny_recordrtc/plugin",component:"tiny_recordrtc",audioButtonName:"tiny_recordrtc_audio",videoButtonName:"tiny_recordrtc_video"},_exports.default})); + +//# sourceMappingURL=common.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/common.min.js.map b/lib/editor/tiny/plugins/recordrtc/amd/build/common.min.js.map new file mode 100644 index 00000000000..d76f8b44c10 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/common.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"common.min.js","sources":["../src/common.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 .\n\n/**\n * Tiny Record RTC common values.\n *\n * @module tiny_recordrtc/common\n * @copyright 2022, Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n pluginName: 'tiny_recordrtc/plugin',\n component: 'tiny_recordrtc',\n audioButtonName: 'tiny_recordrtc_audio',\n videoButtonName: 'tiny_recordrtc_video'\n};\n"],"names":["pluginName","component","audioButtonName","videoButtonName"],"mappings":"uKAuBe,CACXA,WAAY,wBACZC,UAAW,iBACXC,gBAAiB,uBACjBC,gBAAiB"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/configuration.min.js b/lib/editor/tiny/plugins/recordrtc/amd/build/configuration.min.js new file mode 100644 index 00000000000..9bdfc666871 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/configuration.min.js @@ -0,0 +1,11 @@ +define("tiny_recordrtc/configuration",["exports","./common","editor_tiny/utils"],(function(_exports,_common,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.configure=void 0; +/** + * Tiny Record RTC configuration. + * + * @module tiny_recordrtc/configuration + * @copyright 2022, Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +const configureMenu=menu=>{const items=menu.insert.items.split(" ");return items.some(((item,index)=>!!item.match(/(media|video)\b/)&&(items.splice(index+1,0,_common.audioButtonName,_common.videoButtonName),!0)))?menu.insert.items=items.join(" "):(0,_utils.addMenubarItem)(menu,"insert","".concat(_common.audioButtonName," ").concat(_common.videoButtonName)),menu};_exports.configure=instanceConfig=>{return{toolbar:(toolbar=instanceConfig.toolbar,toolbar.map((section=>("content"===section.name&&(section.items.some(((item,index)=>!!item.match(/(media|video)\b/)&&(section.items.splice(index+1,0,_common.audioButtonName,_common.videoButtonName),!0)))||section.items.unshift(_common.audioButtonName,_common.videoButtonName)),section)))),menu:configureMenu(instanceConfig.menu)};var toolbar}})); + +//# sourceMappingURL=configuration.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/configuration.min.js.map b/lib/editor/tiny/plugins/recordrtc/amd/build/configuration.min.js.map new file mode 100644 index 00000000000..f4535178a64 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/configuration.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"configuration.min.js","sources":["../src/configuration.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 .\n\n/**\n * Tiny Record RTC configuration.\n *\n * @module tiny_recordrtc/configuration\n * @copyright 2022, Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {\n audioButtonName,\n videoButtonName\n} from './common';\nimport {\n addMenubarItem,\n} from 'editor_tiny/utils';\n\nconst configureMenu = (menu) => {\n const items = menu.insert.items.split(' ');\n const inserted = items.some((item, index) => {\n // Append after the media or video button.\n if (item.match(/(media|video)\\b/)) {\n items.splice(index + 1, 0, audioButtonName, videoButtonName);\n return true;\n }\n\n return false;\n });\n\n if (inserted) {\n menu.insert.items = items.join(' ');\n } else {\n addMenubarItem(menu, 'insert', `${audioButtonName} ${videoButtonName}`);\n }\n\n return menu;\n};\n\nconst configureToolbar = (toolbar) => {\n // The toolbar contains an array of named sections.\n // The Moodle integration ensures that there is a section called 'content'.\n\n\n return toolbar.map((section) => {\n if (section.name === 'content') {\n const inserted = section.items.some((item, index) => {\n // Append after the media or video button.\n if (item.match(/(media|video)\\b/)) {\n section.items.splice(index + 1, 0, audioButtonName, videoButtonName);\n return true;\n }\n return false;\n });\n\n if (!inserted) {\n section.items.unshift(audioButtonName, videoButtonName);\n }\n }\n\n return section;\n });\n};\n\nexport const configure = (instanceConfig) => {\n // Update the instance configuration to add the Media menu option to the menus and toolbars and upload_handler.\n return {\n toolbar: configureToolbar(instanceConfig.toolbar),\n menu: configureMenu(instanceConfig.menu),\n };\n};\n"],"names":["configureMenu","menu","items","insert","split","some","item","index","match","splice","audioButtonName","videoButtonName","join","instanceConfig","toolbar","map","section","name","unshift"],"mappings":";;;;;;;;MA+BMA,cAAiBC,aACbC,MAAQD,KAAKE,OAAOD,MAAME,MAAM,YACrBF,MAAMG,MAAK,CAACC,KAAMC,UAE3BD,KAAKE,MAAM,qBACXN,MAAMO,OAAOF,MAAQ,EAAG,EAAGG,wBAAiBC,0BACrC,KAOXV,KAAKE,OAAOD,MAAQA,MAAMU,KAAK,+BAEhBX,KAAM,mBAAaS,oCAAmBC,0BAGlDV,yBA4BeY,uBAEf,CACHC,SA5BkBA,QA4BQD,eAAeC,QAvBtCA,QAAQC,KAAKC,UACK,YAAjBA,QAAQC,OACSD,QAAQd,MAAMG,MAAK,CAACC,KAAMC,UAEnCD,KAAKE,MAAM,qBACXQ,QAAQd,MAAMO,OAAOF,MAAQ,EAAG,EAAGG,wBAAiBC,0BAC7C,MAMXK,QAAQd,MAAMgB,QAAQR,wBAAiBC,0BAIxCK,YAQPf,KAAMD,cAAca,eAAeZ,OA7BjBa,IAAAA"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/modal.min.js b/lib/editor/tiny/plugins/recordrtc/amd/build/modal.min.js new file mode 100644 index 00000000000..b74a3730d03 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/modal.min.js @@ -0,0 +1,10 @@ +define("tiny_recordrtc/modal",["exports","core/modal"],(function(_exports,_modal){var obj; +/** + * Abstract Recording Modal for TinyMCE's RecordRTC plugin. + * + * @module tiny_recordrtc/modal + * @copyright 2022 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};class _default extends _modal.default{registerEventListeners(){this.setRemoveOnClose(!0),super.registerEventListeners(),this.registerCloseOnSave(),this.registerCloseOnCancel()}}return _exports.default=_default,_exports.default})); + +//# sourceMappingURL=modal.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/modal.min.js.map b/lib/editor/tiny/plugins/recordrtc/amd/build/modal.min.js.map new file mode 100644 index 00000000000..65086b5bd80 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/modal.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"modal.min.js","sources":["../src/modal.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 .\n\n/**\n * Abstract Recording Modal for TinyMCE's RecordRTC plugin.\n *\n * @module tiny_recordrtc/modal\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\n\nexport default class extends Modal {\n registerEventListeners() {\n // Remove this Modal when it is closed.\n // This must be called before registering any other event listeners.\n this.setRemoveOnClose(true);\n\n // Call the parent registration.\n super.registerEventListeners();\n\n // Register to close on save/cancel.\n this.registerCloseOnSave();\n this.registerCloseOnCancel();\n }\n}\n"],"names":["Modal","registerEventListeners","setRemoveOnClose","registerCloseOnSave","registerCloseOnCancel"],"mappings":";;;;;;;kKAyB6BA,eACzBC,8BAGSC,kBAAiB,SAGhBD,8BAGDE,2BACAC"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/options.min.js b/lib/editor/tiny/plugins/recordrtc/amd/build/options.min.js new file mode 100644 index 00000000000..a00f2f8761a --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/options.min.js @@ -0,0 +1,11 @@ +define("tiny_recordrtc/options",["exports","./common"],(function(_exports,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=_exports.getData=void 0; +/** + * Options helper for Tiny Record RTC plugin. + * + * @module tiny_recordrtc/options + * @copyright 2022, Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +const dataName="".concat(_common.pluginName,":data");_exports.register=editor=>{(0,editor.options.register)(dataName,{processor:"object"})};_exports.getData=editor=>editor.options.get(dataName)})); + +//# sourceMappingURL=options.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/options.min.js.map b/lib/editor/tiny/plugins/recordrtc/amd/build/options.min.js.map new file mode 100644 index 00000000000..1833df1018a --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/options.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"options.min.js","sources":["../src/options.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 .\n\n/**\n * Options helper for Tiny Record RTC plugin.\n *\n * @module tiny_recordrtc/options\n * @copyright 2022, Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {pluginName} from './common';\n\nconst dataName = `${pluginName}:data`;\n\nexport const register = (editor) => {\n const registerOption = editor.options.register;\n\n registerOption(dataName, {\n processor: 'object',\n });\n};\n\nexport const getData = (editor) => editor.options.get(dataName);\n"],"names":["dataName","pluginName","editor","registerOption","options","register","processor","get"],"mappings":";;;;;;;;MAyBMA,mBAAcC,8CAEKC,UAGrBC,EAFuBD,OAAOE,QAAQC,UAEvBL,SAAU,CACrBM,UAAW,6BAIKJ,QAAWA,OAAOE,QAAQG,IAAIP"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/plugin.min.js b/lib/editor/tiny/plugins/recordrtc/amd/build/plugin.min.js new file mode 100644 index 00000000000..db39713a20a --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/plugin.min.js @@ -0,0 +1,10 @@ +define("tiny_recordrtc/plugin",["exports","editor_tiny/loader","editor_tiny/utils","./commands_audio","./commands_video","./configuration","./options","./common"],(function(_exports,_loader,_utils,_commands_audio,_commands_video,Configuration,Options,_common){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * Tiny Record RTC plugin for Moodle. + * + * @module tiny_recordrtc/plugin + * @copyright 2022, Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_commands_audio=_interopRequireDefault(_commands_audio),_commands_video=_interopRequireDefault(_commands_video),Configuration=_interopRequireWildcard(Configuration),Options=_interopRequireWildcard(Options);var _default=new Promise((async resolve=>{const[tinyMCE,setupAudioCommands,setupVideoCommands,pluginMetadata]=await Promise.all([(0,_loader.getTinyMCE)(),(0,_commands_audio.default)(),(0,_commands_video.default)(),(0,_utils.getPluginMetadata)(_common.component,_common.pluginName)]);tinyMCE.PluginManager.add("".concat(_common.component,"/plugin"),(editor=>(Options.register(editor),setupVideoCommands(editor),setupAudioCommands(editor),pluginMetadata))),resolve(["".concat(_common.component,"/plugin"),Configuration])}));return _exports.default=_default,_exports.default})); + +//# sourceMappingURL=plugin.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/plugin.min.js.map b/lib/editor/tiny/plugins/recordrtc/amd/build/plugin.min.js.map new file mode 100644 index 00000000000..c5139d32c81 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/plugin.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"plugin.min.js","sources":["../src/plugin.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 .\n\n/**\n * Tiny Record RTC plugin for Moodle.\n *\n * @module tiny_recordrtc/plugin\n * @copyright 2022, Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {getTinyMCE} from 'editor_tiny/loader';\nimport {getPluginMetadata} from 'editor_tiny/utils';\nimport getSetupAudioCommands from './commands_audio';\nimport getSetupVideoCommands from './commands_video';\nimport * as Configuration from './configuration';\nimport * as Options from './options';\nimport {\n component,\n pluginName\n} from './common';\n\nexport default new Promise(async(resolve) => {\n const [\n tinyMCE,\n setupAudioCommands,\n setupVideoCommands,\n pluginMetadata,\n ] = await Promise.all([\n getTinyMCE(),\n getSetupAudioCommands(),\n getSetupVideoCommands(),\n getPluginMetadata(component, pluginName),\n ]);\n\n tinyMCE.PluginManager.add(`${component}/plugin`, (editor) => {\n // Register options.\n Options.register(editor);\n\n // Setup the Commands (buttons, menu items, and so on) for video.\n setupVideoCommands(editor);\n\n // Setup the Commands (buttons, menu items, and so on) for audio.\n setupAudioCommands(editor);\n\n return pluginMetadata;\n });\n\n // Resolve the Media Plugin and include configuration.\n resolve([`${component}/plugin`, Configuration]);\n});\n"],"names":["Promise","async","tinyMCE","setupAudioCommands","setupVideoCommands","pluginMetadata","all","component","pluginName","PluginManager","add","editor","Options","register","resolve","Configuration"],"mappings":";;;;;;;gTAiCe,IAAIA,SAAQC,MAAAA,gBAEnBC,QACAC,mBACAC,mBACAC,sBACML,QAAQM,IAAI,EAClB,yBACA,8BACA,8BACA,4BAAkBC,kBAAWC,sBAGjCN,QAAQO,cAAcC,cAAOH,8BAAqBI,SAE9CC,QAAQC,SAASF,QAGjBP,mBAAmBO,QAGnBR,mBAAmBQ,QAEZN,kBAIXS,QAAQ,WAAIP,6BAAoBQ"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js b/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js new file mode 100644 index 00000000000..692c207a646 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js @@ -0,0 +1,3 @@ +define("tiny_recordrtc/video_recorder",["exports","./base_recorder","tiny_recordrtc/modal","core/modal_registry","tiny_recordrtc/common"],(function(_exports,_base_recorder,_modal,_modal_registry,_common){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_base_recorder=_interopRequireDefault(_base_recorder),_modal=_interopRequireDefault(_modal),_modal_registry=_interopRequireDefault(_modal_registry);class Video extends _base_recorder.default{configurePlayer(){return this.modalRoot.querySelector("video")}getSupportedTypes(){return["video/webm;codecs=vp9,opus","video/webm;codecs=h264,opus","video/webm;codecs=vp8,opus"]}getParsedRecordingOptions(){return{audioBitsPerSecond:parseInt(this.config.audiobitrate),videoBitsPerSecond:parseInt(this.config.videobitrate)}}getMediaConstraints(){return{audio:!0,video:{width:{ideal:640},height:{ideal:480}}}}playOnCapture(){return!0}getRecordingType(){return"video"}getTimeLimit(){return this.config.videotimelimit}getEmbedTemplateName(){return"tiny_recordrtc/embed_video"}getFileName(prefix){return"".concat(prefix,"-video.webm")}static getModalClass(){var _class;const modalType="".concat(_common.component,"/video_recorder"),registration=_modal_registry.default.get(modalType);if(registration)return registration.module;const VideoModal=(_defineProperty(_class=class extends _modal.default{},"TYPE",modalType),_defineProperty(_class,"TEMPLATE","".concat(_common.component,"/video_recorder")),_class);return _modal_registry.default.register(VideoModal.TYPE,VideoModal,VideoModal.TEMPLATE),VideoModal}}return _exports.default=Video,_exports.default})); + +//# sourceMappingURL=video_recorder.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js.map b/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js.map new file mode 100644 index 00000000000..1559867176e --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/build/video_recorder.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"video_recorder.min.js","sources":["../src/video_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 .\n\n/**\n * Tiny Record RTC - Video recorder configuration.\n *\n * @module tiny_recordrtc/video\n * @copyright 2022 Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseClass from './base_recorder';\nimport Modal from 'tiny_recordrtc/modal';\nimport ModalRegistry from 'core/modal_registry';\nimport {component} from 'tiny_recordrtc/common';\n\nexport default class Video extends BaseClass {\n configurePlayer() {\n return this.modalRoot.querySelector('video');\n }\n\n getSupportedTypes() {\n return [\n 'video/webm;codecs=vp9,opus',\n 'video/webm;codecs=h264,opus',\n 'video/webm;codecs=vp8,opus',\n ];\n\n }\n\n getParsedRecordingOptions() {\n return {\n audioBitsPerSecond: parseInt(this.config.audiobitrate),\n videoBitsPerSecond: parseInt(this.config.videobitrate)\n };\n }\n\n getMediaConstraints() {\n return {\n audio: true,\n video: {\n width: {\n ideal: 640,\n },\n height: {\n ideal: 480,\n },\n },\n };\n }\n\n playOnCapture() {\n // Play the recording back on capture.\n return true;\n }\n\n getRecordingType() {\n return 'video';\n }\n\n getTimeLimit() {\n return this.config.videotimelimit;\n }\n\n getEmbedTemplateName() {\n return 'tiny_recordrtc/embed_video';\n }\n\n getFileName(prefix) {\n return `${prefix}-video.webm`;\n }\n\n static getModalClass() {\n const modalType = `${component}/video_recorder`;\n const registration = ModalRegistry.get(modalType);\n if (registration) {\n return registration.module;\n }\n\n const VideoModal = class extends Modal {\n static TYPE = modalType;\n static TEMPLATE = `${component}/video_recorder`;\n };\n\n ModalRegistry.register(VideoModal.TYPE, VideoModal, VideoModal.TEMPLATE);\n return VideoModal;\n }\n}\n"],"names":["Video","BaseClass","configurePlayer","this","modalRoot","querySelector","getSupportedTypes","getParsedRecordingOptions","audioBitsPerSecond","parseInt","config","audiobitrate","videoBitsPerSecond","videobitrate","getMediaConstraints","audio","video","width","ideal","height","playOnCapture","getRecordingType","getTimeLimit","videotimelimit","getEmbedTemplateName","getFileName","prefix","modalType","component","registration","ModalRegistry","get","module","VideoModal","Modal","register","TYPE","TEMPLATE"],"mappings":"4qBA4BqBA,cAAcC,uBAC/BC,yBACWC,KAAKC,UAAUC,cAAc,SAGxCC,0BACW,CACH,6BACA,8BACA,8BAKRC,kCACW,CACHC,mBAAoBC,SAASN,KAAKO,OAAOC,cACzCC,mBAAoBH,SAASN,KAAKO,OAAOG,eAIjDC,4BACW,CACHC,OAAO,EACPC,MAAO,CACHC,MAAO,CACHC,MAAO,KAEXC,OAAQ,CACJD,MAAO,OAMvBE,uBAEW,EAGXC,yBACW,QAGXC,sBACWnB,KAAKO,OAAOa,eAGvBC,6BACW,6BAGXC,YAAYC,wBACEA,8DAIJC,oBAAeC,qCACfC,aAAeC,wBAAcC,IAAIJ,cACnCE,oBACOA,aAAaG,aAGlBC,mCAAa,cAAcC,wBACfP,uDACOC,6EAGXO,SAASF,WAAWG,KAAMH,WAAYA,WAAWI,UACxDJ"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/recordrtc/amd/src/audio_recorder.js b/lib/editor/tiny/plugins/recordrtc/amd/src/audio_recorder.js new file mode 100644 index 00000000000..9e4723e3e56 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/src/audio_recorder.js @@ -0,0 +1,84 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Tiny Record RTC - audio recorder configuration. + * + * @module tiny_recordrtc/audio + * @copyright 2022 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import BaseClass from './base_recorder'; +import Modal from './modal'; +import ModalRegistry from 'core/modal_registry'; +import {component} from 'tiny_recordrtc/common'; + +export default class Audio extends BaseClass { + configurePlayer() { + return this.modalRoot.querySelector('audio'); + } + + getSupportedTypes() { + return [ + 'audio/webm;codecs=opus', + 'audio/ogg;codecs=opus', + ]; + } + + getParsedRecordingOptions() { + return { + audioBitsPerSecond: parseInt(this.config.audiobitrate), + }; + } + + getMediaConstraints() { + return { + audio: true, + }; + } + + getRecordingType() { + return 'audio'; + } + + getTimeLimit() { + return this.config.audiotimelimit; + } + + getEmbedTemplateName() { + return 'tiny_recordrtc/embed_audio'; + } + + getFileName(prefix) { + return `${prefix}-audio.ogg`; + } + + static getModalClass() { + const modalType = `${component}/audio_recorder`; + const registration = ModalRegistry.get(modalType); + if (registration) { + return registration.module; + } + + const AudioModal = class extends Modal { + static TYPE = modalType; + static TEMPLATE = `${component}/audio_recorder`; + }; + + ModalRegistry.register(AudioModal.TYPE, AudioModal, AudioModal.TEMPLATE); + return AudioModal; + } +} diff --git a/lib/editor/tiny/plugins/recordrtc/amd/src/base_recorder.js b/lib/editor/tiny/plugins/recordrtc/amd/src/base_recorder.js new file mode 100644 index 00000000000..eee2ffe18eb --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/src/base_recorder.js @@ -0,0 +1,840 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . +// + +/** + * Tiny Record RTC type. + * + * @module tiny_recordrtc/recording/base + * @copyright 2022 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {get_string as getString, get_strings as getStrings} from 'core/str'; +import {component} from './common'; +import Pending from 'core/pending'; +import {getData} from './options'; +import uploadFile from 'editor_tiny/uploader'; +import {add as addToast} from 'core/toast'; +import * as ModalEvents from 'core/modal_events'; +import * as ModalFactory from 'core/modal_factory'; +import * as Templates from 'core/templates'; +import {saveCancelPromise} from 'core/notification'; +import {prefetchStrings, prefetchTemplates} from 'core/prefetch'; + +/** + * The RecordRTC base class for audio, video, and any other future types + */ +export default class { + + stopRequested = false; + + /** + * Constructor for the RecordRTC class + * + * @param {TinyMCE} editor The Editor to which the content will be inserted + * @param {Modal} modal The Moodle Modal that contains the interface used for recording + */ + constructor(editor, modal) { + this.ready = false; + + if (!this.checkAndWarnAboutBrowserCompatibility()) { + return; + } + + this.editor = editor; + this.config = getData(editor).params; + this.modal = modal; + this.modalRoot = modal.getRoot()[0]; + this.startStopButton = this.modalRoot.querySelector('button[data-action="startstop"]'); + this.uploadButton = this.modalRoot.querySelector('button[data-action="upload"]'); + + // Disable the record button untilt he stream is acquired. + this.setRecordButtonState(false); + + this.player = this.configurePlayer(); + this.registerEventListeners(); + this.ready = true; + + this.captureUserMedia(); + this.prefetchContent(); + } + + /** + * Check whether the browser is compatible. + * + * @returns {boolean} + */ + isReady() { + return this.ready; + } + + // Disable eslint's valid-jsdoc rule as the following methods are abstract and mnust be overridden by the child class. + + /* eslint-disable valid-jsdoc, no-unused-vars */ + + /** + * Get the Player element for this type. + * + * @returns {HTMLElement} The player element, typically an audio or video tag. + */ + configurePlayer() { + throw new Error(`configurePlayer() must be implemented in ${this.constructor.name}`); + } + + /** + * Get the list of supported mimetypes for this recorder. + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/isTypeSupported} + * + * @returns {string[]} The list of supported mimetypes. + */ + getSupportedTypes() { + throw new Error(`getSupportedTypes() must be implemented in ${this.constructor.name}`); + } + + /** + * Get any recording options passed into the MediaRecorder. + * Please note that the mimeType will be fetched from {@link getSupportedTypes()}. + * + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder#options} + * @returns {Object} + */ + getRecordingOptions() { + throw new Error(`getRecordingOptions() must be implemented in ${this.constructor.name}`); + } + + /** + * Get a filename for the generated file. + * + * Typically this function will take a prefix and add a type-specific suffix such as the extension to it. + * + * @param {string} prefix The prefix for the filename generated by the recorder. + * @returns {string} + */ + getFileName(prefix) { + throw new Error(`getFileName() must be implemented in ${this.constructor.name}`); + } + + /** + * Get a list of constraints as required by the getUserMedia() function. + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#constraints} + * + * @returns {Object} + */ + getMediaConstraints() { + throw new Error(`getMediaConstraints() must be implemented in ${this.constructor.name}`); + } + + /** + * Whether to start playing the recording as it is captured. + * @returns {boolean} Whether to start playing the recording as it is captured. + */ + playOnCapture() { + return false; + } + + /** + * Get the time limit for this recording type. + * + * @returns {number} The time limit in seconds. + */ + getTimeLimit() { + throw new Error(`getTimeLimit() must be implemented in ${this.constructor.name}`); + } + + /** + * Get the name of the template used when embedding the URL in the editor content. + * + * @returns {string} + */ + getEmbedTemplateName() { + throw new Error(`getEmbedTemplateName() must be implemented in ${this.constructor.name}`); + } + + /** + * Fetch the Class of the Modal to be displayed. + * + * @returns {Modal} + */ + static getModalClass() { + throw new Error(`getModalClass() must be implemented in ${this.constructor.name}`); + } + + /* eslint-enable valid-jsdoc, no-unused-vars */ + + /** + * Get the options for the MediaRecorder. + * + * @returns {object} The options for the MediaRecorder instance. + */ + getParsedRecordingOptions() { + const types = this.getSupportedTypes(); + const options = this.getParsedRecordingOptions(); + const compatTypes = types.filter((type) => window.MediaRecorder.isTypeSupported(type)); + + if (compatTypes.length !== 0) { + options.mimeType = compatTypes[0]; + } + + return options; + } + + /** + * Start capturing the User Media and handle success or failure of the capture. + */ + async captureUserMedia() { + try { + const stream = await navigator.mediaDevices.getUserMedia(this.getMediaConstraints()); + this.handleCaptureSuccess(stream); + } catch (error) { + this.handleCaptureFailure(error); + } + } + + /** + * Prefetch some of the content that will be used in the UI. + * + * Note: not all of the strings used are pre-fetched. + * Some of the strings will be fetched because their template is used. + */ + prefetchContent() { + prefetchStrings(component, [ + 'uploading', + 'recordagain_title', + 'recordagain_desc', + 'discard_title', + 'discard_desc', + 'confirm_yes', + 'recordinguploaded', + 'maxfilesizehit', + 'maxfilesizehit_title', + 'uploadfailed', + ]); + + prefetchTemplates([ + this.getEmbedTemplateName(), + 'tiny_recordrtc/timeremaining', + ]); + } + + /** + * Display an error message to the user. + * + * @param {Promise} title The error title + * @param {Promise} content The error message + * @returns {Promise} + */ + async displayAlert(title, content) { + const pendingPromise = new Pending('core/confirm:alert'); + const ModalFactory = await import('core/modal_factory'); + const modal = await ModalFactory.create({ + type: ModalFactory.types.ALERT, + title: title, + body: content, + removeOnClose: true, + }); + + modal.show(); + pendingPromise.resolve(); + + return modal; + } + + /** + * Handle successful capture of the User Media. + * + * @param {MediaStream} stream The stream as captured by the User Media. + */ + handleCaptureSuccess(stream) { + // Set audio player source to microphone stream. + this.player.srcObject = stream; + + if (this.playOnCapture()) { + // Mute audio, distracting while recording. + this.player.muted = true; + + this.player.play(); + } + + this.stream = stream; + this.setupPlayerSource(); + this.setRecordButtonState(true); + } + + /** + * Setup the player to use the stream as a source. + */ + setupPlayerSource() { + if (!this.player.srcObject) { + this.player.srcObject = this.stream; + + // Mute audio, distracting while recording. + this.player.muted = true; + + this.player.play(); + } + } + + /** + * Enable the record button. + * + * @param {boolean|null} enabled Set the button state + */ + setRecordButtonState(enabled) { + this.startStopButton.disabled = !enabled; + } + + /** + * Configure button visibility for the record button. + * + * @param {boolean} visible Set the visibility of the button. + */ + setRecordButtonVisibility(visible) { + const container = this.getButtonContainer('start-stop'); + container.classList.toggle('hide', !visible); + } + + /** + * Enable the upload button. + * + * @param {boolean|null} enabled Set the button state + */ + setUploadButtonState(enabled) { + this.uploadButton.disabled = !enabled; + } + + /** + * Configure button visibility for the upload button. + * + * @param {boolean} visible Set the visibility of the button. + */ + setUploadButtonVisibility(visible) { + const container = this.getButtonContainer('upload'); + container.classList.toggle('hide', !visible); + } + /** + * Handle failure to capture the User Media. + * + * @param {Error} error + */ + handleCaptureFailure(error) { + // Changes 'CertainError' -> 'gumcertain' to match language string names. + var subject = `gum${error.name.replace('Error', '').toLowerCase()}`; + this.displayAlert( + getString(`${subject}_title`, component), + getString(subject, component) + ); + } + + /** + * Close the modal and stop recording. + */ + close() { + // Closing the modal will destroy it and remove it from the DOM. + // It will also stop the recording via the hidden Modal Event. + this.modal.hide(); + } + + /** + * Register event listeners for the modal. + */ + registerEventListeners() { + this.modalRoot.addEventListener('click', this.handleModalClick.bind(this)); + this.modal.getRoot().on(ModalEvents.outsideClick, this.outsideClickHandler.bind(this)); + this.modal.getRoot().on(ModalEvents.hidden, () => { + this.cleanupStream(); + this.requestRecordingStop(); + }); + } + + /** + * Prevent the Modal from closing when recording is on process. + * + * @param {MouseEvent} event The click event + */ + async outsideClickHandler(event) { + if (this.isRecording()) { + // The user is recording. + // Do not distract with a confirmation, just prevent closing. + event.preventDefault(); + } else if (this.hasData()) { + // If there is a blobsize then there is data that may be lost. + // Ask the user to confirm they want to close the modal. + // We prevent default here, and then close the modal if they confirm. + event.preventDefault(); + + try { + await saveCancelPromise( + await getString("discard_title", component), + await getString("discard_desc", component), + await getString("confirm_yes", component), + ); + this.modal.hide(); + } catch (error) { + // Do nothing, the modal will not close. + } + } + } + + /** + * Handle a click within the Modal. + * + * @param {MouseEvent} event The click event + */ + handleModalClick(event) { + const button = event.target.closest('button'); + if (button && button.dataset.action) { + const action = button.dataset.action; + if (action === 'startstop') { + this.handleRecordingStartStopRequested(); + } + + if (action === 'upload') { + this.uploadRecording(); + } + } + } + + /** + * Handle the click event for the recording start/stop button. + */ + handleRecordingStartStopRequested() { + if (this.mediaRecorder?.state === 'recording') { + this.requestRecordingStop(); + } else { + this.startRecording(); + } + } + + /** + * Handle the media stream after it has finished. + */ + async onMediaStopped() { + // Set source of audio player. + this.blob = new Blob(this.data.chunks, { + type: this.mediaRecorder.mimeType + }); + this.player.srcObject = null; + this.player.src = URL.createObjectURL(this.blob); + + // Change the label to "Record again". + this.setRecordButtonTextFromString('recordagain'); + + // Show audio player with controls enabled, and unmute. + this.player.muted = false; + this.player.controls = true; + this.getButtonContainer('player')?.classList.toggle('hide', false); + + // Show upload button. + this.setUploadButtonVisibility(true); + this.setUploadButtonState(true); + } + + /** + * Upload the recording and insert it into the editor content. + */ + async uploadRecording() { + // Trigger error if no recording has been made. + if (this.data.chunks.length === 0) { + this.displayAlert('norecordingfound'); + return; + } + + const fileName = this.getFileName((Math.random() * 1000).toString().replace('.', '')); + + // Upload recording to server. + try { + // Once uploading starts, do not allow any further changes to the recording. + this.setRecordButtonVisibility(false); + + // Disable the upload button. + this.setUploadButtonState(false); + + // Upload the recording. + const fileURL = await uploadFile(this.editor, 'media', this.blob, fileName, (progress) => { + this.setUploadButtonTextProgress(progress); + }); + this.insertMedia(fileURL); + this.close(); + addToast(await getString('recordinguploaded', component)); + } catch (error) { + // Show a toast and unhide the button. + this.setUploadButtonState(true); + + addToast(await getString('uploadfailed', component, {error}), { + type: 'error', + }); + + } + } + + /** + * Helper to get the container that a button is in. + * + * @param {string} purpose The button purpose + * @returns {HTMLElement} + */ + getButtonContainer(purpose) { + return this.modalRoot.querySelector(`[data-purpose="${purpose}-container"]`); + } + + /** + * Check whether the browser is compatible with capturing media. + * + * @returns {boolean} + */ + static isBrowserCompatible() { + return this.checkSecure() && this.hasUserMedia(); + } + + static async display(editor) { + const ModalClass = this.getModalClass(); + const modal = await ModalFactory.create({ + type: ModalClass.TYPE, + templateContext: {}, + large: true, + }); + + // Set up the VideoRecorder. + const recorder = new this(editor, modal); + if (recorder.isReady()) { + modal.show(); + } + return modal; + } + + /** + * Check whether the browser is compatible with capturing media, and display a warning if not. + * + * @returns {boolean} + */ + checkAndWarnAboutBrowserCompatibility() { + if (!this.constructor.checkSecure()) { + getStrings(['insecurealert_title', 'insecurealert'].map((key) => ({key, component}))) + .then(([title, message]) => addToast(message, {title, type: 'error'})) + .catch(); + return false; + } + + if (!this.constructor.hasUserMedia) { + getStrings(['nowebrtc_title', 'nowebrtc'].map((key) => ({key, component}))) + .then(([title, message]) => addToast(message, {title, type: 'error'})) + .catch(); + return false; + } + + return true; + } + + /** + * Check whether the browser supports WebRTC. + * + * @returns {boolean} + */ + static hasUserMedia() { + return (navigator.mediaDevices && window.MediaRecorder); + } + + /** + * Check whether the hostname is either hosted over SSL, or from a valid localhost hostname. + * + * The UserMedia API can only be used in secure contexts as noted. + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#privacy_and_security} + * + * @returns {boolean} Whether the plugin can be loaded. + */ + static checkSecure() { + // Note: We can now use window.isSecureContext. + // https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#feature_detection + // https://developer.mozilla.org/en-US/docs/Web/API/isSecureContext + return window.isSecureContext; + } + + /** + * Update the content of the stop recording button timer. + */ + async setStopRecordingButton() { + const {html, js} = await Templates.renderForPromise('tiny_recordrtc/timeremaining', this.getTimeRemaining()); + Templates.replaceNodeContents(this.startStopButton, html, js); + this.buttonTimer = setInterval(this.updateRecordButtonTime.bind(this), 500); + } + + /** + * Update the time on the stop recording button. + */ + updateRecordButtonTime() { + const {remaining, minutes, seconds} = this.getTimeRemaining(); + if (remaining < 0) { + this.requestRecordingStop(); + } else { + this.startStopButton.querySelector('[data-type="minutes"]').textContent = minutes; + this.startStopButton.querySelector('[data-type="seconds"]').textContent = seconds; + } + } + + /** + * Set the text of the record button using a language string. + * + * @param {string} string The string identifier + */ + async setRecordButtonTextFromString(string) { + this.startStopButton.textContent = await getString(string, component); + } + + /** + * Set the upload button text progress. + * + * @param {number} progress The progress + */ + async setUploadButtonTextProgress(progress) { + this.uploadButton.textContent = await getString('uploading', component, { + progress: Math.round(progress * 100) / 100, + }); + } + + async resetUploadButtonText() { + this.uploadButton.textContent = await getString('upload', component); + } + + /** + * Clear the timer for the stop recording button. + */ + clearButtonTimer() { + if (this.buttonTimer) { + clearInterval(this.buttonTimer); + } + this.buttonTimer = null; + } + + /** + * Get the time remaining for the recording. + * + * @returns {Object} The minutes and seconds remaining. + */ + getTimeRemaining() { + // All times are in milliseconds + const now = new Date().getTime(); + const remaining = Math.floor(this.getTimeLimit() - ((now - this.startTime) / 1000)); + + const formatter = new Intl.NumberFormat(navigator.language, {minimumIntegerDigits: 2}); + const seconds = formatter.format(remaining % 60); + const minutes = formatter.format(Math.floor((remaining - seconds) / 60)); + return { + remaining, + minutes, + seconds, + }; + } + + /** + * Get the maximum file size that can be uploaded. + * + * @returns {number} The max byte size + */ + getMaxUploadSize() { + return this.config.maxrecsize; + } + + /** + * Stop the recording. + * Please note that this should only stop the recording. + * Anything related to processing the recording should be handled by the + * mediaRecorder's stopped event handler which is processed after it has stopped. + */ + requestRecordingStop() { + if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') { + this.stopRequested = true; + } else { + // There is no recording to stop, but the stream must still be cleaned up. + this.cleanupStream(); + } + } + + stopRecorder() { + this.mediaRecorder.stop(); + + // Unmute the player so that the audio is heard during playback. + this.player.muted = false; + } + + /** + * Clean up the stream. + * + * This involves stopping any track which is still active. + */ + cleanupStream() { + if (this.stream) { + this.stream.getTracks() + .filter((track) => track.readyState !== 'ended') + .forEach((track) => track.stop()); + } + } + + /** + * Handle the mediaRecorder `stop` event. + */ + handleStopped() { + // Handle the stream data. + this.onMediaStopped(); + + // Clear the button timer. + this.clearButtonTimer(); + } + + /** + * Handle the mediaRecorder `start` event. + * + * This event is called when the recording starts. + */ + handleStarted() { + this.startTime = new Date().getTime(); + this.setStopRecordingButton(); + } + + /** + * Handle the mediaRecorder `dataavailable` event. + * + * @param {Event} event + */ + handleDataAvailable(event) { + if (this.isRecording()) { + const newSize = this.data.blobSize + event.data.size; + // Recording stops when either the maximum upload size is reached, or the time limit expires. + // The time limit is checked in the `updateButtonTime` function. + if (newSize >= this.getMaxUploadSize()) { + this.stopRecorder(); + this.displayFileLimitHitMessage(); + } else { + // Push recording slice to array. + this.data.chunks.push(event.data); + + // Size of all recorded data so far. + this.data.blobSize = newSize; + + if (this.stopRequested) { + this.stopRecorder(); + } + } + } + } + + async displayFileLimitHitMessage() { + addToast(await getString('maxfilesizehit', component), { + title: await getString('maxfilesizehit_title', component), + type: 'error', + }); + } + + /** + * Check whether the recording is in progress. + * + * @returns {boolean} + */ + isRecording() { + return this.mediaRecorder?.state === 'recording'; + } + + /** + * Whether any data has been recorded. + * + * @returns {boolean} + */ + hasData() { + return !!this.data?.blobSize; + } + + /** + * Start the recording + */ + async startRecording() { + if (this.mediaRecorder) { + // Stop the existing recorder if it exists. + if (this.isRecording()) { + this.mediaRecorder.stop(); + } + + if (this.hasData()) { + const resetRecording = await this.recordAgainConfirmation(); + if (!resetRecording) { + // User cancelled at the confirmation to reset the data, so exit early. + return; + } + this.setUploadButtonVisibility(false); + } + + this.mediaRecorder = null; + } + + // The options for the recording codecs and bitrates. + this.mediaRecorder = new MediaRecorder(this.stream, this.getParsedRecordingOptions()); + + this.mediaRecorder.addEventListener('dataavailable', this.handleDataAvailable.bind(this)); + this.mediaRecorder.addEventListener('stop', this.handleStopped.bind(this)); + this.mediaRecorder.addEventListener('start', this.handleStarted.bind(this)); + + this.data = { + chunks: [], + blobSize: 0 + }; + this.setupPlayerSource(); + this.stopRequested = false; + + // Capture in 50ms chunks. + this.mediaRecorder.start(50); + } + + /** + * Confirm whether the user wants to reset the existing recoring. + * + * @returns {Promise} Whether the user confirmed the reset. + */ + async recordAgainConfirmation() { + try { + await saveCancelPromise( + await getString("recordagain_title", component), + await getString("recordagain_desc", component), + await getString("confirm_yes", component) + ); + return true; + } catch { + return false; + } + } + + /** + * Insert the HTML to embed the recording into the editor content. + * + * @param {string} source The URL to view the media. + */ + async insertMedia(source) { + const {html} = await Templates.renderForPromise( + this.getEmbedTemplateName(), + this.getEmbedTemplateContext({ + source, + }) + ); + this.editor.insertContent(html); + } + + /** + * Add or modify the template parameters for the specified type. + * + * @param {Object} templateContext The Tempalte context to use + * @returns {Object} The finalised template context + */ + getEmbedTemplateContext(templateContext) { + return templateContext; + } +} diff --git a/lib/editor/tiny/plugins/recordrtc/amd/src/commands_audio.js b/lib/editor/tiny/plugins/recordrtc/amd/src/commands_audio.js new file mode 100644 index 00000000000..a95080c9e5a --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/src/commands_audio.js @@ -0,0 +1,62 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Tiny Record RTC - record audio command. + * + * @module tiny_recordrtc/recordAudioCommands + * @copyright 2022, Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {get_string as getString} from 'core/str'; +import {getButtonImage} from 'editor_tiny/utils'; +import { + audioButtonName, + component +} from './common'; +import Recorder from './audio_recorder'; + +export default async() => { + if (!Recorder.isBrowserCompatible()) { + // The browser doesn't support the plugin, so just don't show it. + return () => false; + } + + const [ + audioButtonTitle, + audio, + ] = await Promise.all([ + getString('audiobuttontitle', component), + getButtonImage('audio', component), + ]); + + return (editor) => { + const icon = 'audio'; + editor.ui.registry.addIcon(icon, audio.html); + + editor.ui.registry.addButton(audioButtonName, { + icon, + tooltip: audioButtonTitle, + onAction: () => Recorder.display(editor), + }); + + editor.ui.registry.addMenuItem(audioButtonName, { + icon, + text: audioButtonTitle, + onAction: () => Recorder.display(editor), + }); + }; +}; diff --git a/lib/editor/tiny/plugins/recordrtc/amd/src/commands_video.js b/lib/editor/tiny/plugins/recordrtc/amd/src/commands_video.js new file mode 100644 index 00000000000..91000223c5b --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/src/commands_video.js @@ -0,0 +1,62 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Tiny Record RTC - record video command. + * + * @module tiny_recordrtc/recordVideoCommands + * @copyright 2022, Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {get_string as getString} from 'core/str'; +import {getButtonImage as getVideoIcon} from 'editor_tiny/utils'; +import { + videoButtonName, + component +} from './common'; +import Recorder from './video_recorder'; + +export default async() => { + if (!Recorder.isBrowserCompatible()) { + // The browser doesn't support the plugin, so just don't show it. + return () => false; + } + + const [ + videoButtonTitle, + buttonImage, + ] = await Promise.all([ + getString('videobuttontitle', component), + getVideoIcon('video', component), + ]); + + return (editor) => { + let icon = 'video'; + editor.ui.registry.addIcon(icon, buttonImage.html); + + editor.ui.registry.addButton(videoButtonName, { + icon, + tooltip: videoButtonTitle, + onAction: () => Recorder.display(editor), + }); + + editor.ui.registry.addMenuItem(videoButtonName, { + icon, + text: videoButtonTitle, + onAction: () => Recorder.display(editor), + }); + }; +}; diff --git a/lib/editor/tiny/plugins/recordrtc/amd/src/common.js b/lib/editor/tiny/plugins/recordrtc/amd/src/common.js new file mode 100644 index 00000000000..8e9ebf087e9 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/src/common.js @@ -0,0 +1,29 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Tiny Record RTC common values. + * + * @module tiny_recordrtc/common + * @copyright 2022, Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +export default { + pluginName: 'tiny_recordrtc/plugin', + component: 'tiny_recordrtc', + audioButtonName: 'tiny_recordrtc_audio', + videoButtonName: 'tiny_recordrtc_video' +}; diff --git a/lib/editor/tiny/plugins/recordrtc/amd/src/configuration.js b/lib/editor/tiny/plugins/recordrtc/amd/src/configuration.js new file mode 100644 index 00000000000..d4d8b93053e --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/src/configuration.js @@ -0,0 +1,84 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Tiny Record RTC configuration. + * + * @module tiny_recordrtc/configuration + * @copyright 2022, Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import { + audioButtonName, + videoButtonName +} from './common'; +import { + addMenubarItem, +} from 'editor_tiny/utils'; + +const configureMenu = (menu) => { + const items = menu.insert.items.split(' '); + const inserted = items.some((item, index) => { + // Append after the media or video button. + if (item.match(/(media|video)\b/)) { + items.splice(index + 1, 0, audioButtonName, videoButtonName); + return true; + } + + return false; + }); + + if (inserted) { + menu.insert.items = items.join(' '); + } else { + addMenubarItem(menu, 'insert', `${audioButtonName} ${videoButtonName}`); + } + + return menu; +}; + +const configureToolbar = (toolbar) => { + // The toolbar contains an array of named sections. + // The Moodle integration ensures that there is a section called 'content'. + + + return toolbar.map((section) => { + if (section.name === 'content') { + const inserted = section.items.some((item, index) => { + // Append after the media or video button. + if (item.match(/(media|video)\b/)) { + section.items.splice(index + 1, 0, audioButtonName, videoButtonName); + return true; + } + return false; + }); + + if (!inserted) { + section.items.unshift(audioButtonName, videoButtonName); + } + } + + return section; + }); +}; + +export const configure = (instanceConfig) => { + // Update the instance configuration to add the Media menu option to the menus and toolbars and upload_handler. + return { + toolbar: configureToolbar(instanceConfig.toolbar), + menu: configureMenu(instanceConfig.menu), + }; +}; diff --git a/lib/editor/tiny/plugins/recordrtc/amd/src/modal.js b/lib/editor/tiny/plugins/recordrtc/amd/src/modal.js new file mode 100644 index 00000000000..21980e1b7ff --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/src/modal.js @@ -0,0 +1,39 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Abstract Recording Modal for TinyMCE's RecordRTC plugin. + * + * @module tiny_recordrtc/modal + * @copyright 2022 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Modal from 'core/modal'; + +export default class extends Modal { + registerEventListeners() { + // Remove this Modal when it is closed. + // This must be called before registering any other event listeners. + this.setRemoveOnClose(true); + + // Call the parent registration. + super.registerEventListeners(); + + // Register to close on save/cancel. + this.registerCloseOnSave(); + this.registerCloseOnCancel(); + } +} diff --git a/lib/editor/tiny/plugins/recordrtc/amd/src/options.js b/lib/editor/tiny/plugins/recordrtc/amd/src/options.js new file mode 100644 index 00000000000..4f16258ff3f --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/src/options.js @@ -0,0 +1,36 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Options helper for Tiny Record RTC plugin. + * + * @module tiny_recordrtc/options + * @copyright 2022, Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {pluginName} from './common'; + +const dataName = `${pluginName}:data`; + +export const register = (editor) => { + const registerOption = editor.options.register; + + registerOption(dataName, { + processor: 'object', + }); +}; + +export const getData = (editor) => editor.options.get(dataName); diff --git a/lib/editor/tiny/plugins/recordrtc/amd/src/plugin.js b/lib/editor/tiny/plugins/recordrtc/amd/src/plugin.js new file mode 100644 index 00000000000..1a72bce8430 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/src/plugin.js @@ -0,0 +1,62 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Tiny Record RTC plugin for Moodle. + * + * @module tiny_recordrtc/plugin + * @copyright 2022, Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +import {getTinyMCE} from 'editor_tiny/loader'; +import {getPluginMetadata} from 'editor_tiny/utils'; +import getSetupAudioCommands from './commands_audio'; +import getSetupVideoCommands from './commands_video'; +import * as Configuration from './configuration'; +import * as Options from './options'; +import { + component, + pluginName +} from './common'; + +export default new Promise(async(resolve) => { + const [ + tinyMCE, + setupAudioCommands, + setupVideoCommands, + pluginMetadata, + ] = await Promise.all([ + getTinyMCE(), + getSetupAudioCommands(), + getSetupVideoCommands(), + getPluginMetadata(component, pluginName), + ]); + + tinyMCE.PluginManager.add(`${component}/plugin`, (editor) => { + // Register options. + Options.register(editor); + + // Setup the Commands (buttons, menu items, and so on) for video. + setupVideoCommands(editor); + + // Setup the Commands (buttons, menu items, and so on) for audio. + setupAudioCommands(editor); + + return pluginMetadata; + }); + + // Resolve the Media Plugin and include configuration. + resolve([`${component}/plugin`, Configuration]); +}); diff --git a/lib/editor/tiny/plugins/recordrtc/amd/src/video_recorder.js b/lib/editor/tiny/plugins/recordrtc/amd/src/video_recorder.js new file mode 100644 index 00000000000..85eae290af8 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/amd/src/video_recorder.js @@ -0,0 +1,100 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Tiny Record RTC - Video recorder configuration. + * + * @module tiny_recordrtc/video + * @copyright 2022 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import BaseClass from './base_recorder'; +import Modal from 'tiny_recordrtc/modal'; +import ModalRegistry from 'core/modal_registry'; +import {component} from 'tiny_recordrtc/common'; + +export default class Video extends BaseClass { + configurePlayer() { + return this.modalRoot.querySelector('video'); + } + + getSupportedTypes() { + return [ + 'video/webm;codecs=vp9,opus', + 'video/webm;codecs=h264,opus', + 'video/webm;codecs=vp8,opus', + ]; + + } + + getParsedRecordingOptions() { + return { + audioBitsPerSecond: parseInt(this.config.audiobitrate), + videoBitsPerSecond: parseInt(this.config.videobitrate) + }; + } + + getMediaConstraints() { + return { + audio: true, + video: { + width: { + ideal: 640, + }, + height: { + ideal: 480, + }, + }, + }; + } + + playOnCapture() { + // Play the recording back on capture. + return true; + } + + getRecordingType() { + return 'video'; + } + + getTimeLimit() { + return this.config.videotimelimit; + } + + getEmbedTemplateName() { + return 'tiny_recordrtc/embed_video'; + } + + getFileName(prefix) { + return `${prefix}-video.webm`; + } + + static getModalClass() { + const modalType = `${component}/video_recorder`; + const registration = ModalRegistry.get(modalType); + if (registration) { + return registration.module; + } + + const VideoModal = class extends Modal { + static TYPE = modalType; + static TEMPLATE = `${component}/video_recorder`; + }; + + ModalRegistry.register(VideoModal.TYPE, VideoModal, VideoModal.TEMPLATE); + return VideoModal; + } +} diff --git a/lib/editor/tiny/plugins/recordrtc/classes/plugininfo.php b/lib/editor/tiny/plugins/recordrtc/classes/plugininfo.php new file mode 100644 index 00000000000..8eba29befc5 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/classes/plugininfo.php @@ -0,0 +1,122 @@ +. + +namespace tiny_recordrtc; + +use context; +use editor_tiny\editor; +use editor_tiny\plugin; +use editor_tiny\plugin_with_buttons; +use editor_tiny\plugin_with_configuration; +use editor_tiny\plugin_with_menuitems; + +/** + * Tiny RecordRTC plugin. + * + * @package tiny_recordrtc + * @copyright 2022 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class plugininfo extends plugin implements plugin_with_buttons, plugin_with_menuitems, plugin_with_configuration { + /** + * Whether the plugin is enabled + * + * @param context $context The context that the editor is used within + * @param array $options The options passed in when requesting the editor + * @param array $fpoptions The filepicker options passed in when requesting the editor + * @param editor $editor The editor instance in which the plugin is initialised + * @return boolean + */ + public static function is_enabled( + context $context, + array $options, + array $fpoptions, + ?editor $editor = null + ): bool { + // Disabled if: + // - Not logged in or guest. + // - Files are not allowed. + // - Only URL are supported. + $canhavefiles = !empty($options['maxfiles']); + $canhaveexternalfiles = !empty($options['return_types']) && ($options['return_types'] & FILE_EXTERNAL); + return isloggedin() && !isguestuser() && $canhavefiles && $canhaveexternalfiles; + } + + public static function get_available_buttons(): array { + return [ + 'tiny_recordrtc/tiny_recordrtc_image', + ]; + } + + public static function get_available_menuitems(): array { + return [ + 'tiny_recordrtc/tiny_recordrtc_image', + ]; + } + + public static function get_plugin_configuration_for_context( + context $context, + array $options, + array $fpoptions, + ?editor $editor = null + ): array { + $sesskey = sesskey(); + $allowedtypes = get_config('tiny_recordrtc', 'allowedtypes'); + $audiobitrate = get_config('tiny_recordrtc', 'audiobitrate'); + $videobitrate = get_config('tiny_recordrtc', 'videobitrate'); + $audiotimelimit = get_config('tiny_recordrtc', 'audiotimelimit'); + $videotimelimit = get_config('tiny_recordrtc', 'videotimelimit'); + + // Update $allowedtypes to account for capabilities. + $audioallowed = $allowedtypes === 'audio' || $allowedtypes === 'both'; + $videoallowed = $allowedtypes === 'video' || $allowedtypes === 'both'; + $audioallowed = $audioallowed && has_capability('tiny/recordrtc:recordaudio', $context); + $videoallowed = $videoallowed && has_capability('tiny/recordrtc:recordvideo', $context); + if ($audioallowed && $videoallowed) { + $allowedtypes = 'both'; + } else if ($audioallowed) { + $allowedtypes = 'audio'; + } else if ($videoallowed) { + $allowedtypes = 'video'; + } else { + $allowedtypes = ''; + } + + $maxrecsize = get_max_upload_file_size(); + if (!empty($options['maxbytes'])) { + $maxrecsize = min($maxrecsize, $options['maxbytes']); + } + $params = [ + 'contextid' => $context->id, + 'sesskey' => $sesskey, + 'allowedtypes' => $allowedtypes, + 'audiobitrate' => $audiobitrate, + 'videobitrate' => $videobitrate, + 'audiotimelimit' => $audiotimelimit, + 'videotimelimit' => $videotimelimit, + 'maxrecsize' => $maxrecsize + ]; + + $data = [ + 'params' => $params, + 'fpoptions' => $fpoptions + ]; + + return [ + 'data' => $data + ]; + } +} diff --git a/lib/editor/tiny/plugins/recordrtc/classes/privacy/provider.php b/lib/editor/tiny/plugins/recordrtc/classes/privacy/provider.php new file mode 100644 index 00000000000..7f3575356fa --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/classes/privacy/provider.php @@ -0,0 +1,30 @@ +. + +namespace tiny_recordrtc\privacy; + +/** + * Privacy Subsystem implementation for the recordrtc plugin for TinyMCE. + * + * @package tiny_recordrtc + * @copyright 2022 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements \core_privacy\local\metadata\null_provider { + public static function get_reason(): string { + return 'privacy:metadata'; + } +} diff --git a/lib/editor/tiny/plugins/recordrtc/db/access.php b/lib/editor/tiny/plugins/recordrtc/db/access.php new file mode 100644 index 00000000000..f5e84c86d80 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/db/access.php @@ -0,0 +1,44 @@ +. + +/** + * Tiny text editor recordrtc capabilities. + * + * @package tiny_recordrtc + * @copyright 2022 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$capabilities = [ + // Capability to record audio using this plugin. + 'tiny/recordrtc:recordaudio' => [ + 'captype' => 'write', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => [ + 'user' => CAP_ALLOW, + ], + ], + // Capability to record video using this plugin. + 'tiny/recordrtc:recordvideo' => [ + 'captype' => 'write', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => [ + 'user' => CAP_ALLOW, + ], + ], +]; diff --git a/lib/editor/tiny/plugins/recordrtc/lang/en/tiny_recordrtc.php b/lib/editor/tiny/plugins/recordrtc/lang/en/tiny_recordrtc.php new file mode 100644 index 00000000000..1b19595843e --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/lang/en/tiny_recordrtc.php @@ -0,0 +1,87 @@ +. + +/** + * Strings for component 'tiny_recordrtc', language 'en'. + * + * @package tiny_recordrtc + * @copyright 2022 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$string['allowedtypes'] = 'Allowed types'; +$string['allowedtypes_desc'] = 'Which recording buttons should appear in Atto. In addition to this setting, there are also capabilities which can control access to the buttons to particular users in particular contexts.'; +$string['attachrecording'] = 'Attach recording'; +$string['audioandvideo'] = 'Audio and video'; +$string['audiobitrate'] = 'Audio bitrate'; +$string['audiobitrate_desc'] = 'Quality of audio recording (larger number means higher quality)'; +$string['audiobuttontitle'] = 'Record audio'; +$string['audiotimelimit'] = 'Audio time limit in seconds'; +$string['audiotimelimit_desc'] = 'Maximum recording length allowed for the audio clips'; +$string['audiotitle'] = 'Record Audio'; +$string['confirm_yes'] = 'Yes'; +$string['discard_desc'] = 'There is a reacording data or the recording is in process.
Do you want to close the recording?'; +$string['discard_title'] = 'Close confirmation'; +$string['gumabort'] = 'Something strange happened which prevented the webcam/microphone from being used'; +$string['gumabort_title'] = 'Something happened'; +$string['gumnotallowed'] = 'The user must allow the browser access to the webcam/microphone'; +$string['gumnotallowed_title'] = 'Wrong permissions'; +$string['gumnotfound'] = 'There is no input device connected or enabled'; +$string['gumnotfound_title'] = 'Device missing'; +$string['gumnotreadable'] = 'Something is preventing the browser from accessing the webcam/microphone'; +$string['gumnotreadable_title'] = 'Hardware error'; +$string['gumnotsupported'] = 'Your browser does not support recording over an insecure connection and must close the plugin'; +$string['gumnotsupported_title'] = 'No support for insecure connection'; +$string['gumoverconstrained'] = 'The current webcam/microphone can not produce a stream with the required constraints'; +$string['gumoverconstrained_title'] = 'Problem with constraints'; +$string['gumsecurity'] = 'Your browser does not support recording over an insecure connection and must close the plugin'; +$string['gumsecurity_title'] = 'No support for insecure connection'; +$string['gumtype'] = 'Tried to get stream from the webcam/microphone, but no constraints were specified'; +$string['gumtype_title'] = 'No constraints specified'; +$string['insecurealert'] = 'Your browser might not allow this plugin to work unless it is used either over HTTPS or from localhost'; +$string['insecurealert_title'] = 'Insecure connection!'; +$string['maxfilesizehit'] = 'You have attained the maximum size limit for file uploads'; +$string['maxfilesizehit_title'] = 'Recording stopped'; +$string['norecordingfound'] = 'Something appears to have gone wrong, it seems nothing has been recorded'; +$string['norecordingfound_title'] = 'No recording found'; +$string['nowebrtc'] = 'Your browser offers limited or no support for WebRTC technologies yet, and cannot be used with this plugin. Please switch or upgrade your browser'; +$string['nowebrtc_title'] = 'WebRTC not supported'; +$string['onlyaudio'] = 'Audio only'; +$string['onlyvideo'] = 'Video only'; +$string['pluginname'] = 'Tiny Record RTC plugin for Moodle'; +$string['privacy:metadata'] = 'The RecordRTC plugin does not store any personal data.'; +$string['recordagain'] = 'Record again'; +$string['recordagain_desc'] = 'You have already recorded some content. Recording again will remove this content.
Are you sure you want to reset the recording?'; +$string['recordagain_title'] = 'Record again confirmation'; +$string['recordingfailed'] = 'Recording failed, try again'; +$string['recordinguploaded'] = 'Recording uploaded'; +$string['recordrtc:recordaudio'] = 'Record audio directly into the text editor'; +$string['recordrtc:recordvideo'] = 'Record video directly into the text editor'; +$string['startrecording'] = 'Start recording'; +$string['stoprecording'] = 'Stop recording'; +$string['timelimitwarning'] = 'You must enter a number that is greater than 0.'; +$string['uploadaborted'] = 'Upload aborted:'; +$string['uploadfailed'] = 'Upload failed with error: {$a->error}'; +$string['uploadfailed404'] = 'Upload failed: file too large'; +$string['uploading'] = 'Uploading - {$a->progress}%'; +$string['uploadprogress'] = 'completed'; +$string['videobitrate'] = 'Video bitrate'; +$string['videobitrate_desc'] = 'Quality of video recording (larger number means higher quality)'; +$string['videobuttontitle'] = 'Record video'; +$string['videotimelimit'] = 'Video time limit in seconds'; +$string['videotimelimit_desc'] = 'Maximum recording length allowed for the video clips'; +$string['videotitle'] = 'Record Video'; +$string['helplinktext'] = 'Moodle RecordRTC'; diff --git a/lib/editor/tiny/plugins/recordrtc/pix/audio.svg b/lib/editor/tiny/plugins/recordrtc/pix/audio.svg new file mode 100644 index 00000000000..4990920a681 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/pix/audio.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/lib/editor/tiny/plugins/recordrtc/pix/icon.png b/lib/editor/tiny/plugins/recordrtc/pix/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2fc239721049ec24a6372a59e4c34ee9e0cd80a0 GIT binary patch literal 2802 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/editor/tiny/plugins/recordrtc/settings.php b/lib/editor/tiny/plugins/recordrtc/settings.php new file mode 100644 index 00000000000..405722809d1 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/settings.php @@ -0,0 +1,97 @@ +. + +/** + * Settings that allow turning on and off recordrtc features + * + * @package tiny_recordrtc + * @copyright 2022, Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +// Needed for constants. +require_once($CFG->dirroot . '/lib/editor/tiny/plugins/recordrtc/classes/plugininfo.php'); + +$ADMIN->add('editortiny', new admin_category('tiny_recordrtc', new lang_string('pluginname', 'tiny_recordrtc'))); + +if ($ADMIN->fulltree) { + $defaulttimelimit = 120; + + $url = parse_url($CFG->wwwroot); + $hostname = parse_url($CFG->wwwroot, PHP_URL_HOST); + $isvalid = in_array($hostname, ['localhost', '127.0.0.1', '::1']); + $isvalid = $isvalid || preg_match("/^.*\.localhost$/", $hostname); + + if (!$isvalid && $url['scheme'] !== 'https') { + $warning = html_writer::div(get_string('insecurealert', 'tiny_recordrtc'), 'box py-3 generalbox alert alert-danger'); + $setting = new admin_setting_description('tiny_recordrtc/warning', null, $warning); + $settings->add($setting); + } + + // Types allowed. + $options = [ + 'both' => new lang_string('audioandvideo', 'tiny_recordrtc'), + 'audio' => new lang_string('onlyaudio', 'tiny_recordrtc'), + 'video' => new lang_string('onlyvideo', 'tiny_recordrtc') + ]; + $name = get_string('allowedtypes', 'tiny_recordrtc'); + $desc = get_string('allowedtypes_desc', 'tiny_recordrtc'); + $default = 'both'; + $setting = new admin_setting_configselect('tiny_recordrtc/allowedtypes', $name, $desc, $default, $options); + $settings->add($setting); + + // Audio bitrate. + $name = get_string('audiobitrate', 'tiny_recordrtc'); + $desc = get_string('audiobitrate_desc', 'tiny_recordrtc'); + $default = '128000'; + $setting = new admin_setting_configtext('tiny_recordrtc/audiobitrate', $name, $desc, $default, PARAM_INT, 8); + $settings->add($setting); + + // Video bitrate. + $name = get_string('videobitrate', 'tiny_recordrtc'); + $desc = get_string('videobitrate_desc', 'tiny_recordrtc'); + $default = '2500000'; + $setting = new admin_setting_configtext('tiny_recordrtc/videobitrate', $name, $desc, $default, PARAM_INT, 8); + $settings->add($setting); + + // Audio recording time limit. + $name = get_string('audiotimelimit', 'tiny_recordrtc'); + $desc = get_string('audiotimelimit_desc', 'tiny_recordrtc'); + // Validate audiotimelimit greater than 0. + $setting = new admin_setting_configduration('tiny_recordrtc/audiotimelimit', $name, $desc, $defaulttimelimit); + $setting->set_validate_function(function(int $value): string { + if ($value <= 0) { + return get_string('timelimitwarning', 'tiny_recordrtc'); + } + return ''; + }); + $settings->add($setting); + + // Video recording time limit. + $name = get_string('videotimelimit', 'tiny_recordrtc'); + $desc = get_string('videotimelimit_desc', 'tiny_recordrtc'); + // Validate videotimelimit greater than 0. + $setting = new admin_setting_configduration('tiny_recordrtc/videotimelimit', $name, $desc, $defaulttimelimit); + $setting->set_validate_function(function(int $value): string { + if ($value <= 0) { + return get_string('timelimitwarning', 'tiny_recordrtc'); + } + return ''; + }); + $settings->add($setting); +} diff --git a/lib/editor/tiny/plugins/recordrtc/styles.css b/lib/editor/tiny/plugins/recordrtc/styles.css new file mode 100644 index 00000000000..84ccb509e76 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/styles.css @@ -0,0 +1,43 @@ +.tiny_recordrtc, +.tiny_recordrtc div { + font-weight: normal; + line-height: 40px; + padding: 3px 0 3px 0; +} + +.tiny_recordrtc .alert { + line-height: 40px; + text-align: center; +} + +.tiny_recordrtc audio { + display: block; + width: 100%; +} + +@media screen and (max-width: 670px) { + .tiny_recordrtc video { + display: block; + height: auto; + margin: 0 auto; + width: 100%; + } +} + +@media screen and (min-width: 671px) { + .tiny_recordrtc video { + display: block; + height: 480px; + margin: 0 auto; + width: 640px; + } +} + +.tiny_recordrtc #start-stop, +.tiny_recordrtc #upload { + white-space: normal; +} + +.tiny_recordrtc video { + border-radius: 8px; +} diff --git a/lib/editor/tiny/plugins/recordrtc/templates/audio_recorder.mustache b/lib/editor/tiny/plugins/recordrtc/templates/audio_recorder.mustache new file mode 100644 index 00000000000..bab38d52aa6 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/templates/audio_recorder.mustache @@ -0,0 +1,78 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tiny_recordrtc/audio_recorder + + Audio recorder template. + + Example context (json): + { + } +}} +{{! + Note: + The content of this template should ideally be shared with the video recorder by using a block template. + Unfortunately it is not possible to have nested block templates due to MDL-51145. + Once this issue is resolved, this template should be refactored to use a block template. +}} +{{< core/modal }} + {{$title}}{{#str}} audiotitle, tiny_recordrtc {{/str}}{{/title}} + {{$body}} + {{> core/local/toast/wrapper }} +
+
+
+
+ + {{#str}} insecurealerttitle, tiny_recordrtc {{/str}} + + {{#str}} insecurealert, tiny_recordrtc {{/str}} +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ {{/body}} + + {{$footer}} + + {{/footer}} +{{/ core/modal }} diff --git a/lib/editor/tiny/plugins/recordrtc/templates/embed_audio.mustache b/lib/editor/tiny/plugins/recordrtc/templates/embed_audio.mustache new file mode 100644 index 00000000000..ca8835b11a7 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/templates/embed_audio.mustache @@ -0,0 +1,29 @@ + {{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tiny_recordrtc/embed_audio + + Insert recording template. + + Example context (json): + { + "source": "https://example.com/someaudio.mp3" + } +}} + diff --git a/lib/editor/tiny/plugins/recordrtc/templates/embed_video.mustache b/lib/editor/tiny/plugins/recordrtc/templates/embed_video.mustache new file mode 100644 index 00000000000..ee8658348a8 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/templates/embed_video.mustache @@ -0,0 +1,29 @@ + {{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tiny_recordrtc/embed_video + + Insert recording template. + + Example context (json): + { + "source": "https://example.com/someaudio.mp3" + } +}} + diff --git a/lib/editor/tiny/plugins/recordrtc/templates/insert_recording.mustache b/lib/editor/tiny/plugins/recordrtc/templates/insert_recording.mustache new file mode 100644 index 00000000000..e2f63689b67 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/templates/insert_recording.mustache @@ -0,0 +1,70 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tiny_recordrtc/insert_recording + + Insert recording template. + + Example context (json): + { + + } +}} +
+
+
+
+ + {{#str}} insecurealerttitle, tiny_recordrtc {{/str}} + + {{#str}} insecurealert, tiny_recordrtc {{/str}} +
+
+
+
+ {{#isaudio}} +
+
+ +
+
+ {{/isaudio}} + {{^isaudio}} +
+ +
+ {{/isaudio}} +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
diff --git a/lib/editor/tiny/plugins/recordrtc/templates/timeremaining.mustache b/lib/editor/tiny/plugins/recordrtc/templates/timeremaining.mustache new file mode 100644 index 00000000000..098390efed3 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/templates/timeremaining.mustache @@ -0,0 +1,28 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tiny_recordrtc/timeremaining + + Insert recording template. + + Example context (json): + { + } +}} + + {{#str}}stoprecording, tiny_recordrtc{{/str}} ({{minutes}}:{{seconds}}) + diff --git a/lib/editor/tiny/plugins/recordrtc/templates/video_recorder.mustache b/lib/editor/tiny/plugins/recordrtc/templates/video_recorder.mustache new file mode 100644 index 00000000000..4cfb7dbcba1 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/templates/video_recorder.mustache @@ -0,0 +1,78 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tiny_recordrtc/video_recorder + + Audio recorder template. + + Example context (json): + { + } +}} +{{! + Note: + The content of this template should ideally be shared with the video recorder by using a block template. + Unfortunately it is not possible to have nested block templates due to MDL-51145. + Once this issue is resolved, this template should be refactored to use a block template. +}} +{{< core/modal }} + {{$title}}{{#str}} videotitle, tiny_recordrtc {{/str}}{{/title}} + {{$body}} + {{> core/local/toast/wrapper }} +
+
+
+
+ + {{#str}} insecurealerttitle, tiny_recordrtc {{/str}} + + {{#str}} insecurealert, tiny_recordrtc {{/str}} +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ {{/body}} + + {{$footer}} + + {{/footer}} +{{/ core/modal }} diff --git a/lib/editor/tiny/plugins/recordrtc/version.php b/lib/editor/tiny/plugins/recordrtc/version.php new file mode 100644 index 00000000000..95d3317bff8 --- /dev/null +++ b/lib/editor/tiny/plugins/recordrtc/version.php @@ -0,0 +1,29 @@ +. + +/** + * Tiny media plugin version details. + * + * @package tiny_recordrtc + * @copyright 2022 Stevani Andolo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2022081800; +$plugin->requires = 2020061500; +$plugin->component = 'tiny_recordrtc';