Criando um site com o ASP.NET 2.0 para navegar pela biblioteca de música

Jeff Key - InRule Technology

Introdução

É fácil pressupor que a maioria das pessoas que possui computadores também possui bibliotecas de mídia. Com a popularização dos computadores, e a maior facilidade de conexão em rede e de uso, as pessoas tendem mais a criar redes domésticas. Como servidores não são práticos em uma configuração doméstica, a informação é distribuída entre os computadores da rede, dificultando o gerenciamento e a localização da informação. Isso é, provavelmente, mais evidente nas bibliotecas de música.

A maioria dos tocadores de mídia mantém um banco de dados particular contendo todas as músicas que encontram. Manter atualizados esses bancos de dados pode ser um trabalho ingrato em um único computador, e mantê-los atualizados em vários computadores parece muitas vezes impossível.

Uma solução comum é manter todos os arquivos de música em um único computador e fazer as bibliotecas de mídia dos outros computadores apontar para os arquivos desse computador e manter a atualização com ele. Qualquer pessoa que já tenha tentado fazer isso na prática sabe que é uma solução adequada, mas não ideal. Pessoalmente, eu quero apenas ouvir música, não gerenciá-la.

A minha configuração doméstica consiste em um computador com o Windows Media Center que aciona a minha TV e contém todas as minhas músicas, a minha estação de trabalho e, às vezes, um laptop. Manter a estação de trabalho atualizada com as músicas do Media Center sempre foi uma dor de cabeça, e fazer isso no laptop simplesmente não é prático. Eu queria uma maneira simples e transparente de ouvir música em qualquer lugar do meu apartamento, em qualquer computador, sem precisar me preocupar com a precisão da biblioteca. Optei por criar um pequeno site cuja única finalidade fosse me levar até a música que eu queria ouvir, com a menor dificuldade possível. Utilizei o Microsoft Visual Web Developer 2005 Express Edition Beta 2 para fazer isso.

COM: Um velho amigo

Antigamente, tínhamos uma tecnologia chamada COM que nos permitia utilizar bibliotecas escritas em qualquer linguagem a partir do nosso código, independentemente da linguagem usada (desde que funcionasse com a COM), semelhante ao .NET de hoje. A COM fazia um ótimo trabalho na solução de alguns problemas, mas criava alguns outros que ficaram mais aparentes ao longo do tempo. A .NET é, entre outras coisas, uma solução para muitos desses problemas.

Há alguns anos, a Microsoft começou a expor serviços de sistema operacional e de aplicativos através da COM. Isso permitiu que pessoas comuns escrevessem programas para o Microsoft Word, Windows Scripting Host, Windows Media Player, e muitos outros aplicativos. Devido ao número de linguagens, estruturas, etc. que utilizam a COM, ela ainda é muito importante no mundo da Microsoft e é comum que aplicativos da Microsoft anteriores à .NET utilizem a COM. Por sorte, a .NET "conversa" muito bem com a COM (na maior parte das vezes).

A lição desta história é relevante porque o site utiliza o componente COM do Windows Media Player para acessar a biblioteca de mídia. Adicionar uma referência a um componente COM é tão fácil quanto adicionar uma referência a um conjunto .NET. Clique no menu do site e selecione Adicionar Referência e, em seguida, clique na guia COM da caixa de diálogo Adicionar Referência.

Cc580614.AddWmpComReference(pt-br,MSDN.10).png
Figura 1: Adicionando uma referência ao componente COM do Windows Media Player

Banco de dados

É necessário o acesso rápido a um subconjunto das informações de biblioteca do WMP. Portanto, é necessário um banco de dados na memória que possua as classes Artista, Álbum, Banco de Dados e Faixa. A classe Banco de Dados possui um construtor estático (ou compartilhado no Visual Basic) que preenche o banco de dados por meio de uma chamada ao método Atualizar:

Private Shared Sub Refresh()
	Dim wmp As WindowsMediaPlayer = New WindowsMediaPlayer
	Dim playlist As IWMPPlaylist = wmp.mediaCollection.getAll()
	Dim artistDictionary As Dictionary(Of String, Artist) = _          New Dictionary(Of String, Artist)

	For i As Integer = 0 To playlist.count - 1
		Dim media As IWMPMedia = playlist.Item(i)
		Dim albumArtistName As String = media.getItemInfo("AlbumArtist")
		Dim albumName As String = media.getItemInfo("Album")
		Dim trackName As String = media.getItemInfo("Title")
		Dim trackLocation As String = media.getItemInfo("SourceUrl")
		Dim trackNumberString As String = media.getItemInfo("OriginalIndex")

		Dim theArtist As Artist
		Dim artistSortName As String = Artist.GetSortName(albumArtistName)

		If Not artistDictionary.TryGetValue(artistSortName, theArtist) Then
			theArtist = New Artist(albumArtistName)
			artistDictionary.Add(artistSortName, theArtist)
		End If

		Dim theAlbum As Album

		If Not theArtist.Albums.TryGetValue(albumName, theAlbum) Then
			theAlbum = New Album(albumName, theArtist)
			theArtist.Albums.Add(albumName, theAlbum)
		End If

		Dim theTrack As Track

		If Not theAlbum.Tracks.TryGetValue(trackName, theTrack) Then
			Dim trackNumber As Integer

			If Integer.TryParse(trackNumberString, trackNumber) Then
				theTrack = New Track(trackNumber, trackName, trackLocation)
			Else
				theTrack = New Track(trackName, trackLocation)
			End If

			theTrack.Album = theAlbum
			theAlbum.Tracks.Add(trackName, theTrack)
		End If
	Next

	ArtistList.AddRange(artistDictionary.Values)
	ArtistList.Sort()
End Sub

A biblioteca do WMP é uma estrutura plana de itens de mídia, de forma que a hierarquia Artista -> Álbuns -> Faixas deve ser criada manualmente durante o preenchimento do banco de dados. O acesso à biblioteca do WMP é simples; chamando o método getAll a partir da propriedade mediaCollection do objeto WindowsMediaPlayer, obtemos uma lista de reprodução de todos os itens da biblioteca, representada pela interface IWMPPlaylist. Coletar as informações necessárias de cada item de mídia (representado pela interface IWMPMedia) também é fácil, por meio de uma chamada ao método getItemInfo do IWMPMedia. Assim que todas as informações relevantes do item de mídia forem coletadas, os objetos artista, álbum e faixa devem ser acessados ou criados.

Observe que usei um objeto Dicionário genérico para armazenar os artistas enquanto repetimos através da lista de reprodução. Algumas coleções de mídia possuem dezenas de milhares de itens, e precisar fazer uma pesquisa "na unha" através da ArtistList para cada item de mídia deixa ainda mais lenta uma operação que já é lenta. (O motivo pelo qual o objeto Dicionário é mais rápido está além do escopo deste artigo. Consulte o exame feito por Scott Mitchell da classe Hashtable, o equivalente não genérico do Dicionário, em um artigo da sua fantástica série sobre estrutura de dados.)

Projetando o site

Para simplificar ao máximo as coisas, criaremos três páginas: Artistas, Álbuns do Artista e Álbum.

Cc580614.ArtistsPage(pt-br,MSDN.10).png
Figura 2: A página Artistas

A página Artistas (Figura 2) mostra dez grupos de artistas por vez. Sempre que se clica em um grupo, ele é exibido, e assim por diante, até que o usuário encontre o artista que está procurando.

Cc580614.AlbumsPage(pt-br,MSDN.10).png
Figura 3: A página Álbuns

A página Álbuns (Figura 3) mostra todos os álbuns, inclusive a capa, de um artista. Clicando no nome do álbum, o usuário vai à página Álbum, e clicando no botão de reprodução, inicia uma lista de reprodução contendo as faixas do álbum no Windows Media Player.

Cc580614.AlbumPage(pt-br,MSDN.10).png
Figura 4: A página Álbum

A página Álbum mostra a capa do álbum em tamanho grande e as faixas. O usuário pode reproduzir um álbum inteiro ou apenas faixas separadas.

Cada página utiliza a ligação de dados de objetos e compartilha uma única página mestra. O ASP.NET possui um novo recurso chamado Páginas Mestras, que permite a criação de um layout uniforme para o site, além de dividir funções sem precisar duplicá-las em cada página. A Página Mestra deste site é usada para compartilhar informações de Folhas de Estilo em Cascata (CSS) e hospedar a "barra de migalhas", ou SiteMapPath, como é chamada no ASP.NET. Não falarei sobre o controle SiteMapPath, mas a implementação deve ser interessante para quem precisa criar uma de maneira programática, e não por meio de uma definição estática. Mais informações sobre páginas mestras podem ser encontradas no artigo Master Your Site Design with Visual Inheritance and Page Templates (em inglês), de Fritz Onion.

As páginas Álbuns e Álbum também possuem imagens e botões de reprodução que iniciam arquivos de listas de reprodução do Windows Media Player. As imagens de Álbum ficam armazenadas em um diretório acessível pela Web, e os arquivos de lista de reprodução do WMP são gerados no momento do uso em um manipulador de HTTP. Esses dois tópicos são abordados a seguir.

Ligando objetos a grades

O novo ObjectDataSource é o intermediário entre a grade e os objetos. Ele deve ser configurado para operar com uma classe para retornar os dados solicitados pela grade. Veremos passo a passo a criação da fonte de dados para a página Álbuns.

Cc580614.ObjectDataSourceWizard1(pt-br,MSDN.10).png
Figura 5: O assistente ObjectDataSource, primeira página

A classe "Binder" é selecionada como o objeto comercial. Os objetos usados para representar os artistas, e outros, são simples e representam principalmente dados. Portanto, eles não se parecem muito com métodos de persistência. A classe Binder possui a funcionalidade que, de outra forma, pode ser manipulada pelos objetos.

Cc580614.ObjectDataSourceWizard2(pt-br,MSDN.10).png
Figura 6: O assistente ObjectDataSource, segunda página

A próxima página é onde escolhemos o método que queremos usar para obter os dados. Portanto, escolhemos GetAlbums. Os métodos de Atualização, Inserção e Exclusão também podem ser especificados, mas não são obrigatórios.

Cc580614.ObjectDataSourceWizard3(pt-br,MSDN.10).png
Figura 7: O assistente ObjectDataSource, terceira página

A última página especifica de onde virão os valores dos parâmetros. O valor pode vir de um dos seguintes locais: Cookie, Control, Form, Profile, QueryString e Session.

Assim que a fonte de dados for configurada, basta defini-la como a DataSource da grade para fazer a "mágica".

Gerando valores personalizados de grade

Todas as grades usam colunas de modelo e códigos personalizados para gerar seus hiperlinks. Por exemplo, a página Artistas (figura 2) consiste em uma única GridView vinculada a uma Lista de RangeListItems genérica. A RangeListItem é uma classe personalizada que representa o primeiro e o último ID de artistas no intervalo, além do texto exibido na grade. Os hiperlinks são criados por meio de um campo de modelo na grade e da chamada de um método na seção de script da página, no lado do servidor.

Cc580614.TemplateFieldColumn(pt-br,MSDN.10).png
Figura 8: A coluna de campo de modelo na página Artistas

O atributo NavigateUrl é preenchido por uma chamada ao método CreateNavigateUrl:

Private Function CreateNavigateUrl(ByVal o As Object) As String
	Dim listItem As RangeListItem = CType(o, RangeListItem)
	
	If (listItem.StartIndex = listItem.EndIndex) Then
		Return "albums.aspx?artist=" & listItem.StartIndex
	Else
		Return String.Format("default.aspx?start={0}&end={1}", _              
			listItem.StartIndex, listItem.EndIndex)
	End If
End Function

Mais informações sobre modelos no ASP.NET 2.0 podem ser encontradas no artigo Move Over DataGrid, There's a New Grid in Town! (em inglês), de Dino Esposito.

Exibindo a capa do álbum

O Windows Media Player baixa capas de álbuns nas versões grande e pequena, sempre que possível. Esses arquivos de imagem ficam armazenados nas mesmas pastas que as faixas do álbum, mas, por padrão, ficam ocultos. Essas imagens devem ser copiadas para uma pasta filha do site para que o navegador possa ter acesso a elas. Isso é feito, primeiramente, localizando a imagem correta e, em seguida, copiando esse arquivo para uma subpasta do site e dando a ela um nome exclusivo para que ela possa ser mais facilmente encontrada da próxima vez em que for solicitada. Se a capa do álbum não for encontrada, será usada uma pequena imagem transparente.

Public Function GetAlbumArtUrl(ByVal size As AlbumArtSize) As String
	Dim albumArtFileName As String = GetCustomAlbumArtFileName(size)
	Dim albumArtFullFilename As String = _          Path.Combine(_albumArtDirectory, albumArtFileName)

	Dim fileExists As Boolean = File.Exists(albumArtFullFilename)

	If Not fileExists Then
		Dim dir As String = Path.GetDirectoryName(Tracks(0).Location)
		Dim filename As String

		If Directory.Exists(dir) Then
			filename = GetRealAlbumArtFileName(dir, size)

			If Not filename Is Nothing Then
				File.Copy(filename, albumArtFullFilename)
				File.SetAttributes(albumArtFullFilename, FileAttributes.Normal)
				fileExists = True
			End If
		End If
	End If

	Dim url As String

	If fileExists Then
		url = "/coding4fun/images/coolapps/musiclib/AlbumArt/" & _              
		albumArtFileName
	Else
		url = "/coding4fun/images/coolapps/musiclib/dot.gif"
	End If

	Return url
End Function

O método GetCustomAlbumArtFileName cria um nome consistente usado para salvar a cópia da imagem e acessá-la mais facilmente em solicitações posteriores:

Private Function GetCustomAlbumArtFileName(ByVal size As AlbumArtSize) As String
	Return String.Format("{0}-{1}-{2}.jpg", _          GetAlphanumericString(_artist.Name), _
	GetAlphanumericString(Name), size.ToString)
End Function

Private Function GetAlphanumericString(ByVal s As String) As String
	Dim sb As StringBuilder = New StringBuilder

	For Each c As Char In s
		If Char.IsLetterOrDigit(c) Then
			sb.Append(c)
		End If
	Next

	Return sb.ToString()
End Function

Para tornar as coisas ainda mais interessantes, os nomes de arquivos das capas dos álbuns podem ter qualquer um dos seguintes padrões:

Grande

AlbumArt_{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}_Large.jpg
AlbumArt__Large.jpg
Folder.jpg

Pequena

AlbumArt_{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}_Small.jpg
AlbumArt__Small.jpg
AlbumArtSmall.jpg

GetRealAlbumArtFileName obtém o nome de arquivo da capa do álbum, se ela existir, procurando os padrões acima:

Private Function GetRealAlbumArtFileName(ByVal dir As String, _      ByVal size As AlbumArtSize) As String
	Dim filename As String = Nothing
	Dim filenames() As String = Directory.GetFiles(dir, "AlbumArt*" & _         size.ToString() & ".jpg")

	If (filenames.Length > 0) Then
		filename = filenames(0)
	ElseIf (size = AlbumArtSize.Large) Then
		Dim file As FileInfo = New FileInfo(Path.Combine(dir, "Folder.jpg"))
		If file.Exists Then
			filename = file.FullName
		End If
	End If

	Return filename
End Function

Criando um Lista de Reprodução

As listas de reprodução são criadas pela classe PlaylistCreator, que é um gerenciador leve de solicitações à Web que implementa a interface IHttpHandler. O gerenciador é bastante simples: ele escreve um cabeçalho, avisando o navegador de que o tipo de conteúdo que devolverá é "video/x-ms-asf", criando e devolvendo, em seguida, uma lista de reprodução em XML. O cabeçalho do tipo de conteúdo ajuda o navegador a determinar o que fazer com o conteúdo, sendo um passo muito importante, pois queremos executar a lista de execução, não exibi-la no navegador.

PlaylistCreator grava diretamente no fluxo de resposta com um XmlTextWriter:

    Public Sub ProcessRequest(ByVal context As HttpContext) _      Implements IHttpHandler.ProcessRequest
        _trackIndex = QueryStringHelper.TrackIndex
        _artistIndex = QueryStringHelper.ArtistIndex
        _albumIndex = QueryStringHelper.AlbumIndex

        context.Response.ContentType = "video/x-ms-asf"

        Dim streamWriter As StreamWriter = _          New StreamWriter(context.Response.OutputStream)
        _writer = New XmlTextWriter(streamWriter)
        _writer.WriteProcessingInstruction("wpl", "version=\""1.0\""")

        _writer.WriteStartElement("smil")

        CreateHead()

        _writer.WriteStartElement("body")
        _writer.WriteStartElement("seq")

        If _trackIndex.HasValue Then
            CreateTrackEntry()
        ElseIf _albumIndex.HasValue Then
            CreateAlbumEntries()
        End If

        _writer.WriteEndElement()
        _writer.WriteEndElement()
        _writer.WriteEndElement()

        _writer.Close()
    End Sub

Registrando o Playlist Creator

ASP.NET sabe como tratar as solicitações a páginas ASPX: ele encontra a página com o mesmo nome e a executa. Os gerenciadores de HTTP são tratados de maneira ligeiramente diferente. Deve ser feita uma inserção no arquivo web.config, associando um nome de arquivo ou padrão ao tipo do objeto que será usado para gerenciar a solicitação. O fragmento de XML abaixo é da seção configuration/system.web do arquivo web.config. Ele instrui o ASP.NET a encaminhar qualquer solicitação cuja extensão seja "wpl" a um objeto do tipo "PlaylistCreator".

<httpHandlers>
	<add path="*.wpl" type="PlaylistCreator" verb="*" validate="false" />
</httpHandlers>

Jeff Key é arquiteto da InRule Technology, líder em tecnologias de mecanismos de regras comerciais para .NET. Jeff passou sua vida profissional evoluindo com a plataforma Microsoft, do fat client ao CGI, MTS/COM+ e ASP, além de todos os diferentes estilos da .NET desde o período beta. Entre em contato com ele pelo seu site.

© .

Mostrar: