player.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. /**************************************************
  2. * MKOnlinePlayer v2.41
  3. * 播放器主功能模块
  4. * 编写:mengkun(https://mkblog.cn)
  5. * 时间:2018-3-13
  6. *************************************************/
  7. // 播放器功能配置
  8. var mkPlayer = {
  9. api: "http://music.ytxmgy.com/api.php", // api地址https://www.mqtv.cc/libs/XMusic.api.php 第二个http://music.ytxmgy.com/api.php
  10. loadcount: 20, // 搜索结果一次加载多少条
  11. method: "POST", // 数据传输方式(POST/GET)
  12. defaultlist: 3, // 默认要显示的播放列表编号
  13. autoplay: true, // 是否自动播放(true/false) *此选项在移动端可能无效
  14. coverbg: true, // 是否开启封面背景(true/false) *开启后会有些卡
  15. mcoverbg: true, // 是否开启[移动端]封面背景(true/false)
  16. dotshine: true, // 是否开启播放进度条的小点闪动效果[不支持IE](true/false) *开启后会有些卡
  17. mdotshine: false, // 是否开启[移动端]播放进度条的小点闪动效果[不支持IE](true/false)
  18. volume: 0.6, // 默认音量值(0~1之间)
  19. version: "v2.41", // 播放器当前版本号(仅供调试)
  20. debug: false // 是否开启调试模式(true/false)
  21. };
  22. /*******************************************************
  23. * 以下内容是播放器核心文件,不建议进行修改,否则可能导致播放器无法正常使用!
  24. *
  25. * 哈哈,吓唬你的!想改就改呗!不过建议修改之前先【备份】,要不然改坏了弄不好了。
  26. ******************************************************/
  27. // 存储全局变量
  28. var rem = [];
  29. // 音频错误处理函数
  30. function audioErr() {
  31. // 没播放过,直接跳过
  32. if(rem.playlist === undefined) return true;
  33. if(rem.errCount > 10) { // 连续播放失败的歌曲过多
  34. layer.msg('似乎出了点问题~播放已停止');
  35. rem.errCount = 0;
  36. } else {
  37. rem.errCount++; // 记录连续播放失败的歌曲数目
  38. layer.msg('当前歌曲播放失败,自动播放下一首');
  39. nextMusic(); // 切换下一首歌
  40. }
  41. }
  42. // 点击暂停按钮的事件
  43. function pause() {
  44. if(rem.paused === false) { // 之前是播放状态
  45. rem.audio[0].pause(); // 暂停
  46. } else {
  47. // 第一次点播放
  48. if(rem.playlist === undefined) {
  49. rem.playlist = rem.dislist;
  50. musicList[1].item = musicList[rem.playlist].item; // 更新正在播放列表中音乐
  51. // 正在播放 列表项已发生变更,进行保存
  52. playerSavedata('playing', musicList[1].item); // 保存正在播放列表
  53. listClick(0);
  54. }
  55. rem.audio[0].play();
  56. }
  57. }
  58. // 循环顺序
  59. function orderChange() {
  60. var orderDiv = $(".btn-order");
  61. orderDiv.removeClass();
  62. switch(rem.order) {
  63. case 1: // 单曲循环 -> 列表循环
  64. orderDiv.addClass("player-btn btn-order btn-order-list");
  65. orderDiv.attr("title", "列表循环");
  66. layer.msg("列表循环");
  67. rem.order = 2;
  68. break;
  69. case 3: // 随机播放 -> 单曲循环
  70. orderDiv.addClass("player-btn btn-order btn-order-single");
  71. orderDiv.attr("title", "单曲循环");
  72. layer.msg("单曲循环");
  73. rem.order = 1;
  74. break;
  75. // case 2:
  76. default: // 列表循环(其它) -> 随机播放
  77. orderDiv.addClass("player-btn btn-order btn-order-random");
  78. orderDiv.attr("title", "随机播放");
  79. layer.msg("随机播放");
  80. rem.order = 3;
  81. }
  82. }
  83. // 播放
  84. function audioPlay() {
  85. rem.paused = false; // 更新状态(未暂停)
  86. refreshList(); // 刷新状态,显示播放的波浪
  87. $(".btn-play").addClass("btn-state-paused"); // 恢复暂停
  88. $(".music-cover").addClass("btn-xz"); // 旋转
  89. if((mkPlayer.dotshine === true && !rem.isMobile) || (mkPlayer.mdotshine === true && rem.isMobile)) {
  90. $("#music-progress .mkpgb-dot").addClass("dot-move"); // 小点闪烁效果
  91. }
  92. var music = musicList[rem.playlist].item[rem.playid]; // 获取当前播放的歌曲信息
  93. var msg = " 正在播放: " + music.name + " - " + music.artist; // 改变浏览器标题
  94. // 清除定时器
  95. if (rem.titflash !== undefined )
  96. {
  97. clearInterval(rem.titflash);
  98. }
  99. // 标题滚动
  100. titleFlash(msg);
  101. }
  102. // 标题滚动
  103. function titleFlash(msg) {
  104. // 截取字符
  105. var tit = function() {
  106. msg = msg.substring(1,msg.length)+ msg.substring(0,1);
  107. document.title = msg;
  108. };
  109. // 设置定时间 300ms滚动
  110. rem.titflash = setInterval(function(){tit()}, 300);
  111. }
  112. // 暂停
  113. function audioPause() {
  114. rem.paused = true; // 更新状态(已暂停)
  115. $(".list-playing").removeClass("list-playing"); // 移除其它的正在播放
  116. $(".btn-play").removeClass("btn-state-paused"); // 取消暂停
  117. $(".music-cover").removeClass("btn-xz"); // 旋转
  118. $("#music-progress .dot-move").removeClass("dot-move"); // 小点闪烁效果
  119. // 清除定时器
  120. if (rem.titflash !== undefined )
  121. {
  122. clearInterval(rem.titflash);
  123. }
  124. document.title = rem.webTitle; // 改变浏览器标题
  125. }
  126. // 播放上一首歌
  127. function prevMusic() {
  128. playList(rem.playid - 1);
  129. }
  130. // 播放下一首歌
  131. function nextMusic() {
  132. switch (rem.order ? rem.order : 1) {
  133. case 1,2:
  134. playList(rem.playid + 1);
  135. break;
  136. case 3:
  137. if (musicList[1] && musicList[1].item.length) {
  138. var id = parseInt(Math.random() * musicList[1].item.length);
  139. playList(id);
  140. }
  141. break;
  142. default:
  143. playList(rem.playid + 1);
  144. break;
  145. }
  146. }
  147. // 自动播放时的下一首歌
  148. function autoNextMusic() {
  149. if(rem.order && rem.order === 1) {
  150. playList(rem.playid);
  151. } else {
  152. nextMusic();
  153. }
  154. }
  155. // 歌曲时间变动回调函数
  156. function updateProgress(){
  157. // 暂停状态不管
  158. if(rem.paused !== false) return true;
  159. // 同步进度条
  160. music_bar.goto(rem.audio[0].currentTime / rem.audio[0].duration);
  161. // 同步歌词显示
  162. scrollLyric(rem.audio[0].currentTime);
  163. }
  164. // 显示的列表中的某一项点击后的处理函数
  165. // 参数:歌曲在列表中的编号
  166. function listClick(no) {
  167. // 记录要播放的歌曲的id
  168. var tmpid = no;
  169. // 调试信息输出
  170. if(mkPlayer.debug) {
  171. console.log("点播了列表中的第 " + (no + 1) + " 首歌 " + musicList[rem.dislist].item[no].name);
  172. }
  173. // 搜索列表的歌曲要额外处理
  174. if(rem.dislist === 0) {
  175. // 没播放过
  176. if(rem.playlist === undefined) {
  177. rem.playlist = 1; // 设置播放列表为 正在播放 列表
  178. rem.playid = musicList[1].item.length - 1; // 临时设置正在播放的曲目为 正在播放 列表的最后一首
  179. }
  180. // 获取选定歌曲的信息
  181. var tmpMusic = musicList[0].item[no];
  182. // 查找当前的播放列表中是否已经存在这首歌
  183. for(var i=0; i<musicList[1].item.length; i++) {
  184. if(musicList[1].item[i].id == tmpMusic.id && musicList[1].item[i].source == tmpMusic.source) {
  185. tmpid = i;
  186. playList(tmpid); // 找到了直接播放
  187. return true; // 退出函数
  188. }
  189. }
  190. // 将点击的这项追加到正在播放的条目的下方
  191. musicList[1].item.splice(rem.playid + 1, 0, tmpMusic);
  192. tmpid = rem.playid + 1;
  193. // 正在播放 列表项已发生变更,进行保存
  194. playerSavedata('playing', musicList[1].item); // 保存正在播放列表
  195. } else { // 普通列表
  196. // 与之前不是同一个列表了(在播放别的列表的歌曲)或者是首次播放
  197. if((rem.dislist !== rem.playlist && rem.dislist !== 1) || rem.playlist === undefined) {
  198. rem.playlist = rem.dislist; // 记录正在播放的列表
  199. musicList[1].item = musicList[rem.playlist].item; // 更新正在播放列表中音乐
  200. // 正在播放 列表项已发生变更,进行保存
  201. playerSavedata('playing', musicList[1].item); // 保存正在播放列表
  202. // 刷新正在播放的列表的动画
  203. refreshSheet(); // 更改正在播放的列表的显示
  204. }
  205. }
  206. playList(tmpid);
  207. return true;
  208. }
  209. // 播放正在播放列表中的歌曲
  210. // 参数:歌曲在列表中的ID
  211. function playList(id) {
  212. // 第一次播放
  213. if(rem.playlist === undefined) {
  214. pause();
  215. return true;
  216. }
  217. // 没有歌曲,跳出
  218. if(musicList[1].item.length <= 0) return true;
  219. // ID 范围限定
  220. if(id >= musicList[1].item.length) id = 0;
  221. if(id < 0) id = musicList[1].item.length - 1;
  222. // 记录正在播放的歌曲在正在播放列表中的 id
  223. rem.playid = id;
  224. // 如果链接为空,则 ajax 获取数据后再播放
  225. if(musicList[1].item[id].url === null || musicList[1].item[id].url === "") {
  226. ajaxUrl(musicList[1].item[id], play);
  227. } else {
  228. play(musicList[1].item[id]);
  229. }
  230. }
  231. // 初始化 Audio
  232. function initAudio() {
  233. rem.audio = $('<audio></audio>').appendTo('body');
  234. // 应用初始音量
  235. rem.audio[0].volume = volume_bar.percent;
  236. // 绑定歌曲进度变化事件
  237. rem.audio[0].addEventListener('timeupdate', updateProgress); // 更新进度
  238. rem.audio[0].addEventListener('play', audioPlay); // 开始播放了
  239. rem.audio[0].addEventListener('pause', audioPause); // 暂停
  240. $(rem.audio[0]).on('ended', autoNextMusic); // 播放结束
  241. rem.audio[0].addEventListener('error', audioErr); // 播放器错误处理
  242. }
  243. // 播放音乐
  244. // 参数:要播放的音乐数组
  245. function play(music) {
  246. // 调试信息输出
  247. if(mkPlayer.debug) {
  248. console.log('开始播放 - ' + music.name);
  249. console.info('id: "' + music.id + '",\n' +
  250. 'name: "' + music.name + '",\n' +
  251. 'artist: "' + music.artist + '",\n' +
  252. 'album: "' + music.album + '",\n' +
  253. 'source: "' + music.source + '",\n' +
  254. 'url_id: "' + music.url_id + '",\n' +
  255. 'pic_id: "' + music.pic_id + '",\n' +
  256. 'lyric_id: "' + music.lyric_id + '",\n' +
  257. 'pic: "' + music.pic + '",\n' +
  258. 'url: "' + music.url + '"');
  259. }
  260. // 遇到错误播放下一首歌
  261. if(music.url == "err") {
  262. audioErr(); // 调用错误处理函数
  263. return false;
  264. }
  265. addHis(music); // 添加到播放历史
  266. // 如果当前主界面显示的是播放历史,那么还需要刷新列表显示
  267. if(rem.dislist == 2 && rem.playlist !== 2) {
  268. loadList(2);
  269. } else {
  270. refreshList(); // 更新列表显示
  271. }
  272. try {
  273. rem.audio[0].pause();
  274. rem.audio.attr('src', music.url);
  275. rem.audio[0].play();
  276. } catch(e) {
  277. audioErr(); // 调用错误处理函数
  278. return;
  279. }
  280. rem.errCount = 0; // 连续播放失败的歌曲数归零
  281. music_bar.goto(0); // 进度条强制归零
  282. changeCover(music); // 更新封面展示
  283. ajaxLyric(music, lyricCallback); // ajax加载歌词
  284. music_bar.lock(false); // 取消进度条锁定
  285. }
  286. // 我的要求并不高,保留这一句版权信息可好?
  287. // 保留了,你不会损失什么;而保留版权,是对作者最大的尊重。
  288. console.info('欢迎使用 MKOnlinePlayer!\n当前版本:'+mkPlayer.version+' \n作者:mengkun(https://mkblog.cn)\n歌曲来源于各大音乐平台\nGithub:https://github.com/mengkunsoft/MKOnlineMusicPlayer');
  289. // 音乐进度条拖动回调函数
  290. function mBcallback(newVal) {
  291. var newTime = rem.audio[0].duration * newVal;
  292. // 应用新的进度
  293. rem.audio[0].currentTime = newTime;
  294. refreshLyric(newTime); // 强制滚动歌词到当前进度
  295. }
  296. // 音量条变动回调函数
  297. // 参数:新的值
  298. function vBcallback(newVal) {
  299. if(rem.audio[0] !== undefined) { // 音频对象已加载则立即改变音量
  300. rem.audio[0].volume = newVal;
  301. }
  302. if($(".btn-quiet").is('.btn-state-quiet')) {
  303. $(".btn-quiet").removeClass("btn-state-quiet"); // 取消静音
  304. }
  305. if(newVal === 0) $(".btn-quiet").addClass("btn-state-quiet");
  306. playerSavedata('volume', newVal); // 存储音量信息
  307. }
  308. // 下面是进度条处理
  309. var initProgress = function(){
  310. // 初始化播放进度条
  311. music_bar = new mkpgb("#music-progress", 0, mBcallback);
  312. music_bar.lock(true); // 未播放时锁定不让拖动
  313. // 初始化音量设定
  314. var tmp_vol = playerReaddata('volume');
  315. tmp_vol = (tmp_vol != null)? tmp_vol: (rem.isMobile? 1: mkPlayer.volume);
  316. if(tmp_vol < 0) tmp_vol = 0; // 范围限定
  317. if(tmp_vol > 1) tmp_vol = 1;
  318. if(tmp_vol == 0) $(".btn-quiet").addClass("btn-state-quiet"); // 添加静音样式
  319. volume_bar = new mkpgb("#volume-progress", tmp_vol, vBcallback);
  320. };
  321. // mk进度条插件
  322. // 进度条框 id,初始量,回调函数
  323. mkpgb = function(bar, percent, callback){
  324. this.bar = bar;
  325. this.percent = percent;
  326. this.callback = callback;
  327. this.locked = false;
  328. this.init();
  329. };
  330. mkpgb.prototype = {
  331. // 进度条初始化
  332. init : function(){
  333. var mk = this,mdown = false;
  334. // 加载进度条html元素
  335. $(mk.bar).html('<div class="mkpgb-bar"></div><div class="mkpgb-cur"></div><div class="mkpgb-dot"></div>');
  336. // 获取偏移量
  337. mk.minLength = $(mk.bar).offset().left;
  338. mk.maxLength = $(mk.bar).width() + mk.minLength;
  339. // 窗口大小改变偏移量重置
  340. $(window).resize(function(){
  341. mk.minLength = $(mk.bar).offset().left;
  342. mk.maxLength = $(mk.bar).width() + mk.minLength;
  343. });
  344. // 监听小点的鼠标按下事件
  345. $(mk.bar + " .mkpgb-dot").mousedown(function(e){
  346. e.preventDefault(); // 取消原有事件的默认动作
  347. });
  348. // 监听进度条整体的鼠标按下事件
  349. $(mk.bar).mousedown(function(e){
  350. if(!mk.locked) mdown = true;
  351. barMove(e);
  352. });
  353. // 监听鼠标移动事件,用于拖动
  354. $("html").mousemove(function(e){
  355. barMove(e);
  356. });
  357. // 监听鼠标弹起事件,用于释放拖动
  358. $("html").mouseup(function(e){
  359. mdown = false;
  360. });
  361. function barMove(e) {
  362. if(!mdown) return;
  363. var percent = 0;
  364. if(e.clientX < mk.minLength){
  365. percent = 0;
  366. }else if(e.clientX > mk.maxLength){
  367. percent = 1;
  368. }else{
  369. percent = (e.clientX - mk.minLength) / (mk.maxLength - mk.minLength);
  370. }
  371. mk.callback(percent);
  372. mk.goto(percent);
  373. return true;
  374. }
  375. mk.goto(mk.percent);
  376. return true;
  377. },
  378. // 跳转至某处
  379. goto : function(percent) {
  380. if(percent > 1) percent = 1;
  381. if(percent < 0) percent = 0;
  382. this.percent = percent;
  383. $(this.bar + " .mkpgb-dot").css("left", (percent*100) +"%");
  384. $(this.bar + " .mkpgb-cur").css("width", (percent*100)+"%");
  385. return true;
  386. },
  387. // 锁定进度条
  388. lock : function(islock) {
  389. if(islock) {
  390. this.locked = true;
  391. $(this.bar).addClass("mkpgb-locked");
  392. } else {
  393. this.locked = false;
  394. $(this.bar).removeClass("mkpgb-locked");
  395. }
  396. return true;
  397. }
  398. };