Java课程设计音乐播放器设计.docx
《Java课程设计音乐播放器设计.docx》由会员分享,可在线阅读,更多相关《Java课程设计音乐播放器设计.docx(36页珍藏版)》请在冰豆网上搜索。
Java课程设计音乐播放器设计
程序设计课程设计
综合实验
音乐播放器
班级:
指导老师:
组员:
2014年12月2日
1程序功能描述
音乐播放器是一种用于播放各种音乐文件的多媒体播放软件。
我们以酷狗音乐播放器的操作界面为原型,设计一个实现播放、搜索、下载歌曲的Java音乐播放器。
此音乐播放器支持音乐格式较少,只有MID、WMA、MP3。
最后,为音乐播放器置入一些游戏,增强播放器的娱乐性。
2开发环境描述
IDE:
Eclipse(Luna)、netbeans
JDK:
1.8
图片处理:
Photoshop
3开发技术介绍
1)JavaSound:
JavaSoundAPI是JavaSE平台提供底层的处理声音接口。
使用JavaSoundAPI可以实现各种基于声音的应用,例如声音录制、音乐播放、音乐编辑等。
同时其还提供了第三方的扩展接口(SPI),实现各种音乐格式的解码与转码。
2)JavaZoom:
为了支持MP3的播放,必须为JavaSound扩展MP3的SPI支持库。
开源项目JavaZoom正是提供了一个兼容JavaSound的纯Java解码器。
引用:
jl1.0.1.jar、mp3spi1.9.5.jar、tritonus_share.jar
3)Jaudiotagger:
开源项目Jaudiotagger提供一个Java类库用于编辑音频文件的tag信息(附有此音频的歌手、标题、专辑、音轨长度等的信息)。
引用:
jaudiotagger-2.0.3.jar
4)Jsoup:
Jsoup是一款Java的HTML解析器,可直接解析某个URL地址、HTML文本内容。
它提供了一套非常省力的API。
引用:
jsoup-1.8.1.jar
5)Substance:
Swing自带提供了几种lookandfeel类,然而要设计一个非常精美的GUI界面,却相当麻烦。
使用javasubstance可以很简单地实现。
Substance里面有很多现成的非常漂亮的皮肤。
引用:
substance.jar
4详细设计
4.1功能模块划分
按结构化设计方法,划分出四个功能模块:
歌曲列表、播放控制、搜索及音乐库。
此四个模块正好对应酷狗用户界面的四部分。
酷狗音乐播放器如下:
Figure4.1.1Kugou
程序构建的包
main:
主入口
ui、ui.tool:
用户界面及其使用的一些工具类
song:
包含有歌曲、歌词信息的类
player:
播放相关的类
search:
搜索相关的类
程序结构图如下:
Figure4.1.2程序结构图
4.2用户界面设计
窗体(Frame):
窗体初始大小为975*670;内容面板(ContentPane)由播放面板(PlayPanel)、歌曲列表面板(PlayListPanel)、搜索面板(SearchPanel)、展示面板(ShowPanel)构成,内容面板的布局采用的是BoxLayout+Box,PlayListPanel和SearchPanel对应都绑定了一个工具条(ButtonToolBar)
程序引用了外包Substance设计观感
4.2.1歌曲列表面板
PlayListPanel由一个工具条(ButtonToolBarextendsJToolBar)、JPanel构成,其中JPanel采用CardLayout布局,JPanel加入了3个歌曲列表面板(SongListPanelextendsJScrollPanel)、1个应用面板(JScrollPanel)利用工具条的按钮切换显示面板
Figure4.2.11歌曲目录
歌曲列表面板(SongListPanel)-列表的实现:
利用JTree实现二级目录。
顾名思义,JTree是树状元件,它由众多节点构成,其中JTree需要一个根节点(root)。
关于节点,我们用可派生节点DefaultMutableTreeNode类(implementsTreeNode)即这种节点可以做“树干”也可以做“叶子”
1)利用一个节点构建一个JTree,该节点为根节点,即一级节点
其实我们要实现的歌曲列表是3级节点:
根节点、歌曲目录、歌曲文件,这里我们需要隐藏点根节点:
tree.setRootVisible(false);否则我们将看到3层目录
2)根节点加入节点歌曲目录节点(rootNode.add(aNode))
第2级节点为歌曲目录,可派生歌曲节点:
节点(TreeNode)的其中一个构造器是接受Object对象userObject
JTree是以节点的toString方法返回的字符串显示节点,而节点的toString是由UserObjcet的toString决定,所以用String来构建歌曲目录节点即可
3)歌曲目录节点应该有一个计算当前歌曲数目并在目录中显示的方法
目录节点计算其的子节点数,并更新到目录名
4)歌曲目录节点加入歌曲文件节点
加入方法与上面一样。
这里每个歌曲(File)是一个节点,不可派生子节点
上面说到每个节点在JTree显示的字符串,都是由该节点的UserObject.toString()决定,File的toString返回的是该文件的路径,
这里我们重写DefaultMutableTreeNode的toString让它返回歌曲名,所以构造了SongNode类(extendsDefaultMutableTreeNode)
5)弹出菜单
再做下面各种操作前,需要一个使用载体,我们利用弹出菜单来实现
Figure4.2.12弹出菜单
6)移除歌曲目录
因为用户只能选中第2、3级节点,这里需要判断当前选中的是第几级节点:
获取选中的路径TreePathpath=tree.getSelectionPath()(如果path==null可以不往下执行),通过path.getPathCount()来判断,第3级节点是返回值是3。
如果是第3级节点,需获得它的上级节点(即歌曲目录)
获取末端组件DefaultMutableTreeNodenode=(DefaultMutableTreeNode)path.getLastPathComponent()
获取上级节点DefaultMutableTreeNodeaList=node.getParent()
如果是第2级节点,可直接获取选中路径的末端组件
aList=(DefaultMutableTreeNode)path.getLastPathComponent();
这样保证都从歌曲目录节点操作,
再按以下次序进行判断:
1.如果当前目录是默认目录,不移除,可以通过当前节点在根节点的位置判断root.getIndex(aList)
2.如果当前目录含有歌曲,进行提示是否移除,可以获取该目录节点的子节点数目(getChildCount)或者该节点是否为“叶子”(isLeaf)
3.如果当前目录播放着歌曲,终止播放,这里后面再叙述。
4.最后移除,aList.removeFromParent();
7)清空歌曲目录
与移除目录类似,最后清空使用的方法aList.removeAllChildren();
8)删除歌曲
可以参考移除目录的实现,需要注意的是,要保证当前选中的节点是第3级节点,即歌曲文件,是第2级节点就不往下操作,删除方法同样是aSong.removeFromParent()
9)获取音频文件
通过JFileChooser打开个对话框,获取外部文件,并给其安装过滤器。
过滤器过滤出的格式为mid,mp3,wav
选择文件分为两种模式,1获取多个音频文件,2获取一个文件夹。
需设置JFileChooser的选择模式setFileSelectionMode(intparam)
关于第2种模式,获取了文件夹后,也要给文件夹进行过滤操作(直接判断或者安装过滤器)这里的过滤器是抽象类FileFilter,这里定义了一个AFilter(extendsFileFilter)
最后将这些files加入目录节点即可
10)鼠标右击选中当前节点
给JTree注册MouseListener.,先判断是否鼠标右击,再取与点击点坐标最近的节点tree.getPathForLocation(intx,inty)
应用面板(AppPanel)的实现:
应用面板用JScrollPane,加入多个按钮,一个按钮对应着Expandsion包附加的小游戏程序(Expandsion包导出游戏程序后,被删除)
以上,歌曲列表面板界面基本实现,注意每次对JTree操作后,请更新JTree的状态,tree.updateUI(),否则有可能出现Bug。
在我们实现歌曲播放面板操作功能时,与歌曲列表面板进行交互,会再往列表面板添加功能。
4.2.2播放控制面板
PlayPanel由于JavaSwing布局复杂,我们可以用Eclipse的WindowBuilder或者netbeans的Matisse可视化构建
PlayPanel由以下组件构成
标签:
歌曲名(songNameLabel)当前播放进度时间(currentTimeCountLabel)
当前播放歌曲总时间(audioTotalTimeLabel)
按钮:
上一首(backPlay)下一首(frontPlay)播放(Play)静音(voiceControl)
下载(download)标记(mark)分享(share)
这里用到的按钮,都用到图片;为简化代码,构建一个IconButton来定义上面的按钮
其它:
组合框(JComboBox)mode控制播放模式
滑块条(JSlider)voiceAdjust控制音量
进度条(TimeProgressBar)这里用到的进度条timerProgressBar由于要计时,所以也构建一个TimeProgressBar
Figure4.2.21播放控制面板
4.2.3搜索及展示面板
SearchPanel/ShowPanel的布局可以参考PlayListPanel,这里说下折叠面板效果的实现(下图选中的按钮)
折叠:
SearchPanel/ShowPanel均设不可见,再设置Frame的大小和刷新。
展开:
给Frame注册窗体状态监听器,如果用户按了最大化按钮,会产生一个事件给监听器,这里判断当前窗体的新状态是否为最大化,然后与折叠操作类似。
(也可以不用“最大化”按钮判断,可以设置一个新按钮作事件源)
Figure4.2.31搜索及展示面板
4.3播放功能实现
播放面板功能主要由BasicPlayer、HigherPlayer、TimeProgressBar,BasicPlayer实现的是底层操作(播放、暂停、继续播放、终止、获取音频总时间等)。
HigherPlayer(extendsBasicPlayer)处理面板间的交互,面板与BasicPlayer的交互。
TimeProgressBar绑定了一个Timer,作计时功能。
4.3.1播放歌曲
为实现播放功能,我们这里用了JavaSoundAPI,它可以实现各种基于声音的应用。
JavaSoundAPI的输入/输出相当于IO流,TargetDataLine/SourceDataLine接口对应输入/输出设备,要得到这个设备的对象,需要设备信息:
它是输入还是输出设备,它处理的音频数据格式-即编码格式,不是“WAV/MP3”等文件格式
AudioSystem在此过程中起着工厂类的作用
这里先关注SourceDataLine如何实现播放功能:
BasicPlayer中关于播放的属性
privateAudioInputStreamaudioInputStream;
publicSourceDataLinesourceDataLine;
publicURLaudio;
publicThreadplayThread;
1)获取audioURL
回到SongListPanel,在JTree已注册的MouseListener里增加响应mouseClicked的处理,进行判断,选取第3级节点。
HigherPlayer在load方法里获取这节点的userObect(即File),再转成URL,这里在HigherPlayer,议保存这个节点,因为节点很容易获取它所在的列表节点。
2)从指定的URL获取音频输入流及音频编码格式
先获取音频输入流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除了有实现声音处理的API外,同时它以SPI(服务提供接口)为基础,实现各种音乐格式的解码与转码,SPI以插件形式扩展了音频处理的能力,SPI随程序启动而被启动。
即我们只要给程序引入MP3的SPI支持库,这里用到的第3方支持库是JavaZoom,
获取其中的jl1.0.jar、mp3spil1.9.5.jar、tritonus_share.jar
因为程序解码能力扩展,所以上面不再报异常,接着往下转码
//MPEG1L3转PCM_SIGNED
if(audioFormat.getEncoding()!
=AudioFormat.Encoding.PCM_SIGNED){
audioFormat=newAudioFormat(AudioFormat.Encoding.PCM_SIGNED,
audioFormat.getSampleRate(),16,
audioFormat.getChannels(),
audioFormat.getChannels()*2,
audioFormat.getSampleRate(),false);
audioInputStream=AudioSystem.getAudioInputStream(audioFormat,
audioInputStream);
}
这里将audioFormataudioInputStream分别转换成PCM_SIGNED,对于此转码方法,我们作了搬运工
4)获取输出设备信息及对象
//根据上面的音频格式获取输出设备信息
DataLine.Infoinfo=newInfo(SourceDataLine.class,audioFormat);
//获取输出设备对象
sourceDataLine=(SourceDataLine)AudioSystem.getLine(info);
这里sourceDataLineextendsLine
5)打开输出数据管道并进行IO操作
打开输出管道sourceDataLine.open();
允许此管道执行数据I/OsourceDataLine.start();
到这里,我们从输入流读入数据,再将数据写入输出管道(输出管道会先进入混频器,再进入到端口,这里我们不管混频器,只要知道数据输入到扬声器)
最后,我们应该设置一个独立线程(playThread)启动播放过程,否则主线程会在播放结束前阻塞。
4.3.2暂停及继续播放
为BasicPlayer加入属性:
publicbooleanIsPause=true;//是否为暂停状态
publicbooleanNeedContinue;//当播放同一首歌曲是否继续播放状态
1)暂停
这里IsPause初始为true,是为了后面Play按钮控制使用的
上面我们用到了playTread线程启动播放,那么“暂停”只要让此线程进入阻塞状态即可,“继续播放”则是唤醒线程。
playTread线程执行过程需要占有锁旗标,只要让它释放此锁旗标就达到暂停效果
这个锁旗标(Oject)我们设为BasicPlayer这个对象本身。
(其实随便一个对象也可以)
记住wait(),notify()要在同步块中使用,synchronized(BasicPlayer.this){},否则报异常
在音频数据的I/O读写循环中,设置一个控制标记(IsPause),true时,进入暂停:
BasicPlayer.this.wait();NeedContinue=true;
这里不需要设置IsPause为false,我们play按钮是如下判断,当前状态是暂停,则播放,当前是播放(即不是暂停),则暂停。
2)继续播放
当用户点击相同的歌曲,或者暂停后,再点击play按钮时,继续播放:
方法与暂停播放相对
3)终止当前歌曲播放和播放新选中歌曲功能
BasicPlayer增加属性:
publicbooleanIsComplete;
终止当前播放:
即是销毁当前播放线程
如果当前歌曲播放完成,即线程run完,线程自动销毁
如果当前歌曲播放中,播放新歌曲,我们手工销毁当前播放线程,并初始化IsPause、NeedContinue、IsComplete,之后启动新线程播放新歌曲。
我们需要在HigherPlayer里记录当前播放的歌曲与加载的歌曲,判断两者是否是同一首歌曲。
如果这里用stop也有可能会产生Bug,不安全,我们想要终止当前线程,不一定要销毁,有点强暴,我们让线程执行完就可以,加入新标记booleanIsEnd,True时,完成这个线程(return)。
这样播放新歌曲时,过渡平滑些
4)Play按钮状态
上面说到的方法:
都是用于播放面板Play按钮的功能操作
点击Play按钮的5种状态:
1.载入歌曲为null时,没什么都不做
2.当前没播放歌曲,播放载入歌曲
3.当前播放着歌曲,让它暂停
4.当前播放歌曲已暂停,如果加载歌曲没变,让它继续播放
5.不管当前播放歌曲是否暂停,如果加载歌曲与播放歌曲不同,终止当前播放歌曲线程,播放载入歌曲
4.3.3音量控制
输出设备对象sourceDataLine有获取各种控制的方法
获取当前输出设备对象的浮点控制器对象,类型为总音量控制sourceDataLine.getControl(FloatControl.Type.MASTER_GAIN);
然后通过floatVoiceControl.setValue()/getValue(),设置/获取当前音量(db)音量大概范围为-80F~6F默认为0F
通过一个滑块组件(JSlider)设置其值
4.3.4播放模式
1)上一首/下一首
获取当前歌曲节点的位置(通过其父节点获取的),再指向上一个/下一个,然后加载此歌曲节点,手工触发play按钮
2)单曲播放、循环等模式
因为是播放完,之后进行播放模式操作,给BasicPlayer增加属性booleanIsComplete,判断当前是否播放完。
因为线程播放期间,一直阻塞,所以在线程最后设置IsComplte=true;
组合框获取当前模式的值,进行判断。
在当前歌曲播完后,这里可以先触发一下play按钮,成暂停状态,设置IsPause为true,等待下次播放,随后进行播放模式操作选择,这个与节点所在的位置有关。
4.3.5时间进度条
要想实现进度条,需要得到当前播放歌曲的播放时长,再通过一个计时器,设置进度条的值。
1)获取歌曲播放时长
每个音频文件都要个文件头记录音频信息(作者、采样率、时长等),引用外包jaudiotagger.jar解析音频,步骤如下:
获取音频文件
//AudioFileIO是org.jaudiotagger.audio包的类
AudioFilefile=newAudioFileIO.read(newFile(URI));
获取音频文件头
AudioHeaderaudioHeader=file.getAudioHeader();
获取音频总长度
inttimelength=audioHeader.getTrackLength();
这里的timelength是秒计,之后将timelength转成“0:
00”格式即可。
需要更新播放面板(PlayPanel)的当前歌曲总时间标签(audioTotalTimeLabel)
2)计时及更新进度条
这里构建了一个TimeProgressBar(extendsJProgressBar),其中绑定了一个计时器Timer
当播放歌曲前,更新初始化TimeProgressBar的状态(TimeProgressBar的两个最值为0,timelength),重置Timer,启动Timer。
暂停歌曲时,Timer阻塞。
继续播放时,Timer唤醒。
Timer对应有个线程,执行一次(或定期重复执行)它的任务Task。
Timer.schedule(TimerTasktask,longdelay,longperiod)。
给Timer安排一个任务,从指定延迟时间(delay)后执行task,之后按间隔时间(period这里是1s)执行。
每次执行Task,更新TimeProgressBar的值和当前播放进度时间标签currentTimeCountLabel,当计时时间等于当前歌曲播放时长时,终止Timer,放弃所有任务,Timer.cancel(),当本次任务会执行完。
暂停/继续播放时,可以参考歌曲的处理,来处理Timer,Timer也共享IsPause,最后也是阻塞/唤醒计时线程。
4.4歌词展示实现
4.4.1加载歌词文件
加载歌词文件的方式,我们分为两种方式
1.在加入歌曲文件时,直接判断歌曲文件所在目录是否有对应的歌词文件,同名判断(隐式加入)
2.用户方加入歌词文件(显式加入)
这里加入方法为歌曲文件一样,打开文件对话框让用户选择。
获取歌词文件数组后,与歌曲文件匹配:
为了进行匹配,我们需要记录下当前程序已加载的歌曲(Listsonglist),在程序加入歌曲时,把这些歌曲文件加入到集合即可。
删除歌曲时,集合也要将其删除
之后对加入的歌词文件进行匹配(同名匹配):
利用JDK8的新特性,可以将songlist转成数据流,挑选出满足条件(是否同名)的数据组成子集合,子集合中的元素即是与此歌词匹配的歌曲。
4.4.2解析歌词文件
观察下面的歌词文件(lrc),其实lrc是一种遵循特殊规范的文本文件
[ar:
陈奕迅]
[ti:
k歌之王]
[00:
13.32]我唱得不够动人你别皱眉
[00:
19.89]我愿意和你约定至死
它包含信息有歌手([ar:
])、标题([ti:
])、专辑([al:
])、歌词(时间-歌词键值对)
从Filelrc读取每一行