From 6e5b22162b080caabd9c821b0588108d5c287469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A5=87=E8=B6=A3=E4=BF=9D=E7=BD=97?= Date: Tue, 10 May 2022 00:46:22 +0800 Subject: [PATCH] Feat: Import KPlayer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用奇趣播放器播放音乐 --- src/components/Base/KPlayer/KPlayer.css | 282 ++++++++++ src/components/Base/KPlayer/KPlayerScript.js | 563 +++++++++++++++++++ src/components/Base/KPlayer/index.tsx | 72 +++ src/pages/netease.tsx | 38 +- 4 files changed, 951 insertions(+), 4 deletions(-) create mode 100644 src/components/Base/KPlayer/KPlayer.css create mode 100644 src/components/Base/KPlayer/KPlayerScript.js create mode 100644 src/components/Base/KPlayer/index.tsx diff --git a/src/components/Base/KPlayer/KPlayer.css b/src/components/Base/KPlayer/KPlayer.css new file mode 100644 index 0000000..cabe89a --- /dev/null +++ b/src/components/Base/KPlayer/KPlayer.css @@ -0,0 +1,282 @@ +@charset "UTF-8"; + +/* ---- + +# Kico Player 0.9 +# By: Dreamer-Paul +# Last Update: 2021.8.30 + +一个简洁强大的网页音乐播放器。 + +本代码为奇趣保罗原创,并遵守 MIT 开源协议。欢迎访问我的博客:https://paugram.com + +---- */ + +kplayer{ + --kp-primary: #3498db; + --kp-secondly: #ffc670; + --kp-gray: #aaa; + + display: block; + overflow: auto; + font-size: 14px; + line-height: 1.5em; + background: #fff; + border-radius: 4px; + position: relative; + box-shadow: 0 1px 5px 1px rgba(0, 0, 0, .1), 0 0 5px rgba(0, 0, 0, .1); +} +kplayer *{ + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* - 面板 */ +kplayer .kp-header{ + height: 4em; + cursor: default; + overflow: hidden; + line-height: 1em; + user-select: none; + position: relative; +} + +/* - 专辑 */ +kplayer .kp-cover{ + width: 4em; + height: 4em; + float: left; + margin-right: 1em; + vertical-align: middle; + transition: background .3s; + background: var(--kp-gray) url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMqaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAyMSA3OS4xNTQ5MTEsIDIwMTMvMTAvMjktMTE6NDc6MTYgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTNERjEzRjQzMDQzMTFFOEI5NkQ5NTkwMTU2NDBFMzUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTNERjEzRjUzMDQzMTFFOEI5NkQ5NTkwMTU2NDBFMzUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1M0RGMTNGMjMwNDMxMUU4Qjk2RDk1OTAxNTY0MEUzNSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1M0RGMTNGMzMwNDMxMUU4Qjk2RDk1OTAxNTY0MEUzNSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/uACZBZG9iZQBkwAAAAAEDABUEAwYKDQAABu4AAAdjAAAJgAAAC1D/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) center/cover; +} + +/* - 时间 */ +kplayer .kp-time{ + left: 0; + width: 4em; + opacity: 0; + color: #fff; + display: block; + line-height: 4em; + position: absolute; + text-align: center; + transition: opacity .3s; + background: rgba(0, 0, 0, .5); + border-top-left-radius: 4px; +} +kplayer .kp-header:hover .kp-time{ + opacity: 1; +} + +/* - 信息 */ +kplayer .kp-info{ + margin: .6em 6.5em .5em 5em; +} +kplayer .kico-title, kplayer .kico-artist{ + display: block; + overflow: hidden; + white-space: nowrap; + padding-bottom: .5em; + text-overflow: ellipsis; +} +kplayer .kico-artist{ + color: #aaa; + color: var(--kp-gray); + font-size: .85em; +} + +/* - 按钮 */ +kplayer .kp-controls{ + top: .5em; + left: 5em; + right: 6.5em; + opacity: 0; + background: #fff; + position: absolute; + transition: opacity .3s; +} +kplayer .kp-header:hover .kp-controls{ + opacity: 1; +} + +kplayer .control-item{ + color: #3498db; + color: var(--kp-primary); + cursor: pointer; + display: inline-block; +} +kplayer .control-item svg{ + width: 2.5em; + height: 2.5em; +} + +/* - 设置 */ +kplayer .kp-settings{ + top: .75em; + right: .5em; + position: absolute; +} + +kplayer .setting-item{ + width: 2em; + color: #aaa; + color: var(--kp-gray); + cursor: pointer; + line-height: 2em; + text-align: center; + display: inline-block; +} +kplayer .setting-item svg{ + width: 1.5em; + height: 1.5em; + vertical-align: middle; +} + +/* - 进度条 */ +kplayer .kp-bar{ + left: 4em; + right: 0; + bottom: 0; + height: .4em; + cursor: pointer; + position: absolute; + background: #efefef; +} +kplayer .kp-bar .loaded, +kplayer .kp-bar .played{ + top: 0; + height: .4em; + max-width: 100%; + position: absolute; +} +kplayer .kp-bar .loaded{ + background: #e5e5e5; +} +kplayer .kp-bar .played{ + background-color: #ffc670; + background-color: var(--kp-secondly); +} + +/* 下拉载入 +-------------------------------- */ +kplayer .kp-drop{ + top: 0; + left: 0; + right: 0; + bottom: 0; + opacity: 0; + color: #fff; + visibility: hidden; + position: absolute; + pointer-events: none; + background: rgba(0, 0, 0, .3); + transition: opacity .3s, visibility .3s; +} +kplayer .kp-drop.active{ + opacity: 1; + visibility: visible; +} +kplayer .kp-drop span{ + top: 0; + left: 0; + right: 0; + bottom: 0; + height: 1em; + margin: auto; + display: block; + font-size: 2em; + line-height: 1em; + text-align: center; + position: absolute; +} + +/* 播放列表 +-------------------------------- */ +kplayer .kp-list{ + max-height: 0; + overflow: auto; + list-style: none; + user-select: none; + transition: max-height .3s; + border-bottom: 1px solid #eee; +} +kplayer .kp-list.show{ + max-height: 15em; + max-height: 50vh; +} + +kplayer .kp-list::-webkit-scrollbar{ width: 5px } +kplayer .kp-list::-webkit-scrollbar-thumb{ + background: #eee; +} +kplayer .kp-list::-webkit-scrollbar-thumb:hover{ + background-color: #3498db; + background-color: var(--kp-primary); +} + +kplayer .list-item{ + cursor: pointer; + overflow: hidden; + padding: .75em 1em; + position: relative; + white-space: nowrap; + text-overflow: ellipsis; + transition: background .3s, padding .3s; +} +kplayer .list-item.current{ + padding-left: 1.5em; +} +kplayer .list-item.current:before{ + content: ''; + top: 1em; + left: -.5em; + width: 1em; + height: 1em; + display: block; + position: absolute; + background-color: #ffc670; + background-color: var(--kp-secondly); + transform: rotate(45deg); +} +kplayer .list-item:nth-child(1n){ + background: rgba(0, 0, 0, .02); +} +kplayer .list-item:nth-child(2n){ + background: #fff; +} +kplayer .list-item:hover{ + background: rgba(0, 0, 0, .05); +} + +kplayer .item-number, kplayer .item-artist{ + opacity: .6; +} + +kplayer .item-number{ + margin-right: .75em; +} +kplayer .item-title:after{ + content: '-'; + margin: 0 .25em; +} + +/* 歌词 +-------------------------------- */ +kplayer .kp-lyrics{ + color: #666; + text-align: center; +} +kplayer .kp-lyrics span{ + padding: 1em; + display: block; + overflow: hidden; + line-height: 1em; + white-space: nowrap; + text-overflow: ellipsis; +} +kplayer .kp-lyrics span:empty:after{ + content: "Music..."; +} diff --git a/src/components/Base/KPlayer/KPlayerScript.js b/src/components/Base/KPlayer/KPlayerScript.js new file mode 100644 index 0000000..4e163c8 --- /dev/null +++ b/src/components/Base/KPlayer/KPlayerScript.js @@ -0,0 +1,563 @@ +/* ---- + +# KPlayer 0.9 +# By: Dreamer-Paul +# Last Update: 2021.9.7 + +一个简洁强大的网页音乐播放器。 + +本代码为奇趣保罗原创,并遵守 MIT 开源协议。欢迎访问我的博客:https://paugram.com + +---- */ + +var KPlayer = function KPlayer (settings) { + var that = this; + + // 状态记录 + var current = { + id: 0, + last_id: 0, + playlist: [], + randlist: [], + lyric: [], + lyric_index: 0, + play_mode: 0, // 默认、循环、随机模式 normal(0) loop(1) random(2) + page_title: document.title + }; + + var icons = { + left: '', + right: '', + play: '', + pause: '', + + none: '', + low: '', + mid: '', + max: '', + + loop_all: '', + loop_single: '', + rand: '', + + list: '' + }; + + // 小工具 + var tools = { + // 拖拽文件名称识别 + name: function (filename) { + filename = filename.replace(/\.ogg|\.mp3|\.wav|\.mp4?/, ""); + var sp = filename.split(/\s\-\s|\s\–\s/); + return {title: sp[1], artist: sp[0]}; + }, + // 快捷创建对象 + create: function (tag, set) { + var obj = document.createElement(tag); + + if(!set) return obj; + + if(set.class) obj.className = set.class; + if(set.text) obj.innerText = set.text; + if(set.html) obj.innerHTML = set.html; + + return obj; + }, + // 快捷添加对象 + append: function (obj, child) { + for(var i = 0; i < child.length; i++){ + obj.appendChild(child[i]); + } + } + }; + + // 元素模块 + var elements = { + container: tools.create("kplayer"), + player: document.createElement("audio"), + buttons: { // 操作按钮 + toggle: tools.create("div", { class: "control-item kico-toggle", html: icons.play }), + prev: tools.create("div", { class: "control-item kico-prev", html: icons.left }), + next: tools.create("div", { class: "control-item kico-next", html: icons.right }), + mode: tools.create("div", { class: "setting-item kico-mode", html: icons.loop_all }), + list: tools.create("div", { class: "setting-item kico-list", html: icons.list }), + volume: tools.create("div", { class: "setting-item kico-volume", html: icons.max }) + }, + details: { // 歌曲详情 + title: tools.create("span", { class: "kico-title", text: "欢迎使用 Kico Player" }), + artist: tools.create("span", { class: "kico-artist", text: "奇趣保罗" }), + cover: tools.create("div", { class: "kp-cover" }), + time: tools.create("div", { class: "kp-time", text: "00:00" }) + }, + bar: { // 播放进度条 + wrap: tools.create("div", { class: "kp-bar" }), + loaded: tools.create("div", { class: "loaded" }), + played: tools.create("div", { class: "played" }) + }, + playlist: tools.create("div", { class: "kp-list" }), + lyric: tools.create("span", { text: "欢迎使用 Kico Player" }), + list_items: [] + }; + + var wrapper = { + wrap: tools.create("div", { class: "kp-header" }), + info: tools.create("div", { class: "kp-info" }), + ctrl: tools.create("div", { class: "kp-controls" }), + sets: tools.create("div", { class: "kp-settings" }), + lyric: tools.create("div", { class: "kp-lyrics" }), + drop: tools.create("div", { class: "kp-drop", html: "释放鼠标以添加歌曲" }) + }; + + var interval; + var interval_fc = function () { + elements.details.time.innerText = calc.sec_time(elements.player.currentTime); + elements.bar.played.style.width = (elements.player.currentTime / elements.player.duration) * 100 + "%"; + + if(current.playlist[current.id].lyric){ + if(current.lyric[current.lyric_index] && elements.player.currentTime > current.lyric[current.lyric_index].time){ + elements.lyric.innerText = current.lyric[current.lyric_index].text; + if(current.lyric[current.lyric_index].sub_text){ + elements.lyric.innerHTML = current.lyric[current.lyric_index].text + "

" + current.lyric[current.lyric_index].sub_text; + } + else{ + elements.lyric.innerText = current.lyric[current.lyric_index].text; + } + current.lyric_index++; + } + } + }; + + // 构造模块 + var build = { + elements: function () { + // 歌手歌名 + tools.append(wrapper.info, [elements.details.title, elements.details.artist]); + + // 控制按钮 + tools.append(wrapper.ctrl, [elements.buttons.prev, elements.buttons.toggle, elements.buttons.next]); + + // 设置按钮 + tools.append(wrapper.sets, [elements.buttons.volume, elements.buttons.mode, elements.buttons.list]); + + // 整体框架 + tools.append(wrapper.wrap, [elements.details.cover, elements.details.time, wrapper.info, wrapper.ctrl, wrapper.sets, elements.bar.wrap]); + + wrapper.lyric.appendChild(elements.lyric); + + tools.append(elements.container, [wrapper.wrap, elements.playlist, wrapper.lyric, wrapper.drop]); + settings.container.appendChild(elements.container); // 总添加 + + // 拖动播放 + elements.container.addEventListener("dragenter", function (e) { + e.preventDefault(); + wrapper.drop.classList.add("active"); + }); + elements.container.addEventListener("dragover", function (e) { + e.preventDefault(); + wrapper.drop.classList.add("active"); + }); + elements.container.addEventListener("drag", function (e) { + e.preventDefault(); + wrapper.drop.classList.add("active"); + }); + elements.container.addEventListener("dragleave", function (e) { + e.preventDefault(); + wrapper.drop.classList.remove("active"); + }); + elements.container.addEventListener("drop", function (e) { + e.preventDefault(); + wrapper.drop.classList.remove("active"); + var arr = []; + + for(var i = 0; i < e.dataTransfer.items.length; i++){ + var s = e.dataTransfer.items[i].getAsFile(); + + if (window.createObjectURL) { + url = window.createObjectURL(s) + } + else if (window.createBlobURL) { + url = window.createBlobURL(s) + } + else if (window.URL && window.URL.createObjectURL) { + url = window.URL.createObjectURL(s) + } + else if (window.webkitURL && window.webkitURL.createObjectURL) { + url = window.webkitURL.createObjectURL(s) + } + + var n = tools.name(s.name); + + var single = { + title: n.title, + artist: n.artist, + link: url + }; + + arr.push(single); + } + + that.add(arr); + that.jump(current.playlist.length - 1); + }); + + // 播放器 + elements.player.volume = 1; + + elements.player.addEventListener("play", function () { + if(!interval) interval = setInterval(interval_fc, 500); + elements.buttons.toggle.innerHTML = icons.pause; + actions.title_change(true); + }); + elements.player.addEventListener("pause", function () { + interval = clearInterval(interval); + elements.buttons.toggle.innerHTML = icons.play; + actions.title_change(false); + }); + elements.player.addEventListener("progress", function () { + var percentage = elements.player.buffered.length ? elements.player.buffered.end(elements.player.buffered.length - 1) / elements.player.duration : 0; + elements.bar.loaded.style.width = percentage * 100 + "%"; + }); + elements.player.addEventListener("error", function () { + interval = clearInterval(interval); + elements.buttons.toggle.innerHTML = icons.play; + actions.title_change(false); + + elements.details.title.innerText = ":("; + elements.details.artist.innerText = "发生了错误"; + }); + elements.player.addEventListener("ended", function () { + // 列表和随机列表循环 + if(current.play_mode === 0 || current.play_mode === 2){ + that.next(); + } + // 单曲循环 + else if(current.play_mode === 1){ + current.lyric_index = 0; + that.play(); + elements.lyric.innerText = current.playlist[current.id].title + " (" + current.playlist[current.id].artist + ")"; + } + }); + + // 按钮们 + elements.buttons.toggle.addEventListener("click", that.toggle); + elements.buttons.prev.addEventListener("click", that.prev); + elements.buttons.next.addEventListener("click", that.next); + elements.buttons.mode.addEventListener("click", that.mode); + elements.buttons.list.addEventListener("click", that.list_toggle); + elements.buttons.volume.addEventListener("click", actions.volume); + + // 进度条 + elements.bar.wrap.appendChild(elements.bar.loaded); + elements.bar.wrap.appendChild(elements.bar.played); + elements.bar.wrap.addEventListener("click", function (t) { + if(elements.player.currentTime){ + elements.player.currentTime = (t.offsetX / elements.bar.wrap.clientWidth) * elements.player.duration; + } + if(current.playlist[current.id].lyric){ + calc.lyric(); + } + }); + }, + playlist: function (item) { + function add(s) { + current.playlist.push(s); + current.randlist.push(s); + + var id = current.playlist.length - 1; + var li = tools.create("div", { class: "list-item" }); + + li.innerHTML = "" + (id + 1) + + "" + current.playlist[id].title + + "" + current.playlist[id].artist + ""; + + li.onclick = function () { + if(current.id === id){ + that.toggle(); + } + else { + current.id = id; + that.jump(current.id); + } + }; + + elements.playlist.appendChild(li); + elements.list_items.push(li); + } + + item.forEach(function (t) { + add(t); + }); + }, + randlist: function () { + function rand_gen(min, max) { + return Math.floor(Math.random() * (max - min) + min); + } + + for(var i = 0; i < current.randlist.length - 1; i++){ + i++; + var a = rand_gen(0, current.randlist.length - 1); + var b = rand_gen(0, current.randlist.length - 1); + var cache = current.randlist[a]; + + current.randlist[a] = current.randlist[b]; + current.randlist[b] = cache; + } + }, + lyric: function (lyric, sub_lyric) { + var time, text, sub_text, single; + + // var text = lyric.match(/\[[0-9]{2,}:[0-9]{2}\.[0-9]{2,}\](\S| )+/g).replace(/\[[0-9]{2,}:[0-9]{2}\.[0-9]{2,}\]/g); + + time = lyric.match(/\d{2,}:\d{2,}.\d{1,4}/g); + text = lyric.match(/\d{1}\]+.*/g); + + if(sub_lyric) sub_text = sub_lyric.match(/\d{1}\]+.*/g); + + if(settings.debug) console.log(time, text, sub_text); + + // 时间和歌词数量解析结果对的上 + if (time && (time.length === text.length)) { + current.lyric = time.map((item, index) => { + const _range = item.match(/\d+/g); + const min = parseInt(_range[0] * 60); + const sec = parseInt(_range[1]); + const ms = parseFloat(_range[2].substr(0, 2) / 60); + + const _lyric = { + time: min + sec + ms, + text: text[index].substr(2) + } + + if (sub_text && text.length === sub_text.length) { + _lyric.sub_text = sub_text[index].substr(2); + } + + return _lyric; + }); + + if(settings.debug) console.log(current.lyric); + } + else { + current.lyric = [{ + time: 0, + text: "当前歌词不支持滚动" + }]; + } + } + }; + + // 播放与暂停切换 + this.play = function () { + if(elements.player.src){ + elements.player.play(); + } + }; + this.pause = function () { + if(elements.player.src){ + elements.player.pause(); + } + }; + this.toggle = function () { + if(elements.player.src){ + elements.player.paused ? elements.player.play() : elements.player.pause(); + } + else{ + that.jump(current.id); + } + }; + + this.jump = function (id) { + if(id === undefined) return; + + // 更新信息 + current.id = id; + current.lyric_index = 0; + + elements.details.title.innerText = current.playlist[current.id].title; + elements.details.artist.innerText = current.playlist[current.id].artist; + elements.details.cover.style.backgroundImage = current.playlist[current.id].cover ? "url('" + current.playlist[current.id].cover + "')" : ""; + elements.player.src = current.playlist[current.id].link; + elements.player.play(); + + if("mediaSession" in navigator){ + var _meta = { + title: current.playlist[current.id].title, + artist: current.playlist[current.id].artist, + }; + + if(current.playlist[current.id].album) _meta.album = current.playlist[current.id].album; + if(current.playlist[current.id].cover) _meta.artwork = [{ src: current.playlist[current.id].cover }] + + navigator.mediaSession.metadata = new MediaMetadata(_meta); + navigator.mediaSession.setActionHandler('play', that.play); + navigator.mediaSession.setActionHandler('pause', that.pause); + navigator.mediaSession.setActionHandler('previoustrack', that.prev); + navigator.mediaSession.setActionHandler('nexttrack', that.next); + } + + // 上次播放记录 + if(elements.list_items[current.last_id]) elements.list_items[current.last_id].classList.remove("current"); + elements.list_items[current.id].classList.add("current"); + current.last_id = current.id; + + // 如果有歌词 + if(current.playlist[current.id].lyric && current.playlist[current.id].sub_lyric){ + build.lyric(current.playlist[current.id].lyric, current.playlist[current.id].sub_lyric); + elements.lyric.innerText = current.playlist[current.id].title + " (" + current.playlist[current.id].artist + ")"; + } + else if(current.playlist[current.id].lyric){ + build.lyric(current.playlist[current.id].lyric); + elements.lyric.innerText = current.playlist[current.id].title + " (" + current.playlist[current.id].artist + ")"; + } + else{ + elements.lyric.innerText = "纯音乐,请欣赏..."; + } + + if(settings.debug) console.log("当前 ID:" + current.id); + }; + + // 上一首 + this.prev = function () { + if(current.play_mode === 0 || current.play_mode === 1) { + current.id > 0 ? current.id-- : current.id = current.playlist.length - 1; + that.jump(current.id); + } + else if(current.play_mode === 2){ + var a = current.randlist.indexOf(current.playlist[current.id]); + a === 0 ? current.id = current.randlist.length - 1 : current.id = current.playlist.indexOf(current.randlist[a - 1]); + that.jump(current.id); + } + }; + + // 下一首 + this.next = function () { + current.lyric_index = 0; + + if(current.play_mode === 0 || current.play_mode === 1){ + current.id < current.playlist.length-1 ? current.id++ : current.id = 0; + that.jump(current.id); + } + else if(current.play_mode === 2){ + var a = current.randlist.indexOf(current.playlist[current.id]); + a === current.randlist.length - 1 ? current.id = current.playlist.indexOf(current.randlist[0]) : current.id = current.playlist.indexOf(current.randlist[a + 1]); + that.jump(current.id); + } + }; + + // 播放模式 + this.mode = function () { + var btn = elements.buttons.mode; + + switch (current.play_mode){ + case 0: current.play_mode = 1; btn.innerHTML = icons.loop_single; break; + case 1: current.play_mode = 2; btn.innerHTML = icons.rand; build.randlist(); break; + case 2: current.play_mode = 0; btn.innerHTML = icons.loop_all; break; + } + + if(settings.debug) console.log("当前播放模式:" + current.play_mode); + }; + + // 添加歌曲 + this.add = function (item) { + build.playlist(item); + }; + + // 移除歌曲 + this.remove = function () { + if(current.playlist.length > 0){ + if(current.randlist){ + var del = current.randlist.indexOf(current.playlist[current.playlist.length - 1]); + current.randlist.splice(del, 1); + } + elements.playlist.removeChild(elements.list_items[elements.list_items.length - 1]); + current.playlist.pop(); + elements.list_items.pop(); + } + }; + + // 播放列表切换显示 + this.list_toggle = function () { + elements.playlist.classList.toggle("show"); + }; + + // 事件模块 + var actions = { + volume: function () { + var btn = elements.buttons.volume; + + switch (elements.player.volume){ + case 1: elements.player.volume = 0.75; btn.innerHTML = icons.mid; break; + case 0.75: elements.player.volume = 0.50; btn.innerHTML = icons.low; break; + case 0.50: elements.player.volume = 0.25; btn.innerHTML = icons.none; break; + case 0.25: elements.player.volume = 1; btn.innerHTML = icons.max; break; + } + }, + title_change: function (stat) { + settings.title_change && current.playlist[current.id] && stat === true ? document.title = "▶ " + current.playlist[current.id].title + " - " + current.page_title : document.title = current.page_title; + } + }; + + var calc = { + lyric: function () { + var num = 0; + + current.lyric.forEach(function (t, index) { + if(elements.player.currentTime > t.time){ + num = index; + return false; + } + }); + + current.lyric_index = num; + + if(current.lyric[num].sub_text){ + elements.lyric.innerHTML = current.lyric[num].text.toString() + "

" + current.lyric[num].sub_text.toString(); + } + else{ + elements.lyric.innerText = current.lyric[num].text.toString(); + } + }, + sec_time: function (second) { + if(isNaN(second)){ + return '00:00'; + } + else{ + var add0 = function add0(num) { + return num < 10 ? '0' + num : num; + }; + var min = parseInt(second / 60); + var sec = parseInt(second - min * 60); + var hours = parseInt(min / 60); + var minAdjust = parseInt(second / 60 - 60 * parseInt(second / 60 / 60)); + + return second >= 3600 ? add0(hours) + ':' + add0(minAdjust) + ':' + add0(sec) : add0(min) + ':' + add0(sec); + } + }, + random: function (min, max) { + return Math.floor(min + Math.random() * (max - min)); + } + }; + + // 执行函数 + (function () { + build.elements(); + build.playlist(settings.playlist); + + if(settings.show_list === true){ + that.list_toggle(); + } + if(settings.debug){ + console.log("MP3 兼容情况:" + elements.player.canPlayType("audio/mp3")); + console.log("OGG 兼容情况:" + elements.player.canPlayType("audio/ogg")); + console.log("WAV 兼容情况:" + elements.player.canPlayType("audio/wav")); + } + + var user_agent = navigator.userAgent.toLowerCase().match(/mobile/g); + + if(settings.autoplay === true && user_agent === null) that.jump(current.id); + })(); +}; + +console.log("%c Kico Player %c https://paugram.com ","color: #fff; margin: 1em 0; padding: 5px 0; background: #3498db;","margin: 1em 0; padding: 5px 0; background: #efefef;"); + +export default KPlayer; diff --git a/src/components/Base/KPlayer/index.tsx b/src/components/Base/KPlayer/index.tsx new file mode 100644 index 0000000..fc74b0f --- /dev/null +++ b/src/components/Base/KPlayer/index.tsx @@ -0,0 +1,72 @@ +// React +import React, { useEffect, useRef, forwardRef, useImperativeHandle } from "react"; + + +// UI +import "@/components/Base/KPlayer/KPlayer.css"; + +// @ts-ignore +import KPlayerScript from "@/components/Base/KPlayer/KPlayerScript"; + + +// Interface +import { Ref } from "react"; + +export interface KPlayerSong { + id: number + title: string + artist: string + album: string + alias: string + cover: string + link: string + lyric: string + sub_lyric: string +} + +export interface KPlayerProps {} + +export interface KPlayerRef { + add: (songs: KPlayerSong[]) => void + remove: () => void +} + +type IContainer = typeof KPlayerScript + + +// Components +function KPlayer(props: KPlayerProps, ref: Ref) { + const playerRef = useRef(null); + const containerRef = useRef(null); + + useImperativeHandle(ref, () => ({ + add: (songs) => { + playerRef.current && playerRef.current.add(songs); + }, + remove: () => { + playerRef.current && playerRef.current.remove(); + } + })); + + useEffect(() => { + if (!containerRef.current) return; + + playerRef.current = new KPlayerScript({ + container: containerRef.current, + playlist: [], + show_list: true + }); + + return () => { + // Todo + // kp.destroy(); + } + + }, []); + + return ( +
+ ) +} + +export default forwardRef(KPlayer); diff --git a/src/pages/netease.tsx b/src/pages/netease.tsx index fa1d789..e89080a 100644 --- a/src/pages/netease.tsx +++ b/src/pages/netease.tsx @@ -1,5 +1,5 @@ // React -import React, { useEffect } from "react"; +import React, { useEffect, useRef } from "react"; // UI @@ -9,15 +9,45 @@ import ArticleHead from "@/components/Layout/ArticleHead"; // Tool +import neteaseRequest from "@/server/api/netease"; import { siteUrl } from "@/utils/tool"; +import KPlayer, { KPlayerRef } from "@/components/Base/KPlayer"; + +const songs = [19032267, 862345682, 471969791, 528361653, 563727655, 29966701, 1309903594, 1935948260, 28638748]; // Components function Netease() { + const playerRef = useRef(null); + useEffect(() => { prism.highlightAll(); }, []); + const getSongInfo = async (id: number) => { + const song = await neteaseRequest({ query: { id }}); + + if (!song || !song.data) return; + + playerRef.current && playerRef.current.add([song.data]); + } + + useEffect(() => { + songs.forEach(id => { + getSongInfo(id); + }); + }, []); + + const onAddMusic = () => { + const _id = prompt("请输入一个网易云音乐 ID", "517567145"); + + _id && getSongInfo(Number(_id)); + } + + const onRemoveMusic = () => { + playerRef.current && playerRef.current.remove(); + } + return (
@@ -157,10 +187,10 @@ function Netease() {

测试小工具:

使用 Kico Player 播放音乐

-
+
- - + +

常见问题: