Reproductor MP3 en VB .NET 2017 (3)

Reproductor MP3 con capacidad de reproducción aleatoria y navegación por unidades de disco y carpetas en controles ComboBox y TreeView junto con selección de canción en control ComboBox, en Visual Studio 2017

En 2 artículos anteriores se mostraba la forma de construir, sin conocimientos avanzados de programación, un reproductor de archivos MP3 añadiendo al proyecto el control Active X de Windows Media Player 9:

En este ejercicio se añaden 2 nuevas características.

ComboBox y TreeView
Una es la de elegir la carpeta con los archivos MP3 usando árbol de directorios con 3 controles:
ComboBox que permite elegir una unidad de disco
TreeView que permite desplegar los directorios del disco, recorrer las carpetas de forma recursiva y seleccionar una con archivos MP3 cuya reproducción aleatoria comienza automáticamente
ComboBox que despliega las canciones MP3 de la carpeta seleccionada y permite elegir una de las canciones de la lista para que sea reproducida.

My.Settings
Otra es la de guardar la configuración del programa al cerrarlo usando My.Settings, en concreto se guarda la última carpeta utilizada para que sea con la que arranca el programa sin tener que elegir necesariamente una al comenzar.

Centrar la ventana

Una forma sencilla de centrar la ventana principal del programa es la de utilizar por código las propiedades Widht y Height de Screen.PrimaryScreen.WorkingArea.

'posición x.y del formulario: centrado en la pantalla
Left = (Screen.PrimaryScreen.WorkingArea.Width - Me.Width) \ 2
Top = (Screen.PrimaryScreen.WorkingArea.Height - Me.Height) \ 2


ComboBox con los discos

Se rellena un array de cadenas con las letras de las unidades disponibles en el ordenador. Las letras de unidad se obtienen con GetLogicalDrives de System.Environment.

' rellenar el Combobox con las letras de unidad del sistema
' array de cadenas para contener los nombres de las unidades
Dim drives() As String
drives = System.Environment.GetLogicalDrives()
' rellenar el Combobox desde el array
CbDrive.Items.AddRange(drives)

Carpeta inicial con My.Settings

Si hay una carpeta guardada en la configuración, se recupera con My.Settings como ruta inicial para la reproducción. En caso contrario, se elige la letra C:\ (primera unidad) y se continúa con la selección de carpeta.

 ' si hay carpeta guardada en la configuración al cerrar la aplicación
 ' usarla como carpeta de inicio sin tener que seleccionar en el árbol de carpetas
If Not My.Settings.Ruta = "" Then
strPath = My.Settings.Ruta
' rellenar el Combobox con el primer carácter de strPath (puede ser la letra C, D, etc.) añadiendo :\
' Substring, si lleva un parámetro, es el índice empezando en cero a partir del cual se coge la cadena
' si lleva 2 parámetros el 1º es como en la línea superior y el 2º es el nº de carácteres que se cogen
' en este ejemplo (0,1) se coge 1 carácter empezando por la izquierda, es decir el primer carácter
      CbDrive.Text = strPath.Substring(0, 1) & ":\"
   Else
      CbDrive.SelectedItem = "C:\"
End If

Carpetas de la unidad seleccionada en TreeView

Al elegir unidad de disco se desplegan las carpetas de esa unidad en un TreeView.
Primero se obtienen las carpetas de la unidad con GetDirectories de DirectoryInfo. Se utiliza la clase DirectoryInfo para operaciones típicas con carpetas y archivos como copiar, mover, cambiar de nombre, listar, crear y eliminar directorios, etc. Cada carpera de la undiad se va añadiendo al TreeView como un nodo nuevo hasta completar el árbol completo. Como no se desea listar carpetas o archivos ocultos, se usa FileAttributes.Hidden y FileAttributes.System junto con la característica de que el nombre empiece por un punto para no incluir esos ítems en la lista.

Dim combo As New DirectoryInfo(Me.CbDrive.Text)
Dim carpetas As DirectoryInfo()
carpetas = combo.GetDirectories()
' limpiar el TreeView
TvDir.Nodes.Clear()
' rellenar el TreeView con los nombres de los directorios;
' TreeNode representa a los nodos del TreeView
Dim carpetas2 As DirectoryInfo
' para que no muestre carpetas o archivos ocultos o de sistema
' ni tampoco los que comienzan por un punto
For Each carpetas2 In carpetas
	If Not (carpetas2.Attributes And FileAttributes.Hidden) = FileAttributes.Hidden And Not (carpetas2.Attributes And FileAttributes.System) = FileAttributes.System And Not carpetas2.FullName.StartsWith(".") Then
	    ' el constructor de la clase Treenode inicializa una nueva instancia
	    ' aquí se usa con 1 solo parámetro: el texto de etiqueta especificado
	    nodo = New TreeNode(carpetas2.FullName)
	    TvDir.Nodes.Add(nodo)
	End If
Next

Al elegir un elemento del TreeView hay que cambiar a él para listar las carpetas que contiene. Se hace detectando el evento AfterSelect del TreeView. Se obienen los elementos del nodo elegido y se rellena el TreeView con los nombres de los subdirectorios; TreeNode representa a cada nodo del TreeView. Se emplea de nuevo FileAttributes.Hidden y FileAttributes.System para omitir ítems ocultos.

' obtener los subdirectorios del nodo seleccionado
Dim carpetas3 As DirectoryInfo
carpetas3 = New DirectoryInfo(e.Node.FullPath)
Dim carpetas4() As DirectoryInfo
carpetas4 = carpetas3.GetDirectories()
' rellenar el TreeView con los nombres de los subdirectorios;
' TreeNode representa a los nodos del TreeView
Dim subcarpetas As DirectoryInfo
' para que no muestre carpetas o archivos ocultos o de sistema
' ni tampoco los que comienzan por un punto
For Each subcarpetas In carpetas4
    If Not (subcarpetas.Attributes And FileAttributes.Hidden) = FileAttributes.Hidden And Not (subcarpetas.Attributes And FileAttributes.System) = FileAttributes.System And Not subcarpetas.Name.StartsWith(".") Then
	' el constructor de la clase Treenode inicializa una nueva instancia
	' aquí se usa con 1 solo parámetro: el texto de etiqueta especificado
	nodo = New TreeNode(subcarpetas.Name)
	e.Node.Nodes.Add(nodo.Text)
    End If
Next
' obtener los archivos del subdirectorio
Dim archivos() As FileInfo
archivos = carpetas3.GetFiles()

Al obtener los archivos de la carpeta finalmente elegida sólo deseo los que tienen extensión MP3 o mp3, se hace con un bucle For Each … Next.

' rellenar el Combobox con los nombres de los archivos
' que cumplan la condición: terminar por la extensión mp3 
Dim cancion As FileInfo
For Each cancion In archivos
    ' elegir solamente los archivos con extensión MP3 o mp3
    Select Case cancion.Extension
	Case ".MP3", ".mp3"
	    CbMp3.Items.Add(cancion.Name)
    End Select
Next

La ruta a la carpeta elegida con los archivos MP3 coincide con la ruta del nodo actualmente seleccionado en el TreeView (SelectedNode.FullPath) y ese es el texto que se asigna a una variable String que se pasa al procedimiento que lista los MP3 de la carpeta y subcarpetas y empieza la reproducción aleatoria.

strPath = TvDir.SelectedNode.FullPath
' limpiar listaMp3 de su contenido anterior
If listaMp3.Count > 0 Then
    listaMp3.Clear()
End If
' función recursiva que rellena listaMp3 con los archivos MP3 de carpetas y subcarpetas
' comienza listando archivos y carpetas de strPath (directorio raíz)
Call ListarArchivos(strPath)
' comenzar a reproducir una canción al azar
Call Aleatorio()

Rellenar recursivamente la lista de canciones

La función ListarArchivos se utiliza para obtener los archivos MP3 de froma recursiva desde la carpeta elegida y las subcarpetas que contenga. es una función sobrecargada (overloaded) que se ejecuta de 2 formas diferentes:

  • con un parámetro strpath(string): lista los archivos y carpetas del directorio raíz – ListarArchivos(ByVal strPath As String)
  • con 2 parámetros odir(directoryinfo) y nivel(integer): lista los archivos y carpetas de las subcarpetas descendiendo de nivel – ListarArchivos(ByVal oDir As System.IO.DirectoryInfo, ByVal nivel As Integer).
Private Overloads Function ListarArchivos(ByVal strPath As String) As ArrayList
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
End Function

Private Overloads Function ListarArchivos(ByVal oDir As System.IO.DirectoryInfo, ByVal nivel As Integer) As ArrayList
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
End Function

Reproducción aleatoria

Para que el reproductor vaya cargando de forma aleatoria (shuffle) las canciones de la/s carpeta/s se usa un procedimiento que primero llama al método Randomize que inicia el generador de números aleatorios, después se elige con la función Rnd un número al azar entre los correspondientes a los archivos contenidos en el array de MP3 y el archivo que coincide con el índice seleccionado se asigna al control OCX del reproductor.

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
    strArchivo1 = listaMp3(intCancion)
    OcxPlayer.URL = strArchivo1
End If

Mostrar información de la canción

Se usan las propiedades title y author del archivo que se reproduce para mostrar la información en las etiquetas. Para mostrar en la barra de título de la ventana el título de la canción + el autor de la canción + la ruta a la carpeta se utilizan 2 variables enteras h y k, h para saber dónde está la última aparición de \ en el item que reproduce el control OCX, aquí terminaría la ruta a la carpeta y empezaría el nombre del archivo, k para saber la longitud completa de la cadena con la ruta incluyendo el nombre del archivo, con la función Remove se quitan los caracteres que hay después de la última aparición de \ incluido este carácter para dejar la ruta a la carpeta sin el nombre del archivo.

strAutor = OcxPlayer.currentMedia.getItemInfobyType("author", "", 0)
strTitulo = OcxPlayer.currentMedia.getItemInfobyType("title", "", 0)
h = OcxPlayer.currentMedia.sourceURL.LastIndexOf("\")
k = OcxPlayer.currentMedia.sourceURL.Length
strArchivo_sin1 = OcxPlayer.currentMedia.sourceURL.Remove(h, k - h)
Me.Text = strTitulo &amp; " - " &amp; strAutor &amp; " - " &amp; strArchivo_sin1

Estado de la reproducción

Detectando el evento PlayStateChange del control OCX se puede saber cuándo el reproductor está:

  • reproduciendo (WMPLib.WMPPlayState.wmppsPlaying)
  • detenido (WMPLib.WMPPlayState.wmppsStopped)
  • cambiando de canción (WMPLib.WMPPlayState.wmppsTransitioning).

Ello permte configurar tareas dependiendo del estado en que se encuentre.

Private Sub OcxPlayer_PlayStateChange(ByVal sender As Object, ByVal e As AxWMPLib._WMPOCXEvents_PlayStateChangeEvent) Handles OcxPlayer.PlayStateChange
' dependiendo del estado del reproductor
Select Case e.newState
' REPRODUCIENDO
Case WMPLib.WMPPlayState.wmppsPlaying
' código que se desea ejecutar
' DETENIDO
Case WMPLib.WMPPlayState.wmppsStopped
' código que se desea ejecutar
' PREPARANDO UN NUEVO ITEM
Case WMPLib.WMPPlayState.wmppsTransitioning
' código que se desea ejecutar
End Select
End Sub

Cronómetro para el tiempo de reproducción

Se usa un control temporizador Timer y su evento Tick que se produce cuando ha trascurrido el período de tiempo especificado. Se comienza a contar al comenzar la reproducción y se van añadiendo segundos cambiando de minuto cada 59 segundos.

' para el tiempo de reproducción transcurrido
Private dtTime As DateTime
'
' Código para el cronómetro del tiempo del programa
Dim Minutos, Segundos As Integer ' variables para mostrar el tiempo
' variable Segundos = diferencia en segundos entre una hora y la hora actual 
Segundos = DateDiff(DateInterval.Second, dtTime, DateTime.Now)
Minutos = Segundos \ 60
Segundos = Segundos - (Minutos * 60) ' para que no supere 59
' presentación formateada del tiempo transcurrido
lbTime.Text = Format(Minutos, "00") &amp; ":" &amp; Format(Segundos, "00")

Seleccionar ítem en el ComboBox de canciones

El formulario principal tiene un ComboBox debajo del TreeView, en él se muestran las canciones existentes en la carpeta elegida. Al seleccionar una canción en el ComboBox se produce su evento SelectedIndexChanged, al detectarlo es posible iniciar la reproducción del ítem que se ha seleccionado.

' para la ruta al directorio seleccionado
Private strPath As String
'
' asignar a strPath la ruta completa al archivo elegido en el Combobox
strPath = strPath &amp; "\" &amp; CbMp3.SelectedItem
' reproducir el archivo elegido
OcxPlayer.URL = strPath

Guardar configuración al salir

Para que el programa al cerrarse guarde la ruta a la última carpeta usada se llama al método ChangeAndPersistSettings que se encarga de guardar las variables que se hayan creado en la pestaña Configuración de las propiedades del proyecto. En este ejercicio se ha creado una variable de tipo String llamada Ruta que guarda la ruta a la carpeta obteniéndola desde la variable strPath. El valor de Ruta se almacena cuando el programa está cerrado y está disponible cuando vuelve a ser iniciado.

' AL CERRAR EL FORMULARIO, GUARDAR LA CONFIGURACIÓN
Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
	' guardar la configuración
	Call ChangeAndPersistSettings()
End Sub

' método específico que guarda la configuración, también se puede utilizar en su lugar la opción
' <Save My.Settings on shutdown> de la pestaña Aplicación en las propiedades del proyecto
Sub ChangeAndPersistSettings()
	My.Settings.Ruta = strPath
	' guardar la configuración
	My.Settings.Save()
End Sub

Puedes descargar el proyecto en Visual Studo 2017 desde aquí: ZIP.

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.