Reproductor aleatorio de MP3

[Usando el OCX de Windows Media Player 10.
Ejercicio para .NET Framework 1.1]


Introducción

En agosto de 2003 publiqué un artículo sobre un reproductor de archivos MP3 realizado en Visual Basic .NET 2003 utilizando el OCX de Windows Media Player 9. Retomando ese ejercicio, he añadido la funcionalidad de reproducir los archivos de forma aleatoria. El programa crea un Array con todas las canciones existentes en una determinada carpeta y, mediante las funciones Randomize() y Rnd(), genera un índice aleatorio que asigna al reproductor. En este artículo comentaré los apectos que difieran del artículo anteriormente publicado para evitar repeticiones innecesarias.

Requisito imprescindible: tener instalado Windows Media Player 9 o 10 y añadir su control OCX al proyecto (explicado en el artículo anterior).

Variables globales

Se crean unas variables con visibilidad a nivel de clase para poder utilizarlas en cualquier procedimiento:

' para el nombre y la ruta de los archivos
Private strFilename, strPath As String
' el evento WMPLib.WMPPlayState.wmppsTransitioning se produce
' cuando Windows Media Player está preparando un nuevo ítem
' pero sólo ha de suceder cuando la variable blShuffle sea True
Private blShuffle As Boolean = False
' para el tiempo de reproducción transcurrido
Private dtTime As DateTime
' Array para contener los nombres de los archivos MP3
Private strLista(), strArchivo As String
' para el índice de las canciones
Private intCancion As Integer
Private a As Integer = 1
'
' para escribir en un archivo de texto el índice de los archivos reproducidos
Private objEscritor As StreamWriter

Procedimiento para abrir un archivo

El procedimiento abrirArchivo() presenta al usuario un cuadro de diálogo para elegir entre archivos MP3, WPL y WAV. El resultado de la elección se pasa al reproductor. La variable strPath guarda la ruta a la carpeta que contiene la canción que suena y la variable strLista una matriz con los archivos contenidos en esa carpeta.

Private Sub abrirArchivo() ' procedimiento para buscar y abrir un archivo
   ' limpiar variables y parar el reproductor
   ocxPlayer.Ctlcontrols.stop()
   ocxPlayer.URL = ""
   strPath = ""
   lbIndex.Text = ""
   ' presentar al usuario un cuadro de diálogo
   Dim openFile As New OpenFileDialog
   ' mostrar solamente archivos MP3, WAV y WPL
   openFile.Filter = "Canciones (*.mp3; *.wav; *.wpl)|*.mp3; *.wav; *.wpl"
   ' si elegimos algún archivo
   If openFile.ShowDialog() = Windows.Forms.DialogResult.OK Then
     ' abrir el archivo elegido en el reproductor
     ocxPlayer.URL = openFile.FileName
     ' strPath = ruta a la carpeta que contiene el archivo MP3 hasta la última aparición de \
     ' (ruta sin el nombre de archivo)
     Dim i As Integer ' índice de la última aparición de \
     i = openFile.FileName.LastIndexOf("\")
     strPath = openFile.FileName.Remove(i, openFile.FileName.Length - i)
     ' strArchivo = ruta a la carpeta que contiene el archivo MP3 a partir de la última aparición de \
     '(nombre de archivo sin la ruta)
     strArchivo = openFile.FileName.Substring(i + 1)
     ' Array de String para los archivos MP3 existentes en la carpeta
     strLista = System.IO.Directory.GetFiles(strPath)
     ' índice de la canción que suena en el Array strLista
     'intCancion = Array.IndexOf(strLista, strArchivo)
     '
     ' para poder llamar al método Aleatorio() en el evento wmppsTransitioning
     '
     ' si es una lista de reproducción WPL
     If ocxPlayer.URL.EndsWith("wpl") Then
       blShuffle = False
       lbIndex.Text = "Lista en reproducción"
       ' si no es una lista de reproducción (archivo MP3 o VAW)
     Else
       blShuffle = True
       lbIndex.Text = "Carpeta con " & strLista.Length & " canciones"
     End If
     '
     ' para contar el número de canciones que se van reproduciendo en la sesión,
     ' cada vez que se abre un archivo nuevo, se incrementa en 1
     a += 1
   End If
End Sub

Estado del reproductor

El procedimiento ocxPlayer_PlayStateChange() detecta los cambios en el estado del reproductor (reproduciendo, detenido...) para mostrar información de la canción al cambiar el estado del reproductor (PlayStateChange). Como el OCX tiene configurado su inicio automático, simplemente con elegir un archivo ya cambia el estado de reproducción y comienza a tocar. De este procedimiento, existente en el artículo anterior, sólo comentaré la inclusión de un nuevo estado wmppsTransitioning que tiene lugar cada vez que el reproductor está preparando un nuevo ítem; pero en algunas ocasiones no debe ser detectado por lo que se crea una variable booleana blShuffle que dice al programa cuándo detectar wmppsTransitioning y cuándo no hacerlo.

Por otro lado, el programa puede leer metadatos de los archivos MP3 tales como el título de la canción (.currentMedia.getItemInfobyType("title", "", 0)) o el nombre del autor (.currentMedia.getItemInfobyType("author", "", 0)) y mostrarlos en unas etiquetas de texto. Pero si el archivo MP3 carece de esos tags (particularmente del tag author ya que el tag title se rellena a partir del nombre de archivo) el programa no muestra correctamente información sobre la duración de la canción y el tiempo transcurrido y aparecen errores respecto a los controles de la aplicación que han de verse habilitados o deshabilitados, por ese motivo se captura la excepción al rellenar esas etiquetas informativas.

Los controles del formulario que se muestran como habilitados difieren si el archivo reproducido es una canción independiente (MP3 o VAW) o si es una lista de canciones (WPL).

Private Sub ocxPlayer_PlayStateChange(ByVal sender As Object, ByVal e As AxWMPLib._WMPOCXEvents_PlayStateChangeEvent) Handles ocxPlayer.PlayStateChange
   '
   dtTime = DateTime.Now ' para empezar a contar el tiempo
   ' vaciar la información de la canción anterior
   lbTitulo.Text = ""
   lbTituloa.Text = "Reproducción en curso:"
   lbAutor.Text = ""
   lbAutora.Text = "Intérprete:"
   '
   ' dependiendo del estado del reproductor
   Select Case e.newState
       '
       ' REPRODUCIENDO
     Case WMPLib.WMPPlayState.wmppsPlaying
       '
       'cadena con el nombre del archivo que suena
       strArchivo = ocxPlayer.URL
       ' Array de String para los archivos MP3 existentes en la carpeta
       strLista = System.IO.Directory.GetFiles(strPath)
       ' índice de la canción que suena en el Array strLista
       intCancion = Array.IndexOf(strLista, strArchivo) + 1
       '
       ' mostrar en la barra de título:
       '- el número de orden del archivo en la carpeta
       '- el título de la canción
       If intCancion > 0 Then
         Me.Text = intCancion & ". " & ocxPlayer.currentMedia.getItemInfobyType("title", "", 0) ' título
       Else
         MsgBox("Imposible reproducir el archivo:" & vbCrLf & strArchivo & vbCrLf & _
         "que se corresponde con strLista[" & (intCancion - 1) & "].", MsgBoxStyle.Information, "Error")
       End If
       ' evitar errores con archivos MP3 que no tengan metainformación sobre el ítem Author
       ' (la etiqueta lbAutor se rellena con el nombre del archivo cuando no existe metainformación sobre ese ítem)
       Try
         lbTituloa.Text = "Reproducción en curso:"
         lbTitulo.Text = ocxPlayer.currentMedia.getItemInfobyType("title", "", 0) ' título
         lbAutora.Text = "Intérprete:"
         lbAutor.Text = ocxPlayer.currentMedia.getItemInfobyType("author", "", 0) ' autor
       Catch pollo As Exception
         lbAutor.Text = ""
       End Try
       ' para mostrar la duración de la canción
       Dim minutos As Integer
       Dim segundos As Integer
       minutos = ocxPlayer.currentMedia.duration \ 60 ' división entera entre la duración en segundos y 60
       ' resto entre la duración total y el resultado de la división entera anterior
       segundos = ocxPlayer.currentMedia.duration - (minutos * 60)
       lbTotal.Text = Format(minutos, "00") & ":" & Format(segundos, "00")
       Timer1.Enabled = True ' iniciar el temporizador para cronometrar la canción
       Timer1.Start()
       '
       btAbrir.Enabled = False ' desactivar el botón Abrir
       btStop.Enabled = True ' activar el botón Abrir
       btPlay.Enabled = False ' desactivar el botón Play
       ' botones Prev y Next activos sólo si reproduce una lista
       If ocxPlayer.URL.EndsWith("wpl") Then
         btPrev.Enabled = True ' activar el botón Prev
         btNext.Enabled = True ' activar el botón Next 
         ' si es una lista, no se muestra información con el nº de archivos en la carpeta
         lbIndex.Text = "Lista en reproducción"
         lbCuenta.Text = ""
      Else
      btPrev.Enabled = False ' desactivar el botón Prev
      btNext.Enabled = False ' desactivar el botón Next
      btSaltar.Enabled = True ' activar el botón Saltar 
      ' si no es una lista, se muestra información con el nº de archivos en la carpeta
      'lbIndex.Text = "Canción " & (intCancion + 1) & " de " & strLista.Length
      lbIndex.Text = "Carpeta con " & strLista.Length & " canciones"
      If a = 0 Then
        a = 1
      End If
      lbCuenta.Text = "Suena la canción nº " & a
    End If
    '
    ' DETENIDO
   Case WMPLib.WMPPlayState.wmppsStopped
   btAbrir.Enabled = True ' activar el botón Abrir
   btStop.Enabled = False ' desactivar botón Stop
   btPlay.Enabled = True ' activar botón Play
   btPrev.Enabled = False ' desactivar botón Prev
   btNext.Enabled = False ' desactivar botón Next
   btSaltar.Enabled = False ' desactivar botón Saltar
  ' barra de título de la ventana vacía
  Me.Text = "emiPlayer"
  Timer1.Stop() ' detener temporizador
  Timer1.Enabled = False
  lbTotal.Text = ""
  lbTime.Text = ""
  lbIndex.Text = ""
  lbCuenta.Text = ""
  '
  ' desactivar reproducción aleatoria
  If blShuffle = True Then
   blShuffle = False
  End If
  '
  ' PREPARANDO UN NUEVO ITEM
  Case WMPLib.WMPPlayState.wmppsTransitioning
  ' actuar en el evento wmppsTransitioning sólo si blShuffle es True
   If blShuffle = True Then
    Call Aleatorio()
   End If
  End Select
End Sub

Reproducción aleatoria

El procedimiento Aleatorio() se encarga de generar un número aleatorio entre 1 y el número de elementos que componen la matriz de canciones (es decir, entre 1 y el número total de canciones en la carpeta).

La función Randomize() sin argumento arranca el generador de números aleatorios con un valor de inicialización basado en el temporizador del sistema.

La función Rnd() devuelve un número aleatorio de tipo Single (devuelve un valor menor que 1 pero mayor o igual que cero). Para producir enteros aleatorios en un intervalo dado, se utiliza la siguiente fórmula:

aleatorio = CInt(Int((límite_superior - límite_inferior + 1) * Rnd() + límite_inferior))

En el siguiente ejemplo se genera un entero aleatorio en el intervalo entre 1 y 6:

Dim value As Integer = CInt(Int((6 * Rnd()) + 1))

Como comprobación accesoria y para repasar cómo escribir en un archivo de texto con la clase StreamWriter, cada vez que arranca el programa se crea un archivo de texto en el que se van anotando los índices de las canciones según van siendo reproducidas. El archivo se llama Lista.txt y se crea en el directorio temporal. Si se reproduce una lista (WPL) solamente escribe el título y hora de comienzo.
El constructor de StreamWriter lleva un 2 parámetros:
- la ruta al archivo en que se va a escribir
- un parámetro booleano que puede ser False (lo que se escribe substituye a lo que ya está escrito) o True (lo que se escribe se añade al final de lo que ya está escrito).

Private Sub Aleatorio()
   Try
     ' ejecutar este método cuando blShuffle es True
     If blShuffle = True Then
       ' para que no llame al método Aleatorio() en el evento wmppsTransitioning
       ' hasta que termine el propio método Aleatorio()
       blShuffle = False
       '
       Randomize()
       '
       ' saber el número de archivos MP3 -> límite superior para usar con Rnd()
       Dim n As Integer
       n = strLista.Length - 1
       ' obtener un índice aleatorio
       ' (se usa -1 porque el índice del array empieza en cero)
       intCancion = CInt(n * Rnd() + 1) - 1
       strArchivo = strLista(intCancion)
       '
       ' utilizar solamente archivos MP3, WAV y WPL
       If strArchivo.EndsWith("mp3") Or strArchivo.EndsWith("wav") Or strArchivo.EndsWith("wpl") Then
         ocxPlayer.URL = strArchivo
       Else
         ' si tiene otra extensión diferente, volver a generar un índice aleatorio
         blShuffle = True ' para que se pueda ejecutar Aleatorio()
         Call Aleatorio()
       End If
       ' 
       objEscritor = New StreamWriter(Environment.GetEnvironmentVariable("TEMP") & "\Lista.txt", True)
       objEscritor.WriteLine(a & " - strLista[" & intCancion & "]")
       a = a + 1
       objEscritor.Close()
       '
       ' para poder llamar al método Aleatorio() en el evento wmppsTransitioning
       blShuffle = True
     End If
     '
   Catch pollo2 As Exception
     MsgBox("Error en el índice: " & intCancion & "." & vbCrLf & pollo2.Message)
   End Try
   '
End Sub


Imagen del programa en funcionamiento

Imagen del programa en funcionamiento