Skip to content
media_recorder.module 21.2 KiB
Newer Older
<?php
/**
 * @file
 * Adds a media recorder widget and add form to the media module.
 */

/**
 * Implements hook_libraries_info().
 */
function media_recorder_libraries_info() {
  $libraries = array();
  $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',
    'version arguments' => array(
      'file' => 'swfobject.js',
      'pattern' => '@v([0-9a-zA-Z\.-]+)@',
    ),
    'files' => array(
      'js' => array(
        'swfobject.js' => array(
          'type' => 'file',
          'scope' => 'header',
          'group' => JS_LIBRARY,
        ),
      ),
  $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\.-]+),@',
    ),
    'dependencies' => array(
      'swfobject (>=2.2)',
    ),
    'files' => array(
      'js' => array('recorder.js'),
    ),
  );
  $libraries['Recorderjs'] = array(
    'name' => 'Recorderjs',
    'description' => 'A plugin for recording/exporting the output of Web Audio API nodes',
    'vendor url' => 'https://github.com/mattdiamond/Recorderjs',
    'download url' => 'https://github.com/mattdiamond/Recorderjs/zipball/master',
    'version' => '',
    'files' => array(
      'js' => array('recorder.js'),
  return $libraries;
}

/**
 * Implements hook_menu().
 */
function media_recorder_menu() {
  $items['file/add/record'] = array(
    'title' => 'Record',
    'description' => 'Add recordings to your media library.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('media_recorder_add'),
    'access callback' => 'file_entity_access',
    'access arguments' => array('create'),
    'type' => MENU_LOCAL_TASK,
  );
  $items['media_recorder/record/file'] = array(
    'title' => 'Record',
    'description' => 'Record a video or audio file.',
    'page callback' => 'media_recorder_record_file',
    'access callback' => 'file_entity_access',
    'access arguments' => array('create'),
    'type' => MENU_CALLBACK,
  );
  $items['media_recorder/record/stream/start'] = array(
    'title' => 'Record',
    'description' => 'Record a video or audio file as a stream.',
    'page callback' => 'media_recorder_record_stream_start',
    'access callback' => 'file_entity_access',
    'access arguments' => array('create'),
    'type' => MENU_CALLBACK,
  );
  $items['media_recorder/record/stream/record'] = array(
    'title' => 'Record',
    'description' => 'Record a video or audio file as a stream.',
    'page callback' => 'media_recorder_record_stream_record',
    'access callback' => 'file_entity_access',
    'access arguments' => array('create'),
    'type' => MENU_CALLBACK,
  );
  $items['media_recorder/record/stream/finish'] = array(
    'title' => 'Record',
    'description' => 'Record a video or audio file as a stream.',
    'page callback' => 'media_recorder_record_stream_finish',
    'access callback' => 'file_entity_access',
    'access arguments' => array('create'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/config/media/mediarecorder'] = array(
    'description' => 'Configure the media recorder.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('media_recorder_admin_form'),
    'access arguments' => array('administer files'),
    'file' => 'includes/media_recorder.admin.inc',
/**
 * Implements hook_help().
 */
function media_recorder_help($path, $arg) {
  if ($path == 'admin/help#media_recorder') {
    $output = file_get_contents(drupal_get_path('module', 'media_recorder') . '/README.txt');
    return nl2br($output);
  }
}

/**
 * Implements hook_theme().
 */
function media_recorder_theme($existing, $type, $theme, $path) {
  return array(
    'media_recorder' => array(
      'variables' => array(),
      'template' => 'media-recorder',
      'path' => drupal_get_path('module', 'media_recorder') . '/theme',
    ),
  );
}

/**
 * Menu callback for recording a media file.
 */
function media_recorder_record_stream_start() {

  // Set session status as open.
  $_SESSION['media_recorder']['status'] = TRUE;
  // Delete the file if for some reason file_unmanaged_move was not successful.
  if (file_exists($_SESSION['media_recorder']['tempnam'])) {
    unlink($_SESSION['media_recorder']['tempnam']);
  }

  // Reset temporary file name in case this wasn't reset after stream finished.
  unset($_SESSION['media_recorder']['tempnam']);

  // Reset file format.
  unset($_SESSION['media_recorder']['format']);

  // Create a new temporary file to save streamed data.
  try {
    $_SESSION['media_recorder']['tempnam'] = drupal_tempnam('temporary://', 'mediaRecorder_');
    if (!$_SESSION['media_recorder']['tempnam']) {
      throw new Exception("Unable to create temporary file.");
    }
  }
  catch (Exception $e) {
    header('HTTP/1.0 419 Custom Error');
    echo drupal_json_output($e->getMessage());
    exit;
  }

  // Get file format.
  try {
    $_SESSION['media_recorder']['format'] = $_POST['format'];
    if (!$_SESSION['media_recorder']['format']) {
      throw new Exception("Unable to get file format.");
    }
  }
  catch (Exception $e) {
    header('HTTP/1.0 419 Custom Error');
    echo drupal_json_output($e->getMessage());
    exit;
  }
}

/**
 * Menu callback for recording a media file.
 */
function media_recorder_record_stream_record() {

  // Return if there is no streaming session open.
  if (!$_SESSION['media_recorder']['status']) {
    return;
  }

  // Get file blob url.
  $url = isset($_FILES['mediaRecorder']['tmp_name']) ? $_FILES['mediaRecorder']['tmp_name'] : '';

  // Append streaming data to temporary file while recording is not finished.
  if (!empty($url)) {

    try {
      $data = file_get_contents($url);
      if (!$data) {
        throw new Exception("Streaming data file is empty.");
      }
    }
    catch (Exception $e) {
      header('HTTP/1.0 419 Custom Error');
      echo drupal_json_output($e->getMessage());
      echo drupal_json_output($url);
      exit;
    }

    try {
      $fp = fopen($_SESSION['media_recorder']['tempnam'], 'a');
      if (!$fp) {
        throw new Exception("Unable to open temporary file. Please check that your file permissions are set correctly.");
      }
    }
    catch (Exception $e) {
      header('HTTP/1.0 419 Custom Error');
      echo drupal_json_output($e->getMessage());
      exit;
    }

    if ($data && $fp) {
      try {
        fwrite($fp, $data);
        fclose($fp);
      }
      catch (Exception $e) {
        header('HTTP/1.0 419 Custom Error');
        echo drupal_json_output($e->getMessage());
        exit;
      }
    }

    echo drupal_json_output($_FILES['mediaRecorder']);
  }

  else {
    header('HTTP/1.0 419 Custom Error');
    echo drupal_json_output('Temporary file not found.');
    echo drupal_json_output($url);
    exit;
  }
}

/**
 * Menu callback for recording a media file.
 */
function media_recorder_record_stream_finish() {

  // Check that file exists.
  if (file_exists($_SESSION['media_recorder']['tempnam'])) {

    // Change the file name.
    file_unmanaged_move($_SESSION['media_recorder']['tempnam'], $_SESSION['media_recorder']['tempnam'] . '.' . $_SESSION['media_recorder']['format']);

    $file = file_uri_to_object($_SESSION['media_recorder']['tempnam'] . '.' . $_SESSION['media_recorder']['format']);
    $file->status = 0;
    file_save($file);

    // Delete the file if for some reason file_unmanaged_move was not successful.
    if (file_exists($_SESSION['media_recorder']['tempnam'])) {
      unlink($_SESSION['media_recorder']['tempnam']);
    }

    $_SESSION['media_recorder']['status'] = FALSE;

    // Return file information.
    echo drupal_json_output($file);
  }

  else {
    header('HTTP/1.0 419 Custom Error');
    echo drupal_json_output(t('File is empty.'));
    exit;
  }
}

/**
 * Menu callback for recording a media file.
 */
function media_recorder_record_file() {

  // Get file blob url.
  $url = isset($_FILES['mediaRecorder']['tmp_name']) ? $_FILES['mediaRecorder']['tmp_name'] : '';

  // Attempt to save file data.
  if (!empty($url)) {

    // Create a new temporary file to save data.
    try {
      $uri = drupal_tempnam('temporary://', 'mediaRecorder_');
      if (!$uri) {
        throw new Exception("Unable to create temporary file.");
      }
    }
    catch (Exception $e) {
      header('HTTP/1.0 419 Custom Error');
      echo drupal_json_output($e->getMessage());
      exit;
    }

    try {
      $data = file_get_contents($url);
      if (!$data) {
        throw new Exception("There was no data sent.");
      }
    }
    catch (Exception $e) {
      header('HTTP/1.0 419 Custom Error');
      echo drupal_json_output($e->getMessage());
      echo drupal_json_output($url);
      exit;
    }

    try {
      $fp = fopen($uri, 'a');
      if (!$fp) {
        throw new Exception("Unable to open temporary file. Please check that your file permissions are set correctly.");
      }
    }
    catch (Exception $e) {
      header('HTTP/1.0 419 Custom Error');
      echo drupal_json_output($e->getMessage());
      exit;
    }

    if ($data && $fp) {
      try {
        fwrite($fp, $data);
        fclose($fp);
      }
      catch (Exception $e) {
        header('HTTP/1.0 419 Custom Error');
        echo drupal_json_output($e->getMessage());
        exit;
      }
    }
  else {
    header('HTTP/1.0 419 Custom Error');
    echo drupal_json_output('No file was sent.');
    exit;
  }

  // Check that file exists.
  if (file_exists($uri)) {

    file_unmanaged_move($uri, $uri . '.wav');
    $file = file_uri_to_object($uri . '.wav');
    $file->status = 0;
    file_save($file);
    // Delete the file if for some reason file_unmanaged_move was not successful.
    if (file_exists($uri)) {
      unlink($uri);
    }

    echo drupal_json_output($file);
  }
    header('HTTP/1.0 419 Custom Error');
    echo drupal_json_output(t('There was an error saving the file.'));
    exit;
/**
 * Implements hook_media_browser_plugin_info().
 */
function media_recorder_media_browser_plugin_info() {
  $info['media_recorder'] = array(
    'title' => t('Recorder'),
    'class' => 'MediaRecorderBrowser',
  );

  return $info;
}

 * Provides a form for adding media items using the media recorder.
function media_recorder_add($form, &$form_state) {

  // Set field variables.
  $field_name = 'field_media_recorder';
  $langcode = LANGUAGE_NONE;
  $items = array();
  $delta = 0;
  $field = array(
    'field_name' => $field_name,
    'cardinality' => 1,
    'settings' => array(
      'uri_scheme' => 'public',
      'display_default' => 0,
    ),
  );
  $instance = array(
    'settings' => array(
      'file_directory' => variable_get('media_recorder_upload_directory', ''),
      'file_extensions' => 'wav mp3 m4a mov m4v mp4 mpeg mpg avi ogg oga ogv webm',
    'widget' => array(
      'settings' => array(
        'progress_indicator' => 'throbber',
      ),
    ),
  );
  $element['#field_name'] = $field_name;
  $element['#language'] = $langcode;
  $element['#delta'] = $delta;
  $element['#id'] = 'edit-field-media-recorder-und-0';
  $element['#attributes']['class'][] = 'field-widget-media-recorder';
  $element['#field_parents'] = array();
  $element['#columns'] = array('fid', 'display', 'description');
  $element['#title'] = t('Media Recorder');
  $element['#description'] = '';
  $element['#upload_location'] = 'public://' . variable_get('media_recorder_upload_directory', '');
  $element['#upload_validators']['file_validate_extensions'][] = 'wav mp3 m4a mov m4v mp4 mpeg mpg avi ogg oga ogv webm';
  // Add title field.
  $form['#title'] = t('Title');
  $form['title']['#type'] = 'textfield';
  $form['title']['#title'] = t('Title');
  $form['title']['#required'] = TRUE;
  $form[$field_name]['#type'] = 'container';
  $form[$field_name]['#tree'] = TRUE;
  $form[$field_name][$langcode] = media_recorder_field_widget_form($form, $form_state, $field, $instance, $langcode, $items, $delta, $element);

  // Remove file field widget process, since this is not an actual field widget.
  $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');
  $form['actions']['submit']['#type'] = 'submit';
  $form['actions']['submit']['#value'] = t('Save Recording');

/**
 * Submit handler for media_recorder_add form.
 * @see media_recorder_add()
 */
function media_recorder_add_submit($form, &$form_state) {
  $fid = $form_state['values']['field_media_recorder'][LANGUAGE_NONE][0]['fid'];
  if ($fid != 0) {
    $file = file_load($fid);
    if (!$file->status) {
      $file->status = FILE_STATUS_PERMANENT;
      $file = file_save($file);
    }
    drupal_set_message(t('The file <em>!filename</em> was successfully loaded.', 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');
  }
}

/**
 * Implements hook_field_widget_info().
 */
function media_recorder_field_widget_info() {
  return array(
    'media_recorder' => array(
      'label' => t('Media Recorder'),
      'field types' => array('file'),
      'settings' => array(
        'progress_indicator' => 'throbber',
        'allowed_schemes' => array('public', 'private'),
      ),
      'behaviors' => array(
        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
        'default value' => FIELD_BEHAVIOR_NONE,
      ),
    ),
  );
}

/**
 * Implements hook_field_widget_settings_form().
 */
function media_recorder_field_widget_settings_form($field, $instance) {

  // Get default file widget settings form.
  $form = file_field_widget_settings_form($field, $instance);

  return $form;
}

/**
 * Implements hook_field_widget_form().
 */
function media_recorder_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  // Get default file widget form.
  $elements = file_field_widget_form($form, $form_state, $field, $instance, $langcode, $items, $delta, $element);
  // Alter process callbacks.
  foreach (element_children($elements) as $delta) {
    $elements[$delta]['#process'][] = 'media_recorder_field_widget_form_process';
/**
 * An element #process callback.
 * @see media_recorder_field_widget_form()
 */
function media_recorder_field_widget_form_process($element, &$form_state, $form) {
  $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');
  // Add media recorder.
  $element['record'] = array(
    '#theme' => 'media_recorder',
    '#title' => 'Record',
  );
  // Add hidden refresh submit, which is triggered on record finish.
  // This will rebuild the form with new file preview.
  $element['refresh'] = array(
    '#type' => 'submit',
    '#executes_submit_callback' => FALSE,
    '#limit_validation_errors' => array(),
    '#value' => t('Refresh'),
    '#ajax' => array(
      'callback' => 'media_recorder_field_widget_form_process_ajax_refresh',
      'wrapper' => $element['#id'] . '-file-preview-ajax-wrapper',
      'style' => 'display: none;',
      'class' => array('media-recorder-refresh'),
  // Add file preview to render array.
  $element['preview'] = array(
    '#type' => 'container',
    '#weight' => 50,
    '#attributes' => array(
      'class' => array('media-recorder-file-preview'),
    '#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'][$field_name][$langcode][$delta]['fid']) ? $form_state['values'][$field_name][$langcode][$delta]['fid'] : 0);
  if ($fid) {
    $file = file_load($fid);
    $element['preview']['file'] = file_view($file);
    $element['preview']['#type'] = 'fieldset';
    $element['preview']['#title'] = 'File Preview';
  }

  // Add attached files and settings.
  $element['#attached'] = array(
    'libraries_load' => array(
      array('swfobject'),
    ),
    'js' => array(
      array(
        'type' => 'file',
        'data' => drupal_get_path('module', 'media_recorder') . '/js/media-recorder.js',
        'scope' => 'footer',
      ),
      array(
        'type' => 'setting',
        'data' => array(
          'mediaRecorder' => array(
            'swfurl' => libraries_get_path('recorder.js'),
            'html5url' => libraries_get_path('Recorderjs'),
            'modulePath' => drupal_get_path('module', 'media_recorder') . '/js',
          ),
        ),
        'scope' => 'header',
      ),
    ),
    'css' => array(
      array(
        'type' => 'file',
        'data' => drupal_get_path('module', 'media_recorder') . '/css/media-recorder.css',
      ),
    ),
  );
 * 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;
  // Get the file field element.
  $parents = $form_state['triggering_element']['#array_parents'];
  array_pop($parents);
  $element = drupal_array_get_nested_value($form, $parents);
 * @see media_recorder_field_widget_form_process()
function media_recorder_field_widget_form_process_record_validate(&$element, &$form_state) {

  // Get field information.
  $field_name = $element['#parents'][0];
  $langcode = $element['#parents'][1];
  $delta = isset($element['#parents'][2]) ? $element['#parents'][2] : 0;

  // Get the fid.
  $fid = !empty($form_state['values'][$field_name][$langcode][$delta]['fid']) ? $form_state['values'][$field_name][$langcode][$delta]['fid'] : 0;

  // Load file from fid.
  if ($fid != 0) {
    $file = file_load($fid);
  }

  // Grab title from entity if available.
  if (isset($form_state['values']['title']) && !empty($form_state['values']['title'])) {
    $title = $form_state['values']['title'];
  }

  // Add custom logic for comments.
  elseif (isset($form_state['comment']) && is_object($form_state['comment'])) {
    // Use the comment subject value if present.
    if (isset($form_state['values']['subject']) && !empty($form_state['values']['subject'])) {
      $title = $form_state['values']['subject'];
    }
    // Otherwise use the node title.
    elseif (isset($form_state['comment']->nid) && is_numeric($form_state['comment']->nid)) {
      $node = node_load($form_state['values']['nid']);
      $title = t('Comment on @title', array('@title' => $node->title));
    }
    // Validate file extensions, since managed file validates client side.
    $file->filename = drupal_basename($file->uri);
    $file_validate_size_errors = file_validate($file, $element['#upload_validators']);
    if (empty($file_validate_size_errors)) {

      // Prepare directory.
      if (module_exists('token')) {
        $upload_location = token_replace($element['#upload_location']);
        $upload_location = $element['#upload_location'];
      }
      if (file_prepare_directory($upload_location, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {

        // Add new title.
        if (!empty($title)) {
          $file->filename = trim($title);
        }

        // Move the file to the field's upload location.
        $file = file_move($file, $upload_location);

        // Set file field input & values.
        drupal_array_set_nested_value(
          $form_state['input'],
          $element['#parents'],
          array(
            'fid' => $file->fid,
            'display' => TRUE,
        );
        drupal_array_set_nested_value(
          $form_state['values'],
          $element['#parents'],
          array(
            'fid' => $file->fid,
            'display' => TRUE,

    // Report file validation errors.
    else {
      form_set_error('media_recorder', implode('<br />', $file_validate_size_errors));
      return;
    }