src/dynamics/audio_recorder/recorder/recorder.js
Scoped.define("module:AudioRecorder.Dynamics.Recorder", [
"dynamics:Dynamic",
"module:Assets",
"module:AudioVisualization",
"browser:Info",
"browser:Dom",
"browser:Upload.MultiUploader",
"browser:Upload.FileUploader",
"media:AudioRecorder.AudioRecorderWrapper",
"media:WebRTC.Support",
"base:Types",
"base:Objs",
"base:Strings",
"base:Time",
"base:Timers",
"base:States.Host",
"base:Classes.ClassRegistry",
"base:Collections.Collection",
"base:Promise",
"module:AudioRecorder.Dynamics.RecorderStates.Initial",
"module:AudioRecorder.Dynamics.RecorderStates"
], [
"module:AudioRecorder.Dynamics.Loader",
"module:AudioRecorder.Dynamics.Controlbar",
"module:AudioRecorder.Dynamics.Message",
"module:AudioRecorder.Dynamics.Chooser",
"dynamics:Partials.ShowPartial",
"dynamics:Partials.IfPartial",
"dynamics:Partials.EventPartial",
"dynamics:Partials.OnPartial",
"dynamics:Partials.DataPartial",
"dynamics:Partials.AttrsPartial",
"dynamics:Partials.StylesPartial",
"dynamics:Partials.TemplatePartial",
"dynamics:Partials.HotkeyPartial"
], function(Class, Assets, AudioVisualization, Info, Dom, MultiUploader, FileUploader, AudioRecorderWrapper, WebRTCSupport, Types, Objs, Strings, Time, Timers, Host, ClassRegistry, Collection, Promise, InitialState, RecorderStates, scoped) {
return Class.extend({
scoped: scoped
}, function(inherited) {
return {
template: "<%= template(dirname + '/recorder.html') %>",
attrs: {
/* CSS */
"css": "ba-videorecorder", // inherit from video recorder
"cssaudio": "ba-audiorecorder",
"cssrecorder": "ba-recorder",
"csscommon": "ba-commoncss",
"iecss": "ba-audiorecorder",
"cssimagegallery": "",
"cssloader": "",
"csscontrolbar": "",
"cssmessage": "",
"csstopmessage": "",
"csschooser": "",
"width": "",
"height": "",
"gallerysnapshots": 3,
/* Themes */
"theme": "",
"csstheme": "",
/* Dynamics */
"dynloader": "audiorecorder-loader",
"dyncontrolbar": "audiorecorder-controlbar",
"dynmessage": "audiorecorder-message",
"dynchooser": "audiorecorder-chooser",
"dynaudioplayer": "audioplayer",
/* Templates */
"tmplloader": "",
"tmplcontrolbar": "",
"tmplmessage": "",
"tmplchooser": "",
/* Attributes */
"autorecord": false,
"autoplay": false,
"allowrecord": true,
"allowupload": true,
"allowcustomupload": true,
"primaryrecord": true,
"countdown": 3,
"audiobitrate": null,
"playbacksource": "",
"recordermode": true,
"skipinitial": false,
"skipinitialonrerecord": false,
"timelimit": null,
"timeminlimit": null,
"webrtcstreaming": false,
"webrtconmobile": false,
"webrtcstreamingifnecessary": true,
"microphone-volume": 1.0,
"early-rerecord": false,
"manualsubmit": false,
"allowedextensions": null,
"filesizelimit": null,
"display-timer": true,
"pausable": false,
"visualeffectvisible": true,
"visualeffectsupported": false,
"visualeffectheight": null,
"visualeffectminheight": 120,
"visualeffecttheme": "red-bars", // types: `balloon`, 'red-bars'
/* Configuration */
"simulate": false,
"enforce-duration": null,
"localplayback": false,
"uploadoptions": {},
"playerattrs": {},
"shortMessage": true,
/* Options */
"rerecordable": true,
"allowcancel": false,
"recordings": null,
"ready": true,
"audio-test-mandatory": false
},
scopes: {
player: ">[id='player']"
},
computed: {
"widthHeightStyles:width,height": function() {
var result = {};
var width = this.get("width");
var height = this.get("height");
if (width)
result.width = width + ((width + '').match(/^\d+$/g) ? 'px' : '');
if (height)
result.height = height + ((height + '').match(/^\d+$/g) ? 'px' : '');
return result;
}
},
types: {
"rerecordable": "boolean",
"ready": "boolean",
"autorecord": "boolean",
"autoplay": "boolean",
"allowrecord": "boolean",
"allowupload": "boolean",
"allowcustomupload": "boolean",
"primaryrecord": "boolean",
"recordermode": "boolean",
"skipinitialonrerecord": "boolean",
"localplayback": "boolean",
"skipinitial": "boolean",
"pausable": "boolean",
"enforce-duration": "bool",
"webrtcstreaming": "boolean",
"webrtconmobile": "boolean",
"webrtcstreamingifnecessary": "boolean",
"microphone-volume": "float",
"audiobitrate": "int",
"early-rerecord": "boolean",
"manualsubmit": "boolean",
"simulate": "boolean",
"allowedextensions": "array",
"allowcancel": "boolean",
"display-timer": "boolean",
"audio-test-mandatory": "boolean",
"visualeffectvisible": "boolean"
},
extendables: ["states"],
remove_on_destroy: true,
events: {
"change:microphonehealthy": function(value) {
this.trigger("microphonehealth", value);
},
"change:webrtconmobile": function() {
this.set("recordviafilecapture", Info.isMobile() && (!this.get("webrtconmobile") || !AudioRecorderWrapper.anySupport(this._audioRecorderWrapperOptions())));
},
"change:recordviafilecapture": function() {
if (this.get("recordviafilecapture")) {
this.set("skipinitial", false);
this.set("skipinitialonrerecord", false);
this.set("autorecord", false);
}
},
"change:visualeffectsupported": function(supported) {
if (!supported && this.audioVisualization) this.audioVisualization.destroy();
}
},
create: function() {
// Initialize AudioContext
WebRTCSupport.globals();
if (this.get("theme")) this.set("theme", this.get("theme").toLowerCase());
if (this.get("theme") in Assets.recorderthemes) {
Objs.iter(Assets.recorderthemes[this.get("theme")], function(value, key) {
if (!this.isArgumentAttr(key))
this.set(key, value);
}, this);
}
this.set("ie8", Info.isInternetExplorer() && Info.internetExplorerVersion() < 9);
this.set("hideoverlay", false);
this.set("recordviafilecapture", Info.isMobile() && (!this.get("webrtconmobile") || !AudioRecorderWrapper.anySupport(this._audioRecorderWrapperOptions())));
if (this.get("recordviafilecapture")) {
this.set("skipinitial", false);
this.set("skipinitialonrerecord", false);
this.set("autorecord", false);
}
if (this.get("pausable"))
this.set("resumevisible", false);
this.__attachRequested = false;
this.__activated = false;
this._bound = false;
this.__recording = false;
this.__error = null;
this.host = new Host({
stateRegistry: new ClassRegistry(this.cls.recorderStates())
});
this.host.dynamic = this;
this.host.initialize(this._initialState);
this._timer = new Timers.Timer({
context: this,
fire: this._timerFire,
delay: 250,
start: true
});
this._initSettings();
},
getMediaType: function() {
return "audio";
},
getAudioFile: function() {
return this._audioFile || (this.recorder && this.recorder.localPlaybackSource()) || null;
},
_initialState: InitialState,
state: function() {
return this.host.state();
},
recorderAttached: function() {
return !!this.recorder;
},
audioError: function() {
return this.__error;
},
_error: function(error_type, error_code) {
this.__error = {
error_type: error_type,
error_code: error_code
};
this.trigger("error:" + error_type, error_code);
this.trigger("error", error_type, error_code);
},
_clearError: function() {
this.__error = null;
},
_detachRecorder: function() {
if (this.recorder)
this.recorder.weakDestroy();
this.recorder = null;
this.set("hasrecorder", false);
},
_audioRecorderWrapperOptions: function() {
return {
simulate: this.get("simulate"),
audioBitrate: this.get("audiobitrate"),
webrtcStreaming: !!this.get("webrtcstreaming"),
webrtcStreamingIfNecessary: !!this.get("webrtcstreamingifnecessary"),
// webrtcOnMobile: !!this.get("webrtconmobile"),
localPlaybackRequested: this.get("localplayback")
};
},
_attachRecorder: function() {
if (this.recorderAttached())
return;
if (!this.__activated) {
this.__attachRequested = true;
return;
}
this.set("hasrecorder", true);
this.__attachRequested = false;
var audio = this.activeElement().querySelector("[data-audio='audio']");
this._clearError();
this.recorder = AudioRecorderWrapper.create(Objs.extend({
element: audio
}, this._audioRecorderWrapperOptions()));
// Draw visualization effect for the audio player
// if (this.get("visualeffectvisible") && AudioVisualization.supported()) {
// this.audioVisualization = new AudioVisualization(audio, {
// recorder: this.recorder,
// globalAudioContext: WebRTCSupport.globals().audioContext,
// height: this.recorder._recorder._options.recordResolution.height || 120,
// element: this.activeElement()
// });
// }
if (this.recorder) {
this.trigger("attached");
this.set("pausable", this.get("pausable") && this.recorder.canPause());
} else {
this._error("attach");
}
},
_softwareDependencies: function() {
if (!this.recorderAttached() || !this.recorder)
return Promise.error("No recorder attached.");
return this.recorder.softwareDependencies();
},
_bindMedia: function() {
if (this._bound || !this.recorderAttached() || !this.recorder)
return;
this.recorder.ready.success(function() {
this.recorder.on("require_display", function() {
this.set("hideoverlay", true);
}, this);
this.recorder.bindMedia().error(function(e) {
this.trigger("access_forbidden", e);
this.set("hideoverlay", false);
this.off("require_display", null, this);
this._error("bind", e);
}, this).success(function() {
this.trigger("access_granted");
this.recorder.setVolumeGain(this.get("microphone-volume"));
this.set("hideoverlay", false);
this.off("require_display", null, this);
// Draw visualization effect for the audio player
if (this.get("visualeffectvisible") && AudioVisualization.supported()) {
if (this.get("height") && this.get("height") > this.get("visualeffectminheight")) {
this.set('visualeffectheight', this.get("height"));
} else if (this.get("visualeffectheight") < this.get("visualeffectminheight")) {
this.set('visualeffectheight', this.get("visualeffectminheight"));
}
this.audioVisualization = new AudioVisualization(this.recorder._recorder.stream(), {
element: this.activeElement(),
recorder: this.recorder,
height: this.get("visualeffectheight"),
theme: this.get("visualeffecttheme")
});
// To be able set width of the canvas element
var waitAnalyser = new Timers.Timer({
context: this,
immediate: true,
delay: 50,
fire: function() {
if (this.recorder._analyser) {
try {
this.audioVisualization.initializeVisualEffect();
this.audioVisualization.start();
this.set("visualeffectsupported", true);
} catch (ex) {
this.set("visualeffectsupported", false);
console.warn(ex);
}
waitAnalyser.stop();
}
}
});
this.auto_destroy(waitAnalyser);
}
this.recorder.enumerateDevices().success(function(devices) {
var selected = this.recorder.currentDevices();
this.set("selectedmicrophone", selected.audio);
this.set("microphones", new Collection(Objs.values(devices.audio)));
}, this);
this.recorder.testSoundLevel(true);
this.set("devicetesting", true);
this._bound = true;
this.trigger("bound");
}, this);
}, this);
},
isWebrtcStreaming: function() {
return this.recorder && this.recorder.isWebrtcStreaming();
},
isFormatSupported: function() {
return (this.recorder && this.recorder.supportsLocalPlayback()) || this._audioFilePlaybackable;
},
_initSettings: function() {
this.set("duration", 0);
},
_initializeUploader: function() {
if (this._audioUploader) this._audioUploader.weakDestroy();
if (this._dataUploader) this._dataUploader.weakDestroy();
this._dataUploader = new MultiUploader();
},
_unbindMedia: function() {
if (!this._bound)
return;
this.recorder.unbindMedia();
this._bound = false;
},
_uploadAudioFile: function(file) {
if (this.get("simulate"))
return;
var uploader = FileUploader.create(Objs.extend({
source: file
}, this.get("uploadoptions").audio));
uploader.upload();
this._audioUploader = uploader;
this._dataUploader.addUploader(uploader);
},
_prepareRecording: function() {
return Promise.create(true);
},
_startRecording: function() {
if (this.__recording)
return Promise.error(true);
this.set("devicetesting", false);
return this.recorder.startRecord({
audio: this.get("uploadoptions").audio,
webrtcStreaming: this.get("uploadoptions").webrtcStreaming
}).success(function() {
this.__recording = true;
this.__recording_start_time = Time.now();
}, this);
},
_stopRecording: function() {
if (!this.__recording)
return Promise.error(true);
if (this.audioVisualization) this.audioVisualization.stop();
return this.recorder.stopRecord({
audio: this.get("uploadoptions").audio,
webrtcStreaming: this.get("uploadoptions").webrtcStreaming,
noUploading: this.get("uploadoptions").noUploading
}).success(function(uploader) {
this.__recording = false;
uploader.upload();
this._dataUploader.addUploader(uploader);
}, this);
},
isRecording: function() {
return this.__recording;
},
_verifyRecording: function() {
return Promise.create(true);
},
_afterActivate: function(element) {
inherited._afterActivate.call(this, element);
this.__activated = true;
if (this.__attachRequested)
this._attachRecorder();
this.persistentTrigger("loaded");
},
object_functions: [
"record", "rerecord", "stop", "play", "pause", "reset", "pause_recorder", "resume"
],
functions: {
cancel: function() {
if (confirm(this.stringUnicode("cancel-confirm")))
this.execute("reset");
},
record: function() {
this.host.state().record();
},
record_audio: function() {
this.host.state().selectRecord();
},
upload_audio: function(file) {
this.host.state().selectUpload(file);
},
select_microphone: function(microphone_id) {
if (this.recorder) {
this.recorder.setCurrentDevices({
audio: microphone_id
});
this.recorder.testSoundLevel(true);
this.set("selectedmicrophone", microphone_id);
if (this.audioVisualization) {
this.recorder._recorder.on("bound", function() {
this.audioVisualization.updateSourceStream();
}, this);
}
}
this.set("microphonehealthy", false);
},
rerecord: function() {
if (confirm(this.stringUnicode("rerecord-confirm"))) {
this.host.state().rerecord();
this._initSettings();
}
},
stop: function() {
// If recorder is paused need resume first,
// setting this._recording to true also could be enough
if (this.__paused)
this._resume();
this.host.state().stop();
},
pause_recorder: function() {
if (typeof this.recorder !== 'undefined') {
this.recorder.pauseRecord();
this.recorder._recorder.once("paused", function() {
this.__paused = true;
this.__recording = false;
this.set("resumevisible", true);
}, this);
}
},
resume: function() {
if (typeof this.recorder !== 'undefined')
this._resume();
},
play: function() {
this.host.state().play();
},
pause: function() {
this.host.state().pause();
},
message_click: function() {
this.trigger("message-click");
},
message_link_click: function(link) {
this.trigger("message-link-click", link);
},
playing: function() {
this.trigger("playing");
},
paused: function() {
this.trigger("paused");
},
ended: function() {
this.trigger("ended");
},
reset: function() {
this._stopRecording().callback(function() {
this._unbindMedia();
this._detachRecorder();
this._initSettings();
this.host.state().next("Initial");
}, this);
},
manual_submit: function() {
this.set("rerecordable", false);
this.set("manualsubmit", false);
this.trigger("manually_submitted");
},
ready_to_play: function() {
this.trigger("ready_to_play");
}
},
_resume: function() {
this.__paused = false;
this.__recording = true;
this.recorder.resumeRecord();
this.recorder._recorder.once("resumed", function() {
this.set("resumevisible", false);
}, this);
},
destroy: function() {
this._timer.destroy();
this.host.destroy();
this._detachRecorder();
inherited.destroy.call(this);
},
soundLevel: function() {
return this.recorderAttached() ? this.recorder.soundLevel() : null;
},
_timerFire: function() {
if (this.destroyed())
return;
try {
if (this.recorderAttached() && this.get("devicetesting")) {
if (!this.get("microphonehealthy") && this.soundLevel() >= 1.01) {
this.set("microphonehealthy", true);
this.recorder.testSoundLevel(false);
}
}
} catch (e) {}
this._updateCSSSize();
},
_updateCSSSize: function() {
var width = Dom.elementDimensions(this.activeElement()).width;
this.set("csssize", width > 400 ? "normal" : (width > 300 ? "medium" : "small"));
},
parentWidth: function() {
return this.get("width") || Dom.elementDimensions(this.activeElement()).width;
},
parentHeight: function() {
return this.get("height") || Dom.elementDimensions(this.activeElement()).height;
},
parentAspectRatio: function() {
return this.parentWidth() / this.parentHeight();
}
};
}, {
recorderStates: function() {
return [RecorderStates];
}
})
.register("ba-audiorecorder")
.registerFunctions({
/*<%= template_function_cache(dirname + '/recorder.html') %>*/
})
.attachStringTable(Assets.strings)
.addStrings({
"recorder-error": "An error occurred, please try again later. Click to retry.",
"attach-error": "We could not access the media interface. Depending on the device and browser, you might need to access the page via SSL.",
"software-required": "Please click below to install / activate the following requirements in order to proceed.",
"software-waiting": "Waiting for the requirements to be installed / activated. You might need to refresh the page after completion.",
"access-forbidden": "Access to the media was forbidden. Click to retry.",
"uploading": "Uploading",
"uploading-failed": "Uploading failed - click here to retry.",
"upload-error-duration": "Length of the uploaded audio does not meet the requirements - click here to retry.",
"verifying": "Verifying",
"verifying-failed": "Verifying failed - click here to retry.",
"rerecord-confirm": "Do you really want to redo your audio?",
"cancel-confirm": "Do you really want to cancel your audio upload?",
"audio_file_too_large": "Your audio file is too large (%s) - click here to try again with a smaller audio file.",
"unsupported_audio_type": "Please upload: %s - click here to retry.",
"uploading-src-error": "Unable to play back audio now, uploading is still in progress",
"missing-track": "Required audio track is missing",
"device-already-in-use": "At least one of your input devices are already in use",
"browser-permission-denied": "Permission denied by browser, please grant access and reload page"
});
});