summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNorman Kerr 柯念北2014-08-18 19:40:33 (GMT)
committerNorman Kerr 柯念北2014-08-18 19:40:33 (GMT)
commit37336307f9d5d78650a4f39772948bc73974491f (patch)
tree5b3cef5268a473539056f0d8c18b8ca02af7267c
parentab8809c2d01338a3fee6fd55a3971e581eb73ae6 (diff)
Complete overhaul of js and file handling. Adds ability to stream file chunks while recording. See notes.7.x-1.0-alpha5
Adds video/audio stream recording for firefox (video only works in firefox nightly). This will cut down considerable on upload times. Media recorder only asks for mic permission on record (https://drupal.org/node/2232601). Better UI with help messages. Better error handling on both server and client sides. Added theme template for more customizability. Added better audio visualization and mic meter.
-rw-r--r--README.txt22
-rwxr-xr-xcss/media-recorder.css313
-rw-r--r--images/mic.pngbin3712 -> 0 bytes
-rwxr-xr-ximages/settings.pngbin546 -> 0 bytes
-rw-r--r--includes/media_recorder.admin.inc25
-rwxr-xr-xjs/jquery.mediaRecorder.js510
-rwxr-xr-xjs/media-recorder-api.js305
-rwxr-xr-xjs/media-recorder-flash.js149
-rwxr-xr-xjs/media-recorder-html5.js315
-rwxr-xr-xjs/media-recorder-youtube.js59
-rw-r--r--js/media-recorder.browser.js4
-rwxr-xr-xjs/media-recorder.js77
-rwxr-xr-xmedia_recorder.module534
-rw-r--r--theme/media-recorder.tpl.php26
14 files changed, 1284 insertions, 1055 deletions
diff --git a/README.txt b/README.txt
index 9e2f6c3..d90b2ba 100644
--- a/README.txt
+++ b/README.txt
@@ -7,34 +7,26 @@ REQUIREMENTS
------------
* Media module - https://drupal.org/project/media
* Libraries module - https://drupal.org/project/libraries
- * Modernizr module - https://drupal.org/project/modernizr
- * Transliteration module - https://drupal.org/project/transliteration
* RecorderJS library - https://github.com/mattdiamond/Recorderjs
+ * Recorder.js library - https://github.com/jwagener/recorder.js
* SWFObject library - http://code.google.com/p/swfobject
- * WAMI recorder library - http://code.google.com/p/wami-recorder
-
-INTEGRATION
------------
- * MediaElement - https://drupal.org/project/mediaelement
- Replaces playback HTML5 audio element with mediaelement player.
- * Media: Youtube - https://drupal.org/project/media
- Adds an option to record using the Youtube Upload Widget.
INSTALLATION
------------
+** Use the drush command 'drush mrdl' to automatically download the libraries.
+
1. Install the RecorderJS library in sites/all/libraries. The recorder.js file
should be located at sites/all/libraries/Recorderjs/recorder.js.
-2. Install the SWFObject & Wami recorder libraries in sites/all/libraries. The
+2. Install the SWFObject & flash recorder.js libraries in sites/all/libraries. The
swfobject.js file should be at sites/all/libraries/swfobject/swfobject.js,
- and recorder.js should be at sites/all/libraries/wami/recorder.js.
+ and recorder.js should be at sites/all/libraries/recorder.js/recorder.js.
3. Install dependencies and media recorder module as per:
https://drupal.org/documentation/install/modules-themes/modules-7
-4. Visit the media recorder configuration page to set default audio, file path,
- etc, at admin/config/media/mediarecorder. If you want your default audio in
- the correct file path, set the path and save, then record the default audio.
+4. Visit the media recorder configuration page to set file path,
+ etc, at admin/config/media/mediarecorder.
CREDITS
-------
diff --git a/css/media-recorder.css b/css/media-recorder.css
index c7c62ef..97c3aee 100755
--- a/css/media-recorder.css
+++ b/css/media-recorder.css
@@ -1,270 +1,145 @@
-.media-recorder-wrapper {
- position: relative;
-}
-
-/* Media recorder */
-.media-recorder {
- position: relative;
+div.media-recorder-toggle {
+ margin-bottom: 2px;
padding: 0;
- background: #323232;
- border-radius: 3px;
- display: inline-block;
- vertical-align: bottom;
-}
-
-.media-recorder.HTML5 .controls {
- position: absolute;
- top: 10%;
- left: 10%;
- width: 25%;
- height: 80%;
- display: inline-block;
- z-index: 1;
- color: #fff;
- opacity: 1;
- border-radius: 5px;
- background: #222;
- box-shadow: inset 0px 0px 50px #000;
}
-/* Styling applicable to all buttons */
-.media-recorder.HTML5 .controls .media-recorder-record {
- background-image: url('../images/mic.png');
- background-size: 80%;
- background-repeat: no-repeat;
- background-position: center;
- height: 100%;
- width: 100%;
- vertical-align: middle;
- padding: 0;
+div.media-recorder-toggle .form-item {
margin: 0;
- text-indent: -9999px;
- cursor: pointer;
-}
-
-/* Styling for when the record button hasn't been pressed. */
-.media-recorder.HTML5 .record-off {
- opacity: 1;
-}
-
-/* Styling for when the record button has been pressed. */
-.media-recorder.HTML5 .record-on {
- background: #552020;
- border-radius: 50%;
+ padding: 0;
+ display: inline-block;
}
-.media-recorder.HTML5 .media-recorder-analyser {
- position: absolute;
- top: 10%;
- left: 40%;
- width: 55%;
- height: 80%;
- background: #222;
- box-shadow: inset 0px 0px 50px #000;
- opacity: .7;
- border-radius: 5px 5px 5px 0;
+div.media-recorder-toggle input {
+ display: none;
}
-.media-recorder.HTML5 .volume {
- position: absolute;
- top: 10%;
- left: 4%;
- height: 80%;
+div.media-recorder-toggle input:checked + label.option {
+ background: #323232;
}
-.media-recorder.HTML5 .progressbar {
- width: 100%;
+div.media-recorder-toggle label.option {
+ background: #555555;
+ border: #ccc;
+ border-radius: 3px 3px 0 0;
+ color: #ffffff;
+ margin: 0 5px 0 0;
+ padding: 5px 10px;
+ cursor: pointer;
}
-.media-recorder.HTML5 .media-recorder-status {
- position: absolute;
- bottom: 10%;
- left: 40%;
- width: 51%;
- height: 1em;
- line-height: 1em;
- vertical-align: middle;
- font-size: 1em;
- background: #000;
+.media-recorder-wrapper {
+ padding: 10px;
+ margin: 0;
+ background: #323232;
+ border: none;
color: #fff;
- opacity: .5;
- border-radius: 0 5px 0 0;
- padding: 2%;
}
-.media-recorder.flash .controls {
- position: absolute;
- top: 10%;
- left: 5%;
- width: 25%;
- height: 80%;
- display: inline-block;
- z-index: 1;
- color: #fff;
- opacity: 1;
- border-radius: 5px;
- background: #222;
- box-shadow: inset 0px 0px 50px #000;
+.media-recorder {
+ width: 100%;
}
-.media-recorder-wrapper .flashRecorder {
- background: transparent;
- display: inline-block;
+.media-recorder-status {
+ background: #222222;
+ padding: 5px 10px;
+ margin: 5px 0;
+ box-shadow: inset 0 0 10px #000000;
+ text-align: center;
}
-/* Styling applicable to all buttons */
-.media-recorder.flash .controls .media-recorder-record {
- background-image: url('../images/mic.png');
- background-size: 80%;
- background-repeat: no-repeat;
- background-position: center;
- height: 80%;
- width: 80%;
- vertical-align: middle;
+.media-recorder-preview {
+ background: #222222;
padding: 0;
- margin: 10%;
- text-indent: -9999px;
- cursor: pointer;
-}
-
-/* Styling for when the record button hasn't been pressed. */
-.media-recorder.flash .record-off {
- opacity: 1;
-}
-
-/* Styling for when the record button has been pressed. */
-.media-recorder.flash .record-on {
- background: #552020;
- border-radius: 50%;
-}
-
-.media-recorder.flash .media-recorder-analyser {
- position: absolute;
- top: 10%;
- left: 35%;
- width: 60%;
- height: 80%;
- background: #222;
- box-shadow: inset 0px 0px 50px #000;
- opacity: .7;
- border-radius: 5px 5px 5px 0;
+ margin: 5px 0;
}
-.media-recorder.flash .volume {
- position: absolute;
- top: 10%;
- left: 4%;
- height: 80%;
-}
-
-.media-recorder.flash .media-recorder-status {
- position: absolute;
- bottom: 10%;
- left: 35%;
- max-width: 56%;
- height: 1em;
- line-height: 1em;
+.media-recorder-preview canvas.media-recorder-visualizer {
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ display: inline-block;
vertical-align: middle;
- font-size: 1em;
- background: #000;
- color: #fff;
- opacity: .5;
- border-radius: 0 5px 0 0;
- padding: 2%;
}
-.media-recorder.flash .media-recorder-mic-settings {
- position: absolute;
- top: 10%;
- right: 5%;
+.media-recorder-preview canvas.media-recorder-meter {
width: 10%;
- height: 25%;
- background: #000;
- opacity: .5;
- border-radius: 0 0 0 5px;
- background-image: url('../images/settings.png');
- background-size: 50%;
- background-repeat: no-repeat;
- background-position: center;
- text-indent: -9999px;
- cursor: pointer;
-}
-
-/* A single meter bar - animation is created by subtracting margin on all bars every 50ms */
-.media-recorder.flash .meter-bar {
- width: 1%;
height: 100%;
- float: left;
- vertical-align: middle;
-}
-
-/* Recording styles for a single meter bar. */
-.media-recorder.flash .meter-bar .inner.record {
- background: #ff0000;
+ padding: 0;
+ margin: 0;
+ display: inline-block;
vertical-align: middle;
}
-/* Playing styles for a single meter bar. */
-.media-recorder.flash .meter-bar .inner.play {
- background: #33cc33;
- vertical-align: middle;
+.media-recorder-preview video {
+ width: 90%;
+ padding: 0;
+ margin: 0;
+ display: inline-block;
+ vertical-align: bottom;
}
-@-webkit-keyframes opacity {
- 0% { opacity: 1; }
- 100% { opacity: 0; }
-}
-@-moz-keyframes opacity {
- 0% { opacity: 1; }
- 100% { opacity: 0; }
+.media-recorder-constraints {
+ text-align: center;
+ padding: 5px;
}
-.media-recorder.flash .media-recorder-analyser p {
- text-align: center;
- color: #fff;
- vertical-align: middle;
+button.media-recorder-enable-audio, button.media-recorder-enable-video {
+ display: inline-block;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ background: #555555;
+ color: #fff;
+ border: none;
+ border-radius: 3px;
+ padding: 5px 10px;
+ cursor: pointer;
}
-.media-recorder.flash .media-recorder-analyser p span {
- -webkit-animation-name: opacity;
- -webkit-animation-duration: 1s;
- -webkit-animation-iteration-count: infinite;
-
- -moz-animation-name: opacity;
- -moz-animation-duration: 1s;
- -moz-animation-iteration-count: infinite;
+button.media-recorder-enable-audio.active, button.media-recorder-enable-video.active {
+ font-weight: bold;
+ -webkit-box-shadow: inset 0 0 3px #000000;
+ -moz-box-shadow: inset 0 0 3px #000000;
+ box-shadow: inset 0 0 3px #000000;
}
-.media-recorder.flash .media-recorder-analyser p span:nth-child(2) {
- -webkit-animation-delay: 100ms;
- -moz-animation-delay: 100ms;
+button.media-recorder-enable-video:disabled {
+ color: #ccc;
+ text-decoration: line-through;
}
-.media-recorder.flash .media-recorder-analyser p span:nth-child(3) {
- -webkit-animation-delay: 300ms;
- -moz-animation-delay: 300ms;
+.media-recorder-controls {
+ text-align: center;
+ padding: 5px;
}
-div.media-recorder-toggle .form-item {
- margin: 0;
- padding: 0;
+.media-recorder-controls button {
display: inline-block;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ background: #FF4D4D;
+ color: #fff;
+ border: none;
+ border-radius: 2px;
+ padding: 5px 10px;
+ cursor: pointer;
}
-div.media-recorder-toggle input {
- display: none;
+.media-recorder-controls button.media-recorder-settings {
+ background: #00a5e1;
}
-div.media-recorder-toggle input:checked + label.option {
+.media-recorder-file-preview {
background: #323232;
+ color: #fff;
}
-div.media-recorder-toggle label.option {
- background: #555555;
- border: #ccc;
- border-radius: 3px 3px 0px 0px;
- color: #ffffff;
- margin: 0 5px 0 0;
- padding: 5px 10px;
- cursor: pointer;
+#recorderFlashContainer {
+ background: transparent !important;
+ border: none !important;
+}
+
+#recorderFlashContainer object {
}
diff --git a/images/mic.png b/images/mic.png
deleted file mode 100644
index 869212d..0000000
--- a/images/mic.png
+++ /dev/null
Binary files differ
diff --git a/images/settings.png b/images/settings.png
deleted file mode 100755
index 348a8f9..0000000
--- a/images/settings.png
+++ /dev/null
Binary files differ
diff --git a/includes/media_recorder.admin.inc b/includes/media_recorder.admin.inc
index 47217a4..f7d47fc 100644
--- a/includes/media_recorder.admin.inc
+++ b/includes/media_recorder.admin.inc
@@ -11,7 +11,7 @@
function media_recorder_admin_form($form, $form_state) {
// Check that all libraries exist.
- $required_libraries = array('swfobject', 'wami', 'Recorderjs');
+ $required_libraries = array('swfobject', 'recorder.js', 'Recorderjs');
foreach ($required_libraries as $name) {
$library = libraries_detect($name);
if (!$library['installed']) {
@@ -19,29 +19,6 @@ function media_recorder_admin_form($form, $form_state) {
}
}
- // Recorder width.
- $form['media_recorder_width'] = array(
- '#type' => 'textfield',
- '#title' => t('Media recorder width'),
- '#default_value' => variable_get('media_recorder_width', 300),
- );
-
- // Recorder height.
- $form['media_recorder_height'] = array(
- '#type' => 'textfield',
- '#title' => t('Media recorder height'),
- '#default_value' => variable_get('media_recorder_height', 100),
- );
-
- // Recorder time limit.
- $time_limit = gmdate('i:s', variable_get('media_recorder_timelimit', 300));
- $form['media_recorder_timelimit'] = array(
- '#type' => 'textfield',
- '#title' => t('Media recorder time limit'),
- '#description' => t('Enter a time limit for recordings in seconds. Currently set to @time minutes.', array('@time' => $time_limit)),
- '#default_value' => variable_get('media_recorder_timelimit', 300),
- );
-
// Recorder upload directory.
$form['media_recorder_upload_directory'] = array(
'#type' => 'textfield',
diff --git a/js/jquery.mediaRecorder.js b/js/jquery.mediaRecorder.js
deleted file mode 100755
index fb68623..0000000
--- a/js/jquery.mediaRecorder.js
+++ /dev/null
@@ -1,510 +0,0 @@
-/********************************************************************
- * Project: Drupal Media Recorder jQuery Plugin
- * Description: Adds a media recorder to the drupal media module
- * Author: Norman Kerr
- * License: GPL 2.0 (http://www.gnu.org/licenses/gpl-2.0.html)
- *******************************************************************/
-
-(function($, window, document, undefined) {
-
- // ********************************************************************
- // * Global variables.
- // ********************************************************************
-
- // jQuery plugin variables.
- var defaults = {
- 'timeLimit': 300000
- };
-
- // Normalize features.
- navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
- window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;
- window.URL = window.URL || window.webkitURL;
-
- // Feature detection.
- var browser = $.browser;
- var getUserMediaCheck = typeof(navigator.getUserMedia) === 'function';
- var webAudioCheck = typeof(window.AudioContext) === 'function';
- var flashVersion = swfobject.getFlashPlayerVersion();
- var canvas = document.createElement('canvas');
- var canvasCheck = !!(canvas.getContext && canvas.getContext('2d'));
-
- // ********************************************************************
- // * jQuery Plugin Functions.
- // ********************************************************************
-
- function mediaRecorder(element, options) {
- this.element = element;
- this.options = $.extend({}, defaults, options);
- this.defaults = defaults;
-
- // Check for existing recorder.
- if (typeof this.element.recorder != 'undefined') {
- return this.element.mediaRecorder;
- }
- // Otherwise attach recorder to DOM node for reference
- else {
- this.element.mediaRecorder = this;
- }
-
- // Run webRTC recorder.
- if (getUserMediaCheck && webAudioCheck) {
- // Hide specific file related elements.
- $(this.element).parent().children('span.file, span.file-size, input.media-recorder-upload, input.media-recorder-upload-button').hide();
- // Set audio player width to recorder width.
- $(this.element).parent().children('div.file-audio').width(options.width);
- // Load Recorderjs library and initialize recorder.
- $.ajax({
- url: Drupal.settings.mediaRecorder.html5url + '/recorder.js',
- async: false,
- dataType: "script",
- });
- this.init();
- }
- // Run flash recorder.
- else if (flashVersion.major >= 10) {
- // Hide all file input related elements.
- $(this.element).parent().children('span.file, span.file-size, input.media-recorder-upload, input.media-recorder-upload-button').hide();
- // Set audio player width to recorder width.
- $(this.element).parent().children('div.file-audio').width(options.width);
- // Load recorder.js library and initialize recorder.
- $.ajax({
- url: Drupal.settings.mediaRecorder.swfurl + '/recorder.js',
- async: false,
- dataType: "script",
- });
- this.flashInit();
- }
- // Display flash warning if not newer version.
- else if (flashVersion.major < 10 && flashVersion.major > 0) {
- $(this.element).prepend($('<div class="messages error"><span class="message-text"><a target="_blank" href="https://get.adobe.com/flashplayer">Flash 10</a> or higher must be installed in order to record.</span></div>'));
- }
- // Show file element.
- else {
- // Hide all media recorder related elements.
- $(this.element).parent().children('.media-recorder-toggle').hide();
- $(this.element).hide();
- }
-
- return this;
- }
-
- $.fn.mediaRecorder = function(options) {
- return this.each(function() {
- if (!$.data(this, "plugin_" + mediaRecorder)) {
- $.data(this, "plugin_" + mediaRecorder, new mediaRecorder(this, options));
- }
- });
- };
-
- // ********************************************************************
- // * Media Recorder Prototype Functions.
- // ********************************************************************
-
- mediaRecorder.prototype = {
-
- // ********************************************************************
- // * Initialize Recorder.
- // ********************************************************************
- init: function() {
-
- // Generate general recorder markup.
- var element = $(this.element);
- var options = this.options;
- element.recorder = $('<div class="media-recorder"></div>').width(options.width).height(options.height);
- element.recorder.controls = $('<div class="controls"></div>');
- element.recorder.canvas = $('<canvas class="media-recorder-analyser"></canvas>');
- element.recorder.status = $('<div class="media-recorder-status">00:00 / ' + millisecondsToTime(options.timeLimit) + '</div>');
- element.recorder.statusInterval = 0;
- element.recorder.meterInterval = 0;
- element.recorder.progressInterval = 0;
- element.recorder.HTML5Recorder = null;
- element.recorder.audioContext = null;
-
- // Add button handlers.
- element.recorder.controls.record = $('<div class="media-recorder-record record-off" title="Click the mic to record and to stop recording."><span>Record</span></div>')
- .click(function(){
- mediaRecorder.prototype.record(element, options);
- });
-
- // Set HTML5 variables.
- element.recorder.volume = $('<div class="volume" title="Adjust the mic volume."></div>')
- .slider({
- orientation: "vertical",
- range: "min",
- step: 0.05,
- min: 0,
- max: 1,
- value: 0.8,
- slide: function(event, ui) {
- gainNode.gain.value = ui.value;
- }
- });
-
- // Add markup.
- element.prepend(element.recorder);
- element.recorder.addClass('HTML5');
- element.recorder.append(element.recorder.controls);
- element.recorder.controls.append(element.recorder.controls.record);
- element.recorder.append(element.recorder.volume);
- element.recorder.append(element.recorder.canvas);
- element.recorder.append(element.recorder.status);
-
- // Initiate getUserMedia.
- navigator.getUserMedia(
- {audio: true},
- function(stream) {startUserMedia(element, options, stream);},
- function(error) {onError(error);}
- );
- },
-
- // ********************************************************************
- // * Record Callback.
- // ********************************************************************
- record: function(element, options) {
- element.recorder.HTML5Recorder.record();
- mediaRecorder.prototype.recordStart(element, options);
- },
-
- // ********************************************************************
- // * Start Recording Callback.
- // ********************************************************************
- recordStart: function(element, options) {
- mediaRecorder.prototype.recordDuring(element, options);
- $(element).find('.media-recorder-analyser').html('');
- $(element).find('.media-recorder-record')
- .removeClass('record-off')
- .addClass('record-on')
- .unbind('click').click(function() {
- mediaRecorder.prototype.stop(element, options);
- });
- },
-
- // ********************************************************************
- // * During Recording Callback.
- // ********************************************************************
- recordDuring: function(element, options) {
- var currentSeconds = 0;
- element.recorder.statusInterval = window.setInterval(function() {
- // Set time limit and convert to date obj.
- currentSeconds = currentSeconds + 1;
- var currentMilliSeconds = new Date(currentSeconds * 1000);
- // Stop recording if time limit is reached.
- if ((options.timeLimit - currentMilliSeconds) < 0) {
- mediaRecorder.prototype.stop(element, options);
- }
- time = millisecondsToTime(currentMilliSeconds);
- // Refresh time display with current time.
- $(element).find('.media-recorder-status').html(time + ' / 05:00');
- }, 1000);
- },
-
- // ********************************************************************
- // * Finished Recording Callback.
- // ********************************************************************
- recordFinish: function(element, options) {
- $(element).find('.media-recorder-status').html('00:00 / 05:00');
- $(element).find('.media-recorder-record')
- .removeClass('record-on')
- .addClass('record-off')
- .unbind('click').click(function() {
- mediaRecorder.prototype.record(element, options);
- });
- },
-
- // ********************************************************************
- // * Stop Recording Callback.
- // ********************************************************************
- stop: function(element, options) {
- clearInterval(element.recorder.statusInterval);
- element.recorder.HTML5Recorder.stop();
- element.recorder.HTML5Recorder.exportWAV(function(blob) {
- $(element).find('.media-recorder-status').html('<div class="progressbar"></div>');
- sendBlob(element, options, blob);
- });
- element.recorder.HTML5Recorder.clear();
- mediaRecorder.prototype.recordFinish(element, options);
- },
-
- // ********************************************************************
- // * Initialize Flash Recorder.
- // ********************************************************************
- flashInit: function() {
-
- // Generate general recorder markup.
- var element = $(this.element);
- var options = this.options;
- var wrapperID = $(element).parent().attr('id');
-
- // Build recorder.
- element.recorder = $('<div class="media-recorder"></div>').width(options.width).height(options.height);
- element.recorder.controls = $('<div class="controls"></div>');
- element.recorder.canvas = $('<div class="media-recorder-analyser"></div>');
- element.recorder.status = $('<div class="media-recorder-status">00:00 / ' + millisecondsToTime(options.timeLimit) + '</div>');
- element.recorder.statusInterval = 0;
- element.recorder.meterInterval = 0;
- element.recorder.progressInterval = 0;
-
- // Add button handlers.
- element.recorder.controls.record = $('<div class="media-recorder-record record-off" title="Click the mic to record and to stop recording.">Record</div>')
- .click(function(){
- mediaRecorder.prototype.flashRecord(element, options);
- });
-
- // Add flash recorder markup.
- element.flash = $('<div id="flashRecorder-' + wrapperID + '" class="flashRecorder"></div>');
- element.recorder.micSettings = $('<div class="media-recorder-mic-settings" title="Adjust microphone settings.">Settings</div>')
- .click(function() {
- Recorder.flashInterface().showFlash();
- });
-
- // Add markup.
- element.append(element.flash);
- element.prepend(element.recorder);
- element.recorder.addClass('flash');
- element.recorder.append(element.recorder.controls);
- element.recorder.controls.append(element.recorder.controls.record);
- element.recorder.append(element.recorder.canvas);
- element.recorder.append(element.recorder.micSettings);
- element.recorder.append(element.recorder.status);
-
- // Initialize flash recorder.
- Recorder.initialize({
- swfSrc: Drupal.settings.mediaRecorder.swfurl + '/recorder.swf',
- flashContainer: document.getElementById('flashRecorder-' + wrapperID),
- });
- },
-
- // ********************************************************************
- // * Flash Record Callback.
- // ********************************************************************
- flashRecord: function(element, options) {
- Recorder.record({
- start: mediaRecorder.prototype.flashRecordStart(element, options),
- progress: mediaRecorder.prototype.flashRecordDuring(element, options),
- });
- },
-
- // ********************************************************************
- // * Flash Start Recording Callback.
- // ********************************************************************
- flashRecordStart: function(element, options) {
- $(element).find('.media-recorder-record').removeClass('record-off').addClass('record-on')
- .unbind('click').click(function() {
- mediaRecorder.prototype.flashStop(element, options);
- });
- $(element).find('.media-recorder-analyser').html('<p style="height: ' + options.height / 2 + 'px; line-height: ' + options.height / 2 + 'px">Recording<span>.</span><span>.</span><span>.</span></p>');
- },
-
- // ********************************************************************
- // * Flash During Recording Callback.
- // ********************************************************************
- flashRecordDuring: function(element, options) {
- // Update status interval.
- var currentSeconds = 0;
- element.recorder.statusInterval = window.setInterval(function() {
- // Set time limit and convert to date obj.
- currentSeconds = currentSeconds + 1;
- var currentMilliSeconds = new Date(currentSeconds * 1000);
- // Stop recording if time limit is reached.
- if ((options.timeLimit - currentMilliSeconds) < 0) {
- mediaRecorder.prototype.flashStop(element, options);
- }
- time = millisecondsToTime(currentMilliSeconds);
- // Refresh time display with current time.
- $(element).find('.media-recorder-status').html(time + ' / 05:00');
- }, 1000);
- },
-
- // ********************************************************************
- // * Flash Finished Recording Callback.
- // ********************************************************************
- flashRecordFinish: function(element, options, file) {
- // Clear all progress intervals.
- clearInterval(element.recorder.progressInterval);
- // Set audio and file input values.
- $(element).parent().children('input.media-recorder-fid').val(file.fid);
- $(element).parent().children('input.media-recorder-refresh').trigger('mousedown');
- },
-
- // ********************************************************************
- // * Flash Stop Recording Callback.
- // ********************************************************************
- flashStop: function(element, options) {
- clearInterval(element.recorder.statusInterval);
- Recorder.stop();
- Recorder.upload({
- url: options.recordingPath + '/' + options.fileName,
- audioParam: 'mediaRecorder',
- success: function(response) {
- var file = JSON.parse(response);
- mediaRecorder.prototype.flashRecordFinish(element, options, file);
- },
- });
- var progressCount = 0;
- var progressIndicator = '';
- element.recorder.progressInterval = setInterval(function() {
- progressCount = progressCount + 1;
- progressIndicator = progressIndicator + '.';
- $(element).find('.media-recorder-status').html('Uploading' + progressIndicator);
- if (progressCount === 3) { progressCount = 0; progressIndicator = ''; }
- }, 500);
- }
- };
-
- // ********************************************************************
- // * Private Functions.
- // ********************************************************************
-
- // ********************************************************************
- // * Start getUserMedia Audio Stream.
- // ********************************************************************
- function startUserMedia(element, options, stream) {
- if (webAudioCheck) {
-
- // Audio analyzer variables.
- var analyserNode = null;
- var analyserContext = null;
- var canvas = $("canvas.media-recorder-analyser");
- var canvasWidth = canvas[0].width;
- var canvasHeight = canvas[0].height;
-
- // Create an audio context.
- element.recorder.audioContext = new AudioContext();
-
- // Create a source node.
- mediaStreamSourceNode = element.recorder.audioContext.createMediaStreamSource(stream);
-
- // Create a default gain node.
- gainNode = element.recorder.audioContext.createGain();
- gainNode.gain.value = 0.8;
-
- // Send media stream through gain node.
- mediaStreamSourceNode.connect(gainNode);
-
- // Create analyser node.
- analyserNode = element.recorder.audioContext.createAnalyser();
- analyserNode.fftSize = 2048;
-
- // Send gain node data to analyser node.
- gainNode.connect(analyserNode);
-
- // Create a recorder using the gain node.
- element.recorder.HTML5Recorder = new Recorder(gainNode, {workerPath:Drupal.settings.mediaRecorder.html5url + '/recorderWorker.js'});
-
- // Create a muted gain node.
- zeroGainNode = element.recorder.audioContext.createGain();
- zeroGainNode.gain.value = 0.0;
-
- // Send gain node data through zero gain node.
- gainNode.connect(zeroGainNode);
-
- // Send zero gain data to audio context destination.
- zeroGainNode.connect(element.recorder.audioContext.destination);
-
- // Update audio canvas.
- updateAudioCanvas(element, options);
- }
-
- // ********************************************************************
- // * Update Audio Canvas.
- // ********************************************************************
- function updateAudioCanvas() {
- if (!analyserContext) {
- analyserContext = canvas[0].getContext('2d');
- }
- var spacing = 1;
- var barWidth = 1;
- var numBars = Math.round(canvasWidth / spacing);
- var freqByteData = new Uint8Array(analyserNode.frequencyBinCount);
- analyserNode.getByteFrequencyData(freqByteData);
- analyserContext.clearRect(0, 0, canvasWidth, canvasHeight);
- analyserContext.fillStyle = '#F6D565';
- analyserContext.lineCap = 'round';
- var multiplier = analyserNode.frequencyBinCount / numBars;
- // Draw rectangle for each frequency bin.
- for (var i = 0; i < numBars; ++i) {
- var magnitude = 0;
- var offset = Math.floor(i * multiplier);
- for (var j = 0; j < multiplier; j++) {
- magnitude += freqByteData[offset + j];
- }
- magnitude = magnitude / multiplier;
- var magnitude2 = freqByteData[i * multiplier];
- analyserContext.fillStyle = "hsl( " + Math.round((i * 360) / numBars) + ", 100%, 50%)";
- analyserContext.fillRect(i * spacing, canvasHeight, barWidth, -magnitude);
- }
- rafID = window.requestAnimationFrame(updateAudioCanvas);
- }
- }
-
- // ********************************************************************
- // * Send Blob for getUserMedia recordings.
- // ********************************************************************
- function sendBlob(element, options, blob) {
- var formData = new FormData();
- var fileObj = {};
- formData.append("mediaRecorder", blob);
- var req = new XMLHttpRequest();
- req.upload.onprogress = updateProgress;
- req.addEventListener("progress", updateProgress, false);
- req.addEventListener("load", transferComplete, false);
- req.addEventListener("error", transferFailed, false);
- req.addEventListener("abort", transferCanceled, false);
- req.open("POST", options.recordingPath + '/' + options.fileName, true);
- req.send(formData);
-
- function updateProgress(evt) {
- if (evt.lengthComputable) {
- var percentComplete = (evt.loaded / evt.total) * 100;
- $(element).find('.progressbar').progressbar({
- value: percentComplete
- });
- }
- else {
- $(element).find('.progressbar').progressbar({
- value: 100
- });
- }
- }
-
- function transferComplete(evt) {
- var file = JSON.parse(req.response);
- $(element).parent().children('input.media-recorder-fid').val(file.fid);
- $(element).parent().children('input.media-recorder-refresh').trigger('mousedown');
- }
-
- function transferFailed(evt) {
- onError("An error occurred while transferring the file.");
- }
-
- function transferCanceled(evt) {
- onError("The transfer has been canceled by the user.");
- }
- }
-
- // ********************************************************************
- // * Convert milliseconds to time.
- // ********************************************************************
- function millisecondsToTime(milliSeconds) {
- // Format Current Time
- var milliSecondsDate = new Date(milliSeconds);
- var mm = milliSecondsDate.getMinutes();
- var ss = milliSecondsDate.getSeconds();
- if (mm < 10) {
- mm = "0" + mm;
- }
- if (ss < 10) {
- ss = "0" + ss;
- }
- return mm + ':' + ss;
- }
-
- // ********************************************************************
- // * Error Handler.
- // ********************************************************************
- function onError(msg) {
- alert(msg);
- }
-
-})(jQuery, window, document);
diff --git a/js/media-recorder-api.js b/js/media-recorder-api.js
new file mode 100755
index 0000000..cf96435
--- /dev/null
+++ b/js/media-recorder-api.js
@@ -0,0 +1,305 @@
+/**
+ * @file
+ * Adds an interface between the media recorder jQuery plugin and the drupal media module.
+ */
+
+(function($) {
+ 'use strict';
+
+ Drupal.behaviors.mediaRecorder = {
+ attach: function(context, settings) {
+ $('.field-widget-media-recorder').once().each(function (key, element) {
+
+ // Hide all file field related elements.
+ $(element).find('span.file, span.file-size, .media-recorder-upload, .media-recorder-upload-button, .media-recorder-remove-button').hide();
+
+ // Declare DOM elements.
+ var $element = $(element);
+ var $audioConstraintButton = $element.find('.media-recorder-enable-audio');
+ var $videoConstraintButton = $element.find('.media-recorder-enable-video');
+ var $previewWrapper = $element.find('.media-recorder-preview');
+ var $statusWrapper = $element.find('.media-recorder-status');
+ var $controlsWrapper = $element.find('.media-recorder-controls');
+ var $recordButton = $element.find('.media-recorder-record');
+ var $stopButton = $element.find('.media-recorder-stop');
+
+ // Click handler for enable audio button.
+ $audioConstraintButton.bind('click', function (event) {
+ event.preventDefault();
+ $audioConstraintButton.addClass('active');
+ $videoConstraintButton.removeClass('active');
+ startStream({
+ audio: true,
+ video: false
+ });
+ });
+
+ // Click handler for enable video button.
+ $videoConstraintButton.bind('click', function (event) {
+ event.preventDefault();
+ $audioConstraintButton.removeClass('active');
+ $videoConstraintButton.addClass('active');
+ startStream({
+ audio: true,
+ video: true
+ });
+ });
+
+ // Click handler for record button.
+ $recordButton.bind('click', function (event) {
+ event.preventDefault();
+ Drupal.mediaRecorder.record();
+ });
+
+ // Click handler for stop button.
+ $stopButton.bind('click', function (event) {
+ event.preventDefault();
+ Drupal.mediaRecorder.stop();
+ });
+
+ // Listen for the record event.
+ $(Drupal.mediaRecorder).bind('recordStart', function (event, data) {
+ $recordButton.hide();
+ $stopButton.show();
+
+ $(Drupal.mediaRecorder).trigger('status', 'Recording 00:00');
+
+ var currentSeconds = 0;
+ Drupal.mediaRecorder.statusInterval = setInterval(function () {
+ currentSeconds = currentSeconds + 1;
+ var currentMilliSeconds = new Date(currentSeconds * 1000);
+ var time = millisecondsToTime(currentMilliSeconds);
+ function millisecondsToTime(milliSeconds) {
+ var milliSecondsDate = new Date(milliSeconds);
+ var mm = milliSecondsDate.getMinutes();
+ var ss = milliSecondsDate.getSeconds();
+ if (mm < 10) {
+ mm = "0" + mm;
+ }
+ if (ss < 10) {
+ ss = "0" + ss;
+ }
+ return mm + ':' + ss;
+ }
+ $(Drupal.mediaRecorder).trigger('status', 'Recording ' + time);
+ }, 1000);
+ });
+
+ // Listen for the stop event.
+ $(Drupal.mediaRecorder).bind('recordStop', function (event, data) {
+ $recordButton.show();
+ $stopButton.hide();
+ clearInterval(Drupal.mediaRecorder.statusInterval);
+ $(Drupal.mediaRecorder).trigger('status', 'Press record to start recording.');
+
+ // Append file object data.
+ $element.find('.media-recorder-fid').val(data.fid);
+ $element.find('.media-recorder-refresh').trigger('mousedown');
+ });
+
+ $(Drupal.mediaRecorder).bind('status', function (event, msg) {
+ $statusWrapper.text(msg);
+ });
+
+ // Initial state.
+ $previewWrapper.hide();
+ $controlsWrapper.hide();
+ $(Drupal.mediaRecorder).trigger('status', 'Select audio or video to begin recording.');
+
+ /**
+ * Start user media stream.
+ */
+ function startStream (constraints) {
+ if (Drupal.mediaRecorder.stream) {
+ stopStream();
+ }
+ navigator.getUserMedia(constraints,
+ function(stream) {
+ Drupal.mediaRecorder.stream = stream;
+ Drupal.mediaRecorder.format = constraints.video ? 'webm' : 'ogg';
+ Drupal.mediaRecorder.mimetype = constraints.video ? 'video/webm' : 'audio/ogg';
+ Drupal.mediaRecorder.audioContext = new AudioContext();
+ Drupal.mediaRecorder.analyser = Drupal.mediaRecorder.audioContext.createAnalyser();
+ Drupal.mediaRecorder.microphone = Drupal.mediaRecorder.audioContext.createMediaStreamSource(stream);
+ Drupal.mediaRecorder.analyser.smoothingTimeConstant = 0.75;
+ Drupal.mediaRecorder.analyser.fftSize = 512;
+
+ $previewWrapper.show();
+ $controlsWrapper.show();
+ $stopButton.hide();
+
+ $(Drupal.mediaRecorder).trigger('status', 'Press record to start recording.');
+
+ if (constraints.video) {
+ var video = $('<video muted autoplay src="' + URL.createObjectURL(Drupal.mediaRecorder.stream) + '"></video>');
+ var volumeMeter = $(createVolumeMeter());
+ video.appendTo($previewWrapper).height($previewWrapper.height());
+ volumeMeter.appendTo($previewWrapper).height($previewWrapper.height());
+ video[0].play();
+ $previewWrapper.addClass('video').removeClass('audio');
+ } else {
+ var audioVisualizer = $(createAudioVisualizer());
+ audioVisualizer.appendTo($previewWrapper).height($previewWrapper.height());
+ $previewWrapper.addClass('audio').removeClass('video');
+ }
+ },
+ function(error) {
+ }
+ );
+ }
+
+ /**
+ * Stop user media stream.
+ */
+ function stopStream () {
+ Drupal.mediaRecorder.analyser.disconnect();
+ Drupal.mediaRecorder.microphone.disconnect();
+ Drupal.mediaRecorder.stream.stop();
+ $previewWrapper.text('');
+ $previewWrapper.hide();
+ }
+
+ /**
+ * Create volume meter canvas element that uses getUserMedia stream.
+ */
+ function createVolumeMeter () {
+ var canvas = document.createElement('canvas');
+ var canvasContext = canvas.getContext("2d");
+
+ Drupal.mediaRecorder.meterProcessor = Drupal.mediaRecorder.audioContext.createScriptProcessor(2048, 1, 1);
+ Drupal.mediaRecorder.microphone.connect(Drupal.mediaRecorder.analyser);
+ Drupal.mediaRecorder.analyser.connect(Drupal.mediaRecorder.meterProcessor);
+ Drupal.mediaRecorder.meterProcessor.connect(Drupal.mediaRecorder.audioContext.destination);
+
+ Drupal.mediaRecorder.meterProcessor.onaudioprocess = function() {
+ var freqData = new Uint8Array(Drupal.mediaRecorder.analyser.frequencyBinCount);
+ Drupal.mediaRecorder.analyser.getByteFrequencyData(freqData);
+ var level = Math.max.apply(Math, freqData);
+ canvasContext.clearRect(0, 0, canvas.width, canvas.clientHeight);
+ canvasContext.fillStyle = '#00ff00';
+ canvasContext.fillRect(0, canvas.height - (canvas.height * (level / 255)), canvas.width, canvas.height * (level / 255));
+ };
+
+ canvas.className = 'media-recorder-meter';
+
+ return canvas;
+ }
+
+ /**
+ * Create audio visualizer canvas element that uses getUserMedia stream.
+ */
+ function createAudioVisualizer () {
+ var canvas = document.createElement('canvas');
+ var canvasContext = canvas.getContext("2d");
+ var micStatus = false;
+
+ if (!Drupal.mediaRecorder.audioContext || !Drupal.mediaRecorder.microphone || !Drupal.mediaRecorder.analyser) {
+ var textWidth, textString = 'Audio visualizer unable to initialize';
+
+ canvasContext.font = 'bold 1em Arial';
+ canvasContext.fillStyle = '#ffffff';
+ textWidth = canvasContext.measureText(textString).width;
+ canvasContext.fillText(textString, (canvas.width / 2) - (textWidth / 2), canvas.height / 2);
+
+ return canvas;
+ }
+
+ Drupal.mediaRecorder.visualizerProcessor = Drupal.mediaRecorder.audioContext.createScriptProcessor(2048, 1, 1);
+ Drupal.mediaRecorder.microphone.connect(Drupal.mediaRecorder.analyser);
+ Drupal.mediaRecorder.analyser.connect(Drupal.mediaRecorder.visualizerProcessor);
+ Drupal.mediaRecorder.visualizerProcessor.connect(Drupal.mediaRecorder.audioContext.destination);
+
+ Drupal.mediaRecorder.visualizerProcessor.onaudioprocess = function() {
+ var freqData = new Uint8Array(Drupal.mediaRecorder.analyser.frequencyBinCount);
+ Drupal.mediaRecorder.analyser.getByteFrequencyData(freqData);
+ var volume = getVolume();
+
+ if (volume === 0) {
+ micStatus = false;
+ $(Drupal.mediaRecorder).trigger('status', 'Your mic has a problem. Check your browser or computer audio settings.');
+ } else if (volume && !micStatus) {
+ micStatus = true;
+ $(Drupal.mediaRecorder).trigger('status', 'Press record to start recording.');
+ }
+
+ var barWidth = Math.ceil(canvas.width / (Drupal.mediaRecorder.analyser.frequencyBinCount * 0.5));
+ canvasContext.clearRect(0, 0, canvas.width, canvas.height);
+ for (var i = 0; i < Drupal.mediaRecorder.analyser.frequencyBinCount; i++) {
+ canvasContext.fillStyle = 'hsl(' + i / Drupal.mediaRecorder.analyser.frequencyBinCount * 360 + ', 100%, 50%)';
+ if ((barWidth * i) + barWidth < canvas.width) {
+ canvasContext.fillRect(barWidth * i, canvas.height, barWidth - 1, -(Math.floor((freqData[i] / 255) * canvas.height) + 1));
+ }
+ }
+
+ // Private function for determining current volume.
+ function getVolume() {
+ var values = 0;
+ var length = freqData.length;
+ for (var i = 0; i < length; i++) {
+ values += freqData[i];
+ }
+ return values / length;
+ }
+ };
+
+ canvas.className = 'media-recorder-visualizer';
+
+ return canvas;
+ }
+ });
+
+ /**
+ * Start recording and trigger recording event.
+ */
+ Drupal.mediaRecorder.record = function () {
+
+ // Create a new MediaRecorder.
+ Drupal.mediaRecorder.recorder = new MediaRecorder(Drupal.mediaRecorder.stream);
+ Drupal.mediaRecorder.recorder.ondataavailable = function (e) {
+ var blob = new Blob([e.data], {
+ type: e.data.type || Drupal.mediaRecorder.mimetype
+ });
+ Drupal.mediaRecorder.sendBlob(blob);
+ };
+
+ // Notify server that a recording stream has started.
+ $.ajax({
+ url: Drupal.mediaRecorder.origin + Drupal.settings.basePath + 'media_recorder/record/stream/start',
+ type: 'POST',
+ data: {
+ format: Drupal.mediaRecorder.format
+ },
+ success: function (data, textStatus, jqXHR) {
+ Drupal.mediaRecorder.recorder.start(2000);
+ $(Drupal.mediaRecorder).trigger('recordStart');
+ }
+ });
+ };
+
+ /**
+ * Stop recording and trigger stopped event.
+ */
+ Drupal.mediaRecorder.stop = function () {
+ Drupal.mediaRecorder.recorder.stop();
+
+ // Notify server that a recording stream has stopped.
+ $.ajax({
+ url: Drupal.mediaRecorder.origin + Drupal.settings.basePath + 'media_recorder/record/stream/finish',
+ type: 'POST',
+ data: {},
+ success: function (data, textStatus, jqXHR) {
+ $(Drupal.mediaRecorder).trigger('recordStop', data);
+ }
+ });
+ };
+
+ Drupal.mediaRecorder.sendBlob = function (blob) {
+ var formData = new FormData();
+ formData.append("mediaRecorder", blob);
+ var req = new XMLHttpRequest();
+ req.open('POST', Drupal.mediaRecorder.origin + Drupal.settings.basePath + 'media_recorder/record/stream/record', true);
+ req.send(formData);
+ };
+ },
+ };
+})(jQuery);
diff --git a/js/media-recorder-flash.js b/js/media-recorder-flash.js
new file mode 100755
index 0000000..f67f411
--- /dev/null
+++ b/js/media-recorder-flash.js
@@ -0,0 +1,149 @@
+/**
+ * @file
+ * Adds an interface between the media recorder jQuery plugin and the drupal media module.
+ */
+
+(function($) {
+ 'use strict';
+
+ Drupal.behaviors.mediaRecorder = {
+ attach: function(context, settings) {
+ $('.field-widget-media-recorder').once().each(function (key, element) {
+
+ // Store all data in the element, since we may very well have many recorders on a page.
+ var $element = $(element);
+
+ // Hide file field related markup.
+ $element.find('span.file, span.file-size, .media-recorder-upload, .media-recorder-upload-button, .media-recorder-remove-button').hide();
+
+ // Declare DOM elements.
+ var $constraintsWrapper = $element.find('.media-recorder-constraints');
+ var $previewWrapper = $element.find('.media-recorder-preview');
+ var $statusWrapper = $element.find('.media-recorder-status');
+ var $controlsWrapper = $element.find('.media-recorder-controls');
+ var $recordButton = $element.find('.media-recorder-record');
+ var $stopButton = $element.find('.media-recorder-stop');
+ var $settingsButton = $('<button class="media-recorder-settings">Settings</button>');
+
+ // Initialize flash recorder.
+ Recorder.initialize({
+ swfSrc: settings.basePath + settings.mediaRecorder.swfurl + '/recorder.swf',
+ });
+
+ // Click handler for record button.
+ $recordButton.bind('click', function (event) {
+ event.preventDefault();
+ Drupal.mediaRecorder.record();
+ });
+
+ // Click handler for stop button.
+ $stopButton.bind('click', function (event) {
+ event.preventDefault();
+ Drupal.mediaRecorder.stop();
+ });
+
+ // Click handler for stop button.
+ $settingsButton.bind('click', function (event) {
+ event.preventDefault();
+ Recorder.flashInterface().showFlash();
+ });
+
+ // Listen for the record event.
+ $(Drupal.mediaRecorder).bind('recordStart', function (event, data) {
+ $recordButton.hide();
+ $stopButton.show();
+ $statusWrapper.html('Recording 00:00');
+ });
+
+ // Listen for the progress event.
+ $(Drupal.mediaRecorder).bind('progress', function (event, data) {
+ var time = millisecondsToTime(data);
+ function millisecondsToTime(milliSeconds) {
+ // Format Current Time
+ var milliSecondsDate = new Date(milliSeconds);
+ var mm = milliSecondsDate.getMinutes();
+ var ss = milliSecondsDate.getSeconds();
+ if (mm < 10) {
+ mm = "0" + mm;
+ }
+ if (ss < 10) {
+ ss = "0" + ss;
+ }
+ return mm + ':' + ss;
+ }
+ $statusWrapper.html('Recording ' + time);
+ });
+
+ // Listen for the stop event.
+ $(Drupal.mediaRecorder).bind('recordStop', function (event) {
+ $recordButton.show();
+ $stopButton.hide();
+ });
+
+ $(Drupal.mediaRecorder).bind('uploadStarted', function (event) {
+ $statusWrapper.html('<div>Uploading, please wait...</div>');
+ });
+
+ $(Drupal.mediaRecorder).bind('uploadFinished', function (event, data) {
+ $statusWrapper.html('<div>Press record to start recording.</div>');
+
+ // Append file object data.
+ $element.find('.media-recorder-fid').val(data.fid);
+ $element.find('.media-recorder-refresh').trigger('mousedown');
+ });
+
+ // Initial state.
+ $constraintsWrapper.hide();
+ $previewWrapper.hide();
+ $stopButton.hide();
+ $statusWrapper.html('<div>Press record to start recording.</div>');
+ $controlsWrapper.append($settingsButton);
+ });
+
+ /**
+ * Start recording and trigger recording event.
+ */
+ Drupal.mediaRecorder.record = function () {
+
+ Recorder.record({
+ start: function(){
+
+ // Trigger recording event.
+ $(Drupal.mediaRecorder).trigger('recordStart');
+ },
+ progress: function (milliseconds) {
+
+ // Trigger recording event.
+ $(Drupal.mediaRecorder).trigger('progress', milliseconds);
+ }
+ });
+ };
+
+ /**
+ * Stop recording and trigger stopped event.
+ */
+ Drupal.mediaRecorder.stop = function () {
+
+ Recorder.stop();
+
+ // Trigger uploading event.
+ $(Drupal.mediaRecorder).trigger('uploadStarted');
+
+ Recorder.upload({
+ url: Drupal.mediaRecorder.origin + Drupal.settings.basePath + '/media_recorder/record/file',
+ audioParam: 'mediaRecorder',
+ success: function(response) {
+ var file = JSON.parse(response);
+
+ // Trigger stopped event.
+ $(Drupal.mediaRecorder).trigger('uploadFinished', file);
+ },
+ });
+
+ // Trigger stopped event.
+ $(Drupal.mediaRecorder).trigger('recordStop');
+ };
+
+ },
+ };
+})(jQuery);
diff --git a/js/media-recorder-html5.js b/js/media-recorder-html5.js
new file mode 100755
index 0000000..085216f
--- /dev/null
+++ b/js/media-recorder-html5.js
@@ -0,0 +1,315 @@
+/**
+ * @file
+ * Adds an interface between the media recorder jQuery plugin and the drupal media module.
+ */
+
+(function($) {
+ 'use strict';
+
+ Drupal.behaviors.mediaRecorder = {
+ attach: function(context, settings) {
+ $('.field-widget-media-recorder').once().each(function (key, element) {
+
+ // Hide all file field related elements.
+ $(element).find('span.file, span.file-size, .media-recorder-upload, .media-recorder-upload-button, .media-recorder-remove-button').hide();
+
+ // Declare DOM elements.
+ var $element = $(element);
+ var $audioConstraintButton = $element.find('.media-recorder-enable-audio');
+ var $videoConstraintButton = $element.find('.media-recorder-enable-video');
+ var $previewWrapper = $element.find('.media-recorder-preview');
+ var $statusWrapper = $element.find('.media-recorder-status');
+ var $controlsWrapper = $element.find('.media-recorder-controls');
+ var $recordButton = $element.find('.media-recorder-record');
+ var $stopButton = $element.find('.media-recorder-stop');
+
+ // Click handler for enable audio button.
+ $audioConstraintButton.bind('click', function (event) {
+ event.preventDefault();
+ $audioConstraintButton.addClass('active');
+ $videoConstraintButton.removeClass('active');
+ startStream({
+ audio: true,
+ video: false
+ });
+ });
+
+ // Click handler for enable video button.
+ $videoConstraintButton.bind('click', function (event) {
+ event.preventDefault();
+ $audioConstraintButton.removeClass('active');
+ $videoConstraintButton.addClass('active');
+ startStream({
+ audio: true,
+ video: true
+ });
+ });
+
+ // Click handler for record button.
+ $recordButton.bind('click', function (event) {
+ event.preventDefault();
+ Drupal.mediaRecorder.record();
+ });
+
+ // Click handler for stop button.
+ $stopButton.bind('click', function (event) {
+ event.preventDefault();
+ Drupal.mediaRecorder.stop();
+ });
+
+ // Listen for the record event.
+ $(Drupal.mediaRecorder).bind('recordStart', function (event, data) {
+ $recordButton.hide();
+ $stopButton.show();
+
+ $(Drupal.mediaRecorder).trigger('status', 'Recording 00:00');
+
+ var currentSeconds = 0;
+ Drupal.mediaRecorder.statusInterval = setInterval(function () {
+ currentSeconds = currentSeconds + 1;
+ var currentMilliSeconds = new Date(currentSeconds * 1000);
+ var time = millisecondsToTime(currentMilliSeconds);
+ function millisecondsToTime(milliSeconds) {
+ var milliSecondsDate = new Date(milliSeconds);
+ var mm = milliSecondsDate.getMinutes();
+ var ss = milliSecondsDate.getSeconds();
+ if (mm < 10) {
+ mm = "0" + mm;
+ }
+ if (ss < 10) {
+ ss = "0" + ss;
+ }
+ return mm + ':' + ss;
+ }
+ $(Drupal.mediaRecorder).trigger('status', 'Recording ' + time);
+
+ }, 1000);
+ });
+
+ // Listen for the stop event.
+ $(Drupal.mediaRecorder).bind('recordStop', function (event) {
+ $recordButton.show();
+ $stopButton.hide();
+ clearInterval(Drupal.mediaRecorder.statusInterval);
+ });
+
+ $(Drupal.mediaRecorder).bind('uploadStarted', function (event) {
+ $(Drupal.mediaRecorder).trigger('status', 'Uploading, please wait...');
+ });
+
+ $(Drupal.mediaRecorder).bind('uploadFinished', function (event, data) {
+ $(Drupal.mediaRecorder).trigger('status', 'Press record to start recording.');
+
+ // Append file object data.
+ $element.find('.media-recorder-fid').val(data.fid);
+ $element.find('.media-recorder-refresh').trigger('mousedown');
+ });
+
+ $(Drupal.mediaRecorder).bind('status', function (event, msg) {
+ $statusWrapper.text(msg);
+ });
+
+ // Initial state.
+ $previewWrapper.hide();
+ $controlsWrapper.hide();
+ $(Drupal.mediaRecorder).trigger('status', 'Select audio or video to begin recording.');
+
+ // Disable video.
+ $videoConstraintButton[0].disabled = true;
+ $videoConstraintButton[0].title = 'Video is only enabled on Firefox nightly builds';
+
+ /**
+ * Start user media stream.
+ */
+ function startStream (constraints) {
+ if (Drupal.mediaRecorder.stream) {
+ stopStream();
+ }
+ navigator.getUserMedia(constraints,
+ function(stream) {
+ Drupal.mediaRecorder.stream = stream;
+ Drupal.mediaRecorder.format = constraints.video ? 'webm' : 'ogg';
+ Drupal.mediaRecorder.mimetype = constraints.video ? 'video/webm' : 'audio/ogg';
+ Drupal.mediaRecorder.audioContext = new AudioContext();
+ Drupal.mediaRecorder.analyser = Drupal.mediaRecorder.audioContext.createAnalyser();
+ Drupal.mediaRecorder.microphone = Drupal.mediaRecorder.audioContext.createMediaStreamSource(stream);
+ Drupal.mediaRecorder.analyser.smoothingTimeConstant = 0.75;
+ Drupal.mediaRecorder.analyser.fftSize = 512;
+
+ $previewWrapper.show();
+ $controlsWrapper.show();
+ $stopButton.hide();
+
+ $(Drupal.mediaRecorder).trigger('status', 'Press record to start recording.');
+
+ if (constraints.video) {
+ var video = $('<video muted autoplay src="' + URL.createObjectURL(Drupal.mediaRecorder.stream) + '"></video>');
+ var volumeMeter = $(createVolumeMeter());
+ video.appendTo($previewWrapper).height($previewWrapper.height());
+ volumeMeter.appendTo($previewWrapper).height($previewWrapper.height());
+ video[0].play();
+ $previewWrapper.addClass('video').removeClass('audio');
+ } else {
+ var audioVisualizer = $(createAudioVisualizer());
+ audioVisualizer.appendTo($previewWrapper).height($previewWrapper.height());
+ $previewWrapper.addClass('audio').removeClass('video');
+ }
+ },
+ function(error) {
+ }
+ );
+ }
+
+ /**
+ * Stop user media stream.
+ */
+ function stopStream () {
+ Drupal.mediaRecorder.analyser.disconnect();
+ Drupal.mediaRecorder.microphone.disconnect();
+ Drupal.mediaRecorder.stream.stop();
+ $previewWrapper.text('');
+ $previewWrapper.hide();
+ }
+
+ /**
+ * Create volume meter canvas element that uses getUserMedia stream.
+ */
+ function createVolumeMeter () {
+ var canvas = document.createElement('canvas');
+ var canvasContext = canvas.getContext("2d");
+
+ Drupal.mediaRecorder.meterProcessor = Drupal.mediaRecorder.audioContext.createScriptProcessor(2048, 1, 1);
+ Drupal.mediaRecorder.microphone.connect(Drupal.mediaRecorder.analyser);
+ Drupal.mediaRecorder.analyser.connect(Drupal.mediaRecorder.meterProcessor);
+ Drupal.mediaRecorder.meterProcessor.connect(Drupal.mediaRecorder.audioContext.destination);
+
+ Drupal.mediaRecorder.meterProcessor.onaudioprocess = function() {
+ var freqData = new Uint8Array(Drupal.mediaRecorder.analyser.frequencyBinCount);
+ Drupal.mediaRecorder.analyser.getByteFrequencyData(freqData);
+ var level = Math.max.apply(Math, freqData);
+ canvasContext.clearRect(0, 0, canvas.width, canvas.clientHeight);
+ canvasContext.fillStyle = '#00ff00';
+ canvasContext.fillRect(0, canvas.height - (canvas.height * (level / 255)), canvas.width, canvas.height * (level / 255));
+ };
+
+ canvas.className = 'media-recorder-meter';
+
+ return canvas;
+ }
+
+ /**
+ * Create audio visualizer canvas element that uses getUserMedia stream.
+ */
+ function createAudioVisualizer () {
+ var canvas = document.createElement('canvas');
+ var canvasContext = canvas.getContext("2d");
+ var micStatus = false;
+
+ if (!Drupal.mediaRecorder.audioContext || !Drupal.mediaRecorder.microphone || !Drupal.mediaRecorder.analyser) {
+ var textWidth, textString = 'Audio visualizer unable to initialize';
+
+ canvasContext.font = 'bold 1em Arial';
+ canvasContext.fillStyle = '#ffffff';
+ textWidth = canvasContext.measureText(textString).width;
+ canvasContext.fillText(textString, (canvas.width / 2) - (textWidth / 2), canvas.height / 2);
+
+ return canvas;
+ }
+
+ Drupal.mediaRecorder.visualizerProcessor = Drupal.mediaRecorder.audioContext.createScriptProcessor(2048, 1, 1);
+ Drupal.mediaRecorder.microphone.connect(Drupal.mediaRecorder.analyser);
+ Drupal.mediaRecorder.analyser.connect(Drupal.mediaRecorder.visualizerProcessor);
+ Drupal.mediaRecorder.visualizerProcessor.connect(Drupal.mediaRecorder.audioContext.destination);
+
+ Drupal.mediaRecorder.visualizerProcessor.onaudioprocess = function() {
+ var freqData = new Uint8Array(Drupal.mediaRecorder.analyser.frequencyBinCount);
+ Drupal.mediaRecorder.analyser.getByteFrequencyData(freqData);
+ var volume = getVolume();
+
+ if (volume === 0) {
+ micStatus = false;
+ $(Drupal.mediaRecorder).trigger('status', 'Your mic has a problem. Check your browser or computer audio settings.');
+ } else if (volume && !micStatus) {
+ micStatus = true;
+ $(Drupal.mediaRecorder).trigger('status', 'Press record to start recording.');
+ }
+
+ var barWidth = Math.ceil(canvas.width / (Drupal.mediaRecorder.analyser.frequencyBinCount * 0.5));
+ canvasContext.clearRect(0, 0, canvas.width, canvas.height);
+ for (var i = 0; i < Drupal.mediaRecorder.analyser.frequencyBinCount; i++) {
+ canvasContext.fillStyle = 'hsl(' + i / Drupal.mediaRecorder.analyser.frequencyBinCount * 360 + ', 100%, 50%)';
+ if ((barWidth * i) + barWidth < canvas.width) {
+ canvasContext.fillRect(barWidth * i, canvas.height, barWidth - 1, -(Math.floor((freqData[i] / 255) * canvas.height) + 1));
+ }
+ }
+
+ // Private function for determining current volume.
+ function getVolume() {
+ var values = 0;
+ var length = freqData.length;
+ for (var i = 0; i < length; i++) {
+ values += freqData[i];
+ }
+ return values / length;
+ }
+ };
+
+ canvas.className = 'media-recorder-visualizer';
+
+ return canvas;
+ }
+ });
+
+ /**
+ * Start recording and trigger recording event.
+ */
+ Drupal.mediaRecorder.record = function () {
+
+ // Create a recorder using the gain node.
+ Drupal.mediaRecorder.recorder = new Recorder(Drupal.mediaRecorder.microphone, {workerPath:settings.basePath + settings.mediaRecorder.html5url + '/recorderWorker.js'});
+ Drupal.mediaRecorder.recorder.record();
+
+ // Trigger recording event.
+ $(Drupal.mediaRecorder).trigger('recordStart');
+ };
+
+ /**
+ * Stop recording and trigger stopped event.
+ */
+ Drupal.mediaRecorder.stop = function () {
+
+ // Stop MediaRecorder and delete object (is deletion needed?).
+ Drupal.mediaRecorder.recorder.stop();
+
+ // Export the wav and send to server.
+ Drupal.mediaRecorder.recorder.exportWAV(function(blob) {
+ Drupal.mediaRecorder.sendBlob(blob);
+ });
+
+ // Clear the recorder.
+ Drupal.mediaRecorder.recorder.clear();
+
+ // Trigger stopped event.
+ $(Drupal.mediaRecorder).trigger('recordStop');
+ };
+
+ Drupal.mediaRecorder.sendBlob = function (blob) {
+ var formData = new FormData();
+ var req = new XMLHttpRequest();
+ formData.append("mediaRecorder", blob);
+
+ // Trigger uploading event.
+ $(Drupal.mediaRecorder).trigger('uploadStarted');
+
+ // Send file.
+ req.addEventListener("load", transferComplete, false);
+ req.open('POST', Drupal.mediaRecorder.origin + Drupal.settings.basePath + 'media_recorder/record/file', true);
+ req.send(formData);
+ function transferComplete(evt) {
+ var file = JSON.parse(req.response);
+ $(Drupal.mediaRecorder).trigger('uploadFinished', file);
+ }
+ };
+ },
+ };
+})(jQuery);
diff --git a/js/media-recorder-youtube.js b/js/media-recorder-youtube.js
deleted file mode 100755
index 12ec3b3..0000000
--- a/js/media-recorder-youtube.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * @file
- * Adds an interface between the Youtube upload widget and the Drupal media recorder module.
- */
-
-(function($) {
- Drupal.behaviors.mediaRecorderYoutube = {
- attach: function(context, settings) {
-
- // Load Youtube Upload Widget library.
- $.ajax({
- url: 'https://s.ytimg.com/yts/jsbin/www-widgetapi-vflop0WbJ.js',
- async: false,
- dataType: "script",
- });
- $.ajax({
- url: 'https://www.youtube.com/iframe_api',
- async: false,
- dataType: "script",
- });
-
- var widget;
- var element = $('#youtube-upload-widget');
- var input = element.parent().children('input.media-recorder-youtube');
-
- // Hide specific file related elements.
- element.parent().children('span.file, span.file-size, input.media-recorder-upload, input.media-recorder-upload-button').hide();
-
- // Attaches upload widget to upload div.
- function onYouTubeIframeAPIReady() {
- var statusInterval = window.setInterval(function() {
- console.log(YT);
- if (YT != 'undefined') {
- widget = new YT.UploadWidget('youtube-upload', {
- width: 500,
- events: {
- 'onUploadSuccess': onUploadSuccess,
- }
- });
- clearInterval(statusInterval);
- }
- }, 1000);
-
-
- }
-
- // Callback fired when video is successfully uploaded.
- function onUploadSuccess(event) {
- // Set input value.
- input.val(event.data.videoId);
- // Refresh the media recorder form.
- element.parent().children('input.media-recorder-refresh').trigger('mousedown');
- }
-
- // Create widget.
- onYouTubeIframeAPIReady();
- }
- };
-})(jQuery);
diff --git a/js/media-recorder.browser.js b/js/media-recorder.browser.js
index 66abeac..af2f9fc 100644
--- a/js/media-recorder.browser.js
+++ b/js/media-recorder.browser.js
@@ -22,7 +22,7 @@
file.preview = $('#media-tab-media_recorder .media-recorder-preview .content').html();
// Add to selected media.
- var files = new Array();
+ var files = [];
files.push(file);
Drupal.media.browser.selectMedia(files);
@@ -30,5 +30,5 @@
Drupal.media.browser.submit();
});
}
- }
+ };
})(jQuery);
diff --git a/js/media-recorder.js b/js/media-recorder.js
index f55cc49..0f5d31f 100755
--- a/js/media-recorder.js
+++ b/js/media-recorder.js
@@ -1,21 +1,72 @@
/**
* @file
- * Adds an interface between the media recorder jQuery plugin and the drupal media module.
+ * Loads correct javascript files based on browser features. We do this because of namespace conflicts with external
+ * libraries.
*/
(function($) {
- Drupal.behaviors.mediaRecorder = {
- attach: function(context, settings) {
- $('.media-recorder-wrapper').mediaRecorder({
- 'recordingPath': Drupal.settings.mediaRecorder.recordingPath,
- 'filePath': Drupal.settings.mediaRecorder.filePath,
- 'fileName': Drupal.settings.mediaRecorder.fileName,
- 'timeLimit': Drupal.settings.mediaRecorder.timeLimit,
- 'width': Drupal.settings.mediaRecorder.width,
- 'height': Drupal.settings.mediaRecorder.height,
- 'swfurl': Drupal.settings.mediaRecorder.swfurl,
- 'html5url': Drupal.settings.mediaRecorder.html5url,
+ 'use strict';
+
+ // Add mediaRecorder object to Drupal.
+ Drupal.mediaRecorder = Drupal.mediaRecorder || {};
+
+ // Normalize features.
+ navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
+ window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;
+ window.URL = window.URL || window.webkitURL;
+ Drupal.mediaRecorder.origin = window.location.origin || window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
+
+ // Feature detection.
+ var getUserMediaCheck = typeof(navigator.getUserMedia) === 'function';
+ var mediaRecorderCheck = typeof(window.MediaRecorder) === 'function';
+ var webAudioCheck = typeof(window.AudioContext) === 'function';
+ var swfobjectCheck = typeof(window.swfobject) === 'object';
+ var flashVersionCheck = swfobjectCheck ? (swfobject.getFlashPlayerVersion().major >= 10) : false;
+
+ // Check to see that browser can use the recorder.
+ if ((getUserMediaCheck && webAudioCheck) || (flashVersionCheck && swfobjectCheck)) {
+
+ // Use the MediaRecorder API. Currently only works in firefox.
+ if (getUserMediaCheck && webAudioCheck && mediaRecorderCheck) {
+ $.ajax({
+ url: Drupal.settings.basePath + Drupal.settings.mediaRecorder.modulePath + '/media-recorder-api.js',
+ async: false,
+ dataType: 'script'
});
}
- };
+
+ // Use HTML5 features (Web Audio API).
+ else if (getUserMediaCheck && webAudioCheck && !mediaRecorderCheck) {
+ $.ajax({
+ url: Drupal.settings.basePath + Drupal.settings.mediaRecorder.html5url + '/recorder.js',
+ async: false,
+ dataType: 'script'
+ });
+ $.ajax({
+ url: Drupal.settings.basePath + Drupal.settings.mediaRecorder.modulePath + '/media-recorder-html5.js',
+ async: false,
+ dataType: 'script'
+ });
+ }
+
+ // Use Flash.
+ else if (flashVersionCheck) {
+ $.ajax({
+ url: Drupal.settings.basePath + Drupal.settings.mediaRecorder.swfurl + '/recorder.js',
+ async: false,
+ dataType: 'script'
+ });
+ $.ajax({
+ url: Drupal.settings.basePath + Drupal.settings.mediaRecorder.modulePath + '/media-recorder-flash.js',
+ async: false,
+ dataType: 'script'
+ });
+ }
+ }
+
+ // Otherwise just use the basic file field.
+ else {
+ $('.field-widget-media-recorder').find('.media-recorder-wrapper').hide();
+ }
+
})(jQuery);
diff --git a/media_recorder.module b/media_recorder.module
index 276c59c..a9ab48d 100755
--- a/media_recorder.module
+++ b/media_recorder.module
@@ -74,16 +74,40 @@ function media_recorder_menu() {
'access arguments' => array('create'),
'type' => MENU_LOCAL_TASK,
);
- $items['media_recorder/record/%'] = array(
+ $items['media_recorder/record/file'] = array(
'title' => 'Record',
'description' => 'Record a video or audio file.',
- 'page callback' => 'media_recorder_record',
+ 'page callback' => 'media_recorder_record_file',
+ 'access callback' => 'file_entity_access',
+ 'access arguments' => array('create'),
+ 'type' => MENU_CALLBACK,
+ );
+ $items['media_recorder/record/stream/start'] = array(
+ 'title' => 'Record',
+ 'description' => 'Record a video or audio file as a stream.',
+ 'page callback' => 'media_recorder_record_stream_start',
+ 'access callback' => 'file_entity_access',
+ 'access arguments' => array('create'),
+ 'type' => MENU_CALLBACK,
+ );
+ $items['media_recorder/record/stream/record'] = array(
+ 'title' => 'Record',
+ 'description' => 'Record a video or audio file as a stream.',
+ 'page callback' => 'media_recorder_record_stream_record',
+ 'access callback' => 'file_entity_access',
+ 'access arguments' => array('create'),
+ 'type' => MENU_CALLBACK,
+ );
+ $items['media_recorder/record/stream/finish'] = array(
+ 'title' => 'Record',
+ 'description' => 'Record a video or audio file as a stream.',
+ 'page callback' => 'media_recorder_record_stream_finish',
'access callback' => 'file_entity_access',
'access arguments' => array('create'),
'type' => MENU_CALLBACK,
);
$items['admin/config/media/mediarecorder'] = array(
- 'title' => 'Media Recorder',
+ 'title' => 'Media recorder',
'description' => 'Configure the media recorder.',
'page callback' => 'drupal_get_form',
'page arguments' => array('media_recorder_admin_form'),
@@ -105,33 +129,251 @@ function media_recorder_help($path, $arg) {
}
/**
+ * Implements hook_theme().
+ */
+function media_recorder_theme($existing, $type, $theme, $path) {
+ return array(
+ 'media_recorder' => array(
+ 'variables' => array(),
+ 'template' => 'media-recorder',
+ 'path' => drupal_get_path('module', 'media_recorder') . '/theme',
+ ),
+ );
+}
+
+/**
* Menu callback for recording a media file.
*/
-function media_recorder_record() {
+function media_recorder_record_stream_start() {
+
+ // Set session status as open.
+ $_SESSION['media_recorder']['status'] = TRUE;
- // Get filename.
- $filename = arg(2);
- // File data is sent using XHR.
- if (isset($_FILES['mediaRecorder']['tmp_name'])) {
- $url = $_FILES['mediaRecorder']['tmp_name'];
+ // Delete the file if for some reason file_unmanaged_move was not successful.
+ if (file_exists($_SESSION['media_recorder']['tempnam'])) {
+ unlink($_SESSION['media_recorder']['tempnam']);
+ }
+
+ // Reset temporary file name in case this wasn't reset after stream finished.
+ unset($_SESSION['media_recorder']['tempnam']);
+
+ // Reset file format.
+ unset($_SESSION['media_recorder']['format']);
+
+ // Create a new temporary file to save streamed data.
+ try {
+ $_SESSION['media_recorder']['tempnam'] = drupal_tempnam('temporary://', 'mediaRecorder_');
+ if (!$_SESSION['media_recorder']['tempnam']) {
+ throw new Exception("Unable to create temporary file.");
+ }
+ }
+ catch (Exception $e) {
+ header('HTTP/1.0 419 Custom Error');
+ echo drupal_json_output($e->getMessage());
+ exit;
+ }
+
+ // Get file format.
+ try {
+ $_SESSION['media_recorder']['format'] = $_POST['format'];
+ if (!$_SESSION['media_recorder']['format']) {
+ throw new Exception("Unable to get file format.");
+ }
+ }
+ catch (Exception $e) {
+ header('HTTP/1.0 419 Custom Error');
+ echo drupal_json_output($e->getMessage());
+ exit;
+ }
+}
+
+/**
+ * Menu callback for recording a media file.
+ */
+function media_recorder_record_stream_record() {
+
+ // Return if there is no streaming session open.
+ if (!$_SESSION['media_recorder']['status']) {
+ return;
+ }
+
+ // Get file blob url.
+ $url = isset($_FILES['mediaRecorder']['tmp_name']) ? $_FILES['mediaRecorder']['tmp_name'] : '';
+
+ // Append streaming data to temporary file while recording is not finished.
+ if (!empty($url)) {
+
+ try {
+ $data = file_get_contents($url);
+ if (!$data) {
+ throw new Exception("Streaming data file is empty.");
+ }
+ }
+ catch (Exception $e) {
+ header('HTTP/1.0 419 Custom Error');
+ echo drupal_json_output($e->getMessage());
+ echo drupal_json_output($url);
+ exit;
+ }
+
+ try {
+ $fp = fopen($_SESSION['media_recorder']['tempnam'], 'a');
+ if (!$fp) {
+ throw new Exception("Unable to open temporary file. Please check that your file permissions are set correctly.");
+ }
+ }
+ catch (Exception $e) {
+ header('HTTP/1.0 419 Custom Error');
+ echo drupal_json_output($e->getMessage());
+ exit;
+ }
+
+ if ($data && $fp) {
+ try {
+ fwrite($fp, $data);
+ fclose($fp);
+ }
+ catch (Exception $e) {
+ header('HTTP/1.0 419 Custom Error');
+ echo drupal_json_output($e->getMessage());
+ exit;
+ }
+ }
+
+ echo drupal_json_output($_FILES['mediaRecorder']);
+ }
+
+ else {
+ header('HTTP/1.0 419 Custom Error');
+ echo drupal_json_output('Temporary file not found.');
+ echo drupal_json_output($url);
+ exit;
+ }
+}
+
+/**
+ * Menu callback for recording a media file.
+ */
+function media_recorder_record_stream_finish() {
+
+ // Check that file exists.
+ if (file_exists($_SESSION['media_recorder']['tempnam'])) {
+
+ // Change the file name.
+ file_unmanaged_move($_SESSION['media_recorder']['tempnam'], $_SESSION['media_recorder']['tempnam'] . '.' . $_SESSION['media_recorder']['format']);
+
+ $file = file_uri_to_object($_SESSION['media_recorder']['tempnam'] . '.' . $_SESSION['media_recorder']['format']);
+ $file->status = 0;
+ file_save($file);
+
+ // Delete the file if for some reason file_unmanaged_move was not successful.
+ if (file_exists($_SESSION['media_recorder']['tempnam'])) {
+ unlink($_SESSION['media_recorder']['tempnam']);
+ }
+
+ $_SESSION['media_recorder']['status'] = FALSE;
+
+ // Return file information.
+ echo drupal_json_output($file);
+ }
+
+ else {
+ header('HTTP/1.0 419 Custom Error');
+ echo drupal_json_output(t('File is empty.'));
+ exit;
+ }
+}
+
+/**
+ * Menu callback for recording a media file.
+ */
+function media_recorder_record_file() {
+
+ // Get file blob url.
+ $url = isset($_FILES['mediaRecorder']['tmp_name']) ? $_FILES['mediaRecorder']['tmp_name'] : '';
+
+ // Attempt to save file data.
+ if (!empty($url)) {
+
+ // Create a new temporary file to save data.
+ try {
+ $uri = drupal_tempnam('temporary://', 'mediaRecorder_');
+ if (!$uri) {
+ throw new Exception("Unable to create temporary file.");
+ }
+ }
+ catch (Exception $e) {
+ header('HTTP/1.0 419 Custom Error');
+ echo drupal_json_output($e->getMessage());
+ exit;
+ }
+
+ try {
+ $data = file_get_contents($url);
+ if (!$data) {
+ throw new Exception("There was no data sent.");
+ }
+ }
+ catch (Exception $e) {
+ header('HTTP/1.0 419 Custom Error');
+ echo drupal_json_output($e->getMessage());
+ echo drupal_json_output($url);
+ exit;
+ }
+
+ try {
+ $fp = fopen($uri, 'a');
+ if (!$fp) {
+ throw new Exception("Unable to open temporary file. Please check that your file permissions are set correctly.");
+ }
+ }
+ catch (Exception $e) {
+ header('HTTP/1.0 419 Custom Error');
+ echo drupal_json_output($e->getMessage());
+ exit;
+ }
+
+ if ($data && $fp) {
+ try {
+ fwrite($fp, $data);
+ fclose($fp);
+ }
+ catch (Exception $e) {
+ header('HTTP/1.0 419 Custom Error');
+ echo drupal_json_output($e->getMessage());
+ exit;
+ }
+ }
}
- // Process file.
- if (!empty($filename) && !empty($url)) {
- // Get file data.
- $uri = 'temporary://' . $filename;
- $data = file_get_contents($url);
+ else {
+ header('HTTP/1.0 419 Custom Error');
+ echo drupal_json_output('No file was sent.');
+ exit;
+ }
+
+ // Check that file exists.
+ if (file_exists($uri)) {
+
+ file_unmanaged_move($uri, $uri . '.wav');
- // Save file as temporary file.
- $file = file_save_data($data, $uri, FILE_EXISTS_REPLACE);
+ $file = file_uri_to_object($uri . '.wav');
$file->status = 0;
file_save($file);
+ // Delete the file if for some reason file_unmanaged_move was not successful.
+ if (file_exists($uri)) {
+ unlink($uri);
+ }
+
// Return file information.
echo drupal_json_output($file);
}
+
else {
- echo FALSE;
+ header('HTTP/1.0 419 Custom Error');
+ echo drupal_json_output(t('There was an error saving the file.'));
+ exit;
}
}
@@ -164,12 +406,11 @@ function media_recorder_add($form, &$form_state) {
'uri_scheme' => 'public',
'display_default' => 0,
),
- 'cardinality' => 1,
);
$instance = array(
'settings' => array(
'file_directory' => variable_get('media_recorder_upload_directory', ''),
- 'file_extensions' => 'wav mp3 m4a mov m4v mp4 mpeg mpg avi ogg oga ogv',
+ 'file_extensions' => 'wav mp3 m4a mov m4v mp4 mpeg mpg avi ogg oga ogv webm',
),
'widget' => array(
'settings' => array(
@@ -181,13 +422,14 @@ function media_recorder_add($form, &$form_state) {
$element['#language'] = $langcode;
$element['#delta'] = $delta;
$element['#id'] = 'edit-field-media-recorder-und-0';
+ $element['#attributes']['class'][] = 'field-widget-media-recorder';
$element['#field_parents'] = array();
$element['#columns'] = array('fid', 'display', 'description');
$element['#title'] = t('Media Recorder');
$element['#description'] = '';
$element['#required'] = TRUE;
$element['#upload_location'] = 'public://' . variable_get('media_recorder_upload_directory', '');
- $element['#upload_validators']['file_validate_extensions'][] = 'wav';
+ $element['#upload_validators']['file_validate_extensions'][] = 'wav mp3 m4a mov m4v mp4 mpeg mpg avi ogg oga ogv webm';
// Add title field.
$form['#title'] = t('Title');
@@ -275,7 +517,6 @@ function media_recorder_field_widget_form(&$form, &$form_state, $field, $instanc
$elements = file_field_widget_form($form, $form_state, $field, $instance, $langcode, $items, $delta, $element);
// Alter process callbacks.
- $element_info = element_info('managed_file');
foreach (element_children($elements) as $delta) {
$elements[$delta]['#process'][] = 'media_recorder_field_widget_form_process';
}
@@ -289,131 +530,88 @@ function media_recorder_field_widget_form(&$form, &$form_state, $field, $instanc
*/
function media_recorder_field_widget_form_process($element, &$form_state, $form) {
- // Get field info.
- $field_name = $element['#field_name'];
- $langcode = $element['#language'];
- $delta = $element['#delta'];
-
- // Get fid and set media-recorder specific class.
- $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : (isset($form_state['values'][$field_name][$langcode][$delta]['fid']) ? $form_state['values'][$field_name][$langcode][$delta]['fid'] : 0);
+ // Alter file elements.
$element['fid']['#attributes']['class'][] = 'media-recorder-fid';
-
- // Add file preview to render array.
- if ($fid) {
- $file = file_load($fid);
- $element['preview'] = array(
- 'file' => file_view($file),
- '#weight' => 50,
- '#prefix' => '<div class="media-recorder-preview">',
- '#suffix' => '</div>',
- );
- if ($file->filemime == 'video/youtube') {
- // Remove file element upload validators.
- unset($element['#upload_validators']);
- $form_state['values'][$field_name][$langcode][$delta]['toggle'] = 'youtube';
- }
- }
-
- // Alter file managed buttons.
$element['upload']['#attributes']['class'][] = 'media-recorder-upload';
$element['upload_button']['#attributes']['class'][] = 'media-recorder-upload-button';
$element['remove_button']['#attributes']['class'][] = 'media-recorder-remove-button';
$element['remove_button']['#weight'] = 100;
- // Append a description.
- $element['#description'] .= '<br />Before recording please fill in content title, which is used to generate the recording filename.';
-
- // Add toggle buttons.
- $toggle = isset($form_state['values'][$field_name][$langcode][$delta]['toggle']) ? $form_state['values'][$field_name][$langcode][$delta]['toggle'] : 'record';
- if (module_exists('media_youtube')) {
- $element['toggle'] = array(
- '#type' => 'radios',
- '#options' => array(
- 'record' => t('Recorder'),
- 'youtube' => t('Youtube'),
- ),
- '#default_value' => $toggle,
- '#ajax' => array(
- 'callback' => 'media_recorder_field_widget_form_process_ajax_refresh',
- 'wrapper' => $element['#id'] . '-ajax-wrapper',
- ),
- '#attributes' => array(
- 'class' => array('media-recorder-toggle'),
- ),
- );
- }
-
- // Use the HTML5/Flash recorder.
- if ($toggle == 'record') {
-
- // Add media recorder markup.
- $element['record']['#markup'] = '<div class="media-recorder-wrapper"></div>';
-
- // Add validation handler to beginning of validation handler stack.
- array_unshift($element['#element_validate'], 'media_recorder_field_widget_form_process_record_validate');
-
- // Add javascript libraries.
- $element['#attached']['library'][] = array('system', 'ui.progressbar');
- $element['#attached']['library'][] = array('system', 'ui.slider');
- $element['#attached']['libraries_load'][] = array('swfobject');
- $element['#attached']['js'][] = drupal_get_path('module', 'media_recorder') . '/js/media-recorder.js';
- $element['#attached']['js'][] = drupal_get_path('module', 'media_recorder') . '/js/jquery.mediaRecorder.js';
- }
-
- // Use the youtube upload widget.
- if ($toggle == 'youtube') {
+ // Add validation handler to beginning of validation handler stack.
+ array_unshift($element['#element_validate'], 'media_recorder_field_widget_form_process_record_validate');
- // Add hidden fields for youtube.
- $element['youtube']['#type'] = 'hidden';
- $element['youtube']['#attributes']['class'][] = 'media-recorder-youtube';
-
- // Add youtube recorder markup.
- $element['youtube_upload']['#markup'] = '<div id="youtube-upload-widget"><div id="youtube-player-wrapper"><div id="youtube-player"></div></div><div id="youtube-upload-wrapper"><div id="youtube-upload"></div></div></div>';
-
- // Add validation handler while overwriting normal file validation handlers.
- $element['#element_validate'] = array('media_recorder_field_widget_form_process_youtube_validate');
-
- // Remove file element upload validators.
- unset($element['#upload_validators']);
-
- // Add javascript libraries.
- $element['#attached']['js'][] = drupal_get_path('module', 'media_recorder') . '/js/media-recorder-youtube.js';
- }
+ // Add media recorder.
+ $element['record'] = array(
+ '#theme' => 'media_recorder',
+ '#title' => 'Record',
+ );
// Add hidden refresh submit, which is triggered on record finish.
// This will rebuild the form with new file preview.
$element['refresh'] = array(
'#type' => 'submit',
'#executes_submit_callback' => FALSE,
+ '#limit_validation_errors' => array(),
'#value' => t('Refresh'),
'#ajax' => array(
'callback' => 'media_recorder_field_widget_form_process_ajax_refresh',
- 'wrapper' => $element['#id'] . '-ajax-wrapper',
+ 'wrapper' => $element['#id'] . '-file-preview-ajax-wrapper',
),
'#attributes' => array(
- 'class' => array('js-hide', 'media-recorder-refresh'),
+ 'style' => 'display: none;',
+ 'class' => array('media-recorder-refresh'),
),
);
- // Add javascript settings.
- $element['#attached']['js'][] = array(
- 'data' => array(
- 'mediaRecorder' => array(
- 'recordingPath' => url('media_recorder/record'),
- 'fileName' => uniqid() . '.wav',
- 'filePath' => 'temporary://',
- 'timeLimit' => variable_get('media_recorder_timelimit', 300) * 1000,
- 'width' => variable_get('media_recorder_width', 300),
- 'height' => variable_get('media_recorder_height', 100),
- 'swfurl' => libraries_get_path('recorder.js', TRUE),
- 'html5url' => libraries_get_path('Recorderjs', TRUE),
- ),
+ // Add file preview to render array.
+ $element['preview'] = array(
+ '#type' => 'container',
+ '#weight' => 50,
+ '#attributes' => array(
+ 'class' => array('media-recorder-file-preview'),
),
- 'type' => 'setting',
+ '#id' => $element['#id'] . '-file-preview-ajax-wrapper',
);
- // Add custom css.
- $element['#attached']['css'][] = drupal_get_path('module', 'media_recorder') . '/css/media-recorder.css';
+ // Only display file if fid exists.
+ $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : (isset($form_state['values'][$field_name][$langcode][$delta]['fid']) ? $form_state['values'][$field_name][$langcode][$delta]['fid'] : 0);
+ if ($fid) {
+ $file = file_load($fid);
+ $element['preview']['file'] = file_view($file);
+ $element['preview']['#type'] = 'fieldset';
+ $element['preview']['#title'] = 'File Preview';
+ }
+
+ // Add attached files and settings.
+ $element['#attached'] = array(
+ 'libraries_load' => array(
+ array('swfobject'),
+ ),
+ 'js' => array(
+ array(
+ 'type' => 'file',
+ 'data' => drupal_get_path('module', 'media_recorder') . '/js/media-recorder.js',
+ 'scope' => 'footer',
+ ),
+ array(
+ 'type' => 'setting',
+ 'data' => array(
+ 'mediaRecorder' => array(
+ 'swfurl' => libraries_get_path('recorder.js'),
+ 'html5url' => libraries_get_path('Recorderjs'),
+ 'modulePath' => drupal_get_path('module', 'media_recorder') . '/js',
+ ),
+ ),
+ 'scope' => 'header',
+ ),
+ ),
+ 'css' => array(
+ array(
+ 'type' => 'file',
+ 'data' => drupal_get_path('module', 'media_recorder') . '/css/media-recorder.css',
+ ),
+ ),
+ );
return $element;
}
@@ -430,11 +628,9 @@ function media_recorder_field_widget_form_process_ajax_refresh($form, &$form_sta
// Get the file field element.
$parents = $form_state['triggering_element']['#array_parents'];
array_pop($parents);
- array_pop($parents);
- array_pop($parents);
$element = drupal_array_get_nested_value($form, $parents);
- return $element;
+ return $element['preview'];
}
/**
@@ -484,7 +680,6 @@ function media_recorder_field_widget_form_process_record_validate(&$element, &$f
if (empty($file_validate_size_errors)) {
// Prepare directory.
- $path_info = pathinfo($file->uri);
if (module_exists('token')) {
$upload_location = token_replace($element['#upload_location']);
}
@@ -508,7 +703,6 @@ function media_recorder_field_widget_form_process_record_validate(&$element, &$f
array(
'fid' => $file->fid,
'display' => TRUE,
- 'toggle' => 'record',
)
);
drupal_array_set_nested_value(
@@ -517,7 +711,6 @@ function media_recorder_field_widget_form_process_record_validate(&$element, &$f
array(
'fid' => $file->fid,
'display' => TRUE,
- 'toggle' => 'record',
)
);
}
@@ -530,88 +723,3 @@ function media_recorder_field_widget_form_process_record_validate(&$element, &$f
}
}
}
-
-/**
- * Custom validation callback.
- * @see media_recorder_field_widget_form_process()
- */
-function media_recorder_field_widget_form_process_youtube_validate($element, &$form_state) {
-
- // Get field information.
- $field_name = $element['#parents'][0];
- $langcode = $element['#parents'][1];
- $delta = isset($element['#parents'][2]) ? $element['#parents'][2] : 0;
-
- // Get field values.
- $title = '';
- $fid = !empty($form_state['values'][$field_name][$langcode][$delta]['fid']) ? $form_state['values'][$field_name][$langcode][$delta]['fid'] : 0;
- $youtube = !empty($form_state['values'][$field_name][$langcode][$delta]['youtube']) ? $form_state['values'][$field_name][$langcode][$delta]['youtube'] : '';
-
- // Grab title from entity if available.
- $title = '';
- if (isset($form_state['values']['title']) && !empty($form_state['values']['title'])) {
- $title = $form_state['values']['title'];
- }
-
- // Add custom logic for comments.
- elseif (isset($form_state['comment']) && is_object($form_state['comment'])) {
- // Use the comment subject value if present.
- if (isset($form_state['values']['subject']) && !empty($form_state['values']['subject'])) {
- $title = $form_state['values']['subject'];
- }
- // Otherwise use the node title.
- elseif (isset($form_state['comment']->nid) && is_numeric($form_state['comment']->nid)) {
- $node = node_load($form_state['values']['nid']);
- $title = t('Comment on @title', array('@title' => $node->title));
- }
- }
-
- if (is_string($youtube) && !empty($youtube)) {
-
- // Create a youtube link from youtube video value.
- $embed_code = 'http://youtube.com/watch?v=' . $youtube;
-
- // Try saving the youtube file using media_internet_get_provider().
- try {
- $provider = media_internet_get_provider($embed_code);
- $file = $provider->save();
- }
- catch (Exception $e) {
- form_set_error('youtube', $e->getMessage());
- return;
- }
-
- // Check that file saved correctly.
- if (!$file->fid) {
- form_set_error('youtube', t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $embed_code)));
- return;
- }
- else {
- // Save as temporary file with new title.
- $file->filename = trim($title);
- $file->display = TRUE;
- $file->status = 0;
- $file = file_save($file);
-
- // Set file field input & values.
- drupal_array_set_nested_value(
- $form_state['input'],
- $element['#parents'],
- array(
- 'fid' => $file->fid,
- 'display' => TRUE,
- 'toggle' => 'youtube',
- )
- );
- drupal_array_set_nested_value(
- $form_state['values'],
- $element['#parents'],
- array(
- 'fid' => $file->fid,
- 'display' => TRUE,
- 'toggle' => 'youtube',
- )
- );
- }
- }
-}
diff --git a/theme/media-recorder.tpl.php b/theme/media-recorder.tpl.php
new file mode 100644
index 0000000..2dbc3e4
--- /dev/null
+++ b/theme/media-recorder.tpl.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Video media recorder template.
+ *
+ * @see template_preprocess()
+ * @see template_process()
+ */
+
+?>
+
+<div class="media-recorder-wrapper">
+ <div class="media-recorder">
+ <div class="media-recorder-constraints">
+ <button class="media-recorder-enable-audio" title="Click to enable audio recorder.">Audio</button>
+ <button class="media-recorder-enable-video" title="Click to enable video recorder.">Video</button>
+ </div>
+ <div class="media-recorder-preview"></div>
+ <div class="media-recorder-status"></div>
+ <div class="media-recorder-controls">
+ <button class="media-recorder-record" title="Click to start recording.">Record</button>
+ <button class="media-recorder-stop" title="Click to stop recording.">Stop</button>
+ </div>
+ </div>
+</div>