PDM Avanzado Reprodución de Audio
Sumario
Introdución
No caso da reprodución do audio, teremos que facer uso das clases MediaPlayer e AudioManager.
Clase utilizadas: MediaPlayer: esta clase é a principal utilizada para reproducir audio – vídeo. AudioManager: manexa fontes de audio e gravacións de audio en dispositivos. Nesta parte só imos utilizala para indicar o tipo de audio.
Permisos necesarios a engadir no arquivo AndroidManifest.xml (nos imos engadir todos):
- Se o dispositivo necesita conexión a internet (para escoitar música en streaming, por exemplo) temos que engadir o permiso:
1 <uses-permission android:name="android.permission.INTERNET" />
- Permiso Wake-Lock: no momento no que o S.O. non vexa ‘movemento’ no dispositivo (ou sexa uso) pasará a un estado de modo suspendido. Se queremos que a nosa activity non entre en dito estado podemos facer uso do método MediaPlayer.setWakeMode() e polo tanto necesitaremos o permiso wake_lock:
1 <uses-permission android:name="android.permission.WAKE_LOCK" />
- Permiso de escritura na SD Card: Para ter acceso á memoria externa teremos que especificar no arquivo AndroidManifest.xml que a nosa aplicación necesita permiso de lectura/escritura en dicha memoria.
- Se imos ler na tarxeta SD:
- Se a versión do S.O. Android é inferior á 4.1 non precisamos ningún permiso.
- Se a versión do S.O. Android é superior ou igual á 4.1 debemos engadir o permiso: <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
- Se imos escribir na tarxeta SD:
- Se a versión do S.O. Android é inferior á 4.4 o permiso é: <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> .
- Se a versión do S.O. Android é a 4.4 ou superior. Podemos poñer o mesmo permiso anterior pero as aplicacións dispoñen dun cartafol para escribir na SD (cartafol Android/data/paquete/) sen necesidade de ter o permiso anterior.
Os permisos necesarios son postos no ficheiro AndroidManifest.xml da aplicación.
1 <?xml version="1.0" encoding="utf-8"?>
2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.angel.olamundo" >
3
4 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
5
6 <application
7 android:allowBackup="true"
8 android:icon="@mipmap/ic_launcher"
9 android:label="@string/app_name"
10 android:supportsRtl="true"
11 android:theme="@style/AppTheme" >
12 <activity android:name=".MainActivity" >
13 <intent-filter>
14 <action android:name="android.intent.action.MAIN" />
15
16 <category android:name="android.intent.category.LAUNCHER" />
17 </intent-filter>
18 </activity>
19 </application>
20
21 </manifest>
- Importante: Débense poñer antes da etiqueta <application>.
Nota: se queremos ter unha música de fondo na nosa aplicación o máis lóxico sería ter un servizo.
- Nota importante: A maiores, se imos instalar a nosa aplicación sobre un dispositivo cunha API>=23 é necesario pedir o permiso de acceso á tarxeta SD por programación como está indicado neste punto da Wiki.
MediaPlayer
Os recursos (arquivos de audio) que podemos utilizar con esta clase poden ser:
- Locais: arquivos locais gardados no cartafol /res/raw/ (deberemos crear o cartafol /raw/ se non existe).
- URIs internas: unha URI é unha forma de identificar de forma unívoca un recurso (neste caso multimedia). Podemos referenciar arquivos multimedia gardados nas diferentes tarxetas de memoria do dispositivo.
- URL Externos (streaming): arquivos multimedia que se atopan en Internet.
Podedes consultar a lista de formatos de audio – vídeo soportados en http://developer.android.com/guide/appendix/media-formats.html
Imos facer unha pequena práctica na que imos reproducir un arquivo multimedia.
Creamos no proxecto de Android - Multimedia un cartafol de nome 'raw' dentro do cartafol /res/ do proxecto:
Reproducir música
Para cargar un arquivo de audio temos que seguir os seguintes pasos:
- Crear un obxecto da clase MediaPlayer.
1 private MediaPlayer mediaplayer;
2 .............
3 mediaplayer = new MediaPlayer();
- Chamar ao método setDataSource que queiramos (pode dar lugar a varios tipos de excepcións, polo que teremos que usar try catch para cada tipo delas ou ben capturalos cunha clase Exception aínda que esta forma non é a recomendada).
- No exemplo usamos Exception para simplificar o código xa que se non teríamos que ter isto:
1 try {
2 mediaplayer.setDataSource(path);
3 } catch (IllegalArgumentException e) {
4 // TODO Auto-generated catch block
5 e.printStackTrace();
6 } catch (SecurityException e) {
7 // TODO Auto-generated catch block
8 e.printStackTrace();
9 } catch (IllegalStateException e) {
10 // TODO Auto-generated catch block
11 e.printStackTrace();
12 } catch (IOException e) {
13 // TODO Auto-generated catch block
14 e.printStackTrace();
15 }
Con este método indicamos de onde imos a cargar o audio.
Podemos ter varias posibilidades:
- O arquivo se atopa en /res/raw:
1 MediaPlayer mediaplayer = new MediaPlayer(); 2 Uri uri = Uri.parse("android.resource://" + getPackageName()+ "/" + R.raw.snowflake_persephone); 3 try { 4 mediaplayer.setDataSource(getApplicationContext(), uri); 5 ..... 6 }
- O arquivo provén da tarxeta SD.
- Temos varias opcións:
1 MediaPlayer mediaplayer = new MediaPlayer(); 2 String path=Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator + "snowflake_persephone.mp3"; 3 try { 4 mediaplayer.setDataSource(Uri.encode(path)); 5 ..... 6 }
1 MediaPlayer mediaplayer = new MediaPlayer(); 2 String path =Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator + "snowflake_persephone.mp3"; 3 Uri uri = Uri.parse(path); 4 try { 5 mediaplayer.setDataSource(getApplicationContext(),uri); 6 ..... 7 }
- O arquivo provén de Internet (música en Streaming).
- Nota: Se estades a utilizar un dispositivo real ten que ter conexión a Internet para que funcione.
- Temos varias opcións:
1 MediaPlayer mediaplayer = new MediaPlayer(); 2 String url="http://www.mfiles.co.uk/mp3-downloads/edvard-grieg-peer-gynt1-morning-mood.mp3"; 3 try { 4 mediaplayer.setDataSource(url); 5 ..... 6 }
Pode suceder que a dirección da URL teña espazos en branco ou caracteres especiais. Nese caso teremos que 'codificar' antes a url a partires do último carácter '/':
1 MediaPlayer mediaplayer = new MediaPlayer(); 2 String url="http://www.mfiles.co.uk/mp3-downloads/edvard-grieg-peer-gynt1-morning-mood.mp3"; 3 int pos = url.lastIndexOf('/') + 1; 4 Uri uri = Uri.parse(url.substring(0, pos) + Uri.encode(url.substring(pos))); 5 try { 6 mediaplayer.setDataSource(getApplicationContext(),uri); 7 ..... 8 }
- Chamar ao método setAudioStreamType(AudioManager.STREAM_MUSIC) para indicarlle que o que imos reproducir será música e poñerá o volume que teña o S.O. (lembrar que en Android podemos cambiar o volume da música, notificacións e alarmas).
- Chamar ao método prepare (pode lanzar unha excepción IOException).
- Chamar ao método start.
Todos estes pasos son necesarios facelos nesta orde xa que o MediaPlayer vai pasar por unha serie de estados que nos van obrigar a poder facer só unha accións determinadas dependendo do estado no que nos atopemos.
Vexamos o diagrama de estados do MediaPlayer:
Imos analizar dito diagrama.
Cando instanciamos o MediaPlayer (facemos o new) nos situamos no estado idle. Dende dito estado so podemos pasar o estado initialized chamando o método setDataSource.
Se intentemos chamar a outro método (coma start(),...) daranos unha excepción (IlegalStateException). Cando estamos a tocar unha canción non podemos cambiar por outra, xa que aínda que chamemos o método stop e pasemos o estado de stop, dende dito estado non podemos chamar ao método setDataSource (mirar diagrama).
A única forma de cambiar de canción será chamando ao método reset(), que volve ao estado Idle como amosamos neste anaco do diagrama anterior:
Podemos cargar un arquivo de audio sen facer os pasos anteriores da seguinte forma:
1 MediaPlayer mediaPlayer=MediaPlayer.create(this, R.raw.snowflake_persephone);
2 mediaPlayer.start();
Sendo R.raw.snowflake_persephone un arquivo de audio que se atopa no cartafol /res/raw.
Desta forma estamos a pasar directamente ao estado Prepared do diagrama anterior. No nosa práctica non usaremos esta forma de cargar o audio.
Caso práctico
- Partimos que xa temos creado o proxecto inicial como xa indicamos anteriormente.
- Se non o temos creado antes, crearemos un novo paquete de nome: Multimedia como un subpaquete do teu paquete principal.
- Dentro do paquete Adaptadores crear unha nova 'Empty Activity' de nome: UD05_01_Audio_Reproducir de tipo Launcher e sen compatibilidade.
- Modifica o arquivo AndroidManifiest.xml e engade unha label á activity como xa vimos na creación do proxecto base.
Nota: Nos volcados de pantalla pode ser que apareza outra unidade no título. Non facede caso.
O obxectivo desta práctica é reproducir un arquivo de música gardado en /res/raw e outro en streaming de Internet.
Nota: Se instalades a aplicación nun dispositivo real tedes que ter conexión a Internet para que funcione a reprodución en streaming e deberedes de poñer o permiso correspondente no AndroidManifiest.xml
Preparación
- Copiamos o arquivo de audio (ou outro calquera que podades utilizar) ao cartafol /res/raw.
Media:snowflake_persephone.mp3 .
ATENCIÓN: Ao ser un arquivo que vai ser gardado no interior do cartafol /res de Android, o arquivo non pode ter letras maiúsculas.
Código do layout xml
Nota: Por motivos de tempo para o alumnado o deseño non fai uso de constantes externas definidas no cartafol values. Queda claro que esta debería ser a opción escollida para o deseño das Interfaces de Usuario.
1 <?xml version="1.0" encoding="utf-8"?>
2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:tools="http://schemas.android.com/tools"
4 android:id="@+id/LinearLayout1"
5 android:layout_width="match_parent"
6 android:layout_height="wrap_content"
7 android:orientation="vertical" >
8
9
10 <TextView
11 android:layout_width="match_parent"
12 android:layout_height="wrap_content"
13 android:gravity="center_horizontal"
14 android:layout_marginBottom="20dp"
15 android:text="UD6_01_Multimedia"
16 android:textSize="20sp" />
17
18
19 <TableLayout
20 android:layout_width="match_parent"
21 android:layout_height="wrap_content"
22 android:layout_gravity="bottom"
23 android:isScrollContainer="true"
24 >
25
26 <TableRow
27 android:id="@+id/tableRow1"
28 android:layout_width="match_parent"
29 android:layout_height="wrap_content" >
30
31
32 <Button
33 android:id="@+id/btnReprLocal_UD05_01_audio_repr"
34 android:width="0dp"
35 android:layout_weight="1"
36 android:layout_width="wrap_content"
37 android:layout_height="wrap_content"
38 android:text="LOCAL"
39 android:textSize="10sp"
40 />
41
42 <Button
43 android:id="@+id/btnReprInternet_UD05_01_audio_repr"
44 android:width="0dp"
45 android:layout_weight="1"
46 android:layout_width="wrap_content"
47 android:layout_height="wrap_content"
48 android:text="INTERNET"
49 android:textSize="10sp"
50 />
51
52 </TableRow>
53
54 <TableRow
55 android:id="@+id/tableRow2"
56 android:layout_width="match_parent"
57 android:layout_height="wrap_content" >
58
59 <Button
60 android:id="@+id/btnPararRepr_UD05_01_audio_repr"
61 android:layout_width="0dp"
62 android:layout_height="wrap_content"
63 android:layout_weight="1"
64 android:text="PARAR"
65 android:textSize="10sp" />
66
67 </TableRow>
68 <TableRow
69 android:id="@+id/tableRow4"
70 android:layout_width="match_parent"
71 android:layout_height="wrap_content" >
72
73 <Button
74 android:id="@+id/btnSair_UD05_01_audio_repr"
75 android:layout_width="0dp"
76 android:layout_height="wrap_content"
77 android:layout_weight="1"
78 android:text="SAIR"
79 android:textSize="10sp"
80 />
81
82 </TableRow>
83
84
85 </TableLayout>
86
87 </LinearLayout>
Código da Activity
Obxectivo: Reproducir e parar música local e de Internet.
1 package es.cursoandroid.cifprodolfoucha.aprendiendo.Multimedia;
2
3 import android.Manifest;
4 import android.app.Activity;
5 import android.content.pm.PackageManager;
6 import android.media.AudioManager;
7 import android.media.MediaPlayer;
8 import android.net.Uri;
9 import android.os.Build;
10 import android.os.Bundle;
11 import android.util.Log;
12 import android.view.View;
13 import android.widget.Button;
14 import android.widget.Toast;
15 import es.cursoandroid.cifprodolfoucha.aprendiendo.R;
16
17 public class UD05_01_Audio_Reproducir extends Activity {
18 private MediaPlayer mediaplayer;
19 private boolean pause; // Indica se o mediaplayer estaba tocando cando cambiamos de aplicación
20
21
22 /**
23 * Cambia a canción no MediaPlayer
24 * @param uri
25 */
26 private void cambiarCancion(Uri uri){
27 try {
28 mediaplayer.reset();
29
30 mediaplayer.setDataSource(getApplicationContext(),uri);
31 mediaplayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
32 mediaplayer.prepare();
33 mediaplayer.start();
34 } catch (Exception e) {
35 // TODO Auto-generated catch block
36 e.printStackTrace();
37 Log.e("MULTIMEDIA",e.getMessage());
38 }
39
40 }
41
42 private void xestionarEventos(){
43
44 Button btnLocal = (Button)findViewById(R.id.btnReprLocal_UD05_01_audio_repr);
45 btnLocal.setOnClickListener(new View.OnClickListener() {
46
47 @Override
48 public void onClick(View v) {
49 // TODO Auto-generated method stub
50 Uri uri = Uri.parse("android.resource://" + getPackageName()+ "/" + R.raw.snowflake_persephone);
51 cambiarCancion(uri);
52 }
53 });
54
55 Button btnInternet = (Button)findViewById(R.id.btnReprInternet_UD05_01_audio_repr);
56 btnInternet.setOnClickListener(new View.OnClickListener() {
57
58 @Override
59 public void onClick(View v) {
60 // TODO Auto-generated method stub
61 String url = "http://www.mfiles.co.uk/mp3-downloads/edvard-grieg-peer-gynt1-morning-mood.mp3";
62 // Se a URL ven con espacios en branco teremos que facer un parteUrl + Uri.encode(parteUltinaURL) sendo parteUltinaURL a parte que vai no último lugar.
63 Uri uri = Uri.parse(url);
64 cambiarCancion(uri);
65 }
66 });
67 Button btnParar = (Button)findViewById(R.id.btnPararRepr_UD05_01_audio_repr);
68 btnParar.setOnClickListener(new View.OnClickListener() {
69
70 @Override
71 public void onClick(View v) {
72 // TODO Auto-generated method stub
73 if (mediaplayer.isPlaying())
74 mediaplayer.stop();
75 pause=false;
76 }
77 });
78
79 Button btnSair = (Button) findViewById(R.id.btnSair_UD05_01_audio_repr);
80 btnSair.setOnClickListener(new View.OnClickListener() {
81
82 @Override
83 public void onClick(View v) {
84 // TODO Auto-generated method stub
85 finish();
86 }
87 });
88
89
90 }
91
92 @Override
93 protected void onPause() {
94 super.onPause();
95
96 if (mediaplayer.isPlaying()){
97 mediaplayer.pause();
98 pause = true;
99 }
100 }
101
102 @Override
103 protected void onResume() {
104 super.onResume();
105
106 if (pause) {
107 mediaplayer.start();
108 pause = false;
109 }
110 }
111
112
113
114 @Override
115 protected void onDestroy() {
116 super.onDestroy();
117
118 if (mediaplayer.isPlaying()) mediaplayer.stop();
119
120 if (mediaplayer != null) mediaplayer.release();
121 mediaplayer = null;
122
123 }
124
125 @Override
126 protected void onCreate(Bundle savedInstanceState) {
127 super.onCreate(savedInstanceState);
128 setContentView(R.layout.activity_ud05_01__audio__reproducir);
129
130 mediaplayer = new MediaPlayer();
131 pause = false;
132 xestionarEventos();
133
134
135 }
136 }
- Liña 18: Definimos o reprodutor.
- Liña 19: Definimos unha variable booleana que nos indique cando o reprodutor está en estado de PAUSE.
- Liñas 26-40: Cambiamos de canción. Fixarse como sempre chamamos ao método reset() para pasar ao estado que nos permite cambiar a fonte de audio.
- Liñas 48-52: Prememos o botón de LOCAL e definimos a URI para buscar o arquivo en /res/raw.
- Liñas 59-64: Prememos o botón de INTERNET e definimos a URI para buscar o arquivo indicada na URL.
- Liñas 71-76: Prememos o botón de STOP. Comprobamos se o mediaplayer está tocando para paralo.
- Liñas 106-109: Comprobamos se o mediaplayer está tocando para poñelo en pause. Cambiamos o valor da variable pause a true. Isto é necesario xa que non temos forma de saber a través do mediaplayer se este se atopa nese estado.
- Liñas 106-109: Se volvemos á aplicación comprobamos se o mediaplayer estaba tocando (pause=true) e nese caso continuamos tocando a canción. Isto pasará cando cambiemos de aplicación sen pechala.
- Liñas 118-121: Se saímos da aplicación paramos de tocar e liberamos o mediaplayer.
- Liña 128: Instanciamos o mediaplayer.
Carga asíncrona
Cando poñemos en marcha un arquivo de audio que non se atopa localmente, non é boa idea facelo no fío principal da aplicación, xa que se leva un tempo cargar a música, e a aplicación quedaría bloqueada mentres tanto. Para evitalo teríamos que crear nós un fío de execución separado do principal, pero este traballo xa o temos feito, se chamamos ao método prepareAsync() . Cando a mediaplayer estea listo, chamará automaticamente ao método onPrepared da interface MediaPlayer.OnPreparedListener. Para asociar dita interface o mediaplayer, faremos uso do método setOnPreparedListener().
Os pasos serían:
- Asociar a interface ao mediaplayer:
1 mediaplayer.setOnPreparedListener(new OnPreparedListener(){
2
3 }
4 );
- Ao definir internamente a clase que xestionará o preparedlistener, é necesario implementar o método da interface dentro da definición:
1 mediaplayer.setOnPreparedListener(new OnPreparedListener(){
2
3 public void onPrepared(MediaPlayer arg0) {
4 // TODO Auto-generated method stub
5
6 }
7 }
8 );
- Agora xa podemos codificar o método onPrepared que será chamado automaticamente cando o música estea preparada. O único que temos que facer dentro de dito método será unha chamada ao método start() da clase MediaPlayer.
Nota: Agora en vez de chamar o método prepare, teremos que chamar o método prepareAsync().
Caso práctico
O obxectivo desta práctica é facer o mesmo que na práctica anterior pero chamando ao método prepareAsync.
Preparación
- Partimos que xa temos creado o proxecto inicial como xa indicamos anteriormente.
- Se non o temos creado antes, crearemos un novo paquete de nome: Multimedia como un subpaquete do teu paquete principal.
- Dentro do paquete Adaptadores crear unha nova 'Empty Activity' de nome: UD05_02_Audio_Reproducir de tipo Launcher e sen compatibilidade.
- Modifica o arquivo AndroidManifiest.xml e engade unha label á activity como xa vimos na creación do proxecto base.
- Na activity cargaremos o mesmo layout que no caso práctico anterior.
- Quen o prefira pode facelo sobre un layout novo copiando todo o código e cambiando os nomes (id´s) dos views.
Código da Activity
Obxectivo: Amosar como cargar unha canción de forma asíncrona.
1 package es.cursoandroid.cifprodolfoucha.aprendiendo.Multimedia;
2
3
4 import android.app.Activity;
5 import android.media.AudioManager;
6 import android.media.MediaPlayer;
7 import android.net.Uri;
8 import android.os.Bundle;
9 import android.util.Log;
10 import android.view.View;
11 import android.widget.Button;
12
13 import es.cursoandroid.cifprodolfoucha.aprendiendo.R;
14
15 public class UD05_02_Audio_Reproducir extends Activity {
16 private MediaPlayer mediaplayer;
17 private boolean pause; // Indica se o mediaplayer estaba tocando cando cambiamos de aplicación
18
19
20 /**
21 * Cambia a canción no MediaPlayer
22 * @param uri
23 */
24 private void cambiarCancion(Uri uri){
25 try {
26 mediaplayer.reset();
27
28 mediaplayer.setDataSource(getApplicationContext(),uri);
29 mediaplayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
30 mediaplayer.prepareAsync();
31 // mediaplayer.start(); O FACEMOS DE FORMA ASINCRONA
32 } catch (Exception e) {
33 // TODO Auto-generated catch block
34 e.printStackTrace();
35 Log.e("MULTIMEDIA",e.getMessage());
36 }
37
38 }
39
40 private void xestionarEventos(){
41
42 mediaplayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
43
44 @Override
45 public void onPrepared(MediaPlayer mp) {
46 // TODO Auto-generated method stub
47 mediaplayer.start();
48 }
49 });
50
51 Button btnLocal = (Button)findViewById(R.id.btnReprLocal_UD05_01_audio_repr);
52 btnLocal.setOnClickListener(new View.OnClickListener() {
53
54 @Override
55 public void onClick(View v) {
56 // TODO Auto-generated method stub
57 Uri uri = Uri.parse("android.resource://" + getPackageName()+ "/" + R.raw.snowflake_persephone);
58 cambiarCancion(uri);
59 }
60 });
61
62 Button btnInternet = (Button)findViewById(R.id.btnReprInternet_UD05_01_audio_repr);
63 btnInternet.setOnClickListener(new View.OnClickListener() {
64
65 @Override
66 public void onClick(View v) {
67 // TODO Auto-generated method stub
68 String url = "http://www.mfiles.co.uk/mp3-downloads/edvard-grieg-peer-gynt1-morning-mood.mp3";
69 // Se a URL ven con espacios en branco teremos que facer un parteUrl + Uri.encode(parteUltinaURL) sendo parteUltinaURL a parte que vai no último lugar.
70 Uri uri = Uri.parse(url);
71 cambiarCancion(uri);
72 }
73 });
74 Button btnParar = (Button)findViewById(R.id.btnPararRepr_UD05_01_audio_repr);
75 btnParar.setOnClickListener(new View.OnClickListener() {
76
77 @Override
78 public void onClick(View v) {
79 // TODO Auto-generated method stub
80 if (mediaplayer.isPlaying())
81 mediaplayer.stop();
82 pause=false;
83 }
84 });
85
86 Button btnSair = (Button) findViewById(R.id.btnSair_UD05_01_audio_repr);
87 btnSair.setOnClickListener(new View.OnClickListener() {
88
89 @Override
90 public void onClick(View v) {
91 // TODO Auto-generated method stub
92 finish();
93 }
94 });
95
96
97 }
98
99 @Override
100 protected void onPause() {
101 super.onPause();
102
103 if (mediaplayer.isPlaying()){
104 mediaplayer.pause();
105 pause = true;
106 }
107 }
108
109 @Override
110 protected void onResume() {
111 super.onResume();
112
113 if (pause) {
114 mediaplayer.start();
115 pause = false;
116 }
117 }
118
119 @Override
120 protected void onDestroy() {
121 super.onDestroy();
122
123 if (mediaplayer.isPlaying()) mediaplayer.stop();
124
125 if (mediaplayer != null) mediaplayer.release();
126 mediaplayer = null;
127
128 }
129
130 @Override
131 protected void onCreate(Bundle savedInstanceState) {
132 super.onCreate(savedInstanceState);
133 setContentView(R.layout.activity_ud05_01__audio__reproducir);
134
135 mediaplayer = new MediaPlayer();
136 pause = false;
137 xestionarEventos();
138
139
140 }
141 }
- Liña 133: Fixarse como estamos a cargar o layout da práctica 1.
-- Ángel D. Fernández González e Carlos Carrión Álvarez -- (2014).