iRec mediaApi refactor - CGastrell/phonegap GitHub Wiki
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);
}
}
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);
}
}
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 denew 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 elplayTime
). El color esta expresado en hexadecimal con un#
delante. -
colorTagPendiente
- {String}: idemcolorTagPasado
. 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"
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.
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);
}
});
});
},
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);
},
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();
},
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;
},
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');
},
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;
},
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