# TadmPlayer **Repository Path**: tadm/TadmPlayer ## Basic Information - **Project Name**: TadmPlayer - **Description**: A simple player by vue family~ - **Primary Language**: HTML - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: http://tadm.gitee.io/tadmplayer - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-04-04 - **Last Updated**: 2021-03-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Tadm-Player-Vue
> 利用 Vue 全家桶实现多源音乐平台,妈妈再也不用担心我听不到喜欢的歌曲啦! - 前台 --- vue 全家桶 - 后端 - 网易云 - [NetEaseApi](https://github.com/Binaryify/NeteaseCloudMusicApi) - 酷狗 - 暂只支持开发环境 - 其他数据源敬请期待~ ## Preview - Github: https://github.com/Liuhongwei3/TadmPlayerVue - Online Demo: https://tadm.gitee.io/tadmplayer (建议 PC 端体验) ## Usage ``` git clone https://github.com/Liuhongwei3/TadmPlayerVue.git npm install npm run serve npm run build ``` ## Announcement 本项目仅供学习使用,不提供任何资源存储,歌曲等数据来自互联网,其版权归资源所属版权方~ ## 项目技能树 `Vue` + `Vue-Router` + `Vuex` +`Vue-lazyload` + `ES6` + `Axios` + `Webpack` + `Fontawesome` + `Better-scroll` + `Element-UI` + `async-await` + `NProgress` + `v-viewer` + `...` ## Features - Play music and remember songs to history - Visit comments & Enjoy The CloudHotComments - Enjoy Top Song list & playlists - Visit your NetEaseCloudMusic Song list & Followers - Search more songs & Users & details & albums & singers & ... - Visit Top Singers & enjoy your favorite player's songs & listen them - watch mvs (all/top/...) - watch shared/searched videos - Maybe more ~ ## Shortcut > 部分截图或许过时,以线上站点为主。 - 首页 ![home.png](https://i.loli.net/2020/11/18/CRBuvm471nl2ch3.png) - 榜单 ![top.png](https://i.loli.net/2020/11/18/9TWd3MQZFyPBcmi.png) - 搜索 ![search.png](https://i.loli.net/2020/11/18/vKi1g9DbndBp8V7.png) - 用户 ![user.png](https://i.loli.net/2020/11/18/AEzPmRhc4Y6apgX.png) - 评论 ![comment.png](https://i.loli.net/2020/11/18/VXxeghmzdoQC5bY.png) - More > preview online web~ ## 项目文件目录树
点击查看

tadm-player-vue
├── babel.config.js
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
├── README.md
├── src
│   ├── App.vue
│   ├── assets
│   │   ├── 404.jpg
│   │   └── play_icon.png
│   ├── components
│   │   ├── common
│   │   │   ├── backTop
│   │   │   │   └── BackTop.vue
│   │   │   ├── items
│   │   │   │   └── Items.vue
│   │   │   ├── Nav-bar
│   │   │   │   └── NavBar.vue
│   │   │   ├── noResult
│   │   │   │   └── NoResult.vue
│   │   │   └── scroll
│   │   │       ├── HorizontalScroll.vue
│   │   │       └── VerticalScroll.vue
│   │   └── content
│   │       ├── CommContent.vue
│   │       ├── DetailContent.vue
│   │       ├── Drawer.vue
│   │       ├── Event.vue
│   │       ├── EventContent.vue
│   │       ├── LoadMoreFoot.vue
│   │       ├── LogContent.vue
│   │       ├── MvContent.vue
│   │       ├── RLyric.vue
│   │       ├── ShowMv.vue
│   │       ├── ShowVideo.vue
│   │       ├── UserContent.vue
│   │       └── VideoContent.vue
│   ├── css
│   │   ├── App.css
│   │   └── element.css
│   ├── element.js
│   ├── features.js
│   ├── filters.js
│   ├── main.js
│   ├── network
│   │   ├── netease
│   │   │   ├── doReq.js
│   │   │   ├── index.js
│   │   │   └── requests.js
│   │   ├── qq
│   │   │   ├── doReq.js
│   │   │   ├── index.js
│   │   │   └── requests.js
│   │   └── req.js
│   ├── router
│   │   └── index.js
│   ├── store
│   │   ├── actions.js
│   │   ├── index.js
│   │   ├── mutations.js
│   │   └── state.js
│   ├── utils.js
│   └── views
│       ├── About.vue
│       ├── Album.vue
│       ├── Comment.vue
│       ├── Detail.vue
│       ├── History.vue
│       ├── Home.vue
│       ├── HotDetail.vue
│       ├── Mv.vue
│       ├── MyVideo.vue
│       ├── Play.vue
│       ├── Search.vue
│       ├── Singer.vue
│       ├── Top.vue
│       └── User.vue
└── vue.config.js
## 细节处理 ### Axios 封装 - index.js ```js import axios from "axios"; import NProgress from "nprogress"; export function request(config) { const baseURL = process.env.NODE_ENV === "development" ? "http://localhost:3000/" : "https://api.mtnhao.com/"; const instance = axios.create({ baseURL, timeout: 10000, }); instance.interceptors.request.use( (config) => { NProgress.start(); return config; }, (error) => { const { response } = error; if (!response) { error = { response: { statusText: "网络错误,请检查您的网络连接!" } }; } return Promise.reject(error); } ); instance.interceptors.response.use( (response) => { NProgress.done(); return response; }, (error) => { const { response } = error; if (!response) { error = { response: { statusText: "网络错误,请检查您的网络连接!" } }; } return Promise.reject(error); } ); return instance(config); // Promise } ``` - doReq.js ```js import { request } from "./index"; import { to, notify } from "@/utils"; const doReq = async (url) => { // notify("info", "信息提示", "加载数据中!"); let [err, data] = await to(request({ url })); if (err) { notify("error", "加载错误", err.response.statusText); return false; } else { return data; } }; export default doReq; ``` - request.js ```js import { request } from "./index"; import doReq from "./doReq"; // 0: pc // 1: android // 2: iphone // 3: ipad const getBanner = async (type = 0) => { let flag = await doReq(`/banner?type=${type}`); if (!flag || !flag.data || !flag.data.banners) { return []; } return flag.data.banners; }; more... ``` ### 歌词部分 - 歌词解析 ```js export function parseLyric(data) { let lrc = data.lrc.lyric; let tlrc = data.tlyric.lyric; let lyrics = lrc.split("\n"); let tlyrics = tlrc ? tlrc.split("\n") : []; let lrcObj = []; let lrcMap = new Map(); let tlrcMap = new Map(); let lrcTime = new Map(); let j = 0; for (let i = 0; i < lyrics.length; i++) { let lyric = decodeURIComponent(lyrics[i]); let timeReg = /\[\d*:\d*((\.|\:)\d*)*\]/g; let timeRegExpArr = lyric.match(timeReg); if (!timeRegExpArr) continue; let clause = lyric.replace(timeReg, ""); for (let k = 0, h = timeRegExpArr.length; k < h; k++) { let t = timeRegExpArr[k]; let min = Number(String(t.match(/\[\d*/i)).slice(1)), sec = Number(String(t.match(/\:\d*/i)).slice(1)); let time = min * 60 + sec; if (clause) { lrcTime.set(time, j++); lrcMap.set(time, clause); lrcObj.push({ time: time, text: clause }); } } } for (let i = 0; i < tlyrics.length; i++) { let tlyric = decodeURIComponent(tlyrics[i]); let timeReg = /\[\d*:\d*((\.|\:)\d*)*\]/g; let timeRegExpArr1 = tlyric.match(timeReg); if (!timeRegExpArr1) continue; let clause1 = tlyric.replace(timeReg, ""); for (let k = 0, h = timeRegExpArr1.length; k < h; k++) { let t = timeRegExpArr1[k]; let min = Number(String(t.match(/\[\d*/i)).slice(1)), sec = Number(String(t.match(/\:\d*/i)).slice(1)); let time = min * 60 + sec; if (clause1) { tlrcMap.set(time, clause1); } } } return [lrcObj, lrcMap, tlrcMap, lrcTime]; } ``` - 歌词滚动 ```html
{{ item.text }}
{{ item.ttext }}
``` ```js computed: { lyricTop() { return `transform :translate3d(0, ${ -lineH * (this.lyricIndex - this.top) }px, 0)`; }, }, watch: { lyricIndex(newValue) { this.lineH = document .getElementsByClassName("lyrics") [newValue].getBoundingClientRect().height; this.calcTop(); if (newValue > this.top && newValue < this.lyrics.length - this.top) { let y = (newValue - this.top) * this.lineH; this.$refs.musicLyric.scrollTo(0, -y, 1000); } }, }, methods:{ calcTop() { let style = document.documentElement.getBoundingClientRect(); let height = style.width >= 768 ? style.height * 0.5 : style.height * 0.4; this.top = Math.floor(height / this.lineH / 2); }, } ``` - 当前歌词高亮 ```js audio.addEventListener("timeupdate", () => { let curTime = Math.floor(audio.currentTime); // 使用 map 代替之前的 for 循环比较,避免不必要的性能消耗 if (this.lyricsMap.has(curTime)) { this.activeLyric = this.lyricsMap.get(curTime); } if (this.lyricsTime.has(curTime)) { this.lyricIndex = this.lyricsTime.get(curTime); } }); ``` ### 音乐下载 ```js // PC export function createDownload(name, player, data) { let blob = new Blob([data], { type: "audio/mpeg;charset=utf-8" }); let downloadElement = document.createElement("a"); let href = window.URL.createObjectURL(blob); downloadElement.href = href; downloadElement.download = name + "-" + player + ".mp3"; document.body.appendChild(downloadElement); downloadElement.click(); document.body.removeChild(downloadElement); window.URL.revokeObjectURL(href); } ``` ### Better-scroll - 公共组件(Vertical) ```html ``` ```js // https://better-scroll.github.io/docs/zh-CN/ import BScroll from "better-scroll"; import { debounce, throttle } from "@/utils"; export default { name: "VerticalScroll", data() { return { scroll: null, }; }, props: { probeType: { type: Number, default: 3, }, data: { type: Array, default: () => { return []; }, }, listenScroll: { type: Boolean, default: false, }, pullUpLoad: { type: Boolean, default: false, }, }, mounted() { setTimeout(this.__initScroll, 500); window.addEventListener("resize", this.refresh); }, methods: { __initScroll() { // 1.初始化BScroll对象 if (!this.$refs.wrapper) return; this.scroll = new BScroll(this.$refs.wrapper, { probeType: this.probeType, click: true, dblclick: true, stopPropagation: true, mouseWheel: true, pullUpLoad: this.pullUpLoad, }); // 2.将监听事件回调 this.listenScroll && this.scroll.on( "scroll", throttle((position) => { this.$emit("scroll", position); }) ); // 3.监听上拉到底部 this.pullUpLoad && this.scroll.on( "pullingUp", this.pullUpLoad && debounce(() => { this.$emit("pullingUp"); }) ); }, refresh() { // 重新计算 BetterScroll,当 DOM 结构发生变化的时候务必要调用确保滚动的效果正常 this.scroll && this.scroll.refresh && this.scroll.refresh(); }, finishPullUp() { // finishPullUp: 这个类似控制一个开关,比如在触发 pullingUp 事件的时候,插件肯定会把一个开关给关掉,防止用户重复上拉 // 在数据加载完成以后,需要执行 finishPullUp() 把开关打开,以便下次可以继续执行上拉刷新 this.scroll && this.scroll.finishPullUp && this.scroll.finishPullUp(); }, scrollTo(x, y, time) { this.scroll && this.scroll.scrollTo && this.scroll.scrollTo(x, y, time); }, }, beforeDestroy() { this.scroll.destroy(); }, }; ``` - Usage ```html ``` ### async-await 统一错误处理 ```js export function to(promise) { return promise .then((data) => { return [null, data]; }) .catch((err) => { return [err]; }); } ``` ### 懒加载 > 由于本站图片较多,因此使用了**懒加载以及分页**的策略来避免性能消耗过大 ### 防抖 & 节流 ```js export function debounce(func, delay = 500, immediate = false) { let timer; return function(...args) { let context = this; timer && clearTimeout(timer); if (immediate) { let callNow = !timer; timer = setTimeout(() => (timer = null), delay); if (callNow) { func.apply(context, args); } } else { timer = setTimeout(() => func.apply(context, ...args), delay); } }; } export function throttle(func, delay = 500, immediate = false) { let prev = Date.now(); return function(...args) { let now = Date.now(), context = this; if (now - prev >= delay) { func.apply(context, args); prev = Date.now(); } }; } ``` ### 其他封装 > 详见 `components` 文件夹 ## About > If you like it,Thanks to star and talk more~ ## Thanks > NetEaseCloudMusic & NetEaseMusic & All tools producer ~ Github Repo: https://github.com/Liuhongwei3/TadmPlayerVue Gitee Repo: https://gitee.com/tadm/TadmPlayer