Reproductor de archivos MP3 en VB (2)

Reproductor de canciones MP3 capaz de hacer reproducción aleatoria de los archivos existentes en una carpeta seleccionada mostrando información de la canción, realizado con Visual Studio 2017 con VB

Anteriormente publiqué un artículo sobre un reproductor de archivos MP3 muy sencillo con botones de reproducción y selección de archivos, utilizando el OCX de Windows Media Player 9 (WMP en adelante). Continuando ese ejercicio, se ha incluido 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 para que suene el ítem del array con el mismo índice. El control OCX de WMP se mantiene oculto, no se muestra en la ventana principal del programa pero se utilizan sus funcionalidades.
Tienes disponible una versión más compleja de este ejercicio: 3.

El control ActiveX se añade al cuadro de herramientas con el menú Herramientas > Elegir elementos del cuadro de herramientas > Componentes COM > Windows Media Player. Aparece en las herramientas de la sección Interoperabilidad WPF. Desde ahí es muy sencillo arrastrarlo al formulario como cualquier otro control. Al hacerlo, se añaden a la solución 2 archivos: AxInterop.WMPLib.dll y Interop.WMPLib.dll. Estos 2 archivos han de estar en la misma carpeta que el ejecutable de la aplicación. En mi proyecto ya están incluidos con la opción de que se copien a la carpeta bin al compilar.

El OCX tiene el nombre de AxWindowsMediaPlayer1; puede cambiarse por otro más útil, en este ejercicio se denomina ocxPlayer. En la configuración de las propiedades del control ocxPlayer pulsando sobre él con el botón derecho elegimos el modo de visualización Invisible (equivale a “uimode = invisible” en la lista de propiedades de Visual Studio) para que el control no sea mostrado en el formulario, aunque seguimos teniendo disponiendo de todas sus funcionalidades. Conviene marcar también “Inicio automático” y elegir el volumen por defecto que tendrá el reproductor al arrancar. Las demás opciones pueden dejarse como están.

Aparte de ello, Visual Studio coloca en bin otra librería llamada stdole.dll. Es un archivo de tipo PIA (primary interop assembly) que se necesita para interoperar con objetos COM de Office, WMP, etc.

Variables globales

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

' VARIABLES GLOBALES'
' para la ruta al directorio seleccionado'
    Private 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 título y el autor de las canciones'
    Private strTitulo, strAutor, strAlbum As String
' para el tiempo de reproducción transcurrido'
    Private dtTime As DateTime
' strArchivo = nombre del archivo MP3'
' strArchivo_sin = nombre del archivo MP3 sin los 4 últimos caracteres (.mp3)'
    Private strArchivo, strArchivo_sin As String
' colección con los nombres de todos los archivos MP3 del directorio y subdirectorios'
    Private listaMp3 As ArrayList
' para el índice de las canciones'
    Private intCancion As Integer
' i = índice de la última aparición de \ en la ruta al archivo MP3'
' j = longitud de la cadena con el nombre del archivo MP3'
    Dim i, j As Integer


Procedimiento para abrir un archivo

El procedimiento abrirCarpeta() presenta al usuario un cuadro de diálogo para elegir una carpeta que contenga archivos MP3. El resultado de la elección se pasa al reproductor. La variable strPath guarda la ruta a la carpeta y la variable listaMp3 guarda una matriz con los archivos contenidos en esa carpeta.

' cuadro de diálogo de selección de carpeta (FolderBrowserDialog)'
If folderDlg.ShowDialog = DialogResult.OK Then
	' strPath contiene la ruta al directorio seleccionado'
	 strPath = folderDlg.SelectedPath
	' limpiar listaMp3 de su contenido anterior'
	If listaMp3.Count > 0 Then
	    listaMp3.Clear()
	End If
End If


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 sonar.

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 es bien detectado por lo que he creado 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.

' dependiendo del estado del reproductor'
Select Case e.newState
	' REPRODUCIENDO'
	Case WMPLib.WMPPlayState.wmppsPlaying
	strAutor = OcxPlayer.currentMedia.getItemInfobyType("author", "", 0)
	strTitulo = OcxPlayer.currentMedia.getItemInfobyType("title", "", 0)
	strAlbum = OcxPlayer.currentMedia.getItemInfobyType("album", "", 0)
	 ' DETENIDO'
	Case WMPLib.WMPPlayState.wmppsStopped
	' PREPARANDO UN NUEVO ITEM'
	Case WMPLib.WMPPlayState.wmppsTransitioning
End Select


Obtener los archivos que contiene la carpeta elegida y las subcarpetas

Se emplea función recursiva que rellena listaMp3 con los archivos MP3 de carpetas y subcarpetas, es una función sobrecargada que se ejecuta de 2 formas diferentes:

  • con un parámetro strpath (string): lista los archivos y carpetas del directorio raíz
  • con 2 parámetros odir (DirectoryInfo) y nivel(integer): lista los archivos y carpetas de las subcarpetas.
' FUNCIÓN RECURSIVA QUE RELLENA listaMp3 CON LOS ARCHIVOS MP3 DE CARPETAS Y SUBCARPETAS'
' ES UNA FUNCIÓN SOBRECARGADA QUE SE EJECUTA DE 2 FORMAS DIFERENTES:'
' - CON UN PARÁMETRO strPath(String): LISTA LOS ARCHIVOS Y CARPETAS DEL DIRECTORIO RAÍZ'
' - CON 2 PARÁMETROS oDir(DirectoryInfo) y nivel(Integer): LISTA LOS ARCHIVOS Y CARPETAS DE LAS SUBCARPETAS'
Private Overloads Function ListarArchivos(ByVal strPath As String) As ArrayList
        Try
            Dim oDir As New System.IO.DirectoryInfo(strPath) ' propiedades del directorio raíz'
            Dim oSubDir() As System.IO.DirectoryInfo ' propiedades de los subdirectorios'
            Dim oFiles() As System.IO.FileInfo ' propiedades de los archivos'
            Dim i As Integer ' para contar elementos en bucles'
            oFiles = oDir.GetFiles ' archivos del directorio raíz'
          
            ' añadir al ArrayList cada uno de los archivos MP3 del directorio raíz'
            ' se emplea la propiedad FullName pues contiene la ruta completa al archivo'
            ' necesaria para que el reproductor pueda abrir el archivo mediante su propiedad URL'
            For i = 0 To oFiles.Length - 1
                If Not oFiles(i).Name.StartsWith(".") Then
                    If oFiles(i).Name.EndsWith("mp3") OrElse oFiles(i).Name.EndsWith("MP3") Then
                        listaMp3.Add(oFiles(i).FullName)
                    End If
                End If
            Next
            
            ' obtener las subcarpetas del directorio raíz'
            oSubDir = oDir.GetDirectories
            ' en cada subcarpeta, se llama a la función recursiva con 2 parámetros:'
            ' - la subcarpeta actual'
            ' - la profundidad de la subcarpeta respecto al directorio raíz (se empieza por 1)'
            For i = 0 To oSubDir.Length - 1
                Call ListarArchivos(oSubDir(i), 1)
            Next
            
            lbIndex0.Text = "Nº de canciones:"
            lbIndex.Text = " " + CStr(listaMp3.Count)
        Catch ex As Exception
            MsgBox("Error en Recursive(strPath): " + vbCrLf + ex.Message)
        End Try
End Function

Private Overloads Function ListarArchivos(ByVal oDir As System.IO.DirectoryInfo, ByVal nivel As Integer) As ArrayList
        Try
            Dim oSubDir() As System.IO.DirectoryInfo ' propiedades de los subdirectorios'
            Dim oFiles() As System.IO.FileInfo ' propiedades de los archivos'
            Dim i As Integer ' para contar elementos en bucles'
            oFiles = oDir.GetFiles ' archivos que contiene el directorio en cada llamada recursiva'
            
            ' añadir a listaMp3 cada uno de los archivos MP3 del subdirectorio'
            ' se emplea la propiedad FullName pues contiene la ruta completa al archivo'
            For i = 0 To oFiles.Length - 1
                If oFiles(i).Name.EndsWith("mp3") OrElse oFiles(i).Name.EndsWith("MP3") Then
                    listaMp3.Add(oFiles(i).FullName)
                End If
            Next
            
            ' obtener las subcarpetas del subdirectorio'
            oSubDir = oDir.GetDirectories()
            ' en cada subcarpeta, se llama a la función recursiva con 2 parámetros:'
            ' - la carpeta actual'
            ' - la profundidad de la carpeta respecto al directorio raíz (avanza 1 en cada pasada)'
            For i = 0 To oSubDir.Length - 1
                Call ListarArchivos(oSubDir(i), nivel + 1)
            Next
        Catch ex As Exception
            MsgBox("Error en Recursive(oDir, nivel): " + vbCrLf + ex.Message)
        End Try
End Function


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, 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 60:

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

El código del método Aleatorio() es:

' 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() hacemos blShuffle = False'
	blShuffle = False
	Randomize()
	' saber el número de archivos MP3 -> límite superior para usar con Rnd()'
	Dim n As Integer
	n = listaMp3.Count
	' obtener un índice aleatorio'
	' (se usa -1 porque el índice del ArrayList empieza en cero y listaMp3.Count empieza en 1)'
	intCancion = CInt(n * Rnd() + 1) - 1
	' asignar el archivo al reproductor si el índice está dentro del rango adecuado'
	If intCancion >= 0 And intCancion < listaMp3.Count Then
	    strArchivo = listaMp3(intCancion)
	    OcxPlayer.URL = strArchivo
	End If
End If


ZIP con la aplicación completa

Puedes descargar la aplicación completa desde aquí.

Nota: el ajuste automático de escala del formulario que muestra la ventana de la aplicación se puede configurar de varias maneras, lo tienes en las propiedades del formulario como AutoScaleMode, la opción con la que obtengo el mejor resultado es con Dpi.