1、目录1 程序功能描述32 开发环境描述33 开发技术介绍34 详细设计44.1功能模块划分44.2 用户界面设计54.2.1 歌曲列表面板54.2.2 播放控制面板84.2.3 搜索及展示面板84.3 播放功能实现94.3.1播放歌曲94.3.2 暂停及继续播放114.3.3 音量控制124.3.4 播放模式124.3.5 时间进度条134.4 歌词展示实现144.4.1 加载歌词文件144.4.2 解析歌词文件144.4.3 展示歌词154.5 搜索网络歌曲资源164.5.1 获取HTML文本164.5.2 解析HTML文本184.5.3 抓取数据描述204.6 网络歌曲资源处理204.6.
2、1 歌曲资源的载体204.6.2 歌曲资源的操作244.7 程序内置的游戏264.7.1 2048264.7.2 贪吃蛇274.7.3 五子棋275程序运行286 实验小结301 程序功能描述音乐播放器是一种用于播放各种音乐文件的多媒体播放软件。我们以酷狗音乐播放器的操作界面为原型,设计一个实现播放、搜索、下载歌曲的Java音乐播放器。此音乐播放器支持音乐格式较少,只有MID、WMA、MP3。最后,为音乐播放器置入一些游戏,增强播放器的娱乐性。2 开发环境描述IDE:Eclipse(Luna)、netbeansJDK:1.8图片处理:Photoshop3 开发技术介绍1)Java Sound
3、:Java Sound API是Java SE平台提供底层的处理声音接口。使用Java Sound API可以实现各种基于声音的应用,例如声音录制、音乐播放、音乐编辑等。同时其还提供了第三方的扩展接口(SPI),实现各种音乐格式的解码与转码。2)Java Zoom :为了支持MP3的播放,必须为JavaSound扩展MP3的SPI支持库。开源项目JavaZoom正是提供了一个兼容JavaSound的纯Java解码器。引用:jl1.0.1.jar、mp3spi1.9.5.jar、tritonus_share.jar3)Jaudiotagger :开源项目Jaudiotagger提供一个Java类
4、库用于编辑音频文件的tag信息(附有此音频的歌手、标题、专辑、音轨长度等的信息)。引用:jaudiotagger-2.0.3.jar4)Jsoup :Jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API。引用:jsoup-1.8.1.jar5)Substance:Swing自带提供了几种look and feel类,然而要设计一个非常精美的GUI界面,却相当麻烦。使用java substance可以很简单地实现。Substance里面有很多现成的非常漂亮的皮肤。引用:substance.jar4 详细设计4.1功能模块划分按结构
5、化设计方法,划分出四个功能模块:歌曲列表、播放控制、搜索及音乐库。此四个模块正好对应酷狗用户界面的四部分。酷狗音乐播放器如下:Figure 4.1.1 Kugou程序构建的包main:主入口ui、ui.tool:用户界面及其使用的一些工具类song:包含有歌曲、歌词信息的类player:播放相关的类search:搜索相关的类程序结构图如下:Figure 4.1.2 程序结构图4.2 用户界面设计窗体(Frame):窗体初始大小为975*670;内容面板(ContentPane)由播放面板(PlayPanel)、歌曲列表面板(PlayListPanel)、搜索面板(SearchPanel)、展示
6、面板(ShowPanel)构成,内容面板的布局采用的是BoxLayout+Box,PlayListPanel和SearchPanel对应都绑定了一个工具条(ButtonToolBar)程序引用了外包Substance 设计观感4.2.1 歌曲列表面板PlayListPanel由一个工具条(ButtonToolBar extends JToolBar)、JPanel构成,其中JPanel采用CardLayout布局,JPanel加入了3个歌曲列表面板(SongListPanel extends JScrollPanel)、1个应用面板(JScrollPanel)利用工具条的按钮切换显示面板Fig
7、ure 4.2.1 1 歌曲目录歌曲列表面板(SongListPanel)-列表的实现: 利用JTree实现二级目录。顾名思义,JTree是树状元件,它由众多节点构成,其中JTree需要一个根节点(root)。关于节点,我们用可派生节点DefaultMutableTreeNode类(implements TreeNode ) 即这种节点可以做“树干”也可以做“叶子”1)利用一个节点构建一个JTree,该节点为根节点,即一级节点其实我们要实现的歌曲列表是3级节点:根节点、歌曲目录、歌曲文件,这里我们需要隐藏点根节点:tree.setRootVisible(false); 否则我们将看到3层目录2
8、)根节点加入节点歌曲目录节点(rootNode.add(aNode))第2级节点为歌曲目录,可派生歌曲节点:节点(TreeNode)的其中一个构造器是接受Object对象userObjectJTree是以节点的toString方法返回的字符串显示节点,而节点的toString是由UserObjcet的toString决定,所以用String来构建歌曲目录节点即可3)歌曲目录节点应该有一个计算当前歌曲数目并在目录中显示的方法目录节点计算其的子节点数,并更新到目录名4)歌曲目录节点加入歌曲文件节点加入方法与上面一样。这里每个歌曲(File)是一个节点,不可派生子节点上面说到每个节点在JTree显示
9、的字符串,都是由该节点的UserObject.toString()决定,File的toString返回的是该文件的路径,这里我们重写DefaultMutableTreeNode的toString 让它返回歌曲名,所以构造了SongNode类(extends DefaultMutableTreeNode)5)弹出菜单再做下面各种操作前,需要一个使用载体,我们利用弹出菜单来实现Figure 4.2.1 2 弹出菜单6)移除歌曲目录因为用户只能选中第2、3级节点,这里需要判断当前选中的是第几级节点:获取选中的路径TreePath path = tree.getSelectionPath()(如果pa
10、th=null 可以不往下执行),通过path.getPathCount()来判断,第3级节点是返回值是3。如果是第3级节点,需获得它的上级节点(即歌曲目录)获取末端组件DefaultMutableTreeNode node=(DefaultMutableTreeNode) path.getLastPathComponent()获取上级节点DefaultMutableTreeNode aList=node. getParent()如果是第2级节点,可直接获取选中路径的末端组件aList=(DefaultMutableTreeNode)path.getLastPathComponent();这样
11、保证都从歌曲目录节点操作,再按以下次序进行判断:1.如果当前目录是默认目录,不移除,可以通过当前节点在根节点的位置判断root.getIndex(aList)2.如果当前目录含有歌曲,进行提示是否移除,可以获取该目录节点的子节点数目(getChildCount)或者该节点是否为“叶子”(isLeaf)3.如果当前目录播放着歌曲,终止播放,这里后面再叙述。4.最后移除,aList .removeFromParent();7)清空歌曲目录与移除目录类似,最后清空使用的方法aList.removeAllChildren();8)删除歌曲可以参考移除目录的实现,需要注意的是,要保证当前选中的节点是第3
12、级节点,即歌曲文件,是第2级节点就不往下操作,删除方法同样是aSong. removeFromParent()9)获取音频文件通过JFileChooser打开个对话框,获取外部文件,并给其安装过滤器。过滤器过滤出的格式为mid,mp3,wav选择文件分为两种模式,1获取多个音频文件,2获取一个文件夹。需设置JFileChooser的选择模式setFileSelectionMode(int param)关于第2种模式,获取了文件夹后,也要给文件夹进行过滤操作(直接判断或者安装过滤器)这里的过滤器是抽象类 FileFilter,这里定义了一个AFilter(extends FileFilter)最
13、后将这些files加入目录节点即可10)鼠标右击选中当前节点给JTree注册MouseListener.,先判断是否鼠标右击,再取与点击点坐标最近的节点tree.getPathForLocation(int x, int y)应用面板(AppPanel)的实现:应用面板用JScrollPane,加入多个按钮,一个按钮对应着Expandsion包附加的小游戏程序(Expandsion包导出游戏程序后,被删除)以上,歌曲列表面板界面基本实现,注意每次对JTree操作后,请更新JTree的状态,tree.updateUI(),否则有可能出现Bug。在我们实现歌曲播放面板操作功能时,与歌曲列表面板进行
14、交互,会再往列表面板添加功能。4.2.2 播放控制面板PlayPanel由于Java Swing布局复杂,我们可以用Eclipse的WindowBuilder或者netbeans的Matisse可视化构建PlayPanel由以下组件构成标签:歌曲名(songNameLabel)当前播放进度时间(currentTimeCountLabel) 当前播放歌曲总时间(audioTotalTimeLabel)按钮:上一首(backPlay)下一首(frontPlay)播放(Play)静音(voiceControl) 下载(download)标记(mark)分享(share)这里用到的按钮,都用到图片;为
15、简化代码,构建一个IconButton来定义上面的按钮其它:组合框(JComboBox) mode控制播放模式 滑块条(JSlider) voiceAdjust控制音量进度条(TimeProgressBar) 这里用到的进度条timerProgressBar 由于要计时,所以也构建一个TimeProgressBarFigure 4.2.2 2 播放控制面板4.2.3 搜索及展示面板SearchPanel/ShowPanel的布局可以参考PlayListPanel,这里说下折叠面板效果的实现(下图选中的按钮)折叠:SearchPanel/ShowPanel均设不可见,再设置Frame的大小和刷新
16、。展开:给Frame注册窗体状态监听器,如果用户按了最大化按钮,会产生一个事件给监听器,这里判断当前窗体的新状态是否为最大化,然后与折叠操作类似。(也可以不用“最大化”按钮判断,可以设置一个新按钮作事件源) Figure 4.2.3 3 搜索及展示面板4.3 播放功能实现播放面板功能主要由BasicPlayer、HigherPlayer、TimeProgressBar,BasicPlayer实现的是底层操作(播放、暂停、继续播放、终止、获取音频总时间等)。HigherPlayer(extends BasicPlayer)处理面板间的交互,面板与BasicPlayer的交互。TimeProgre
17、ssBar绑定了一个Timer,作计时功能。4.3.1播放歌曲为实现播放功能,我们这里用了Java Sound API,它可以实现各种基于声音的应用。Java Sound API的输入/输出相当于IO流,TargetDataLine/SourceDataLine接口对应输入/输出设备,要得到这个设备的对象,需要设备信息:它是输入还是输出设备,它处理的音频数据格式-即编码格式,不是“WAV/MP3”等文件格式AudioSystem在此过程中起着工厂类的作用这里先关注SourceDataLine如何实现播放功能:BasicPlayer中关于播放的属性private AudioInputStream
18、 audioInputStream;public SourceDataLine sourceDataLine;public URL audio;public Thread playThread;1)获取audio URL回到SongListPanel,在JTree已注册的MouseListener里增加响应mouseClicked的处理,进行判断,选取第3级节点。HigherPlayer在load方法里获取这节点的userObect(即File),再转成URL,这里在HigherPlayer,议保存这个节点,因为节点很容易获取它所在的列表节点。2)从指定的URL获取音频输入流及音频编码格式先获
19、取音频输入流AudioSystem.getAudioInputStream(audio),再获取其音频编码格式audioInputStream.getFormat()Javax.Sound默认支持的编码格式有PCM_SIGNED、PCM_UNSIGNED、ALAW、ULAW,对于这些编码格式我们都不这么了解,只知道WAV与MID采用的是PCM_SIGNED当要播放MP3档时,这里会报异常,因为Javax.Sound并不支持MPEG1L3编码(MP3采用此格式编码)3)将MPEG1L3编码转换成PCM_SIGNED首先要扩展Javax.Sound的解码能力,之后再进行转码。Javax.Sound
20、除了有实现声音处理的API外,同时它以SPI(服务提供接口)为基础,实现各种音乐格式的解码与转码,SPI以插件形式扩展了音频处理的能力,SPI随程序启动而被启动。即我们只要给程序引入MP3的SPI支持库,这里用到的第3方支持库是JavaZoom,获取其中的jl1.0.jar、mp3spil1.9.5.jar、tritonus_share.jar因为程序解码能力扩展,所以上面不再报异常,接着往下转码/ MPEG1L3转PCM_SIGNEDif (audioFormat.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) audioFormat =
21、new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,audioFormat.getSampleRate(), 16,audioFormat.getChannels(),audioFormat.getChannels() * 2,audioFormat.getSampleRate(), false);audioInputStream = AudioSystem.getAudioInputStream(audioFormat,audioInputStream);这里将audioFormat audioInputStream分别转换成PCM_SIGNED,
22、对于此转码方法,我们作了搬运工4)获取输出设备信息及对象/根据上面的音频格式获取输出设备信息DataLine.Info info = new Info(SourceDataLine.class, audioFormat);/获取输出设备对象sourceDataLine = (SourceDataLine) AudioSystem.getLine(info);这里sourceDataLine extends Line5)打开输出数据管道并进行IO操作打开输出管道sourceDataLine.open();允许此管道执行数据 I/O sourceDataLine.start();到这里,我们从输入
23、流读入数据,再将数据写入输出管道(输出管道会先进入混频器,再进入到端口,这里我们不管混频器,只要知道数据输入到扬声器)最后,我们应该设置一个独立线程(playThread)启动播放过程,否则主线程会在播放结束前阻塞。4.3.2 暂停及继续播放为BasicPlayer加入属性:public boolean IsPause = true;/ 是否为暂停状态public boolean NeedContinue;/ 当播放同一首歌曲 是否继续播放状态1)暂停这里IsPause初始为true,是为了后面Play按钮控制使用的上面我们用到了playTread线程启动播放,那么“暂停”只要让此线程进入阻塞
24、状态即可,“继续播放”则是唤醒线程。playTread线程执行过程需要占有锁旗标,只要让它释放此锁旗标就达到暂停效果这个锁旗标(Oject)我们设为BasicPlayer这个对象本身。(其实随便一个对象也可以)记住wait(),notify()要在同步块中使用,synchronized(BasicPlayer.this),否则报异常在音频数据的I/O读写循环中,设置一个控制标记(IsPause),true时,进入暂停:BasicPlayer.this.wait();NeedContinue = true;这里不需要设置IsPause为false,我们play按钮是如下判断,当前状态是暂停,则播
25、放,当前是播放(即不是暂停),则暂停。2)继续播放当用户点击相同的歌曲,或者暂停后,再点击play按钮时,继续播放:方法与暂停播放相对3)终止当前歌曲播放和播放新选中歌曲功能BasicPlayer增加属性:public boolean IsComplete;终止当前播放:即是销毁当前播放线程如果当前歌曲播放完成,即线程run完,线程自动销毁如果当前歌曲播放中,播放新歌曲,我们手工销毁当前播放线程,并初始化IsPause、NeedContinue、IsComplete,之后启动新线程播放新歌曲。我们需要在HigherPlayer里记录当前播放的歌曲与加载的歌曲,判断两者是否是同一首歌曲。如果这里
26、用stop也有可能会产生Bug,不安全,我们想要终止当前线程,不一定要销毁,有点强暴,我们让线程执行完就可以,加入新标记boolean IsEnd,True时,完成这个线程(return)。这样播放新歌曲时,过渡平滑些4)Play按钮状态上面说到的方法:都是用于播放面板Play按钮的功能操作点击Play按钮的5种状态:1.载入歌曲为null时,没什么都不做2.当前没播放歌曲,播放载入歌曲3.当前播放着歌曲,让它暂停4.当前播放歌曲已暂停,如果加载歌曲没变,让它继续播放5.不管当前播放歌曲是否暂停,如果加载歌曲与播放歌曲不同,终止当前播放歌曲线程,播放载入歌曲4.3.3 音量控制输出设备对象so
27、urceDataLine有获取各种控制的方法获取当前输出设备对象的浮点控制器对象,类型为总音量控制sourceDataLine.getControl(FloatControl.Type.MASTER_GAIN);然后通过floatVoiceControl.setValue()/getValue(),设置/获取当前音量(db)音量大概范围为-80F6F 默认为0F通过一个滑块组件(JSlider)设置其值4.3.4 播放模式1)上一首/下一首获取当前歌曲节点的位置(通过其父节点获取的),再指向上一个/下一个,然后加载此歌曲节点,手工触发play按钮2)单曲播放、循环等模式因为是播放完,之后进行播
28、放模式操作,给BasicPlayer增加属性boolean IsComplete,判断当前是否播放完。因为线程播放期间,一直阻塞,所以在线程最后设置IsComplte=true;组合框获取当前模式的值,进行判断。在当前歌曲播完后,这里可以先触发一下play按钮,成暂停状态,设置IsPause为true,等待下次播放,随后进行播放模式操作选择,这个与节点所在的位置有关。4.3.5 时间进度条要想实现进度条,需要得到当前播放歌曲的播放时长,再通过一个计时器,设置进度条的值。1)获取歌曲播放时长每个音频文件都要个文件头记录音频信息(作者、采样率、时长等),引用外包jaudiotagger.jar解析
29、音频,步骤如下:获取音频文件/AudioFileIO是org.jaudiotagger.audio包的类AudioFile file=new AudioFileIO.read(new File(URI);获取音频文件头AudioHeader audioHeader=file.getAudioHeader();获取音频总长度int timelength=audioHeader.getTrackLength();这里的timelength是秒计,之后将timelength转成“0:00”格式即可。需要更新播放面板(PlayPanel)的当前歌曲总时间标签(audioTotalTimeLabel)2
30、)计时及更新进度条这里构建了一个TimeProgressBar(extends JProgressBar),其中绑定了一个计时器Timer当播放歌曲前,更新初始化TimeProgressBar的状态(TimeProgressBar的两个最值为0,timelength),重置Timer,启动Timer。暂停歌曲时,Timer阻塞。继续播放时,Timer唤醒。Timer对应有个线程,执行一次(或定期重复执行)它的任务Task。Timer.schedule(TimerTask task,long delay ,long period)。给Timer安排一个任务,从指定延迟时间(delay)后执行ta
31、sk,之后按间隔时间(period 这里是1s)执行。每次执行Task,更新TimeProgressBar的值和当前播放进度时间标签currentTimeCountLabel,当计时时间等于当前歌曲播放时长时,终止Timer,放弃所有任务,Timer.cancel(),当本次任务会执行完。暂停/继续播放时,可以参考歌曲的处理,来处理Timer,Timer也共享IsPause,最后也是阻塞/唤醒计时线程。4.4 歌词展示实现4.4.1 加载歌词文件加载歌词文件的方式,我们分为两种方式1.在加入歌曲文件时,直接判断歌曲文件所在目录是否有对应的歌词文件,同名判断(隐式加入)2.用户方加入歌词文件(显
32、式加入)这里加入方法为歌曲文件一样,打开文件对话框让用户选择。获取歌词文件数组后,与歌曲文件匹配:为了进行匹配,我们需要记录下当前程序已加载的歌曲(Listsonglist),在程序加入歌曲时,把这些歌曲文件加入到集合即可。删除歌曲时,集合也要将其删除之后对加入的歌词文件进行匹配(同名匹配):利用JDK8的新特性,可以将songlist转成数据流,挑选出满足条件(是否同名)的数据组成子集合,子集合中的元素即是与此歌词匹配的歌曲。4.4.2 解析歌词文件观察下面的歌词文件(lrc),其实lrc是一种遵循特殊规范的文本文件ar:陈奕迅ti:k歌之王00:13.32我唱得不够动人你别皱眉00:19.
33、89我愿意和你约定至死它包含信息有歌手(ar:)、标题(ti:)、专辑(al:)、歌词(时间-歌词键值对)从File lrc读取每一行文本,进行解析。我们定义一个类(LrcInfos)保存解析的信息1.歌手、标题、专辑的解析只要判断该行字符串的头部是否对应为ar:、ti:、al:2.歌词键值对的解析时间键的格式形如00:00.00,为了解析这种格式的文本,我们需要用到正则表达式,对应上面的时间格式的正则表达式为d2:d2.d2,在正则表达式里、.、d为元字符(可以理解为关键字),d为0-9数字,2表示匹配两次,其它为固定匹配,之后将此正则式装载到正则模式类Pattern patternPpil
34、e(regex)让字符串对这个模式进行匹配Matcher matcher = pattern.matcher(line); Matcher是保存已成功匹配子串信息的类:如匹配的子串的始末位置等。判断是否有成功匹配的子串:matcher. find(),获取这个匹配的子串String time = matcher.group()这里的time就是00:00.00格式的字符串这里将这个time解析成Integer,歌词是按秒展示的,这样可以对应前面的进度条,然后获取这个时间子串之后的歌词文本,然后将时间-歌词键值对保存到HashMap中,方便提取数据。4.4.3 展示歌词展示歌词的载体用JText
35、Area,就是把歌词按时间打印到JTextArea这里很自然想到之前的计时进度条,当计时器到一定秒数,要显示歌词信息时,从Map lrcInfosMap提取信息1.初始显示歌词我们要做滚动歌词的效果,可以在计时器初始化时,从LrcInfosMap提取从0秒起的若干条歌词,打印到textArea,做歌词的初始显示。定义printNextLrcInTheTime(int time,int line),定time为0。(为了美观,初始显示歌词时 line为23)2.歌词滚动歌词滚动效果的实现,可以将textArea的第一行歌词剪去,整体上移,显示下一条歌词:剪去第一行歌词,同时整体上移,最后显示下一
36、条3.整体描述当计时器到一定秒数,即要显示歌词时,我们将第一行歌词剪去,整体上移,现第一行歌词即是当前时间对应的歌词(即焦点在第一行),再显示现有的最后一条歌词的下一条。注意:初始显示歌词时,应该从第二行开始。如下图:Figure 4.4.3 4 歌词展示4.5 搜索网络歌曲资源播放歌曲时,是用IO流将本地歌曲数据写入音频设备,那么很自然,同样应该可以用IO流将网络歌曲数据写入音频设备。这先要获取网络歌曲资源链接4.5.1 获取HTML文本1)使用Jsoup抓取HTML中数据1.获取HTML代码为了抓取数据,第一步当然要获取HTML代码,然后分析它。可以利用URL打开要访问地址的一个连接,然后
37、从这个连接对象获取输入流,最后IO流读写,读入的数据就是HTML代码,要注意HTML是用什么字符集编码的,URLConnection connection = new URL(searchUrl).openConnection()connection.getInputStream()利用开源项目JSoup实现这个功能很简单Document document = Jsoup.connect(searchUrl).get();获取HTML文档(代码)2.获取HTML代码中某标签的属性值或文本HTML的标签可以看成一个节点,节点包含下级节点。在Jsoup中获取节点对象,然后从这个节点对象获取它的属性
38、值或者它的下级节点对象,如下:利用Jsoup中的选择器(其中Elements、Element是Jsoup包中的类)获取HTML文文件对象中含有class属性且属性值=number的span节点组:Elements spanNodes=document.select(spanclass=number)获取span节点组的第一个节点对象:Element aNode=spanNodes.first()获取节点对象的属性值,这里是获得该节点中href的绝对路径,去掉abs:得到的是相对路径:String href=aNode.attr(abs:href)获取该节点的文本:String text=aNo
39、de.text();如Hello 获取的是HelloJsoup的API很友好下面,我们将百度音乐作为我们的音乐库,它的HTML是以“utf-8”编码String baseUrl = 忽略:/2)拼接搜索地址,获取搜索结果的HTML文档Figure 4.5.1 5 百度音乐搜索结果列表观察百度音乐搜索的地址,可以发现以下规则“s=1”-是否展开更多的歌曲“key”-key的值为搜索的关键字“start”-start的值为该结果页面第一首歌曲的序号,这个可以用来切换搜索结果页面“size”-size的值为歌曲的数目,size应该为20,曾试过将size设为其它值,该页面还是显示20首歌曲如要搜索“
40、下雨天”搜索结果页是第二页“忽略:/key值由我们searchPanel的文本框获取,注意编码方式,如果key的值不是“utf-8”编码,我们利用URLEncoder将它转换。URLEncoder.encode(string key, string encode);最后用Jsoup获取这个搜索地址的HTML文件(Document searchListDoc)这里,我们可以去爬取百度音乐新歌榜的数据地址:忽略:/构建SearchSong类用于爬取歌曲资料,构建SongInfos类来保存歌曲资料4.5.2 解析HTML文本1)进入搜索结果页面每首歌曲信息页面对应的地址每首歌曲对应的节点是这个节点可
41、以获取这个歌曲信息页面的地址,也可以获取歌曲名等一些信息如忽略:/7319923为歌曲的ID然后获取这个歌曲信息页面的HTML文档,如下图:Figure 4.5.2 6 百度音乐搜索结果列表分析2)在歌曲信息页面中爬取歌手名、专辑名、歌词链接等数据歌曲信息页面Figure 4.5.2 2 百度音乐歌曲信息页面其中两个有用节点,可以获取歌手名、专辑名、歌词链接数据Figure 4.5.2 3 百度音乐歌曲信息页面HTML代码这里说下,获取歌词链接,歌词链接在 节点中,获取这个data-lyricdata的属性值,然后用正则式匹配出后面的链接,正则式可以(/.*.lrc)3)爬取歌曲资源链接歌曲资
42、源链接对于我们很重要。资源链接形式如下:忽略:/先看下百度音乐的下载页面,这个页面的地址很简单拼接,但是百度音乐用JavaScript做了登录函数并加密歌曲资源链接,我们对JavaScript不了解,也不知道怎么实现登录百度的效果和它的加密算法Figure 4.5.2 4 百度音乐下载歌曲页面我们只好从另一个页面爬取到数据忽略:/这是百度音乐的一个老接口,指向的是一个XML档。这里有我们需要的资源链接,分析这个老接口的链接:形式忽略:/song和singer是我们要填入的内容,其它地方都是固定的。下面请注意XML档里,节点,它们下级节点和都有这个资源链接的一部分。Figure 4.5.2 5
43、百度音乐XML这里最好爬取里的链接,因为的链接更有可能指向错误的地方同时这里可以爬取到歌曲文件总字节数(size)和歌曲的比特率(bitRate,注意乘上1000,它的单位是kbps),这两个属性可以算出歌曲的时长(time) time=size*8/bitRate这个计算出的时间不够准确,误差在2-4秒4.5.3 抓取数据描述最后,取到的数据包含歌名、歌手、专辑、歌曲资源链接、歌词链接等,如下图Figure 4.5.3 1 抓取歌曲数据描述4.6 网络歌曲资源处理4.6.1 歌曲资源的载体音乐库,是搜索网络资源的载体。可以使用JTable类来实现,这里构建了LibraryPanel类,来表示
44、音乐库面板。这里用了机器(netbeans的Matisse)来构建音乐库面板, LibraryPanel上面的组件为JTable,下面为JToolBar,如下图:Figure 4.6.1 1 音乐库载体1)JTable的Model数据处理JTable的数据处理等都由Model执行,构建了LibraryTableModel(extends DefaultTableModel)这个Model是由标题栏(歌曲,歌手,专辑,操作),其中前3项为String 不可编辑,操作项是JPanel 可编辑,这些在Matisse都可以设置。1.初始化表格数据先初始化表格数据(Object initData),固定Model的行数为20,因为我们爬取的数据最多20首歌曲。 initDat