简介
可以想象,大多数计算机用户都拥有媒体库。随着计算机的价格变得更加便宜、联网更加容易并且更易于使用,人们更有可能创建家庭网络。由于服务器在家庭设置中并不实用,因此信息就遍布于网络中计算机上,这对于管理和查找它们而言就比较困难。这表现在音乐库中可能最明显。
大多数媒体播放器都拥有一个包含它们所知的所有歌曲的专用数据库。在一台计算机上保持这些数据库为最新可能是件小事,但在几台计算机上都保持数据库最新似乎不大可能。
常见的解决方案是,将所有音乐文件放在一台计算机上,让其他计算机的媒体库指向这台计算机上的文件并与之保持一致。所有实际尝试过此方法的人都知道,这个解决方案可以满足要求,但不理想。就我个人而言,我只想听音乐,不想管理它。
我的家庭设置包括一台 Windows Media Center 计算机(它驱动我的电视机并包括了上面的所有音乐)、我的工作站,有时还包括一台便携机。保持工作站上的音乐与“媒体中心”上的音乐一致一直都是个麻烦的问题,而且在便携机上这么做行不通。我需要一种简单、干净的方法来收听来自公寓内任何地方、任何电脑上的音乐,而不必考虑媒体库的准确位置。我选择生成一个小型 Web 站点,它唯一的作用是让我尽可能省事地听我想听的音乐。我使用 Microsoft Visual Web Developer 2005 Express Edition Beta 2 来实现此目的。
COM:一个老朋友
以前有一种称为 COM 的技术,和现在 .NET 的作用很类似,它允许我们在代码中使用任何语言编写的库,无论我们使用的是哪种语言(只要它支持 COM)。COM 在解决某些问题方面做的不错,但也带来了少数其他问题,而且这些问题日益明显。除此之外,.NET 还是许多问题的解决方案。
多年以前,Microsoft 开始通过 COM 公开操作系统和应用程序服务。这样,普通人就可以针对 Microsoft Word、Windows Scripting Host、Windows Media Player 等编写代码。由于很多语言、框架等都支持 COM,因此 COM 仍然是 Microsoft 世界中的主角,而且经常是在 Microsoft 应用程序中先发现 COM 支持,然后才发现 .NET 支持。幸运的是,.NET 能与 COM很好的交流(大部分时间)。
回顾这些历史是有必要的,因为站点使用 Windows Media Player COM 组件访问它的媒体库。添加对 COM 组件的引用和添加对 .NET 程序集的引用一样简单。单击 Web 站点菜单,选择“Add Reference”,然后单击“Add Reference”对话框上的“COM”选项卡。
![](https://i-blog.csdnimg.cn/blog_migrate/e944b0d25c0489dc8dc30440be07286e.png)
图 1:添加对 Windows Media Player COM 组件的引用
数据库
需要快速访问 WMP 库信息的子集,因此需要一个包含 Artist、Album、Database 和 Track 类的内存中数据库。Database 类有一个通过调用 Refresh 方法来填充数据库的静态(或者在 Visual Basic 中共享)构造函数:
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
WMP 库是媒体项目的平面结构,因此必须在填充数据库时手动创建 Artist -> Albums -> Tracks 层次结构。访问 WMP 库很简单;调用 WindowsMediaPlayer 对象 mediaCollection 属性的 getAll 方法会返回库中所有项目的 playlist,由 IWMPPlaylist 接口来表示。从每个媒体项目(由 IWMPMedia 接口表示)收集所需的信息很容易,只需调用 IWMPMedia 的 getItemInfo 方法。收集了媒体项目的所有相关信息后,必须检索或创建艺术家、曲集和曲目。
请注意,在反复播放 playlist 时,我使用通用的 Dictionary 对象来存储艺术家。一些媒体集合包括上万个项目,必须强制搜索 ArtistList 以查找每个媒体项目会进一步降低本就缓慢的操作速度。(Dictionary 对象比较快的原因不在本文讨论范围内。请参见 Scott Mitchell 对 Hashtable 类的考察,该类是 Dictionary 的非通用副本,可以在关于数据结构的神奇系列中的文章中找到。)
设计 Web 站点
要使一切尽可能简单,我们要制作三个页面:艺术家、艺术家的曲集和曲集。
![](https://i-blog.csdnimg.cn/blog_migrate/1f913fa5f955c4c507618b070f8003c7.png)
图 2:艺术家页
艺术家页(图 2)一次显示十组艺术家。每当单击一个组时,即显示该组,直到用户找到要查找的艺术家。
![](https://i-blog.csdnimg.cn/blog_migrate/0c5755eb81a8fb09562767938fbfa7c1.png)
图 3:艺术家的曲集页
艺术家的曲集页(图 3)显示一个艺术家的所有曲集,包括图片。单击曲集名,用户就可以转到曲集页,单击播放按钮将启动 Windows Media Player 中包含该曲集中曲目的 playlist。
![](https://i-blog.csdnimg.cn/blog_migrate/98de4f6090d7df87e0ca949042a36968.png)
图 4:曲集页
曲集页显示曲集大图和曲目。用户可以播放整个曲集或单个曲目。
每页都使用对象数据绑定,并且共享一个通用的母版页。ASP.NET 有一个称为母版页的新增功能,该功能使您可以为 Web 站点创建一致的布局并共享公共功能,而无需为每页复制功能。此 Web 站点中的母版页用于共享级联样式表(Cascading Style Sheet,CSS)信息,并承载“breadcrumb bar”或 SiteMapPath(ASP.NET 中的叫法)。我不打算讨论 SiteMapPath 控件,但对于任何需要以编程方式(而不是静态定义方式)驱动控件的人而言,会对 SiteMapPath 控件的实现感兴趣。有关母版页的更多信息,请参阅 Fritz Onion 的 Master Your Site Design with Visual Inheritance and Page Templates。
艺术家的曲集和曲集页还包括图像以及启动 Windows Media Player playlist 文件的播放按钮。曲集图像在 Web 可访问目录中缓存,而 WMP playlist 文件是在 HTTP 处理程序中动态生成的。下面对这两个主题进行介绍。
将对象绑定到网格
新增的 ObjectDataSource 是网格和对象之间的媒介。必须将它配置为与类一起工作以返回网格请求的数据。我们将逐步创建艺术家曲集页的数据源。
![](https://i-blog.csdnimg.cn/blog_migrate/14d583146e11d535a5a86faf8a2d1783.png)
图 5:ObjectDataSource 向导,第一页
“Binder”类被选为业务对象。用来代表艺术家等的对象很简单,并且主要代表数据,因此在持久性方法方面没有太大困难。Binder 类提供的功能,对象自己也能实现。
![](https://i-blog.csdnimg.cn/blog_migrate/b8a307fc4cfbf4498040d97d7e7b8ac9.png)
图 6:ObjectDataSource 向导,第二页
在下一页,我们选择用来获得数据的方法,因此我们选择 GetAlbums。也可以指定 Update、Insert 和 Delete 方法,但不是必需的。
![](https://i-blog.csdnimg.cn/blog_migrate/9e989a69f23f47c589e84d0f4dd7b3d7.png)
图 7:ObjectDataSource 向导,第三页
最后一页指定参数值的来源。值可以来自于以下任何地方:Cookie、Control、Form、Profile、QueryString 和 Session。
配置数据源后,只需将它设置为网格的 DataSource,魔法就会生效。
生成自定义网格值
所有网格都使用模板列和自定义代码以生成自己的超级链接。例如,艺术家页(图 2)由一个绑定到通用 RangeListItems 列表的 GridView 组成。RangeListItem 是一个自定义类,它代表范围内第一个和最后一个艺术家 ID,以及网格中显示的文本。通过在网格中使用一个模板字段,并在该页的服务器端脚本片断中调用一个方法,可以创建超级链接。
<asp:TemplateField ItemStyle-HorizontalAlign=Center> <ItemTemplate> <asp:HyperLink ID="HyperLink1" runat="server" NavigateURL='<%# CreateNavigateURL(GetDataItem() %>' Text='<%# Eval("Text") %>'></asp:HyperLink> </ItemTemplate> </asp:TemplateField>
通过调用 CreateNavigateUrl 方法填充 NavigateUrl 属性:
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
有关 ASP.NET 2.0 中模板的更多信息,请参阅 Dino Esposito 的 Move Over DataGrid, There's a New Grid in Town!。
显示曲集图片
如果可能,Windows Media Player 会下载曲集图片,包括大小两个版本。这些图像文件存储在曲目所在的文件夹下,但默认情况下是隐藏的。这些图像必须复制到 Web 站点的子文件夹中,以便浏览器可以访问它们。通过以下操作可以实现此目的:先找到正确的图像,然后将该文件复制到 Web 站点的子文件夹中,并用唯一的名称命名它,以便下次需要时可以更方便地检索到该图像。如果找不到曲集图片,则使用一个小型的透明图像。
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
GetCustomAlbumArtFileName 方法创建一个一致的名称,该名称用于存储图像的副本,并且在后续请求中更轻松地检索该副本:
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
了让一切更有趣,曲集图片文件名可以与以下任何模式匹配:
大 | AlbumArt_{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}_Large.jpg |
AlbumArt__Large.jpg | |
Folder.jpg | |
小 | AlbumArt_{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}_Small.jpg |
AlbumArt__Small.jpg | |
AlbumArtSmall.jpg |
GetRealAlbumArtFileName 通过搜索上面的模式来获得曲集图片的文件名(如果存在):
创建 Playlist
Playlist 是用 PlaylistCreator 类创建的,该类是一个实现 IHttpHandler 接口的轻量级 Web 请求处理程序。该处理程序相当简单:它写出一个标题,通知浏览器它要返回的内容类型是“video/x-ms-asf”,然后创建并返回一个 XML playlist。内容类型标题帮助浏览器确定如何处理内容,这是一个非常重要的步骤,因为我们希望在浏览器中播放 playlist 而不是显示它。
PlaylistCreator 通过 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
注册 Playlist 创建者
ASP.NET 知道如何处理对 ASPX 页面的请求:它找到具有相同名称的页面并执行它。对 HTTP 处理程序的处理方法略有不同。必须在 Web.config 文件中记录一个条目,该文件将文件名或模式与要用来处理请求的对象类型相关联。下面的 XML 片段来自于 Web.config 文件的 configuration/system.Web 部分。它指示 ASP.NET 将所有扩展名为“wpl”的请求指向“PlaylistCreator”类型的对象。
<httpHandlers> <add path="*.wpl" type="PlaylistCreator" verb="*" validate="false" /> </httpHandlers>
Jeff Key 是一名 InRule Technology 的架构师,该公司是 .NET 业务规则引擎技术的领导者。Jeff 的职业生涯主要从事发展 Microsoft 平台,从胖客户端到 CGI、MTS/COM+ 和 ASP,以及自测试版以来的各种不同风格的 .NET。可通过他的 Web 站点与他联系。