Grabar y reproducir - CGastrell/phonegap GitHub Wiki

Grabar y reproducir.

Para terminar la aplicacion vamos a agregar un boton que grabe y detenga la grabacion. Vamos a necesitar crear un directorio audio donde vamos a ubicar el archivo de grabacion y cuando detengamos la grabacion vamos a cargar ese mismo archivo para poder escucharlo.

HTML

Primero vamos a agregar un boton mas en el HTML a continuacion de los otros 2 botones que teniamos:

<a href="#" id="rec" class="ui-btn ui-btn-b ui-icon-comment ui-btn-icon-top">Grabar()</a>

Archivo de grabacion

En este ejemplo simple vamos a usar siempre el mismo archivo, o sea que siempre vamos a estar sobreescribiendolo.

Para esto vamos a hacer una function que se encargue de:

  • buscar el directorio cordova.file.externalDataDirectory
    • buscar en este directorio un directorio audio, si no existe debe crearlo
      • crear un archivo en el directorio audio que se llame record.amr, si no existe debe crearlo

No vale la pena usar fileApi que alguna vez creamos. Podemos empezar por probar con una funcion simple:

function getRecordFile(){
  var root = cordova.file.externalDataDirectory;
  window.resolveLocalFileSystemURL(root, function(rootDir){
    console.log(rootDir);
  });
}

Prueben en la consola asi no hay que recompilar la aplicacion cada vez. La idea es ver como llegar hasta este archivo. Con esta funcion visualizamos el directorio raiz de almacenamiento de nuestra aplicacion.

Ahora expandimos la funcionalidad para buscar el directorio audio y pasamos la opcion create:true para que, si no lo encuentra, lo cree:

function getRecordFile(){
  var root = cordova.file.externalDataDirectory;
  window.resolveLocalFileSystemURL(root, function(rootDir){
    console.log(rootDir);
    rootDir.getDirectory('audio', {create: true}, function(audioDir){
      console.log(audioDir);
    });
  });
}

Ahora la funcion deberia llegar a imprimir la segunda referencia de directorio: audioDir. Volvemos a extender la funcionalidad para buscar el archivo record.amr:

function getRecordFile(){
  var root = cordova.file.externalDataDirectory;
  window.resolveLocalFileSystemURL(root, function(rootDir){
    console.log(rootDir);
    rootDir.getDirectory('audio', {create: true}, function(audioDir){
      console.log(audioDir);
      audioDir.getFile('record.amr', {create: true}, function(file){
        console.log(file);
      });
    });
  });
}

Ahora que llegamos hasta aca podemos ver que tenemos un spaghetti code, pero como solo lo necesitamos una vez nos vamos permitir la atrocidad. Pero parte del problema es que, como estamos dentro del resultado de 3 funciones, no podemos simplemente devolver el valor. Cualquier llamada a return en estas funciones no podra elevar el valor hasta la funcion inicial. Entonces la convertimos en asincronica agregando un parametro en la funcion inicial, que debera ser una funcion, y ejecutaremos esta funcion al llegar al resultado final:

function getRecordFile(callback){ // <-- recibimos una func como parametro
  var root = cordova.file.externalDataDirectory;
  window.resolveLocalFileSystemURL(root, function(rootDir){
    rootDir.getDirectory('audio', {create: true}, function(audioDir){
      audioDir.getFile('record.amr', {create: true}, function(file){
        callback(file); // <-- ejecutamos la funcion, pasandole
                        // el resultado
      });
    });
  });
}

recordApi

Para grabar con prolijidad vamos a hacer otro objeto en nuestra aplicacion: recordApi. Se va a encargar de:

  • inicializar el boton
  • obtener el archivo de grabacion llamando a getRecordFile()
  • manejar las llamadas a startRecord() y stopRecord()
  • usar mediaApi.load cuando termine la grabacion para que podamos escucharla

Inicializacion

var recordApi = {
  initialize: function(){
    //inicializar el boton
    $('#rec').click(function(e){
      e.preventDefault();
      if(recordApi.isRecording) {
        recordApi.stop();
      }else{
        recordApi.record();
      }
    });

    //guardamos una referencia al boton
    recordApi.button = $('#rec');

    //inicializar estado
    recordApi.isRecording = false;

    //obtener la ruta al archivo de grabacion
    getRecordFile(function(file){
      recordApi.recordFile = file.nativeURL;
    });
  },

Handlers

El handler de error solo nos muestra el error. onStop (que en mediaApi llamamos onSuccess) se encarga de que cuando termine la grabacion, esta se cargue en mediaApi. Y onStatus ahora cumple la funcion de decorar la interfaz: a medida que detecte el cambio a RUNNING (que en el caso de grabacion indica que esta grabando) cambiara el boton y cuando detecte el cambio a STOPPED lo volvera a la normalidad:

  onStop: function(){
    recordApi.media.release();
    mediaApi.load(recordApi.recordFile);
  },
  onError: function(err){
    console.log('Recording error');
    console.log(err);
  },
  onStatus: function(status){
    switch(status) {
      case Media.MEDIA_RUNNING:
        console.log('Status change: running');
        recordApi.isRecording = true;
        recordApi.button.css('background-color','red');
        recordApi.button.text('Grabando...');
      break;
      case Media.MEDIA_STOPPED:
        console.log('Status change: stopped');
        recordApi.isRecording = false;
        recordApi.button.css('background-color', '#333');
        recordApi.button.text('Grabar()');
      break;
    }
  },

Start/Stop

Por ultimo estan las funciones que ejecuta el boton. Como las consecuencias de estas acciones las estamos manejando desde onStatus, aca solo indicamos las acciones a tomar:

  record: function(){
    recordApi.media = new Media(recordApi.recordFile, recordApi.onStop, recordApi.onError, recordApi.onStatus);
    recordApi.media.startRecord();
  },
  stop: function(){
    recordApi.media.stopRecord();
  },

Revisamos un poco la sintaxis y probamos.

Nota: para revisar la sintaxis es bueno mantener ordenado el objeto que estamos creando. Basicamente el objeto recordApi tiene esta estructura (sin importar el orden de los metodos):

var recordApi = {
  initialize: function(){},
  record: function(){},
  stop: function(){},
  onStop: function(){},
  onError: function(err){},
  onStatus: function(status){}
}

Continuaremos revisando la interfaz y funcionalidad de Interview Recorder