viernes, 2 de diciembre de 2011

Listar videos de youtube en Android

Antes que nada , déjenme decirles que es la primera vez que realizo una entrada en un blog , asi que disculpen la calidad del contenido , recién me estoy informando de como usar blogger XD , ahora pasemos a lo importante : el código....
Aqui doy por hecho que ya sabes configurar el entorno de trabajo de android , eclipse y que ya has podido levantar tu "HolaMundo" , ejemplos de esos hay bastantes, aqui le di prioridad al  uso de :
  • Layouts en android
  • Personalizar una lista
  • Pasar de una interfaz a otra
  • Listar contenido remoto , en este caso videos de youtube
Espero les sea de ayuda , así que vamos a empezar.

Entorno de desarrollo:
  • Sistema Operativo : Windows 7 32 bits
  • Jdk : 1.6
  • Eclipse : Spring Tool Suite 2.7.1.
  • Plataforma Android : 2.3.3 API 10
Libreriás requeridas
* Google Guice 2.0 : guice-2.0-no_aop.jar
http://code.google.com/p/google-guice/downloads/detail?name=guice-2.0-no_aop.jar&can=2&q=
* Roboguice 1.1.2 : roboguice-1.1.2.jar
http://repo1.maven.org/maven2/org/roboguice/roboguice/1.1.2/roboguice-1.1.2.jar

Para esta aplicación he usado Roboguice , que es similar a la inyección de dependencias de spring , me parece sencillo y útil , y me permite mantener el mismo tipo de estructura al igual que los proyectos de spring (daos , services , vistas , etc).

Aqui una vista de la estructura del proyecto :

Explicaré las clases del paquete "config"

1.- CanalYoutubeGoogleGuiceModule

package com.midominio.canalyoutube.config;

import roboguice.config.AbstractAndroidModule;

import com.midominio.canalyoutube.dao.VideoDao;
import com.midominio.canalyoutube.dao.impl.VideoDaoImpl;
import com.midominio.canalyoutube.service.VideoService;
import com.midominio.canalyoutube.service.impl.VideoServiceImpl;

public class CanalYoutubeGoogleGuiceModule extends AbstractAndroidModule {

@Override
protected void configure() {
//Iniciando Daos
bind(VideoDao.class).to(VideoDaoImpl.class);
//Iniciando Servicios
bind(VideoService.class).to(VideoServiceImpl.class);
}


Esta clase es el núcleo de google guice , simplemente heredamos de AbstractAndroidModule y colocamos una instancia de la clase que queremos quede como si fuera un bean de spring , con la sentencia bind
decimos que se setee un VideoDaoImpl.class cada vez que coloque como atributo una variable de tipo VideoDao , lo mismo para los servicios.

2.- CanalYoutubeRoboGuiceApplication 


package com.midominio.canalyoutube.config;

import java.util.List;

import roboguice.application.RoboApplication;

import com.google.inject.Module;

public class CanalYoutubeRoboGuiceApplication extends RoboApplication{

private Module module = new CanalYoutubeGoogleGuiceModule();
@Override
protected void addApplicationModules(List<Module> modules) {
modules.add(module);
}

public void setModule(Module module) {
this.module = module;
}
}


Esta clase de RoboGuice es la instancia principal , aqui seteamos el módulo creado anteriormente

3.- Constantes

package com.midominio.canalyoutube.config;

public interface Constantes {

public static String USER_ID = "whatdafaqshow";
public static String FEED_URL ="http://gdata.youtube.com/feeds/api/users/";
public static String TIPO_VIDEO_SUBIDAS_TOTALES= "uploads";
public static int MAX_VIDEOS_POR_PAGINA = 15;

}


Aqui definimos las variables importantes , como el canal de donde vamos a buscar ( uno de los que me gusta Whatdafaqshow ), la url de donde sacar los datos , el tipo de consulta , en este caso , los videos subidos al canal , y la cantidad de videos por página (de momento solo puse 15 ya que las consultas a la api de google traen de 25 en 25 como máximo)

Clases del paquete DAO : 

4.- Video


package com.midominio.canalyoutube.dao.domain;

import java.util.List;

public class Video {

String titulo;
String ruta;
List<Miniatura> miniaturas;
public String getTitulo() {
return titulo;
}
public void setTitulo(String titulo) {
this.titulo = titulo;
}
public String getRuta() {
return ruta;
}
public void setRuta(String ruta) {
this.ruta = ruta;
}
public List<Miniatura> getMiniaturas() {
return miniaturas;
}
public void setMiniaturas(List<Miniatura> miniaturas) {
this.miniaturas = miniaturas;
}
}

La consulta a la api de google es un xml el cual tendremos que parsear y colocarlo en clases para su fácil manejo , en este caso la clase video , contendrá el titulo , la ruta del video y las miniaturas(imágenes) del video.

5.- Miniatura


package com.midominio.canalyoutube.dao.domain;

public class Miniatura {

String ruta;
String width;
String height;
String time;
public String getRuta() {
return ruta;
}
public void setRuta(String ruta) {
this.ruta = ruta;
}
public String getWidth() {
return width;
}
public void setWidth(String width) {
this.width = width;
}
public String getHeight() {
return height;
}
public void setHeight(String height) {
this.height = height;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}

}

Las miniaturas contienen datos como alto , ancho , tiempo en que aparecen y la ruta donde se encuentran en internet.

6.- VideoDao



package com.midominio.canalyoutube.dao;

import java.util.List;

import com.midominio.canalyoutube.dao.domain.Video;

public interface VideoDao {
public List<Video> obtenerVideos();
public List<Video> obtenerVideos(int start_index,int max_results);
}


La interfaz plantilla de los métodos para recuperar los videos , en este caso 2 , donde se traen los videos con los parámetros por defecto , y otro donde se traen los videos con una paginación personalizada

7.- VideoDaoImpl

package com.midominio.canalyoutube.dao.impl;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.midominio.canalyoutube.config.Constantes;
import com.midominio.canalyoutube.dao.VideoDao;
import com.midominio.canalyoutube.dao.domain.Miniatura;
import com.midominio.canalyoutube.dao.domain.Video;
import com.midominio.canalyoutube.util.LoggerUtil;

public class VideoDaoImpl implements VideoDao {
@Override
public List<Video> obtenerVideos(){
int start_index = 1;
int max_results = Constantes.MAX_VIDEOS_POR_PAGINA;
return obtenerVideos(start_index,max_results);
}
@Override
public List<Video> obtenerVideos(int start_index,int max_results)  {
List<Video> lista = null;
String ruta = Constantes.FEED_URL + Constantes.USER_ID + "/" + Constantes.TIPO_VIDEO_SUBIDAS_TOTALES + "?start-index="+start_index+"&amp;max-results="+max_results;
URL url = null;
URLConnection connection = null;
HttpURLConnection httpConnection = null;
try {
lista = new ArrayList<Video>();
url = new URL(ruta);
connection = url.openConnection();
httpConnection = (HttpURLConnection)connection; 
int responseCode = httpConnection.getResponseCode(); 
if (responseCode == HttpURLConnection.HTTP_OK) { 
 InputStream in = httpConnection.getInputStream(); 

                 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                 DocumentBuilder db = dbf.newDocumentBuilder();

                 // Parse the earthquake feed.
                 Document dom = db.parse(in);      
                 Element docEle = dom.getDocumentElement();
                 
                 NodeList nl = docEle.getElementsByTagName("entry");
                 
                 if (nl != null && nl.getLength() > 0) {
                     for (int i = 0 ; i < nl.getLength(); i++) {
                      Video x = new Video();
                      Element entry = (Element)nl.item(i);
                        
                      //Seteando el titulo
                      Element title = (Element)entry.getElementsByTagName("title").item(0);
                         x.setTitulo(title.getFirstChild().getNodeValue());
                         
                         //Seteando las miniaturas
                         Element mediaGroup = (Element)entry.getElementsByTagName("media:group").item(0);
                         NodeList thumbnails = mediaGroup.getElementsByTagName("media:thumbnail");
                         List<Miniatura> listaMiniaturas = new ArrayList<Miniatura>();
                         for(int j = 0; j<thumbnails.getLength();j++){
                          Element e = (Element)thumbnails.item(j);
                          Miniatura m = new Miniatura();
                          m.setRuta(e.getAttribute("url"));
                          m.setWidth(e.getAttribute("width"));
                          m.setHeight(e.getAttribute("height"));
                          m.setTime(e.getAttribute("time"));
                          listaMiniaturas.add(m);
                         }
                         x.setMiniaturas(listaMiniaturas);
                         
                         //Seteando la url del video
                         NodeList datosVideo = mediaGroup.getElementsByTagName("media:content");
                      Element e = (Element)datosVideo.item(datosVideo.getLength()-1);
                      x.setRuta(e.getAttribute("url"));
                         
                         lista.add(x);
                     }
                 }
}


} catch (MalformedURLException e) {
LoggerUtil.v(VideoDaoImpl.class, e.getMessage());
lista = null;
} catch (IOException e) {
LoggerUtil.v(VideoDaoImpl.class, e.getMessage());
lista = null;
} catch (ParserConfigurationException e) {
LoggerUtil.v(VideoDaoImpl.class, e.getMessage());
lista = null;
} catch (SAXException e) {
LoggerUtil.v(VideoDaoImpl.class, e.getMessage());
lista = null;
}
return lista;
}

}

Aqui lo que hacemos es parsear el xml recibido y tomar los datos de los videos , para entender como es el xml de una llamada a youtube , haremos la prueba accediendo a ésta url 

http://gdata.youtube.com/feeds/api/users/whatdafaqshow/uploads?start-index=1&max-results=15

Se puede colocar tambien http://gdata.youtube.com/feeds/api/users/whatdafaqshow/uploads , pero usará los parametros por defecto , desde el primer video y máximo de resultados , 25

En mozilla colocamos ver codigo fuente y dicho codigo lo guardamos como xml , el cual contiene

<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' xmlns:app='http://purl.org/atom/app#' 
xmlns:media='http://search.yahoo.com/mrss/' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' 
xmlns:gd='http://schemas.google.com/g/2005' xmlns:yt='http://gdata.youtube.com/schemas/2007'>
<id>http://gdata.youtube.com/feeds/api/users/whatdafaqshow/uploads</id>
<updated>2011-11-27T22:30:11.578Z</updated>
<category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/>
<title type='text'>Uploads by whatdafaqshow</title>
<logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo>
<link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/users/whatdafaqshow'/>
<link rel='alternate' type='text/html' href='http://www.youtube.com/profile?user=whatdafaqshow#p/u'/>
<link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' 
href='http://gdata.youtube.com/feeds/api/users/whatdafaqshow/uploads'/>
<link rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml' 
href='http://gdata.youtube.com/feeds/api/users/whatdafaqshow/uploads/batch'/>
<link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/users/whatdafaqshow/uploads?start-index=1&amp;max-results=25'/>
<link rel='next' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/users/whatdafaqshow/uploads?start-index=26&amp;max-results=25'/>
<author>
<name>whatdafaqshow</name>
<uri>http://gdata.youtube.com/feeds/api/users/whatdafaqshow</uri>
</author>
<generator version='2.1' uri='http://gdata.youtube.com'>YouTube data API</generator>
<openSearch:totalResults>108</openSearch:totalResults>
<openSearch:startIndex>1</openSearch:startIndex>
<openSearch:itemsPerPage>25</openSearch:itemsPerPage>
<entry>
<id>http://gdata.youtube.com/feeds/api/videos/aQujLSfJ13A</id>
<published>2011-11-20T22:41:26.000Z</published>
<updated>2011-11-27T22:27:03.000Z</updated>
<category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/>
<category scheme='http://gdata.youtube.com/schemas/2007/categories.cat' term='People' label='Gente y blogs'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='whatdafaqshow'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='what'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='da'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='faq'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='mox'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='wdfshow'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='videos'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='graciosos'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='la'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='historia'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='de'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='internet'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='whatdafaq'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='wdf'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='niños'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='malignos'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='rusos'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='japoneses'/>
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='raros'/>
<title type='text'>Niños Malignos!</title>
<content type='text'>PAGINA WEB : http://www.whatdafaqshow.com

FB Oficial ‪http://www.facebook.com/wdfshow‬

Mox FB: ‪http://www.facebook.com/moxwdf‬

Twitter: ‪http://bit.ly/iytacG‬

Episodio 105 de WDF!?
</content>
<link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=aQujLSfJ13A&amp;feature=youtube_gdata'/>
<link rel='http://gdata.youtube.com/schemas/2007#video.responses' type='application/atom+xml' 
href='http://gdata.youtube.com/feeds/api/videos/aQujLSfJ13A/responses'/>
<link rel='http://gdata.youtube.com/schemas/2007#video.related' type='application/atom+xml' 
href='http://gdata.youtube.com/feeds/api/videos/aQujLSfJ13A/related'/>
<link rel='http://gdata.youtube.com/schemas/2007#mobile' type='text/html' 
href='http://m.youtube.com/details?v=aQujLSfJ13A'/>
<link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/users/whatdafaqshow/uploads/aQujLSfJ13A'/>
<author>
<name>WHATDAFAQSHOW</name>
<uri>http://gdata.youtube.com/feeds/api/users/whatdafaqshow</uri>
</author>
<gd:comments>
<gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/aQujLSfJ13A/comments' countHint='1915'/>
</gd:comments>
<media:group>
<media:category label='Gente y blogs' scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>People</media:category>
<media:content url='http://www.youtube.com/v/aQujLSfJ13A?version=3&amp;f=user_uploads&amp;app=youtube_gdata' type='application/x-shockwave-flash' medium='video' isDefault='true' expression='full' duration='381' yt:format='5'/>
<media:content url='rtsp://v8.cache1.c.youtube.com/CigLENy73wIaHwlw18knLaMLaRMYDSANFEgGUgx1c2VyX3VwbG9hZHMM/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='381' yt:format='1'/>
<media:content url='rtsp://v3.cache6.c.youtube.com/CigLENy73wIaHwlw18knLaMLaRMYESARFEgGUgx1c2VyX3VwbG9hZHMM/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='381' yt:format='6'/>
<media:description type='plain'>PAGINA WEB : http://www.whatdafaqshow.com

FB Oficial ‪http://www.facebook.com/wdfshow‬

Mox FB: ‪http://www.facebook.com/moxwdf‬

Twitter: ‪http://bit.ly/iytacG‬

Episodio 105 de WDF!?
</media:description>
<media:keywords>whatdafaqshow, what, da, faq, mox, wdfshow, videos, graciosos, la, historia, de, internet, whatdafaq, wdf, niños, malignos, rusos, japoneses, raros</media:keywords>
<media:player url='http://www.youtube.com/watch?v=aQujLSfJ13A&amp;feature=youtube_gdata_player'/>
<media:thumbnail url='http://i.ytimg.com/vi/aQujLSfJ13A/0.jpg' height='360' width='480' time='00:03:10.500'/>
<media:thumbnail url='http://i.ytimg.com/vi/aQujLSfJ13A/1.jpg' height='90' width='120' time='00:01:35.250'/>
<media:thumbnail url='http://i.ytimg.com/vi/aQujLSfJ13A/2.jpg' height='90' width='120' time='00:03:10.500'/>
<media:thumbnail url='http://i.ytimg.com/vi/aQujLSfJ13A/3.jpg' height='90' width='120' time='00:04:45.750'/>
<media:title type='plain'>Niños Malignos!</media:title><yt:duration seconds='381'/>
</media:group>
<gd:rating average='4.8676705' max='5' min='1' numRaters='9431' rel='http://schemas.google.com/g/2005#overall'/>
<yt:statistics favoriteCount='712' viewCount='366645'/>
</entry>
             :
             :
</feed>

Aqui lo que nos interesa son los tags <entry> y dentro de estos , el tag "title"  y los grupos "media:thumbnail" y "media:content" , que es donde encontraremos las miniaturas y las ruta del video respectivamente
NOTA : para el tag < media : content  >, tomamos el ultimo tag que es el que contiene la ruta del archivo .3gp

Clases del paquete "SERVICE"

8.- VideoService

package com.midominio.canalyoutube.service;

import java.util.List;

import com.midominio.canalyoutube.view.domain.ItemVideo;

public interface VideoService {

public List<ItemVideo> obtenerVideos();
}



Es la interfaz plantilla para comunicar el DAO , con la capa Vista

9.- VideoServiceImpl


package com.midominio.canalyoutube.service.impl;

import java.util.ArrayList;
import java.util.List;

import com.google.inject.Inject;
import com.midominio.canalyoutube.dao.VideoDao;
import com.midominio.canalyoutube.dao.domain.Video;
import com.midominio.canalyoutube.service.VideoService;
import com.midominio.canalyoutube.view.domain.ItemVideo;

public class VideoServiceImpl implements VideoService {

@Inject
VideoDao videoDao;
@Override
public List<ItemVideo> obtenerVideos() {
List<ItemVideo> rpta = null;
List<Video> lista = videoDao.obtenerVideos();

if(lista!=null){
int dim = lista.size();
if(dim>0){
rpta = new ArrayList<ItemVideo>();
for(int i = 0;i<dim ;i++){
Video x = lista.get(i);
ItemVideo aux = new ItemVideo();
aux.setTitulo(x.getTitulo());
aux.setRuta(x.getRuta());
try{
aux.setMiniatura(x.getMiniaturas().get(1).getRuta());
}catch(NullPointerException npe){
aux.setMiniatura("");
}
rpta.add(aux);
}
}
}
return rpta;
}

}

Aqui , la anotación destacable es @Inject , que sirve para insertar una instancia de la Clase VideoDaoImpl , definida en la configuración.
En esta clase tomamos un objeto video del xml y lo convertimos en un objeto ItemVideo , que es para la capa Vista , en las miniaturas , elegimos en este caso la segunda , porque la primera es demasiado grande y descuadra el diseño.

Clases del paquete "view"

10.- ItemVideo

package com.midominio.canalyoutube.view.domain;

import android.os.Parcel;
import android.os.Parcelable;

import com.midominio.canalyoutube.util.LoggerUtil;

public class ItemVideo implements Parcelable {

private String titulo;
private String ruta;
private String miniatura;
public ItemVideo(){}
public ItemVideo(Parcel source){
        LoggerUtil.v(ItemVideo.class,"Parseando desde la fuente");
        titulo = source.readString();
        ruta = source.readString();
        miniatura = source.readString();
}
public String getTitulo() {
return titulo;
}
public void setTitulo(String titulo) {
this.titulo = titulo;
}
public String getRuta() {
return ruta;
}
public void setRuta(String ruta) {
this.ruta = ruta;
}
public String getMiniatura() {
return miniatura;
}
public void setMiniatura(String miniatura) {
this.miniatura = miniatura;
}
@Override
public String toString() {
return titulo;
}
@Override
public int describeContents() {
return hashCode();
}
@Override
public void writeToParcel(Parcel destino, int flags) {
LoggerUtil.v(ItemVideo.class, "Parseando : "+ flags);
destino.writeString(titulo);
destino.writeString(ruta);
destino.writeString(miniatura);
}
    public static final Parcelable.Creator<ItemVideo> CREATOR = new Parcelable.Creator<ItemVideo>() {

        public ItemVideo createFromParcel(Parcel in){
            return new ItemVideo(in);
        }

        public ItemVideo[] newArray(int size){
            return new ItemVideo[size];
        }

    };
}

Esta clase será usada como tipo de dato de la lista de videos mostrada en el móvil , aqui lo destacable , es la implementación de Parcelable , esta implementación , nos permitirá pasar este tipo de dato entre Activitys  (como las ventanas de una aplicacion de escritorio , o las páginas de una aplicacion web) de Android , para ello se debe crear una variable estática CREATOR , con el método writeToParcel , donde se escribe los atributos de la clase , y el método createFromParcel ,  lee el Parcel y coloca la lectura en los atributos , para ello la clase debe tener un constructor que tenga una clase Parcel de parámetro.
NOTA : El orden de lectura y escritura de atributos en el Parcel , debe ser el mismo 

Ahora vamos a las vistas

11.- Inicio

Código Inicio.java

package com.midominio.canalyoutube.view;

import java.util.List;

import roboguice.activity.RoboActivity;
import roboguice.inject.InjectView;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.AdapterView.OnItemClickListener;

import com.google.inject.Inject;
import com.midominio.canalyoutube.service.VideoService;
import com.midominio.canalyoutube.view.domain.ItemVideo;
import com.midominio.canalyoutube.view.list.ItemVideoListAdapter;

public class Inicio extends RoboActivity {

@Inject
VideoService videoService;
@InjectView(R.id.listaEpisodios)
        ListView listaEpisodios;
ItemVideoListAdapter adaptador;
List<ItemVideo> listaVideos;
    
@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.inicio);
        listaVideos = videoService.obtenerVideos();
     adaptador = new ItemVideoListAdapter(this,R.layout.inicio_lista_itemvideo,listaVideos);
     listaEpisodios.setAdapter(adaptador);
    
     listaEpisodios.setOnItemClickListener(new OnItemClickListener() {
     public void onItemClick(AdapterView<?> parent,View view,int position, long id) {
         
     //Tomando el objeto
     ItemVideo v = (ItemVideo)listaEpisodios.getItemAtPosition(position);
    
          //Colocando el objeto en la vista
          Intent i = new Intent(view.getContext(),RepVideo.class);
          i.putExtra("itemVideo",v);
                startActivityForResult(i, 0);
         }
     });
 }

Código : inicio.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical"
    android:layout_width="fill_parent" android:layout_height="fill_parent">

<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" 
 android:text="@string/lista_episodios" style="@style/tituloActivity" />
    <ListView android:id="@+id/listaEpisodios" android:layout_height="wrap_content" android:layout_width="match_parent" />
    
</LinearLayout>



Aquí lo destacable son las anotaciones @Inject y @InjectView , que insertan un objeto y un componente de vista respectivamente , en @InjectView , se debe tomar el recurso de una plantilla determinada , en este caso de la plantilla inicio.xml.
La clase ItemVideoListAdapter , es una extension de ArrayAdapter , para que cada item de la lista se muestre de la forma <Foto><Texto> ya que el ListView por defecto de android , solo muestra texto .
También se ha colocado un evento a cada item de la lista , para que cuando se le clickee vaya a otra Activity llevando los datos del video, usando Intent y su método putExtra

NOTA : 
  • No se debe heredar de Activity sino de RoboActivity , de lo contrario los @Inject no funcionarán.
  • Al tomar por id , en el xml es  android:id="@+id/listaEpisodios" y en el java es @InjectView(R.id.listaEpisodios)
    ListView listaEpisodios;
    Esto reemplaza a este tipo de sentencias 
    TextView tt = (TextView) v.findViewById(R.id.texto);

11.- ItemVideoListAdapter

Código ItemVideoListAdapter.java

package com.midominio.canalyoutube.view.list;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.midominio.canalyoutube.util.LoggerUtil;
import com.midominio.canalyoutube.view.R;
import com.midominio.canalyoutube.view.domain.ItemVideo;

public class ItemVideoListAdapter extends ArrayAdapter<ItemVideo> {
Context context;
private List<ItemVideo> items;
 
public ItemVideoListAdapter(Context context, int textViewResourceId, List<ItemVideo> items) {
super(context, textViewResourceId, items);
this.items = items;
this.context = context;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.inicio_lista_itemvideo, null);
}
ItemVideo item = items.get(position);
if (item != null) {
 
//poblamos la lista de elementos
TextView tt = (TextView) v.findViewById(R.id.texto);
ImageView im = (ImageView) v.findViewById(R.id.icono);
 
//Seteando la imagen
if (im!= null) {
try {
URL url = new URL(item.getMiniatura());
URLConnection conn = url.openConnection();
conn.connect();
InputStream is = conn.getInputStream();
           BufferedInputStream bis = new BufferedInputStream(is);
           Bitmap bm = BitmapFactory.decodeStream(bis);
           bis.close();
               is.close();
               im.setImageBitmap(bm);
} catch (MalformedURLException e) {
LoggerUtil.v(ItemVideoListAdapter.class,e.getMessage());
im.setImageResource(R.drawable.icon);
} catch (IOException e) {
LoggerUtil.v(ItemVideoListAdapter.class,e.getMessage());
im.setImageResource(R.drawable.icon);
}
}
if (tt != null) {
tt.setText(item.getTitulo());
}
}
return v;
}
}

Código : inicio_lista_itemvideo.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight" android:padding="6dip">
<ImageView android:id="@+id/icono" android:layout_width="wrap_content" android:layout_height="fill_parent"
  android:layout_alignParentTop="true" android:layout_alignParentBottom="true" android:layout_marginRight="6dip "/>
<TextView android:id="@+id/texto" android:layout_width="fill_parent" android:layout_height="fill_parent"
 android:layout_toRightOf="@id/icono" android:layout_alignParentRight="true" android:layout_centerVertical="true"
 android:singleLine="true" android:ellipsize="marquee" />

</RelativeLayout>

Aquí en el método getView() asignaremos la imagen y el título , cabe destacar que al ser una imagen remota , la deberemos cargar como mapa de bits.
Como son items personalizados , tambien debe tener un xml para la estructura de la interfaz.
NOTAS : como estamos sobreescribiendo la forma de mostrar la lista , tambien necesita un archivo de layout

11.- RepVideo

Código : RepVideo.java

package com.midominio.canalyoutube.view;

import roboguice.activity.RoboActivity;
import roboguice.inject.InjectView;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.MediaController;
import android.widget.TextView;
import android.widget.VideoView;

import com.midominio.canalyoutube.view.domain.ItemVideo;

public class RepVideo extends RoboActivity{

@InjectView(R.id.video)
        VideoView videoView;
@InjectView(R.id.txt_titulo)
TextView txtTitulo;
@InjectView(R.id.txt_ruta)
TextView txtRuta;
@InjectView(R.id.btnInicio)
Button play;
@InjectView(R.id.btnPausa)
Button pause;
@InjectView(R.id.btnRegresar)
Button regresar;
int flagPausaInicio;
ItemVideo v;
@Override
    public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.repvideo);
flagPausaInicio = 0; //0 es que no se ha reproducido
    Intent i = getIntent();
        v = (ItemVideo) i.getParcelableExtra("itemVideo");          
        txtTitulo.setText(v.getTitulo());
        txtRuta.setText(v.getRuta());
         
        MediaController mc = new MediaController(this.getBaseContext());
        mc.setAnchorView(videoView);
        
        videoView.setMediaController(mc);
        videoView.requestFocus();
        
        play.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
               if(flagPausaInicio==0){
                Uri video = Uri.parse(v.getRuta());
                videoView.setVideoURI(video);
                videoView.start();
                flagPausaInicio = 1;
               }else{
                videoView.resume(); 
               }
            }
        });
        
        pause.setOnClickListener(new View.OnClickListener() {
           public void onClick(View view) {
           videoView.stopPlayback(); 
       }
    });
        
        regresar.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                Intent intent = new Intent();
                setResult(RESULT_OK, intent);
                finish();
            }
        });
}
}

Código : repvideo.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Button android:text="@string/btnRegresar"
        android:id="@+id/btnRegresar"
        android:layout_width="fill_parent" android:textSize="16px" android:layout_height="40px" />

    <TextView android:layout_width="fill_parent" android:id="@+id/txt_titulo"
        android:layout_height="wrap_content" style="@style/tituloActivity" />
    <TextView android:layout_width="fill_parent" android:id="@+id/txt_ruta"
        android:layout_height="wrap_content" />
     
    <RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content"
        android:id="@+id/frame">
        <VideoView android:id="@+id/video" android:layout_width="226px"
            android:layout_height="194px" android:keepScreenOn="true" />
    </RelativeLayout>
    
    <Button android:text="@string/btnInicio" android:id="@+id/btnInicio"
        android:layout_width="fill_parent" android:textSize="16px" android:layout_height="40px" />
    <Button android:text="@string/btnPausa" android:id="@+id/btnPausa"
        android:layout_width="fill_parent" android:textSize="16px" android:layout_height="40px" />
</LinearLayout>

Al pulsar cualquier item de la lista , nos llevará a esta interfaz donde se reproducirá el video seleccionado , con la clase Intent , obtendremos el objeto enviado desde la interfaz principal , tambien cuenta con un botón para pausar y reproducir el video (aquí hacemos uso de un flag , en caso de que no se haya cargado el video , usa el método start() , si se continua de una pausa , usa el método resume), y un botón para regresar al listado

NOTAS: 
  • El video a reproducir debe ser la ruta del archivo .3gp
  • Este Activity solo funciona en un móvil real , no en el emulador
12.- LoggerUtil

package com.midominio.canalyoutube.util;

import android.util.Log;

public class LoggerUtil {

public static void v(String tag,String mensaje){
Log.v(tag,mensaje);
}
public static void v(Class<?> clase,String mensaje){
Log.v(clase.getSimpleName(),mensaje);
}
}


Esta clase permite insertar en el log usando como tag el nombre de la clase , o un nombre personalizado












NOTA : para ver el log se debe activar la perspectiva DDMS en Eclipse


12.- AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.midominio.canalyoutube.view"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="10" />


    <application android:name="com.midominio.canalyoutube.config.CanalYoutubeRoboGuiceApplication"
    android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".Inicio" android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".RepVideo" android:label="@string/app_name"></activity>
    </application>
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>
</manifest>

Habrá que hacer algunos cambios a este fichero de configuración , como agregar la segunda "Activity" , el parámetro android:name, esto debido a que estamos usando Roboguice , y es parte de la configuración que nos pide.
También debemos agregar el tag de uses-permission  , para poder acceder a Internet.


13.- Styles.xml y Strings.xml


Al igual que css , en styles.xml , podemos definir estilos para varios componentes de android , en este caso para nuestra etiqueta de texto.


<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="tituloActivity">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:typeface">monospace</item>
<item name="android:background">#79B900</item>
<item name="android:textColor">#FFF</item>
<item name="android:textSize">16px</item>
<item name="android:textStyle">bold</item>
</style>
</resources>

En el caso de strings.xml , nos sirve para definir textos para la aplicación

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Canal Youtube</string>
    <string name="lista_episodios">Lista de Episodios</string>
    <string name="btnRegresar">Regresar</string>
    <string name="btnInicio">Play</string>
    <string name="btnPausa">Pause</string>
</resources>

Interfaz Final -
Si todo nos compiló correctamente , la aplicación debería verse de la siguiente forma

Inicio.java





RepVideo.java


NOTAS FINALES :
  • De momento no cuento con un smartphone , sino de hecho probaba el video youtube , disculpen por eso.
  • Varios de éstos códigos , están en otros tutoriales de Internet , solo quise agruparlos en uno ya que no encontré un tutorial parecido
  • Si tienen correcciones y/o mejoras del código , bienvenido sea.
Bueno , espero les haya sido de utilidad.

PD : si pensaron que no les daba el código , se equivocaron , aqui el enlace

http://www.mediafire.com/?hyu91hw6r7ssfpb

Avisen si falla el enlace

Saludos a todos

3 comentarios:

  1. DOC EL CONDIGO NO FUNCIONA EN EL EMULADOR NECESARIAMENTRE TIENE K KORRER EN EL DISPOSITIVO???

    ResponderEliminar
  2. Guau! Es demasiado bueno para ver como gran colección sobre RoboGuice. Necesito másRoboGuice Tutorials

    ResponderEliminar
  3. No funciona la aplicacion

    ResponderEliminar