MetroMusic音乐播放器开发心得

MetroMusic音乐播放器开发心得

   在这个假期,我独立开发了自己的音乐播放器MetroMusic,之所以叫做MetroMusic,是因为这个播放器的界面采用目前最为流行的win8Metro风格。这个项目起初是为了学习winform的界面设计,比如自定义界面、换肤等功能,后来这个项目越走越远,变成了一个不折不扣的播放器了。

项目名称:MetroMusic

项目类型:WinForm播放器开发

开发环境:VS2010+.net4.0框架

功能:1、单曲、列表播放

      2、顺序、循环、随机三种播放模式切换

      3、同步歌词、自动从网络获取歌词,并和mp3文件同目录(未优化,下一版本具体优化)

      4、喜欢功能

      5、智能推荐,基于机器学习算法的歌曲推荐,基于本地乐库(未优化,下一版本具体优化)

播放器效果截图

好了,言归正传,下面就来讲一讲我在开发这个播放器的过程中遇到的一些问题和心得。这个播放器采用了WMP内核(菜鸟通用,呵呵),首先WMP就是由系统自带的MediaPlayer封装而成的一个组件,可以通过引用的方法加入到我们的项目中。对于WMP而言,我们可以只关心界面问题,然后剩下的就是把界面上的元素和WMP内核关联起来。

一、界面

对于界面,好多人可能都会问到一个透明的问题,尤其是用图片模拟按钮的时候。其实,透明的问题很好解决,以本项目为例,要使得界面上的控制按钮透明于背景图片,只需要:

        BtnPlay.Parent = Back

        BtnPlay.BackColor = Color.Transparent

怎么样,简单吧,事实证明,对于支持BackColor属性的控件都可以这样做。那么那些控件不支持呢,比如List控件、Listview控件等(这就是我的播放器没有播放列表的原因,呵呵,但是这并不影响它实现列表播放)。至于按钮的鼠标动作,我想大家都会,这里就不介绍了。

时间进度条:一般对于这个我们会采用自己写控件的办法,但是本人很菜,还没到写控件的地步,所以本人用的方法很菜。怎么做呢?做两个Panel容器,一个作为背景,一个作为实际的时间条,通过计时器获取当前歌曲进度,然后计算当前时间占总时间的百分比,最后转化为panel的宽度就可以了,不过,缺点是不支持拖动:

If MediaPlayer.playState = WMPPlayState.wmppsPlaying Then

    TimeEnd.Text = MediaPlayer.currentMedia.durationString

TimeStart.Text = MediaPlayer.Ctlcontrols.currentPositionString

Dim nowtime As Double = MediaPlayer.currentMedia.duration

Dim total As Double = MediaPlayer.Ctlcontrols.currentPosition

Dim x As Integer = nowtime

Dim s As Double = total / nowtime

TimeDraw(s)

End If

其中TimeDraw()为绘制时间轴的函数

    Private Sub TimeDraw(ByVal i As Double) '时间轴的绘制

        RealTime.BackColor = Color.White

        RealTime.Width = TimeLine.Width * i

    End Sub

音量拖动条:这个和上面的差不多,缺点是定位比较麻烦,所以采用粗略定位,代码很雷人的,高手勿喷!

'模拟滑动条来调节声音

Private Sub VolumeBack_MouseUp()

If Volume.Width < e.X Then

Volume.Width = Volume.Width + 10

 MediaPlayer.settings.volume = 100 * (Volume.Width / VolumeBack.Width)

End If

End Sub

Private Sub Volume_MouseUp()

If  Volume.Width > e.X Then

    Volume.Width = Volume.Width - 10

    MediaPlayer.settings.volume = 100 * (Volume.Width / VolumeBack.Width)

End If

End Sub

二、按钮控制

提到按钮控制,即如何让界面和WMP关联的问题。这里呢,WMP已经封装好了大部分的功能。假定将AxWindowsMediaPlayer命名为MediaPlayer,那么“播放”、“暂停”、“上一曲”、“下一曲”的代码实现分别为:

MediaPlayer.Ctlcontrols.play()

MediaPlayer.Ctlcontrols.pause()

MediaPlayer.Ctlcontrols.previous()

MediaPlayer.Ctlcontrols.next()

   这里需要说明的是,“上一曲”、“下一曲”的功能,如果直接使用WMP内核的播放列表的话,可以直接使用上面的代码,如果采取其他方式,比如用列表的方法显示播放列表,此时,你需要自己写两个事件来处理他们,播放列表的问题,我在后面会讲到。

   三、歌曲信息

   对于一首歌而言,在某些情况下,我们可能需要获取歌曲的名称、歌手、专辑、歌曲长度、歌曲位置等信息,这个时候,我们就可以使用以下代码轻松实现上述功能,但是这里要注意,只有当播放器处于播放的状态时才可以获取。

歌曲名:MediaPlayer.currentMedia.getItemInfo("Title")

歌手名:MediaPlayer.currentMedia.getItemInfo("Author")

专辑名:MediaPlayer.currentMedia.getItemInfo("Album")

      类似地我们可以实现更多的功能,但是对于一个播放器而言这些已经足够了(显示歌曲名、歌手名、专辑)。此外,本人曾经研究过豆瓣音乐、百度音乐的API。这里便将我自己写的程序送给大家吧,做在线音乐会用得着的。

豆瓣音乐:

http://api.douban.com/music/歌曲名?

返回一个标准的xml文件,对其进行解析即可。

百度音乐:

http://box.zhangmen.baidu.com/x?op=12&count=1&title=歌曲名?/歌手名?

返回一个含有<![CDATA[]]〉的非标准xml文件,其解析程序如下:

    Dim count As Integer

    Public function  getUrl(ByVal str As String)

        Dim myRequest  As HttpWebRequest = Nothing

        Dim myHttpResponse As HttpWebResponse = Nothing

        Dim doubanurl As String = "http://box.zhangmen.baidu.com/x?op=12&count=1&title="

        doubanurl = doubanurl + str + "$$$$$$"

        myRequest = WebRequest.Create(doubanurl)

        myHttpResponse = myRequest.GetResponse()

        Dim reader As StreamReader = New StreamReader(myHttpResponse.GetResponseStream())

        Dim xmldetail As String = reader.ReadToEnd()

        reader.Close()

        myHttpResponse.Close()

        '以下为歌曲搜索部分

        Dim all As String = xmldetail

        Label5.Text = "正在为您搜索,请稍后...."

        Dim countStart As Integer = InStr(all, "<count>") + Len("<count>")

        Dim countEnd As Integer = InStr(all, "</count>")

        count = Val(Mid(all, countStart, countEnd - countEnd + 1)) '获取返回歌曲结果的数目

        '开始音乐搜索

        Dim encodeStart As Integer

        Dim encodeEnd As Integer

        Dim decodeStart As Integer

        Dim decodeEnd As Integer

        Dim url1 As String

        Dim url2 As String

        encodeStart = 1 : encodeEnd = 1 : decodeStart = 1 : decodeEnd = 1

        Dim i As Integer = 0 '设置记录变量i,用于统计数据

        '反复读取寻找歌曲链接

        Do

            Dim s1 As Integer = encodeStart

            Dim e1 As Integer = encodeEnd

            Dim s2 As Integer = decodeStart

            Dim e2 As Integer = decodeEnd

            encodeStart = InStr(s1, all, "<encode><![CDATA[") + Len("<encode><![CDATA[")

            encodeEnd = InStr(e1 + Len("]]></encode>"), all, "]]></encode>")

            url1 = Mid(all, encodeStart, encodeEnd - encodeStart + 1)

            decodeStart = InStr(s2, all, "<decode><![CDATA[") + Len("<decode><![CDATA[")

            decodeEnd = InStr(e2 + Len("]]></decode>"), all, "]]></decode>")

            url2 = Mid(all, decodeStart, decodeEnd - decodeStart + 1)

            ListBox1.Items.Add(Trim(url1) & Trim(url2))

            i = i + 1

        Loop Until i > count - 1

        '音乐链接搜索结束   //这里的情况比较复杂,暂时搞不定

        Dim lrcStart As Integer

        Dim lrcEnd As Integer

        Dim lrc1 As Integer

        Dim lrc2 As Integer

        'lrcStart = 1 : lrcEnd = 1 : i = 0

        ''以下为歌词搜索部分(

        Do

          Dim l1 As Integer = lrcStart

          Dim l2 As Integer = lrcEnd

          lrcStart = InStr(l1, all, "<lrcid>") + Len("<lrcid>")

          lrcEnd = InStr(l2 + Len("</lrcid>"), all, "</lrcid>")

          lrc1 = Val(Mid(all, lrcStart, lrcEnd - lrcStart + 1)) '转化为数字

          lrc2 = Int(lrc1 / 100)

          ListBox1.Items.Add("http://box.zhangmen.baidu.com/bdlrc/" & str(lrc2) & "/" & lrc1 & ".lrc")

          i = i + 1

          loop Until i > count - 1

          歌词搜索部分结束

        Return "已为您找到" & count & "首相关音乐"

End Function

这段程序写得很麻烦,因为里面最关键的就是循环读取的问题,不知道大家对于这种XML文件会采用什么方法解析,如果有更好的方法,希望大家可以告诉我。

四、播放列表

这个可能是播放器开发中比较重要的一部分了,通常的方式是使用List控件或者Listview控件加载歌曲列表,并通过一些事件来驱动程序运行的方式(list+动态数组)。其次,就是很多都不知道,但是事实上已经封装在WMP里的播放列表。但是这两种方式都没有提供播放列表存储的方案,因此在这里提一下我自己的方案。我的方案是建立这样一个xml文件

<?xml version="1.0" encoding="gb2312"?>

<playlist>

  <song>

    <name>降温</name>

    <url>E:\音乐资料\许嵩\降温.mp3</url>

  </song>

  <song>

    <name>幻听</name>

    <url>E:\音乐资料\许嵩\幻听.mp3</url>

  </song>

  <song>

    <name>拆东墙</name>

    <url>E:\音乐资料\许嵩\拆东墙.mp3</url>

  </song>

  <song>

    <name>伴虎</name>

    <url>E:\音乐资料\许嵩\伴虎.mp3</url>

  </song>

  <song>

    <name>拆东墙</name>

    <url>E:\音乐资料\许嵩\拆东墙.mp3</url>

  </song>

       然后剩下的问题就是一个读写xml文件的过程了,但是对于XML而言,添加歌曲很方便,但是对于删除来说去有着很多的不便,因此本项目实际开发过程中并没有采用第一种方法,而是将List控件或者Listview控件做的列表省去,这样的话,我们只关心列表的读取而不必关心其他的问题。

   (1)、列表的读取

Dim fi As new FileInfo(Application.StartupPath &

"\Playlist\playlist.xml") '判断是否存在播放列表文件playlist.xml

If fi.Exists() = True Then

Dim xmldoc As New XmlDocument

xmldoc.Load(Application.StartupPath & "\Playlist\playlist.xml")

Dim nodelist As XmlNodeList

Dim root As XmlElement = xmldoc.DocumentElement

nodelist = root.SelectNodes("/playlist/song/url")

Dim node As XmlNode = Nothing

MediaList = MediaPlayer.playlistCollection.newPlaylist("MediaList")

For Each node In nodelist

MediaList.appendItem(MediaPlayer.newMedia(node.InnerText)) '给播放列表里增加歌曲

Next

End If

     这里需要引入WMPLib,即Imports WMPLib,然后定义MediaList为播放列表,即 Dim MediaList As WMPLib.IWMPPlaylist,这样的话,我们就可以把歌曲添加到这样一个看不见的列表当中了。你可能会问,这个播放列表怎样和播放器关联起来呢?很简单,MediaPlayer.currentPlaylist = MediaList,这样,播放器就可以按照这个列表进行播放了,并且是可以直接使用上一曲、下一曲按钮的哦。那么增加歌曲呢,同样简单:

   (2)、增加歌曲到列表

Dim xmldoc As New XmlDocument

xmldoc.Load(Application.StartupPath & "\playlist\playlist.xml")

Dim node As XmlNode = xmldoc.CreateNode(Xml.XmlNodeType.Element, "song", "")

xmldoc.DocumentElement.AppendChild(node)

Dim node1 As XmlNode = xmldoc.CreateNode(Xml.XmlNodeType.Element, "name", "")

node1.InnerText = MediaPlayer.currentMedia.getItemInfo("Title")

node.AppendChild(node1)

Dim node2 As XmlNode = xmldoc.CreateNode(Xml.XmlNodeType.Element, "url", "")

node2.InnerText = OpenDialog.FileName

node.AppendChild(node2)

xmldoc.Save(Application.StartupPath & "\playlist\playlist.xml")

(3)Bug的解决方案

使用这种方法会有好多Bug,我觉得是WMP库的问题,比如,如果为播放器指定播放列表中的某一项,那么播放列表就会失效,上一曲、下一曲按钮无效;如果使用MediaPlayer.currentMedia.getItemInfo("Title")来获取播放列表中正在播放的歌曲的信息,会得到空集;如果使用播放列表的话,将无法直接列表中获得歌曲的地址(url);这些问题我是怎样解决的呢?第一个,指定列表后,默认从第一个播放,无须指定某一项;第二个,获取当前歌曲在列表中的索引,通过查询XML文件获取当前歌曲得信息;第三个和第二个的方法一样。

五、同步歌词

同步歌词是一个比较重要的功能,它的实现呢,原理很简单,就是拿播放器当前的时间进度和歌词文件中的[]标签内时间进行匹配。原理很简单,但是我在开发的过程中还是遇到了很多的问题,现在我把我的教训总结在下面:

第一、歌词的读取和分析分开。因为歌词的读取不需要循环,而歌词的分析是需要循环的,这样效率跟高。

第二、歌词的分析需要放在定时器里,最好是和时间进度放一块儿。

第三、虽然在写时间进度时用到了相关的参数,比如:MediaPlayer.Ctlcontrols.currentPosition,但是这个一定要写在分析的过程中。

   最后,送上我自己写的一个类:

Imports System.IO

Imports Microsoft.VisualBasic

Public Class lrcshow

    Dim lrclist As ArrayList

    Dim lrcTimeLabel As String

    Dim lrc As String

    Public Function OpenLrcFile(ByVal MusicURL As String)

        Dim LrcUrl As String = MusicURL.Replace(".mp3", ".lrc")

        Dim fi As New FileInfo(LrcUrl)

        lrclist = New ArrayList

        lrclist.Clear()

        If fi.Exists() = True Then

            Dim sr As New StreamReader(LrcUrl, System.Text.Encoding.GetEncoding("gb2312"))

            Dim a As Integer = 0

            While sr.Peek() > -1

                If sr.ReadLine() <> "" Then

                    lrclist.Add(sr.ReadLine())

                End If

            End While

            sr.Close()

        End If

        Return Nothing

    End Function

    Public Function show()

        If MetroMusic.mainform.MediaPlayer.playState = WMPLib.WMPPlayState.wmppsMediaEnded Then

            lrclist.Clear()

        End If

        Dim ct As String, MTime As String, i As Integer, k As Integer, j As Integer, Find As String, PosT As Double, ss As String, mm As String, b As String

        PosT = MetroMusic.mainform.MediaPlayer.Ctlcontrols.currentPosition

        ct = Format(PosT, "0000.00")

        ss = Format((Strings.Left(ct, 4) Mod 60), "00")

        mm = Format((Strings.Left(ct, 4) \ 60), "00")

        MTime = mm & ":" & ss

        For i = 0 To lrclist.Count - 1

            If InStr(lrclist(i), Strings.Left(MTime, 5)) <> 0 Then

                b = lrclist(i)

                j = Len(b)

                For k = 1 To j

                    Find = Strings.Right(b, k)

                    If InStr(Find, "]") <> 0 Then

                        lrc = Strings.Right(b, k - 1) : Exit For

                    End If

                Next

            End If

        Next i

        Return lrc

    End Function

End Class

   使用时,很好用,先用OpenLrcFile()方法加载一个歌词文件,然后用show()方法显示歌词就可以了,还有,这是桌面歌词的实现方法。缺点是貌似有两秒的延迟,这个问题就交给大家帮我改啦,哈哈!                                                      

                                                                                                                                                                                                                                             秦元培

                                                                                                                                                                                                                                      2013216

MetroMusic音乐播放器开发心得MetroMusic音乐播放器开发心得

MetroMusic音乐播放器开发心得MetroMusic音乐播放器开发心得

 

你可能感兴趣的:(metro)