Un reproductor multimedia paso a paso

En el siguiente ejercicio vamos a profundizar en el objeto MediaPlayer por medio de un ejemplo, donde trataremos de realizar un reproductor de vídeos personalizado.

Ejercicio paso a paso: Un reproductor multimedia pasó a paso

1. Crea una nueva aplicación con los siguientes datos:

Project name: VideoPlayer

Application name: VideoPlayer

Package name: org.example.videoplayer

Create Activity: VideoPlayer

Min SDK Version: 3

2. En la carpeta res/drawable  arrastra los cuatro ficheros de iconos: play.png, pause.png, stop.png y log.png Los puedes encontrar en [1].

play.png pause.png stop.png log.png

3. Reemplaza el fichero el fichero res/layout/activity_main.xml por:

<RelativeLayout
       xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_height="match_parent"
       android:layout_width="match_parent">
    <LinearLayout
            android:id="@+id/ButonsLayout"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:orientation="horizontal"
            android:layout_alignParentTop="true">
        <ImageButton android:id="@+id/play"
                     android:layout_height="wrap_content"
                     android:layout_width="wrap_content"
                     android:src="@drawable/play"/>
        <ImageButton android:id="@+id/pause"
                     android:layout_height="wrap_content"
                     android:layout_width="wrap_content"
                     android:src="@drawable/pause"/>
        <ImageButton android:id="@+id/stop"
                     android:layout_height="wrap_content"
                     android:layout_width="wrap_content"
                     android:src="@drawable/stop"/>
        <ImageButton android:id="@+id/logButton"
                     android:layout_height="wrap_content"
                     android:layout_width="wrap_content"
                     android:src="@drawable/log"/>
        <EditText    android:id="@+id/path"
                     android:layout_height="match_parent"
                     android:layout_width="match_parent"
                     android:text="/data/video.3gp"/>
    </LinearLayout>
    <VideoView android:id="@+id/surfaceView"
                android:layout_height="match_parent"
                android:layout_width="match_parent"
                android:layout_below="@+id/ButonsLayout"/>
    <ScrollView android:id="@+id/ScrollView"
                android:layout_height="100px"
                android:layout_width="match_parent"
                android:layout_alignParentBottom="true">
        <TextView    android:id="@+id/Log"
                     android:layout_height="wrap_content"
                     android:layout_width="match_parent"
                     android:text="Log:"/>
    </ScrollView>
</RelativeLayout>

La apariencia del Layout anterior se muestra a continuación:

4. Reemplaza el código de la clase VideoPlayer por:

public class VideoPlayer extends Activity implements 
                    OnBufferingUpdateListener, OnCompletionListener, 
                    MediaPlayer.OnPreparedListener, SurfaceHolder.Callback {
       private MediaPlayer mediaPlayer;
       private SurfaceView surfaceView;
       private SurfaceHolder surfaceHolder;
       private EditText editText;
       private ImageButton bPlay, bPause, bStop, bLog;
       private TextView logTextView;
       private boolean pause;
       private String path;
       private int savePos = 0;
 
       public void onCreate(Bundle bundle) {
             super.onCreate(bundle);
             setContentView(R.layout.activity_main);
             surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
             surfaceHolder = surfaceView.getHolder();
             surfaceHolder.addCallback(this);
             surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
             editText = (EditText) findViewById(R.id.path);
             editText.setText( 
                    "http://personales.gan.upv.es/~jtomas/video.3gp");
             No tengo los derechos, pero es un tráiler, creo que no hace falta

             logTextView = (TextView) findViewById(R.id.Log);
             bPlay = (ImageButton) findViewById(R.id.play);
             bPlay.setOnClickListener(new OnClickListener() {
                 public void onClick(View view) {
                    if (mediaPlayer != null) {
                        if (pause) {
                            mediaPlayer.start();
                        } else {
                            playVideo();
                        }
                     }
                  }
             });
             bPause = (ImageButton) findViewById(R.id.pause);
             bPause.setOnClickListener(new OnClickListener() {
                  public void onClick(View view) {
                     if (mediaPlayer != null) {
                         pause = true;
                         mediaPlayer.pause();
                     }
                  }
             });
             bStop = (ImageButton) findViewById(R.id.stop);
             bStop.setOnClickListener(new OnClickListener() {
                    public void onClick(View view) {
                       if (mediaPlayer != null) {
                           pause = false;
                           mediaPlayer.stop();
                      }
                  }
             });
             bLog = (ImageButton) findViewById(R.id.logButton);
             bLog.setOnClickListener(new OnClickListener() {
                    public void onClick(View view) {
                       if (logTextView.getVisibility()==TextView.VISIBLE) {
                             logTextView.setVisibility(TextView.INVISIBLE);
                       } else {
                             logTextView.setVisibility(TextView.VISIBLE);
                       }
                   }
             });
             log("");
       }

Como puedes ver la aplicación extiende la clase Activity. Además implementamos cuatro interfaces que corresponden a varios escuchadores de eventos. Luego continúa la declaración de variables. Las primeras corresponden a diferentes elementos de la aplicación y su significado resulta obvio. La variable pause nos indica si el usuario ha pulsado el botón correspondiente, la variable path nos indica dónde está el vídeo en reproducción y la variable savePos almacena la posición de reproducción.

5. Añade:

private void playVideo() {
         try {
             pause = false;
             path = editText.getText().toString();
             mediaPlayer = new MediaPlayer();
             mediaPlayer.setDataSource(path);
             mediaPlayer.setDisplay(surfaceHolder);
             mediaPlayer.prepare();
             // mMediaPlayer.prepareAsync(); Para streaming
             mediaPlayer.setOnBufferingUpdateListener(this);
             mediaPlayer.setOnCompletionListener(this);
             mediaPlayer.setOnPreparedListener(this);
             mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
             mediaPlayer.seekTo(savePos);
          } catch (Exception e) {
             log("ERROR: " + e.getMessage());
          }
       }

El código continúa con la definición del método playVideo(). Este método se encarga de obtener la ruta de reproducción, crear un nuevo objeto MediaPlayer, luego se le asigna la ruta y la superficie de visualización, a continuación se prepara la reproducción del vídeo. En caso de querer reproducir un stream desde la red, esta función puede tardar bastante tiempo, en tal caso es recomendable utilizar en su lugar el método prepareAsync() que permite continuar con la ejecución del programa, aunque sin esperar a que el vídeo esté preparado. Las siguientes tres líneas asignan a nuestro objeto varios escuchadores de eventos que serán descritos más adelante. Tras preparar el tipo de audio, se sitúa la posición de reproducción a los milisegundos indicados en la variable savePos. Si se trata de una nueva reproducción, esta variable será cero.

6.  Añade el código:

public void onBufferingUpdate(MediaPlayer arg0, int percent) {
             log("onBufferingUpdate percent:" + percent);
       }
 
       public void onCompletion(MediaPlayer arg0) {
             log("onCompletion called");
       }

 Los métodos anteriores implementan los interfaces OnBufferingUpdateListener y OnCompletionListener. El primero irá mostrando el porcentaje de obtención de búfer de reproducción, mientras que el segundo será invocado cuando el vídeo en reproducción llegue al final.

7. Añade el código:

public void onPrepared(MediaPlayer mediaplayer) {
    log("onPrepared called");
    int mVideoWidth = mediaPlayer.getVideoWidth();
    int mVideoHeight = mediaPlayer.getVideoHeight();
    if (mVideoWidth != 0 && mVideoHeight != 0) {
        surfaceHolder.setFixedSize(mVideoWidth, mVideoHeight);
        mediaPlayer.start();
    }
 }

El método anterior implementa la interfaz OnPreperedListener. Es invocado una vez que el vídeo ya está preparado para su reproducción. En este momento podemos conocer el alto y el ancho del vídeo y ponerlo en reproducción.

8. Añade el código:

public void surfaceCreated(SurfaceHolder) {
          log("surfaceCreated called");
          playVideo);
       }
 
       public void surfaceChanged(SurfaceHolder surfaceholder, 
                                                   int i, int j, int k) {
             log("surfaceChanged called");
       }
 
       public void surfaceDestroyed(SurfaceHolder surfaceholder) {
             log("surfaceDestroyed called");
       }

Los métodos anteriores implementan la interfaz SurfaceHolder.Callback. Se invocarán cuando nuestra superficie de visualización se cree, cambie o se destruya. Los métodos que siguen corresponden a acciones del ciclo de vida de una actividad:

9.  Añade el código:

@Override protected void onDestroy() {
         super.onDestroy();
         if (mediaPlayer != null) {
             mediaPlayer.release();
             mediaPlayer = null;
         }
                 }

Este método se invoca cuando la actividad va a ser destruida. Dado que un objeto de la clase MediaPlayer consume muchos recursos, resulta interesante liberarlos lo antes posible.

.10.  Añade el código:

@Override public void onPause() {
   super.onPause();
   if (mediaPlayer != null & !pause) {
       mediaPlayer.pause();
   }
}
@Override public void onResume() {
   super.onResume();
   if (mediaPlayer != null & !pause) {
       mediaPlayer.start();
   }
}

 Los dos métodos anteriores se invocan cuando la actividad pasa a un segundo plano y cuando vuelve a primer plano. Dado que queremos que el vídeo deje de reproducirse y continúe reproduciéndose en cada uno de estos casos, se invocan a los métodos pause() y start(), respectivamente. No hay que confundir esta acción con la variable pause que lo que indica es que el usuario ha pulsado el botón correspondiente.

      11.  Añade el código:

@Override
  protected void onSaveInstanceState(Bundle guardarEstado) {
     super.onSaveInstanceState(guardarEstado);
     if (mediaPlayer != null) {
         int pos = mediaPlayer.getCurrentPosition();
         guardarEstado.putString("ruta", path);
         guardarEstado.putInt("posicion", pos);
     }
   }
 
   @Override
   protected void onRestoreInstanceState(Bundle recEstado) {
      super.onRestoreInstanceState(recEstado);
      if (recEstado != null) {
          path = recEstado.getString("ruta");
          savePos = recEstado.getInt("posicion");
      }
   }

Cuando este sistema necesita memoria, puede decidir eliminar nuestra actividad. Antes de hacerlo llamará al método onSaveInstanceState para darnos la oportunidad de guardar información sensible. Si más adelante el usuario vuelve a la aplicación, esta se volverá a cargar, invocándose el método onRestoreInstanceState, donde podremos restaurar el estado perdido. En nuestro caso la información a guardar son las variables path y savePos, que representan el vídeo y la posición que estamos reproduciendo.

Ocurre el mismo proceso cuando el usuario cambia la posición del teléfono. Es decir, cuando el teléfono se voltea las actividades son destruidas y vueltas a crear, por lo que también se invocan estos métodos.

12. Añade el código:

 private void log(String s) {
         logTextView.append(s + "\n");
   }
}

El último método es utilizado por varios escuchadores de eventos para mostrar información sobre lo que está pasando. Esta información puede visualizarse o no, utilizando el botón correspondiente.

13. Solicita  el permiso para accede a Internet añadiendo en AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"/>