iRec mediaApi refactor - CGastrell/phonegap GitHub Wiki

mediaApi refactor

La ultima vez que vimos mediaApi se veia asi (version etonini):

var mediaApi = {
  playTime: 0,
  initialize: function() {
    //inicializacion de estado de reproduccion
    mediaApi.isPlaying = false;

    //initialize with load
    // mediaApi.load('/android_asset/www/intro.mp3');
  },
  pausa: function() {
    if(!mediaApi.isPlaying) {
      return;
    }
    clearInterval(mediaApi.interval);
    mediaApi.interval = null;
    mediaApi.isPlaying = false;
    mediaApi.audio.pause();
  },
  play: function() {
    if(mediaApi.isPlaying || !mediaApi.audio) {
      return;
    }
    mediaApi.interval = setInterval(function(){
      mediaApi.audio.getCurrentPosition(function(t){
        mediaApi.playTime = t;
        mediaApi.currentTime.text( clockFormat(t) );
      });
    },500);
    mediaApi.isPlaying = true;
    mediaApi.audio.play();
  },
  createTagButton: function(ref) {
    var button = $('<button />')
      .addClass("ui-btn ui-btn-inline ui-mini")
      .text('+')
      .click(function(e){
        var d = new Date(mediaApi.entrevista.start);
        d.setSeconds(d.getSeconds() + mediaApi.playTime);
        mediaApi.entrevista.tags.push({ref: ref, time: d});
      });
    return button;
  },
  onSuccess: function(){
    if(mediaApi.interval) {
      clearInterval(mediaApi.interval);
      mediaApi.currentTime.text("00:00");
      mediaApi.interval = null;
      mediaApi.isPlaying = false;
    }
    console.log('media stop/played/rec success');
  },
  load: function(entrevistaId) {
    if(mediaApi.isPlaying) {
      mediaApi.pausa();
    }
    if(mediaApi.audio) {
      mediaApi.audio.release();
    }
    mediaApi.entrevista = entrevistas.lista[entrevistaId];
    mediaApi.audio = new Media(mediaApi.entrevista.audioPath, mediaApi.onSuccess, mediaApi.onError, mediaApi.onStatus);
    //fake play
    mediaApi.audio.play();
    console.log('audio file loaded');
  },
  onStatus: function(status) {
    switch(status) {
      case Media.MEDIA_NONE: console.log('Status change: idle');
      break;
      case Media.MEDIA_STARTING:
        console.log('Status change: starting');
      break;
      case Media.MEDIA_RUNNING:
        console.log('Status change: running');
        if(!mediaApi.audio.initialized) {
          mediaApi.audio.getCurrentPosition(function(){
            console.log(mediaApi.audio._duration);
            mediaApi.totalTime.text( clockFormat(mediaApi.audio._duration) );
            mediaApi.audio.stop();
            mediaApi.audio.initialized = true;
          });
        }
      break;
      case Media.MEDIA_PAUSED: console.log('Status change: paused');
      break;
      case Media.MEDIA_STOPPED: console.log('Status change: stopped');
      break;
      default: console.log('unknown status');
    }
  },
  onError: function(err) {
    console.log('Error');
    console.log(err);
  }
}

Nombre

Si bien heredamos mediaApi de las practicas anteriores ahora su verdadera funcion es administrar las revisiones de entrevistas, asi que en primera medida vamos a buscar y reemplazar todas las ocurrencias de mediaApi por revisionApi.

En segunda instancia vamos a agregar funcionalidad que vamos a necesitar:

var revisionApi = {
  playTime: 0,
  mediaStartDate: 0,
  mediaDuration: 0,
  entrevista: null,
  colorTagPasado: '#84EC61',
  colorTagPendiente: '#f6f6f6',
  isPlaying: false,
  interval: null,
  currentTime: $('#currentTime').text("00:00"),
  reset: function(){
    if(revisionApi.isPlaying) {
      revisionApi.pausa();
    }
    if(revisionApi.audio) {
      revisionApi.audio.release();
    }
    revisionApi.mediaStartDate = 0;
    revisionApi.mediaDuration = 0;
    revisionApi.interval = null;
    revisionApi.audio = null;
    revisionApi.playTime = 0;
    revisionApi.entrevista = null;
    revisionApi.isPlaying = false;
    revisionApi.currentTime.text("00:00");
  },
  initialize: function() {},
  pausa: function() {
    if(!revisionApi.isPlaying) {
      return;
    }
    clearInterval(revisionApi.interval);
    revisionApi.interval = null;
    revisionApi.isPlaying = false;
    revisionApi.audio.pause();
  },
  onUpdate: function() {
    revisionApi.audio.getCurrentPosition(function(t){
      revisionApi.playTime = t;
      revisionApi.currentTime.text( clockFormat(t) );
      console.log(t);
      $('button.tag').each(function(i,e){
        if($(e).data('miliseconds') < t * 1000) {
          $(e).css('background-color',revisionApi.colorTagPasado);
        }
      });
    });
  },
  seek: function(ms) {
    $('button.tag').each(function(i,e){
      if(ms > $(e).data('miliseconds')) {
        $(e).css('background-color',revisionApi.colorTagPasado);
      }else{
        $(e).css('background-color',revisionApi.colorTagPendiente);
      }
    });
    revisionApi.audio.seekTo(ms);
  },
  play: function() {
    if(revisionApi.isPlaying || !revisionApi.audio) {
      return;
    }
    revisionApi.interval = setInterval(revisionApi.onUpdate,500);
    revisionApi.isPlaying = true;
    revisionApi.audio.play();
  },
  createTagButton: function(ref) {
    var button = $('<button />')
      .addClass("ui-btn ui-btn-inline ui-mini")
      .text('+')
      .click(function(e){
        var d = new Date(revisionApi.entrevista.start);
        d.setSeconds(d.getSeconds() + revisionApi.playTime);
        revisionApi.entrevista.tags.push({ref: ref, time: d});
        $(this).parent().append(revisionApi.createSeekButton(d));
      });
    return button;
  },
  onSuccess: function(){
    if(revisionApi.interval) {
      clearInterval(revisionApi.interval);
      revisionApi.currentTime.text("00:00");
      revisionApi.interval = null;
      revisionApi.isPlaying = false;
    }
    console.log('media stop/played/rec success');
  },
  load: function(entrevistaId) {
    revisionApi.reset();
    revisionApi.entrevista = entrevistas.lista[entrevistaId];
    revisionApi.mediaStartDate = new Date(revisionApi.entrevista.start);
    revisionApi.audio = new Media(revisionApi.entrevista.audioPath, revisionApi.onSuccess, revisionApi.onError, revisionApi.onStatus);
    //fake play
    revisionApi.audio.play();
    console.log('audio file loaded');
  },
  createSeekButton: function(time) {
    var milisecondsFromStart = new Date(time) - revisionApi.mediaStartDate;
    var button = $('<button />')
      .addClass("ui-btn ui-btn-inline ui-mini")
      .addClass('tag')
      .data('miliseconds', milisecondsFromStart)
      .text('Go!')
      .click(function(e){
        console.log('reproducir desde '+time);
        revisionApi.seek(milisecondsFromStart);
      });
    return button;
  },
  onStatus: function(status) {
    switch(status) {
      case Media.MEDIA_NONE: console.log('Status change: idle');
      break;
      case Media.MEDIA_STARTING:
        console.log('Status change: starting');
      break;
      case Media.MEDIA_RUNNING:
        console.log('Status change: running');
        if(!revisionApi.audio.initialized) {
          revisionApi.audio.getCurrentPosition(function(){
            console.log(revisionApi.audio._duration);
            revisionApi.mediaDuration = revisionApi.audio._duration;
            revisionApi.audio.stop();
            revisionApi.audio.initialized = true;
          });
        }
      break;
      case Media.MEDIA_PAUSED: console.log('Status change: paused');
      break;
      case Media.MEDIA_STOPPED: console.log('Status change: stopped');
      break;
      default: console.log('unknown status');
    }
  },
  onError: function(err) {
    console.log('Error');
    console.log(err);
  }
}

Cuales fueron los cambios

Propiedades

Agregamos una serie de propiedades al objeto con un valor inicial:

  playTime: 0,
  mediaStartDate: 0,
  mediaDuration: 0,
  entrevista: null,
  colorTagPasado: '#84EC61',
  colorTagPendiente: '#f6f6f6',
  isPlaying: false,
  interval: null,
  currentTime: $('#currentTime').text("00:00"),
  • playTime - {int}: la posicion de reproduccion en segundos. Mantendremos este valor actualizado gracias al interval que estara corriendo cada vez que reproduzcamos una entrevista
  • mediaStartDate - {int}: la fecha de inicio de grabacion de la entrevista en revision. Se expresa en milisegundos a traves de new Date(entrevista.start). Como las referencias (tags) de nuestras entrevistas estan registradas en fechas mantendremos una referencia en esta propiedad para accederla cuando necesitemos
  • mediaDuration - {float}: valor decimal en segundos de la duracion de la entrevista en revision
  • entrevista - {Object}: una referencia al objeto de la entrevista
  • colorTagPasado - {String}: un color que asignaremos a los botones de los tags que ya pasaron (segun el playTime). El color esta expresado en hexadecimal con un # delante.
  • colorTagPendiente - {String}: idem colorTagPasado. Este color es el que tienen los botones de jQueryMobile por defecto. Necesitamos esta referencia para cuando nos movamos a distintas posiciones de la entrevista y debamos volver el estado de un boton
  • isPlaying - {Boolean}: estado de reproduccion
  • interval - {int}: referencia al interval que usamos para mantener actualizada la posicion de reproduccion. Necesario para poder limpiarlo
  • currentTime - {jQuery Object}: una referencia a donde estaremos mostrando la posicion de reproduccion. En este caso tomamos la referencia y al mismo tiempo establecemos el valor inicial en "00:00"

Metodos

reset()

La funcion initialize ahora esta vacia. Realmente no la necesitamos en este punto. Agregamos una funcion reset para limpiar el estado del objeto cuando necesitemos.

  reset: function(){
    if(revisionApi.isPlaying) {
      revisionApi.pausa();
    }
    if(revisionApi.audio) {
      revisionApi.audio.release();
    }
    revisionApi.mediaStartDate = 0;
    revisionApi.mediaDuration = 0;
    revisionApi.playTime = 0;
    revisionApi.interval = null;
    revisionApi.audio = null;
    revisionApi.playTime = 0;
    revisionApi.entrevista = null;
    revisionApi.isPlaying = false;
    revisionApi.currentTime.text("00:00");
  },

La funcion reset se encarga, obviamente, de volver todos propiedades que puedan haber variado a sus valores iniciales.

onUpdate()

Anteriormente generabamos una funcion para correr con setInterval en el mismo momento que empezabamos a reproducir el archivo de audio. Ahora tenemos esta funcion ya declarada en onUpdate() de manera tal que cuando invocamos el metodo play() solo tenemos que hacer referencia a esta funcion: setInterval(revisionApi.onUpdate, 500);. Ademas de actualizar el valor del reloj tambien corroboramos todos los tags y a aquellos cuyo valor de data('miliseconds') sea menor a la posicion donde estamos les cambiaremos el color de fondo por colorTagPasado:

  onUpdate: function() {
    revisionApi.audio.getCurrentPosition(function(t){
      revisionApi.playTime = t;
      revisionApi.currentTime.text( clockFormat(t) );
      //recordar que t esta expresado en SEGUNDOS!!!
      $('button.tag').each(function(i,e){
        if($(e).data('miliseconds') < t * 1000) {
          $(e).css('background-color', revisionApi.colorTagPasado);
        }
      });
    });
  },

seek(ms)

Como tenemos que actualizar la interfaz (cambiando el color de los botones) cuando cambiemos la posicion de reproduccion no podemos usar la funcion seekTo() directamente. En lugar de eso usamos esta funcion y, antes de cambiar la posicion, actualizamos los tags segun si la posicion a donde estamos cambiando implica que ya se reprodujeron esos tags o no:

  seek: function(ms) {
    $('button.tag').each(function(i,e){
      if(ms > $(e).data('miliseconds')) {
        $(e).css('background-color',revisionApi.colorTagPasado);
      }else{
        $(e).css('background-color',revisionApi.colorTagPendiente);
      }
    });
    revisionApi.audio.seekTo(ms);
  },

play()

El unico cambio visible en el metodo play() es que ahora usamos la funcion revisionApi.onUpdate como parametro de setInterval

  play: function() {
    if(revisionApi.isPlaying || !revisionApi.audio) {
      return;
    }
    revisionApi.interval = setInterval(revisionApi.onUpdate,500);
    revisionApi.isPlaying = true;
    revisionApi.audio.play();
  },

createTagButton()

Crea y devuelve un boton para agregar tags. Esta funcion se llama cuando navegamos a la vista de revision en el event handler $('#revision').on('pageshow', function...
Para generar la referencia instanciamos la fecha de inicio de la grabacion (new Date(revisionApi.entrevista.start)) y le sumamos los segundos que ya reprodujimos (revisionApi.playTime). Luego llamamos a revisionApi.createSeekButton() para generar el boton del tag:

  createTagButton: function(ref) {
    var button = $('<button />')
      .addClass("ui-btn ui-btn-inline ui-mini")
      .text('+')
      .click(function(e){
        var d = new Date(revisionApi.entrevista.start);
        d.setSeconds(d.getSeconds() + revisionApi.playTime);
        revisionApi.entrevista.tags.push({ref: ref, time: d});
        $(this).parent().append(revisionApi.createSeekButton(d));
      });
    return button;
  },

load()

Minimos cambios para suplir las cosas que haciamos en initialize():

  load: function(entrevistaId) {
    revisionApi.reset();
    revisionApi.entrevista = entrevistas.lista[entrevistaId];
    revisionApi.mediaStartDate = new Date(revisionApi.entrevista.start);
    revisionApi.audio = new Media(revisionApi.entrevista.audioPath, revisionApi.onSuccess, revisionApi.onError, revisionApi.onStatus);
    //fake play
    revisionApi.audio.play();
    console.log('audio file loaded');
  },

createSeekButton()

Crea y devuelve un boton para cambiar la posicion de reproduccion. Son los botones de tags que agregamos con la funcionalidad de createTagButton()

  createSeekButton: function(time) {
    var milisecondsFromStart = new Date(time) - revisionApi.mediaStartDate;
    var button = $('<button />')
      .addClass("ui-btn ui-btn-inline ui-mini")
      .addClass('tag')
      .data('miliseconds', milisecondsFromStart)
      .text('Go!')
      .click(function(e){
        console.log('reproducir desde '+time);
        revisionApi.seek(milisecondsFromStart);
      });
    return button;
  },

onStatus

En este metodo lo unico que esta cambiado es que ya no tenemos un lugar donde mostrar la duracion total del archivo de audio, entonces solo lo guardamos en la propiedad revisionApi.mediaDuration

      case Media.MEDIA_RUNNING:
        console.log('Status change: running');
        if(!revisionApi.audio.initialized) {
          revisionApi.audio.getCurrentPosition(function(){
            console.log(revisionApi.audio._duration);
            revisionApi.mediaDuration = revisionApi.audio._duration;
            revisionApi.audio.stop();
            revisionApi.audio.initialized = true;
          });
        }
      break;

Y este fue el primero nomas, ahora vamos a pasar a los cambios (no hay tanto refactor) de recordApi

⚠️ **GitHub.com Fallback** ⚠️