diff --git a/README.txt b/README.txt index 9e2f6c3a42144a52a6e97f26043c18efa90a0e0f..d90b2ba0bce10b3dabb669e2f3cca82d37a6f1b4 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 c7c62efc7f5eee2d9236f26870043cfdf7d0b610..97c3aeea2187674a9427ea537d02c2976f7872e6 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 869212d508fca1a8547eddddaf404accb55d5e74..0000000000000000000000000000000000000000 Binary files a/images/mic.png and /dev/null differ diff --git a/images/settings.png b/images/settings.png deleted file mode 100755 index 348a8f944bc8d612fa50bfeb5bfde1290530d151..0000000000000000000000000000000000000000 Binary files a/images/settings.png and /dev/null differ diff --git a/includes/media_recorder.admin.inc b/includes/media_recorder.admin.inc index 47217a46c7a17b58011247c9761c176fd5932e53..f7d47fcd0f1d8beb45aa89f5b5d7b737671854de 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 fb68623adf76a7c3b8bd5cdc63634921c9c7846b..0000000000000000000000000000000000000000 --- 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($('
Flash 10 or higher must be installed in order to record.
')); - } - // 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 = $('
').width(options.width).height(options.height); - element.recorder.controls = $('
'); - element.recorder.canvas = $(''); - element.recorder.status = $('
00:00 / ' + millisecondsToTime(options.timeLimit) + '
'); - 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 = $('
Record
') - .click(function(){ - mediaRecorder.prototype.record(element, options); - }); - - // Set HTML5 variables. - element.recorder.volume = $('
') - .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('
'); - 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 = $('
').width(options.width).height(options.height); - element.recorder.controls = $('
'); - element.recorder.canvas = $('
'); - element.recorder.status = $('
00:00 / ' + millisecondsToTime(options.timeLimit) + '
'); - element.recorder.statusInterval = 0; - element.recorder.meterInterval = 0; - element.recorder.progressInterval = 0; - - // Add button handlers. - element.recorder.controls.record = $('
Record
') - .click(function(){ - mediaRecorder.prototype.flashRecord(element, options); - }); - - // Add flash recorder markup. - element.flash = $('
'); - element.recorder.micSettings = $('
Settings
') - .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('

Recording...

'); - }, - - // ******************************************************************** - // * 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 0000000000000000000000000000000000000000..cf96435fff40478ee06e378181e33fedde2bea13 --- /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 = $(''); + 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 0000000000000000000000000000000000000000..f67f411f93ef14a3befeb8b390482610e9207d9b --- /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 = $(''); + + // 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('
Uploading, please wait...
'); + }); + + $(Drupal.mediaRecorder).bind('uploadFinished', function (event, data) { + $statusWrapper.html('
Press record to start recording.
'); + + // 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('
Press record to start recording.
'); + $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 0000000000000000000000000000000000000000..085216f8364dd502120e7dae8f76509d07a194b8 --- /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 = $(''); + 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 12ec3b3bc22fe2b50df6699d3ed1ab3dd1c8ad5b..0000000000000000000000000000000000000000 --- 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 66abeac5b9ad54a14a356bee5dfb662e11789da6..af2f9fcb63cb33600b60b22706c04580b28a995e 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 f55cc496b09da705521fbc5b9b00b921b5776a62..0f5d31f03489eab5f00e1ac01796fe6eeff74b76 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 276c59c067a0a6e666e1f1d592192083e67c6d97..a9ab48d94e52ff6ed32d6142dab92481805db6a7 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'), @@ -104,34 +128,252 @@ 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' => '
', - '#suffix' => '
', - ); - 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'] .= '
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'] = '
'; - - // 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'] = '
'; - - // 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 0000000000000000000000000000000000000000..2dbc3e41eed530f5247406cfe239a2726ceff31d --- /dev/null +++ b/theme/media-recorder.tpl.php @@ -0,0 +1,26 @@ + + +
+
+
+ + +
+
+
+
+ + +
+
+