课程设计报告.docx
《课程设计报告.docx》由会员分享,可在线阅读,更多相关《课程设计报告.docx(24页珍藏版)》请在冰豆网上搜索。
课程设计报告
成绩
徐州工程学院
综合训练报告
课程名称面向对象程序设计综合设计
专业计算机科学与技术(单)
班级09计单
学生姓名夏军
学号20090502137
设计题目MP3音乐播放器
指导教师杨兴运
设计起止时间:
2011年12月21日至2012年1月6日
MP3音乐播放器
一、需求分析
软件界面布局需求
软件的基本布局如图1-1所示。
并且要求软件启动时,其界面必须处于屏幕正中央。
图1-1软件界面布局
音乐播放控制需求
播放控制主要是对播放状态进行控制,为了支持用户更便捷的控制音乐的播放,此功能需要几个控制按钮。
控制按钮代表的功能有:
播放/暂停、停止、上一首、下一首、打开、退出。
其中播放/暂停是复合功能按钮,可将播放状态变为播放或暂停。
其基本布局如图1-2。
当用户启动软件,软件处于就绪状态,不播放任何音乐。
当用户通过打开按钮选择文件后,点击播放按钮,则此时软件播放播放列表中的第一个播放条目。
当处于播放状态时,用户点击播放按钮,则播放状态由播放状态转为暂停状态。
再次点击则再次进入播放状态。
当播放状态为播放或暂停时,点击停止可将其音乐关闭。
点击上一首/下一首按钮可以根据播放列表中的条目进行选择。
点击打开按钮可以进行音乐文件的选择。
点击退出则弹出确认是否退出对话框。
图1-2播放控制布局
播放进度显示需求
当音乐处于播放状态,能够通过进度条显示当前音乐的播放进度。
当某一音乐播放完毕,则进度条归0。
播放列表需求
播放列表中显示加入的歌曲的编号、名称和时长。
并能对加入列表中的播放条目进行编辑、播放、下载歌词、评级等功能。
可将通过文件选择对话框选择的文件加入播放列表中。
歌词面板需求
能将歌词文本显示在歌词面板中,并且为居中对齐。
下载歌词需求
当用户选中播放列表中的播放条目,可以执行下载对应歌词的功能。
并显示在歌词面板上。
二、具体实现
软件界面布局实现
创建一个框架,将框架分成1行2列的布局。
在第1列中加入播放控制面板和播放列表面板。
在第二列中加入歌词面板。
其中播放控制面板宽为1/2框架宽,面板高为1/3框架高;播放列表面板宽为1/2框架宽,面板高位2/3框架高;歌词面板宽为1/2框架宽,面板高为框架高。
对于启动时处于正中央,实现手段可以是:
首先获取当前计算机屏幕的宽高。
然后得到框架的宽和高为屏幕的1/2。
具体代码:
classMusicFrameextendsJFrame{
privateListPanellistPanel;//播放列表面板
privatePlayPanelplayPanel;//播放控制面板
privateLRCPanellrcPanel;//歌词面板
Toolkitkit=Toolkit.getDefaultToolkit();
DimensionscreenSize=kit.getScreenSize();
intscreenHeight=screenSize.height;//获取屏幕大小
intscreenWidth=screenSize.width;
setResizable(false);
setBounds(screenWidth/4,screenHeight/4-100,screenWidth/2,screenHeight/2+100);//设置框架启动位置和大小
setLayout(newGridLayout(1,2));
JPanelleftSpace=newJPanel();
JPanelrightSpace=newJPanel();
listPanel=newListPanel(this);
playPanel=newPlayPanel(this);
lrcPanel=newLRCPanel(this);
leftSpace.add(playPanel);
leftSpace.add(listPanel);
rightSpace.add(lrcPanel);
}
音乐播放控制实现
音乐播放控制是整个设计环节中最复杂最具挑战性的一环,因为音乐播放控制不仅要控制音乐播放线程的播放,还需要同时处理来自按钮和播放列表发来的操作命令。
首先实现布局问题:
首先6个按钮控件,并将其加入面板内。
为了让6个按钮能够响应动作,则需要编写对应的监听器。
为了方便起见,只编写了一个监听方法,并在方法内比对捕获的事件对象,从而确定执行相应的代码段。
但《Java核心技术》一书中说这种方法并不好,不利于国际化。
这里就姑且偷懒一次吧!
下面实现各个按钮的功能。
播放/暂停按钮:
播放暂停按钮实现音乐的播放和暂停控制。
当用户点击播放时,判断当前播放列表中是否有播放条目。
如果有则播放第一条音乐记录。
音乐播放的具体过程:
首先获取到音乐文件路径,接着创建一个播放线程PlayThread对象。
将音乐路径加入其中。
执行播放的线程PlayThread的代码如下:
publicclassPlayThreadimplementsRunnable{
privateStringfilePath;//音乐文件的路径
privateMyPlayerplayer;//播放音乐的播放器
privateLongtotalFrame;//音乐文件的总帧数
privateThreadplayerThread;//执行播放音乐的线程
privatebooleancomplete;//判断当前音乐是否播放完毕
/*----------------初始化参数-------------*/
publicPlayThread(StringfilePath){
this.filePath=filePath;
this.playerInitialize();
this.totalFrame=player.getTotalFrame();
}
privatevoidplayerInitialize(){
/*------------将播放路径加入到播放器MyPlayer类对象中------*/
try{
StringurlAsString="file:
///"
+filePath;
this.player=newMyPlayer(new.URL(urlAsString));
}catch(Exceptionex){
ex.printStackTrace();
}
}
/*--------------执行播放的方法---------------*/
privatevoidplay(){
//当歌曲播放时,整个播放执行处于一个阻塞状态,所以为了是软件能够
//继续运行,播放动作需要在另外一个独立的线程中运行。
//这里创建了一个播放线程,并将类对象自己加入其中,然后启动线程
this.playerThread=newThread(this,"AudioPlayerThread");
this.playerThread.start();
}
/*----------------执行暂停功能的方法-------------*/
privatevoidpause(){
this.player.pause();//让播放器player暂停
this.playerThread.stop();//杀死当前执行的线程
}
//整个播放是处于一个非播即停的状态,所以设置了一个播放开关
publicvoidplayToggle(){
if(this.player.isPaused==true){
this.play();
}else{
this.pause();
}
}
//给播放器进度监听线程使用,目的是获取当前已播放的帧数
publicintgetFrameCurrent(){
returnplayer.getFrameCurrent();
}
//该方法负责获取计算音乐文件的时长所需的总帧数参数。
因为每帧约定为26ms
//播放时长T=帧数*26ms,此方法也给播放进度监听线程提供数据来计算
//进度率
publicLonggetTotalFrame(){
returntotalFrame;
}
//
publicbooleanisComplete(){
returncomplete;
}
//线程执行的代码段
publicvoidrun(){
try{
//当歌曲为第一次播放,则从开头播放
//否则执行该方法,则播放上一次被暂停的位置的数据
complete=this.player.resume();
}catch(javazoom.jl.decoder.JavaLayerExceptionex){
ex.printStackTrace();
}
}
}
为了更加详细的论述播放器player对象的工作原理。
下面介绍player对象的实现。
player对象的类是MyPlayer类。
MyPlayer是Player类的子类。
Player是第三方类库Jlayer中的类。
因为Jlayer并没有实现暂停播放的控制功能,所以需要继承已经存在的Player类,编写具有暂停功能的子类MyPlayer。
MyPlayer类主要代码如下:
publicclassMyPlayer{
private.URLurlToStreamFrom;//音乐文件路径
privateBitstreambitstream;//音乐文件位流对象
privateDecoderdecoder;//Mp3解码器
privateAudioDeviceaudioDevice;//声卡
privatebooleanisClosed=false;//是否关闭状态
privatebooleanisComplete=false;//是否完成播放状态
privateintframeIndexCurrent;//当前播放的帧索引
publicbooleanisPaused;//是否暂停播放状态
/*初始化待播放的音乐文件路径和设置暂停状态*/
publicMyPlayer(URLurlToStreamFrom)throwsJavaLayerException{
this.urlToStreamFrom=urlToStreamFrom;
isPaused=true;
}
//暂停状态
publicvoidpause(){
this.isPaused=true;//置暂停状态为真
this.close();//执行关闭方法
}
//播放开始从0开始播放
publicbooleanplay()throwsJavaLayerException{
returnthis.play(0);
}
//从指定帧位置播放
publicbooleanplay(intframeIndexStart)throwsJavaLayerException{
returnthis.play(frameIndexStart,-1,52);
}
/*---------------真正执行播放任务的代码---------*/
publicbooleanplay(intframeIndexStart,intframeIndexFinal,
intcorrectionFactorInFrames)throwsJavaLayerException{
try{
//打开文件流
this.bitstream=newBitstream(this.urlToStreamFrom.openStream());
}catch(java.io.IOExceptionex){
}
//创建一个声卡对象
this.audioDevice=FactoryRegistry.systemRegistry().createAudioDevice();
//创建一个解码器对象
this.decoder=newDecoder();
this.audioDevice.open(decoder);//将解码器置于声卡中
booleanshouldContinueReadingFrames=true;
this.isPaused=false;
this.frameIndexCurrent=0;
//跳帧:
跳到上一次被暂停的帧位置
while(shouldContinueReadingFrames==true
&&this.frameIndexCurrent-correctionFactorInFrames){
shouldContinueReadingFrames=this.skipFrame();
this.frameIndexCurrent++;
}
while(shouldContinueReadingFrames==true
&&this.frameIndexCurrentif(this.isPaused==true){
shouldContinueReadingFrames=false;
try{
//如果暂停状态被改变,则让线程睡眠
Thread.sleep
(1);
}catch(Exceptionex){
}
}else{
shouldContinueReadingFrames=this.decodeFrame();
this.frameIndexCurrent++;
}
}
//最后,为了保证所有的帧都进入声卡中
if(this.audioDevice!
=null){
this.audioDevice.flush();
synchronized(this){
this.isComplete=(this.isClosed==false);
this.close();
}
}
returnthis.isComplete;
}
//执行恢复方法
publicbooleanresume()throwsJavaLayerException{
//返回是否完成状态,副作用是启动播放
returnthis.play(this.frameIndexCurrent);
}
//关闭播放,因为暂停并不是暂停而是停止播放,
//只不过暂停时记住了断点,导致下次播放时可以从断点处继续
publicsynchronizedvoidclose(){
//关闭时首先将声卡关闭,然后将流关闭
if(this.audioDevice!
=null){
this.isClosed=true;
this.audioDevice.close();
this.audioDevice=null;
try{
this.bitstream.close();
}catch(Exceptionex){
}
}
}
//获得总的帧数
//基本思想是打开流并调用skipFrame()方法,如果该方法返回真
//说明文件没有到结尾,因为skipFrame()方法每次只读一帧,所以
//可以对其计数
publicLonggetTotalFrame(){
Longtotal=0L;
try{
this.bitstream=newBitstream(this.urlToStreamFrom.openStream());
}catch(java.io.IOExceptionex){
}
try{
while(skipFrame())
total++;
}catch(JavaLayerExceptione){
e.printStackTrace();
}
returntotal;
}
publicbooleanskipFrame()throwsJavaLayerException{
booleanreturnValue=false;
Headerheader=bitstream.readFrame();
if(header!
=null){
bitstream.closeFrame();
returnValue=true;
}
returnreturnValue;
}
//获得当前的帧索引
publicintgetFrameCurrent(){
returnframeIndexCurrent;
}
停止按钮的实现代码:
privatevoidclickStop(){
if(playState){
player.playToggle();
playState=false;
plprLpleteListen();
}elseif(player!
=null){
slider.setValue(0);
player=null;
}
}
停止按钮的主要功能是让播放状态从播放或者暂停状态转到停止状态。
停止播放的动作执行的过程是:
如果当前播放状态为真,则调用播放开关函数playToggle()将播放暂停,然后置播放状态为false,表示处于非播放状态。
。
然后调用完成监听函数。
完成监听的功能在接下来会详细阐述。
这里只需知道它用来控制播放进度的。
上一首/下一首按钮功能的实现代码片段:
1)上一首/下一首按钮监听器代码片段
elseif(source==previousButton){
tempPlayState=playState;
if(tempPlayState)
clickStop();
if(playList.size()>=1){
musicURL=getMusicURL(false);
}
if(tempPlayState)
clickPlay();//如果一开始在播放,则播放
}elseif(source==nextButton){
tempPlayState=playState;
if(tempPlayState)
clickStop();
if(playList.size()>=1){
musicURL=getMusicURL(true);
}
if(tempPlayState)
clickPlay();//如果一开始在播放,则播放
2)获取上一首/下一首歌曲文件路径的方法
privateStringgetMusicURL(booleanisNext){
intsize=playList.size();
if(isNext){
index=(index+1)%size;
returnplayList.get(index).getFilePath();
}else{
index=(index-1+size)%size;
returnplayList.get(index).getFilePath();
}
}
当调用该方法时,如果传入的参数为true,则表示需要当前音乐文件的下一位置的音乐文件路径。
否则为前一位置的音乐文件路径。
打开按钮功能的实现:
当点击打开按钮时,界面弹出一个文件选择对话框,这个文件选择对话框是JFileChooser的子类MusicFileChooser产生的。
MusicFileChooser类用来实现文件的选择和为播放列表返回一个DefaultTableModel类对象,该对象为播放列表显示播放条目的数据。
在MusicFileChooser类中还实现了一个文件过滤器类,此类的功能是当显示文件选择对话框时,对话框的选择文件类型为MP3文件类型。
③播放进度显示实现
播放进度显示的基本思想是通过获取播放文件的当前的帧位置和总帧数,两者相除得到进度,然后将进度条设置为此进度值。
执行获取播放帧位置需要一个独立的线程,此线程只在播放器播放音乐时才执行监听动作。
④播放列表需求
当从播放窗口中选择好文件后,点击确定,则此时被选中的文件被加入到播放列表中。
播放列表是是派生自JTable。
播放列表playTable在类中重写了JTable的两个方法,publicStringgetToolTipText(MouseEventevent)和publicPointgetToolTipLocation(MouseEventevent)前者允许使用渲染器的提示(如果设置了文本)。
后者用于返回工具提示在此组件坐标系统中的位置。
对被选中的播放列表条目进行编辑,主要原理是使用方法playTable.getSelectedRows()来获取被选择的行,然后调用JTable中的方法对其进行操作。
歌词面板需求
歌词面板主要对歌词文本进行显示,由于歌词文本是无格式的,而在歌词面板显示的时候需要居中对齐。
实现的思想是:
获取当前歌词面板所能容纳的最长字符个数maxLength,然后求出每一行歌词文本的长度length。
通过公式:
(maxLength-length)/2计算出需要在本行歌词文本前加入的空格符号数量。
对应的代码:
publicStringlrcFormatAjust(Stringsource){//居中对齐歌词
StringBuffersb=newStringBuffer();
intmaxLength=28;//歌词面板的宽度
for