├── .gitattributes ├── .gitignore ├── CleanPlayer.pro ├── baidumusic.cpp ├── baidumusic.h ├── cookiejar.cpp ├── cookiejar.h ├── data ├── clean-player.desktop └── clean-player.png ├── doc └── image │ ├── appearence.png │ └── lyric.png ├── image ├── add.png ├── clear.png ├── close-normal.png ├── delete.png ├── down.png ├── left.png ├── list.png ├── logo.ico ├── logo.png ├── next.png ├── pause.png ├── play.png ├── previous.png ├── random.png ├── right.png ├── search.png ├── sequence.png ├── shuffle.png ├── slider-point.png ├── title.png ├── up.png ├── update.png ├── volume-high-icon.png ├── volume-low-icon.png ├── volume-medium-icon.png └── volume-mute-icon.png ├── main.cpp ├── qml ├── Main.qml └── resource │ ├── BottomBar.qml │ ├── Container.qml │ ├── Lyric.qml │ ├── Playlist.qml │ ├── PlaylistView.qml │ ├── SearchResult.qml │ ├── SideBar.qml │ ├── Suggestion.qml │ ├── TopBar.qml │ └── utils.js ├── readme.md ├── res.qrc ├── updatesummary.txt ├── util.cpp └── util.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | *.pro.user 9 | bin/ 10 | tmp/ 11 | *.tmp 12 | *.bak 13 | *.swp 14 | *~.nib 15 | local.properties 16 | .classpath 17 | .settings/ 18 | .loadpath 19 | 20 | # External tool builders 21 | .externalToolBuilders/ 22 | 23 | # Locally stored "Eclipse launch configurations" 24 | *.launch 25 | 26 | # CDT-specific 27 | .cproject 28 | 29 | # PDT-specific 30 | .buildpath 31 | 32 | 33 | ################# 34 | ## Visual Studio 35 | ################# 36 | 37 | ## Ignore Visual Studio temporary files, build results, and 38 | ## files generated by popular Visual Studio add-ons. 39 | 40 | # User-specific files 41 | *.suo 42 | *.user 43 | *.sln.docstates 44 | 45 | # Build results 46 | 47 | [Dd]ebug/ 48 | [Rr]elease/ 49 | x64/ 50 | build/ 51 | [Bb]in/ 52 | [Oo]bj/ 53 | 54 | # MSTest test Results 55 | [Tt]est[Rr]esult*/ 56 | [Bb]uild[Ll]og.* 57 | 58 | *_i.c 59 | *_p.c 60 | *.ilk 61 | *.meta 62 | *.obj 63 | *.pch 64 | *.pdb 65 | *.pgc 66 | *.pgd 67 | *.rsp 68 | *.sbr 69 | *.tlb 70 | *.tli 71 | *.tlh 72 | *.tmp 73 | *.tmp_proj 74 | *.log 75 | *.vspscc 76 | *.vssscc 77 | .builds 78 | *.pidb 79 | *.log 80 | *.scc 81 | 82 | # Visual C++ cache files 83 | ipch/ 84 | *.aps 85 | *.ncb 86 | *.opensdf 87 | *.sdf 88 | *.cachefile 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | 102 | # TeamCity is a build add-in 103 | _TeamCity* 104 | 105 | # DotCover is a Code Coverage Tool 106 | *.dotCover 107 | 108 | # NCrunch 109 | *.ncrunch* 110 | .*crunch*.local.xml 111 | 112 | # Installshield output folder 113 | [Ee]xpress/ 114 | 115 | # DocProject is a documentation generator add-in 116 | DocProject/buildhelp/ 117 | DocProject/Help/*.HxT 118 | DocProject/Help/*.HxC 119 | DocProject/Help/*.hhc 120 | DocProject/Help/*.hhk 121 | DocProject/Help/*.hhp 122 | DocProject/Help/Html2 123 | DocProject/Help/html 124 | 125 | # Click-Once directory 126 | publish/ 127 | 128 | # Publish Web Output 129 | *.Publish.xml 130 | *.pubxml 131 | 132 | # NuGet Packages Directory 133 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 134 | #packages/ 135 | 136 | # Windows Azure Build Output 137 | csx 138 | *.build.csdef 139 | 140 | # Windows Store app package directory 141 | AppPackages/ 142 | 143 | # Others 144 | sql/ 145 | *.Cache 146 | ClientBin/ 147 | [Ss]tyle[Cc]op.* 148 | ~$* 149 | *~ 150 | *.dbmdl 151 | *.[Pp]ublish.xml 152 | *.pfx 153 | *.publishsettings 154 | 155 | # RIA/Silverlight projects 156 | Generated_Code/ 157 | 158 | # Backup & report files from converting an old project file to a newer 159 | # Visual Studio version. Backup files are not needed, because we have git ;-) 160 | _UpgradeReport_Files/ 161 | Backup*/ 162 | UpgradeLog*.XML 163 | UpgradeLog*.htm 164 | 165 | # SQL Server files 166 | App_Data/*.mdf 167 | App_Data/*.ldf 168 | 169 | ############# 170 | ## Windows detritus 171 | ############# 172 | 173 | # Windows image file caches 174 | Thumbs.db 175 | ehthumbs.db 176 | 177 | # Folder config file 178 | Desktop.ini 179 | 180 | # Recycle Bin used on file shares 181 | $RECYCLE.BIN/ 182 | 183 | # Mac crap 184 | .DS_Store 185 | 186 | 187 | ############# 188 | ## Python 189 | ############# 190 | 191 | *.py[co] 192 | 193 | # Packages 194 | *.egg 195 | *.egg-info 196 | dist/ 197 | build/ 198 | eggs/ 199 | parts/ 200 | var/ 201 | sdist/ 202 | develop-eggs/ 203 | .installed.cfg 204 | 205 | # Installer logs 206 | pip-log.txt 207 | 208 | # Unit test / coverage reports 209 | .coverage 210 | .tox 211 | 212 | #Translations 213 | *.mo 214 | 215 | #Mr Developer 216 | .mr.developer.cfg 217 | -------------------------------------------------------------------------------- /CleanPlayer.pro: -------------------------------------------------------------------------------- 1 | QT += quick qml network multimedia xml 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | TARGET = clean-player 6 | TEMPLATE = app 7 | 8 | SOURCES += \ 9 | main.cpp \ 10 | baidumusic.cpp \ 11 | cookiejar.cpp \ 12 | util.cpp 13 | 14 | OTHER_FILES += \ 15 | qml/PlayerControler.qml \ 16 | qml/LyricView.qml \ 17 | updatesummary.txt \ 18 | qml/MainWindow.qml \ 19 | qml/TopBar.qml \ 20 | data/clean-player.desktop \ 21 | data/clean-player.png 22 | 23 | 24 | RESOURCES += \ 25 | res.qrc \ 26 | 27 | HEADERS += \ 28 | baidumusic.h \ 29 | cookiejar.h \ 30 | util.h 31 | 32 | DISTFILES += \ 33 | readme.md \ 34 | qml/Main.qml \ 35 | qml/resource/PlayButton.qml \ 36 | qml/resource/PreviousButton.qml \ 37 | qml/resource/SearchBar.qml \ 38 | qml/resource/SearchResult.qml \ 39 | qml/resource/Playlist.qml \ 40 | qml/resource/PlaylistView.qml \ 41 | qml/resource/TopBar.qml \ 42 | qml/resource/Suggestion.qml \ 43 | qml/resource/Container.qml \ 44 | qml/resource/BottomBar.qml \ 45 | qml/resource/SideBar.qml \ 46 | qml/resource/Lyric.qml \ 47 | qml/resource/utils.js 48 | 49 | # icons 50 | icons.files = data/clean-player.png 51 | 52 | # desktop 53 | desktop.files = data/clean-player.desktop 54 | 55 | isEmpty(INSTALL_PREFIX) { 56 | unix: INSTALL_PREFIX = /usr 57 | else: INSTALL_PREFIX = .. 58 | } 59 | 60 | unix: { 61 | desktop.path = $$INSTALL_PREFIX/share/applications 62 | icons.path = $$INSTALL_PREFIX/share/icons/hicolor/64x64/apps 63 | INSTALLS += desktop icons 64 | QMAKE_STRIP=echo 65 | } 66 | 67 | target.path = $$INSTALL_PREFIX/bin 68 | 69 | INSTALLS += target 70 | -------------------------------------------------------------------------------- /baidumusic.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "baidumusic.h" 11 | 12 | //页面大小 13 | const int PAGESIZE = 20; 14 | 15 | //音乐搜索API 16 | //参数%1:搜索关键字 17 | //参数%2: 起始位置,为(page-1)*20 18 | const QString ApiOfSearch = "http://music.baidu.com/search?key=%1&start=%2&size=20&s=1"; 19 | 20 | //搜索建议API 21 | //参数%1:歌曲id 22 | const QString ApiOfSuggestion = "http://sug.music.baidu.com/info/suggestion?format=json&word=%1&version=2&from=0"; 23 | 24 | //歌曲信息API 25 | //参数%1:歌曲id 26 | const QString ApiOfSongInfo = "http://play.baidu.com/data/music/songinfo?songIds=%1"; 27 | 28 | //歌曲链接API 29 | //参数%1:歌曲id 30 | const QString ApiOfSongLink = "http://play.baidu.com/data/music/songlink?songIds=%1&type=m4a,mp3"; 31 | 32 | BaiduMusic::BaiduMusic(QObject *parent) : QObject(parent) 33 | { 34 | searchReply = 0; 35 | suggestionReply = 0; 36 | songInfoReply = 0; 37 | songLinkReply = 0; 38 | lyricReply = 0; 39 | manager.setCookieJar(&cookieJar); 40 | } 41 | 42 | BaiduMusic::~BaiduMusic() 43 | { 44 | 45 | } 46 | 47 | //搜索关键字 48 | void BaiduMusic::search(const QString &keyword, int page) 49 | { 50 | //删除原来的响应 51 | if(searchReply){ 52 | searchReply->deleteLater(); 53 | } 54 | 55 | //起始位置 56 | int start = (page-1)*PAGESIZE; 57 | 58 | //构造请求链接url 59 | QUrl url = QUrl(ApiOfSearch.arg(keyword).arg(start)); 60 | searchReply = manager.get(QNetworkRequest(url)); 61 | connect(searchReply,SIGNAL(finished()),this,SLOT(searchReplyFinished())); 62 | } 63 | 64 | void BaiduMusic::getSuggestion(QString keyword) 65 | { 66 | if(suggestionReply){ 67 | suggestionReply->deleteLater(); 68 | } 69 | 70 | QUrl url = QUrl(ApiOfSuggestion.arg(keyword)); 71 | suggestionReply = manager.get(QNetworkRequest(url)); 72 | connect(suggestionReply,SIGNAL(finished()),this,SLOT(suggestionReplyFinished())); 73 | } 74 | 75 | void BaiduMusic::getSongInfo(QString songId) 76 | { 77 | if(songInfoReply){ 78 | songInfoReply->deleteLater(); 79 | } 80 | 81 | QUrl url = QUrl(ApiOfSongInfo.arg(songId)); 82 | songInfoReply = manager.get(QNetworkRequest(url)); 83 | connect(songInfoReply,SIGNAL(finished()),this,SLOT(songInfoReplyFinished())); 84 | } 85 | 86 | void BaiduMusic::getSongLink(QString songId) 87 | { 88 | if(songLinkReply){ 89 | songLinkReply->deleteLater(); 90 | } 91 | 92 | QUrl url = QUrl(ApiOfSongLink.arg(songId)); 93 | songLinkReply = manager.get(QNetworkRequest(url)); 94 | connect(songLinkReply,SIGNAL(finished()),this,SLOT(songLinkReplyFinished())); 95 | } 96 | 97 | void BaiduMusic::getLyric(QString url) 98 | { 99 | if(lyricReply){ 100 | lyricReply->deleteLater(); 101 | } 102 | lyricReply = manager.get(QNetworkRequest(QUrl(url))); 103 | connect(lyricReply,SIGNAL(finished()),this,SLOT(lyricReplyFinished())); 104 | } 105 | 106 | QString BaiduMusic::unifyResult(QString r) 107 | { 108 | return r.replace(QRegularExpression("songid|songId"),"sid") 109 | .replace(QRegularExpression("author|artistname"),"singer") 110 | .replace(QRegularExpression("songname|songName"),"sname"); 111 | } 112 | 113 | void BaiduMusic::searchReplyFinished() 114 | { 115 | 116 | QString url = searchReply->request().url().toString(); 117 | 118 | int keywordBegin = url.indexOf("key=") + 4; 119 | int keywordEnd = url.indexOf("&start="); 120 | 121 | int pageBeginPos = url.indexOf("start=") + 6; 122 | int pageEndPos = url.indexOf("&size="); 123 | 124 | //当前页 125 | int currentPage = url.mid(pageBeginPos,pageEndPos-pageBeginPos).toInt()/PAGESIZE + 1; 126 | 127 | //关键字 128 | QString keyword = url.mid(keywordBegin,keywordEnd-keywordBegin); 129 | if(searchReply->error()){ 130 | 131 | //如果出错,pageCount为-1; 132 | emit searchComplete(currentPage,1,keyword,"{error:"+searchReply->errorString()+"}"); 133 | return; 134 | } 135 | 136 | //TODO:未搜索到内容的判断 137 | 138 | QString html = searchReply->readAll(); 139 | QStringList songList; 140 | QRegularExpression re("
  • 和 147 | songData = songData.replace(""","\"").replace("<em>","").replace("<\\/em>",""); 148 | songList << songData; 149 | } 150 | 151 | //构造json数组 152 | QString songArray = "[" + songList.join(",") + "]"; 153 | QString result = unifyResult(songArray); 154 | //匹配总页数 155 | QRegularExpression pageCountRe("\">(\\d+)\\s*0 ? pageCount : 1; 163 | 164 | emit searchComplete(currentPage,pageCount,keyword,result); 165 | } 166 | 167 | void BaiduMusic::suggestionReplyFinished() 168 | { 169 | if(suggestionReply->error()){ 170 | emit getSuggestionComplete("{error:"+suggestionReply->errorString()+"}"); 171 | return; 172 | } 173 | QString sug = suggestionReply->readAll(); 174 | emit getSuggestionComplete(unifyResult(sug)); 175 | } 176 | 177 | void BaiduMusic::songInfoReplyFinished() 178 | { 179 | if(songInfoReply->error()){ 180 | emit getSongInfoComplete("{error:" + songInfoReply->errorString() + "}"); 181 | return; 182 | } 183 | 184 | QString songinfo = songInfoReply->readAll(); 185 | emit getSongInfoComplete(songinfo); 186 | } 187 | 188 | void BaiduMusic::songLinkReplyFinished() 189 | { 190 | 191 | if(songLinkReply->error()){ 192 | emit getSongLinkComplete("{error:" + songLinkReply->errorString() + "}"); 193 | return; 194 | } 195 | 196 | QString songlink = songLinkReply->readAll(); 197 | 198 | emit getSongLinkComplete(unifyResult(songlink)); 199 | } 200 | 201 | void BaiduMusic::lyricReplyFinished() 202 | { 203 | QString url = lyricReply->url().toString(); 204 | 205 | if(lyricReply->error()){ 206 | emit getLyricComplete(url, ""); 207 | return; 208 | } 209 | qDebug()<rawHeaderList(); 210 | emit getLyricComplete(url, lyricReply->readAll()); 211 | } 212 | -------------------------------------------------------------------------------- /baidumusic.h: -------------------------------------------------------------------------------- 1 | #ifndef BAIDUMUSIC_H 2 | #define BAIDUMUSIC_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "cookiejar.h" 10 | 11 | class BaiduMusic : public QObject 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit BaiduMusic(QObject *parent = 0); 16 | ~BaiduMusic(); 17 | 18 | 19 | /** 20 | * @brief search 搜索歌曲 21 | * @param keyword 关键字 22 | * @param page 页数 23 | */ 24 | Q_INVOKABLE void search(const QString& keyword, int page); 25 | 26 | /** 27 | * @brief getSuggestion 获取搜索建议 28 | * @param keyword 百度音乐歌曲id 29 | */ 30 | Q_INVOKABLE void getSuggestion(QString keyword); 31 | 32 | /** 33 | * @brief getSongInfo 获取歌曲信息 34 | * @param songId 35 | */ 36 | Q_INVOKABLE void getSongInfo(QString songId); 37 | 38 | /** 39 | * @brief getSongLink 获取歌曲链接,包括下载链接和歌词连接等 40 | * @param songId 41 | */ 42 | Q_INVOKABLE void getSongLink(QString songId); 43 | 44 | /** 45 | * @brief getLyric 根据歌词链接下载歌词 46 | * @param url 47 | */ 48 | Q_INVOKABLE void getLyric(QString url); 49 | 50 | 51 | private: 52 | QNetworkAccessManager manager; 53 | QNetworkReply* searchReply; 54 | QNetworkReply* suggestionReply; 55 | QNetworkReply* songInfoReply; 56 | QNetworkReply* songLinkReply; 57 | QNetworkReply* lyricReply; 58 | 59 | //保存所有cookie 60 | CookieJar cookieJar; 61 | 62 | //统一结果,如songid转换为sid,songname转换为sname 63 | QString unifyResult(QString r); 64 | private slots: 65 | void searchReplyFinished(); 66 | void suggestionReplyFinished(); 67 | void songInfoReplyFinished(); 68 | void songLinkReplyFinished(); 69 | void lyricReplyFinished(); 70 | signals: 71 | /** 72 | * @brief searchComplete 搜索完毕 73 | * @param currentPage 当前页 74 | * @param pageCount 总页数 75 | * @param keyword 关键字 76 | * @param songList 歌曲列表,json数据 77 | * [ 78 | * {"songItem": 79 | * { 80 | * "sid":877578, 81 | * "author":"Beyond", 82 | * "sname":"海阔天空", 83 | * "oid":877578, 84 | * "pay_type":"2" 85 | * } 86 | * }, 87 | * {"songItem": 88 | * ... 89 | * }, 90 | * ... 91 | * ] 92 | */ 93 | void searchComplete(int currentPage,int pageCount,QString keyword, QString songList); 94 | 95 | /** 96 | * @brief getSuggestionComplete 获取搜索建议完毕 97 | * @param suggestion 搜索建议json数据 98 | * { 99 | * "data": { 100 | * "song": [{ 101 | * "songid": "877578", 102 | * "songname": "\u6d77\u9614\u5929\u7a7a", 103 | * "encrypted_songid": "", 104 | * "has_mv": "1", 105 | * "yyr_artist": "0", 106 | * "artistname": "Beyond" 107 | * }, 108 | * ... 109 | * ], 110 | * "artist": [{ 111 | * "artistid": "2345733", 112 | * "artistname": "\u6d77\u9614\u5929\u7a7a", 113 | * "artistpic": "http:\/\/a.hiphotos.baidu.com\/ting\/pic\/item\/6d81800a19d8bc3eb42695cc808ba61ea8d3458d.jpg", 114 | * "yyr_artist": "0" 115 | * }, 116 | * ... 117 | * ], 118 | * "album": [{ 119 | * "albumid": "197864", 120 | * "albumname": "\u6d77\u9614\u5929\u7a7a", 121 | * "artistname": "Beyond", 122 | * "artistpic": "http:\/\/a.hiphotos.baidu.com\/ting\/pic\/item\/6c224f4a20a4462314dd8c409a22720e0cf3d7f8.jpg" 123 | * }, 124 | * ... 125 | * ] 126 | * }, 127 | * "Pro": ["artist", "song", "album"] 128 | * } 129 | * 130 | */ 131 | 132 | void getSuggestionComplete(QString suggestion); 133 | 134 | void getSongInfoComplete(QString songInfo); 135 | 136 | /** 137 | * @brief getSongLinkComplete 获取歌曲连接完毕 138 | * @param songLink 139 | */ 140 | void getSongLinkComplete(QString songLink); 141 | 142 | void getLyricComplete(QString url,QString lyricContent); 143 | public slots: 144 | }; 145 | 146 | #endif // BAIDUMUSIC_H 147 | -------------------------------------------------------------------------------- /cookiejar.cpp: -------------------------------------------------------------------------------- 1 | #include "cookiejar.h" 2 | 3 | CookieJar::CookieJar() 4 | { 5 | 6 | } 7 | 8 | CookieJar::~CookieJar() 9 | { 10 | 11 | } 12 | 13 | QList CookieJar::getCookies() 14 | { 15 | return allCookies(); 16 | } 17 | 18 | void CookieJar::setCookies(const QList &cookieList) 19 | { 20 | setAllCookies(cookieList); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /cookiejar.h: -------------------------------------------------------------------------------- 1 | #ifndef COOKIEJAR_H 2 | #define COOKIEJAR_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class CookieJar : public QNetworkCookieJar 9 | { 10 | public: 11 | CookieJar(); 12 | ~CookieJar(); 13 | 14 | QList getCookies(); 15 | void setCookies(const QList& cookieList); 16 | }; 17 | 18 | #endif // COOKIEJAR_H 19 | -------------------------------------------------------------------------------- /data/clean-player.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Exec=/usr/bin/clean-player 5 | Name[zh_CN]=clean player 6 | Comment[zh_CN]=Baidu Online Music Player Client 7 | Name=clean-player 8 | Comment=Baidu Online Music Player Client 9 | Icon=clean-player 10 | Categories=AudioVideo;Player;QT; 11 | -------------------------------------------------------------------------------- /data/clean-player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/data/clean-player.png -------------------------------------------------------------------------------- /doc/image/appearence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/doc/image/appearence.png -------------------------------------------------------------------------------- /doc/image/lyric.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/doc/image/lyric.png -------------------------------------------------------------------------------- /image/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/add.png -------------------------------------------------------------------------------- /image/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/clear.png -------------------------------------------------------------------------------- /image/close-normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/close-normal.png -------------------------------------------------------------------------------- /image/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/delete.png -------------------------------------------------------------------------------- /image/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/down.png -------------------------------------------------------------------------------- /image/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/left.png -------------------------------------------------------------------------------- /image/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/list.png -------------------------------------------------------------------------------- /image/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/logo.ico -------------------------------------------------------------------------------- /image/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/logo.png -------------------------------------------------------------------------------- /image/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/next.png -------------------------------------------------------------------------------- /image/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/pause.png -------------------------------------------------------------------------------- /image/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/play.png -------------------------------------------------------------------------------- /image/previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/previous.png -------------------------------------------------------------------------------- /image/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/random.png -------------------------------------------------------------------------------- /image/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/right.png -------------------------------------------------------------------------------- /image/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/search.png -------------------------------------------------------------------------------- /image/sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/sequence.png -------------------------------------------------------------------------------- /image/shuffle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/shuffle.png -------------------------------------------------------------------------------- /image/slider-point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/slider-point.png -------------------------------------------------------------------------------- /image/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/title.png -------------------------------------------------------------------------------- /image/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/up.png -------------------------------------------------------------------------------- /image/update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/update.png -------------------------------------------------------------------------------- /image/volume-high-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/volume-high-icon.png -------------------------------------------------------------------------------- /image/volume-low-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/volume-low-icon.png -------------------------------------------------------------------------------- /image/volume-medium-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/volume-medium-icon.png -------------------------------------------------------------------------------- /image/volume-mute-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pansinm/CleanPlayer/37bc90a6c7fac79ab5e34593c41646f872936755/image/volume-mute-icon.png -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "baidumusic.h" 11 | #include "util.h" 12 | int main(int argc,char* argv[]) 13 | { 14 | QApplication app(argc,argv); 15 | QQuickView viewer; 16 | 17 | qmlRegisterType("CleanPlayerCore",1,0,"BaiduMusic"); 18 | qmlRegisterType("CleanPlayerCore",1,0,"Util"); 19 | 20 | 21 | //无边框,背景透明 22 | //viewer.setFlags(Qt::FramelessWindowHint|Qt::Window|Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint); 23 | // viewer.setTitle("Clean Player"); 24 | 25 | //这行不注释掉的话在XP系统无法正常显示 26 | //viewer.setColor(QColor(Qt::transparent)); 27 | 28 | //viewer.rootContext()->setContextProperty("mainwindow",&viewer); 29 | 30 | viewer.setSource(QUrl("qrc:/qml/qml/Main.qml")); 31 | viewer.setIcon(QIcon(":/image/image/logo.png")); 32 | viewer.show(); 33 | 34 | return app.exec(); 35 | } 36 | -------------------------------------------------------------------------------- /qml/Main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtMultimedia 5.4 3 | import QtQuick.Controls 1.3 4 | import QtQuick.Controls.Styles 1.3 5 | import "./resource" 6 | import CleanPlayerCore 1.0 7 | 8 | Rectangle { 9 | id: root 10 | width: 1000 11 | height: 600 12 | color: "#333333" 13 | 14 | MediaPlayer { 15 | id: mediaplayer 16 | } 17 | 18 | //工具函数 19 | Util { 20 | id: util 21 | } 22 | 23 | //百度音乐Api 24 | BaiduMusic { 25 | id: baiduMusic 26 | } 27 | 28 | //播放列表 29 | Playlist { 30 | id: playlist 31 | mediaPlayer: mediaplayer 32 | baiduMusic: baiduMusic 33 | util: util 34 | } 35 | 36 | //顶栏 37 | TopBar { 38 | id: topBar 39 | 40 | anchors.left: parent.left 41 | anchors.right: parent.right 42 | 43 | baiduMusic: baiduMusic 44 | suggestion: suggestion 45 | } 46 | 47 | //底部栏 48 | BottomBar { 49 | id: bottomBar 50 | anchors.left: parent.left 51 | anchors.bottom: parent.bottom 52 | width: parent.width 53 | color: "#444444" 54 | 55 | mediaPlayer: mediaplayer 56 | playlist: playlist 57 | baiduMusic: baiduMusic 58 | onLyricHiddenChanged: { 59 | lyricView.visible = !lyricHidden 60 | } 61 | } 62 | 63 | //边栏 64 | SideBar { 65 | id: sideBar 66 | 67 | anchors.left: parent.left 68 | anchors.top: topBar.bottom 69 | anchors.bottom: bottomBar.top 70 | 71 | playlist: playlist 72 | container: container 73 | } 74 | 75 | //搜索建议 76 | Suggestion { 77 | id: suggestion 78 | 79 | anchors.left: sideBar.right 80 | anchors.leftMargin: 28 81 | anchors.top: topBar.bottom 82 | anchors.topMargin: -15 83 | 84 | z: 100 85 | 86 | baiduMusic: baiduMusic 87 | playlist: playlist 88 | } 89 | 90 | //歌词 91 | Lyric { 92 | id: lyricView 93 | baiduMusic: baiduMusic 94 | playlist: playlist 95 | mediaPlayer: mediaplayer 96 | z: 2 97 | anchors.left: parent.left 98 | anchors.right: parent.right 99 | anchors.top: sideBar.top 100 | anchors.bottom: bottomBar.top 101 | visible: false 102 | } 103 | 104 | //内容区域 105 | Container { 106 | id: container 107 | lyric: lyricView 108 | anchors.left: sideBar.right 109 | anchors.top: topBar.bottom 110 | anchors.bottom: bottomBar.top 111 | anchors.right: parent.right 112 | 113 | baiduMusic: baiduMusic 114 | playlist: playlist 115 | } 116 | 117 | MouseArea { 118 | anchors.fill: parent 119 | z: -1 120 | onClicked: { 121 | suggestion.hide() 122 | } 123 | } 124 | 125 | //关闭时 126 | Component.onDestruction: { 127 | //保存播放列表 128 | playlist.saveTo("playlist") 129 | } 130 | 131 | //加载结束 132 | Component.onCompleted: { 133 | //加载播放列表 134 | playlist.loadFrom("playlist") 135 | sideBar.update() 136 | 137 | //列表中第一首为默认播放歌曲 138 | if (playlist.count() > 0) { 139 | playlist.index = 0 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /qml/resource/BottomBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | import QtQuick.Controls.Styles 1.3 4 | import QtMultimedia 5.4 5 | 6 | import CleanPlayerCore 1.0 7 | 8 | Rectangle { 9 | height: 60 10 | color: "#444444" 11 | 12 | property bool isPlaying: false 13 | property MediaPlayer mediaPlayer 14 | property Playlist playlist 15 | property BaiduMusic baiduMusic 16 | property bool lyricHidden: true 17 | 18 | Rectangle { 19 | id: songPicWrapper 20 | width: parent.height - 10 21 | height: parent.height - 10 22 | 23 | anchors.left: parent.left 24 | anchors.leftMargin: 5 25 | anchors.top: parent.top 26 | anchors.topMargin: 5 27 | 28 | Image { 29 | id: songPic 30 | anchors.fill: parent 31 | } 32 | MouseArea { 33 | anchors.fill: parent 34 | onClicked: { 35 | lyricHidden = !lyricHidden 36 | } 37 | } 38 | } 39 | 40 | //播放、上一首、下一首 41 | Rectangle { 42 | id: playController 43 | width: 150 44 | height: parent.height 45 | color: "#00000000" 46 | anchors.leftMargin: 20 47 | anchors.left: songPicWrapper.right 48 | anchors.bottom: parent.bottom 49 | 50 | Rectangle { 51 | id: previousButton 52 | anchors.left: parent.left 53 | anchors.verticalCenter: parent.verticalCenter 54 | width: 40 55 | height: 40 56 | radius: 20 57 | color: "#00000000" 58 | Image { 59 | source: "qrc:/image/image/previous.png" 60 | width: 50 61 | height: 50 62 | anchors.centerIn: parent 63 | } 64 | 65 | MouseArea { 66 | anchors.fill: parent 67 | hoverEnabled: true 68 | onEntered: parent.color = "#33ffffff" 69 | onExited: parent.color = "#00000000" 70 | onClicked: { 71 | playlist.previous() 72 | } 73 | } 74 | } 75 | 76 | Rectangle { 77 | id: playButton 78 | anchors.centerIn: parent 79 | width: 44 80 | height: 44 81 | radius: 22 82 | color: "#00000000" 83 | 84 | Image { 85 | source: isPlaying ? "qrc:/image/image/pause.png" : "qrc:/image/image/play.png" 86 | width: 56 87 | height: 56 88 | anchors.centerIn: parent 89 | } 90 | 91 | MouseArea { 92 | anchors.fill: parent 93 | hoverEnabled: true 94 | onEntered: parent.color = "#33ffffff" 95 | onExited: parent.color = "#00000000" 96 | 97 | onClicked: { 98 | isPlaying = !isPlaying 99 | if (isPlaying) { 100 | playlist.play() 101 | } else { 102 | playlist.pause() 103 | } 104 | } 105 | } 106 | } 107 | Rectangle { 108 | id: nextButton 109 | anchors.right: parent.right 110 | anchors.verticalCenter: parent.verticalCenter 111 | width: 40 112 | height: 40 113 | radius: 20 114 | color: "#00000000" 115 | 116 | Image { 117 | source: "qrc:/image/image/next.png" 118 | width: 50 119 | height: 50 120 | anchors.centerIn: parent 121 | } 122 | 123 | MouseArea { 124 | anchors.fill: parent 125 | hoverEnabled: true 126 | onEntered: parent.color = "#33ffffff" 127 | onExited: parent.color = "#00000000" 128 | onClicked: { 129 | playlist.next() 130 | } 131 | } 132 | } 133 | } 134 | 135 | Rectangle { 136 | id: currentSongInfo 137 | height: 45 138 | width: 120 139 | color: "#00000000" 140 | clip: true 141 | anchors.verticalCenter: parent.verticalCenter 142 | anchors.left: playController.right 143 | anchors.leftMargin: 15 144 | 145 | Text { 146 | id: sname 147 | height: 15 148 | color: "white" 149 | anchors.left: parent.left 150 | anchors.top: parent.top 151 | text: "" 152 | } 153 | Text { 154 | id: singer 155 | height: 15 156 | color: "white" 157 | anchors.left: parent.left 158 | anchors.top: sname.bottom 159 | text: "歌手:" 160 | } 161 | Text { 162 | id: album 163 | height: 15 164 | color: "white" 165 | anchors.left: parent.left 166 | anchors.top: singer.bottom 167 | text: "专辑:" 168 | } 169 | 170 | function setSongName(name) { 171 | sname.text = name 172 | } 173 | 174 | function setSinger(singername) { 175 | singer.text = "歌手:" + singername 176 | } 177 | 178 | function setAlbum(albumname) { 179 | album.text = "专辑:" + albumname 180 | } 181 | 182 | //清空信息 183 | function clear() { 184 | sname.text = "" 185 | singer.text = "歌手:" 186 | album.text = "专辑:" 187 | } 188 | } 189 | 190 | Rectangle { 191 | anchors.left: currentSongInfo.right 192 | anchors.leftMargin: 15 193 | anchors.right: volumeControler.left 194 | anchors.rightMargin: 15 195 | anchors.verticalCenter: parent.verticalCenter 196 | Text { 197 | id: currentTime 198 | anchors.left: parent.left 199 | anchors.verticalCenter: parent.verticalCenter 200 | color: "white" 201 | text: "00:00" 202 | } 203 | 204 | Text { 205 | id: totalTime 206 | anchors.right: parent.right 207 | anchors.verticalCenter: parent.verticalCenter 208 | color: "white" 209 | text: "00:00" 210 | } 211 | 212 | //进度条 213 | Slider { 214 | id: slider 215 | anchors.left: currentTime.right 216 | anchors.leftMargin: 5 217 | anchors.right: totalTime.left 218 | anchors.rightMargin: 5 219 | anchors.verticalCenter: parent.verticalCenter 220 | maximumValue: 0 221 | value: 0 222 | stepSize: 0.5 223 | style: SliderStyle { 224 | groove: Rectangle { 225 | implicitWidth: 100 226 | implicitHeight: 2 227 | color: "white" 228 | radius: 1 229 | } 230 | handle: Rectangle { 231 | width: 8 232 | height: 8 233 | radius: 4 234 | color: "white" 235 | } 236 | } 237 | onValueChanged: { 238 | currentTime.text = formatTime(value) 239 | totalTime.text = formatTime(maximumValue) 240 | } 241 | 242 | //释放鼠标的时候 243 | onPressedChanged: { 244 | if (!pressed) { 245 | mediaPlayer.seek(value) 246 | } 247 | } 248 | 249 | //格式化时间 250 | function formatTime(ms) { 251 | //补0 252 | function pad(num, n) { 253 | var len = num.toString().length 254 | while (len < n) { 255 | num = "0" + num 256 | len++ 257 | } 258 | return num 259 | } 260 | 261 | var min = Math.floor(ms / 1000 / 60) 262 | var sec = Math.floor(ms / 1000 % 60) 263 | return pad(min, 2) + ":" + pad(sec, 2) 264 | } 265 | } 266 | } 267 | 268 | Rectangle { 269 | id: volumeControler 270 | height: 32 271 | width: 140 272 | x: 40 273 | anchors.right: parent.right 274 | anchors.verticalCenter: parent.verticalCenter 275 | color: "#00000000" 276 | Rectangle { 277 | id: volumeImgResion 278 | anchors.verticalCenter: parent.verticalCenter 279 | anchors.left: parent.left 280 | width: 32 281 | height: parent.height 282 | color: "#00000000" 283 | Image { 284 | id: volumeImg 285 | width: 22 286 | height: 22 287 | anchors.centerIn: parent 288 | source: "qrc:/image/image/volume-medium-icon.png" 289 | } 290 | MouseArea { 291 | anchors.fill: parent 292 | hoverEnabled: true 293 | onEntered: parent.color = "#33ffffff" 294 | onExited: parent.color = "#00000000" 295 | onClicked: { 296 | mediaPlayer.muted = !mediaPlayer.muted 297 | } 298 | } 299 | } 300 | 301 | Slider { 302 | id: volumeSlider 303 | anchors.left: volumeImgResion.right 304 | anchors.leftMargin: 3 305 | anchors.verticalCenter: volumeImgResion.verticalCenter 306 | height: 10 307 | width: 100 308 | maximumValue: 1.0 309 | minimumValue: 0 310 | value: 0.7 311 | stepSize: 0.02 312 | onValueChanged: { 313 | mediaPlayer.volume = volumeSlider.value 314 | setVolumeIcon() 315 | if (mediaPlayer.muted) { 316 | mediaPlayer.muted = false 317 | } 318 | } 319 | 320 | style: SliderStyle { 321 | groove: Rectangle { 322 | implicitWidth: 100 323 | implicitHeight: 2 324 | color: "white" 325 | radius: 1 326 | } 327 | handle: Rectangle { 328 | width: 8 329 | height: 8 330 | radius: 4 331 | color: "white" 332 | } 333 | } 334 | 335 | //设置音量图标 336 | function setVolumeIcon() { 337 | if (value > 0.8) { 338 | volumeImg.source = "qrc:/image/image/volume-high-icon.png" 339 | } else if (value > 0.45) { 340 | volumeImg.source = "qrc:/image/image/volume-medium-icon.png" 341 | } else if (value > 0) { 342 | volumeImg.source = "qrc:/image/image/volume-low-icon.png" 343 | } else if (value === 0) { 344 | volumeImg.source = "qrc:/image/image/volume-mute-icon.png" 345 | } 346 | } 347 | } 348 | } 349 | 350 | //计时器,定时更新进度条 351 | Timer { 352 | interval: 500 353 | running: true 354 | repeat: true 355 | onTriggered: { 356 | //正在拖拽的时候不更新位置 357 | if (!slider.pressed) { 358 | slider.value = mediaPlayer.position 359 | } 360 | } 361 | } 362 | 363 | Connections { 364 | target: mediaPlayer 365 | onStopped: { 366 | slider.maximumValue = 0 367 | slider.value = 0 368 | isPlaying = false 369 | } 370 | 371 | onPaused: { 372 | isPlaying = false 373 | } 374 | 375 | onPlaying: { 376 | slider.maximumValue = mediaPlayer.duration 377 | slider.value = mediaPlayer.position 378 | isPlaying = true 379 | } 380 | 381 | onDurationChanged: { 382 | slider.maximumValue = mediaPlayer.duration 383 | } 384 | 385 | onMutedChanged: { 386 | if (mediaPlayer.muted) { 387 | volumeImg.source = "qrc:/image/image/volume-mute-icon.png" 388 | } else { 389 | volumeSlider.value = mediaPlayer.volume 390 | volumeSlider.setVolumeIcon() 391 | } 392 | } 393 | } 394 | 395 | Connections { 396 | target: baiduMusic 397 | onGetSongInfoComplete: { 398 | try { 399 | var songinfo = JSON.parse(songInfo) 400 | var currentSong = songinfo.data.songList[0] 401 | var picLink = currentSong.songPicSmall 402 | var queryId = currentSong.queryId 403 | if (playlist.getSong(playlist.currentIndex()).sid == queryId) { 404 | //设置歌曲图片 405 | songPic.source = picLink 406 | //设置专辑名称 407 | currentSongInfo.setAlbum(currentSong.albumName) 408 | } 409 | } catch (e) { 410 | console.log("BottomBar[onGetSongInfoComplete]:" + e) 411 | } 412 | } 413 | } 414 | 415 | Connections { 416 | target: playlist 417 | onIndexChanged: { 418 | currentSongInfo.clear() 419 | var song = playlist.getSong(playlist.index) 420 | currentSongInfo.setSongName(song.sname) 421 | currentSongInfo.setSinger(song.singer) 422 | } 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /qml/resource/Container.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import CleanPlayerCore 1.0 3 | 4 | 5 | //内容区域 6 | Rectangle { 7 | id: containerRoot 8 | property BaiduMusic baiduMusic 9 | property Playlist playlist 10 | property Lyric lyric 11 | 12 | //播放列表 13 | SearchResult { 14 | id: searchResult 15 | anchors.fill: parent 16 | visible: false 17 | baiduMusic: parent.baiduMusic 18 | playlist: parent.playlist 19 | } 20 | //播放列表 21 | PlaylistView { 22 | id: playlistView 23 | 24 | anchors.fill: parent 25 | 26 | visible: true 27 | playlist: parent.playlist 28 | } 29 | 30 | //显示搜索结果 31 | function showSearchResult() { 32 | searchResult.visible = true 33 | playlistView.visible = false 34 | lyric.visible = false 35 | } 36 | 37 | //显示播放列表 38 | function showPlaylist() { 39 | searchResult.visible = false 40 | playlistView.visible = true 41 | lyric.visible = false 42 | } 43 | 44 | //更新播放列表 45 | function updatePlaylist(listname) { 46 | playlistView.update(listname) 47 | } 48 | 49 | Connections { 50 | target: baiduMusic 51 | onSearchComplete: { 52 | try { 53 | var songlist = JSON.parse(songList) 54 | //如果错误 55 | if (songlist.error) { 56 | 57 | //TODO:搜索出错 58 | } 59 | } catch (e) { 60 | console.log(e) 61 | return 62 | } 63 | searchResult.setResultInfo(currentPage, pageCount, 64 | keyword, songlist) 65 | showSearchResult() 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /qml/resource/Lyric.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import CleanPlayerCore 1.0 3 | import QtMultimedia 5.4 4 | 5 | Rectangle { 6 | color: '#333' 7 | property Playlist playlist 8 | property BaiduMusic baiduMusic 9 | property MediaPlayer mediaPlayer 10 | property string currentLyricUrl 11 | property int fontSize: 8 12 | property int lineSpace: 20 13 | 14 | //顺序插入 15 | function insertLyric(time, content) { 16 | var count = lyricModel.count 17 | if (count == 0) { 18 | lyricModel.append({ 19 | time: time, 20 | content: content 21 | }) 22 | return 23 | } 24 | if (time < lyricModel.get(0).time) { 25 | lyricModel.insert(0, { 26 | time: time, 27 | content: content 28 | }) 29 | return 30 | } 31 | if (time > lyricModel.get(count - 1).time) { 32 | lyricModel.append({ 33 | time: time, 34 | content: content 35 | }) 36 | return 37 | } 38 | 39 | for (var i = 0; i < count; ++i) { 40 | if (lyricModel.get(i).time < time && time < lyricModel.get( 41 | i + 1).time) { 42 | lyricModel.insert(i + 1, { 43 | time: time, 44 | content: content 45 | }) 46 | } 47 | } 48 | } 49 | 50 | //给定时间显示的歌词 51 | function getLyricIndex(time) { 52 | var count = lyricModel.count 53 | if (count == 0) { 54 | return -1 55 | } 56 | 57 | if (time < lyricModel.get(0).time) { 58 | return 0 59 | } 60 | 61 | for (var i = 0; i < count; ++i) { 62 | if (lyricModel.get(i).time <= time && time < lyricModel.get( 63 | i + 1).time) { 64 | return i 65 | } 66 | } 67 | return count - 1 68 | } 69 | 70 | ListModel { 71 | id: lyricModel 72 | } 73 | 74 | Component { 75 | id: lyricDelegate 76 | Rectangle { 77 | id: lyricLineRect 78 | height: lineSpace 79 | width: parent.parent.width 80 | color: "#00000000" 81 | Text { 82 | id: lyricTxt 83 | anchors.centerIn: parent 84 | color: lyricLineRect.ListView.isCurrentItem ? "white" : "#80ffffff" 85 | text: content 86 | font.family: "微软雅黑" 87 | font.pointSize: lyricLineRect.ListView.isCurrentItem ? fontSize + 2 : fontSize 88 | } 89 | } 90 | } 91 | //歌词显示列表 92 | ListView { 93 | id: lyricView 94 | anchors.fill: parent 95 | spacing: 5 96 | model: lyricModel 97 | delegate: lyricDelegate 98 | clip: true 99 | onCurrentItemChanged: NumberAnimation { 100 | target: lyricView 101 | property: "contentY" 102 | to: lyricView.currentItem.y - 65 103 | duration: 500 104 | easing.type: Easing.OutSine 105 | } 106 | } 107 | 108 | Connections { 109 | target: mediaPlayer 110 | onPositionChanged: { 111 | //给0.1s补偿 112 | var i = getLyricIndex(mediaPlayer.position + 100) 113 | if (i >= 0) { 114 | lyricView.currentIndex = i 115 | } 116 | } 117 | } 118 | 119 | Connections { 120 | target: baiduMusic 121 | //歌曲播放地址获取完毕 122 | onGetSongLinkComplete: { 123 | try { 124 | var link = JSON.parse(songLink) 125 | 126 | //如果还是当前播放歌曲,则立即播放,否则不处理 127 | if (playlist.currentSid == link.data.songList[0].sid) { 128 | if (link.data.songList[0].lrcLink === '') { 129 | return 130 | } 131 | 132 | var lrcLink = link.data.songList[0].lrcLink 133 | 134 | if (!/^http/.test(lrcLink)) { 135 | lrcLink = 'http://play.baidu.com' + link.data.songList[0].lrcLink 136 | } 137 | 138 | currentLyricUrl = lrcLink 139 | baiduMusic.getLyric(lrcLink) 140 | } 141 | } catch (e) { 142 | console.log("getLink:" + e) 143 | } 144 | } 145 | onGetLyricComplete: { 146 | if (currentLyricUrl == url) { 147 | //解析歌词,允许单行多时间 148 | var lines = lyricContent.split('\n') 149 | lines.forEach(function (line, index) { 150 | var rex = /^((\[\d+:\d+\.\d+\])+)(.*)/ 151 | var result = line.match(rex) 152 | if (result) { 153 | var content = result[3] //歌词 154 | var times = result[1].split(/\[|\]/) //时间 155 | times.forEach(function (str) { 156 | var re = /^(\d+):(\d+)\.(\d+)$/ 157 | ; 158 | var rs = str.match(re) 159 | 160 | if (rs) { 161 | var min = parseInt(rs[1]) 162 | var sec = parseInt(rs[2]) 163 | var ms = parseInt(rs[3]) * 10 //点后面每单位代表10ms 164 | var time = min * 60 * 1000 + sec * 1000 + ms 165 | insertLyric(time, content) 166 | } 167 | }) 168 | } 169 | }) 170 | } 171 | } 172 | } 173 | Connections { 174 | target: playlist 175 | onIndexChanged: { 176 | lyricModel.clear() 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /qml/resource/Playlist.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtMultimedia 5.4 3 | import CleanPlayerCore 1.0 4 | 5 | Item { 6 | property int index: -1 7 | property var playlists: { 8 | 默认列表: [] 9 | } //播放列表,默认加载【默认列表】 10 | property string currentList: "默认列表" 11 | property MediaPlayer mediaPlayer 12 | property BaiduMusic baiduMusic 13 | property Util util 14 | property string currentSid 15 | //列表中的歌曲数目 16 | function count(list) { 17 | var listname = list ? list : currentList 18 | console.log("listname:" + listname) 19 | if (typeof playlists[listname] == 'undefined') { 20 | playlists[listname] = [] 21 | } 22 | return playlists[listname].length 23 | } 24 | 25 | function getSong(i, list) { 26 | var listname = list ? list : currentList 27 | return playlists[listname][i] 28 | } 29 | 30 | //返回指定列表的歌曲 31 | function getSongList(list) { 32 | var listname = list ? list : currentList 33 | return playlists[listname] 34 | } 35 | 36 | //当前播放位置 37 | function currentIndex() { 38 | return index 39 | } 40 | 41 | //添加歌曲到列表 42 | function addSong(song, list) { 43 | var listname = list ? list : currentList 44 | if (typeof playlists[listname] == 'undefined') { 45 | playlists[listname] = [] 46 | } 47 | playlists[listname].push(song) 48 | } 49 | 50 | //插入到指定位置 51 | function insertSong(pos, song, list) { 52 | playlists[list].splice(pos, 0, song) 53 | } 54 | 55 | //替换列表中的歌曲 56 | function replace(pos, song, list) { 57 | playlists[list].splice(pos, 1, song) 58 | } 59 | 60 | //播放指定位置歌曲 61 | function setIndex(i) { 62 | console.log("setIndex:" + i + " length:" + playlists[currentList].length) 63 | if (i < 0 || i > (playlists[currentList].length - 1)) { 64 | return 65 | } 66 | 67 | mediaPlayer.pause() 68 | 69 | index = i 70 | 71 | //如果是本地音乐,则直接播放 72 | if (playlists[currentList][index].localpath) { 73 | mediaPlayer.source = playlists[currentList][index].localpath 74 | mediaPlayer.play() 75 | return 76 | } 77 | var curSid = playlists[currentList][index].sid 78 | currentSid = curSid 79 | //如果不是本地音乐,则获取歌曲链接 80 | baiduMusic.getSongLink(curSid) 81 | //获取专辑图片 82 | baiduMusic.getSongInfo(curSid) 83 | } 84 | 85 | //下一首 86 | function next() { 87 | setIndex(index + 1) 88 | } 89 | 90 | //上一首 91 | function previous() { 92 | setIndex(index - 1) 93 | } 94 | 95 | //播放当前歌曲 96 | function play() { 97 | if (mediaPlayer.source == "") { 98 | setIndex(index) 99 | return 100 | } 101 | mediaPlayer.play() 102 | } 103 | 104 | //暂停播放 105 | function pause() { 106 | mediaPlayer.pause() 107 | } 108 | 109 | //从文件加载列表 110 | function loadFrom(filename) { 111 | try { 112 | var savedList = JSON.parse(util.readFile(filename)) 113 | for (var i in savedList) { 114 | playlists[i] = savedList[i] 115 | } 116 | index = 0 117 | } catch (e) { 118 | console.log("Playlist[loadFrom]:" + e) 119 | } 120 | } 121 | 122 | //保存文件 123 | function saveTo(filename) { 124 | util.saveFile(filename, JSON.stringify(playlist.playlists)) 125 | } 126 | 127 | Connections { 128 | target: baiduMusic 129 | //歌曲播放地址获取完毕 130 | onGetSongLinkComplete: { 131 | try { 132 | var link = JSON.parse(songLink) 133 | //如果还是当前播放歌曲,则立即播放,否则不处理 134 | if (playlists[currentList][index].sid == link.data.songList[0].sid) { 135 | var mp3link = link.data.songList[0].songLink 136 | mediaPlayer.source = mp3link 137 | mediaPlayer.play() 138 | } 139 | } catch (e) { 140 | console.log("getLink:" + e) 141 | } 142 | } 143 | } 144 | 145 | Connections { 146 | target: mediaPlayer 147 | onStopped: { 148 | if (mediaplayer.status == MediaPlayer.EndOfMedia) { 149 | next() 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /qml/resource/PlaylistView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | 4 | 5 | //歌曲列表 6 | Rectangle { 7 | width: 800 8 | height: 500 9 | property Playlist playlist 10 | 11 | TableView { 12 | width: parent.width 13 | anchors.fill: parent 14 | onDoubleClicked: { 15 | playlist.setIndex(row) 16 | } 17 | 18 | rowDelegate: Rectangle { 19 | height: 25 20 | color: styleData.selected ? "#448" : (styleData.alternate ? "#eee" : "#fff") 21 | } 22 | TableViewColumn { 23 | role: "listIndex" 24 | title: " " 25 | width: 50 26 | } 27 | 28 | TableViewColumn { 29 | role: "sname" 30 | title: "歌曲名称" 31 | width: 200 32 | } 33 | TableViewColumn { 34 | role: "singer" 35 | title: "歌手" 36 | width: 100 37 | } 38 | model: listModel 39 | } 40 | 41 | ListModel { 42 | id: listModel 43 | } 44 | 45 | //更新列表 46 | function update(listname) { 47 | listModel.clear() 48 | var list = playlist.getSongList(listname) 49 | for (var i = 0; i < list.length; i++) { 50 | list[i].listIndex = i + 1 51 | listModel.append(list[i]) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /qml/resource/SearchResult.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | import CleanPlayerCore 1.0 4 | import "utils.js" as Func 5 | 6 | 7 | //搜索结果 8 | Rectangle { 9 | width: 800 10 | height: 500 11 | property int currentPage: 1 12 | property int pageCount: 1 13 | property string keyword: "" 14 | property BaiduMusic baiduMusic 15 | property Playlist playlist 16 | 17 | TableView { 18 | id: resultView 19 | width: parent.width 20 | anchors.top: parent.top 21 | anchors.left: parent.left 22 | anchors.bottom: bottomTools.top 23 | onDoubleClicked: { 24 | var song = resultModel.get(row) 25 | playlist.addSong(Func.cloneObject(song)) 26 | var last = playlist.count() - 1 27 | playlist.setIndex(last) 28 | } 29 | 30 | rowDelegate: Rectangle { 31 | height: 25 32 | color: styleData.selected ? "#448" : (styleData.alternate ? "#eee" : "#fff") 33 | } 34 | 35 | TableViewColumn { 36 | role: "listIndex" 37 | title: " " 38 | width: 50 39 | } 40 | 41 | TableViewColumn { 42 | role: "sname" 43 | title: "歌曲名称" 44 | width: 200 45 | } 46 | TableViewColumn { 47 | role: "singer" 48 | title: "歌手" 49 | width: 100 50 | } 51 | model: resultModel 52 | } 53 | 54 | ListModel { 55 | id: resultModel 56 | } 57 | 58 | //底部工具条 59 | Rectangle { 60 | id: bottomTools 61 | width: parent.width 62 | height: 40 63 | anchors.left: parent.left 64 | anchors.bottom: parent.bottom 65 | 66 | Component { 67 | id: pageDelegate 68 | Item { 69 | width: 20 70 | height: 20 71 | anchors.verticalCenter: parent.verticalCenter 72 | Text { 73 | anchors.centerIn: parent 74 | text: linkText 75 | onLinkActivated: { 76 | var pagenum = parseInt(link) 77 | console.log(pagenum) 78 | if (pagenum) { 79 | baiduMusic.search(keyword, pagenum) 80 | } 81 | } 82 | } 83 | } 84 | } 85 | 86 | ListModel { 87 | id: pageModel 88 | } 89 | 90 | Button { 91 | id: prevPageButton 92 | enabled: false 93 | text: "<<" 94 | width: 30 95 | anchors.left: parent.left 96 | anchors.leftMargin: 30 97 | anchors.verticalCenter: parent.verticalCenter 98 | onClicked: { 99 | if (currentPage > 1) { 100 | baiduMusic.search(keyword, currentPage - 1) 101 | } 102 | } 103 | } 104 | 105 | ListView { 106 | id: pageView 107 | height: 30 108 | width: 60 109 | anchors.verticalCenter: parent.verticalCenter 110 | anchors.left: prevPageButton.right 111 | anchors.leftMargin: 20 112 | 113 | model: pageModel 114 | delegate: pageDelegate 115 | orientation: ListView.LeftToRight 116 | } 117 | 118 | Button { 119 | enabled: false 120 | width: 30 121 | anchors.left: pageView.right 122 | anchors.leftMargin: 20 123 | anchors.verticalCenter: parent.verticalCenter 124 | id: nextPageButton 125 | text: ">>" 126 | onClicked: { 127 | if (currentPage < pageCount) { 128 | baiduMusic.search(keyword, currentPage + 1) 129 | } 130 | } 131 | } 132 | } 133 | 134 | //返回 135 | function getItems() { 136 | var items = [] 137 | for (var i = 0; i < resultModel.count; i++) { 138 | items.push(resultModel.get(i)) 139 | } 140 | return items 141 | } 142 | 143 | //显示搜索结果 144 | function setResultInfo(curpage, pagecount, keyword_, songList) { 145 | resultModel.clear() 146 | keyword = keyword_ 147 | 148 | //更新页面链接,只显示3页 149 | function updatePageLink(cur, count) { 150 | pageModel.clear() 151 | console.log("cur:" + cur + "count:" + count) 152 | 153 | //显示的第一页和最后一页 154 | var beginPage = cur < 3 ? 1 : cur - 1 155 | var endPage = cur < count - 1 ? cur + 1 : count 156 | 157 | //如果当前页为第一页,则最后一页为3 158 | if (cur == 1 && count >= 3) { 159 | endPage = 3 160 | } 161 | 162 | //如果当前页为最后一页,则起始页为pagecount - 2; 163 | if (cur == count && count >= 3) { 164 | beginPage = count - 2 165 | } 166 | 167 | //调整页码宽度 168 | if (count < 3) { 169 | pageView.width = 20 * count 170 | } else { 171 | pageView.width = 60 172 | } 173 | 174 | prevPageButton.enabled = true 175 | nextPageButton.enabled = true 176 | 177 | if (cur == 1) { 178 | prevPageButton.enabled = false 179 | } 180 | 181 | if (cur == count) { 182 | nextPageButton.enabled = false 183 | } 184 | 185 | console.log(beginPage + "------------" + endPage) 186 | for (var i = beginPage; i <= endPage; i++) { 187 | var link = "" + i + "" 188 | if (i == cur) { 189 | link = "" + i 190 | } 191 | pageModel.append({ 192 | linkText: link 193 | }) 194 | } 195 | } 196 | 197 | try { 198 | currentPage = curpage 199 | pageCount = pagecount 200 | updatePageLink(currentPage, pageCount) 201 | //添加歌曲列表 202 | for (var i in songList) { 203 | songList[i].songItem.listIndex = parseInt(i) + 1 //序号 204 | songList[i].songItem.sid = "" + songList[i].songItem.sid //sid转换为字符串 205 | resultModel.append(songList[i].songItem) 206 | } 207 | } catch (e) { 208 | console.log("SearchResult[setResultInfo:]" + e) 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /qml/resource/SideBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import CleanPlayerCore 1.0 3 | 4 | 5 | //左侧栏 6 | Rectangle { 7 | id: leftList 8 | width: 200 9 | color: "#555555" 10 | 11 | property Playlist playlist 12 | property Container container 13 | 14 | Component { 15 | id: leftListViewDelegate 16 | Item { 17 | width: 200 18 | height: 20 19 | Rectangle { 20 | anchors.fill: parent 21 | color: "gray" 22 | Text { 23 | anchors.centerIn: parent 24 | text: listname 25 | } 26 | 27 | MouseArea { 28 | id: mouseArea 29 | anchors.fill: parent 30 | hoverEnabled: true 31 | onClicked: { 32 | leftListView.currentIndex = index 33 | container.updatePlaylist(listname) 34 | container.showPlaylist() 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | ListModel { 42 | id: leftListModel 43 | } 44 | 45 | ListView { 46 | id: leftListView 47 | anchors.fill: parent 48 | delegate: leftListViewDelegate 49 | model: leftListModel 50 | clip: true 51 | focus: true 52 | 53 | highlight: Component { 54 | Rectangle { 55 | radius: 3 56 | gradient: Gradient { 57 | GradientStop { 58 | position: 0.00 59 | color: "#80a9ccf3" 60 | } 61 | GradientStop { 62 | position: 1.00 63 | color: "#808abae6" 64 | } 65 | } 66 | } 67 | } 68 | 69 | header: Component { 70 | Item { 71 | width: 200 72 | height: 30 73 | Rectangle { 74 | anchors.fill: parent 75 | color: "white" 76 | Text { 77 | anchors.centerIn: parent 78 | font.pixelSize: 16 79 | text: "播放列表" 80 | } 81 | } 82 | } 83 | } 84 | } 85 | 86 | function update() { 87 | for (var i in playlist.playlists) { 88 | leftListModel.append({ 89 | listname: i 90 | }) 91 | } 92 | container.updatePlaylist("默认列表") 93 | container.showPlaylist() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /qml/resource/Suggestion.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import CleanPlayerCore 1.0 3 | import "utils.js" as Func 4 | 5 | 6 | //搜索建议弹出框 7 | Rectangle { 8 | color: "white" 9 | visible: false 10 | 11 | width: 240 12 | height: 200 13 | 14 | property Playlist playlist 15 | property BaiduMusic baiduMusic 16 | 17 | ListModel { 18 | id: suggestionModel 19 | } 20 | 21 | Component { 22 | id: suggestionDelegate 23 | Item { 24 | id: wrapper 25 | width: 200 26 | height: 20 27 | Rectangle { 28 | Text { 29 | text: sname + '-' + singer 30 | } 31 | } 32 | MouseArea { 33 | anchors.fill: parent 34 | onClicked: { 35 | wrapper.ListView.view.currentIndex = index 36 | hide() 37 | var song = suggestionModel.get(index) 38 | playlist.addSong(Func.cloneObject(song)) 39 | var last = playlist.count() - 1 40 | playlist.setIndex(last) 41 | } 42 | } 43 | } 44 | } 45 | 46 | ListView { 47 | id: suggestionView 48 | height: parent.height 49 | width: parent.width 50 | model: suggestionModel 51 | delegate: suggestionDelegate 52 | clip: true 53 | } 54 | 55 | //显示搜索建议 56 | function show() { 57 | visible = true 58 | } 59 | 60 | //隐藏搜索建议 61 | function hide() { 62 | visible = false 63 | } 64 | 65 | //设置显示的歌曲 66 | function setDisplaySongs(songs) { 67 | suggestionModel.clear() 68 | for (var i in songs) { 69 | //转换为字符串 70 | songs[i].sid = "" + songs[i].sid 71 | suggestionModel.append(songs[i]) 72 | } 73 | } 74 | 75 | Connections { 76 | target: baiduMusic 77 | onGetSuggestionComplete: { 78 | try { 79 | var sug = JSON.parse(suggestion) 80 | var data = sug.data 81 | var songs = data.song 82 | setDisplaySongs(songs) 83 | show() 84 | } catch (e) { 85 | console.log("Suggestion[onGetSuggestionComplete]:" + e) 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /qml/resource/TopBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import CleanPlayerCore 1.0 3 | 4 | 5 | //顶部栏 6 | Rectangle { 7 | width: parent.width 8 | height: 60 9 | color: "#444" 10 | property Suggestion suggestion 11 | property BaiduMusic baiduMusic 12 | //左上角 13 | Rectangle { 14 | id: brand 15 | width: 200 16 | height: parent.height 17 | anchors.top: parent.top 18 | anchors.left: parent.left 19 | color: "#333" 20 | Text { 21 | id: brandText 22 | font.pixelSize: 28 23 | color: "#eee" 24 | text: qsTr("CleanPlayer") 25 | anchors.centerIn: parent 26 | } 27 | } 28 | //搜索条 29 | Rectangle { 30 | width: 300 31 | height: 28 32 | radius: 14 33 | 34 | anchors.left: brand.right 35 | anchors.leftMargin: 15 36 | anchors.verticalCenter: parent.verticalCenter 37 | 38 | //输入框 39 | TextInput { 40 | id: input 41 | anchors.left: parent.left 42 | anchors.leftMargin: 12 43 | anchors.right: searchButton.left 44 | anchors.verticalCenter: parent.verticalCenter 45 | font.pixelSize: 15 46 | clip: true 47 | 48 | //输入改变 49 | onTextChanged: { 50 | if (text === "") { 51 | suggestion.hide() 52 | return 53 | } 54 | baiduMusic.getSuggestion(text) 55 | } 56 | 57 | onFocusChanged: { 58 | if (!focus) { 59 | suggestion.hide() 60 | } 61 | } 62 | 63 | //编辑完成,按enter键 64 | onAccepted: { 65 | search() 66 | } 67 | } 68 | 69 | //搜索按钮 70 | Rectangle { 71 | id: searchButton 72 | height: 20 73 | width: 20 74 | anchors.right: parent.right 75 | anchors.rightMargin: 14 76 | anchors.verticalCenter: parent.verticalCenter 77 | Image { 78 | id: searchIcon 79 | anchors.fill: parent 80 | source: "qrc:/image/image/search.png" 81 | } 82 | MouseArea { 83 | anchors.fill: parent 84 | onClicked: search() 85 | } 86 | } 87 | } 88 | 89 | //边条 90 | Rectangle { 91 | width: parent.width 92 | height: 2 93 | color: "#E61E16" 94 | anchors.bottom: parent.bottom 95 | } 96 | 97 | //点击搜索按钮或按Enter 98 | function search() { 99 | if (input.text == "") { 100 | return 101 | } 102 | baiduMusic.search(input.text, 1) 103 | suggestion.hide() 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /qml/resource/utils.js: -------------------------------------------------------------------------------- 1 | //克隆对象 2 | function cloneObject(obj) { 3 | var o 4 | switch (typeof obj) { 5 | case 'undefined': 6 | break 7 | case 'string': 8 | o = obj + '' 9 | break 10 | case 'number': 11 | o = obj - 0 12 | break 13 | case 'boolean': 14 | o = obj 15 | break 16 | case 'object': 17 | if (obj === null) { 18 | o = null 19 | } else { 20 | if (obj instanceof Array) { 21 | o = [] 22 | for (var i = 0, len = obj.length; i < len; i++) { 23 | o.push(cloneObject(obj[i])) 24 | } 25 | } else { 26 | o = { 27 | 28 | } 29 | for (var k in obj) { 30 | o[k] = cloneObject(obj[k]) 31 | } 32 | } 33 | } 34 | break 35 | default: 36 | o = obj 37 | break 38 | } 39 | return o 40 | } 41 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Clean Player 2 | 3 | --- 4 | 5 | > 此项目不再维护, 6 | 7 | > 因为接口变更,可能存在不能播放的情况 8 | 9 | CleanPlayer是Baidu音乐的在线客户端程序,基于Qt5.4,支持歌曲搜索、在线播放、歌词解析。 10 | 11 | 点击专辑封面进入或退出歌词界面 12 | 13 | ### TODO 14 | 15 | + 自定义列表 16 | + 本地歌曲 17 | 18 | 19 | ### 外观 20 | 21 | ![](./doc/image/appearence.png) 22 | 23 | ![](./doc/image/lyric.png) 24 | 25 | ### 百度音乐API 26 | + 搜索建议 27 | 28 | http://sug.music.baidu.com/info/suggestion?format=json&word=关键字&version=2&from=0&callback=函数名 29 | + 搜索(需要从html中截取有效信息) 30 | 31 | http://music.baidu.com/search?key=关键字&start=起始位置&size=20 32 | 起始位置 =(当前页面数-1)× 20 33 | + 歌曲信息 34 | 35 | http://play.baidu.com/data/music/songinfo?songIds=歌曲id 或 36 | http://play.baidu.com/data/music/songinfo?songIds=id1,id2,id3 37 | + 歌曲链接 38 | 39 | http://play.baidu.com/data/music/songlink?songIds=歌曲id&type=m4a,mp3 40 | 41 | 42 | ### 其他 43 | 1. 基于[QT5.4](http://qt-project.org/downloads),遵守[LGPL协议](http://www.gnu.org/licenses/lgpl.html); 44 | 2. 软件由QML结合C++编写而成,QML编写界面,C++编写网络、文件读写; 45 | 3. 此程序只作为学习交流使用; 46 | 47 | -------------------------------------------------------------------------------- /res.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | qml/Main.qml 4 | qml/resource/SearchResult.qml 5 | qml/resource/Playlist.qml 6 | qml/resource/PlaylistView.qml 7 | qml/resource/BottomBar.qml 8 | qml/resource/Container.qml 9 | qml/resource/utils.js 10 | qml/resource/Suggestion.qml 11 | qml/resource/TopBar.qml 12 | qml/resource/SideBar.qml 13 | qml/resource/Lyric.qml 14 | 15 | 16 | image/next.png 17 | image/pause.png 18 | image/play.png 19 | image/previous.png 20 | image/slider-point.png 21 | image/search.png 22 | image/volume-high-icon.png 23 | image/volume-low-icon.png 24 | image/volume-medium-icon.png 25 | image/volume-mute-icon.png 26 | image/logo.png 27 | 28 | 29 | -------------------------------------------------------------------------------- /updatesummary.txt: -------------------------------------------------------------------------------- 1 | Clean Player v 150704.01 2 | 1.重写整个程序 3 | 2.支持歌曲搜索 4 | 3.暂只支持在线播放 5 | 6 | ------------------------------ 7 | 8 | Clean Player v 0.4.2 9 | ——2014/6/13 10 | 11 | 1.优化下载逻辑,切换歌曲后,中断并清空先前的下载; 12 | 2.随机模式下,优化歌曲切换速度; 13 | 3.修复快速切换歌曲时偶尔崩溃的bug; 14 | 4.调整背景。 15 | 16 | ------------------------------ 17 | 18 | Clean Player v 0.4.1 19 | ———2014/6/9 20 | 1.当前歌词字体大小+2; 21 | 2.重新编写readme。 22 | 23 | ------------------------------ 24 | 25 | Clean Player v 0.4.0 26 | ——2014/6/6 27 | 28 | 1.增强歌词解析功能,能够解析offset和[00:00]类歌词; 29 | 2.列表展开和收缩功能按钮放置于右侧,鼠标滑过时唤出; 30 | 3.增加清空列表功能; 31 | 4.增加网络请求时箭头旋转动画; 32 | 5.修复若干bug。 33 | 34 | ------------------------------ 35 | 36 | Clean Player v 0.3.4 37 | ——2014/6/5 38 | 39 | 1.列表收缩后歌词字体放大; 40 | 2.修复快速收缩展开列表时,列表和歌词重叠的bug; 41 | 3.增加功能,双击歌词切换列表展开收缩状态; 42 | 3.增加最小化按钮。 43 | 44 | ------------------------------ 45 | 46 | Clean Player v 0.3.2 47 | ——2014/6/4 48 | 49 | 1.优化界面配色 50 | 2.控件位置微调; 51 | 3.增加播放列表展开,收缩功能; 52 | 4.修复载入大量文件时软件卡顿的bug。 53 | 54 | Clean Plyer v 0.3.0 55 | ——2014/6/3 56 | 57 | ------------------------------ 58 | 59 | 1.优化歌词解析,能够解析单行多时间歌词; 60 | 2.增加播放模式和音量调节; 61 | 3.界面局部调整。 62 | 4.修复大量下载请求可能崩溃的bug; 63 | 5.修复歌词路径绑定出错导致程序崩溃的bug。 64 | 65 | ------------------------------ 66 | 67 | Clean Player v 0.2.0 68 | ——2014/6/2 69 | 70 | 1.重写了网络解析模块,用栈管理下载顺序; 71 | 2.改用json交换C++和QML数据; 72 | 3.调整界面; 73 | 4.修复大量bug。 74 | 75 | ------------------------------ 76 | 77 | Clean Player v 0.1.0 78 | ——2014/5/30 79 | 80 | 1.实现基本的播放功能; 81 | 2.实现在线搜索并下载歌词、专辑封面; 82 | 3.实现歌词解析,简单播放列表。 83 | 84 | -------------------------------------------------------------------------------- /util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include 3 | 4 | Util::Util(QObject *parent) : QObject(parent) 5 | { 6 | 7 | } 8 | 9 | Util::~Util() 10 | { 11 | 12 | } 13 | 14 | QString Util::readFile(QString filename) 15 | { 16 | QFile file(filename); 17 | if (!file.open(QIODevice::ReadOnly | QIODevice::Text)){ 18 | return ""; 19 | } 20 | return file.readAll(); 21 | } 22 | 23 | void Util::saveFile(QString filename, QString content) 24 | { 25 | QFile file(filename); 26 | if (!file.open(QIODevice::WriteOnly | QIODevice::Text)){ 27 | return; 28 | } 29 | file.write(content.toUtf8()); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | 4 | #include 5 | #include 6 | 7 | class Util : public QObject 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit Util(QObject *parent = 0); 12 | ~Util(); 13 | Q_INVOKABLE QString readFile(QString filename); 14 | Q_INVOKABLE void saveFile(QString filename,QString content); 15 | signals: 16 | 17 | public slots: 18 | }; 19 | 20 | #endif // UTIL_H 21 | --------------------------------------------------------------------------------