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