diff --git a/README.txt b/README.txt index d90b2ba0bce10b3dabb669e2f3cca82d37a6f1b4..cc269070aeccb77b75f9a48b7b430e695982d465 100644 --- a/README.txt +++ b/README.txt @@ -8,8 +8,8 @@ REQUIREMENTS * Media module - https://drupal.org/project/media * Libraries module - https://drupal.org/project/libraries * RecorderJS library - https://github.com/mattdiamond/Recorderjs - * Recorder.js library - https://github.com/jwagener/recorder.js - * SWFObject library - http://code.google.com/p/swfobject + * FlashWavRecorder library - https://github.com/michalstocki/FlashWavRecorder + * SWFObject library - https://github.com/swfobject/swfobject INSTALLATION ------------ @@ -18,9 +18,10 @@ INSTALLATION 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 & 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/recorder.js/recorder.js. +2. Install the SWFObject & FlashWavRecorder libraries in sites/all/libraries. The + files swfobject.js and recorder.js should be at: + swfobject.js: sites/{site}/libraries/swfobject/swfobject/swfobject.js + recorder.js: sites/{site}/libraries/FlashWavRecorder/html/js/recorder.js 3. Install dependencies and media recorder module as per: https://drupal.org/documentation/install/modules-themes/modules-7 diff --git a/css/media-recorder.css b/css/media-recorder.css old mode 100755 new mode 100644 index 97c3aeea2187674a9427ea537d02c2976f7872e6..e95960863ce2f8dce4c97b42b23230d0a5de8851 --- a/css/media-recorder.css +++ b/css/media-recorder.css @@ -1,32 +1,3 @@ -div.media-recorder-toggle { - margin-bottom: 2px; - padding: 0; -} - -div.media-recorder-toggle .form-item { - margin: 0; - padding: 0; - display: inline-block; -} - -div.media-recorder-toggle input { - display: none; -} - -div.media-recorder-toggle input:checked + label.option { - background: #323232; -} - -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-wrapper { padding: 10px; margin: 0; @@ -50,63 +21,29 @@ div.media-recorder-toggle label.option { .media-recorder-preview { background: #222222; padding: 0; - margin: 5px 0; + margin: 0; + text-align: center; } -.media-recorder-preview canvas.media-recorder-visualizer { +.media-recorder-preview canvas.media-recorder-meter { width: 100%; - height: 100%; padding: 0; margin: 0; - display: inline-block; - vertical-align: middle; + vertical-align: bottom; } -.media-recorder-preview canvas.media-recorder-meter { - width: 10%; - height: 100%; - padding: 0; +.media-recorder-video { + width: 100%; + padding: 5px 0; margin: 0; - display: inline-block; vertical-align: middle; } -.media-recorder-preview video { - width: 90%; - padding: 0; +.media-recorder-audio { + width: 100%; + padding: 5px 0; margin: 0; - display: inline-block; - vertical-align: bottom; -} - -.media-recorder-constraints { - text-align: center; - padding: 5px; -} - -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; -} - -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; -} - -button.media-recorder-enable-video:disabled { - color: #ccc; - text-decoration: line-through; + vertical-align: middle; } .media-recorder-controls { @@ -119,7 +56,6 @@ button.media-recorder-enable-video:disabled { -webkit-appearance: none; -moz-appearance: none; appearance: none; - background: #FF4D4D; color: #fff; border: none; border-radius: 2px; @@ -127,19 +63,22 @@ button.media-recorder-enable-video:disabled { cursor: pointer; } -.media-recorder-controls button.media-recorder-settings { +.media-recorder-settings { background: #00a5e1; } -.media-recorder-file-preview { - background: #323232; - color: #fff; +.media-recorder-record { + background: #FF4D4D; } -#recorderFlashContainer { - background: transparent !important; - border: none !important; +.media-recorder-play { + background: #2861ff; } -#recorderFlashContainer object { +.media-recorder-stop { + background: #ffa42a; +} + +#flash-wrapper { + text-align: center; } diff --git a/includes/MediaRecorderBrowser.inc b/includes/MediaRecorderBrowser.inc old mode 100755 new mode 100644 diff --git a/includes/media_recorder.admin.inc b/includes/media_recorder.admin.inc index 35e44c64ae3638faa87fb8d0d042946d2b0df81f..a944a3e158523f7183fae07aa9376c800051db13 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', 'recorder.js', 'Recorderjs'); + $required_libraries = array('swfobject', 'FlashWavRecorder', 'Recorderjs'); foreach ($required_libraries as $name) { $library = libraries_detect($name); if (!$library['installed']) { @@ -19,6 +19,42 @@ function media_recorder_admin_form($form, $form_state) { } } + // Recorder constraints. + $constraints = variable_get('media_recorder_constraints', array( + 'audio' => TRUE, + 'video' => TRUE, + 'video_resolution' => 320, + )); + $form['media_recorder_constraints'] = array( + '#type' => 'fieldset', + '#tree' => TRUE, + '#title' => t('Media Constraints'), + '#description' => t('Select which recording options will be available using the Media Browser.'), + ); + $form['media_recorder_constraints']['audio'] = array( + '#type' => 'checkbox', + '#title' => t('Audio'), + '#default_value' => $constraints['audio'], + ); + $form['media_recorder_constraints']['video'] = array( + '#type' => 'checkbox', + '#title' => t('Video'), + '#default_value' => $constraints['video'], + ); + $form['media_recorder_constraints']['video_resolution'] = array( + '#type' => 'radios', + '#title' => t('Video Resolution'), + '#default_value' => $constraints['video_resolution'], + '#options' => array( + 640 => t('640 x 480'), + 480 => t('480 x 360'), + 320 => t('320 x 240'), + 240 => t('240 x 180'), + 180 => t('180 x 135'), + ), + ); + + // Recorder time limit. $form['media_recorder_time_limit'] = array( '#type' => 'textfield', '#title' => t('Time Limit'), @@ -45,5 +81,13 @@ function media_recorder_admin_form($form, $form_state) { ); } + // Recorder CSS enabled. + $form['media_recorder_css'] = array( + '#type' => 'checkbox', + '#title' => t('Use default CSS stylesheet?'), + '#description' => t('Disable if you want to use a different set of styles without having to override the default CSS.'), + '#default_value' => variable_get('media_recorder_css', TRUE), + ); + return system_settings_form($form); } diff --git a/includes/media_recorder.drush.inc b/includes/media_recorder.drush.inc index 34b7e2be595b549f0e8c30a0e5b09c4dfac84cb0..dea498e73cb5ba72b32c3be8fb5bf26da1966a46 100644 --- a/includes/media_recorder.drush.inc +++ b/includes/media_recorder.drush.inc @@ -9,11 +9,14 @@ * Implements hook_drush_command(). */ function media_recorder_drush_command() { + $items = array(); + $items['media-recorder-download-libraries'] = array( 'description' => dt('Download and install all libraries associated with Media Recorder.'), 'aliases' => array('mrdl'), 'callback' => 'drush_media_recorder_download_libraries', ); + return $items; } @@ -22,13 +25,18 @@ function media_recorder_drush_command() { * @see media_recorder_drush_command() */ function drush_media_recorder_download_libraries() { + if (!module_exists('libraries')) { + return FALSE; + } + + // Get base path. $base_path = drush_get_context('DRUSH_DRUPAL_CORE'); - // Create the libraries path if it doesn't exist. - drush_mkdir($base_path . '/sites/all/libraries'); + // Get sites path. + $site_path = (conf_path() == 'sites/default') ? 'sites/all' : conf_path(); // Check that all libraries exist. - $required_libraries = array('swfobject', 'recorder.js', 'Recorderjs'); + $required_libraries = array('swfobject', 'FlashWavRecorder', 'Recorderjs'); foreach ($required_libraries as $name) { // Get library info for each library. @@ -44,7 +52,8 @@ function drush_media_recorder_download_libraries() { drush_op('chdir', drush_tempdir()); // Get library path and download link from library info. - $library_path = $library['library path'] ? $base_path . '/' . $library['library path'] : $base_path . '/sites/all/libraries/' . $name; + $library_path = $base_path . '/' . $site_path . '/libraries/' . $name; + $download_url = ''; if (!empty($library['download url'])) { $download_url = $library['download url']; @@ -52,25 +61,33 @@ function drush_media_recorder_download_libraries() { // Download and unzip into libraries. if (!empty($download_url)) { + // Download the zip archive. $filename = drush_download_file($download_url); if (!file_exists($filename)) { return drush_set_error(dt('Unable to download @url', array('@url' => $download_url))); } + // Decompress the zip archive. $extract = drush_tarball_extract($filename, FALSE, TRUE); + // Move directory. - if (is_dir($extract[0])) { - drush_move_dir($extract[0], $library_path); + if (is_dir($extract[0]) && drush_move_dir($extract[0], $library_path)) { + drush_log(dt('The @name library has been downloaded to @path', array('@name' => $name, '@path' => $library_path)), 'success'); + } + elseif (is_dir($extract[1]) && drush_move_dir($extract[1], $library_path)) { + drush_log(dt('The @name library has been downloaded to @path', array('@name' => $name, '@path' => $library_path)), 'success'); } - elseif (is_dir($extract[1])) { - drush_move_dir($extract[1], $library_path); + else { + drush_log(dt('Unable to download @name library to @path', array('@name' => $name, '@path' => $library_path)), 'success'); } - drush_log(dt('The @name library has been downloaded to @path', array('@name' => $name, '@path' => $library_path)), 'success'); } // Return to base path. drush_op('chdir', $base_path); + } + + return NULL; } diff --git a/js/media-recorder-api.js b/js/media-recorder-api.js old mode 100755 new mode 100644 index c7cff0f377f7ac032b9415e9f5bdb5fe408af254..1cae3bdc52e49399b96d904194ef290bb3576126 --- a/js/media-recorder-api.js +++ b/js/media-recorder-api.js @@ -3,350 +3,574 @@ * Adds an interface between the media recorder jQuery plugin and the drupal media module. */ -(function($) { +(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 - }); - }); + Drupal.MediaRecorder = (function () { + var settings = Drupal.settings.mediaRecorder.settings; + var origin = window.location.origin || window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port : ''); + var audioContext = null; + var canvasContext = null; + var visualizerProcessor = null; + var freqData = null; + var volume = 0; + var barWidth = 0; + var level = 0; + var meterProcessor = null; + var constraints = {}; + var localStream = null; + var recorder = null; + var recordURL = null; + var playbackURL = null; + var format = null; + var mimetype = null; + var analyser = null; + var microphone = null; + var blobs = []; + var files = []; + var blobCount = 0; + var statusInterval = null; + var $element = null; + var $statusWrapper = null; + var $previewWrapper = null; + var $video = null; + var $audio = null; + var $meter = null; + var $startButton = null; + var $recordButton = null; + var $stopButton = null; + var $playButton = null; + var $settingsButton = null; + var $videoButton = null; + var $audioButton = null; + + /** + * Set status message. + */ + function setStatus(message) { + $element.trigger('status', message); + } - // Click handler for record button. - $recordButton.bind('click', function (event) { - event.preventDefault(); - Drupal.mediaRecorder.record(); - }); + /** + * Create volume meter canvas element that uses getUserMedia stream. + */ + function createVolumeMeter() { - // Click handler for stop button. - $stopButton.bind('click', function (event) { - event.preventDefault(); - Drupal.mediaRecorder.stop(); - }); + // Private function for determining current volume. + function getVolume() { + var values = 0; + var length = freqData.length; - // Listen for the record event. - $(Drupal.mediaRecorder).bind('recordStart', function (event, data) { - var currentSeconds = 0; - var timeLimit = millisecondsToTime(new Date(parseInt(Drupal.mediaRecorder.settings.time_limit, 10) * 1000)); - - $recordButton.hide(); - $stopButton.show(); - $(Drupal.mediaRecorder).trigger('status', 'Recording 00:00 (Time Limit: ' + timeLimit + ')'); - - 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; - } + for (var i = 0; i < length; i++) { + values += freqData[i]; + } - Drupal.mediaRecorder.statusInterval = setInterval(function () { - currentSeconds = currentSeconds + 1; - var currentMilliSeconds = new Date(currentSeconds * 1000); - var time = millisecondsToTime(currentMilliSeconds); - $(Drupal.mediaRecorder).trigger('status', 'Recording ' + time + ' (Time Limit: ' + timeLimit + ')'); + return values / length; + } - if (currentSeconds >= Drupal.mediaRecorder.settings.time_limit) { - Drupal.mediaRecorder.stop(); - } - }, 1000); - }); + canvasContext = $meter[0].getContext("2d"); + meterProcessor = audioContext.createScriptProcessor(1024, 1, 1); + microphone.connect(analyser); + analyser.connect(meterProcessor); + meterProcessor.connect(audioContext.destination); + meterProcessor.onaudioprocess = function () { + freqData = new Uint8Array(analyser.frequencyBinCount); + analyser.getByteFrequencyData(freqData); + volume = getVolume(); + level = Math.max.apply(Math, freqData); + + if (volume === 0) { + $meter.addClass('muted'); + } + else { + $meter.removeClass('muted'); + } - // Listen for the stop event. - $(Drupal.mediaRecorder).bind('recordStop', function (event) { - $stopButton.hide(); - clearInterval(Drupal.mediaRecorder.statusInterval); - $(Drupal.mediaRecorder).trigger('status', 'Processing file...'); - }); + canvasContext.clearRect(0, 0, $meter[0].width, $meter[0].height); + canvasContext.fillStyle = '#00ff00'; + canvasContext.fillRect(0, 0, $meter[0].width * (level / 255), $meter[0].height); + }; + } - // Append file object data. - $(Drupal.mediaRecorder).bind('refreshData', function (event, data) { - $element.find('.media-recorder-fid').val(data.fid); - $element.find('.media-recorder-refresh').trigger('mousedown'); - $recordButton[0].disabled = false; - $(Drupal.mediaRecorder).trigger('status', 'Press record to start recording.'); - $recordButton.show(); - }); + /** + * Create audio visualizer canvas element that uses getUserMedia stream. + */ + function createAudioVisualizer() { - $(Drupal.mediaRecorder).bind('status', function (event, msg) { - $statusWrapper.text(msg); - }); + // Private function for determining current volume. + function getVolume() { + var values = 0; + var length = freqData.length; - // 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.recorder = new MediaRecorder(Drupal.mediaRecorder.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[0].muted = 'muted'; // Firefox isn't muting as expected. - 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) { - alert("There was a problem accessing your camera or mic. Please click 'Allow' at the top of the page."); - } - ); + for (var i = 0; i < length; i++) { + values += freqData[i]; } - /** - * Stop user media stream. - */ - function stopStream () { - Drupal.mediaRecorder.analyser.disconnect(); - Drupal.mediaRecorder.microphone.disconnect(); - Drupal.mediaRecorder.stream.stop(); - $previewWrapper.text(''); - $previewWrapper.hide(); + return values / length; + } + + canvasContext = $meter[0].getContext("2d"); + + visualizerProcessor = audioContext.createScriptProcessor(1024, 1, 1); + microphone.connect(analyser); + analyser.connect(visualizerProcessor); + visualizerProcessor.connect(audioContext.destination); + + visualizerProcessor.onaudioprocess = function () { + freqData = new Uint8Array(analyser.frequencyBinCount); + analyser.getByteFrequencyData(freqData); + volume = getVolume(); + barWidth = Math.ceil($meter[0].width / (analyser.frequencyBinCount * 0.5)); + + if (volume === 0) { + $meter.addClass('muted'); + } + else { + $meter.removeClass('muted'); } - /** - * 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; + canvasContext.clearRect(0, 0, $meter[0].width, $meter[0].height); + for (var i = 0; i < analyser.frequencyBinCount; i++) { + canvasContext.fillStyle = 'hsl(' + i / analyser.frequencyBinCount * 360 + ', 100%, 50%)'; + if ((barWidth * i) + barWidth < $meter[0].width) { + canvasContext.fillRect(barWidth * i, $meter[0].height, barWidth - 1, -(Math.floor((freqData[i] / 255) * $meter[0].height))); + } } + }; + } - /** - * Create audio visualizer canvas element that uses getUserMedia stream. - */ - function createAudioVisualizer () { - var canvas = document.createElement('canvas'); - var canvasContext = canvas.getContext("2d"); - var micStatus = false; + /** + * Toggle to recording preview. + */ + function recordingPreview() { + if (constraints.video) { + $video.show(); + $audio.hide(); + $video[0].src = recordURL; + $video[0].muted = 'muted'; + $video[0].controls = ''; + $video[0].load(); + $video[0].play(); + $meter.height(20); + } + else { + $video.hide(); + $audio.hide(); + $meter.height($meter.width() / 2); + } + } - if (!Drupal.mediaRecorder.audioContext || !Drupal.mediaRecorder.microphone || !Drupal.mediaRecorder.analyser) { - var textWidth, textString = 'Audio visualizer unable to initialize'; + /** + * Toggle to recording preview. + */ + function playbackPreview() { + if (blobs.length === 0) { + return; + } + if (constraints.video) { + playbackURL = URL.createObjectURL(new Blob(blobs, {type: mimetype})); + $video.show(); + $audio.hide(); + $video[0].src = playbackURL; + $video[0].muted = ''; + $video[0].controls = 'controls'; + $video[0].load(); + } + else { + playbackURL = URL.createObjectURL(new Blob(blobs, {type: mimetype})); + $audio.show(); + $audio[0].src = playbackURL; + $audio[0].load(); + } + } - canvasContext.font = 'bold 1em Arial'; - canvasContext.fillStyle = '#ffffff'; - textWidth = canvasContext.measureText(textString).width; - canvasContext.fillText(textString, (canvas.width / 2) - (textWidth / 2), canvas.height / 2); + /** + * Send a blob as form data to the server. Requires jQuery 1.5+. + */ + function sendBlob(blob, count) { + + // Create formData object. + var formData = new FormData(); + formData.append("blob", blob); + formData.append("count", count); + + // Return ajax promise. + $.ajax({ + url: origin + Drupal.settings.basePath + 'media_recorder/record/stream/record', + type: 'POST', + data: formData, + processData: false, + contentType: false, + success: function (data) { + files.push(data); + }, + error: function (jqXHR, textStatus, errorThrown) { + } + }); + } + + /** + * Stop user media stream. + */ + function stopStream() { + analyser.disconnect(); + microphone.disconnect(); + localStream.stop(); + $previewWrapper.hide(); + $startButton.show(); + $recordButton.hide(); + $stopButton.hide(); + } + + /** + * Start user media stream. + */ + function startStream() { + if (localStream) { + stopStream(); + } + navigator.getUserMedia( + constraints, + function (stream) { + localStream = stream; + recorder = new MediaRecorder(localStream); + recordURL = URL.createObjectURL(localStream); + format = constraints.video ? 'webm' : 'ogg'; + mimetype = constraints.video ? 'video/webm' : 'audio/ogg'; + audioContext = new AudioContext(); + analyser = audioContext.createAnalyser(); + analyser.smoothingTimeConstant = 0.75; + analyser.fftSize = 512; + microphone = audioContext.createMediaStreamSource(stream); + + $previewWrapper.show(); + $meter.show(); + $startButton.hide(); + $videoButton.hide(); + $audioButton.hide(); + $recordButton.show(); + $stopButton.hide(); + recordingPreview(); - return canvas; + if (constraints.video) { + createVolumeMeter(); + } + else { + createAudioVisualizer(); } - 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.'); - } + setStatus('Press record to start recording.'); + }, + function (error) { + stopStream(); + alert("There was a problem accessing your camera or mic. Please click 'Allow' at the top of the page."); + } + ); + } - 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)); - } - } + /** + * Enable mic or camera. + */ + function start() { + if (settings.constraints.audio && !settings.constraints.video) { + constraints = { + audio: true, + video: false + }; + startStream(); + } + else if (!settings.constraints.audio && settings.constraints.video) { + startStream(); + } + else { + $startButton.hide(); + $videoButton.show(); + $audioButton.show(); + setStatus('Record with audio or video?'); + } + } - // 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; - } - }; + /** + * Stop recording and trigger stopped event. + */ + function stop() { + recorder.stop(); + $element.trigger('recordStop'); + } - canvas.className = 'media-recorder-visualizer'; + /** + * Start recording and trigger recording event. + */ + function record() { + blobs = []; + files = []; + blobCount = 0; + + // Triggered on data available. + recorder.ondataavailable = function (e) { + var blob = new Blob([e.data], {type: e.data.type || mimetype}); + if (blob.size > 0) { + blobs.push(blob); + blobCount++; + sendBlob(blob, blobCount); + } + }; + + // Triggered when recording is stopped. Requires ajax 1.5+. + recorder.onstop = function (e) { + $(document).ajaxStop(function () { + $(this).unbind("ajaxStop"); + $.ajax({ + url: origin + Drupal.settings.basePath + 'media_recorder/record/stream/finish', + type: 'POST', + async: true, + data: { + count: blobs.length + }, + success: function (data) { + $element.trigger('refreshData', data); + }, + error: function (jqXHR, textStatus, errorThrown) { + alert('There was an issue saving your recording, please try again.'); + } + }); + }); + }; - return canvas; + // Notify server that recording has started. Start recording if server is available. + $.ajax({ + url: origin + Drupal.settings.basePath + 'media_recorder/record/stream/start', + type: 'POST', + data: { + format: format + }, + async: false, + success: function (data) { + recorder.start(3000); + $element.trigger('recordStart'); + }, + error: function (jqXHR, textStatus, errorThrown) { + alert('There was an issue starting your recording, please try again.'); } }); + } - /** - * Start recording and trigger recording event. - */ - Drupal.mediaRecorder.record = function () { - Drupal.mediaRecorder.blobs = []; - Drupal.mediaRecorder.blobCount = 0; - - // Triggered on data available. - Drupal.mediaRecorder.recorder.ondataavailable = function (e) { - var blob = new Blob([e.data], { type: e.data.type || Drupal.mediaRecorder.mimetype }); - if (blob.size > 0) { - Drupal.mediaRecorder.blobCount++; - Drupal.mediaRecorder.sendBlob(blob, Drupal.mediaRecorder.blobCount); - } - }; + /** + * Initialize all control buttons. + */ + function initializeButtons() { - // Triggered when recording is stopped. Requires ajax 1.5+. - Drupal.mediaRecorder.recorder.onstop = function (e) { - $(document).ajaxStop(function () { - $(this).unbind("ajaxStop"); - $.ajax({ - url: Drupal.mediaRecorder.origin + Drupal.settings.basePath + 'media_recorder/record/stream/finish', - type: 'POST', - async: true, - data: { - count: Drupal.mediaRecorder.blobs.length - }, - success: function (data) { - $(Drupal.mediaRecorder).trigger('refreshData', data); - }, - error: function (data) { - alert('There was an issue saving your recording, please try again.'); - } - }); - }); + // Click handler for enable audio button. + $startButton.bind('click', function (event) { + event.preventDefault(); + start(); + }); + + // Click handler for record button. + $recordButton.bind('click', function (event) { + event.preventDefault(); + $recordButton[0].disabled = true; + $recordButton.hide(); + $stopButton.show(); + record(); + }); + + // Click handler for stop button. + $stopButton.bind('click', function (event) { + event.preventDefault(); + $stopButton.hide(); + $recordButton.show(); + stop(); + }); + + // Click handler for to change to video. + $videoButton.bind('click', function (event) { + event.preventDefault(); + setStatus('Allow access at top of page.'); + startStream(); + }); + + // Click handler for to change to audio. + $audioButton.bind('click', function (event) { + event.preventDefault(); + constraints = { + audio: true, + video: false }; + setStatus('Allow access at top of page.'); + startStream(); + }); - // Notify server that recording has started. Start recording if server is available. - $.ajax({ - url: Drupal.mediaRecorder.origin + Drupal.settings.basePath + 'media_recorder/record/stream/start', - type: 'POST', - data: { - format: Drupal.mediaRecorder.format - }, - async: false, - success: function (data) { - Drupal.mediaRecorder.recorder.start(1000); - $(Drupal.mediaRecorder).trigger('recordStart'); - } - }); - }; + } - /** - * Stop recording and trigger stopped event. - */ - Drupal.mediaRecorder.stop = function () { - Drupal.mediaRecorder.recorder.stop(); - $(Drupal.mediaRecorder).trigger('recordStop'); - }; + /** + * Initialize recorder. + */ + function initializeEvents() { + + // Listen for the record event. + $element.bind('recordStart', function (event, data) { + var currentSeconds = 0; + var timeLimitFormatted = millisecondsToTime(new Date(parseInt(settings.time_limit, 10) * 1000)); + + recordingPreview(); + setStatus('Recording 00:00 (Time Limit: ' + timeLimitFormatted + ')'); + + statusInterval = setInterval(function () { + currentSeconds = currentSeconds + 1; + var currentMilliSeconds = new Date(currentSeconds * 1000); + var time = millisecondsToTime(currentMilliSeconds); + setStatus('Recording ' + time + ' (Time Limit: ' + timeLimitFormatted + ')'); - /** - * Send a blob as form data to the server. Requires jQuery 1.5+. - */ - Drupal.mediaRecorder.sendBlob = function (blob, count) { - - // Create formData object. - var formData = new FormData(); - formData.append("blob", blob); - formData.append("count", count); - - // Return ajax promise. - $.ajax({ - url: Drupal.mediaRecorder.origin + Drupal.settings.basePath + 'media_recorder/record/stream/record', - type: 'POST', - data: formData, - processData: false, - contentType: false, - success: function (data) { - Drupal.mediaRecorder.blobs.push(data); - }, - error: function (jqXHR, textStatus, errorThrown) { - console.log(jqXHR, textStatus, errorThrown); + if (currentSeconds >= settings.time_limit) { + stop(); } - }); + }, 1000); + }); + + // Listen for the stop event. + $element.bind('recordStop', function (event) { + clearInterval(statusInterval); + setStatus('Please wait while the recording finishes uploading...'); + }); + + // Append file object data. + $element.bind('refreshData', function (event, data) { + $element.find('.media-recorder-fid').val(data.fid); + $recordButton[0].disabled = false; + playbackPreview(); + setStatus('Press record to start recording.'); + }); + + $element.bind('status', function (event, msg) { + $statusWrapper.text(msg); + }); + } + + /** + * Convert milliseconds to time format. + */ + 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; + } + + /** + * Initialize recorder. + */ + function init(element) { + $element = $(element); + $statusWrapper = $element.find('.media-recorder-status'); + $previewWrapper = $element.find('.media-recorder-preview'); + $video = $element.find('.media-recorder-video'); + $audio = $element.find('.media-recorder-audio'); + $meter = $element.find('.media-recorder-meter'); + $startButton = $element.find('.media-recorder-enable'); + $recordButton = $element.find('.media-recorder-record'); + $stopButton = $element.find('.media-recorder-stop'); + $playButton = $element.find('.media-recorder-play'); + $settingsButton = $element.find('.media-recorder-settings'); + $videoButton = $element.find('.media-recorder-enable-video'); + $audioButton = $element.find('.media-recorder-enable-audio'); + + // Initial state. + $recordButton[0].disabled = false; + $recordButton.hide(); + $stopButton.hide(); + $playButton.hide(); + $settingsButton.hide(); + $video.hide(); + $audio.hide(); + $meter.hide(); + $videoButton.hide(); + $audioButton.hide(); + $previewWrapper.hide(); + + constraints.audio = true; + constraints.video = {}; + if (settings.constraints.video) { + switch (settings.constraints.video_resolution) { + case '640': + constraints.video = { + width: 640, + height: 480 + }; + break; + case '480': + constraints.video = { + width: 480, + height: 360 + }; + break; + case '320': + constraints.video = { + width: 320, + height: 240 + }; + break; + case '240': + constraints.video = { + width: 240, + height: 180 + }; + break; + case '180': + constraints.video = { + width: 180, + height: 135 + }; + break; + } + constraints.video.frameRate = { + min: 30, + ideal: 30, + max: 30 + }; } + + // Show file preview if file exists. + if (Drupal.settings.mediaRecorder.file) { + var file = Drupal.settings.mediaRecorder.file; + switch (file.type) { + case 'video': + $previewWrapper.show(); + $video.show(); + $audio.hide(); + $video[0].src = Drupal.settings.mediaRecorder.file.url; + $video[0].muted = ''; + $video[0].controls = 'controls'; + $video[0].load(); + break; + case 'audio': + $previewWrapper.show(); + $audio.show(); + $video.hide(); + $audio[0].src = Drupal.settings.mediaRecorder.file.url; + $audio[0].muted = ''; + $audio[0].controls = 'controls'; + $audio[0].load(); + break; + } + } + + initializeButtons(); + initializeEvents(); + setStatus('Click \'Start\' to enable your mic & camera.'); } - }; + + return { + init: init, + start: start, + record: record, + stop: stop + }; + })(); })(jQuery); diff --git a/js/media-recorder-flash.js b/js/media-recorder-flash.js old mode 100755 new mode 100644 index 5b8ed6079d858fbe97ac02df9c455a4b2134ae16..b4511c5c53aef37db1e91ad64f8b8837193eac59 --- a/js/media-recorder-flash.js +++ b/js/media-recorder-flash.js @@ -3,152 +3,362 @@ * Adds an interface between the media recorder jQuery plugin and the drupal media module. */ -(function($) { +(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(); - }); - - // Listen for the progress event. - $(Drupal.mediaRecorder).bind('progress', function (event, 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; - } - var time = millisecondsToTime(data); - var timeLimit = millisecondsToTime(new Date(parseInt(Drupal.mediaRecorder.settings.time_limit, 10) * 1000)); + Drupal.MediaRecorderFlash = (function () { + var settings = Drupal.settings.mediaRecorder.settings; + var origin = window.location.origin || window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port : ''); + var recordingName; + var statusInterval; + var $element; + var $statusWrapper; + var $previewWrapper; + var $video; + var $audio; + var $meter; + var $startButton; + var $recordButton; + var $playButton; + var $stopButton; + var $settingsButton; + var $videoButton; + var $audioButton; - $statusWrapper.html('Recording ' + time + ' (Time Limit: ' + timeLimit + ')'); + /** + * Set status message. + */ + function setStatus(message) { + $element.trigger('status', message); + } - if (data / 1000 >= Drupal.mediaRecorder.settings.time_limit) { - Drupal.mediaRecorder.stop(); - } - }); + /** + * Toggle to recording preview. + */ + function showSettings() { + FWRecorder.showPermissionWindow({permanent: true}); + } + + /** + * Send a blob as form data to the server. Requires jQuery 1.5+. + */ + function sendBlob(blob) { + setStatus('Uploading, please wait...'); + + // Create formData object. + var formData = new FormData(); + var req = new XMLHttpRequest(); + formData.append("mediaRecorder", blob); + + // Send file. + req.addEventListener("load", transferComplete, false); + req.open('POST', origin + Drupal.settings.basePath + 'media_recorder/record/file', true); + req.send(formData); + function transferComplete(evt) { + var file = JSON.parse(req.response); + $element.trigger('uploadFinished', file); + } + } + + /** + * Stop recording and trigger stopped event. + */ + function start() { + showSettings(); + } - // Listen for the stop event. - $(Drupal.mediaRecorder).bind('recordStop', function (event) { - $recordButton.show(); - $stopButton.hide(); - }); + /** + * Stop recording and trigger stopped event. + */ + function stop() { + FWRecorder.stopPlayBack(); + FWRecorder.stopRecording(); + } - $(Drupal.mediaRecorder).bind('uploadStarted', function (event) { - $statusWrapper.html('
Uploading, please wait...
'); - }); + /** + * Start recording and trigger recording event. + */ + function record() { + FWRecorder.record(recordingName); + } - $(Drupal.mediaRecorder).bind('uploadFinished', function (event, data) { - $statusWrapper.html('
Press record to start recording.
'); + /** + * Initialize all control buttons. + */ + function initializeButtons() { - // Append file object data. - $element.find('.media-recorder-fid').val(data.fid); - $element.find('.media-recorder-refresh').trigger('mousedown'); - }); + // Click handler for enable button. + $startButton.bind('click', function (event) { + event.preventDefault(); + $startButton[0].disabled = true; + start(); + setStatus('Allow access to your mic in the settings panel.'); + }); + + // Click handler for record button. + $recordButton.bind('click', function (event) { + event.preventDefault(); + $recordButton[0].disabled = true; + $recordButton.hide(); + $playButton.hide(); + $stopButton.show(); + record(); + }); - // Initial state. - $constraintsWrapper.hide(); - $previewWrapper.hide(); + // Click handler for stop button. + $stopButton.bind('click', function (event) { + event.preventDefault(); $stopButton.hide(); - $statusWrapper.html('
Press record to start recording.
'); - $controlsWrapper.append($settingsButton); + $recordButton.show(); + $playButton.show(); + stop(); + }); + + // Click handler for play button. + $playButton.bind('click', function (event) { + event.preventDefault(); + $playButton.hide(); + $stopButton.show(); + FWRecorder.playBack(recordingName); + }); + + // Click handler for settings button. + $settingsButton.bind('click', function (event) { + event.preventDefault(); + showSettings(); + }); + } + + /** + * Initialize recorder. + */ + function initializeEvents() { + + // FWRecorder ready event. + $element.bind('ready', function (event) { + + // Fix for weird FWRecorder use of window instead of document. + window['flashRecorder'] = document['flashRecorder']; + + FWRecorder.connect('flashRecorder', 0); + FWRecorder.recorderOriginalWidth = 1; + FWRecorder.recorderOriginalHeight = 1; + + if (!FWRecorder.isMicrophoneAccessible()) { + $startButton.show(); + } + }); + + // FWRecorder microphone_connected event. + $element.bind('microphone_user_request', function (event) { + FWRecorder.showPermissionWindow(); + }); + + // FWRecorder microphone_connected event. + $element.bind('microphone_connected', function (event) { + FWRecorder.configure(22, 50, 0, 4000); + $startButton.hide(); + $recordButton.show(); + setStatus('Press record to start recording.'); + FWRecorder.observeLevel(); }); - /** - * Start recording and trigger recording event. - */ - Drupal.mediaRecorder.record = function () { + // FWRecorder microphone_not_connected event. + $element.bind('microphone_not_connected', function (event) { + $startButton.show(); + $startButton[0].disabled = false; + $recordButton.hide(); + setStatus('Click \'Start\' to enable your mic & camera.'); + }); + + // FWRecorder no_microphone_found event. + $element.bind('no_microphone_found', function (event, name, data) { + $startButton.show(); + $startButton[0].disabled = false; + $recordButton.hide(); + setStatus('No mic found. Click \'Start\' to enable your mic & camera.'); + }); + + // FWRecorder permission_panel_closed event. + $element.bind('permission_panel_closed', function (event) { + FWRecorder.defaultSize(); + }); + + // FWRecorder observing_level event. + $element.bind('observing_level', function (event, name, data) { + $meter.show(); + $meter.height(20); + }); + + // FWRecorder observing_level_stopped event. + $element.bind('observing_level_stopped', function (event, name, data) { + $meter.hide(); + }); - Recorder.record({ - start: function(){ + // FWRecorder microphone_level event. + $element.bind('microphone_level', function (event, name, data) { + var level = data * 100; + $meter.width(level + '%'); + if (data * 100 <= 70) { + $meter.css({ + 'background': 'green' + }); + } + else if (level > 70 && level < 85) { + $meter.css({ + 'background': 'yellow' + }); + } + else if (level >= 85) { + $meter.css({ + 'background': 'red' + }); + } + }); + + // FWRecorder recording event. + $element.bind('recording', function (event, data) { + var currentSeconds = 0; + var timeLimitFormatted = millisecondsToTime(new Date(parseInt(settings.time_limit, 10) * 1000)); - // Trigger recording event. - $(Drupal.mediaRecorder).trigger('recordStart'); - }, - progress: function (milliseconds) { + setStatus('Recording 00:00 (Time Limit: ' + timeLimitFormatted + ')'); + statusInterval = setInterval(function () { + currentSeconds = currentSeconds + 1; + var currentMilliSeconds = new Date(currentSeconds * 1000); + var time = millisecondsToTime(currentMilliSeconds); + setStatus('Recording ' + time + ' (Time Limit: ' + timeLimitFormatted + ')'); - // Trigger recording event. - $(Drupal.mediaRecorder).trigger('progress', milliseconds); + if (currentSeconds >= settings.time_limit) { + stop(); } - }); - }; + }, 1000); + }); - /** - * Stop recording and trigger stopped event. - */ - Drupal.mediaRecorder.stop = function () { + // FWRecorder recording_stopped event. + $element.bind('recording_stopped', function (event) { + var blob = FWRecorder.getBlob(recordingName); + clearInterval(statusInterval); + sendBlob(blob); + }); - Recorder.stop(); + // FWRecorder recording_stopped event. + $element.bind('stopped', function (event) { + $playButton.show(); + $stopButton.hide(); + }); - // Trigger uploading event. - $(Drupal.mediaRecorder).trigger('uploadStarted'); + $element.bind('uploadFinished', function (event, data) { + $element.find('.media-recorder-fid').val(data.fid); + $recordButton[0].disabled = false; + setStatus('Press record to start recording.'); + }); - Recorder.upload({ - url: Drupal.mediaRecorder.origin + Drupal.settings.basePath + '/media_recorder/record/file', - audioParam: 'mediaRecorder', - success: function(response) { - var file = JSON.parse(response); + $element.bind('status', function (event, msg) { + $statusWrapper.text(msg); + }); + } - // Trigger stopped event. - $(Drupal.mediaRecorder).trigger('uploadFinished', file); - }, - }); + /** + * Convert milliseconds to time format. + */ + 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; + } - // Trigger stopped event. - $(Drupal.mediaRecorder).trigger('recordStop'); + /** + * Initialize recorder. + */ + function init(element) { + $element = $(element); + $statusWrapper = $element.find('.media-recorder-status'); + $previewWrapper = $element.find('.media-recorder-preview'); + $video = $element.find('.media-recorder-video'); + $audio = $element.find('.media-recorder-audio'); + $meter = $element.find('.media-recorder-meter'); + $startButton = $element.find('.media-recorder-enable'); + $recordButton = $element.find('.media-recorder-record'); + $stopButton = $element.find('.media-recorder-stop'); + $playButton = $element.find('.media-recorder-play'); + $settingsButton = $element.find('.media-recorder-settings'); + $videoButton = $element.find('.media-recorder-enable-video'); + $audioButton = $element.find('.media-recorder-enable-audio'); + recordingName = 'audio'; + + // Append flash recorder div. + $element.append('

Your browser must have JavaScript enabled and the Adobe Flash Player installed.

'); + + // Initialize flash recorder. + window.fwr_event_handler = function (eventName) { + $element.trigger(eventName, arguments); }; + swfobject.embedSWF( + Drupal.settings.basePath + Drupal.settings.mediaRecorder.flashurl + '/html/recorder.swf', + 'flashcontent', + 1, + 1, + '11.0.0', + '', + {}, + {}, + {'id': 'flashRecorder', 'name': 'flashRecorder'} + ); + + // Initial state. + $recordButton.hide(); + $recordButton.hide(); + $stopButton.hide(); + $playButton.hide(); + $stopButton.before($playButton); + $video.hide(); + $audio.hide(); + $meter.hide(); + $videoButton.hide(); + $audioButton.hide(); + + // Show file preview if file exists. + if (Drupal.settings.mediaRecorder.file) { + var file = Drupal.settings.mediaRecorder.file; + switch (file.type) { + case 'video': + $previewWrapper.show(); + $video.show(); + $audio.hide(); + $video[0].src = Drupal.settings.mediaRecorder.file.url; + $video[0].muted = ''; + $video[0].controls = 'controls'; + $video[0].load(); + break; + case 'audio': + $previewWrapper.show(); + $audio.show(); + $video.hide(); + $audio[0].src = Drupal.settings.mediaRecorder.file.url; + $audio[0].muted = ''; + $audio[0].controls = 'controls'; + $audio[0].load(); + break; + } + } + + initializeButtons(); + initializeEvents(); + setStatus('Click \'Start\' to enable your mic & camera.'); + } - }, - }; + return { + init: init, + start: start, + record: record, + stop: stop + }; + })(); })(jQuery); diff --git a/js/media-recorder-html5.js b/js/media-recorder-html5.js old mode 100755 new mode 100644 index 36e50ea9e15ca73fc1eed8980e384684bae41aa6..1766f0939ab088f6a759fbc7212905c4f04ce970 --- a/js/media-recorder-html5.js +++ b/js/media-recorder-html5.js @@ -3,319 +3,426 @@ * Adds an interface between the media recorder jQuery plugin and the drupal media module. */ -(function($) { +(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) { - var currentSeconds = 0; - var timeLimit = millisecondsToTime(new Date(parseInt(Drupal.mediaRecorder.settings.time_limit, 10) * 1000)); - - $recordButton.hide(); - $stopButton.show(); - $(Drupal.mediaRecorder).trigger('status', 'Recording 00:00 (Time Limit: ' + timeLimit + ')'); - - 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.MediaRecorderHTML5 = (function () { + var settings = Drupal.settings.mediaRecorder.settings; + var origin = window.location.origin || window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port : ''); + var audioContext; + var canvasContext; + var visualizerProcessor; + var freqData; + var volume; + var barWidth; + var level; + var meterProcessor; + var constraints; + var localStream; + var recorder; + var recordURL; + var playbackURL; + var mimetype; + var analyser; + var microphone; + var blobs; + var statusInterval; + var $element; + var $statusWrapper; + var $previewWrapper; + var $video; + var $audio; + var $meter; + var $startButton; + var $recordButton; + var $playButton; + var $stopButton; + var $settingsButton; + var $videoButton; + var $audioButton; + + /** + * Set status message. + */ + function setStatus(message) { + $element.trigger('status', message); + } + + /** + * Create volume meter canvas element that uses getUserMedia stream. + */ + function createVolumeMeter() { + canvasContext = $meter[0].getContext("2d"); + meterProcessor = audioContext.createScriptProcessor(1024, 1, 1); + microphone.connect(analyser); + analyser.connect(meterProcessor); + meterProcessor.connect(audioContext.destination); + meterProcessor.onaudioprocess = function () { + freqData = new Uint8Array(analyser.frequencyBinCount); + analyser.getByteFrequencyData(freqData); + level = Math.max.apply(Math, freqData); + canvasContext.clearRect(0, 0, $meter[0].width, $meter[0].height); + canvasContext.fillStyle = '#00ff00'; + canvasContext.fillRect(0, 0, $meter[0].width * (level / 255), $meter[0].height); + }; + } - Drupal.mediaRecorder.statusInterval = setInterval(function () { - currentSeconds = currentSeconds + 1; - var currentMilliSeconds = new Date(currentSeconds * 1000); - var time = millisecondsToTime(currentMilliSeconds); - $(Drupal.mediaRecorder).trigger('status', 'Recording ' + time + ' (Time Limit: ' + timeLimit + ')'); + /** + * Create audio visualizer canvas element that uses getUserMedia stream. + */ + function createAudioVisualizer() { - if (currentSeconds >= Drupal.mediaRecorder.settings.time_limit) { - Drupal.mediaRecorder.stop(); - } - }, 1000); - }); + // Private function for determining current volume. + function getVolume() { + var values = 0; + var length = freqData.length; - // 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 for now. - $videoConstraintButton.hide(); - $audioConstraintButton.text('Start'); - $(Drupal.mediaRecorder).trigger('status', 'Click \'Start\' to enable your microphone.'); - - /** - * 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) { - } - ); + for (var i = 0; i < length; i++) { + values += freqData[i]; } - /** - * Stop user media stream. - */ - function stopStream () { - Drupal.mediaRecorder.analyser.disconnect(); - Drupal.mediaRecorder.microphone.disconnect(); - Drupal.mediaRecorder.stream.stop(); - $previewWrapper.text(''); - $previewWrapper.hide(); - } + return values / length; + } - /** - * 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; - } + canvasContext = $meter[0].getContext("2d"); + + visualizerProcessor = audioContext.createScriptProcessor(1024, 1, 1); + microphone.connect(analyser); + analyser.connect(visualizerProcessor); + visualizerProcessor.connect(audioContext.destination); - /** - * Create audio visualizer canvas element that uses getUserMedia stream. - */ - function createAudioVisualizer () { - var canvas = document.createElement('canvas'); - var canvasContext = canvas.getContext("2d"); - var micStatus = false; + visualizerProcessor.onaudioprocess = function (audioProcessingEvent) { + freqData = new Uint8Array(analyser.frequencyBinCount); + analyser.getByteFrequencyData(freqData); + volume = getVolume(); - if (!Drupal.mediaRecorder.audioContext || !Drupal.mediaRecorder.microphone || !Drupal.mediaRecorder.analyser) { - var textWidth, textString = 'Audio visualizer unable to initialize'; + if (volume === 0) { + $meter.addClass('muted'); + } + else { + $meter.removeClass('muted'); + } - canvasContext.font = 'bold 1em Arial'; - canvasContext.fillStyle = '#ffffff'; - textWidth = canvasContext.measureText(textString).width; - canvasContext.fillText(textString, (canvas.width / 2) - (textWidth / 2), canvas.height / 2); + barWidth = Math.ceil($meter[0].width / (analyser.frequencyBinCount * 0.5)); + canvasContext.clearRect(0, 0, $meter[0].width, $meter[0].height); + for (var i = 0; i < analyser.frequencyBinCount; i++) { + canvasContext.fillStyle = 'hsl(' + i / analyser.frequencyBinCount * 360 + ', 100%, 50%)'; + if ((barWidth * i) + barWidth < $meter[0].width) { + canvasContext.fillRect(barWidth * i, $meter[0].height, barWidth - 1, -(Math.floor((freqData[i] / 255) * $meter[0].height))); + } + } + }; + } + + /** + * Toggle to recording preview. + */ + function recordingPreview() { + if (constraints.video) { + $video.show(); + $audio.hide(); + $video[0].src = recordURL; + $video[0].muted = 'muted'; + $video[0].controls = ''; + $video[0].load(); + $video[0].play(); + $meter.height(10); + } + else { + $video.hide(); + $audio.hide(); + $meter.height($meter.width() / 2); + } + } + + /** + * Toggle to recording preview. + */ + function playbackPreview() { + if (blobs.length === 0) { + return; + } + if (constraints.video) { + playbackURL = URL.createObjectURL(new Blob(blobs, {type: mimetype})); + $video.show(); + $audio.hide(); + $video[0].src = playbackURL; + $video[0].muted = ''; + $video[0].controls = 'controls'; + $video[0].load(); + } + else { + playbackURL = URL.createObjectURL(new Blob(blobs, {type: mimetype})); + $audio.show(); + $audio[0].src = playbackURL; + $audio[0].load(); + } + } + + /** + * Send a blob as form data to the server. Requires jQuery 1.5+. + */ + function sendBlob(blob, count) { + + // Create formData object. + var formData = new FormData(); + var req = new XMLHttpRequest(); + formData.append("mediaRecorder", blob); + blobs = [blob]; + + // Send file. + req.addEventListener("load", transferComplete, false); + req.open('POST', origin + Drupal.settings.basePath + 'media_recorder/record/file', true); + req.send(formData); + function transferComplete(evt) { + var file = JSON.parse(req.response); + $element.trigger('uploadFinished', file); + } + } + + /** + * Stop user media stream. + */ + function stopStream() { + analyser.disconnect(); + microphone.disconnect(); + localStream.stop(); + $previewWrapper.hide(); + $startButton.show(); + $recordButton.hide(); + $stopButton.hide(); + } + + /** + * Start user media stream. + */ + function startStream() { + if (localStream) { + stopStream(); + } + navigator.getUserMedia( + constraints, + function (stream) { + localStream = stream; + recordURL = URL.createObjectURL(localStream); + mimetype = settings.constraints.video ? 'video/webm' : 'audio/ogg'; + audioContext = new AudioContext(); + analyser = audioContext.createAnalyser(); + analyser.smoothingTimeConstant = 0.75; + analyser.fftSize = 512; + microphone = audioContext.createMediaStreamSource(stream); + recorder = new Recorder(microphone, {workerPath: Drupal.settings.basePath + Drupal.settings.mediaRecorder.html5url + '/recorderWorker.js'}); + + $previewWrapper.show(); + $meter.show(); + $startButton.hide(); + $recordButton.show(); + $stopButton.hide(); + recordingPreview(); - return canvas; + if (constraints.video) { + createVolumeMeter(); + } + else { + createAudioVisualizer(); } - 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; + setStatus('Press record to start recording.'); + }, + function (error) { + stopStream(); + alert("There was a problem accessing your camera or mic. Please click 'Allow' at the top of the page."); } + ); + } + + /** + * Stop recording and trigger stopped event. + */ + function start() { + constraints = { + audio: true, + video: false + }; + + startStream(); + } + + /** + * Stop recording and trigger stopped event. + */ + function stop() { + recorder.stop(); + recorder.exportWAV(function (blob) { + sendBlob(blob); + }); + recorder.clear(); + $element.trigger('recordStop'); + } + + /** + * Start recording and trigger recording event. + */ + function record() { + recorder.record(); + $element.trigger('recordStart'); + } + + /** + * Initialize all control buttons. + */ + function initializeButtons() { + + // Click handler for enable audio button. + $startButton.bind('click', function (event) { + event.preventDefault(); + $startButton[0].disabled = true; + start(); + setStatus('Allow access at top of page.'); }); - /** - * Start recording and trigger recording event. - */ - Drupal.mediaRecorder.record = function () { + // Click handler for record button. + $recordButton.bind('click', function (event) { + event.preventDefault(); + $recordButton[0].disabled = true; + $recordButton.hide(); + $stopButton.show(); + record(); + }); - // 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(); + // Click handler for stop button. + $stopButton.bind('click', function (event) { + event.preventDefault(); + $stopButton.hide(); + $recordButton.show(); + stop(); + }); + } - // Trigger recording event. - $(Drupal.mediaRecorder).trigger('recordStart'); - }; + /** + * Initialize recorder. + */ + function initializeEvents() { - /** - * Stop recording and trigger stopped event. - */ - Drupal.mediaRecorder.stop = function () { + // Listen for the record event. + $element.bind('recordStart', function (event, data) { + var currentSeconds = 0; + var timeLimitFormatted = millisecondsToTime(new Date(parseInt(settings.time_limit, 10) * 1000)); - // Stop MediaRecorder and delete object (is deletion needed?). - Drupal.mediaRecorder.recorder.stop(); + recordingPreview(); + setStatus('Recording 00:00 (Time Limit: ' + timeLimitFormatted + ')'); - // Export the wav and send to server. - Drupal.mediaRecorder.recorder.exportWAV(function(blob) { - Drupal.mediaRecorder.sendBlob(blob); - }); + statusInterval = setInterval(function () { + currentSeconds = currentSeconds + 1; + var currentMilliSeconds = new Date(currentSeconds * 1000); + var time = millisecondsToTime(currentMilliSeconds); + setStatus('Recording ' + time + ' (Time Limit: ' + timeLimitFormatted + ')'); - // Clear the recorder. - Drupal.mediaRecorder.recorder.clear(); + if (currentSeconds >= settings.time_limit) { + stop(); + } + }, 1000); + }); - // Trigger stopped event. - $(Drupal.mediaRecorder).trigger('recordStop'); - }; + // Listen for the stop event. + $element.bind('recordStop', function (event) { + clearInterval(statusInterval); + setStatus('Uploading, please wait...'); + }); - 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); + $element.bind('uploadFinished', function (event, data) { + $element.find('.media-recorder-fid').val(data.fid); + $recordButton[0].disabled = false; + playbackPreview(); + setStatus('Press record to start recording.'); + }); + + $element.bind('status', function (event, msg) { + $statusWrapper.text(msg); + }); + } + + /** + * Convert milliseconds to time format. + */ + 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; + } + + /** + * Initialize recorder. + */ + function init(element) { + $element = $(element); + $statusWrapper = $element.find('.media-recorder-status'); + $previewWrapper = $element.find('.media-recorder-preview'); + $video = $element.find('.media-recorder-video'); + $audio = $element.find('.media-recorder-audio'); + $meter = $element.find('.media-recorder-meter'); + $startButton = $element.find('.media-recorder-enable'); + $recordButton = $element.find('.media-recorder-record'); + $stopButton = $element.find('.media-recorder-stop'); + $playButton = $element.find('.media-recorder-play'); + $settingsButton = $element.find('.media-recorder-settings'); + $videoButton = $element.find('.media-recorder-enable-video'); + $audioButton = $element.find('.media-recorder-enable-audio'); + + // Initial state. + $recordButton.hide(); + $stopButton.hide(); + $playButton.hide(); + $settingsButton.hide(); + $video.hide(); + $audio.hide(); + $meter.hide(); + $videoButton.hide(); + $audioButton.hide(); + $previewWrapper.hide(); + + // Show file preview if file exists. + if (Drupal.settings.mediaRecorder.file) { + var file = Drupal.settings.mediaRecorder.file; + switch (file.type) { + case 'video': + $previewWrapper.show(); + $video.show(); + $audio.hide(); + $video[0].src = Drupal.settings.mediaRecorder.file.url; + $video[0].muted = ''; + $video[0].controls = 'controls'; + $video[0].load(); + break; + case 'audio': + $previewWrapper.show(); + $audio.show(); + $video.hide(); + $audio[0].src = Drupal.settings.mediaRecorder.file.url; + $audio[0].muted = ''; + $audio[0].controls = 'controls'; + $audio[0].load(); + break; } - }; - }, - }; + } + + initializeButtons(); + initializeEvents(); + setStatus('Click \'Start\' to enable your mic & camera.'); + } + + return { + init: init, + start: start, + record: record, + stop: stop + }; + })(); })(jQuery); diff --git a/js/media-recorder.browser.js b/js/media-recorder.browser.js index af2f9fcb63cb33600b60b22706c04580b28a995e..d23533fddf8d186fc29f608f9ec5429faf3a5e71 100644 --- a/js/media-recorder.browser.js +++ b/js/media-recorder.browser.js @@ -3,23 +3,25 @@ * Media browser JS integration for the Media Recorder module. */ -(function($) { +(function ($) { + 'use strict'; + Drupal.behaviors.mediaRecorderBrowser = { - attach: function (context, settings) { + attach: function () { // Bind click handler to media recorder submit input. - $('#media-tab-media_recorder input[type="submit"]').bind('click', function (event) { + $('#media-recorder-add #edit-submit').bind('click', function (event) { // Prevent regular form submit. event.preventDefault(); // Get the fid value. - var fid = $('#media-tab-media_recorder .media-recorder-fid').val(); + var fid = $('#media-recorder-add .media-recorder-fid').val(); // Build file object. var file = {}; file.fid = fid; - file.preview = $('#media-tab-media_recorder .media-recorder-preview .content').html(); + file.preview = $('#media-recorder-add .media-recorder-preview').html(); // Add to selected media. var files = []; diff --git a/js/media-recorder.js b/js/media-recorder.js old mode 100755 new mode 100644 index 394130acf48087e91d9afaeac78dd6dfe2959844..47b7abcdc4f82535d03e156da318beb26f940318 --- a/js/media-recorder.js +++ b/js/media-recorder.js @@ -4,70 +4,59 @@ * libraries. */ -(function($) { +(function ($) { 'use strict'; - // Add mediaRecorder object to Drupal. - Drupal.mediaRecorder = Drupal.mediaRecorder || {}; - Drupal.mediaRecorder.settings = Drupal.settings.mediaRecorder.settings; - // 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 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' - }); + Drupal.behaviors.mediaRecorder = { + attach: function () { + + // Check to see that browser can use the recorder. + if ((getUserMediaCheck && webAudioCheck) || (flashVersionCheck && swfobjectCheck)) { + + $('.field-widget-media-recorder').once().each(function (key, element) { + + // Add Drupal MediaRecorder module and initialize. + if (getUserMediaCheck && webAudioCheck && mediaRecorderCheck) { + element.recorder = Drupal.MediaRecorder; + } + else if (getUserMediaCheck && webAudioCheck && !mediaRecorderCheck) { + element.recorder = Drupal.MediaRecorderHTML5; + } + else if (flashVersionCheck) { + + // Check for IE9+. + if (!document.addEventListener) { + alert("The media recorder is not available on versions of Internet Explorer earlier than IE9."); + $('.field-widget-media-recorder').find('.media-recorder-wrapper').hide(); + return; + } + element.recorder = Drupal.MediaRecorderFlash; + } + + // Hide the normal file input. + $(element).find('span.file, span.file-size, .media-recorder-upload, .media-recorder-upload-button, .media-recorder-remove-button').hide(); + + // Initialize the recorder. + element.recorder.init(element); + }); + } + + // Otherwise just use the basic file field. + else { + $('.field-widget-media-recorder').find('.media-recorder-wrapper').hide(); + } } - - // 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.info b/media_recorder.info old mode 100755 new mode 100644 diff --git a/media_recorder.install b/media_recorder.install old mode 100755 new mode 100644 index 4795676f0b33586bf8e141520f572864c99fcb9e..c6a378f3a5a5586b9c20f18434debe2240b91756 --- a/media_recorder.install +++ b/media_recorder.install @@ -18,7 +18,7 @@ function media_recorder_requirements($phase) { // Check that all libraries exist. $required_libraries = array( 'swfobject', - 'recorder.js', + 'FlashWavRecorder', 'Recorderjs', ); foreach ($required_libraries as $name) { @@ -53,7 +53,7 @@ function media_recorder_enable() { foreach ($required_libraries as $name) { $library = libraries_detect($name); if (!$library['installed']) { - $drush_msg = t('You can use the drush command "drush @site mrdl" to automatically install all required libraries.'); + $drush_msg = t('You can use the drush command "drush mrdl" to automatically install all required libraries.'); drupal_set_message($library['error message'] . ' ' . $drush_msg, 'error'); } } @@ -63,8 +63,8 @@ function media_recorder_enable() { * Implements hook_uninstall(). */ function media_recorder_uninstall() { - variable_del('media_recorder_width'); - variable_del('media_recorder_height'); - variable_del('media_recorder_timelimit'); + variable_del('media_recorder_constraints'); + variable_del('media_recorder_time_limit'); variable_del('media_recorder_upload_directory'); + variable_del('media_recorder_css'); } diff --git a/media_recorder.module b/media_recorder.module old mode 100755 new mode 100644 index 67a96ee165a83a363cfee81af58f4b0ffeeae689..09a7139433cb17bb6496880027533fa85f72b14e --- a/media_recorder.module +++ b/media_recorder.module @@ -13,15 +13,15 @@ function media_recorder_libraries_info() { $libraries['swfobject'] = array( 'name' => 'SWFObject', 'description' => 'SWFObject is an easy-to-use and standards-friendly method to embed Flash content, which utilizes one small JavaScript file.', - 'vendor url' => 'http://code.google.com/p/swfobject', - 'download url' => 'http://swfobject.googlecode.com/files/swfobject_2_2.zip', + 'vendor url' => 'https://github.com/swfobject/swfobject', + 'download url' => 'https://github.com/swfobject/swfobject/zipball/master', 'version arguments' => array( - 'file' => 'swfobject.js', + 'file' => 'swfobject/swfobject.js', 'pattern' => '@v([0-9a-zA-Z\.-]+)@', ), 'files' => array( 'js' => array( - 'swfobject.js' => array( + 'swfobject/swfobject.js' => array( 'type' => 'file', 'scope' => 'header', 'group' => JS_LIBRARY, @@ -29,20 +29,23 @@ function media_recorder_libraries_info() { ), ), ); - $libraries['recorder.js'] = array( - 'name' => 'recorder.js', - 'description' => 'JavaScript library to record audio in browsers as used in the SoundCloud Javascript SDK.', - 'vendor url' => 'https://github.com/jwagener/recorder.js', - 'download url' => 'https://github.com/jwagener/recorder.js/zipball/master', - 'version arguments' => array( - 'file' => 'recorder.js', - 'pattern' => '@version: ([0-9a-zA-Z\.-]+),@', - ), + $libraries['FlashWavRecorder'] = array( + 'name' => 'FlashWavRecorder', + 'description' => 'Simple flash file for recording audio and saving as a WAV.', + 'vendor url' => 'https://github.com/michalstocki/FlashWavRecorder', + 'download url' => 'https://github.com/michalstocki/FlashWavRecorder/zipball/master', + 'version' => '0.9.0', 'dependencies' => array( 'swfobject (>=2.2)', ), 'files' => array( - 'js' => array('recorder.js'), + 'js' => array( + 'html/js/recorder.js' => array( + 'type' => 'file', + 'scope' => 'header', + 'group' => JS_LIBRARY, + ), + ), ), ); $libraries['Recorderjs'] = array( @@ -127,6 +130,7 @@ function media_recorder_help($path, $arg) { $output = file_get_contents(drupal_get_path('module', 'media_recorder') . '/README.txt'); return nl2br($output); } + return NULL; } /** @@ -274,7 +278,8 @@ function media_recorder_record_stream_finish() { } // Sort files in case they are out of order. - ksort($files, SORT_NATURAL); + $files = array_keys($files); + natsort($files); // Open temp file. try { @@ -290,7 +295,7 @@ function media_recorder_record_stream_finish() { } // Iterate over file list and append to temp file. - foreach (array_keys($files) as $filename) { + foreach ($files as $filename) { // Get data from file. try { @@ -338,8 +343,10 @@ function media_recorder_record_stream_finish() { // Change the file name and save as temporary managed file. try { - 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']); + $uri = $_SESSION['media_recorder']['tempnam']; + $extension = '.' . $_SESSION['media_recorder']['format']; + file_unmanaged_move($uri, $uri . $extension); + $file = file_uri_to_object($uri . $extension); $file->status = 0; file_save($file); } @@ -422,8 +429,9 @@ function media_recorder_record_file() { // Change the file name and save as temporary managed file. try { - file_unmanaged_move($uri, $uri . '.wav'); - $file = file_uri_to_object($uri . '.wav'); + $extension = '.wav'; + file_unmanaged_move($uri, $uri . $extension); + $file = file_uri_to_object($uri . $extension); $file->status = 0; file_save($file); } @@ -433,9 +441,6 @@ function media_recorder_record_file() { return; } - // Close session. - unset($_SESSION['media_recorder']); - // Return file information. drupal_json_output($file); } @@ -455,7 +460,7 @@ function media_recorder_media_browser_plugin_info() { /** * Provides a form for adding media items using the media recorder. */ -function media_recorder_add($form, &$form_state) { +function media_recorder_add($form, &$form_state, $types = array(), $multiselect = FALSE) { // Set field variables. $field_name = 'field_media_recorder'; @@ -479,6 +484,11 @@ function media_recorder_add($form, &$form_state) { 'settings' => array( 'progress_indicator' => 'throbber', 'time_limit' => variable_get('media_recorder_time_limit', 300), + 'constraints' => variable_get('media_recorder_constraints', array( + 'audio' => TRUE, + 'video' => TRUE, + 'video_resolution' => 320, + )), ), ), ); @@ -512,8 +522,10 @@ function media_recorder_add($form, &$form_state) { $key = array_search('file_field_widget_process', $form[$field_name][$langcode][0]['#process']); unset($form[$field_name][$langcode][0]['#process'][$key]); - // Add media browser javascript and CSS. - drupal_add_js(drupal_get_path('module', 'media_recorder') . '/js/media-recorder.browser.js'); + // Add javascript if this is the media browser. + if ($types) { + drupal_add_js(drupal_get_path('module', 'media_recorder') . '/js/media-recorder.browser.js'); + } // Add a submit button. $form['actions']['submit']['#type'] = 'submit'; @@ -534,7 +546,7 @@ function media_recorder_add_submit($form, &$form_state) { $file->status = FILE_STATUS_PERMANENT; $file = file_save($file); } - drupal_set_message(t('The file !filename was successfully loaded.', array('!filename' => l(check_plain($file->filename), 'file/' . $file->fid))), 'status'); + drupal_set_message(t('The file !filename was successfully saved.', array('!filename' => l(check_plain($file->filename), 'file/' . $file->fid))), 'status'); } else { drupal_set_message(t('An unrecoverable error occurred. Try reloading the page and submitting again.'), 'error'); @@ -552,6 +564,12 @@ function media_recorder_field_widget_info() { 'settings' => array( 'progress_indicator' => 'throbber', 'allowed_schemes' => array('public', 'private'), + 'time_limit' => 300, + 'constraints' => array( + 'audio' => TRUE, + 'video' => TRUE, + 'video_resolution' => 320, + ), ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, @@ -575,10 +593,37 @@ function media_recorder_field_widget_settings_form($field, $instance) { '#type' => 'textfield', '#title' => t('Time Limit'), '#description' => t('Time limit in seconds. Defaults to 300 seconds (5 minutes).'), - '#default_value' => isset($settings['time_limit']) ? $settings['time_limit'] : 300, + '#default_value' => $settings['time_limit'], '#element_validate' => array('element_validate_integer_positive'), '#required' => TRUE, ); + $form['constraints'] = array( + '#type' => 'fieldset', + '#title' => t('Media Constraints'), + '#description' => t('Select which recording options will be available.'), + ); + $form['constraints']['audio'] = array( + '#type' => 'checkbox', + '#title' => t('Audio'), + '#default_value' => $settings['constraints']['audio'], + ); + $form['constraints']['video'] = array( + '#type' => 'checkbox', + '#title' => t('Video'), + '#default_value' => $settings['constraints']['video'], + ); + $form['constraints']['video_resolution'] = array( + '#type' => 'radios', + '#title' => t('Video Resolution'), + '#default_value' => $settings['constraints']['video_resolution'], + '#options' => array( + 640 => t('640 x 480'), + 480 => t('480 x 360'), + 320 => t('320 x 240'), + 240 => t('240 x 180'), + 180 => t('180 x 135'), + ), + ); return $form; } @@ -606,15 +651,13 @@ function media_recorder_field_widget_form(&$form, &$form_state, $field, $instanc */ function media_recorder_field_widget_form_process($element, &$form_state, $form) { - // Alter file elements. - $element['fid']['#attributes']['class'][] = 'media-recorder-fid'; - $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; - - // Add validation handler to beginning of validation handler stack. - array_unshift($element['#element_validate'], 'media_recorder_field_widget_form_process_record_validate'); + // Get current file if it exists. + $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : (isset($form_state['values'][$element['#field_name']][$element['#language']][$element['#delta']]['fid']) ? $form_state['values'][$element['#field_name']][$element['#language']][$element['#delta']]['fid'] : 0); + $file = NULL; + if ($fid) { + $file = file_load($fid); + $file->url = file_create_url($file->uri); + } // Add media recorder. $element['record'] = array( @@ -622,99 +665,77 @@ function media_recorder_field_widget_form_process($element, &$form_state, $form) '#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'] . '-file-preview-ajax-wrapper', - ), - '#attributes' => array( - 'style' => 'display: none;', - 'class' => array('media-recorder-refresh'), - ), + // Add libraries. + $element['#attached']['libraries_load'] = array( + array('swfobject'), + array('FlashWavRecorder'), + array('Recorderjs'), ); - // Add file preview to render array. - $element['preview'] = array( - '#type' => 'container', - '#weight' => 50, - '#attributes' => array( - 'class' => array('media-recorder-file-preview'), + // Add custom js. + $element['#attached']['js'] = array( + array( + 'type' => 'file', + 'data' => drupal_get_path('module', 'media_recorder') . '/js/media-recorder-api.js', + 'scope' => 'footer', ), - '#id' => $element['#id'] . '-file-preview-ajax-wrapper', - ); - - // Only display file if fid exists. - $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : (isset($form_state['values'][$element['#field_name']][$element['#language']][$element['#delta']]['fid']) ? $form_state['values'][$element['#field_name']][$element['#language']][$element['#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'), + array( + 'type' => 'file', + 'data' => drupal_get_path('module', 'media_recorder') . '/js/media-recorder-html5.js', + 'scope' => 'footer', ), - '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', - 'settings' => $element['#settings'], - ), + array( + 'type' => 'file', + 'data' => drupal_get_path('module', 'media_recorder') . '/js/media-recorder-flash.js', + 'scope' => 'footer', + ), + array( + 'type' => 'file', + 'data' => drupal_get_path('module', 'media_recorder') . '/js/media-recorder.js', + 'scope' => 'footer', + ), + array( + 'type' => 'setting', + 'data' => array( + 'mediaRecorder' => array( + 'settings' => $element['#settings'], + 'flashurl' => libraries_get_path('FlashWavRecorder'), + 'html5url' => libraries_get_path('Recorderjs'), + 'file' => $file, ), - 'scope' => 'header', ), + 'scope' => 'header', ), - 'css' => array( + ); + + // Add custom css. + if (variable_get('media_recorder_css', TRUE)) { + $element['#attached']['css'] = array( array( 'type' => 'file', 'data' => drupal_get_path('module', 'media_recorder') . '/css/media-recorder.css', ), - ), - ); - - return $element; -} - -/** - * Ajax callback for form element rebuild. - * @see media_recorder_field_widget_form_process() - */ -function media_recorder_field_widget_form_process_ajax_refresh($form, &$form_state) { + ); + } - // Rebuild the form. - $form_state['rebuild'] = TRUE; + // Alter file elements. + $element['fid']['#attributes']['class'][] = 'media-recorder-fid'; + $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; - // Get the file field element. - $parents = $form_state['triggering_element']['#array_parents']; - array_pop($parents); - $element = drupal_array_get_nested_value($form, $parents); + // Add validation handler to beginning of validation handler stack. + array_unshift($element['#element_validate'], 'media_recorder_field_widget_form_process_validate'); - return $element['preview']; + return $element; } /** * Custom validation callback. * @see media_recorder_field_widget_form_process() */ -function media_recorder_field_widget_form_process_record_validate(&$element, &$form_state) { +function media_recorder_field_widget_form_process_validate(&$element, &$form_state, $form) { // Get field information. $field_name = $element['#parents'][0]; diff --git a/theme/media-recorder.tpl.php b/theme/media-recorder.tpl.php index 2dbc3e41eed530f5247406cfe239a2726ceff31d..f8331a85b4a586b8dbf354bdef75ddbaf0d0f442 100644 --- a/theme/media-recorder.tpl.php +++ b/theme/media-recorder.tpl.php @@ -6,21 +6,29 @@ * * @see template_preprocess() * @see template_process() + * + * Important: Do not change any classes for elements. Media Recorder uses + * these to bind recorder functionality. Adding classes will not affect this. */ ?>
-
- - +
+ + +
-
+ + + + +