Java課程設(shè)計-音樂播放器_第1頁
Java課程設(shè)計-音樂播放器_第2頁
Java課程設(shè)計-音樂播放器_第3頁
Java課程設(shè)計-音樂播放器_第4頁
Java課程設(shè)計-音樂播放器_第5頁
已閱讀5頁,還剩27頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

1、程序設(shè)計課程設(shè)計綜合實驗音樂播放器班級:指導(dǎo)老師:組員:2014年12月2日目錄1 程序功能描述32 開發(fā)環(huán)境描述33 開發(fā)技術(shù)介紹34 詳細(xì)設(shè)計44.1功能模塊劃分44.2 用戶界面設(shè)計54.2.1 歌曲列表面板54.2.2 播放控制面板84.2.3 搜索及展示面板84.3 播放功能實現(xiàn)94.3.1播放歌曲94.3.2 暫停及繼續(xù)播放114.3.3 音量控制124.3.4 播放模式124.3.5 時間進度條134.4 歌詞展示實現(xiàn)144.4.1 加載歌詞文件144.4.2 解析歌詞文件144.4.3 展示歌詞154.5 搜索網(wǎng)絡(luò)歌曲資源164.5.1 獲取HTML文本164.5.2 解析HT

2、ML文本184.5.3 抓取數(shù)據(jù)描述204.6 網(wǎng)絡(luò)歌曲資源處理204.6.1 歌曲資源的載體204.6.2 歌曲資源的操作244.7 程序內(nèi)置的游戲264.7.1 2048264.7.2 貪吃蛇274.7.3 五子棋275程序運行286 實驗小結(jié)301 程序功能描述音樂播放器是一種用于播放各種音樂文件的多媒體播放軟件。我們以酷狗音樂播放器的操作界面為原型,設(shè)計一個實現(xiàn)播放、搜索、下載歌曲的Java音樂播放器。此音樂播放器支持音樂格式較少,只有MID、WMA、MP3。最后,為音樂播放器置入一些游戲,增強播放器的娛樂性。2 開發(fā)環(huán)境描述IDE:Eclipse(Luna)、netbeansJDK:

3、1.8圖片處理:Photoshop3 開發(fā)技術(shù)介紹1)Java Sound :Java Sound API是Java SE平臺提供底層的處理聲音接口。使用Java Sound API可以實現(xiàn)各種基于聲音的應(yīng)用,例如聲音錄制、音樂播放、音樂編輯等。同時其還提供了第三方的擴展接口(SPI),實現(xiàn)各種音樂格式的解碼與轉(zhuǎn)碼。2)Java Zoom :為了支持MP3的播放,必須為JavaSound擴展MP3的SPI支持庫。開源項目JavaZoom正是提供了一個兼容JavaSound的純Java解碼器。引用:jl1.0.1.jar、mp3spi1.9.5.jar、tritonus_share.jar3)J

4、audiotagger :開源項目Jaudiotagger提供一個Java類庫用于編輯音頻文件的tag信息(附有此音頻的歌手、標(biāo)題、專輯、音軌長度等的信息)。引用:jaudiotagger-2.0.3.jar4)Jsoup :Jsoup 是一款Java 的HTML解析器,可直接解析某個URL地址、HTML文本內(nèi)容。它提供了一套非常省力的API。引用:jsoup-1.8.1.jar5)Substance:Swing自帶提供了幾種look and feel類,然而要設(shè)計一個非常精美的GUI界面,卻相當(dāng)麻煩。使用java substance可以很簡單地實現(xiàn)。Substance里面有很多現(xiàn)成的非常漂亮

5、的皮膚。引用:substance.jar4 詳細(xì)設(shè)計4.1功能模塊劃分按結(jié)構(gòu)化設(shè)計方法,劃分出四個功能模塊:歌曲列表、播放控制、搜索及音樂庫。此四個模塊正好對應(yīng)酷狗用戶界面的四部分??峁芬魳凡シ牌魅缦拢篎igure 4.1.1 Kugou程序構(gòu)建的包main:主入口ui、ui.tool:用戶界面及其使用的一些工具類song:包含有歌曲、歌詞信息的類player:播放相關(guān)的類search:搜索相關(guān)的類程序結(jié)構(gòu)圖如下:Figure 4.1.2 程序結(jié)構(gòu)圖4.2 用戶界面設(shè)計窗體(Frame):窗體初始大小為975*670;內(nèi)容面板(ContentPane)由播放面板(PlayPanel)、歌曲列表

6、面板(PlayListPanel)、搜索面板(SearchPanel)、展示面板(ShowPanel)構(gòu)成,內(nèi)容面板的布局采用的是BoxLayout+Box,PlayListPanel和SearchPanel對應(yīng)都綁定了一個工具條(ButtonToolBar)程序引用了外包Substance 設(shè)計觀感4.2.1 歌曲列表面板PlayListPanel由一個工具條(ButtonToolBar extends JToolBar)、JPanel構(gòu)成,其中JPanel采用CardLayout布局,JPanel加入了3個歌曲列表面板(SongListPanel extends JScrollPanel)

7、、1個應(yīng)用面板(JScrollPanel)利用工具條的按鈕切換顯示面板Figure 4.2.1 1 歌曲目錄歌曲列表面板(SongListPanel)-列表的實現(xiàn): 利用JTree實現(xiàn)二級目錄。顧名思義,JTree是樹狀元件,它由眾多節(jié)點構(gòu)成,其中JTree需要一個根節(jié)點(root)。關(guān)于節(jié)點,我們用可派生節(jié)點DefaultMutableTreeNode類(implements TreeNode ) 即這種節(jié)點可以做“樹干”也可以做“葉子”1)利用一個節(jié)點構(gòu)建一個JTree,該節(jié)點為根節(jié)點,即一級節(jié)點其實我們要實現(xiàn)的歌曲列表是3級節(jié)點:根節(jié)點、歌曲目錄、歌曲文件,這里我們需要隱藏點根節(jié)點:tr

8、ee.setRootVisible(false); 否則我們將看到3層目錄2)根節(jié)點加入節(jié)點歌曲目錄節(jié)點(rootNode.add(aNode))第2級節(jié)點為歌曲目錄,可派生歌曲節(jié)點:節(jié)點(TreeNode)的其中一個構(gòu)造器是接受Object對象userObjectJTree是以節(jié)點的toString方法返回的字符串顯示節(jié)點,而節(jié)點的toString是由UserObjcet的toString決定,所以用String來構(gòu)建歌曲目錄節(jié)點即可3)歌曲目錄節(jié)點應(yīng)該有一個計算當(dāng)前歌曲數(shù)目并在目錄中顯示的方法目錄節(jié)點計算其的子節(jié)點數(shù),并更新到目錄名4)歌曲目錄節(jié)點加入歌曲文件節(jié)點加入方法與上面一樣。這里每

9、個歌曲(File)是一個節(jié)點,不可派生子節(jié)點上面說到每個節(jié)點在JTree顯示的字符串,都是由該節(jié)點的UserObject.toString()決定,F(xiàn)ile的toString返回的是該文件的路徑,這里我們重寫DefaultMutableTreeNode的toString 讓它返回歌曲名,所以構(gòu)造了SongNode類(extends DefaultMutableTreeNode)5)彈出菜單再做下面各種操作前,需要一個使用載體,我們利用彈出菜單來實現(xiàn)Figure 4.2.1 2 彈出菜單6)移除歌曲目錄因為用戶只能選中第2、3級節(jié)點,這里需要判斷當(dāng)前選中的是第幾級節(jié)點:獲取選中的路徑TreePa

10、th path = tree.getSelectionPath()(如果path=null 可以不往下執(zhí)行),通過path.getPathCount()來判斷,第3級節(jié)點是返回值是3。如果是第3級節(jié)點,需獲得它的上級節(jié)點(即歌曲目錄)獲取末端組件DefaultMutableTreeNode node=(DefaultMutableTreeNode) path.getLastPathComponent()獲取上級節(jié)點DefaultMutableTreeNode aList=node. getParent()如果是第2級節(jié)點,可直接獲取選中路徑的末端組件aList=(DefaultMutableT

11、reeNode)path.getLastPathComponent();這樣保證都從歌曲目錄節(jié)點操作,再按以下次序進行判斷:1.如果當(dāng)前目錄是默認(rèn)目錄,不移除,可以通過當(dāng)前節(jié)點在根節(jié)點的位置判斷root.getIndex(aList)2.如果當(dāng)前目錄含有歌曲,進行提示是否移除,可以獲取該目錄節(jié)點的子節(jié)點數(shù)目(getChildCount)或者該節(jié)點是否為“葉子”(isLeaf)3.如果當(dāng)前目錄播放著歌曲,終止播放,這里后面再敘述。4.最后移除,aList .removeFromParent();7)清空歌曲目錄與移除目錄類似,最后清空使用的方法aList.removeAllChildren();

12、8)刪除歌曲可以參考移除目錄的實現(xiàn),需要注意的是,要保證當(dāng)前選中的節(jié)點是第3級節(jié)點,即歌曲文件,是第2級節(jié)點就不往下操作,刪除方法同樣是aSong. removeFromParent()9)獲取音頻文件通過JFileChooser打開個對話框,獲取外部文件,并給其安裝過濾器。過濾器過濾出的格式為mid,mp3,wav選擇文件分為兩種模式,1獲取多個音頻文件,2獲取一個文件夾。需設(shè)置JFileChooser的選擇模式setFileSelectionMode(int param)關(guān)于第2種模式,獲取了文件夾后,也要給文件夾進行過濾操作(直接判斷或者安裝過濾器)這里的過濾器是抽象類 FileFilt

13、er,這里定義了一個AFilter(extends FileFilter)最后將這些files加入目錄節(jié)點即可10)鼠標(biāo)右擊選中當(dāng)前節(jié)點給JTree注冊MouseListener.,先判斷是否鼠標(biāo)右擊,再取與點擊點坐標(biāo)最近的節(jié)點tree.getPathForLocation(int x, int y)應(yīng)用面板(AppPanel)的實現(xiàn):應(yīng)用面板用JScrollPane,加入多個按鈕,一個按鈕對應(yīng)著Expandsion包附加的小游戲程序(Expandsion包導(dǎo)出游戲程序后,被刪除)以上,歌曲列表面板界面基本實現(xiàn),注意每次對JTree操作后,請更新JTree的狀態(tài),tree.updateUI()

14、,否則有可能出現(xiàn)Bug。在我們實現(xiàn)歌曲播放面板操作功能時,與歌曲列表面板進行交互,會再往列表面板添加功能。4.2.2 播放控制面板PlayPanel由于Java Swing布局復(fù)雜,我們可以用Eclipse的WindowBuilder或者netbeans的Matisse可視化構(gòu)建PlayPanel由以下組件構(gòu)成標(biāo)簽:歌曲名(songNameLabel)當(dāng)前播放進度時間(currentTimeCountLabel) 當(dāng)前播放歌曲總時間(audioTotalTimeLabel)按鈕:上一首(backPlay)下一首(frontPlay)播放(Play)靜音(voiceControl) 下載(dow

15、nload)標(biāo)記(mark)分享(share)這里用到的按鈕,都用到圖片;為簡化代碼,構(gòu)建一個IconButton來定義上面的按鈕其它:組合框(JComboBox<String>) mode控制播放模式 滑塊條(JSlider) voiceAdjust控制音量進度條(TimeProgressBar) 這里用到的進度條timerProgressBar 由于要計時,所以也構(gòu)建一個TimeProgressBarFigure 4.2.2 2 播放控制面板4.2.3 搜索及展示面板SearchPanel/ShowPanel的布局可以參考PlayListPanel,這里說下折疊面板效果的實現(xiàn)(

16、下圖選中的按鈕)折疊:SearchPanel/ShowPanel均設(shè)不可見,再設(shè)置Frame的大小和刷新。展開:給Frame注冊窗體狀態(tài)監(jiān)聽器,如果用戶按了最大化按鈕,會產(chǎn)生一個事件給監(jiān)聽器,這里判斷當(dāng)前窗體的新狀態(tài)是否為最大化,然后與折疊操作類似。(也可以不用“最大化”按鈕判斷,可以設(shè)置一個新按鈕作事件源) Figure 4.2.3 3 搜索及展示面板4.3 播放功能實現(xiàn)播放面板功能主要由BasicPlayer、HigherPlayer、TimeProgressBar,BasicPlayer實現(xiàn)的是底層操作(播放、暫停、繼續(xù)播放、終止、獲取音頻總時間等)。HigherPlayer(exten

17、ds BasicPlayer)處理面板間的交互,面板與BasicPlayer的交互。TimeProgressBar綁定了一個Timer,作計時功能。4.3.1播放歌曲為實現(xiàn)播放功能,我們這里用了Java Sound API,它可以實現(xiàn)各種基于聲音的應(yīng)用。Java Sound API的輸入/輸出相當(dāng)于IO流,TargetDataLine/SourceDataLine接口對應(yīng)輸入/輸出設(shè)備,要得到這個設(shè)備的對象,需要設(shè)備信息:它是輸入還是輸出設(shè)備,它處理的音頻數(shù)據(jù)格式-即編碼格式,不是“WAV/MP3”等文件格式AudioSystem在此過程中起著工廠類的作用這里先關(guān)注SourceDataLine

18、如何實現(xiàn)播放功能:BasicPlayer中關(guān)于播放的屬性private AudioInputStream audioInputStream;public SourceDataLine sourceDataLine;public URL audio;public Thread playThread;1)獲取audio URL回到SongListPanel,在JTree已注冊的MouseListener里增加響應(yīng)mouseClicked的處理,進行判斷,選取第3級節(jié)點。HigherPlayer在load方法里獲取這節(jié)點的userObect(即File),再轉(zhuǎn)成URL,這里在HigherPlayer

19、,議保存這個節(jié)點,因為節(jié)點很容易獲取它所在的列表節(jié)點。2)從指定的URL獲取音頻輸入流及音頻編碼格式先獲取音頻輸入流AudioSystem.getAudioInputStream(audio),再獲取其音頻編碼格式audioInputStream.getFormat()Javax.Sound默認(rèn)支持的編碼格式有PCM_SIGNED、PCM_UNSIGNED、ALAW、ULAW,對于這些編碼格式我們都不這么了解,只知道WAV與MID采用的是PCM_SIGNED當(dāng)要播放MP3檔時,這里會報異常,因為Javax.Sound并不支持MPEG1L3編碼(MP3采用此格式編碼)3)將MPEG1L3編碼轉(zhuǎn)換

20、成PCM_SIGNED首先要擴展Javax.Sound的解碼能力,之后再進行轉(zhuǎn)碼。Javax.Sound除了有實現(xiàn)聲音處理的API外,同時它以SPI(服務(wù)提供接口)為基礎(chǔ),實現(xiàn)各種音樂格式的解碼與轉(zhuǎn)碼,SPI以插件形式擴展了音頻處理的能力,SPI隨程序啟動而被啟動。即我們只要給程序引入MP3的SPI支持庫,這里用到的第3方支持庫是JavaZoom,獲取其中的jl1.0.jar、mp3spil1.9.5.jar、tritonus_share.jar因為程序解碼能力擴展,所以上面不再報異常,接著往下轉(zhuǎn)碼/ MPEG1L3轉(zhuǎn)PCM_SIGNEDif (audioFormat.getEncoding(

21、) != AudioFormat.Encoding.PCM_SIGNED) audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,audioFormat.getSampleRate(), 16,audioFormat.getChannels(),audioFormat.getChannels() * 2,audioFormat.getSampleRate(), false);audioInputStream = AudioSystem.getAudioInputStream(audioFormat,audioInputStr

22、eam);這里將audioFormat audioInputStream分別轉(zhuǎn)換成PCM_SIGNED,對于此轉(zhuǎn)碼方法,我們作了搬運工4)獲取輸出設(shè)備信息及對象/根據(jù)上面的音頻格式獲取輸出設(shè)備信息DataLine.Info info = new Info(SourceDataLine.class, audioFormat);/獲取輸出設(shè)備對象sourceDataLine = (SourceDataLine) AudioSystem.getLine(info);這里sourceDataLine extends Line5)打開輸出數(shù)據(jù)管道并進行IO操作打開輸出管道sourceDataLine.o

23、pen();允許此管道執(zhí)行數(shù)據(jù) I/O sourceDataLine.start();到這里,我們從輸入流讀入數(shù)據(jù),再將數(shù)據(jù)寫入輸出管道(輸出管道會先進入混頻器,再進入到端口,這里我們不管混頻器,只要知道數(shù)據(jù)輸入到揚聲器)最后,我們應(yīng)該設(shè)置一個獨立線程(playThread)啟動播放過程,否則主線程會在播放結(jié)束前阻塞。4.3.2 暫停及繼續(xù)播放為BasicPlayer加入屬性:public boolean IsPause = true;/ 是否為暫停狀態(tài)public boolean NeedContinue;/ 當(dāng)播放同一首歌曲 是否繼續(xù)播放狀態(tài)1)暫停這里IsPause初始為true,是為了

24、后面Play按鈕控制使用的上面我們用到了playTread線程啟動播放,那么“暫?!敝灰尨司€程進入阻塞狀態(tài)即可,“繼續(xù)播放”則是喚醒線程。playTread線程執(zhí)行過程需要占有鎖旗標(biāo),只要讓它釋放此鎖旗標(biāo)就達到暫停效果這個鎖旗標(biāo)(Oject)我們設(shè)為BasicPlayer這個對象本身。(其實隨便一個對象也可以)記住wait(),notify()要在同步塊中使用,synchronized(BasicPlayer.this),否則報異常在音頻數(shù)據(jù)的I/O讀寫循環(huán)中,設(shè)置一個控制標(biāo)記(IsPause),true時,進入暫停:BasicPlayer.this.wait();NeedContinue

25、= true;這里不需要設(shè)置IsPause為false,我們play按鈕是如下判斷,當(dāng)前狀態(tài)是暫停,則播放,當(dāng)前是播放(即不是暫停),則暫停。2)繼續(xù)播放當(dāng)用戶點擊相同的歌曲,或者暫停后,再點擊play按鈕時,繼續(xù)播放:方法與暫停播放相對3)終止當(dāng)前歌曲播放和播放新選中歌曲功能BasicPlayer增加屬性:public boolean IsComplete;終止當(dāng)前播放:即是銷毀當(dāng)前播放線程如果當(dāng)前歌曲播放完成,即線程run完,線程自動銷毀如果當(dāng)前歌曲播放中,播放新歌曲,我們手工銷毀當(dāng)前播放線程,并初始化IsPause、NeedContinue、IsComplete,之后啟動新線程播放新歌曲

26、。我們需要在HigherPlayer里記錄當(dāng)前播放的歌曲與加載的歌曲,判斷兩者是否是同一首歌曲。如果這里用stop也有可能會產(chǎn)生Bug,不安全,我們想要終止當(dāng)前線程,不一定要銷毀,有點強暴,我們讓線程執(zhí)行完就可以,加入新標(biāo)記boolean IsEnd,True時,完成這個線程(return)。這樣播放新歌曲時,過渡平滑些4)Play按鈕狀態(tài)上面說到的方法:都是用于播放面板Play按鈕的功能操作點擊Play按鈕的5種狀態(tài):1.載入歌曲為null時,沒什么都不做2.當(dāng)前沒播放歌曲,播放載入歌曲3.當(dāng)前播放著歌曲,讓它暫停4.當(dāng)前播放歌曲已暫停,如果加載歌曲沒變,讓它繼續(xù)播放5.不管當(dāng)前播放歌曲是否

27、暫停,如果加載歌曲與播放歌曲不同,終止當(dāng)前播放歌曲線程,播放載入歌曲4.3.3 音量控制輸出設(shè)備對象sourceDataLine有獲取各種控制的方法獲取當(dāng)前輸出設(shè)備對象的浮點控制器對象,類型為總音量控制sourceDataLine.getControl(FloatControl.Type.MASTER_GAIN);然后通過floatVoiceControl.setValue()/getValue(),設(shè)置/獲取當(dāng)前音量(db)音量大概范圍為-80F6F 默認(rèn)為0F通過一個滑塊組件(JSlider)設(shè)置其值4.3.4 播放模式1)上一首/下一首獲取當(dāng)前歌曲節(jié)點的位置(通過其父節(jié)點獲取的),再指向

28、上一個/下一個,然后加載此歌曲節(jié)點,手工觸發(fā)play按鈕2)單曲播放、循環(huán)等模式因為是播放完,之后進行播放模式操作,給BasicPlayer增加屬性boolean IsComplete,判斷當(dāng)前是否播放完。因為線程播放期間,一直阻塞,所以在線程最后設(shè)置IsComplte=true;組合框獲取當(dāng)前模式的值,進行判斷。在當(dāng)前歌曲播完后,這里可以先觸發(fā)一下play按鈕,成暫停狀態(tài),設(shè)置IsPause為true,等待下次播放,隨后進行播放模式操作選擇,這個與節(jié)點所在的位置有關(guān)。4.3.5 時間進度條要想實現(xiàn)進度條,需要得到當(dāng)前播放歌曲的播放時長,再通過一個計時器,設(shè)置進度條的值。1)獲取歌曲播放時長每

29、個音頻文件都要個文件頭記錄音頻信息(作者、采樣率、時長等),引用外包jaudiotagger.jar解析音頻,步驟如下:獲取音頻文件/AudioFileIO是org.jaudiotagger.audio包的類AudioFile file=new AudioFileIO.read(new File(URI);獲取音頻文件頭AudioHeader audioHeader=file.getAudioHeader();獲取音頻總長度int timelength=audioHeader.getTrackLength();這里的timelength是秒計,之后將timelength轉(zhuǎn)成“0:00”格式即可

30、。需要更新播放面板(PlayPanel)的當(dāng)前歌曲總時間標(biāo)簽(audioTotalTimeLabel)2)計時及更新進度條這里構(gòu)建了一個TimeProgressBar(extends JProgressBar),其中綁定了一個計時器Timer當(dāng)播放歌曲前,更新初始化TimeProgressBar的狀態(tài)(TimeProgressBar的兩個最值為0,timelength),重置Timer,啟動Timer。暫停歌曲時,Timer阻塞。繼續(xù)播放時,Timer喚醒。Timer對應(yīng)有個線程,執(zhí)行一次(或定期重復(fù)執(zhí)行)它的任務(wù)Task。Timer.schedule(TimerTask task,long

31、delay ,long period)。給Timer安排一個任務(wù),從指定延遲時間(delay)后執(zhí)行task,之后按間隔時間(period 這里是1s)執(zhí)行。每次執(zhí)行Task,更新TimeProgressBar的值和當(dāng)前播放進度時間標(biāo)簽currentTimeCountLabel,當(dāng)計時時間等于當(dāng)前歌曲播放時長時,終止Timer,放棄所有任務(wù),Timer.cancel(),當(dāng)本次任務(wù)會執(zhí)行完。暫停/繼續(xù)播放時,可以參考歌曲的處理,來處理Timer,Timer也共享IsPause,最后也是阻塞/喚醒計時線程。4.4 歌詞展示實現(xiàn)4.4.1 加載歌詞文件加載歌詞文件的方式,我們分為兩種方式1.在加入

32、歌曲文件時,直接判斷歌曲文件所在目錄是否有對應(yīng)的歌詞文件,同名判斷(隱式加入)2.用戶方加入歌詞文件(顯式加入)這里加入方法為歌曲文件一樣,打開文件對話框讓用戶選擇。獲取歌詞文件數(shù)組后,與歌曲文件匹配:為了進行匹配,我們需要記錄下當(dāng)前程序已加載的歌曲(List<SongNode>songlist),在程序加入歌曲時,把這些歌曲文件加入到集合即可。刪除歌曲時,集合也要將其刪除之后對加入的歌詞文件進行匹配(同名匹配):利用JDK8的新特性,可以將songlist轉(zhuǎn)成數(shù)據(jù)流,挑選出滿足條件(是否同名)的數(shù)據(jù)組成子集合,子集合中的元素即是與此歌詞匹配的歌曲。4.4.2 解析歌詞文件觀察下面

33、的歌詞文件(lrc),其實lrc是一種遵循特殊規(guī)范的文本文件ar:陳奕迅ti:k歌之王00:13.32我唱得不夠動人你別皺眉00:19.89我愿意和你約定至死它包含信息有歌手(ar:)、標(biāo)題(ti:)、專輯(al:)、歌詞(時間-歌詞鍵值對)從File lrc讀取每一行文本,進行解析。我們定義一個類(LrcInfos)保存解析的信息1.歌手、標(biāo)題、專輯的解析只要判斷該行字符串的頭部是否對應(yīng)為ar:、ti:、al:2.歌詞鍵值對的解析時間鍵的格式形如00:00.00,為了解析這種格式的文本,我們需要用到正則表達式,對應(yīng)上面的時間格式的正則表達式為d2:d2.d2,在正則表達式里、.、d為元字符(

34、可以理解為關(guān)鍵字),d為0-9數(shù)字,2表示匹配兩次,其它為固定匹配,之后將此正則式裝載到正則模式類Pattern patternPpile(regex)讓字符串對這個模式進行匹配Matcher matcher = pattern.matcher(line); Matcher是保存已成功匹配子串信息的類:如匹配的子串的始末位置等。判斷是否有成功匹配的子串:matcher. find(),獲取這個匹配的子串String time = matcher.group()這里的time就是00:00.00格式的字符串這里將這個time解析成Integer,歌詞是按秒展示的,這樣可以對應(yīng)前面的進度條,然后獲

35、取這個時間子串之后的歌詞文本,然后將時間-歌詞鍵值對保存到HashMap中,方便提取數(shù)據(jù)。4.4.3 展示歌詞展示歌詞的載體用JTextArea,就是把歌詞按時間打印到JTextArea這里很自然想到之前的計時進度條,當(dāng)計時器到一定秒數(shù),要顯示歌詞信息時,從Map<Integer, String> lrcInfosMap提取信息1.初始顯示歌詞我們要做滾動歌詞的效果,可以在計時器初始化時,從LrcInfosMap提取從0秒起的若干條歌詞,打印到textArea,做歌詞的初始顯示。定義printNextLrcInTheTime(int time,int line),定time為0。(

36、為了美觀,初始顯示歌詞時 line為23)2.歌詞滾動歌詞滾動效果的實現(xiàn),可以將textArea的第一行歌詞剪去,整體上移,顯示下一條歌詞:剪去第一行歌詞,同時整體上移,最后顯示下一條3.整體描述當(dāng)計時器到一定秒數(shù),即要顯示歌詞時,我們將第一行歌詞剪去,整體上移,現(xiàn)第一行歌詞即是當(dāng)前時間對應(yīng)的歌詞(即焦點在第一行),再顯示現(xiàn)有的最后一條歌詞的下一條。注意:初始顯示歌詞時,應(yīng)該從第二行開始。如下圖:Figure 4.4.3 4 歌詞展示4.5 搜索網(wǎng)絡(luò)歌曲資源播放歌曲時,是用IO流將本地歌曲數(shù)據(jù)寫入音頻設(shè)備,那么很自然,同樣應(yīng)該可以用IO流將網(wǎng)絡(luò)歌曲數(shù)據(jù)寫入音頻設(shè)備。這先要獲取網(wǎng)絡(luò)歌曲資源鏈接4

37、.5.1 獲取HTML文本1)使用Jsoup抓取HTML中數(shù)據(jù)1.獲取HTML代碼為了抓取數(shù)據(jù),第一步當(dāng)然要獲取HTML代碼,然后分析它??梢岳肬RL打開要訪問地址的一個連接,然后從這個連接對象獲取輸入流,最后IO流讀寫,讀入的數(shù)據(jù)就是HTML代碼,要注意HTML是用什么字符集編碼的,URLConnection connection = new URL(searchUrl).openConnection()connection.getInputStream()利用開源項目JSoup實現(xiàn)這個功能很簡單Document document = Jsoup.connect(searchUrl).ge

38、t();獲取HTML文檔(代碼)2.獲取HTML代碼中某標(biāo)簽的屬性值或文本HTML的標(biāo)簽可以看成一個節(jié)點,節(jié)點包含下級節(jié)點。在Jsoup中獲取節(jié)點對象,然后從這個節(jié)點對象獲取它的屬性值或者它的下級節(jié)點對象,如下:利用Jsoup中的選擇器(其中Elements、Element是Jsoup包中的類)獲取HTML文文件對象中含有class屬性且屬性值=number的span節(jié)點組:Elements spanNodes=document.select("spanclass=number")獲取span節(jié)點組的第一個節(jié)點對象:Element aNode=spanNodes.first

39、()獲取節(jié)點對象的屬性值,這里是獲得該節(jié)點中href的絕對路徑,去掉abs:得到的是相對路徑:String href=aNode.attr("abs:href")獲取該節(jié)點的文本:String text=aNode.text();如<span>Hello</span> 獲取的是HelloJsoup的API很友好下面,我們將百度音樂作為我們的音樂庫,它的HTML是以“utf-8”編碼String baseUrl = ""2)拼接搜索地址,獲取搜索結(jié)果的HTML文檔Figure 4.5.1 5 百度音樂搜索結(jié)果列表觀察百度音樂搜索的地

40、址,可以發(fā)現(xiàn)以下規(guī)則“s=1”-是否展開更多的歌曲“key”-key的值為搜索的關(guān)鍵字“start”-start的值為該結(jié)果頁面第一首歌曲的序號,這個可以用來切換搜索結(jié)果頁面“size”-size的值為歌曲的數(shù)目,size應(yīng)該為20,曾試過將size設(shè)為其它值,該頁面還是顯示20首歌曲如要搜索“下雨天”搜索結(jié)果頁是第二頁“key值由我們searchPanel的文本框獲取,注意編碼方式,如果key的值不是“utf-8”編碼,我們利用URLEncoder將它轉(zhuǎn)換。URLEncoder.encode(string key, string encode);最后用Jsoup獲取這個搜索地址的HTML文件

41、(Document searchListDoc)這里,我們可以去爬取百度音樂新歌榜的數(shù)據(jù)地址:構(gòu)建SearchSong類用于爬取歌曲資料,構(gòu)建SongInfos類來保存歌曲資料4.5.2 解析HTML文本1)進入搜索結(jié)果頁面每首歌曲信息頁面對應(yīng)的地址每首歌曲對應(yīng)的節(jié)點是<div class=“song-item clearfix”>這個節(jié)點可以獲取這個歌曲信息頁面的地址,也可以獲取歌曲名等一些信息如7319923為歌曲的ID然后獲取這個歌曲信息頁面的HTML文檔,如下圖:Figure 4.5.2 6 百度音樂搜索結(jié)果列表分析2)在歌曲信息頁面中爬取歌手名、專輯名、歌詞鏈接等數(shù)據(jù)歌曲

42、信息頁面Figure 4.5.2 2 百度音樂歌曲信息頁面其中兩個有用節(jié)點,可以獲取歌手名、專輯名、歌詞鏈接數(shù)據(jù)Figure 4.5.2 3 百度音樂歌曲信息頁面HTML代碼這里說下,獲取歌詞鏈接,歌詞鏈接在 <a data-lyricdata>節(jié)點中,獲取這個data-lyricdata的屬性值,然后用正則式匹配出后面的鏈接,正則式可以(/.*.lrc)3)爬取歌曲資源鏈接歌曲資源鏈接對于我們很重要。資源鏈接形式如下:先看下百度音樂的下載頁面,這個頁面的地址很簡單拼接,但是百度音樂用JavaScript做了登錄函數(shù)并加密歌曲資源鏈接,我們對JavaScript不了解,也不知道怎么

43、實現(xiàn)登錄百度的效果和它的加密算法Figure 4.5.2 4 百度音樂下載歌曲頁面我們只好從另一個頁面爬取到數(shù)據(jù)這是百度音樂的一個老接口,指向的是一個XML檔。這里有我們需要的資源鏈接,分析這個老接口的鏈接:形式song和singer是我們要填入的內(nèi)容,其它地方都是固定的。下面請注意XML檔里<url>,<durl>節(jié)點,它們下級節(jié)點<encode>和<decode>都有這個資源鏈接的一部分。Figure 4.5.2 5 百度音樂XML這里最好爬取<durl>里的鏈接,因為<url>的鏈接更有可能指向錯誤的地方同時這里可以

44、爬取到歌曲文件總字節(jié)數(shù)(size)和歌曲的比特率(bitRate,注意乘上1000,它的單位是kbps),這兩個屬性可以算出歌曲的時長(time) time=size*8/bitRate這個計算出的時間不夠準(zhǔn)確,誤差在2-4秒4.5.3 抓取數(shù)據(jù)描述最后,取到的數(shù)據(jù)包含歌名、歌手、專輯、歌曲資源鏈接、歌詞鏈接等,如下圖Figure 4.5.3 1 抓取歌曲數(shù)據(jù)描述4.6 網(wǎng)絡(luò)歌曲資源處理4.6.1 歌曲資源的載體音樂庫,是搜索網(wǎng)絡(luò)資源的載體??梢允褂肑Table類來實現(xiàn),這里構(gòu)建了LibraryPanel類,來表示音樂庫面板。這里用了機器(netbeans的Matisse)來構(gòu)建音樂庫面板,

45、LibraryPanel上面的組件為JTable,下面為JToolBar,如下圖:Figure 4.6.1 1 音樂庫載體1)JTable的Model數(shù)據(jù)處理JTable的數(shù)據(jù)處理等都由Model執(zhí)行,構(gòu)建了LibraryTableModel(extends DefaultTableModel)這個Model是由標(biāo)題欄(歌曲,歌手,專輯,操作),其中前3項為String 不可編輯,操作項是JPanel 可編輯,這些在Matisse都可以設(shè)置。1.初始化表格數(shù)據(jù)先初始化表格數(shù)據(jù)(Object initData),固定Model的行數(shù)為20,因為我們爬取的數(shù)據(jù)最多20首歌曲。 initData元素

46、全為null2.表格加入數(shù)據(jù)用的是DefaultTableModel的addRow(Object data)和removeRow(int row)當(dāng)我們搜索歌曲后,得到結(jié)果歌曲集合(List<songInfos>),要往表格中加入集合的元素。首先要清空表格數(shù)據(jù),使用removeRow(int row) /清除每一行然后加入一行數(shù)據(jù)Object data = new Object song, singer, album, panel ;addRow(data);這里很有可能報異常java.lang.ArrayIndexOutOfBoundsException在JTable添加數(shù)據(jù),刪

47、除數(shù)據(jù)頻繁操作,JTable出現(xiàn)數(shù)組越界,這個異常不影響表格操作,這里有兩個想法:1.不要這種分頁設(shè)計,只往表格添加數(shù)據(jù),不刪除數(shù)據(jù)。(表格有一個滾動面板)2.重置表格,重置表格模型,即庫面板加入了一個新的表格panel是我們自定的一個面板類的對象這個面板類OperationPanel包含3個按鈕(播放,加入默認(rèn)列表,下載按鈕)3.單元格渲染器-顯示組件在JTabel單元格中的組件,默認(rèn)是字符串顯示的(調(diào)用組件的toString),如果要顯示組件的樣子,需要用到渲染器類(CellRenderer)。這里我們要正確顯示OperationPanel,需要給“操作”欄定義渲染器dataTable.g

48、etColumn("操作").setCellRenderer(CellRenderer cellrenderer)這里 CellRenderer接口,我們用DefaultTableCellRenderer類對象,然后實現(xiàn)getTableCellRendererComponnent()方法getTableCellRendererComponnent返回的Component對象就是要顯示的組件,Object value是單元格的值當(dāng)value是JPanel類或子類的一個物件時,把它塑型回JPanel,并返回它。否則返回的super的方法4.單元格編輯器-觸發(fā)組件,產(chǎn)生動作上面的

49、渲染器只是顯示了單元格里的組件,然而要這個組件響應(yīng)回自己的動作(如OperationPanel里的按鈕,當(dāng)點擊這個單元格時,并不能觸發(fā)按鈕)這里要用到編輯器(AbstractCellEditor和TableCellEditor)AbstractCellEditor是一個抽象類,默認(rèn)實現(xiàn)了TableCellEditor接口的許多方法TableCellEditor的Component getTableCellEditorComponent方法AbstractCellEditor并沒有實現(xiàn)此方法,當(dāng)需要編輯單元格時,調(diào)用此方法,編輯的是此返回的組件對象。Overridepublic Componen

50、t getTableCellEditorComponent(JTable table, Object value,boolean isSelected, int row, int column) return value instanceof JPanel ? panel = (JPanel) value : null;panel記錄下這個組件(OperationPanel),因為當(dāng)退出這個單元格的編輯,需要告知渲染器,它應(yīng)該顯示什么。AbstractCellEditor沒有實現(xiàn)的方法Object getCellEditorValue(),這個返回的Object對象,會設(shè)為渲染器方法getTa

51、bleCellRendererComponent的value,即Object是要退出編輯時當(dāng)前單元格要顯示的組件,這里返回上面記錄的panel5.一個問題最后,有個問題,當(dāng)“操作”欄其中一個單元格處于編輯狀態(tài)時,再執(zhí)行搜索歌曲,表格加入新的歌曲項時,明明先清空了表格,然而此行該個單元格還是處于編輯狀態(tài),且OperationPanel沒有被置換掉(其操作的歌曲還是之前的)解決方法是,當(dāng)搜索歌曲前,退出單元格的編輯即可。這里是選中表格所有數(shù)據(jù)libraryPanel.getDataTable().selectAll()在表格更新完數(shù)據(jù)后,再撤銷選中clearSelection();2)JTable

52、的分頁JTable分頁效果的實現(xiàn),是由JToolBar里的按鈕(pageButton)控制。回到爬取數(shù)據(jù)時,拼接的百度音樂搜索地址,其中start的值為結(jié)果頁面的第一首歌的序號。我們可以用pageButton顯示的文本(第幾頁 page),還改變start的值,再進行搜索。然后將搜索得到的List<SongInfos>用Map,使之與page映射。這樣就不用搜索了一次后,又對同一頁進行搜索(直接從Map提取出List)注意pageButton按鈕組只能選擇一個,選擇pageButton前,要撤銷其它的按鈕的選擇,nextButton/frontButton,后一頁/前一頁應(yīng)該先改變

53、每一個pageButton的頁數(shù),再進行搜索,后一頁/前一頁都有一個上限/下限上限-先爬取搜索到的歌曲總數(shù)number,第number/20(或者+1)頁下限-第1頁3)“搜索”按鈕總體描述frontButton/nextButton執(zhí)行的流程:1.如果正在搜索(searchThread已啟動),給個提示,不再進行下面操作;2.如果pageButton按鈕組第一個/最后一個按鈕,顯示的頁數(shù)是第一頁/最后一頁,則不用改變pageButton按鈕組的頁面數(shù)否則改變每一個pageButton的頁面數(shù),觸發(fā)當(dāng)前按下的按鈕(這個按鈕,應(yīng)該在按下pageButton時作個標(biāo)記)去進行搜索pageButto

54、n執(zhí)行的流程:1.如果正在搜索(searchThread已啟動),給個提示,不再進行下面操作;2.如果在歌曲Map中,不包含該pageButton顯示的頁面數(shù),即Map中沒有該頁面的歌曲,當(dāng)然如果此頁面數(shù)已超過上限頁面,則不用進行下面操作。設(shè)置start為page*10,這里應(yīng)該有個boolean pageFlag,來判斷是分頁搜索還是全新搜索,再觸發(fā)searchButton(后面說到)3.如果在歌曲Map中,包含該pageButton顯示的頁面的歌曲,Map提出List<SongInfos> songlist, 然后將songlist加進表格中 searchButton執(zhí)行的流程:1.如果搜索文本框,沒有字符,不再進行下面操作2.如果搜索關(guān)鍵詞沒有改變且不是進行分頁搜索,不再進行下面操作3.如果進行新關(guān)鍵詞搜索,初始化pageButton的文本和選擇狀態(tài)4.如果搜索線程已啟動,不再進行下面操作5.啟動線程進行搜索操作,5.1.

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論