├── .eslintignore ├── .env.server ├── .env.production ├── .env ├── commitlint.config.js ├── .husky ├── pre-commit └── commit-msg ├── src ├── static │ └── images │ │ ├── close_@2x.png │ │ ├── close_@3x.png │ │ ├── logo_@2x.png │ │ ├── logo_@3x.png │ │ ├── music-ico.png │ │ ├── search_@2x.png │ │ ├── search_@3x.png │ │ ├── active-list_@2x.png │ │ ├── active-list_@3x.png │ │ ├── current-type1.png │ │ ├── current-type2.png │ │ ├── current-type3.png │ │ ├── current-type4.png │ │ ├── current-type5.png │ │ ├── icon-goback_@2x.png │ │ ├── icon-goback_@3x.png │ │ ├── icon-list_@2x.png │ │ ├── icon-list_@3x.png │ │ ├── icon-next_@2x.png │ │ ├── icon-next_@3x.png │ │ ├── icon-pause_@2x.png │ │ ├── icon-pause_@3x.png │ │ ├── icon-play_@2x.png │ │ ├── icon-play_@3x.png │ │ ├── icon-prev_@2x.png │ │ ├── icon-prev_@3x.png │ │ ├── loop-play_@2x.png │ │ ├── loop-play_@3x.png │ │ ├── order-play_@2x.png │ │ ├── order-play_@3x.png │ │ ├── play-next_@2x.png │ │ ├── play-next_@3x.png │ │ ├── play-pause_@2x.png │ │ ├── play-pause_@3x.png │ │ ├── play-play_@2x.png │ │ ├── play-play_@3x.png │ │ ├── play-prev_@2x.png │ │ ├── play-prev_@3x.png │ │ ├── random-play_@2x.png │ │ ├── random-play_@3x.png │ │ ├── singer-default.jpg │ │ ├── collect-white_@2x.png │ │ ├── collect-white_@3x.png │ │ ├── current-type1_@3x.png │ │ ├── current-type2_@3x.png │ │ ├── current-type3_@3x.png │ │ ├── current-type4_@3x.png │ │ └── current-type5_@3x.png ├── index.html ├── store │ ├── index.ts │ ├── reducers │ │ └── index.ts │ └── actions │ │ └── index.ts ├── App.tsx ├── components │ ├── list-item.tsx │ ├── lrc-color.tsx │ ├── drop-list.tsx │ ├── detail-list.tsx │ ├── lrc-scroll.tsx │ ├── play-operate.tsx │ └── time-wrap.tsx ├── less │ ├── list.less │ ├── mixins │ │ └── index.less │ ├── suspend-lyric.less │ ├── play-operate.less │ ├── player.less │ ├── app.less │ ├── header.less │ └── play-detail.less ├── main.tsx ├── types │ └── index.ts ├── ts │ └── adapt.ts ├── api │ └── index.ts └── pages │ ├── player.tsx │ ├── header.tsx │ ├── list.tsx │ ├── suspend-lyric.jsx │ └── play-detail.jsx ├── dist ├── static │ ├── images │ │ ├── logo_@2x.1fddc3f.png │ │ ├── logo_@3x.d488aa1.png │ │ ├── play-play_@2x.3c465a4.png │ │ ├── play-play_@3x.4dadb1b.png │ │ ├── play-pause_@2x.1f2a1bc.png │ │ ├── play-pause_@3x.db72d15.png │ │ └── singer-default.a96a15f.jpg │ ├── js │ │ └── runtime.246fc33a.js │ └── css │ │ ├── vendors.2438495c.css │ │ └── app.04b37dd3.css └── index.html ├── .gitignore ├── .prettierrc.js ├── lint-staged.config.js ├── postcss.config.js ├── stylelint.config.js ├── tsconfig.node.json ├── babel.config.js ├── tsconfig.json ├── config └── index.ts ├── server ├── index.ts └── song.json ├── .eslintrc.js ├── README.md └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | dll/ 4 | src/libs/ -------------------------------------------------------------------------------- /.env.server: -------------------------------------------------------------------------------- 1 | SERVER_PORT = 8088 2 | TS_NODE_PROJECT = tsconfig.node.json -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NODE_ENV = production 2 | TS_NODE_PROJECT = tsconfig.node.json -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NODE_ENV = development 2 | PORT = 8080 3 | TS_NODE_PROJECT = tsconfig.node.json -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged --allow-empty 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /src/static/images/close_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/close_@2x.png -------------------------------------------------------------------------------- /src/static/images/close_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/close_@3x.png -------------------------------------------------------------------------------- /src/static/images/logo_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/logo_@2x.png -------------------------------------------------------------------------------- /src/static/images/logo_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/logo_@3x.png -------------------------------------------------------------------------------- /src/static/images/music-ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/music-ico.png -------------------------------------------------------------------------------- /src/static/images/search_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/search_@2x.png -------------------------------------------------------------------------------- /src/static/images/search_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/search_@3x.png -------------------------------------------------------------------------------- /src/static/images/active-list_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/active-list_@2x.png -------------------------------------------------------------------------------- /src/static/images/active-list_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/active-list_@3x.png -------------------------------------------------------------------------------- /src/static/images/current-type1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/current-type1.png -------------------------------------------------------------------------------- /src/static/images/current-type2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/current-type2.png -------------------------------------------------------------------------------- /src/static/images/current-type3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/current-type3.png -------------------------------------------------------------------------------- /src/static/images/current-type4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/current-type4.png -------------------------------------------------------------------------------- /src/static/images/current-type5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/current-type5.png -------------------------------------------------------------------------------- /src/static/images/icon-goback_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/icon-goback_@2x.png -------------------------------------------------------------------------------- /src/static/images/icon-goback_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/icon-goback_@3x.png -------------------------------------------------------------------------------- /src/static/images/icon-list_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/icon-list_@2x.png -------------------------------------------------------------------------------- /src/static/images/icon-list_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/icon-list_@3x.png -------------------------------------------------------------------------------- /src/static/images/icon-next_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/icon-next_@2x.png -------------------------------------------------------------------------------- /src/static/images/icon-next_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/icon-next_@3x.png -------------------------------------------------------------------------------- /src/static/images/icon-pause_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/icon-pause_@2x.png -------------------------------------------------------------------------------- /src/static/images/icon-pause_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/icon-pause_@3x.png -------------------------------------------------------------------------------- /src/static/images/icon-play_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/icon-play_@2x.png -------------------------------------------------------------------------------- /src/static/images/icon-play_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/icon-play_@3x.png -------------------------------------------------------------------------------- /src/static/images/icon-prev_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/icon-prev_@2x.png -------------------------------------------------------------------------------- /src/static/images/icon-prev_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/icon-prev_@3x.png -------------------------------------------------------------------------------- /src/static/images/loop-play_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/loop-play_@2x.png -------------------------------------------------------------------------------- /src/static/images/loop-play_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/loop-play_@3x.png -------------------------------------------------------------------------------- /src/static/images/order-play_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/order-play_@2x.png -------------------------------------------------------------------------------- /src/static/images/order-play_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/order-play_@3x.png -------------------------------------------------------------------------------- /src/static/images/play-next_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/play-next_@2x.png -------------------------------------------------------------------------------- /src/static/images/play-next_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/play-next_@3x.png -------------------------------------------------------------------------------- /src/static/images/play-pause_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/play-pause_@2x.png -------------------------------------------------------------------------------- /src/static/images/play-pause_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/play-pause_@3x.png -------------------------------------------------------------------------------- /src/static/images/play-play_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/play-play_@2x.png -------------------------------------------------------------------------------- /src/static/images/play-play_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/play-play_@3x.png -------------------------------------------------------------------------------- /src/static/images/play-prev_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/play-prev_@2x.png -------------------------------------------------------------------------------- /src/static/images/play-prev_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/play-prev_@3x.png -------------------------------------------------------------------------------- /src/static/images/random-play_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/random-play_@2x.png -------------------------------------------------------------------------------- /src/static/images/random-play_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/random-play_@3x.png -------------------------------------------------------------------------------- /src/static/images/singer-default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/singer-default.jpg -------------------------------------------------------------------------------- /dist/static/images/logo_@2x.1fddc3f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/dist/static/images/logo_@2x.1fddc3f.png -------------------------------------------------------------------------------- /dist/static/images/logo_@3x.d488aa1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/dist/static/images/logo_@3x.d488aa1.png -------------------------------------------------------------------------------- /src/static/images/collect-white_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/collect-white_@2x.png -------------------------------------------------------------------------------- /src/static/images/collect-white_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/collect-white_@3x.png -------------------------------------------------------------------------------- /src/static/images/current-type1_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/current-type1_@3x.png -------------------------------------------------------------------------------- /src/static/images/current-type2_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/current-type2_@3x.png -------------------------------------------------------------------------------- /src/static/images/current-type3_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/current-type3_@3x.png -------------------------------------------------------------------------------- /src/static/images/current-type4_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/current-type4_@3x.png -------------------------------------------------------------------------------- /src/static/images/current-type5_@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/src/static/images/current-type5_@3x.png -------------------------------------------------------------------------------- /dist/static/images/play-play_@2x.3c465a4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/dist/static/images/play-play_@2x.3c465a4.png -------------------------------------------------------------------------------- /dist/static/images/play-play_@3x.4dadb1b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/dist/static/images/play-play_@3x.4dadb1b.png -------------------------------------------------------------------------------- /dist/static/images/play-pause_@2x.1f2a1bc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/dist/static/images/play-pause_@2x.1f2a1bc.png -------------------------------------------------------------------------------- /dist/static/images/play-pause_@3x.db72d15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/dist/static/images/play-pause_@3x.db72d15.png -------------------------------------------------------------------------------- /dist/static/images/singer-default.a96a15f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jianguo-h/react-music-player/HEAD/dist/static/images/singer-default.a96a15f.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | test/unit/coverage 4 | test/e2e/reports 5 | package-lock.json 6 | /*.log 7 | /*.eslintcache 8 | /*.stylelintcache 9 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react music player 6 | 7 | 8 |
9 | 10 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 箭头函数只有一个参数的时候可以忽略括号 3 | arrowParens: 'avoid', 4 | // 分号 5 | semi: true, 6 | // 使用单引号 7 | singleQuote: true, 8 | // 缩进 9 | tabWidth: 2, 10 | jsxSingleQuote: true, 11 | }; 12 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | './**/*.{js?(x),ts?(x)}': [ 3 | 'prettier --ignore-path .eslintignore --write', 4 | 'eslint --fix', 5 | ], 6 | 'src/**/*.{css,less,scss,sass}': ['prettier --write', 'stylelint --fix'], 7 | }; 8 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | module.exports = { 3 | plugins: [ 4 | require('autoprefixer')({ 5 | overrideBrowserslist: ['> 5%', 'not ie <= 8', 'last 2 versions'], 6 | }), 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import * as reducers from './reducers'; 2 | import thunkMiddleware from 'redux-thunk'; 3 | import { combineReducers, createStore, applyMiddleware } from 'redux'; 4 | 5 | const reducer = combineReducers(reducers); 6 | const middlewares = applyMiddleware(thunkMiddleware); 7 | const store = createStore(reducer, middlewares); 8 | 9 | export default store; 10 | 11 | export type RootState = ReturnType; 12 | 13 | export type GetStateFn = () => RootState; 14 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Outlet } from 'react-router-dom'; 3 | import ReactHeader from './pages/header'; 4 | import Player from './pages/player'; 5 | import SuspendLyric from './pages/suspend-lyric'; 6 | import { hot } from 'react-hot-loader/root'; 7 | 8 | const App: React.FC = () => { 9 | return ( 10 |
11 | 12 | 13 | 14 | 15 |
16 | ); 17 | }; 18 | 19 | export default hot(App); 20 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'], 3 | rules: { 4 | 'comment-empty-line-before': 'always', 5 | 'rule-empty-line-before': [ 6 | 'always', 7 | { ignore: ['after-comment', 'inside-block'] }, 8 | ], 9 | }, 10 | overrides: [ 11 | { 12 | files: ['**/*.less'], 13 | customSyntax: 'postcss-less', 14 | rules: { 15 | 'selector-class-pattern': null, 16 | }, 17 | }, 18 | ], 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/list-item.tsx: -------------------------------------------------------------------------------- 1 | import { ISong } from '@src/types'; 2 | import React from 'react'; 3 | 4 | interface IProps { 5 | song: ISong; 6 | index: number; 7 | active: boolean; 8 | onPlay: (index: number) => void; 9 | } 10 | 11 | const ListItem: React.FC = props => { 12 | const { song, onPlay, index, active } = props; 13 | 14 | return ( 15 |
  • onPlay(index)}> 16 |

    {song.FileName}

    17 |
  • 18 | ); 19 | }; 20 | 21 | export default ListItem; 22 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "target": "es5", 5 | "strict": true, 6 | "module": "commonjs", 7 | "noImplicitAny": true, 8 | "baseUrl": "./", 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "allowSyntheticDefaultImports": true, 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "paths": { 16 | "*": ["@types/*"] 17 | } 18 | }, 19 | "include": [ 20 | "./build", 21 | "./server" 22 | ], 23 | "exclude": ["node_modules"] 24 | } -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react music player 6 | 7 | 8 |
    9 | 10 | -------------------------------------------------------------------------------- /src/less/list.less: -------------------------------------------------------------------------------- 1 | @import (reference) './mixins/index.less'; 2 | 3 | #content { 4 | .list { 5 | ul { 6 | padding: 0 0.2778rem; 7 | li { 8 | display: flex; 9 | .fontSize(28); 10 | padding: 0.5556rem 0.2778rem; 11 | border-bottom: 1px solid #ddd; 12 | align-items: center; 13 | &.active { 14 | color: #2ca2f9; 15 | } 16 | .filename { 17 | flex: 1; 18 | } 19 | } 20 | } 21 | } 22 | .noSongData { 23 | display: flex; 24 | align-items: center; 25 | justify-content: center; 26 | height: 100%; 27 | p { 28 | .fontSize(32); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | assumptions: { 3 | setPublicClassFields: true, 4 | }, 5 | presets: [ 6 | [ 7 | '@babel/env', 8 | { 9 | modules: false, 10 | useBuiltIns: 'usage', 11 | corejs: 3, 12 | }, 13 | ], 14 | '@babel/preset-react', 15 | ], 16 | plugins: [ 17 | 'react-hot-loader/babel', 18 | [ 19 | '@babel/plugin-proposal-decorators', 20 | { 21 | legacy: true, 22 | }, 23 | ], 24 | '@babel/plugin-proposal-class-properties', 25 | [ 26 | 'import', 27 | { 28 | libraryName: 'antd-mobile', 29 | style: true, 30 | }, 31 | ], 32 | ], 33 | }; 34 | -------------------------------------------------------------------------------- /src/less/mixins/index.less: -------------------------------------------------------------------------------- 1 | .fontSize(@fontSize) { 2 | font-size: round((@fontSize / 2)) * 1px; 3 | [data-dpr='2'] & { 4 | font-size: @fontSize * 1px; 5 | } 6 | [data-dpr='3'] & { 7 | font-size: round((@fontSize / 2) * 3) * 1px; 8 | } 9 | [data-dpr='4'] & { 10 | font-size: round(@fontSize * 2) * 1px; 11 | } 12 | } 13 | 14 | .bgImgContain { 15 | background-size: contain; 16 | background-repeat: no-repeat; 17 | background-position: center center; 18 | } 19 | 20 | .bgImg(@url, @suffix: '.png') { 21 | background-image: url('../../static/images/@{url}_@2x@{suffix}'); 22 | [data-dpr='3'] & { 23 | background-image: url('../../static/images/@{url}_@3x@{suffix}'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "allowJs": true, 5 | "target": "es5", 6 | "strict": true, 7 | "module": "esnext", 8 | "sourceMap": true, 9 | "noImplicitAny": true, 10 | "jsx": "preserve", 11 | "baseUrl": "./", 12 | "esModuleInterop": true, 13 | "experimentalDecorators": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "allowSyntheticDefaultImports": true, 16 | "moduleResolution": "node", 17 | "skipLibCheck": true, 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "downlevelIteration": true, 22 | "paths": { 23 | "@src/*": ["src/*"] 24 | } 25 | }, 26 | "include": ["src"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /src/components/lrc-color.tsx: -------------------------------------------------------------------------------- 1 | import { ILrcColor } from '@src/types'; 2 | import React from 'react'; 3 | 4 | interface IProps { 5 | changeLrcColor: (index: number) => void; 6 | lrcColorList: ILrcColor[]; 7 | } 8 | 9 | const LrcColor: React.FC = props => { 10 | const { lrcColorList, changeLrcColor } = props; 11 | return ( 12 |
    13 | {lrcColorList.map((currentObj, index) => { 14 | return ( 15 |
  • changeLrcColor(index)} 22 | >
  • 23 | ); 24 | })} 25 |
    26 | ); 27 | }; 28 | 29 | export default LrcColor; 30 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import store from './store'; 3 | import { render } from 'react-dom'; 4 | import { Provider } from 'react-redux'; 5 | import App from './App'; 6 | import adapt from './ts/adapt'; 7 | import './less/app.less'; 8 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 9 | import List from './pages/list'; 10 | 11 | // some config 12 | adapt(); 13 | 14 | render( 15 | 16 | 17 | 18 | }> 19 | } /> 20 | } /> 21 | } /> 22 | 23 | 24 | 25 | , 26 | document.getElementById('app') 27 | ); 28 | -------------------------------------------------------------------------------- /src/less/suspend-lyric.less: -------------------------------------------------------------------------------- 1 | @import (reference) './mixins/index.less'; 2 | 3 | #suspend-lyric { 4 | &.fadeIn { 5 | opacity: 1; 6 | visibility: visible; 7 | } 8 | opacity: 0; 9 | visibility: hidden; 10 | transition: opacity 0.3s; 11 | touch-action: none; 12 | position: fixed; 13 | top: 0; 14 | left: 10%; 15 | width: 80%; 16 | z-index: 99; 17 | color: #fff; 18 | border-radius: 4px; 19 | padding: 0.2778rem; 20 | background-color: rgba(0, 0, 0, 0.5); 21 | .fontSize(30); 22 | .close { 23 | width: 0.4167rem; 24 | height: 0.4167rem; 25 | position: absolute; 26 | right: 0.2778rem; 27 | top: 0.2778rem; 28 | .bgImg('close'); 29 | .bgImgContain; 30 | } 31 | & > p { 32 | height: 1rem; 33 | line-height: 1rem; 34 | &:last-child { 35 | text-align: right; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /config/index.ts: -------------------------------------------------------------------------------- 1 | import { Options } from 'http-proxy-middleware'; 2 | 3 | export const devPort = process.env.PORT ?? 8080; 4 | 5 | export const serverPort = process.env.SERVER_PORT ?? 8088; 6 | 7 | export const proxyTable: { 8 | [path: string]: Options; 9 | } = { 10 | // 搜索接口 11 | '/songsearch': { 12 | target: 'http://songsearch.kugou.com/song_search_v2', 13 | changeOrigin: true, 14 | pathRewrite: { 15 | '^/songsearch': '', 16 | }, 17 | }, 18 | // 获取歌曲接口 19 | '/play': { 20 | target: 'http://www.kugou.com/yy/index.php', 21 | changeOrigin: true, 22 | pathRewrite: { 23 | '^/play': '', 24 | }, 25 | }, 26 | // 搜索框关键词搜索接口 27 | '/searchtip': { 28 | target: 'http://searchtip.kugou.com/getSearchTip', 29 | changeOrigin: true, 30 | pathRewrite: { 31 | '^/searchtip': '', 32 | }, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/drop-list.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface IProps { 4 | resultCount?: number; 5 | searchTip?: string; 6 | resultList?: any[]; 7 | search: (keyword: string) => () => void; 8 | } 9 | 10 | const DropList: React.FC = props => { 11 | const { 12 | resultCount = 0, 13 | searchTip = '正在搜索...', 14 | resultList = [], 15 | search, 16 | } = props; 17 | 18 | return ( 19 |
    20 | {resultCount > 0 ? ( 21 |
      22 | {resultList.map((item, index) => { 23 | return ( 24 |
    • 25 | {item.HintInfo} 26 |
    • 27 | ); 28 | })} 29 |
    30 | ) : ( 31 |

    {searchTip}

    32 | )} 33 |
    34 | ); 35 | }; 36 | 37 | export default DropList; 38 | -------------------------------------------------------------------------------- /src/less/play-operate.less: -------------------------------------------------------------------------------- 1 | @import (reference) './mixins/index.less'; 2 | 3 | .play-operate { 4 | & > span { 5 | display: inline-block; 6 | width: 0.6944rem; 7 | height: 0.6944rem; 8 | .bgImgContain; 9 | } 10 | .prev { 11 | .bgImg('icon-prev'); 12 | } 13 | .play { 14 | margin: 0 0.3333rem; 15 | .bgImg('icon-play'); 16 | } 17 | .pause { 18 | .bgImg('icon-pause'); 19 | } 20 | .next { 21 | .bgImg('icon-next'); 22 | } 23 | } 24 | 25 | .play-detail { 26 | flex: 1; 27 | text-align: center; 28 | padding: 0.2778rem 0; 29 | & > span { 30 | width: 0.9167rem; 31 | height: 0.9167rem; 32 | vertical-align: middle; 33 | } 34 | .prev { 35 | .bgImg('play-prev'); 36 | } 37 | .play { 38 | margin: 0 0.5556rem; 39 | width: 1.25rem; 40 | height: 1.25rem; 41 | .bgImg('play-play'); 42 | } 43 | .pause { 44 | .bgImg('play-pause'); 45 | } 46 | .next { 47 | .bgImg('play-next'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import { Action } from 'redux'; 2 | 3 | export enum SongType { 4 | new = 'new', 5 | local = 'local', 6 | recommend = 'recommend', 7 | } 8 | 9 | export type SongTypes = SongType.new | SongType.local | SongType.recommend; 10 | 11 | export interface ISong { 12 | FileName?: string; 13 | SongName?: string; 14 | SingerName?: string; 15 | } 16 | 17 | export interface ILrcColor { 18 | defaultColor: string; 19 | activeColor: string; 20 | currentImgSrc: string; 21 | } 22 | 23 | export interface IPlayLrc { 24 | startTime: string; 25 | curLrc: string; 26 | index: number; 27 | } 28 | 29 | export interface IPlaySongInfo extends ISong { 30 | index?: number; 31 | } 32 | 33 | export interface ILrcConfig { 34 | activeColor?: string; 35 | defaultColor?: string; 36 | activeLrcIndex: number; 37 | } 38 | 39 | export interface IAction extends Action { 40 | payload: { 41 | [property: string]: any; 42 | }; 43 | } 44 | 45 | export type AudioEle = HTMLAudioElement | null; 46 | -------------------------------------------------------------------------------- /src/components/detail-list.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { playSong } from '@src/store/actions'; 4 | import { RootState } from '@src/store'; 5 | import { IPlaySongInfo, ISong } from '@src/types'; 6 | 7 | const DetailList: React.FC = () => { 8 | const dispatch = useDispatch(); 9 | const songList = useSelector(state => state.songList); 10 | const curPlaySong = useSelector( 11 | state => state.curPlaySong 12 | ); 13 | 14 | const onPlay = (index: number) => () => { 15 | dispatch(playSong(index)); 16 | }; 17 | 18 | return ( 19 |
    20 |
      21 | {songList.map((song, index) => { 22 | return ( 23 |
    • 28 | {index + 1}. {song.FileName} 29 |
    • 30 | ); 31 | })} 32 |
    33 |
    34 | ); 35 | }; 36 | 37 | export default DetailList; 38 | -------------------------------------------------------------------------------- /src/components/lrc-scroll.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | import { RootState } from '@src/store'; 4 | import { ILrcConfig } from '@src/types'; 5 | import { IPlayLrc } from '@src/types'; 6 | 7 | interface IProps { 8 | translateY?: number; 9 | lrcBoxRef: React.LegacyRef; 10 | } 11 | 12 | const LrcScroll: React.FC = props => { 13 | const { translateY = 0, lrcBoxRef } = props; 14 | 15 | const lrcConfig = useSelector( 16 | state => state.lrcConfig 17 | ); 18 | const curPlayLrcArr = useSelector( 19 | state => state.curPlayLrcArr 20 | ); 21 | 22 | return ( 23 |
    31 | {curPlayLrcArr.map((lrcObj, index) => { 32 | return ( 33 |

    41 | {lrcObj.curLrc} 42 |

    43 | ); 44 | })} 45 |
    46 | ); 47 | }; 48 | 49 | export default LrcScroll; 50 | -------------------------------------------------------------------------------- /server/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import song from './song.json'; 3 | import { createProxyMiddleware } from 'http-proxy-middleware'; 4 | import { proxyTable, serverPort } from '../config'; 5 | import path from 'path'; 6 | 7 | const app = express(); 8 | 9 | // config express router 10 | const songData = JSON.parse(JSON.stringify(song)); 11 | const routes = ['new', 'recommend', 'local']; 12 | for (const route of routes) { 13 | app.get('/api/' + route, (_, res) => { 14 | res.json({ 15 | path: route, 16 | data: songData[route], 17 | }); 18 | }); 19 | } 20 | 21 | // config exress server proxy 22 | const isDev = process.env.NODE_ENV === 'development'; 23 | Object.keys(proxyTable).forEach(ctx => { 24 | let options = proxyTable[ctx]; 25 | if (typeof options === 'string') { 26 | options = { target: options }; 27 | } 28 | app.use( 29 | ctx, 30 | createProxyMiddleware({ 31 | changeOrigin: true, 32 | target: isDev ? 'http://localhost:' + serverPort : options.target, 33 | pathRewrite: isDev ? undefined : options.pathRewrite, 34 | }) 35 | ); 36 | }); 37 | 38 | // static path config 39 | const distPath = path.join(__dirname, '../dist'); 40 | app.use('/', express.static(distPath)); 41 | 42 | app.listen(serverPort, () => { 43 | console.log( 44 | '\n express backend server start at http://localhost:' + serverPort 45 | ); 46 | }); 47 | -------------------------------------------------------------------------------- /dist/static/js/runtime.246fc33a.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c = props => { 13 | const { showDetail = false } = props; 14 | 15 | const dispatch = useDispatch(); 16 | 17 | const paused = useSelector(state => state.paused); 18 | const curPlaySong = useSelector( 19 | state => state.curPlaySong 20 | ); 21 | 22 | const opearteClass = showDetail ? 'play-operate play-detail' : 'play-operate'; 23 | const playClass = paused ? 'play pause' : 'play'; 24 | 25 | const changePlay = (operate: 'prev' | 'next') => () => { 26 | let newCurPlayIndex = curPlaySong.index; 27 | if (newCurPlayIndex !== undefined) { 28 | operate === 'next' ? newCurPlayIndex++ : newCurPlayIndex--; 29 | dispatch(playSong(newCurPlayIndex)); 30 | } 31 | }; 32 | 33 | return ( 34 |
    35 | 36 | dispatch(togglePlayStatus())} 39 | > 40 | 41 |
    42 | ); 43 | }; 44 | 45 | export default PlayOperate; 46 | -------------------------------------------------------------------------------- /src/components/time-wrap.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface IProps { 4 | updateProgress: (evt: React.MouseEvent) => void; 5 | curPlayTime?: number | string; 6 | progress?: number | string; 7 | endTime?: number | string; 8 | progressBarRef: React.LegacyRef; 9 | } 10 | 11 | function formatTime(time: number): string { 12 | let minutes: number | string = Math.floor(time / 60); 13 | let seconds: number | string = Math.floor(time % 60); 14 | if (minutes < 10) { 15 | minutes = '0' + minutes; 16 | } 17 | if (seconds < 10) { 18 | seconds = '0' + seconds; 19 | } 20 | return minutes + ':' + seconds; 21 | } 22 | 23 | const TimeWrap: React.FC = props => { 24 | const { 25 | curPlayTime = 0, 26 | progress = 0, 27 | endTime = 0, 28 | updateProgress, 29 | progressBarRef, 30 | } = props; 31 | 32 | return ( 33 |
    34 |
    {formatTime(Number(curPlayTime))}
    35 |
    36 |
    41 |
    42 |
    46 |
    47 |
    {formatTime(Number(endTime))}
    48 |
    49 | ); 50 | }; 51 | 52 | export default TimeWrap; 53 | -------------------------------------------------------------------------------- /src/ts/adapt.ts: -------------------------------------------------------------------------------- 1 | // rem 适配手机屏幕 2 | export default function () { 3 | // const evt = "onorientationchange" in window ? "onorientationchange" : "resize"; 4 | const isIphone = window.navigator.appVersion.match(/iphone/gi); 5 | let dpr = window.devicePixelRatio; 6 | if (isIphone) { 7 | if (dpr >= 3 && (!dpr || dpr >= 3)) { 8 | dpr = 3; 9 | } else if (dpr >= 2 && (!dpr || dpr >= 2)) { 10 | dpr = 2; 11 | } else { 12 | dpr = 1; 13 | } 14 | } else { 15 | dpr = 1; 16 | } 17 | 18 | const scale = 1 / dpr; 19 | const docEl = document.documentElement; 20 | let metaEl = document.querySelector("meta[name='viewport']"); 21 | const headEl = document.querySelector('head'); 22 | 23 | if (!metaEl) { 24 | metaEl = document.createElement('meta'); 25 | metaEl.setAttribute('name', 'viewport'); 26 | if (headEl) { 27 | headEl.appendChild(metaEl); 28 | } 29 | } 30 | const fn = () => { 31 | let docElWidth = docEl.getBoundingClientRect().width; 32 | if (docElWidth / dpr > 540) { 33 | docElWidth = 540 * dpr; 34 | } 35 | const fontSize = docElWidth / 10; 36 | if (metaEl) { 37 | metaEl.setAttribute( 38 | 'content', 39 | 'initial-scale=' + 40 | scale + 41 | ',maximum-scale=' + 42 | scale + 43 | ', minimum-scale=' + 44 | scale + 45 | ',user-scalable=no' 46 | ); 47 | } 48 | docEl.setAttribute('data-dpr', dpr.toString()); 49 | docEl.style.fontSize = fontSize + 'px'; 50 | }; 51 | window.addEventListener('resize', fn, false); 52 | document.addEventListener('DOMContentLoaded', fn, false); 53 | } 54 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import { SongTypes } from '@src/types'; 2 | import axios, { AxiosRequestConfig } from 'axios'; 3 | 4 | async function callApi(requestConfig: AxiosRequestConfig, title: string) { 5 | try { 6 | console.log('[request.title] ' + title); 7 | console.log(requestConfig.data ?? requestConfig.params); 8 | return await axios(requestConfig); 9 | } catch (err) { 10 | throw err; 11 | } 12 | } 13 | 14 | // 根据关键字搜索(模糊查询用到) 15 | export async function search(keyword: string) { 16 | return await callApi( 17 | { 18 | method: 'post', 19 | url: '/searchtip', 20 | data: { keyword }, 21 | }, 22 | '搜索' 23 | ); 24 | } 25 | 26 | // 获取静态json数据中的歌曲列表 27 | export async function getList(path: SongTypes) { 28 | return await callApi( 29 | { 30 | method: 'get', 31 | url: '/api/' + path, 32 | }, 33 | '获取静态json数据中的歌曲列表' 34 | ); 35 | } 36 | 37 | // 获取歌曲的一些信息 38 | export async function getSongInfo(songName: string, page: number = 1) { 39 | return await callApi( 40 | { 41 | method: 'get', 42 | url: '/songsearch', 43 | params: { 44 | page, 45 | pagesize: 20, 46 | keyword: songName, 47 | platform: 'WebFilter', 48 | userid: -1, 49 | iscorrection: 1, 50 | privilege_filter: 0, 51 | filter: 2, 52 | }, 53 | }, 54 | '获取歌曲信息' 55 | ); 56 | } 57 | 58 | // 根据hash值获取歌曲的信息 59 | export async function play(hash: string) { 60 | return await callApi( 61 | { 62 | method: 'get', 63 | url: '/play', 64 | params: { 65 | r: 'play/getdata', 66 | hash, 67 | }, 68 | }, 69 | '根据hash值获取歌曲的信息' 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /src/less/player.less: -------------------------------------------------------------------------------- 1 | @import (reference) './mixins/index.less'; 2 | 3 | #player { 4 | &.fade { 5 | animation: fade 0.5s; 6 | } 7 | .footer-play { 8 | background-color: rgba(0, 0, 0, 0.8); 9 | position: fixed; 10 | bottom: 0; 11 | left: 0; 12 | width: 100%; 13 | padding: 0.2778rem; 14 | display: flex; 15 | align-items: center; 16 | .footer-left { 17 | flex: 1; 18 | display: flex; 19 | align-items: center; 20 | .footer-singer { 21 | width: 1.4444rem; 22 | height: 1.4444rem; 23 | border-radius: 100%; 24 | overflow: hidden; 25 | img { 26 | display: block; 27 | width: 100%; 28 | height: 100%; 29 | } 30 | } 31 | .rotate { 32 | animation-name: rotate; 33 | animation-duration: 6s; 34 | animation-timing-function: linear; 35 | animation-iteration-count: infinite; 36 | } 37 | .paused { 38 | animation-play-state: paused; 39 | } 40 | .footer-playerInfo { 41 | flex: 1; 42 | color: #fff; 43 | margin: 0 0.2778rem; 44 | .fontSize(30); 45 | p { 46 | width: 4.4444rem; 47 | white-space: nowrap; 48 | overflow: hidden; 49 | text-overflow: ellipsis; 50 | } 51 | .singer-name { 52 | color: #888; 53 | margin-top: 0.1389rem; 54 | .fontSize(26); 55 | } 56 | } 57 | } 58 | } 59 | // 淡入 animation 60 | @keyframes fade { 61 | from { 62 | opacity: 0; 63 | } 64 | to { 65 | opacity: 1; 66 | } 67 | } 68 | // 旋转 animation 69 | @keyframes rotate { 70 | form { 71 | transform: rotate(0deg); 72 | } 73 | to { 74 | transform: rotate(360deg); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: [ 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react-hooks/recommended', 8 | 'prettier', 9 | 'plugin:prettier/recommended', 10 | ], 11 | plugins: ['react'], 12 | parserOptions: { 13 | sourceType: 'module', 14 | ecmaVersion: 2019, 15 | ecmaFeatures: { 16 | jsx: true, 17 | }, 18 | }, 19 | env: { 20 | browser: true, 21 | es6: true, 22 | node: true, 23 | }, 24 | settings: { 25 | react: { 26 | pragma: 'React', 27 | version: 'detect', 28 | }, 29 | }, 30 | rules: { 31 | curly: 'error', 32 | '@typescript-eslint/no-var-requires': 'warn', 33 | '@typescript-eslint/explicit-function-return-type': 'off', 34 | '@typescript-eslint/interface-name-prefix': 'off', 35 | '@typescript-eslint/no-empty-interface': 'error', 36 | '@typescript-eslint/explicit-module-boundary-types': 'off', 37 | '@typescript-eslint/no-inferrable-types': [ 38 | 'error', 39 | { 40 | ignoreParameters: true, 41 | }, 42 | ], 43 | '@typescript-eslint/naming-convention': [ 44 | 'error', 45 | { 46 | selector: 'interface', 47 | format: ['StrictPascalCase'], 48 | prefix: ['I'], 49 | leadingUnderscore: 'forbid', 50 | trailingUnderscore: 'forbid', 51 | }, 52 | { 53 | selector: 'typeLike', 54 | format: ['PascalCase', 'StrictPascalCase'], 55 | leadingUnderscore: 'forbid', 56 | trailingUnderscore: 'forbid', 57 | }, 58 | { 59 | selector: 'default', 60 | format: [ 61 | 'strictCamelCase', 62 | 'snake_case', 63 | 'StrictPascalCase', 64 | 'UPPER_CASE', 65 | ], 66 | leadingUnderscore: 'forbid', 67 | trailingUnderscore: 'forbid', 68 | }, 69 | ], 70 | }, 71 | overrides: [ 72 | { 73 | files: ['*.ts', '*.tsx'], 74 | rules: { 75 | 'react/prop-types': 'off', 76 | }, 77 | }, 78 | ], 79 | }; 80 | -------------------------------------------------------------------------------- /src/less/app.less: -------------------------------------------------------------------------------- 1 | @import (reference) './mixins/index.less'; 2 | @none: none; 3 | @normal: normal; 4 | 5 | /* reset */ 6 | html { 7 | outline: @none; 8 | overflow-x: hidden; 9 | -webkit-tap-highlight-color: transparent; // 去除点击时的高亮边框 10 | -webkit-overflow-scrolling: touch; // 增加弹性滚动效果 11 | -webkit-text-size-adjust: @none; // 防止转屏时文字大小变化 12 | -webkit-touch-callout: @none; // 禁止 iOS 弹出各种操作窗口 13 | -webkit-user-select: @none; // 禁止用户选择文字 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | body { 19 | font-family: 'Avenir', Helvetica, Arial, sans-serif; 20 | color: #333; 21 | .fontSize(24); 22 | } 23 | 24 | html, 25 | body { 26 | position: relative; 27 | height: 100%; 28 | } 29 | 30 | html, 31 | body, 32 | div, 33 | table, 34 | th, 35 | tr, 36 | td, 37 | tbody, 38 | thead, 39 | tfoot, 40 | form, 41 | span, 42 | a, 43 | p, 44 | dl, 45 | dt, 46 | dd, 47 | strong, 48 | ol, 49 | ul, 50 | li, 51 | i, 52 | em, 53 | label, 54 | input, 55 | button, 56 | select, 57 | textarea, 58 | img, 59 | h1, 60 | h2, 61 | h3, 62 | h4, 63 | h5, 64 | h6, 65 | b { 66 | padding: 0; 67 | margin: 0; 68 | font-style: @normal; 69 | font-weight: @normal; 70 | border: none; 71 | } 72 | 73 | html, 74 | body, 75 | div, 76 | a, 77 | p, 78 | ul, 79 | ol, 80 | li, 81 | dl, 82 | dt, 83 | dd, 84 | h1, 85 | h2, 86 | h3, 87 | h4, 88 | h5, 89 | h6, 90 | input, 91 | button, 92 | select, 93 | textarea { 94 | box-sizing: border-box; 95 | } 96 | 97 | input, 98 | button, 99 | textarea, 100 | select { 101 | border: @none; 102 | outline: @none; 103 | line-height: @normal; 104 | background: @none; 105 | -webkit-appearance: @none; // 去除ios中按钮的默认样式 106 | } 107 | 108 | textarea { 109 | resize: @none; 110 | padding: 0 0.1111rem; 111 | } 112 | 113 | input[type='checkbox'] { 114 | -webkit-appearance: checkbox; 115 | } 116 | 117 | input[type='radio'] { 118 | -webkit-appearance: radio; 119 | } 120 | 121 | input[type='text'], 122 | input[type='password'], 123 | input[type='tel'], 124 | input[type='email'], 125 | input[type='number'] { 126 | padding: 0 0.125rem; 127 | .fontSize(28); 128 | } 129 | 130 | ol, 131 | ul, 132 | li { 133 | list-style: @none; 134 | } 135 | 136 | a, 137 | a:visited, 138 | a:link, 139 | a:active, 140 | a:focus { 141 | color: #343434; 142 | text-decoration: @none; 143 | } 144 | 145 | #root { 146 | padding-top: 2.6944rem; 147 | } 148 | -------------------------------------------------------------------------------- /src/less/header.less: -------------------------------------------------------------------------------- 1 | @import (reference) './mixins/index.less'; 2 | 3 | #header { 4 | position: fixed; 5 | width: 100%; 6 | z-index: 10; 7 | top: 0; 8 | left: 0; 9 | background-color: #fff; 10 | .header-search { 11 | display: flex; 12 | align-items: center; 13 | height: 1.25rem; 14 | padding: 0 0.2778rem; 15 | background-color: #2ca2f9; 16 | .logo { 17 | width: 2.8333rem; 18 | height: 0.6528rem; 19 | .bgImg('logo'); 20 | .bgImgContain; 21 | } 22 | .search-form { 23 | flex: 1; 24 | height: 0.7778rem; 25 | margin: 0.2361rem 0.2778rem; 26 | border: 1px solid #fff; 27 | border-radius: 4px; 28 | position: relative; 29 | input { 30 | vertical-align: top; 31 | height: 100%; 32 | color: #fff; 33 | &::-webkit-input-placeholder { 34 | color: #fff; 35 | } 36 | } 37 | .search-list { 38 | position: absolute; 39 | left: 0; 40 | top: 0.75rem; 41 | width: 100%; 42 | border: 1px solid #2ca2f9; 43 | border-radius: 4px; 44 | background-color: #fff; 45 | z-index: 10; 46 | li, 47 | p { 48 | padding: 0.2778rem; 49 | .fontSize(28); 50 | } 51 | } 52 | } 53 | .search { 54 | width: 0.4722rem; 55 | height: 0.4722rem; 56 | .bgImgContain; 57 | .bgImg('search'); 58 | } 59 | } 60 | .header-tab { 61 | ul { 62 | display: flex; 63 | padding: 0 0.2778rem; 64 | li { 65 | flex: 1; 66 | line-height: 1.3889rem; 67 | text-align: center; 68 | .fontSize(32); 69 | a { 70 | display: block; 71 | &.active { 72 | border-bottom: 2px solid #33a3f5; 73 | color: #33a3f5; 74 | } 75 | } 76 | } 77 | } 78 | } 79 | .header-search-result { 80 | position: relative; 81 | background-color: #dedede; 82 | .goback { 83 | top: 50%; 84 | left: 0.1389rem; 85 | width: 0.5556rem; 86 | height: 0.5556rem; 87 | position: absolute; 88 | transform: translate3d(0, -50%, 0); 89 | .bgImgContain; 90 | .bgImg('icon-goback'); 91 | } 92 | .searchCount { 93 | color: #5d5d5d; 94 | line-height: 1.3889rem; 95 | padding-left: 0.8333rem; 96 | .fontSize(28); 97 | em { 98 | color: #2ca2f9; 99 | margin: 0 0.1389rem; 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | 最近在入坑react,撸完文档后发现没啥项目可练手,所以只好拿之前用vue做的[音乐播放器](https://github.com/jianguo-h/vue-music-player)来改版了。由于是react的初学者,在很多方面都还是个小菜鸟,组件设计上可能也有很多不规范的地方,如果你已经是个react的大神了,觉得项目有任何不规范的代码或是设计,记得联系我哦!当然如果你和我一样也是个初学者,希望这个项目能帮到你一点,记得点颗星O(∩_∩)O。移动端的一个小项目,**chrome浏览器下请切换至手机模式查看** 4 | >**注意**:该项目使用了koa2,所以node的版本需在7.6以上,windows的小伙伴需要升级node可去官网下载最新的安装包后直接覆盖安装,由于本人没有用过mac,所以说用mac的小伙伴百度下如何升级node吧 5 | 6 | # 项目简介 7 | 8 | ## 技术栈 9 | 10 | react + react-router-dom + redux + react-redux + axios + antd-mobile + es6 + less 11 | 12 | ## 已实现的功能 13 | 14 | * 搜索功能,包括搜索歌曲和歌手 15 | * 歌曲的播放,暂停功能 16 | * 切歌,歌曲的前进和后退 17 | * 歌词滚动,悬浮框歌词 18 | * 歌词颜色切换,顺序、逆序、随机播放 19 | * 滚动加载等(该功能只在搜索歌曲后的页面有效) 20 | 21 | ## Build Setup 22 | 23 | ``` bash 24 | # git clone https://github.com/jianguo-h/react-music-player.git 25 | 26 | # install dependencies 27 | npm i(cnpm i) 28 | 如果安装了yarn, 也可以yarn install 29 | 30 | # serve with hot reload at localhost:8080 31 | 安装好依赖后, 启动项目, 这里分 2 步 32 | 1). npm run server 33 | 2). npm run start 34 | 35 | # build for production with minification 36 | npm run build 37 | 38 | # also you can 39 | 或者执行完第一步后,也可以在控制台下直接运行npm run server命令 40 | 该命令会直接运行koa目录下的index.js文件,加载已打包好的dist目录下的文件 41 | 启动成功后直接在浏览器中打开 http://localhost:8088 即可 42 | ``` 43 | 44 | ## 部分效果图 45 | 46 | ### 首页和播放页 47 | 48 | 49 | 50 | ### 播放详情和搜索功能 51 | 52 | 53 | ## 目录 54 | 55 | ``` 56 | . 57 | ├── build // webpack配置文件 58 | ├── config // webpack的一些配置 59 | ├── dist // 已打包好的目录 60 | ├── koa // koa的一些配置 61 | ├── src // 源码 62 | │   ├── api // 接口管理 63 | │   ├── components // 功能性组件 64 | │   ├── js // 其他js 65 | │   ├── less // 样式文件less 66 | │   ├── pages // 页面级性组件 67 | │   ├── routes // react路由配置 68 | │   ├── store // redux配置 69 | │   ├── App.jsx // 根组件文件 70 | │   ├── main.js // 入口文件 71 | │ ├── static // 静态资源 72 | ├── img // 图片 73 | ├── data // json数据 74 | ├── index.html // 入口html文件 75 | . 76 | ``` 77 | 78 | ## 其他说明 79 | 由于接口调用的是酷狗官方的接口,需要跨域,所以项目中使用了代理,配置在config目录和build目录下的dev-server.js中,注意**请不要频繁访问请求,很有可能被酷狗封死接口** 80 | -------------------------------------------------------------------------------- /src/pages/player.tsx: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from 'react-redux'; 2 | import PlayDetail from './play-detail'; 3 | import React, { useState, useRef } from 'react'; 4 | import PlayOperate from '../components/play-operate'; 5 | import { Toast } from 'antd-mobile'; 6 | import { setAudio, setIsPlayed, playSong } from '@src/store/actions'; 7 | import { RootState } from '@src/store'; 8 | import { IPlaySongInfo } from '@src/types'; 9 | import '../less/player.less'; 10 | import { AudioEle } from '@src/types'; 11 | 12 | const Player: React.FC = () => { 13 | const dispatch = useDispatch(); 14 | 15 | const audioSrc = useSelector( 16 | state => state.audioSrc 17 | ); 18 | const loop = useSelector(state => state.loop); 19 | const canPlayed = useSelector(state => state.canPlayed); 20 | const isPlayed = useSelector(state => state.isPlayed); 21 | const curPlaySong = useSelector( 22 | state => state.curPlaySong 23 | ); 24 | const curPlayImgSrc = useSelector( 25 | state => state.curPlayImgSrc 26 | ); 27 | const paused = useSelector(state => state.paused); 28 | const lock = useSelector(state => state.lock); 29 | const modeType = useSelector(state => state.modeType); 30 | 31 | const audioEl = useRef(null); 32 | const [showDetail, setShowDetail] = useState(false); 33 | 34 | const setCurrentTime = (time: number) => { 35 | if (audioEl.current) { 36 | audioEl.current.currentTime = time; 37 | console.log(audioEl.current.currentTime); 38 | } 39 | }; 40 | 41 | const onCanplay = () => { 42 | if (lock) return; 43 | 44 | if (audioEl.current?.readyState === 4) { 45 | audioEl.current?.play(); 46 | dispatch(setAudio(audioEl.current)); 47 | dispatch(setIsPlayed(true)); 48 | } else { 49 | Toast.fail('歌曲暂时无法播放, 请稍后重试'); 50 | } 51 | }; 52 | 53 | const onEnded = () => { 54 | if (modeType !== 'order') return; 55 | 56 | const nextPlayIndex = curPlaySong.index ?? 0 + 1; 57 | dispatch(playSong(nextPlayIndex)); 58 | }; 59 | 60 | const playDetailProps = { 61 | showDetail, 62 | setShowDetail, 63 | setCurrentTime, 64 | }; 65 | 66 | let footerSingerClass = 'footer-singer'; 67 | footerSingerClass = isPlayed 68 | ? footerSingerClass + ' rotate' 69 | : footerSingerClass; 70 | footerSingerClass = paused 71 | ? footerSingerClass + ' paused' 72 | : footerSingerClass; 73 | 74 | return canPlayed ? ( 75 |
    76 |
    80 |
    setShowDetail(true)}> 81 |
    82 | 歌手图片 83 |
    84 |
    85 |

    {curPlaySong.SongName}

    86 |

    {curPlaySong.SingerName}

    87 |
    88 |
    89 |
    90 | 91 |
    92 |
    93 | {/* 播放详情组件 */} 94 | 95 |
    96 | 103 |
    104 |
    105 | ) : null; 106 | }; 107 | 108 | export default Player; 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-music-player", 3 | "version": "1.0.0", 4 | "description": "a React.js project", 5 | "main": "src/main.tsx", 6 | "scripts": { 7 | "prepare": "husky install", 8 | "start": "env-cmd ts-node build/dev-server.ts", 9 | "build": "env-cmd -f .env.production ts-node build/build.ts", 10 | "server": "env-cmd -f .env.server ts-node server/index.ts", 11 | "lint": "npm run prettier && npm run lint-script && npm run lint-style", 12 | "prettier": "prettier --ignore-path .eslintignore --write \"./**/*.{js?(x),ts?(x),css,less,scss,sass}\"", 13 | "lint-script": "eslint --cache --ext .js,.jsx,.ts,.tsx --ignore-path .eslintignore --fix ./", 14 | "lint-style": "stylelint --ignore-path .eslintignore --cache --fix src/**/*.{css,less,scss,sass}" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/jianguo-h/react-music-player.git" 19 | }, 20 | "author": "jianguo", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/jianguo-h/react-music-player/issues" 24 | }, 25 | "homepage": "https://github.com/jianguo-h/react-music-player#readme", 26 | "dependencies": { 27 | "@babel/core": "^7.16.10", 28 | "@babel/plugin-proposal-class-properties": "^7.16.7", 29 | "@babel/plugin-proposal-decorators": "^7.16.7", 30 | "@babel/preset-env": "^7.16.11", 31 | "@babel/preset-react": "^7.16.7", 32 | "@hot-loader/react-dom": "^17.0.2", 33 | "antd-mobile": "^2.3.4", 34 | "autoprefixer": "^10.4.2", 35 | "axios": "^0.25.0", 36 | "babel-loader": "^8.2.3", 37 | "babel-plugin-import": "^1.13.3", 38 | "clean-webpack-plugin": "^4.0.0", 39 | "core-js": "^3.20.3", 40 | "css-loader": "^6.5.1", 41 | "css-minimizer-webpack-plugin": "^3.4.1", 42 | "env-cmd": "^10.1.0", 43 | "html-webpack-plugin": "^5.5.0", 44 | "less": "^4.1.2", 45 | "less-loader": "^10.2.0", 46 | "lodash": "^4.17.21", 47 | "mini-css-extract-plugin": "^2.5.2", 48 | "postcss": "^8.4.5", 49 | "postcss-loader": "^6.2.1", 50 | "prop-types": "^15.8.1", 51 | "react": "^17.0.2", 52 | "react-dom": "^17.0.2", 53 | "react-hot-loader": "^4.13.0", 54 | "react-redux": "^7.2.6", 55 | "react-router": "^6.2.1", 56 | "react-router-dom": "^6.2.1", 57 | "redux": "^4.1.2", 58 | "redux-thunk": "^2.4.1", 59 | "ts-loader": "^9.2.6", 60 | "ts-node": "^10.4.0", 61 | "tsconfig-paths": "^3.12.0", 62 | "typescript": "^4.5.5", 63 | "webpack": "^5.66.0", 64 | "webpack-merge": "^5.8.0" 65 | }, 66 | "devDependencies": { 67 | "@commitlint/cli": "^16.1.0", 68 | "@commitlint/config-conventional": "^16.0.0", 69 | "@types/detect-port": "^1.3.2", 70 | "@types/express": "^4.17.13", 71 | "@types/lodash": "^4.14.178", 72 | "@types/node": "^17.0.10", 73 | "@types/react": "^17.0.38", 74 | "@types/react-dom": "^17.0.11", 75 | "@types/react-redux": "^7.1.22", 76 | "@types/react-router": "^5.1.18", 77 | "@types/react-router-dom": "^5.3.3", 78 | "@typescript-eslint/eslint-plugin": "^5.10.0", 79 | "@typescript-eslint/parser": "^5.10.0", 80 | "detect-port": "^1.3.0", 81 | "eslint": "^8.7.0", 82 | "eslint-config-prettier": "^8.3.0", 83 | "eslint-plugin-import": "^2.25.4", 84 | "eslint-plugin-prettier": "^4.0.0", 85 | "eslint-plugin-react": "^7.28.0", 86 | "eslint-plugin-react-hooks": "^4.3.0", 87 | "eslint-webpack-plugin": "^3.1.1", 88 | "express": "^4.17.2", 89 | "fork-ts-checker-webpack-plugin": "^6.5.0", 90 | "http-proxy-middleware": "^2.0.1", 91 | "husky": "^7.0.4", 92 | "lint-staged": "^12.2.2", 93 | "postcss-less": "^6.0.0", 94 | "prettier": "^2.5.1", 95 | "style-loader": "^3.3.1", 96 | "stylelint": "^14.2.0", 97 | "stylelint-config-prettier": "^9.0.3", 98 | "stylelint-config-standard": "^24.0.0", 99 | "stylelint-prettier": "^2.0.0", 100 | "webpack-dev-server": "^4.7.3" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /server/song.json: -------------------------------------------------------------------------------- 1 | { 2 | "local": [ 3 | { 4 | "SongName": "童话镇", 5 | "SingerName": "陈一发儿", 6 | "FileName": "陈一发儿 - 童话镇" 7 | }, 8 | { 9 | "SongName": "说散就散", 10 | "SingerName": "冯提莫", 11 | "FileName": "冯提莫 - 说散就散" 12 | }, 13 | { 14 | "SongName": "刚好遇见你", 15 | "SingerName": "冯提莫", 16 | "FileName": "冯提莫 - 刚好遇见你" 17 | }, 18 | { 19 | "SongName": "江南", 20 | "SingerName": "林俊杰", 21 | "FileName": "林俊杰 - 江南" 22 | }, 23 | { 24 | "SongName": "年轮", 25 | "SingerName": "张碧晨", 26 | "FileName": "张碧晨 - 年轮" 27 | }, 28 | { 29 | "SongName": "走在冷风中", 30 | "SingerName": "刘思涵", 31 | "FileName": "刘思涵 - 走在冷风中" 32 | }, 33 | { 34 | "SongName": "剑伤", 35 | "SingerName": "李易峰", 36 | "FileName": "李易峰 - 剑伤" 37 | }, 38 | { 39 | "SongName": "海阔天空", 40 | "SingerName": "Beyond", 41 | "FileName": "Beyond - 海阔天空" 42 | }, 43 | { 44 | "SongName": "就算没有如果", 45 | "SingerName": "香香", 46 | "FileName": "香香 - 就算没有如果" 47 | }, 48 | { 49 | "SongName": "淘汰", 50 | "SingerName": "陈奕迅", 51 | "FileName": "陈奕迅 - 淘汰" 52 | }, 53 | { 54 | "SongName": "喜欢你", 55 | "SingerName": "G.E.M.邓紫棋", 56 | "FileName": "G.E.M.邓紫棋 - 喜欢你" 57 | }, 58 | { 59 | "SongName": "小幸运", 60 | "SingerName": "田馥甄", 61 | "FileName": "田馥甄 - 小幸运" 62 | }, 63 | { 64 | "SongName": "剑心", 65 | "SingerName": "张杰", 66 | "FileName": "张杰 - 剑心" 67 | }, 68 | { 69 | "SongName": "Let It Go", 70 | "SingerName": "Idina Menzel", 71 | "FileName": "Idina Menzel - Let It Go" 72 | } 73 | ], 74 | "new": [ 75 | { 76 | "SongName": "体面", 77 | "SingerName": "于文文", 78 | "FileName": "于文文 - 体面" 79 | }, 80 | { 81 | "SongName": "追光者", 82 | "SingerName": "岑宁儿", 83 | "FileName": "岑宁儿 - 追光者" 84 | }, 85 | { 86 | "SongName": "远走高飞", 87 | "SingerName": "金志文、徐佳莹", 88 | "FileName": "金志文、徐佳莹 - 远走高飞" 89 | }, 90 | { 91 | "SongName": "瞒着你就爱", 92 | "SingerName": "凤凰传奇", 93 | "FileName": "凤凰传奇 - 瞒着你就爱" 94 | }, 95 | { 96 | "SongName": "慢慢习惯", 97 | "SingerName": "刘德华", 98 | "FileName": "刘德华 - 慢慢习惯" 99 | }, 100 | { 101 | "SongName": "对你说爱", 102 | "SingerName": "孙楠", 103 | "FileName": "孙楠 - 对你说爱" 104 | }, 105 | { 106 | "SongName": "失落的缘", 107 | "SingerName": "谭维维", 108 | "FileName": "谭维维 - 失落的缘" 109 | }, 110 | { 111 | "SongName": "等小姐", 112 | "SingerName": "阿悄", 113 | "FileName": "阿悄 - 等小姐" 114 | }, 115 | { 116 | "SongName": "从心出发", 117 | "SingerName": "庄心妍", 118 | "FileName": "庄心妍 - 从心出发" 119 | }, 120 | { 121 | "SongName": "动物世界", 122 | "SingerName": "薛之谦", 123 | "FileName": "薛之谦 - 动物世界" 124 | }, 125 | { 126 | "SongName": "亲爱的,同学", 127 | "SingerName": "谭松韵", 128 | "FileName": "谭松韵 - 亲爱的,同学" 129 | }, 130 | { 131 | "SongName": "珍珠", 132 | "SingerName": "吉克隽逸", 133 | "FileName": "吉克隽逸 - 珍珠" 134 | }, 135 | { 136 | "SongName": "天衍录", 137 | "SingerName": "柳岩", 138 | "FileName": "柳岩 - 天衍录" 139 | } 140 | ], 141 | "recommend": [ 142 | { 143 | "SongName": "刚好遇见你", 144 | "SingerName": "李玉刚", 145 | "FileName": "李玉刚 - 刚好遇见你" 146 | }, 147 | { 148 | "SongName": "演员", 149 | "SingerName": "薛之谦", 150 | "FileName": "薛之谦 - 演员" 151 | }, 152 | { 153 | "SongName": "逆流成河", 154 | "SingerName": "金南玲", 155 | "FileName": "金南玲 - 逆流成河" 156 | }, 157 | { 158 | "SongName": "勉为其难", 159 | "SingerName": "王冕", 160 | "FileName": "王冕 - 勉为其难" 161 | }, 162 | { 163 | "SongName": "没有你陪伴真的好孤单", 164 | "SingerName": "梦然", 165 | "FileName": "梦然 - 没有你陪伴真的好孤单" 166 | }, 167 | { 168 | "SongName": "社会摇", 169 | "SingerName": "萧全", 170 | "FileName": "萧全 - 社会摇" 171 | }, 172 | { 173 | "SongName": "你还要我怎样", 174 | "SingerName": "薛之谦", 175 | "FileName": "薛之谦 - 你还要我怎样" 176 | }, 177 | { 178 | "SongName": "繁花", 179 | "SingerName": "董贞", 180 | "FileName": "董贞 - 繁花" 181 | }, 182 | { 183 | "SongName": "走着走着就散了", 184 | "SingerName": "庄心妍", 185 | "FileName": "庄心妍 - 走着走着就散了" 186 | }, 187 | { 188 | "SongName": "童话镇", 189 | "SingerName": "陈一发", 190 | "FileName": "陈一发 - 童话镇" 191 | } 192 | ] 193 | } -------------------------------------------------------------------------------- /src/pages/header.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | import { NavLink, useNavigate, useLocation } from 'react-router-dom'; 4 | import DropList from '../components/drop-list'; 5 | import { search as apiSearch } from '@src/api'; 6 | import debounce from 'lodash/debounce'; 7 | import '../less/header.less'; 8 | import { RootState } from '@src/store'; 9 | import { SongType } from '@src/types'; 10 | 11 | const Header: React.FC = () => { 12 | const navigate = useNavigate(); 13 | const location = useLocation(); 14 | 15 | const searchListCount = useSelector( 16 | state => state.searchListCount 17 | ); 18 | 19 | const [keyword, setKeyword] = useState(''); 20 | const [resultCount, setResultCount] = useState(0); 21 | const [resultList, setResultList] = useState([]); 22 | const [searchTip, setSearchTip] = useState(''); 23 | 24 | // 根据关键字搜索(模糊查询) 25 | const query = debounce((queryText: string) => { 26 | if (queryText.trim() === '') return; 27 | 28 | setResultCount(0); 29 | setSearchTip('正在搜索...'); 30 | 31 | apiSearch(queryText) 32 | .then(res => { 33 | console.log('>>> [res] 根据关键字搜索', res); 34 | if (res.status === 200 && res.statusText === 'OK') { 35 | const { RecordDatas, RecordCount } = res.data.data[0]; 36 | setResultList(RecordDatas); 37 | setResultCount(RecordCount); 38 | if (RecordDatas.length <= 0) { 39 | setResultCount(0); 40 | setSearchTip('暂无结果...'); 41 | } 42 | } else { 43 | setSearchTip('搜索出错, 请稍后重试'); 44 | } 45 | }) 46 | .catch(err => { 47 | console.log('>>> [err] 根据关键字搜索', err); 48 | setResultCount(0); 49 | setSearchTip('网络出现错误或服务不可用'); 50 | }); 51 | }, 600); 52 | 53 | // 监听输入框的input事件 54 | const onInput = useCallback((evt: React.ChangeEvent) => { 55 | evt.persist(); 56 | setKeyword(evt.target.value); 57 | query(evt.target.value); 58 | }, []); 59 | 60 | // 点击搜索事件, keyword为关键字 61 | const search = (searchText: string) => () => { 62 | if (!searchText) { 63 | window.alert('请输入搜索内容'); 64 | return; 65 | } 66 | navigate('/search/' + searchText); 67 | setKeyword(''); 68 | }; 69 | 70 | // 后退 71 | const goback = () => { 72 | history.go(-1); 73 | }; 74 | 75 | const getActiveLinkClass = (params: { isActive: boolean }) => { 76 | return params.isActive ? 'active' : ''; 77 | }; 78 | 79 | const path = location.pathname.split('/')[1]; 80 | const dropListProps = { 81 | resultCount, 82 | resultList, 83 | searchTip, 84 | search, 85 | }; 86 | 87 | return ( 88 | 142 | ); 143 | }; 144 | 145 | export default Header; 146 | -------------------------------------------------------------------------------- /src/less/play-detail.less: -------------------------------------------------------------------------------- 1 | @import (reference) './mixins/index.less'; 2 | 3 | #playDetail, 4 | .playDetail-mark { 5 | position: fixed; 6 | top: 0; 7 | left: 0; 8 | bottom: 0; 9 | width: 100%; 10 | } 11 | 12 | #playDetail { 13 | z-index: 99; 14 | display: flex; 15 | flex-direction: column; 16 | background-size: cover; 17 | background-color: #fff; 18 | background-repeat: no-repeat; 19 | background-position: center; 20 | transform-origin: 0 100%; 21 | transform: translateX(100%) rotateZ(90deg); 22 | transition: all 0.5s; 23 | &.slideIn { 24 | transform: translateX(0) rotateZ(0deg); 25 | } 26 | 27 | .playDetail-top { 28 | position: relative; 29 | padding: 0.4167rem 0; 30 | .fontSize(30); 31 | .goback { 32 | top: 50%; 33 | left: 0.2778rem; 34 | width: 0.5556rem; 35 | height: 0.5556rem; 36 | position: absolute; 37 | transform: translate3d(0, -50%, 0); 38 | .bgImgContain; 39 | .bgImg('icon-goback'); 40 | } 41 | .playDetail-title { 42 | color: #fff; 43 | text-align: center; 44 | padding: 0 0.9722rem; 45 | } 46 | } 47 | .playDetail-center { 48 | color: #fff; 49 | overflow: hidden; 50 | position: relative; 51 | margin: 2rem 0; 52 | flex: 1; 53 | .lrc-box { 54 | height: 100%; 55 | transition: all 0.5s; 56 | p { 57 | padding: 0.1389rem 0; 58 | text-align: center; 59 | .fontSize(28); 60 | &.current { 61 | color: #d1c90e; 62 | } 63 | } 64 | } 65 | } 66 | .playDetail-bottom { 67 | position: relative; 68 | .lrc-switch { 69 | position: absolute; 70 | left: 0.8333rem; 71 | top: -0.8333rem; 72 | } 73 | .lrcColor-box { 74 | position: absolute; 75 | right: 0.8333rem; 76 | top: -0.8333rem; 77 | .cur-lrcColor, 78 | .color-list li { 79 | width: 0.8333rem; 80 | height: 0.8333rem; 81 | background-size: contain; 82 | background-repeat: no-repeat; 83 | background-position: center; 84 | } 85 | .color-list { 86 | position: fixed; 87 | bottom: 4.1667rem; 88 | right: 0.5556rem; 89 | padding: 0.2778rem; 90 | background-color: #000; 91 | li { 92 | margin-bottom: 0.2778rem; 93 | &:last-of-type { 94 | margin-bottom: 0; 95 | } 96 | } 97 | } 98 | } 99 | .time-wrap { 100 | color: #dcdcdc; 101 | display: flex; 102 | align-items: center; 103 | padding: 0 0.4167rem; 104 | margin: 0.2778rem 0; 105 | .progress-wrap { 106 | flex: 1; 107 | height: 0.0833rem; 108 | position: relative; 109 | margin: 0 0.2778rem; 110 | background-color: #6c6b70; 111 | .progress-bar { 112 | width: 100%; 113 | height: 100%; 114 | position: relative; 115 | z-index: 1; 116 | } 117 | .progress { 118 | left: 0; 119 | top: 0; 120 | height: 100%; 121 | position: absolute; 122 | background-color: #3195fd; 123 | } 124 | .progress-dot { 125 | top: 50%; 126 | position: absolute; 127 | width: 0.3333rem; 128 | height: 0.3333rem; 129 | border-radius: 100%; 130 | background-color: #3195fd; 131 | transform: translate3d(0, -50%, 0); 132 | } 133 | } 134 | } 135 | .play-operateBox { 136 | display: flex; 137 | align-items: center; 138 | margin-bottom: 0.2778rem; 139 | .listen-mode { 140 | width: 0.8333rem; 141 | height: 0.75rem; 142 | margin-left: 0.8333rem; 143 | .bgImgContain; 144 | .mode-tip { 145 | left: 50%; 146 | bottom: 18%; 147 | .fontSize(22); 148 | color: #d1c90e; 149 | position: fixed; 150 | transform: translate3d(-50%, 0, 0); 151 | } 152 | } 153 | .order-play { 154 | .bgImg('order-play'); 155 | } 156 | .loop-play { 157 | .bgImg('loop-play'); 158 | } 159 | .random-play { 160 | .bgImg('random-play'); 161 | } 162 | .detail-list { 163 | margin-right: 0.8333rem; 164 | .icon-list { 165 | width: 0.8333rem; 166 | height: 0.8333rem; 167 | .bgImgContain; 168 | .bgImg('icon-list'); 169 | } 170 | .active-list { 171 | .bgImg('active-list'); 172 | } 173 | .play-list { 174 | color: #fff; 175 | position: fixed; 176 | right: 0.8333rem; 177 | bottom: 1.9444rem; 178 | width: 6.6667rem; 179 | border-radius: 4px; 180 | max-height: 10rem; 181 | overflow-y: scroll; 182 | background-color: rgba(0, 0, 0, 0.9); 183 | li { 184 | padding: 0.1944rem 0.2778rem; 185 | overflow: hidden; 186 | white-space: nowrap; 187 | text-overflow: ellipsis; 188 | &.active { 189 | color: #2ca2f9; 190 | } 191 | } 192 | } 193 | } 194 | } 195 | } 196 | } 197 | 198 | .playDetail-mark { 199 | background-color: rgba(0, 0, 0, 0.6); 200 | } 201 | -------------------------------------------------------------------------------- /src/pages/list.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useEffect } from 'react'; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import _cloneDeep from 'lodash/cloneDeep'; 4 | import ListItem from '../components/list-item'; 5 | import { 6 | setView, 7 | setSongList, 8 | setSearchListCount, 9 | playSong, 10 | } from '../store/actions'; 11 | import { getList as apiGetList, getSongInfo as apiGetSongInfo } from '@src/api'; 12 | import { Toast } from 'antd-mobile'; 13 | import '../less/list.less'; 14 | import { useParams } from 'react-router'; 15 | import { RootState } from '@src/store'; 16 | import { IPlaySongInfo, SongType, SongTypes } from '@src/types'; 17 | import { ISong } from '@src/types'; 18 | 19 | const List: React.FC = () => { 20 | const dispatch = useDispatch(); 21 | 22 | const { keyword, type = SongType.new } = 23 | useParams<{ keyword?: string; type?: SongTypes }>(); 24 | 25 | console.log('params', keyword, type); 26 | 27 | const searchObj = useRef({ 28 | page: 1, 29 | totalPage: 0, 30 | loading: false, 31 | allLoaded: false, 32 | }); 33 | 34 | const view = useSelector(state => state.view); 35 | const curPlaySong = useSelector( 36 | state => state.curPlaySong 37 | ); 38 | const isPlayed = useSelector(state => state.isPlayed); 39 | 40 | const [list, setList] = useState([]); 41 | 42 | // 渲染静态数据(song.json中的)列表数据 43 | const getStaticList = () => { 44 | Toast.loading('加载中...', 0); 45 | apiGetList(type) 46 | .then(res => { 47 | console.log('>>> [res] 渲染列表数据', res); 48 | setTimeout(() => { 49 | Toast.hide(); 50 | setList(prevList => [...prevList, ...res.data.data]); 51 | }, 800); 52 | }) 53 | .catch(err => { 54 | Toast.hide(); 55 | Toast.fail('请求出错'); 56 | console.log('>>> [err] 渲染列表数据', err); 57 | }); 58 | }; 59 | 60 | // 获取根据关键字搜索后得到的歌曲列表 61 | const getSearchList = () => { 62 | if (!keyword) { 63 | return; 64 | } 65 | 66 | searchObj.current.loading = true; 67 | Toast.loading('加载中...', 0); 68 | 69 | apiGetSongInfo(keyword, searchObj.current.page) 70 | .then(res => { 71 | Toast.hide(); 72 | console.log('>>> [res] 搜索后得到的歌曲列表', res); 73 | const data = _cloneDeep(res.data.data); 74 | if (res.status === 200 && res.statusText === 'OK') { 75 | const searchListCount = data.total; 76 | searchObj.current.totalPage = Math.ceil(searchListCount / 20); 77 | const searchSongList = data.lists.map((song: ISong) => { 78 | return { 79 | SingerName: song.SingerName, 80 | SongName: song.SongName, 81 | FileName: song.FileName, 82 | }; 83 | }); 84 | searchObj.current.loading = false; 85 | setList(prevList => [...prevList, ...searchSongList]); 86 | dispatch(setSearchListCount(searchListCount)); 87 | } 88 | }) 89 | .catch(err => { 90 | Toast.hide(); 91 | console.log('>>> [err] 搜索后得到的歌曲列表', err); 92 | Toast.fail('网络出现错误或服务暂时不可用'); 93 | }); 94 | }; 95 | 96 | // 播放歌曲 97 | const onPlay = (curPlayIndex: number) => { 98 | dispatch(setView(type)); 99 | dispatch(setSongList(list)); 100 | dispatch(playSong(curPlayIndex)); 101 | }; 102 | 103 | // 滑动加载 104 | const scrollLoad = () => { 105 | if (searchObj.current.loading || searchObj.current.allLoaded) { 106 | return; 107 | } 108 | const docEl = document.documentElement; 109 | /* 110 | scrollTop 元素滚动的高度 111 | scrollHeight 元素的实际高度(包括滚动的高度) 112 | clientHeight 元素在窗口可见的高度(不包括滚动的高度) 113 | */ 114 | const { scrollTop, scrollHeight, clientHeight } = docEl; 115 | const offsetHeight = scrollHeight - scrollTop - clientHeight; 116 | 117 | if (offsetHeight <= 100) { 118 | if (searchObj.current.page < searchObj.current.totalPage) { 119 | searchObj.current.page += 1; 120 | getSearchList(); 121 | } else { 122 | searchObj.current.allLoaded = true; 123 | document.onscroll = null; // 注销事件 124 | Toast.info('已加载全部数据!'); 125 | } 126 | } 127 | }; 128 | 129 | useEffect(() => { 130 | if (!keyword) { 131 | getStaticList(); 132 | } else { 133 | getSearchList(); 134 | } 135 | 136 | let timer: NodeJS.Timeout | undefined; 137 | document.onscroll = () => { 138 | if (timer !== undefined) { 139 | clearTimeout(timer); 140 | } 141 | timer = setTimeout(() => { 142 | if (keyword) { 143 | scrollLoad(); 144 | } 145 | }, 100); 146 | }; 147 | 148 | return () => { 149 | document.onscroll = null; 150 | }; 151 | }, [type]); 152 | 153 | return ( 154 |
    155 |
    156 |
      157 | {list.map((song, index) => { 158 | const listItemProps = { 159 | onPlay, 160 | song, 161 | index, 162 | active: view === type && index === curPlaySong.index && isPlayed, 163 | }; 164 | return ; 165 | })} 166 |
    167 |
    168 |
    169 | ); 170 | }; 171 | 172 | export default List; 173 | -------------------------------------------------------------------------------- /src/store/reducers/index.ts: -------------------------------------------------------------------------------- 1 | // reducers 2 | import { 3 | SET_VIEW, 4 | SET_SONG_LIST, 5 | SET_SEARCH_LIST_COUNT, 6 | SET_AUDIO, 7 | SET_AUDIO_SRC, 8 | SET_IS_PLAYED, 9 | SET_CAN_PLAYED, 10 | SET_PAUSED, 11 | SET_CUR_PLAY_IMR_SRC, 12 | SET_CUR_PLAY_LRC_ARR, 13 | SET_CUR_PLAY_SONG, 14 | SET_LOCK, 15 | SET_LOOP, 16 | SET_MODE_TYPE, 17 | SET_LRC_CONFIG, 18 | SET_LRC_SWITCH, 19 | } from '../actions'; 20 | import { 21 | AudioEle, 22 | IAction, 23 | ILrcConfig, 24 | IPlayLrc, 25 | IPlaySongInfo, 26 | ISong, 27 | } from '@src/types'; 28 | 29 | // 当前播放歌曲所属的路由 30 | export const view = (state: string = '', action: IAction): string => { 31 | if (action.type === SET_VIEW) { 32 | return action.payload.view; 33 | } 34 | return state; 35 | }; 36 | 37 | // 当前播放歌曲所属的歌词列表 38 | export const songList = (state: ISong[] = [], action: IAction): ISong[] => { 39 | if (action.type === SET_SONG_LIST) { 40 | return action.payload.songList; 41 | } 42 | return state; 43 | }; 44 | 45 | // 搜索框搜索后得到的歌曲数量 46 | export const searchListCount = (state: number = 0, action: IAction): number => { 47 | if (action.type === SET_SEARCH_LIST_COUNT) { 48 | return action.payload.searchListCount; 49 | } 50 | return state; 51 | }; 52 | 53 | // audio的dom节点 54 | export const audio = (state: AudioEle = null, action: IAction): AudioEle => { 55 | if (action.type === SET_AUDIO) { 56 | return action.payload.audio; 57 | } 58 | return state; 59 | }; 60 | 61 | // audioSrc播放来源 62 | export const audioSrc = ( 63 | state: string | null = null, 64 | action: IAction 65 | ): string | null => { 66 | if (action.type === SET_AUDIO_SRC) { 67 | return action.payload.audioSrc; 68 | } 69 | return state; 70 | }; 71 | 72 | // 是否有音乐在播放 73 | export const isPlayed = (state: boolean = false, action: IAction): boolean => { 74 | if (action.type === SET_IS_PLAYED) { 75 | return action.payload.isPlayed; 76 | } 77 | return state; 78 | }; 79 | 80 | // 是否可以播放音乐 81 | export const canPlayed = (state: boolean = false, action: IAction): boolean => { 82 | if (action.type === SET_CAN_PLAYED) { 83 | return action.payload.canPlayed; 84 | } 85 | return state; 86 | }; 87 | 88 | // 音频是否为暂停状态 89 | export const paused = (state: boolean = false, action: IAction): boolean => { 90 | if (action.type === SET_PAUSED) { 91 | if (action.payload.paused !== undefined) { 92 | return action.payload.paused; 93 | } 94 | return !state; 95 | } 96 | return state; 97 | }; 98 | 99 | const defaultCurPlaySong: IPlaySongInfo = { 100 | index: -1, // 当前播放歌曲的索引 101 | FileName: '', // 当前播放歌曲的全名 102 | SongName: '', // 当前播放歌曲的歌曲名 103 | SingerName: '', // 当前播放歌曲的歌手名 104 | }; 105 | export const curPlaySong = ( 106 | state: IPlaySongInfo = defaultCurPlaySong, 107 | action: IAction 108 | ): IPlaySongInfo => { 109 | if (action.type === SET_CUR_PLAY_SONG) { 110 | return { 111 | ...state, 112 | ...action.payload.curPlaySong, 113 | }; 114 | } 115 | return state; 116 | }; 117 | 118 | // 歌手图片来源, 默认值 119 | export const curPlayImgSrc = ( 120 | state: string = require('../../static/images/singer-default.jpg'), 121 | action: IAction 122 | ): string => { 123 | if (action.type === SET_CUR_PLAY_IMR_SRC) { 124 | return action.payload.curPlayImgSrc; 125 | } 126 | return state; 127 | }; 128 | 129 | // 歌词数组 130 | export const curPlayLrcArr = ( 131 | state: IPlayLrc[] = [], 132 | action: IAction 133 | ): IPlayLrc[] => { 134 | if (action.type === SET_CUR_PLAY_LRC_ARR) { 135 | if (action.payload.lyrics.length === 0) { 136 | return state; 137 | } 138 | const lrc: string[] = action.payload.lyrics 139 | .replace(/\n/g, '') 140 | .split('[') 141 | .slice(1); 142 | const curPlayLrcArr: IPlayLrc[] = []; 143 | for (const [index, item] of lrc.entries()) { 144 | const times = item.split(']')[0].replace('.', ':').split(':'); 145 | const time = 146 | Number(times[0]) * 60 + Number(times[1]) + Number(times[2]) / 1000; 147 | const obj = { 148 | index, 149 | startTime: time.toFixed(2), 150 | curLrc: item.split(']')[1], 151 | }; 152 | curPlayLrcArr.push(obj); 153 | } 154 | return curPlayLrcArr; 155 | } 156 | return state; 157 | }; 158 | 159 | // 事件开关, 防止canplay事件多次执行 160 | export const lock = (state: boolean = false, action: IAction): boolean => { 161 | if (action.type === SET_LOCK) { 162 | return action.payload.lock; 163 | } 164 | return state; 165 | }; 166 | 167 | // 歌曲是否循环播放 168 | export const loop = (state: boolean = false, action: IAction): boolean => { 169 | if (action.type === SET_LOOP) { 170 | return action.payload.loop; 171 | } 172 | return state; 173 | }; 174 | 175 | // 播放模式 176 | export const modeType = (state: string = 'order', action: IAction): string => { 177 | if (action.type === SET_MODE_TYPE) { 178 | return action.payload.modeType; 179 | } 180 | return state; 181 | }; 182 | 183 | // 歌词的默认配置 184 | const defaultLrcConfig: ILrcConfig = { 185 | activeColor: '#d1c90e', // 高亮行的歌词颜色 186 | defaultColor: '#b2f5b5', // 其他行的歌词颜色 187 | activeLrcIndex: 0, // 高亮行歌词的索引 188 | }; 189 | export const lrcConfig = ( 190 | state: ILrcConfig = defaultLrcConfig, 191 | action: IAction 192 | ): ILrcConfig => { 193 | if (action.type === SET_LRC_CONFIG) { 194 | return { 195 | ...state, 196 | ...action.payload.lrcConfig, 197 | }; 198 | } 199 | return state; 200 | }; 201 | 202 | // 是否显示悬浮歌词 203 | export const lrcSwitch = (state: boolean = false, action: IAction): boolean => { 204 | if (action.type === SET_LRC_SWITCH) { 205 | return action.payload.lrcSwitch; 206 | } 207 | return state; 208 | }; 209 | -------------------------------------------------------------------------------- /src/pages/suspend-lyric.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import React, { Component } from 'react'; 3 | import { bindActionCreators } from 'redux'; 4 | import { setLrcSwitch } from '../store/actions'; 5 | import PropTypes from 'prop-types'; 6 | import '../less/suspend-lyric.less'; 7 | 8 | @connect( 9 | state => ({ 10 | canPlayed: state.canPlayed, 11 | curPlayLrcArr: state.curPlayLrcArr, 12 | lrcConfig: state.lrcConfig, 13 | lrcSwitch: state.lrcSwitch, 14 | }), 15 | dispatch => ({ 16 | ...bindActionCreators( 17 | { 18 | setLrcSwitch, 19 | }, 20 | dispatch 21 | ), 22 | }) 23 | ) 24 | class SuspendLyric extends Component { 25 | static propTypes = { 26 | canPlayed: PropTypes.bool, 27 | curPlayLrcArr: PropTypes.array, 28 | lrcConfig: PropTypes.object, 29 | lrcSwitch: PropTypes.bool, 30 | setLrcSwitch: PropTypes.func, 31 | }; 32 | 33 | constructor() { 34 | super(); 35 | this.boundary = { 36 | // 各个边界的值 37 | left: 0, 38 | right: 0, 39 | top: 0, 40 | bottom: 0, 41 | }; 42 | this.isDrag = false; // 判断是否处于拖拽中 43 | this.startX = 0; // 鼠标按下起始位置的x坐标 44 | this.startY = 0; // 鼠标按下起始位置的y坐标 45 | this.offsetX = 0; // 元素在x轴上的偏移量 46 | this.offsetY = 0; // 元素在y轴上的偏移量 47 | 48 | this.state = { 49 | firstLrc: {}, // 第一行的歌词 50 | nextLrc: {}, // 第二行的歌词 51 | }; 52 | } 53 | UNSAFE_componentWillReceiveProps(nextProps) { 54 | if (nextProps.curPlayLrcArr.length > 0) { 55 | this.getBoundary(); 56 | this.setSuspendLrc(nextProps); 57 | } 58 | } 59 | getBoundary() { 60 | const left = 0; 61 | const right = document.body.offsetWidth - this.suspendLyric.offsetWidth; 62 | const top = 0; 63 | const bottom = document.body.offsetHeight - this.suspendLyric.offsetHeight; 64 | this.boundary = Object.assign({}, this.boundary, { 65 | left, // 左边界 66 | right, // 右边界 67 | top, // 上边界 68 | bottom, // 下边界 69 | }); 70 | } 71 | // 设置上下两行歌词 72 | setSuspendLrc(props) { 73 | let firstLrc = {}; 74 | let nextLrc = {}; 75 | const { curPlayLrcArr, lrcConfig } = props; 76 | const activeLrcIndex = lrcConfig.activeLrcIndex; 77 | if (activeLrcIndex === 0) { 78 | firstLrc = curPlayLrcArr[0]; 79 | } else if ((activeLrcIndex + 1) % 2 === 0) { 80 | if (!curPlayLrcArr[activeLrcIndex + 1]) { 81 | firstLrc = { 82 | ...curPlayLrcArr[activeLrcIndex], 83 | index: activeLrcIndex + 1, 84 | curLrc: '', 85 | }; 86 | } else { 87 | firstLrc = curPlayLrcArr[activeLrcIndex + 1]; 88 | } 89 | } else { 90 | firstLrc = curPlayLrcArr[activeLrcIndex]; 91 | } 92 | 93 | if (activeLrcIndex === 0 || activeLrcIndex === 1) { 94 | nextLrc = curPlayLrcArr[1]; 95 | } else if ((activeLrcIndex + 1) % 2 === 1) { 96 | if (!curPlayLrcArr[activeLrcIndex + 1]) { 97 | nextLrc = { 98 | ...curPlayLrcArr[activeLrcIndex], 99 | index: activeLrcIndex + 1, 100 | curLrc: '', 101 | }; 102 | } else { 103 | nextLrc = curPlayLrcArr[activeLrcIndex + 1]; 104 | } 105 | } else { 106 | nextLrc = curPlayLrcArr[activeLrcIndex]; 107 | } 108 | 109 | this.setState({ 110 | firstLrc, 111 | nextLrc, 112 | }); 113 | } 114 | touchstart(evt) { 115 | this.isDrag = true; 116 | this.startX = evt.targetTouches[0].pageX; 117 | this.startY = evt.targetTouches[0].pageY; 118 | this.offsetX = this.suspendLyric.offsetLeft; 119 | this.offsetY = this.suspendLyric.offsetTop; 120 | } 121 | touchmove(evt) { 122 | /*if(!evt.defaultPrevented) { 123 | evt.preventDefault(); 124 | }*/ 125 | const endX = evt.targetTouches[0].pageX; 126 | const endY = evt.targetTouches[0].pageY; 127 | let endLeft = endX - this.startX + this.offsetX; 128 | let endTop = endY - this.startY + this.offsetY; 129 | 130 | if (endLeft <= this.boundary.left) { 131 | endLeft = this.boundary.left; 132 | } else if (endLeft >= this.boundary.right) { 133 | endLeft = this.boundary.right; 134 | } 135 | if (endTop <= this.boundary.top) { 136 | endTop = this.boundary.top; 137 | } else if (endTop >= this.boundary.bottom) { 138 | endTop = this.boundary.bottom; 139 | } 140 | 141 | this.suspendLyric.style.left = endLeft + 'px'; 142 | this.suspendLyric.style.top = endTop + 'px'; 143 | } 144 | touchend() { 145 | this.isDrag = false; 146 | } 147 | // 关闭悬浮歌词 148 | close() { 149 | this.props.setLrcSwitch(false); 150 | window.localStorage.lrcSwitch = false; 151 | } 152 | render() { 153 | const { canPlayed, lrcConfig, lrcSwitch } = this.props; 154 | 155 | const { firstLrc, nextLrc } = this.state; 156 | const firstLrcStyle = { 157 | color: 158 | firstLrc.index === lrcConfig.activeLrcIndex 159 | ? lrcConfig.activeColor 160 | : lrcConfig.defaultColor, 161 | }; 162 | const nextLrcStyle = { 163 | color: 164 | nextLrc.index === lrcConfig.activeLrcIndex 165 | ? lrcConfig.activeColor 166 | : lrcConfig.defaultColor, 167 | }; 168 | return ( 169 |
    (this.suspendLyric = el)} 173 | onTouchStart={evt => { 174 | this.touchstart(evt); 175 | }} 176 | onTouchMove={evt => { 177 | this.touchmove(evt); 178 | }} 179 | onTouchEnd={evt => { 180 | this.touchend(evt); 181 | }} 182 | > 183 | 184 |

    {firstLrc.curLrc}

    185 |

    {nextLrc.curLrc}

    186 |
    187 | ); 188 | } 189 | } 190 | 191 | export default SuspendLyric; 192 | -------------------------------------------------------------------------------- /src/store/actions/index.ts: -------------------------------------------------------------------------------- 1 | // action creators function 2 | import { getSongInfo as apiGetSongInfo, play as apiPlay } from '@src/api'; 3 | import { Toast } from 'antd-mobile'; 4 | import _cloneDeep from 'lodash/cloneDeep'; 5 | import { Dispatch } from 'redux'; 6 | import { 7 | AudioEle, 8 | IAction, 9 | ILrcConfig, 10 | IPlaySongInfo, 11 | ISong, 12 | } from '@src/types'; 13 | import { ThunkDispatch } from 'redux-thunk'; 14 | import { GetStateFn, RootState } from '..'; 15 | 16 | export const SET_VIEW = 'SET_VIEW'; 17 | export function setView(view: string): IAction { 18 | return { 19 | type: SET_VIEW, 20 | payload: { 21 | view, 22 | }, 23 | }; 24 | } 25 | 26 | export const SET_SONG_LIST = 'SET_SONG_LIST'; 27 | export function setSongList(songList: ISong[]): IAction { 28 | return { 29 | type: 'SET_SONG_LIST', 30 | payload: { 31 | songList, 32 | }, 33 | }; 34 | } 35 | 36 | export const SET_SEARCH_LIST_COUNT = 'SET_SEARCH_LIST_COUNT'; 37 | export function setSearchListCount(searchListCount: number): IAction { 38 | return { 39 | type: SET_SEARCH_LIST_COUNT, 40 | payload: { 41 | searchListCount, 42 | }, 43 | }; 44 | } 45 | 46 | export const SET_AUDIO = 'SET_AUDIO'; 47 | export function setAudio(audio: AudioEle): IAction { 48 | return { 49 | type: SET_AUDIO, 50 | payload: { 51 | audio, 52 | }, 53 | }; 54 | } 55 | 56 | export const SET_AUDIO_SRC = 'SET_AUDIO_SRC'; 57 | export function setAudioSrc(audioSrc: string | null): IAction { 58 | return { 59 | type: SET_AUDIO_SRC, 60 | payload: { 61 | audioSrc, 62 | }, 63 | }; 64 | } 65 | 66 | export const SET_IS_PLAYED = 'SET_IS_PLAYED'; 67 | export function setIsPlayed(isPlayed: boolean): IAction { 68 | return { 69 | type: SET_IS_PLAYED, 70 | payload: { 71 | isPlayed, 72 | }, 73 | }; 74 | } 75 | 76 | export const SET_CAN_PLAYED = 'SET_CAN_PLAYED'; 77 | export function setCanPlayed(canPlayed: boolean): IAction { 78 | return { 79 | type: SET_CAN_PLAYED, 80 | payload: { 81 | canPlayed, 82 | }, 83 | }; 84 | } 85 | 86 | export const SET_PAUSED = 'SET_PAUSED'; 87 | export function setPaused(paused?: boolean): IAction { 88 | if (paused !== undefined) { 89 | return { 90 | type: SET_PAUSED, 91 | payload: { 92 | paused, 93 | }, 94 | }; 95 | } 96 | return { 97 | type: SET_PAUSED, 98 | payload: {}, 99 | }; 100 | } 101 | 102 | export const SET_CUR_PLAY_IMR_SRC = 'SET_CUR_PLAY_IMR_SRC'; 103 | export function setCurPlayImgSrc(curPlayImgSrc: string): IAction { 104 | return { 105 | type: SET_CUR_PLAY_IMR_SRC, 106 | payload: { 107 | curPlayImgSrc, 108 | }, 109 | }; 110 | } 111 | 112 | export const SET_CUR_PLAY_LRC_ARR = 'SET_CUR_PLAY_LRC_ARR'; 113 | export function setCurPlayLrcArr(lyrics: string): IAction { 114 | return { 115 | type: SET_CUR_PLAY_LRC_ARR, 116 | payload: { 117 | lyrics, 118 | }, 119 | }; 120 | } 121 | 122 | export const SET_LOCK = 'SET_LOCK'; 123 | export function setLock(lock: boolean): IAction { 124 | return { 125 | type: SET_LOCK, 126 | payload: { 127 | lock, 128 | }, 129 | }; 130 | } 131 | 132 | export const SET_LOOP = 'SET_LOOP'; 133 | export function setLoop(loop: boolean): IAction { 134 | return { 135 | type: SET_LOOP, 136 | payload: { 137 | loop, 138 | }, 139 | }; 140 | } 141 | 142 | export const SET_MODE_TYPE = 'SET_MODE_TYPE'; 143 | export function setModeType(modeType: string): IAction { 144 | return { 145 | type: SET_MODE_TYPE, 146 | payload: { 147 | modeType, 148 | }, 149 | }; 150 | } 151 | 152 | export const SET_LRC_CONFIG = 'SET_LRC_CONFIG'; 153 | export function setLrcConfig(lrcConfig: ILrcConfig): IAction { 154 | return { 155 | type: SET_LRC_CONFIG, 156 | payload: { 157 | lrcConfig, 158 | }, 159 | }; 160 | } 161 | 162 | export const SET_LRC_SWITCH = 'SET_LRC_SWITCH'; 163 | export function setLrcSwitch(lrcSwitch: boolean): IAction { 164 | return { 165 | type: SET_LRC_SWITCH, 166 | payload: { 167 | lrcSwitch, 168 | }, 169 | }; 170 | } 171 | 172 | export const SET_CUR_PLAY_SONG = 'SET_CUR_PLAY_SONG'; 173 | export function setCurPlaySong(curPlaySong: IPlaySongInfo): IAction { 174 | return { 175 | type: SET_CUR_PLAY_SONG, 176 | payload: { 177 | curPlaySong, 178 | }, 179 | }; 180 | } 181 | 182 | export function togglePlayStatus() { 183 | return (dispatch: Dispatch, getState: GetStateFn) => { 184 | const state = getState(); 185 | if (!state.audioSrc) { 186 | dispatch(setPaused(false)); 187 | return; 188 | } 189 | dispatch(setPaused()); 190 | 191 | const { audio, paused } = getState(); 192 | if (paused) { 193 | audio?.pause(); 194 | } else { 195 | audio?.pause(); 196 | } 197 | }; 198 | } 199 | 200 | export function playSong(curPlayIndex: number) { 201 | return async ( 202 | dispatch: ThunkDispatch, 203 | getState: GetStateFn 204 | ) => { 205 | Toast.loading('加载中...', 0); 206 | 207 | dispatch(setIsPlayed(false)); 208 | dispatch(setAudioSrc('')); 209 | dispatch(setCurPlayLrcArr('')); 210 | dispatch( 211 | setCurPlayImgSrc(require('../../static/images/singer-default.jpg')) 212 | ); 213 | dispatch(togglePlayStatus()); 214 | dispatch(setLock(false)); 215 | dispatch( 216 | setLrcConfig({ 217 | activeLrcIndex: 0, 218 | }) 219 | ); 220 | 221 | const state = getState(); 222 | if (curPlayIndex < 0) { 223 | curPlayIndex = state.songList.length - 1; 224 | } else if (curPlayIndex >= state.songList.length) { 225 | curPlayIndex = 0; 226 | } 227 | const curPlaySong: IPlaySongInfo = { 228 | ...state.songList[curPlayIndex], 229 | }; 230 | curPlaySong.index = curPlayIndex; 231 | dispatch(setCurPlaySong(curPlaySong)); 232 | 233 | try { 234 | const songName = curPlaySong.FileName; 235 | if (!songName) { 236 | return; 237 | } 238 | const infoRes = await apiGetSongInfo(songName); 239 | console.log('>>> [res] 获取歌曲的一些信息', infoRes); 240 | if (infoRes.status === 200 && infoRes.statusText === 'OK') { 241 | let rightSong: any = ''; 242 | for (const song of infoRes.data.data.lists) { 243 | if (song.FileName === songName) { 244 | rightSong = _cloneDeep(song); 245 | break; 246 | } 247 | } 248 | const hash = rightSong.FileHash; 249 | try { 250 | const playRes = await apiPlay(hash); 251 | console.log('>>> [res] 根据hash值获取歌曲的播放信息', playRes); 252 | if (playRes.status === 200 && playRes.statusText === 'OK') { 253 | const data = _cloneDeep(playRes.data.data); 254 | Toast.hide(); 255 | if (!data.play_url) { 256 | Toast.fail('暂无播放来源'); 257 | return; 258 | } 259 | const audioSrc = data.play_url; 260 | const curPlayImgSrc = data.img; 261 | const lyrics = data.lyrics; 262 | dispatch(setCanPlayed(true)); 263 | dispatch(setAudioSrc(audioSrc)); 264 | dispatch(setCurPlayLrcArr(lyrics)); 265 | dispatch(setCurPlayImgSrc(curPlayImgSrc)); 266 | } else { 267 | Toast.hide(); 268 | Toast.fail('播放歌曲失败'); 269 | return; 270 | } 271 | } catch (err) { 272 | Toast.hide(); 273 | console.log('>>> [err] 获取歌曲的信息', err); 274 | Toast.fail('网络出现错误或服务暂时不可用'); 275 | } 276 | } else { 277 | Toast.hide(); 278 | Toast.fail('播放歌曲失败'); 279 | return; 280 | } 281 | } catch (err) { 282 | Toast.hide(); 283 | Toast.fail('网络出现错误或服务暂时不可用'); 284 | throw new Error(err as string); 285 | } 286 | }; 287 | } 288 | -------------------------------------------------------------------------------- /dist/static/css/vendors.2438495c.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}[hidden],template{display:none}.am-fade-appear,.am-fade-enter{opacity:0}.am-fade-appear,.am-fade-enter,.am-fade-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-timing-function:cubic-bezier(.55,0,.55,.2);animation-timing-function:cubic-bezier(.55,0,.55,.2);-webkit-animation-play-state:paused;animation-play-state:paused}.am-fade-appear.am-fade-appear-active,.am-fade-enter.am-fade-enter-active{-webkit-animation-name:amFadeIn;animation-name:amFadeIn;-webkit-animation-play-state:running;animation-play-state:running}.am-fade-leave.am-fade-leave-active{-webkit-animation-name:amFadeOut;animation-name:amFadeOut;-webkit-animation-play-state:running;animation-play-state:running}@-webkit-keyframes amFadeIn{0%{opacity:0}to{opacity:1}}@keyframes amFadeIn{0%{opacity:0}to{opacity:1}}@-webkit-keyframes amFadeOut{0%{opacity:1}to{opacity:0}}@keyframes amFadeOut{0%{opacity:1}to{opacity:0}}.am-slide-up-appear,.am-slide-up-enter{-webkit-transform:translateY(100%);transform:translateY(100%)}.am-slide-up-appear,.am-slide-up-enter,.am-slide-up-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-timing-function:cubic-bezier(.55,0,.55,.2);animation-timing-function:cubic-bezier(.55,0,.55,.2);-webkit-animation-play-state:paused;animation-play-state:paused}.am-slide-up-appear.am-slide-up-appear-active,.am-slide-up-enter.am-slide-up-enter-active{-webkit-animation-name:amSlideUpIn;animation-name:amSlideUpIn;-webkit-animation-play-state:running;animation-play-state:running}.am-slide-up-leave.am-slide-up-leave-active{-webkit-animation-name:amSlideUpOut;animation-name:amSlideUpOut;-webkit-animation-play-state:running;animation-play-state:running}@-webkit-keyframes amSlideUpIn{0%{-webkit-transform:translateY(100%);transform:translateY(100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes amSlideUpIn{0%{-webkit-transform:translateY(100%);transform:translateY(100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes amSlideUpOut{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translateY(100%);transform:translateY(100%)}}@keyframes amSlideUpOut{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translateY(100%);transform:translateY(100%)}}.am.am-zoom-enter,.am.am-zoom-leave{display:block}.am-zoom-appear,.am-zoom-enter{opacity:0;-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-timing-function:cubic-bezier(.55,0,.55,.2);animation-timing-function:cubic-bezier(.55,0,.55,.2);-webkit-animation-timing-function:cubic-bezier(.18,.89,.32,1.28);animation-timing-function:cubic-bezier(.18,.89,.32,1.28);-webkit-animation-play-state:paused;animation-play-state:paused}.am-zoom-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-timing-function:cubic-bezier(.55,0,.55,.2);animation-timing-function:cubic-bezier(.55,0,.55,.2);-webkit-animation-timing-function:cubic-bezier(.6,-.3,.74,.05);animation-timing-function:cubic-bezier(.6,-.3,.74,.05);-webkit-animation-play-state:paused;animation-play-state:paused}.am-zoom-appear.am-zoom-appear-active,.am-zoom-enter.am-zoom-enter-active{-webkit-animation-name:amZoomIn;animation-name:amZoomIn;-webkit-animation-play-state:running;animation-play-state:running}.am-zoom-leave.am-zoom-leave-active{-webkit-animation-name:amZoomOut;animation-name:amZoomOut;-webkit-animation-play-state:running;animation-play-state:running}@-webkit-keyframes amZoomIn{0%{opacity:0;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:scale(0);transform:scale(0)}to{opacity:1;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:scale(1);transform:scale(1)}}@keyframes amZoomIn{0%{opacity:0;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:scale(0);transform:scale(0)}to{opacity:1;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes amZoomOut{0%{opacity:1;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:scale(1);transform:scale(1)}to{opacity:0;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:scale(0);transform:scale(0)}}@keyframes amZoomOut{0%{opacity:1;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:scale(1);transform:scale(1)}to{opacity:0;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:scale(0);transform:scale(0)}}.am-slide-down-appear,.am-slide-down-enter{-webkit-transform:translateY(-100%);transform:translateY(-100%)}.am-slide-down-appear,.am-slide-down-enter,.am-slide-down-leave{-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-timing-function:cubic-bezier(.55,0,.55,.2);animation-timing-function:cubic-bezier(.55,0,.55,.2);-webkit-animation-play-state:paused;animation-play-state:paused}.am-slide-down-appear.am-slide-down-appear-active,.am-slide-down-enter.am-slide-down-enter-active{-webkit-animation-name:amSlideDownIn;animation-name:amSlideDownIn;-webkit-animation-play-state:running;animation-play-state:running}.am-slide-down-leave.am-slide-down-leave-active{-webkit-animation-name:amSlideDownOut;animation-name:amSlideDownOut;-webkit-animation-play-state:running;animation-play-state:running}@-webkit-keyframes amSlideDownIn{0%{-webkit-transform:translateY(-100%);transform:translateY(-100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes amSlideDownIn{0%{-webkit-transform:translateY(-100%);transform:translateY(-100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes amSlideDownOut{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translateY(-100%);transform:translateY(-100%)}}@keyframes amSlideDownOut{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translateY(-100%);transform:translateY(-100%)}}*,:after,:before{-webkit-tap-highlight-color:rgba(0,0,0,0)}body{background-color:#f5f5f9;font-size:14px}[contenteditable]{-webkit-user-select:auto!important}:focus,a{outline:none}a{background:transparent;text-decoration:none}.am-icon{fill:currentColor;background-size:cover;width:22px;height:22px}.am-icon-xxs{width:15px;height:15px}.am-icon-xs{width:18px;height:18px}.am-icon-sm{width:21px;height:21px}.am-icon-md{width:22px;height:22px}.am-icon-lg{width:36px;height:36px}.am-icon-loading{-webkit-animation:cirle-anim 1s linear infinite;animation:cirle-anim 1s linear infinite}@-webkit-keyframes cirle-anim{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes cirle-anim{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.am-toast{position:fixed;width:100%;z-index:1999;font-size:14px;text-align:center}.am-toast>span{max-width:50%}.am-toast.am-toast-mask{height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;left:0;top:0}.am-toast.am-toast-mask,.am-toast.am-toast-nomask{-webkit-transform:translateZ(1px);transform:translateZ(1px)}.am-toast.am-toast-nomask{position:fixed;max-width:50%;width:auto;left:50%;top:50%}.am-toast.am-toast-nomask .am-toast-notice{-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.am-toast-notice-content .am-toast-text{min-width:60px;border-radius:3px;color:#fff;background-color:rgba(58,58,58,.9);line-height:1.5;padding:9px 15px}.am-toast-notice-content .am-toast-text.am-toast-text-icon{border-radius:5px;padding:15px}.am-toast-notice-content .am-toast-text.am-toast-text-icon .am-toast-text-info{margin-top:6px}.am-switch{display:inline-block;vertical-align:middle;-ms-flex-item-align:center;align-self:center}.am-switch,.am-switch .checkbox{-webkit-box-sizing:border-box;box-sizing:border-box;position:relative;cursor:pointer}.am-switch .checkbox{width:51px;height:31px;border-radius:31px;background:#e5e5e5;z-index:0;margin:0;padding:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;-webkit-transition:all .3s;transition:all .3s}.am-switch .checkbox:before{width:48px;-webkit-box-sizing:border-box;box-sizing:border-box;z-index:1;-webkit-transform:scale(1);transform:scale(1)}.am-switch .checkbox:after,.am-switch .checkbox:before{content:" ";position:absolute;left:1.5px;top:1.5px;height:28px;border-radius:28px;background:#fff;-webkit-transition:all .2s;transition:all .2s}.am-switch .checkbox:after{width:28px;z-index:2;-webkit-transform:translateX(0);transform:translateX(0);-webkit-box-shadow:2px 2px 4px rgba(0,0,0,.21);box-shadow:2px 2px 4px rgba(0,0,0,.21)}.am-switch .checkbox.checkbox-disabled{z-index:3}.am-switch input[type=checkbox]{position:absolute;top:0;left:0;opacity:0;width:100%;height:100%;z-index:2;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.am-switch input[type=checkbox]:checked+.checkbox{background:#4dd865}.am-switch input[type=checkbox]:checked+.checkbox:before{-webkit-transform:scale(0);transform:scale(0)}.am-switch input[type=checkbox]:checked+.checkbox:after{-webkit-transform:translateX(20px);transform:translateX(20px)}.am-switch input[type=checkbox]:disabled+.checkbox{opacity:.3}.am-switch.am-switch-android .checkbox{width:72px;height:23px;border-radius:3px;background:#a7aaa6}.am-switch.am-switch-android .checkbox:before{display:none}.am-switch.am-switch-android .checkbox:after{width:35px;height:21px;border-radius:2px;-webkit-box-shadow:none;box-shadow:none;left:1PX;top:1PX}.am-switch.am-switch-android input[type=checkbox]:checked+.checkbox{background:#108ee9}.am-switch.am-switch-android input[type=checkbox]:checked+.checkbox:before{-webkit-transform:scale(0);transform:scale(0)}.am-switch.am-switch-android input[type=checkbox]:checked+.checkbox:after{-webkit-transform:translateX(35px);transform:translateX(35px)} -------------------------------------------------------------------------------- /src/pages/play-detail.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { bindActionCreators } from 'redux'; 3 | import React, { Component } from 'react'; 4 | import _cloneDeep from 'lodash/cloneDeep'; 5 | import PropTypes from 'prop-types'; 6 | import { Switch } from 'antd-mobile'; 7 | import PlayOperate from '../components/play-operate'; 8 | import LrcScroll from '../components/lrc-scroll'; 9 | import TimeWrap from '../components/time-wrap'; 10 | import DetailList from '../components/detail-list'; 11 | import LrcColor from '../components/lrc-color'; 12 | import { 13 | setModeType, 14 | setLrcSwitch, 15 | setLrcConfig, 16 | setLock, 17 | playSong, 18 | togglePlayStatus, 19 | setLoop, 20 | } from '../store/actions'; 21 | import '../less/play-detail.less'; 22 | 23 | const lrcColorList = [ 24 | // 歌词颜色列表数组 25 | { 26 | defaultColor: '#b2f5b5', 27 | activeColor: '#d1c90e', 28 | currentImgSrc: '/images/current-type1.png', 29 | }, 30 | { 31 | defaultColor: '#c1f3dc', 32 | activeColor: '#33a3f5', 33 | currentImgSrc: '/images/current-type2.png', 34 | }, 35 | { 36 | defaultColor: '#a0f533', 37 | activeColor: '#f32d2d', 38 | currentImgSrc: '/images/current-type3.png', 39 | }, 40 | { 41 | defaultColor: '#eff366', 42 | activeColor: '#21d10e', 43 | currentImgSrc: '/images/current-type4.png', 44 | }, 45 | { 46 | defaultColor: '#efe8b2', 47 | activeColor: '#d200d2', 48 | currentImgSrc: '/images/current-type5.png', 49 | }, 50 | ]; 51 | @connect( 52 | state => ({ 53 | lrcConfig: state.lrcConfig, 54 | view: state.view, 55 | audio: state.audio, 56 | curPlaySong: state.curPlaySong, 57 | songList: state.songList, 58 | isPlayed: state.isPlayed, 59 | paused: state.paused, 60 | curPlayImgSrc: state.curPlayImgSrc, 61 | curPlayLrcArr: state.curPlayLrcArr, 62 | lock: state.lock, 63 | modeType: state.modeType, 64 | lrcSwitch: state.lrcSwitch, 65 | }), 66 | dispatch => ({ 67 | ...bindActionCreators( 68 | { 69 | togglePlayStatus, 70 | setModeType, 71 | setLrcSwitch, 72 | setLrcConfig, 73 | setLock, 74 | playSong, 75 | setLoop, 76 | }, 77 | dispatch 78 | ), 79 | }) 80 | ) 81 | class PlayDetail extends Component { 82 | static propTypes = { 83 | showDetail: PropTypes.bool, 84 | lrcConfig: PropTypes.object, 85 | view: PropTypes.string, 86 | audio: PropTypes.element, 87 | curPlaySong: PropTypes.object, 88 | songList: PropTypes.array, 89 | isPlayed: PropTypes.bool, 90 | paused: PropTypes.bool, 91 | curPlayImgSrc: PropTypes.string, 92 | curPlayLrcArr: PropTypes.array, 93 | lock: PropTypes.bool, 94 | modeType: PropTypes.string, 95 | lrcSwitch: PropTypes.bool, 96 | togglePlayStatus: PropTypes.func.isRequired, 97 | setModeType: PropTypes.func.isRequired, 98 | setLrcSwitch: PropTypes.func.isRequired, 99 | setLrcConfig: PropTypes.func.isRequired, 100 | setLock: PropTypes.func.isRequired, 101 | playSong: PropTypes.func.isRequired, 102 | setLoop: PropTypes.func.isRequired, 103 | setShowDetail: PropTypes.func.isRequired, 104 | setCurrentTime: PropTypes.func.isRequired, 105 | }; 106 | static defaultProps = { 107 | showDetail: false, 108 | }; 109 | constructor() { 110 | super(); 111 | this.progressTimer = null; // 控制进度条的定时器 112 | this.rollTimer = null; // 控制歌词滚动的定时器 113 | this.endTime = 0; // 歌曲结束时间(秒为单位) 114 | this.progressSpeed = 0; // 进度条前进的速度 115 | this.modeSwitch = false; // 防止连续点击 116 | this.mode = 1; // 初始化播放模式的数字 117 | this.state = { 118 | isShowList: false, // 是否显示歌曲列表 119 | curPlayTime: 0, // 当前播放时间(秒为单位) 120 | progress: 0, // 当前歌曲播放进度 121 | translateY: 0, // 歌词滚动的距离 122 | modeTip: '顺序播放', // 播放模式提示 123 | showModeTip: false, // 是否显示提示 124 | currentImgSrc: '', // 当前颜色的背景图 125 | isShowColorList: false, // 是否显示颜色列表 126 | }; 127 | } 128 | UNSAFE_componentWillMount() { 129 | this.init(); 130 | } 131 | UNSAFE_componentWillReceiveProps(nextProps) { 132 | if (this.props.isPlayed !== nextProps.isPlayed) { 133 | if (nextProps.isPlayed) { 134 | this.initPlay(nextProps); 135 | } 136 | } 137 | if (this.props.paused !== nextProps.paused) { 138 | if (nextProps.paused) { 139 | this.clearTimer(); 140 | } else { 141 | this.progressTimer = setTimeout(() => { 142 | this.progressGo(); 143 | }, 1000); 144 | this.rollTimer = setTimeout(() => { 145 | this.lrcRoll(nextProps); 146 | }, 100); 147 | } 148 | } 149 | } 150 | // 根据localStorage中的数据初始化播放信息 151 | init() { 152 | let mode = 1; 153 | let { currentColorObj, modeType, lrcSwitch } = window.localStorage; 154 | if (currentColorObj) { 155 | currentColorObj = JSON.parse(currentColorObj); 156 | } else { 157 | currentColorObj = _cloneDeep(lrcColorList[0]); 158 | } 159 | if (!modeType) { 160 | modeType = 'order'; 161 | } 162 | if (modeType === 'loop') { 163 | mode = 2; 164 | } else if (modeType === 'random') { 165 | mode = 3; 166 | } 167 | if (lrcSwitch === 'false') { 168 | lrcSwitch = false; 169 | } else { 170 | lrcSwitch = true; 171 | } 172 | this.props.setModeType(modeType); 173 | this.props.setLrcSwitch(lrcSwitch); 174 | this.props.setLrcConfig({ 175 | defaultColor: currentColorObj.defaultColor, 176 | activeColor: currentColorObj.activeColor, 177 | }); 178 | 179 | this.mode = mode; 180 | this.setState({ 181 | currentImgSrc: currentColorObj.currentImgSrc, 182 | }); 183 | } 184 | // 初始化播放信息 185 | initPlay(nextProps) { 186 | this.endTime = parseInt(nextProps.audio.duration); 187 | this.progressSpeed = Number((100 / this.endTime).toFixed(2)); 188 | this.props.setCurrentTime.bind(this, 0); 189 | this.setState({ 190 | progress: 0, 191 | curPlayTime: 0, 192 | }); 193 | if (this.progressTimer) { 194 | this.clearTimer(); 195 | } 196 | this.progressGo(); 197 | this.lrcRoll(nextProps); 198 | } 199 | // 时间进度条前进 200 | progressGo() { 201 | this.setState(prevState => ({ 202 | curPlayTime: prevState.curPlayTime + 1, 203 | progress: prevState.progress + this.progressSpeed, 204 | })); 205 | if (this.state.progress < 100) { 206 | this.progressTimer = setTimeout(() => { 207 | this.progressGo(); 208 | }, 1000); 209 | } else { 210 | this.setState({ 211 | progress: 100, 212 | curPlayTime: this.endTime, 213 | }); 214 | this.dealMode(); 215 | } 216 | } 217 | // 歌词滚动 218 | lrcRoll(nextProps) { 219 | const curPlayLrcArr = _cloneDeep(nextProps.curPlayLrcArr); 220 | const lrcLen = curPlayLrcArr.length; 221 | const currentTime = Number(nextProps.audio.currentTime.toFixed(2)); 222 | /* eslint-disable prefer-const */ 223 | for (let [index, curPlayLrc] of curPlayLrcArr.entries()) { 224 | if (Number(curPlayLrc.startTime) >= currentTime) { 225 | if (index === 0) { 226 | index = 1; 227 | } else if (index === lrcLen - 1) { 228 | index = lrcLen - 1; 229 | } 230 | this.translateLrc(index - 1); 231 | break; 232 | } else if (currentTime >= Number(curPlayLrcArr[lrcLen - 1].startTime)) { 233 | // 为了处理点击进度条时直接点击在最后面时的情况 234 | this.translateLrc(lrcLen - 1); 235 | } 236 | } 237 | if (currentTime < this.endTime) { 238 | this.rollTimer = setTimeout(() => { 239 | this.lrcRoll(nextProps); 240 | }, 100); 241 | } else { 242 | return; 243 | } 244 | } 245 | // 根据当前高亮歌词行的索引值来计算滚动的距离 246 | translateLrc(newCurLrcIndex) { 247 | this.props.setLrcConfig({ 248 | activeLrcIndex: newCurLrcIndex, 249 | }); 250 | if (!this.props.showDetail) return; 251 | 252 | // const prevTranslateY = this.state.translateY; 253 | const lrcBoxHeight = this.lrcBox.offsetHeight; 254 | const childHeight = this.lrcBox.firstChild.offsetHeight; 255 | const curShowNum = Math.floor(lrcBoxHeight / childHeight); 256 | // const nextTranslateY = childHeight * (newCurLrcIndex - curShowNum + 1); 257 | if (newCurLrcIndex >= curShowNum - 1) { 258 | this.setState({ 259 | translateY: childHeight * (newCurLrcIndex - curShowNum + 1), 260 | }); 261 | } else { 262 | this.setState({ 263 | translateY: 0, 264 | }); 265 | } 266 | } 267 | // 点击进度条更新时间 268 | updateProgress(evt) { 269 | /* 270 | * 这里不能写 const offsetX = evt.offsetX 271 | * 不知是什么原因获取不到 272 | */ 273 | const parentOffsetLeft = this.progressBar.parentNode.offsetLeft; 274 | const offsetX = evt.pageX - parentOffsetLeft; 275 | const targetWidth = this.progressBar.offsetWidth; 276 | const newProgress = Number(((offsetX / targetWidth) * 100).toFixed(2)); 277 | const newCurPlayTime = parseInt( 278 | ((this.endTime * newProgress) / 100).toFixed(2) 279 | ); 280 | this.setState({ 281 | progress: newProgress, 282 | curPlayTime: newCurPlayTime, 283 | }); 284 | this.props.setCurrentTime(newCurPlayTime); 285 | this.props.setLock(true); 286 | } 287 | // 切换播放模式 288 | switchMode() { 289 | // const { showModeTip } = this.state; 290 | if (this.modeSwitch) return; 291 | 292 | let modeType = 'order'; 293 | let modeTip = '顺序播放'; 294 | let loop = false; 295 | this.mode++; 296 | this.modeSwitch = true; 297 | this.setState({ 298 | showModeTip: true, 299 | }); 300 | if (this.mode % 3 === 1) { 301 | modeTip = '顺序播放'; 302 | modeType = 'order'; 303 | } else if (this.mode % 3 === 2) { 304 | modeTip = '循环播放'; 305 | modeType = 'loop'; 306 | loop = true; 307 | } else { 308 | modeTip = '随机播放'; 309 | modeType = 'random'; 310 | } 311 | this.setState({ 312 | modeTip, 313 | }); 314 | this.props.setLoop(loop); 315 | this.props.setModeType(modeType); 316 | window.localStorage.modeType = modeType; 317 | setTimeout(() => { 318 | this.modeSwitch = false; 319 | this.setState({ 320 | showModeTip: false, 321 | }); 322 | }, 3000); 323 | } 324 | // 歌曲结束时根据播放模式来处理 325 | dealMode() { 326 | this.props.setLock(false); 327 | if (this.props.modeType === 'random') { 328 | const randomIndex = Math.floor( 329 | Math.random() * this.props.songList.length 330 | ); 331 | this.props.playSong(randomIndex); 332 | } else if (this.props.modeType === 'loop') { 333 | this.initPlay(this.props); 334 | } 335 | } 336 | // 清除定时器 337 | clearTimer() { 338 | clearTimeout(this.progressTimer); 339 | clearTimeout(this.rollTimer); 340 | } 341 | // 点击列表时播放歌曲 342 | playSong(curPlayIndex) { 343 | this.props.playSong(curPlayIndex); 344 | this.setState({ 345 | isShowList: false, 346 | }); 347 | } 348 | // 点击改变歌词颜色 349 | changeLrcColor(index) { 350 | const currentItem = _cloneDeep(lrcColorList[index]); 351 | this.setState({ 352 | ...currentItem, 353 | isShowColorList: false, 354 | }); 355 | this.props.setLrcConfig({ 356 | defaultColor: currentItem.defaultColor, 357 | activeColor: currentItem.activeColor, 358 | }); 359 | 360 | // 存入localStorage中 361 | window.localStorage.currentColorObj = JSON.stringify(currentItem); 362 | } 363 | // 悬浮歌词开关 364 | toggleLrcSwitch(lrcSwitch) { 365 | lrcSwitch = !lrcSwitch; 366 | this.props.setLrcSwitch(lrcSwitch); 367 | window.localStorage.lrcSwitch = lrcSwitch; 368 | } 369 | // 是否显示歌曲列表 370 | toggleShowList() { 371 | this.setState(prevState => ({ 372 | isShowList: !prevState.isShowList, 373 | })); 374 | } 375 | // 是否显示歌词颜色列表 376 | toggleShowColorList() { 377 | this.setState(prevState => ({ 378 | isShowColorList: !prevState.isShowColorList, 379 | })); 380 | } 381 | render() { 382 | const { 383 | curPlaySong, 384 | curPlayImgSrc, 385 | curPlayLrcArr, 386 | modeType, 387 | showDetail, 388 | paused, 389 | lrcConfig, 390 | songList, 391 | lrcSwitch, 392 | } = this.props; 393 | const { 394 | modeTip, 395 | isShowList, 396 | translateY, 397 | curPlayTime, 398 | progress, 399 | showModeTip, 400 | currentImgSrc, 401 | isShowColorList, 402 | } = this.state; 403 | 404 | // 传递给 PlayOperate 组件的props 405 | const playOperateProps = { 406 | paused, 407 | showDetail, 408 | curPlaySong, 409 | togglePlayStatus: this.props.togglePlayStatus, 410 | playSong: this.props.playSong, 411 | }; 412 | // 传递给 LrcScrollP 组件的props 413 | const lrcScrollProps = { 414 | curPlayLrcArr, 415 | translateY, 416 | ...lrcConfig, 417 | lrcBoxRef: el => (this.lrcBox = el), 418 | }; 419 | // 传递给 TimeWrap 组件的props 420 | const TimeWrapProps = { 421 | curPlayTime, 422 | progress, 423 | endTime: this.endTime, 424 | updateProgress: this.updateProgress.bind(this), 425 | progressBarRef: el => (this.progressBar = el), 426 | }; 427 | // 传递给 DetailList 组件的props 428 | const DetailListProps = { 429 | playSong: this.playSong.bind(this), 430 | songList, 431 | curPlaySong, 432 | }; 433 | // 传递给 LrcColor 组件的props 434 | const LrcColorProps = { 435 | changeLrcColor: this.changeLrcColor.bind(this), 436 | lrcColorList, 437 | }; 438 | return ( 439 |
    444 |
    445 |
    446 |
    450 |
    {curPlaySong.FileName}
    451 |
    452 |
    453 | {/* 歌词滚动组件 */} 454 | 455 |
    456 |
    457 |
    458 | {/* 歌词开关组件 */} 459 | 464 |
    465 |
    466 |
    474 | {/* 歌词颜色列表组件 */} 475 | {isShowColorList ? : null} 476 |
    477 | {/* 时间进度条组件 */} 478 | 479 |
    480 |
    484 | {showModeTip ?
    {modeTip}
    : null} 485 |
    486 | {/* 歌曲前进后退功能组件 */} 487 | 488 |
    489 |
    493 | {/* 详情页歌曲列表组件 */} 494 | {isShowList ? : null} 495 |
    496 |
    497 |
    498 |
    499 | ); 500 | } 501 | } 502 | 503 | export default PlayDetail; 504 | -------------------------------------------------------------------------------- /dist/static/css/app.04b37dd3.css: -------------------------------------------------------------------------------- 1 | #header{position:fixed;width:100%;z-index:10;top:0;left:0;background-color:#fff}#header .header-search{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:1.25rem;padding:0 .2778rem;background-color:#2ca2f9}#header .header-search .logo{width:2.8333rem;height:.6528rem;background-image:url(/static/images/logo_@2x.1fddc3f.png);background-size:contain;background-repeat:no-repeat;background-position:50%}[data-dpr="3"] #header .header-search .logo{background-image:url(/static/images/logo_@3x.d488aa1.png)}#header .header-search .search-form{-webkit-box-flex:1;-ms-flex:1;flex:1;height:.7778rem;margin:.2361rem .2778rem;border:1px solid #fff;border-radius:4px;position:relative}#header .header-search .search-form input{vertical-align:top;height:100%;color:#fff}#header .header-search .search-form input::-webkit-input-placeholder{color:#fff}#header .header-search .search-form .search-list{position:absolute;left:0;top:.75rem;width:100%;border:1px solid #2ca2f9;border-radius:4px;background-color:#fff;z-index:10}#header .header-search .search-form .search-list li,#header .header-search .search-form .search-list p{padding:.2778rem;font-size:14px}[data-dpr="2"] #header .header-search .search-form .search-list li,[data-dpr="2"] #header .header-search .search-form .search-list p{font-size:28px}[data-dpr="3"] #header .header-search .search-form .search-list li,[data-dpr="3"] #header .header-search .search-form .search-list p{font-size:42px}[data-dpr="4"] #header .header-search .search-form .search-list li,[data-dpr="4"] #header .header-search .search-form .search-list p{font-size:56px}#header .header-search .search{width:.4722rem;height:.4722rem;background-size:contain;background-repeat:no-repeat;background-position:50%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAiCAMAAAANmfvwAAABelBMVEX///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8f4rH3AAAAfXRSTlMAAQIDBAYHCQoLDQ4RExQVFhkaHB0eISMkJSgpLTQ1Njc6Oz5BQkNHSk1RVFVWV1haW1xhZmpscnN0dnh8fX+AgYKDhIWGiIuNlpufpKWnqKqxsrS1ubq8xcjJy83P0NTV19ja29ze4ePk5ufq7e7v8PHy9PX29/n6+/z9/sWxzl8AAAGMSURBVBgZfcGHQxJxAIbh78jMwlFxpFLZtGELsaG2bJpQCi20pWlXmG0hBH3/9/hx3HXA6fPIFx3NfPi+WVzN3x1SqPhMCd9iKqJW1vg6TRYSarb3OXVfX83mFv5grF9QUNc8Nb+nhyzV7D77tAJUR/WflaNmbr98R94C5WH5xoGtCQV1PgPWetQQLwI31WxXDkirYQZ4YalF9DNU+lUXLUI5rjYjwLTqUkBG7ayPUFBdBjivELeAhIz3sLFHIU4Cl2X8gGWF6QUmZVRhXqGAhzKK8E5hOoEpGQX4pjADwDUZeWBQIa4Ap2RMARMKMQvlLhnHgKWI2hwowWu5FoExtUkDl+RKAT8PqcW5LVjqkCvyBlg+qCbHfwFJeQ4XgdWjCkj+pcax5RmpAuXHPWoYnMPl2PKkNqgpZa+eGEgM38hv4nFseU6vEc6x5elNVwhYGXNwObZ8/U8KuCovL3bIdnB96lZAIjn56N71M/tkxBxcd7StmENdVtuLORgPtIPYCvClTzvpvp2936d/uAmvZP9q06wAAAAASUVORK5CYII=)}[data-dpr="3"] #header .header-search .search{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAD2UlEQVRoQ9WajVFUMRDH91+BUIFQgViBWoFSgVCBWIFQgVKBUIFQgViBUoFQgVjBOv9z381dLpvsy8vd3O0MM8zw8vHLbvYrQDqKqu6JyItkynsATx2XcafClEVU9UhE3orIaxHh74Tx5E5E+HML4NeUdb2xTTCq+l5EzgygZV8PInIlIpc9tTYKRlU/GMRBC0FmDM3vC4CLHvOFYFSVm/9q5tRj3XQOaup4qvlVYVT1nYGU7kMvwDMAl62TFWFU9cRAWudvGXcF4LRloAszEuTWPBW91AMAmo2Yq6aXo5lSw/R8EWkCysKYaX2rrPqXl9cucDiOqCq94LmIPKvM/xEA5w/LCoxd9p+VmEFN0L5nGhgrpjFCfaqMfQOAsSkkOZjvFa81+sS8nZgp8/Q9LVHjh9FYtAQTuCenABjsuollETx9D4iBlVqsSgrzp2Be3TSS7kpVmQ7RIjyhdqomPYepaOUaAN302sQcw2dngdD6izC/zYWm89FrHUTtdgqtqvL0n2fmCN2dGYzZLT1YTi4A0JWuXSrmVr2vAww3m3OTG9PKcFKqysCb1kT8M0sHBl5XBhh6k1eZr0K22lNlhbvzBGA/AqPOR1XV9gQJmHzRq8EiPi9/TvY3cfHThVWVFz4Xd4oZAWFcHw+gWiL01oxpxzP7ojMqwTwC6FVRjmJW1e4wPwBQaxsXVWXKxD5DKs2aYV1yuHGS/3Gvu2ZkC+8M+wQ33gHzzrC2Z4K5Td7MCxVlb2beY1fiTDFU1DKAagrR+06pKos19udSqXrXWm4WylZ7Aqmql71XU6sBhvHEywI2mTWXWlvFy88DXaxnvGyV2nkZqfSmaqiglaqJpTClU7kBcDx1s6XxhbvCYSHrSHsAXqXHCcONhbHQlZI9XFOlMCx+Ss2/7iWBJbpc0+tlhxspub6Zl0oMB97UOs1py55Ial3LSTA8IZpbqX1KYC7S9AJmNRQ7McUyeOEAQhbh9ZrZ7C415uZasrsUgjIIZsMtDZIqUK9XAGqSCeCQBD4a7dA2YilBLfCQpkgRKPI+U+oFT9lY61gXqFoWW0+NJ55rzrVuaOq4LFAVxrJqOgVWf9HHorGbpVmOPawVoBDMsDOLCYQau7AHR4gTvsEUSuXSwSwBjYJJoPjM0KopPlYxXi1VjY1A8wS0CWYBiuZHL0Vvxcw71xXl5/cLb553paS1AWhepkyCqSSOe60NxAagmXbWBjPWA6TfjwSaZdVbC2Ne1OufpewzR7DVMEEglghHvIdbDxMAmmfVOwFjQExOGQ6GbJ4aOV/8x4edgUliHLutK//s8A+VssdDzc4fOwAAAABJRU5ErkJggg==)}#header .header-tab ul{display:-webkit-box;display:-ms-flexbox;display:flex;padding:0 .2778rem}#header .header-tab ul li{-webkit-box-flex:1;-ms-flex:1;flex:1;line-height:1.3889rem;text-align:center;font-size:16px}[data-dpr="2"] #header .header-tab ul li{font-size:32px}[data-dpr="3"] #header .header-tab ul li{font-size:48px}[data-dpr="4"] #header .header-tab ul li{font-size:64px}#header .header-tab ul li a{display:block}#header .header-tab ul li a.active{border-bottom:2px solid #33a3f5;color:#33a3f5}#header .header-search-result{position:relative;background-color:#dedede}#header .header-search-result .goback{top:50%;left:.1389rem;width:.5556rem;height:.5556rem;position:absolute;-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0);background-size:contain;background-repeat:no-repeat;background-position:50%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAABp0lEQVRYR8XY4VHDMAwF4PcmgBEYgQ2gG5QJgA1gAsoEwATABLABZQNGgQkep5571+slURJLjv/Gcb5IsS2HaNQknQJ4AnAJYAvgnuSv93h6HSKuF9wXgPOD8bYkV9746cAe3M5F0n2+28F7w6Hrks4AfBxFbn/LC8k7b/w0oCRLp6XVvr3j9k7yxsPtojym09Q+UbgUYCQuHBiNCwVm4MKADm7UbO37zqsniSTbGWwp6ZqttyTfpk6yw/5VQEm2VLz2AKpxVSlugZsNbIWbBWyJmwxsjZsEHMD9WY1H8qdmtlYtM5Ks6rBi87il4kZFUNK6rHPNcWOBVp5fdERvRdKupTZ3oZa0AfDQobBvzpDuuaLmDVygDS7JtqvrJZCjgCOQtq0tN4v3kRuIpKXZ0h2OHB3BpZCTgU66wyM5C9gSORvYClkFLMi+bTAk3dXAgkyrrEOAmcgwYBYyFJiBDAdGI1OAkcg0YEHaof4TwElHJbQh+eiVYqnAgrT/hFbYdiGvSNoL9LZ0oIP8JmlRXhY4gHT/tDaJ4EGpZul+LmecbwBr78jwD7Oo1Sng61YSAAAAAElFTkSuQmCC)}[data-dpr="3"] #header .header-search-result .goback{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAEoUlEQVRoQ9WbW8imUxTHf3+nHHIIKS6ISI5pkCLGIYyhKU0awwyZoYzBjLjEhckVZTQyUs6GCxSSQRnn4oILiguuKacccj78tbS/evtm5t372e/zvM/z7dvvv/dev2ftb++119qvGHizfRBwNXAS8C2wRdKTtWartuM0+tmeD2wGdps132ZJC2tsGCyw7XOBF7YBO8N5hqR3mkIPEtj2hcBLGZgbJN0354FtLwaeLQCZ+x62vQTYBOyYAX5V0oKCj7KVZDBL2vZy4DEgZ9OHwFmSfp6zwLbj2HmwAPZ94BxJv9bARp/c16wdt7if7dVAyebzOnCRpN+LB9+GsFdg2zcDdxcAxFm8SNLfBdqxkt6Abd8G3FEAEGfxYkn/FGizkl6Abd8F3JK1Dp4BlkhygbZIMnVg2xuA6wus2yRpWYGukWSqwLZjJ76mwMKHJa0s0DWWTA3YdpyxVxRYuF7STQW6KknnwLZ3SNHTpQUWrpN0e4GuWtIpsO0IEWPjubjAwrWS7i3QTSTpDNj2TsDzQNx8cm2VpAdyojb+3gmw7V3S9S7utONaHDcrJT3SBkzJGK0D2941ZSnOLIBdJumpEkPb0rQKnGAj5j01Y2CEiEslldx722L9f5zWgG3vAbxWAPtXiotfaZWkcLBWgG3vCYRnT87M+wewUNKWQvtal00MbHsf4A3ghIx1vwHnSXq3dYoGA04EbHs/4C3gmMycv6SL+wcNbOtEWg1s+wDgbeDIjGU/pZTMR50QNBy0CjhVAyInfFhmvu8S7CcN7epM3hg4hYvhreMzVn0NnCbpi86srxi4BvhK4NHMXF8B8yV9XmFTp11qgO8B1masisxib0fPONtqgCN//HgG+Mvk4UEt5+pIy3Yk1hZloL9J0J91ukYbDt7YwzF+ipnfA+Zl5vsBiBrQ3N2lZwBt758irGMz0D8C50vqPeioXtIj0Hun4CN3REWktaDvsHJi4LS84+IQsfSJGU/3fnFoBThB7w7Ede/0DHSvV8PWgBN0pHVejktCBjou/1E6ebHhBtuKvGqX3t7MKXEXIBdkrPsXWD7t9E6rHh7ZyCI1+zRwSQZ66gm8ToBHwOMt1eUF63BqKdpOgdP/9UPAigLoqSThOwdO0PcDqwqg75R0a4FuIkmrm9aYzWwdUAKzUdJ1ExFlOk8FOHl6DbC+AKazUulUlvQooO1rgY0F0J0Uw6cOnDx9FRCbWW51RdUxqhOtvO2Y+ci5SQuc0Vxi+zLgCSBqx+Naa693egVOno4EwnNAlFXHtahoRLXiz+afdusevXh4xgzb8V4yQtGdMzCREo7rZfULvN49PAJ9dqolz34EPvsbTPTGcjDAaXmfkopxUYEc1z5OKaPIolS1Xpf0qMW2Iz8WiYS9MiSfxr1b0vc1xIMBTp4+LqWMoiI5rkWCPxL9kfBv1AYFnKCPAt4EoliXW97zmp7TgwNO0EekMuyBGegVTR/EDBI4QR+cPH3oGOgNkm5ssqYHC5yg40daUXA/fDtQqyXF9bO4DRo4Qe8LROBx9CyqKOUcIimeUhS3wQMn6EgDxxvM+NVLfIB4J7Kmpvb8H09JU0ySfGtnAAAAAElFTkSuQmCC)}#header .header-search-result .searchCount{color:#5d5d5d;line-height:1.3889rem;padding-left:.8333rem;font-size:14px}[data-dpr="2"] #header .header-search-result .searchCount{font-size:28px}[data-dpr="3"] #header .header-search-result .searchCount{font-size:42px}[data-dpr="4"] #header .header-search-result .searchCount{font-size:56px}#header .header-search-result .searchCount em{color:#2ca2f9;margin:0 .1389rem}.play-operate>span{display:inline-block;width:.6944rem;height:.6944rem;background-size:contain;background-repeat:no-repeat;background-position:50%}.play-operate .prev{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAACMklEQVRoQ+2a4U3EMAyFnyeADYAJgAlgA9gANoANuJsANoANuBFgA9iAEdjA6EmuFFXtXeM6aYTqP/ygafzlOY7rnOCfmPwTDqwgrSk5qoiq3gI4Txz+FJGPWgCqegzgDgD9+AWwFZGvsfkHQQzifWDQ5b6XRUHa/K8ACJPamYj8DM0zBrIB8DQwgKvC/xUxVT0F8GwqDM0xOn8zIKr6AICL1FchBWJ4XzepiKnAMBp0sOd0myCqyvB9PKBCu4qo6gUAqsC/OdaOIqaCN2EsD6Kq3ANUgZnJa8uB2MHW7QUvQDduGRA72HguzFFhuc1uKjCMWF5EWj1F9pQXEUDlQTIPNi9UWZCJ5YXX+fJ7pJIKZUFmHmxedeJCC8DOWV54nS+iCL/QcuujCIDwAzHSKc+7wkLLM3nkmBUkcjUj3rUq0l/Fb6tkjyKW1/GOMEW2AN4AvAC4cTgyd0gcSNfXsi8+Qp3M9S5jfDwIJ7fvDnZBhpp5Gf5NfrQMSDe9dUUYbleTXfI9WBYkAbq3/VMqGdQBScKN6rCTHm31QBJ12P6JTgb1QRIgNuOYECLCbTkQCze2g6jO3GSwLEiiDttDBPKq0wZIkgwYbrwPybV2QHpnD9VJ7ykPgbUHkgAxEVChKeHWLkiSDKYUom2D9JIBgcYK0ezLUJYabEL3rfj19IFCNO962uQmDNMlb1l5t70TEfa0qph1Mbl32H7iDwY2+36wsP4WpYosGZOsimQsVpVH/wAwuYBCLQNhTgAAAABJRU5ErkJggg==)}[data-dpr="3"] .play-operate .prev{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAADY0lEQVR4Xu2c8ZFUIQzGkwrUCtQK1Ar0KlArUCvQDtQKPCtQO7gO7uxAK/Ds4KwgzjeTm2F2dpeE40Fg4d/NAu/3vhDCA5hWMRNgs+UypAXLIYIFa8FyEHCYLmVtAUtEnu+p9zcz3zja62oqIm+I6BERPSWiX0T0g5mvrZ3KKktE7hPRpTawWy9AvWPmC2uDPexE5BURfVFQaRfQ/zNmBrhsscACiJdHarph5gfZljoY6Iv+RkSAdahcM/NjS/cssP7seSO7dT+zvh1Lp2rYqMudExE8I1deW7zDAktyLamUrwx2m5uICMYkqOmFo7HPzPwpZz8VLBF5T0R4aIuaUjZfmfnDScASEUQ3DOAeNaVsfjJz9r/DK0tEPqqacsI49vvcsEQESoCaoKq7ljlh6XQAasqOMQ6C88FSNSHSIeLVLPPAUjXB5d7WJJTUNQcsTVWgJu90wMN1bFjGVMUDZM5oeIfJZSm88ZRVmKqUAhp3UqqTS0wHthybDoEdQ1maqmAArzG5LFVZfFiVUpVSQGO44YaTy1Jw8ZS1UapSCiiusgKqKR6sxpPLUpX1d8NGqUopoBjK6ji5LAXXR1kdUpVSQP2URUT4suv9qlLjQWvU0VRZ3zdca6oBI1dHU1i5zkT/fcFyvKEFa8FyEHCYLmUtWA4CDtOlrAXLQcBh2lRZf4nooaNz0UybwjrTNXTsjboXjYShP21hMfOVrl0h9Tm2B9XQ9+Ym7WHdPqKuigLaKK7ZDxagqcrwHRDbg6KXvrASlWF7EFS279BBFIgxYCXQsF0IW60jBoBYsBLXBDAcC4lU4sHaCQCA9iQIsbiwEmiYlyEI9HbN+LDUNSMEgDFgJSrDYSS4Zo+52ViwkgAA18SxkpZlPFiJyrBXC3OzVgFgXFgJNAz+LZLz8WElAQBj2ZbJ+RywGiXnc8HaODmfD9ZOAIBr1krO54W1QXI+P6yKyflpwKoUAE7njHQ61ReRkuS82ul73KqRWxUIda+DbtP0rM5Wu9chd2PIP2bucd4mmzvqBmBAO/ayzf23nL5HnoYLLg41iLto0KGQRT+cHEvOTarCw2VhJREHSyjp2WS454XnlqCeNBUavgPc3kuDPbDnnmthTLB6PmSkthcsx9tYsBYsBwGH6VLWguUg4DD9DwbtRGrw1y5IAAAAAElFTkSuQmCC)}.play-operate .play{margin:0 .3333rem;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAABg0lEQVRoQ+1awVHDQAzc7YB0kA6gA0gFoQOgAugA6CBUQFIBoQLogJQSKhCjmfOMuZzvZOKZ8Fg9Y1k66bTei71EwczsDMA9gGsAF8nlE8Ca5KZ0z7G/DeTcAngh6bmrxvxqCvjRKyB38WLuWoHHXDezOYC3Y3KWCvEirhoLeSb5NGaxNV8z+6oU0d1azfmrEDPzUfLORGxGch9xbBRxC+A1EGdPcjbklxfiXX4MBHWXRWR2W7HMbA3gpuWXrg/mzAtxUF0Gg04yXmY2JqcKqW2OdqTfHY1WNisCOwBhRBipkLAwIoxkj03xiHjk8NQlHhGPiEfqfxWFEWFEGBFGgi+UAOispbOWzlp1vAgjwogwIoyUOqDvI/o+Ej1tiUfEI+KR0/DIKQQDLpxZBh8OYR75zxKOb5KuWipaSVQTUSJM8k6rW5GZ7QCcN3YlLqrxQEnm5MUMBd6QdCHMZJZkTj5if855sCO9Lj0A8AV3wd8BbEm6CGZySw30nD7e/ZyriHjnB76s+EJHsFaDAAAAAElFTkSuQmCC)}[data-dpr="3"] .play-operate .play{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAACpElEQVR4Xu2c4VEUQRSEuyPQDJQMyECJQI1AiQCJQIhAjUCIQEKACCQDIAOJ4Flt7VYhunvTc3d7ZdGvav/1m3n7Te/c/WqioarqJYD3AF4Pj7puAVwDuCB53rDMYpKq0qxvAewD0Oyqy+E5J6nZ7eKqjqr6BOAjgOczWm3+jqTg7ayqSnC+PwA0NcsJyVN30FlYVfUNwAdj0UOSZ4Z+Y9KqkusFau5QH+53RvLQGWASVlWdAJCrnPoJ4GBphw3XxA8D1PhOX0nqq2mqf8IaNr9pWuFv0SXJg87erraqkqN0R/XUXusdNgXrC4Cjnp2HnuYB1tjjd+uaB6slmt01BUuW1mXZW8ckBXzrVVX6jD6vsdEtyb2W/ilY1dI8ozklqTtv69V5t/4xF8mV/wrUsC1YVyT167T1CiwDcWAFlkHAkMZZgWUQMKRxVmAZBAxpnBVYBgFDGmcFlkHAkMZZgWUQMKRxVmAZBAxpnBVYBgFDGmcFlkHAkMZZgWUQMKRxVmAZBAxpnBVYBgFDGmcFlkHAkMZZgWUQMKRxVmAZBAxpnBVYBgFDGmcFlkHAkMZZgWUQMKRxVmAZBAxpnBVYBgFDGmcFlkHAkMZZgWUQMKRxVmAZBAxpnBVYBgFDGmcFlkHAkMZZgWUQMKRxVmAZBAxpnBVYBgFDGmc9MVhPKotG+XivjAN+LP2fUo7uSI65gLOvnPysDeRniXRvMttiCUejDarqAsCbzi+hORht05l/94rm3FHmn5Isn5nAmlPZtO6qNEklQyrzs7UUv6lTXryGNEnt3QpMeaVOUuY8LL3xEBOnlLW5Ie4UULi0ox6fyJBTKmAvVpxW1691U3xbVSn7U6cwpsoKnACNCbg7ySadAlJVmnVM6x3BXQ0JuMon7UrA/QXU6b1q1MMpBQAAAABJRU5ErkJggg==)}.play-operate .pause{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAABt0lEQVRoQ92a4U3DMBSE7zaATdgARoENYIN2AtoNGAEmADaADWCDdoKgk2yphZrEif38XP9sq+p9uthfci1xYg3DcAXgEcANgDcADyQ/Tn3Wy2tMgLwGiMO3VwC2JHdehj+cIwUyJIb9AnBHUim5WrkgcfgNgLWndOaCCEiXmNJ59hDNEpA4v0AE1HTvlACJ6axIblulUwokzq9DQOnoUDBdpUFiOhuSa0uSGiBxfglU6ZiItCZIBDIRqQWIgKqL1AqkukitQaqJtAVIFZG2BCkq0tYgxUTqBWSxSD2BLBKpR5BZIvUMkiVS7yAxnadQgCSfeXoBGRVpTyAxHVVT6gyOVo8gO5KX5wCyJ3lxDiDdX1p7ALep+qmXPaJ2Ri1Nt8fvd0hhtKL1nIhaGLUxk4o/jyCfIYWs9sUTiDazElDrkr28gLyHFGY3lK1BlIJOoz+3HLmRtAR5CSlM2sxjYC1A/hXb2MCp961BRsXmHWSy2DyDZInNI8gssXkCWSQ2LyCLxdYapJjYWoIUFVsLkCpiswapJjYrkOpiKw2iR8vrX19qIrbSIPrjmW6tBaMj9d7q9/K5ID8EpShCtl5SmQAAAABJRU5ErkJggg==)}[data-dpr="3"] .play-operate .pause{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAACq0lEQVR4Xu3cPYsUMRzH8e+v9UX4EuxsLK60ExvBSlALO0EQPB8QrUSw9PQdqJVg6UOlIChoZaGlj53g4VOhYCRwgwi7k8xuMpN/MgtXTeZ28tnh9guTiwi8nHO7gIvAWeAr8AS4LelO6Nzajis0IefcPeDggnEPgeOSPoV+Ry3He7GccxvA457Jfgc2Jd2sBaRvHiGsTeBqBMRz4KikNxFjzQ4JYV0GLkXO7hdwxf9I+h15jqlhKbG6ib8Gjkh6aUoi4mJzYPm3/QPcAM5J+hFxHSaG5MLqJv9x5xvzkQmNwEXmxure/hZwUtIXy2hjYXmjz8ApSR7O5GtMrA7IbMxOgeXRfMyeB7YkOSu32VRYnc+LncwwEbNTY3k0MzFbApaZmC0Jq/iYLQ2r6JgtFavImC0dq6iYtYBVTMxawpo8Zq1hTRqzVrEmiVnLWKPHbA1Yo8VsLVijxGxtWFljtlasLDFbM1bymG0BK1nMtoKVJGZbw1orZlvEWjlmW8YaHLOtY3V32XvgsKRnfY/lZqx/OtvAbknfloHNWP/LHJJ0d8aKe+x9RtK1GSsOa76z4px4JWnP/Ac+rPUW2JD0YcZaLjBoOWfL34aDFwq3iLXyqp3WsNZaD9YKVpKVhi1gJVvDWjNW8tXRtWJlWXdfG1bW/+ioBWtQXIaDfvGIGrAGx2WLWCvHZWtYa8VlK1hJ4rIFrGRxWTNW8risFStLXNaGlTUua8EaJS5rwBotLi1jjR6XVrEmiUtrWJPGpSWsyePSAlYxcVk6VlFxWSpWkXFZGlbRcVkSVvFxWQKWmbjMhRW7559fuHqs9T3/9gMPej6Jn37/UknXV/20LJ3X+3THT8Q59xTYt2BS94EToQVgljBC1xqD5XfAPQ0cAPYC74ALljcNC6EsO/4X+BvVW4SZA/wAAAAASUVORK5CYII=)}.play-operate .next{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAACFUlEQVRoQ+2Z/VHDMAzFnzZghG4AbAAbsAEwATAB7QZlAo4NygTQCSgTQDeACcw9zubcSxx/NHHskvztxPr5KZIsCQ7kkQPhwARSmpL/RxGl1BGAG63AWkRec6ih9z229tqKyKdrb68iSqk3ACfWBwhyJyKboYA0xAcAHqJ5vgCcumA6QZRSVwAeHQYvASxEhBv0+iilzgC8tHz03OURPpA5gPsOKyk11Vn1STIGiLGfIARy+nAM6JggtJMuthSRRYzRbWvHBjE2MQhQneToVgqIAUoOBqWBGHe7FZGnGHcrEcTYTze7Dg0GJYMYoHlIMKgBhEAM0VTHGQxqAbFzD4EalUFtICYY0N0e7GBQI4gdDP4K0ZpBdnKPrrazFo0x6SF0LYMB805bsTpY9RtqXF/rJhDffaSvkw79zqTIpEiorzjWMWpd5ryz72lv4/U1ADY8ZrmbD32BfANgqcLLGGrN7L8q2HeV2kCoAgEaraSaQJ41RGtzrwaQrQbo7LCUDsI7B39ob4u1VJAgFUq/WLHJzeog6ilJkXf9LySNHkoAYUhl/zdahZJcq5HYovzJWjyWIjvlRarxYyvSmdhSoYZQxDV6c5YXqcYPqoiuRBl57Okq7wrssHsTWyqUHoYy+9v78vBmrn1DprqcrF7oCesqtKOeCmHe0zD2NHnTdXhekH0NyvX+BJLrpEP3mRQJPalc634AI0KHQitfCLEAAAAASUVORK5CYII=)}[data-dpr="3"] .play-operate .next{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAADL0lEQVR4Xu2c4X3UMAzFpQ1gAhiBDWAD2ACYAJiAdoN2AsoE0AkoExQ2oBNQJhC/dxfTa8+J/Rznwtny11Ni539PkZTIUfGRTUCzLd1QHBYhAoflsAgChKkry2ERBAhTV9bSsMzsnYi8Gua5FZFzVb0i5j24qZm9FpGnDya+VNUfuYuhlWVmn0TkTWQCTPrhf4RmZtci8mwEyltVvcgBRsEysxci8i1xYkwMaFDc6sPM4AFfJhZyq6qPcxbKwgIIyDk1AOq9qn5OGS79u5mdiMjHqXlUNYtDllGYyMxwX3pOXCDsIfNfxDFVTdeE9VtEHhVcDf5dBIGDu+aasKwAVDgE6oLKDho1jxVWgPZ1gHYQlR07LEADqBNVPZ+h1KxDW4AVLhS5GVwzOzHMIrRj1BKscFlnInK6RABoERagIQAgmcU9rdpoFdZuAAC0KrlZ67BCADhT1dO5EusB1m4AmFWc9wRrdgDoEVZwTbo47xVWUBlVnPcOK0BDBZAMAA7rLkQmi3OHtZ9PjBbnDiuefEWLc4c1nakiACA32xTnDisvrd8U53gXsNYz+DlPSvMusa4VAgAUFt5xRs++1AuLY4OVhd5hZWHaGjksh0UQIExdWQ6LIECYurIcFkGAMHVlbWF9H5JSNN+Njt5h/UHDHV6reW047WKXA6hNL4XDisO6GSDd69JxWPuw0GCCx8x7nTkO6w5WVE27LB3WlgYaSdBRODl6h/VzuDdltSn1CgvpAPofkmrq3Q2RXCJvojtrelIW1IQoh+fpRaMXWPeSyyJSHSSl/0qVUkC93LOwfQWdMtXavlt0w2RyWaqy1mCNliqlgFp0QySXcLlFt6e0oKysUqV3ZSG5hJqySpVeYc1OLkvBremGKDeekAsvLlXIeaLma8JidrJCTXC5rM3aNcDEzrEmrNw90lVKlRoA14SV2n1ftVSpBCu1+/5GVR9+7yE6NbWhHGcwszF1LZpczgFnZmjQfRk5B/Xn0rAGYPgIBlSGzeVIAy5KnjXNAcAeO3yTAh/CCBviEayumHUXwWIX2oq9wyL+SYflsAgChKkry2ERBAhTV5bDIggQpn8BHWgXaqeHtMEAAAAASUVORK5CYII=)}.play-detail{-webkit-box-flex:1;-ms-flex:1;flex:1;text-align:center;padding:.2778rem 0}.play-detail>span{width:.9167rem;height:.9167rem;vertical-align:middle}.play-detail .prev{background-image:url(data:image/png;base64,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)}[data-dpr="3"] .play-detail .prev{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGMAAABjCAYAAACPO76VAAAKu0lEQVR4Xu2di5EUNxBApQhsIjBEYIjAEIFxBIYIDBEYIjBEYBOBIQKbCAwRGCKwiaBdb6tna26uW7+VVrN3VtUWV+zsfPTUX7U0MVxQE5H7IYSvQghfhxD422sfQgj/hhC+xBj5+yJa3ONdigid/Z12+MMQwl39tN7upxACnz9DCB9ijO9aTzTyd7uBoaP+xxACnZ8a9b36AzB83u1FeqbCEBFGPACenDjyTwWE1LxSMPw9pU2BISLfhxCeqRRMefDERZGWlzFG/j1rOysMEUEKXpwoBZ9V/687io5Dva3bYuxbO5Rz/hZjfNN6gtrfnQWGiDwOIfzSAOH9YnTxjlpGq4gsDgBw+Pvbyk5CbT1tuXbldcJQGGoTfq1QR4z6twCIMfJv96aeGlD4YKtwlUsa9/M8xjjMpgyDISI/q0rKPegXBYBKOLueVqlFclGhuUbs8irG+DJ3YMv33WGoNPxe4J4iBb/pw/GQU5tKDE4Fn5y0MGhQXV2lpCsMHWWoJYI2rwHhRYwRELtrFVAYQADppk67wRARDDSjKtVwGfGmdt8UCvf6U+ZmUVvPezzQyTD0ppEG9K7X8Iqe9BbrHh2QO4dmBggISc94DXv3NHeu3PcnwVAQfyTsA8b52V5VUq5z1t+LCFKP9HuNhOSjGGOz/WuGUQDio0rDxWRNc3BUSrAR3zjHngSkCUYBCKJWJKJ5lOQ6Ztb3+uw4H6R0rNYMpBpGAYjXMcacIZ/Vl92uKyIA8WIT0vQPai/WAgMbsc0DLdfF1duly1rbMSXHZ4BUG/UqGCKC10QKwWq3CsTSASJCf9AvVqvSEsUwMhe9lSBWQFIq64fSwLAIhnoRqCcrsq6iXyL+l3hMQmXhxDwoibGyMDIG+02M0VNbl9inJ92ziOD2Wl5WkUEvgUH0aaUEiCMe3kT3tZWIDlySh1aiMZsKSsJQ9fSXcXNE1oC4MQFdK4Dt73QyC5VutXspdZWD4bmxt9pg58CJCAlG5nO2jUmzR97vXRgJ74nSllRSMHevt+J7EUFrWFO8rndlwlDd97fhPaGe7pd4BpfQ46qGMbjda6cSKv5TjPGe1T8eDE/MskboEiBwj8a0MPPbOCvdmoh4zo+p5j0YSAUFZuv2Oca4/b9uN36uE6nUkwq/5pLHGLPeZc19JrwrUzquXTxhK4YYbZ0zx9gB+m2M8XXNA9ccWzA/z3xE16KIhDG/1p8WjLNKhYhsrzdEFWayCAvTETDIWlixx7VA8AqMhI88SirI/m59ctfA1UjB+thMXm19aHcYap88G0ya5BirbWFYCS/WOKSqPVr7CCNqwQg9dXdhocQwyVAY9N8/RkddyettYfCDbccPSwSOhKHGk/otb+7FGkRDJEOBWHmrK1rgCCMhyldEqVkMjB+OgqH2gTkGb50H8ZKVPxoJg0CZwbFtxyBwDcMi9zHGOGzhyggYBYV0JDjpGByHbRsGQ6WDdPp2EBwz32sYlorqHghtDGtXmyEiZJdTgduxUEJEZAIMKwg8qqoDjEToPkxF6XW7wchMCXO5Ky7zJBieqjpkcxcYVoHWMC9qGZE91FRBtYpZSDcJhudVHUKHBYZlL4ZnZ0+FURDIUWT92Jp3mQFDtYGVzT3YjQWGFXUPtRenqin1/sgxeTFQciZyIgzLbryPMT5cYJzdmJ0CoyCQy87NT4RhlvYQ6MaE8U5OEfaIN2rVVGHFe1HqZiIM02kJIdwDhmnhe6YkPHA1MAoyrhhq7ENR1nUiDM+IPwKGlcQ66LAeoz91jlIYehzRa8o+sP6juEBiFoxE8Pd89zAKMq7sAwKIqor3yTCQ3u3im5e7hlEQyDUnMfcKw6Z0hrV3ifmTOzrPkUr0nbQi6n8YGwOSgIHq8RajdCmg2yGMd6ipPUqGZ/exC7jcVfbBOtlkGNYk3vu9wiB69vb4qF6EskMYlgDsFgZLsLhhb6eC5nVzqyTllKyDurauN+XmSmbGGRrkkcD0JARVxWRQcWyxmUvZJYzdBn0FK0vp36L0x3ZgTbYZlxdnrFSKV+qyHFK9ZcRkGNas6iHoc7OIM9WUMZLJoeGFeHaE0cbkfpGnNRmGrSJTgVfpg7VCK81NrSSEIDC1QwGVewDJ2pFZMBL9/QDJ8LOIhRnQc8FQT4T7BYi3sQqSwcRYcj36RBj+fEYyi9i5RN5QPc0FCYly++Uy7Gnl7pg2EYblvR5KopaZPsu6X8IcOKOMh/PsCBKEt3XNjkyEwRrJbc7t0NcLDMtbYbdMEnbDWq3NcCJpHiwXIALkih2ZASNhEg71BgsMbyrwIuqm9CEBkgoQMezHWcBJMLy6qUM/rysKLXdraIVID8nYRNWpbSM4lLT7YTHOJBiWvTjWp61hWHajaGV/qx7rDUM7ObWxCocAjD0FrRL90bW2VknU0TavYXjbvg2rEhkBQ4GgdjHeqUSjNXE1DIZX+LFO56xhsKbOqsxuntrMScwoGArksEawcrvtkTA8FXpn8fa2i2WyCzpyHVzz/UgYqwARPV2ymzM/GQIj4UVdKbbbwvD0bVNmNAfGgdG94Lpg583lVkfB8BKdV3ZL2MLwVmZ2X/S4yjeRT1rvhpktzcxBduKRXKJxiGSkdpvYrpW0lh57FEdJB4aUay5Gd9iun1rKiu724pHukpGQymtLrC0YniEf6ua2jPaW32QmrI7GtOXcRu4NTUP6Y7uzBBUud7dpGm+7Cs/yDw0Ce3RA6TmMRGN3rzGxO4K58YAHw5OObqUypZ028jhVW9gSbGLXLWB1Dt8KFUyp4DlT+015O8IMMbAjO33GuUXE2zjN3Y4jBcPzrHi24u1BZ3TE7GsmjDbL2tivy5waTm7pk6gAL94edHbHnPv6icVHWdc5u7+SU/7JiW+Ed9UTlnpqlvfEZbKTdSUwMOZMzFhJty6llj07ZOa5EksYirYTzMLQHE8qLT0kGJzZqS3XzqwlKbKxRTAUSGri5lYDyeS+iuOXGhh4V6mpzVsJJLPMrWojnGIYKh0p+8EhtwpIZuOY6u3Jq2AokFw1xq0AkrERTaurqmEUArmxXlZqK1Y1/E0gkumQnEehujJVQIY7jBfR9ZWbufsa+X3hDm/NG/Y3ScbywHpzqQKy7q/cHNnZqXMXbBzTLBHLdU+CUaiyOOxQHjO6qn0EqMLXaLtbKdXc08kwVl5WrhIDKaEYediOzjUPXnKs7pdOCVNqK9lqr8m7dhcYCoQbLqnEwIYAhf0Cd9lEhGoSpoJze78XB3QlD9oNxsqO5CrDl0N3BUW9JDYCKIGAfWC/km6vnz7Jm8oYO2KR3JuC11B4KCatsiuOSkZYzTHqhLDrJzN+JTtbD3uDc3fJWHeE5mwYabk3zy8/AwbGni2WhoEREVY9UY2CFOdU0XJvw9/gPBRGpS3ZDmgMPm4zH8Dw/o7qmEW9Ieqy6HwktuUVRTgd2LmixZs1krk+djiMlS1hBCIlpaWW3jOtd1oD0rqDUDNLQfP679b+wckAQvUgaLng2WAMgNLyvKW/OSuE5abODmMFhZGLzubjVfiVdl6P44gXcDp4u81QdTQ8zjilN1SvE1yh188JBgB4ciQ2z6KKUv00TTK8m1J/HygYWv5dF0Wfwpzf0vmLU8ALDKdIwK4lI9fDGgvgAGCcSw3zYtzpfFbuDnOVc/df+v1/rz0QgRQHXbIAAAAASUVORK5CYII=)}.play-detail .play{margin:0 .5556rem;width:1.25rem;height:1.25rem;background-image:url(/static/images/play-play_@2x.3c465a4.png)}[data-dpr="3"] .play-detail .play{background-image:url(/static/images/play-play_@3x.4dadb1b.png)}.play-detail .pause{background-image:url(/static/images/play-pause_@2x.1f2a1bc.png)}[data-dpr="3"] .play-detail .pause{background-image:url(/static/images/play-pause_@3x.db72d15.png)}.play-detail .next{background-image:url(data:image/png;base64,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)}[data-dpr="3"] .play-detail .next{background-image:url(data:image/png;base64,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)}#playDetail,.playDetail-mark{position:fixed;top:0;left:0;bottom:0;width:100%}#playDetail{z-index:99;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;background-size:cover;background-color:#fff;background-repeat:no-repeat;background-position:50%;-webkit-transform-origin:0 100%;transform-origin:0 100%;-webkit-transform:translateX(100%) rotate(90deg);transform:translateX(100%) rotate(90deg);-webkit-transition:all .5s;transition:all .5s}#playDetail.slideIn{-webkit-transform:translateX(0) rotate(0deg);transform:translateX(0) rotate(0deg)}#playDetail .playDetail-top{position:relative;padding:.4167rem 0;font-size:15px}[data-dpr="2"] #playDetail .playDetail-top{font-size:30px}[data-dpr="3"] #playDetail .playDetail-top{font-size:45px}[data-dpr="4"] #playDetail .playDetail-top{font-size:60px}#playDetail .playDetail-top .goback{top:50%;left:.2778rem;width:.5556rem;height:.5556rem;position:absolute;-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0);background-size:contain;background-repeat:no-repeat;background-position:50%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAABp0lEQVRYR8XY4VHDMAwF4PcmgBEYgQ2gG5QJgA1gAsoEwATABLABZQNGgQkep5571+slURJLjv/Gcb5IsS2HaNQknQJ4AnAJYAvgnuSv93h6HSKuF9wXgPOD8bYkV9746cAe3M5F0n2+28F7w6Hrks4AfBxFbn/LC8k7b/w0oCRLp6XVvr3j9k7yxsPtojym09Q+UbgUYCQuHBiNCwVm4MKADm7UbO37zqsniSTbGWwp6ZqttyTfpk6yw/5VQEm2VLz2AKpxVSlugZsNbIWbBWyJmwxsjZsEHMD9WY1H8qdmtlYtM5Ks6rBi87il4kZFUNK6rHPNcWOBVp5fdERvRdKupTZ3oZa0AfDQobBvzpDuuaLmDVygDS7JtqvrJZCjgCOQtq0tN4v3kRuIpKXZ0h2OHB3BpZCTgU66wyM5C9gSORvYClkFLMi+bTAk3dXAgkyrrEOAmcgwYBYyFJiBDAdGI1OAkcg0YEHaof4TwElHJbQh+eiVYqnAgrT/hFbYdiGvSNoL9LZ0oIP8JmlRXhY4gHT/tDaJ4EGpZul+LmecbwBr78jwD7Oo1Sng61YSAAAAAElFTkSuQmCC)}[data-dpr="3"] #playDetail .playDetail-top .goback{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAEoUlEQVRoQ9WbW8imUxTHf3+nHHIIKS6ISI5pkCLGIYyhKU0awwyZoYzBjLjEhckVZTQyUs6GCxSSQRnn4oILiguuKacccj78tbS/evtm5t372e/zvM/z7dvvv/dev2ftb++119qvGHizfRBwNXAS8C2wRdKTtWartuM0+tmeD2wGdps132ZJC2tsGCyw7XOBF7YBO8N5hqR3mkIPEtj2hcBLGZgbJN0354FtLwaeLQCZ+x62vQTYBOyYAX5V0oKCj7KVZDBL2vZy4DEgZ9OHwFmSfp6zwLbj2HmwAPZ94BxJv9bARp/c16wdt7if7dVAyebzOnCRpN+LB9+GsFdg2zcDdxcAxFm8SNLfBdqxkt6Abd8G3FEAEGfxYkn/FGizkl6Abd8F3JK1Dp4BlkhygbZIMnVg2xuA6wus2yRpWYGukWSqwLZjJ76mwMKHJa0s0DWWTA3YdpyxVxRYuF7STQW6KknnwLZ3SNHTpQUWrpN0e4GuWtIpsO0IEWPjubjAwrWS7i3QTSTpDNj2TsDzQNx8cm2VpAdyojb+3gmw7V3S9S7utONaHDcrJT3SBkzJGK0D2941ZSnOLIBdJumpEkPb0rQKnGAj5j01Y2CEiEslldx722L9f5zWgG3vAbxWAPtXiotfaZWkcLBWgG3vCYRnT87M+wewUNKWQvtal00MbHsf4A3ghIx1vwHnSXq3dYoGA04EbHs/4C3gmMycv6SL+wcNbOtEWg1s+wDgbeDIjGU/pZTMR50QNBy0CjhVAyInfFhmvu8S7CcN7epM3hg4hYvhreMzVn0NnCbpi86srxi4BvhK4NHMXF8B8yV9XmFTp11qgO8B1masisxib0fPONtqgCN//HgG+Mvk4UEt5+pIy3Yk1hZloL9J0J91ukYbDt7YwzF+ipnfA+Zl5vsBiBrQ3N2lZwBt758irGMz0D8C50vqPeioXtIj0Hun4CN3REWktaDvsHJi4LS84+IQsfSJGU/3fnFoBThB7w7Ede/0DHSvV8PWgBN0pHVejktCBjou/1E6ebHhBtuKvGqX3t7MKXEXIBdkrPsXWD7t9E6rHh7ZyCI1+zRwSQZ66gm8ToBHwOMt1eUF63BqKdpOgdP/9UPAigLoqSThOwdO0PcDqwqg75R0a4FuIkmrm9aYzWwdUAKzUdJ1ExFlOk8FOHl6DbC+AKazUulUlvQooO1rgY0F0J0Uw6cOnDx9FRCbWW51RdUxqhOtvO2Y+ci5SQuc0Vxi+zLgCSBqx+Naa693egVOno4EwnNAlFXHtahoRLXiz+afdusevXh4xgzb8V4yQtGdMzCREo7rZfULvN49PAJ9dqolz34EPvsbTPTGcjDAaXmfkopxUYEc1z5OKaPIolS1Xpf0qMW2Iz8WiYS9MiSfxr1b0vc1xIMBTp4+LqWMoiI5rkWCPxL9kfBv1AYFnKCPAt4EoliXW97zmp7TgwNO0EekMuyBGegVTR/EDBI4QR+cPH3oGOgNkm5ssqYHC5yg40daUXA/fDtQqyXF9bO4DRo4Qe8LROBx9CyqKOUcIimeUhS3wQMn6EgDxxvM+NVLfIB4J7Kmpvb8H09JU0ySfGtnAAAAAElFTkSuQmCC)}#playDetail .playDetail-top .playDetail-title{color:#fff;text-align:center;padding:0 .9722rem}#playDetail .playDetail-center{color:#fff;overflow:hidden;position:relative;margin:2rem 0;-webkit-box-flex:1;-ms-flex:1;flex:1}#playDetail .playDetail-center .lrc-box{height:100%;-webkit-transition:all .5s;transition:all .5s}#playDetail .playDetail-center .lrc-box p{padding:.1389rem 0;text-align:center;font-size:14px}[data-dpr="2"] #playDetail .playDetail-center .lrc-box p{font-size:28px}[data-dpr="3"] #playDetail .playDetail-center .lrc-box p{font-size:42px}[data-dpr="4"] #playDetail .playDetail-center .lrc-box p{font-size:56px}#playDetail .playDetail-center .lrc-box p.current{color:#d1c90e}#playDetail .playDetail-bottom{position:relative}#playDetail .playDetail-bottom .lrc-switch{position:absolute;left:.8333rem;top:-.8333rem}#playDetail .playDetail-bottom .lrcColor-box{position:absolute;right:.8333rem;top:-.8333rem}#playDetail .playDetail-bottom .lrcColor-box .color-list li,#playDetail .playDetail-bottom .lrcColor-box .cur-lrcColor{width:.8333rem;height:.8333rem;background-size:contain;background-repeat:no-repeat;background-position:50%}#playDetail .playDetail-bottom .lrcColor-box .color-list{position:fixed;bottom:4.1667rem;right:.5556rem;padding:.2778rem;background-color:#000}#playDetail .playDetail-bottom .lrcColor-box .color-list li{margin-bottom:.2778rem}#playDetail .playDetail-bottom .lrcColor-box .color-list li:last-of-type{margin-bottom:0}#playDetail .playDetail-bottom .time-wrap{color:#dcdcdc;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0 .4167rem;margin:.2778rem 0}#playDetail .playDetail-bottom .time-wrap .progress-wrap{-webkit-box-flex:1;-ms-flex:1;flex:1;height:.0833rem;position:relative;margin:0 .2778rem;background-color:#6c6b70}#playDetail .playDetail-bottom .time-wrap .progress-wrap .progress-bar{width:100%;height:100%;position:relative;z-index:1}#playDetail .playDetail-bottom .time-wrap .progress-wrap .progress{left:0;top:0;height:100%;position:absolute;background-color:#3195fd}#playDetail .playDetail-bottom .time-wrap .progress-wrap .progress-dot{top:50%;position:absolute;width:.3333rem;height:.3333rem;border-radius:100%;background-color:#3195fd;-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0)}#playDetail .playDetail-bottom .play-operateBox{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:.2778rem}#playDetail .playDetail-bottom .play-operateBox .listen-mode{width:.8333rem;height:.75rem;margin-left:.8333rem;background-size:contain;background-repeat:no-repeat;background-position:50%}#playDetail .playDetail-bottom .play-operateBox .listen-mode .mode-tip{left:50%;bottom:18%;font-size:11px;color:#d1c90e;position:fixed;-webkit-transform:translate3d(-50%,0,0);transform:translate3d(-50%,0,0)}[data-dpr="2"] #playDetail .playDetail-bottom .play-operateBox .listen-mode .mode-tip{font-size:22px}[data-dpr="3"] #playDetail .playDetail-bottom .play-operateBox .listen-mode .mode-tip{font-size:33px}[data-dpr="4"] #playDetail .playDetail-bottom .play-operateBox .listen-mode .mode-tip{font-size:44px}#playDetail .playDetail-bottom .play-operateBox .order-play{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA2CAMAAACsuQomAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABgFBMVEUAAADRyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ4AAACJrzboAAAAfnRSTlMABYbILqU0MjGXLwOsLQSvLASzKgW2KDpAuScftf2RCrzdGOENF+UOFegPFPvsEBP48BHL8xIN9xMH+xRyFUOXcQpMpXwPehkYF8IWEOv8FBHu+BMS8vQSE/bxERT67RANyumX/rAbB8QiBsEkBr4lBbonBbconyoKKwotnC7FX4rxAAAAAWJLR0QAiAUdSAAAAAd0SU1FB+EFDhUoKEv2/Q8AAAErSURBVEjH7dVDd4ZBDIbh1LZt27Zt27Y1v719v6Sn6zzZ9t5lcW2SOWeIVPn5u4BAHfkryP0UDGLnK8SCXSiEw0SHIzgiUnQUoqNjRMciOi5edAKiE5NEJ3tTSqqyNNHplOHwKNOCsyw424JzcmGbB9wqX2xBod4WiS0u0dtSsWXleltRybaqWm9ratnW1QPLamDb2ATYZrYtrYClNp9t70AsUaeHuzBL1N3T24fa/7T1DwwOoXbYu/MIiPl5jlqwG4PwuOgJBE9OMZ6eQfTsHOv5BUQvLrFeXkH06hrr9Q1Eb26x3t6h3T34x9inA8tfdWjBRxZ8bMH6Tk7Fnunt+YXYS729uhZ7o7e3d2Lv9fbhUewTsKxnsS+AfRX7Blh6Z/uBWKJPz379Tt+7+bNhIR7K3wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wNS0xNFQyMTo0MDo0MCswODowML9iI04AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDUtMTRUMjE6NDA6NDArMDg6MDDOP5vyAAAAAElFTkSuQmCC)}[data-dpr="3"] #playDetail .playDetail-bottom .play-operateBox .order-play{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAABQCAMAAACu7ZJWAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACClBMVEUAAADRyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ4AAABb7fF1AAAArHRSTlMAHp3obAIu0Aru0QrRCtEK0gpf0gpy0wpy0wtx1Atx1Atx1Qtw1Qtw1gsBX8fa1gsFzM1s5fSJERakIUMhQyFEIUQgRCBFIEUgPCACHxofHB8EH04fRfe1GVHJIFclJiYm+CY0JzsnOyc7JzonOig6KDkoHbkoFpH24WUDu8RQscLFCH3FCH3EB37EB37DB3/DB3/CBwCAwgdpwQcHwQcewAcXwAcA4r8GJ74GUnDCewAAAAFiS0dEAIgFHUgAAAAHdElNRQfhBBIUIQsePpyUAAAB40lEQVRYw+3X2TuVURTH8VUUGcpUISWZMkZKGYpCZShkCFEaNKCozCmFRKlUFCWhQuw/srdzHGu543l+6+587/Z78Xn2xbv2fjYRpC1bjYvrNoy1vu3mf27uCvQOG208PPG0Wc3LW402O3ep0cbHF0z7se0fALZ3s71nL9gOZDsoGGzvYztkP9g+wHboQbAdxvahcLAdwXZkFNiOZvtwjLWOjYtPgMV24hFKSjY6HaUUJdkYOqZHH9ejU/XoE3o0nUzTkdOhI5Mh5EyofErIp6FylpCzofIZIZ+FyjlCzoXKeUI+B5XPC/kCVM4XcgFULhRyEVSmiyxfwspUvCaXgGUqdciX0TJRmV0ux8tEFZXGXKnSkJ05c7a5qq0xr7mqIdfaz5A6Bdpx8l3To811PdrUo+kbbN8E07fEvXsbbDcI+w7Yvivse2D7vrAbwXaTsJvB9gNhPwTbLcJuBduPhP3YWj9p29TDasO1U4cObEWdenSXHt2tR/fo0U97leRn1t/3vO8FLJZf9kNHZoDlwVdQeYjl18NQeYTlN2+h8ijL795D5Q8sj32Eyp9Y/jyOhCe+sPx1ErrnKZa/fYfK0yz/mIHK9HNNnp3DyjTvkH/9Bsv0Z8EuLy6hZaK/Nnl5Zd3HfyzmCj8NCY3/AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA0LTE4VDIwOjMzOjExKzA4OjAwPq8CCwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNC0xOFQyMDozMzoxMSswODowME/yurcAAAAASUVORK5CYII=)}#playDetail .playDetail-bottom .play-operateBox .loop-play{background-image:url(data:image/png;base64,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)}[data-dpr="3"] #playDetail .playDetail-bottom .play-operateBox .loop-play{background-image:url(data:image/png;base64,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)}#playDetail .playDetail-bottom .play-operateBox .random-play{background-image:url(data:image/png;base64,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)}[data-dpr="3"] #playDetail .playDetail-bottom .play-operateBox .random-play{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAABVCAYAAADJ/vPXAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAIvUlEQVR42u2de6xdRRXGf+cilYcOklBooUhF21IEJlhxTKAGlNRStBRQoS20BatiSas0QISoKBr1gkpoLUIrtCgvwyMYeRmhVcCYUSSMiVJqX2ILiMpjNEFuW+ofe25zetn77LX32a9rzpfc5J49a9as+e4+81przW3RQyKcVUcB/cA04PfApdr4X+XR1aq7M02Fs+oE4LGYogu08Tdk1ddXd4cajNUJz693Vn0+q7Ie0cnYs0PZdc6qBVmU9YjOj2XOqgulwj2ik3GPQOYHzqqFEmU9opNxPvAPgdwSZ9WiNKEe0QnQxr8KjAf+LhC/1ln1xU4CPaI7QBv/CjABeF4gfo2z6qKkwh7RKQhv9kRgq0D8+86qxXEFPaIFCGQfCfxNIP49Z9XFQx/2iBZCG++Bo4BnBeJXO6suaX/QIzoD2sjeLBC/yll16eCHXWcdzqojgOOBEcBOYAD4D/Ay8KI23tXd0bxwVr0DeB9wIKCAPQpQezFwuEDuMm38d1rOKoAlgGjhHfAwcC+wRhv/5wo5E8FZdSwwC1hMM761X205q84C7uhS0e3AMm38b+rqibOqD5gDrKzLhk5oOavWAeMK1LkQWKGNf72qTjirpgH3V9VeHrScVTso5+u1iOgtf6Ms451V+wI3AZ8qq42i0AesLUn3EmCHs+r4MpQ7qw4DPMOAZIiIvr7kNh53VvU7q4qY6QFwVk0gWmI1YaIToRUMXwHML7mttcBkbfw/u1HirBoLbKqEneJwqMhnGMbCyeFnHnBwF40ero3PRZSzaj/glRxV1wFLgce18U91YfugHVlWamO08VtzOWfDUuo04BvAe3OoGKeNX5+xzRbwCHBShmpXEk3IL+bpZ4IdZxMtZyUYo43fCgV4wZ1V44BvA2dmrLrLCGE7C4kmWAluBxZq4//Vbf+G2DATuE0ofog2/rnBD4WFGzirjgEeAA4RVhkADtDG/1ugezzwjFDvJ7TxdxfVrzYbZgO3CMUP1sbvdoZd2Kytjf8jcCjwJWGVEcBdYUjo1MEW8s3IpJJIPocuSIaCl0fa+J3a+H7k4+gU4AspMmcB75E0r41/ssj+ADirzgV+IhQfHUcylBip5KyaCIgOnLTxrQQd+xKdIKbhpLyhWil9mAusEoqP0sYn+hdLW/Br458mcm6m4acdyiQniheVRPIMCiIZKoi9c1Zp4KkOIrFjmrNqf+ClFPV/AI7Txu8s2OY+YIdQ/CDJ8rH0LWxwGHw8ofiSpDGN9LEb4IyiSQ7YTyDzBkKSoaKzAm38fcC3YoqWxsmHHeAVKWoXa+Ml/rs8eC2lfDvRcCHeCFV5KPM1dg9GmdbhzFpy7nJdWYZq4/8LLEsoHiBaXUiimHahMqK18duAE8PHJ4EH4+ScVXsB301Rd3oFjoXFRO66dqwj2vFlPhirPBDdWfUV4E5t/NqE8lnArR1UbAf20sZLJ6tu7R1J5Pneoo3/S149jYr4F87207XxP6/b1qxo2sG5xBvzQN1G5kHTiE4bm+dUNWQUjcYQ7ax6N/CBFLG76rYzLxpDNFHkTydcoY1PW982Fo2YDIXb7dHa+BfqtjUvmvJGz00pv3c4kwwNeKOdVSOAtM3HsUU4VetEE97o6SnlL9H59G9YoNY3OmxQttH5D16KD7Bq1P1GnyywodHBi1LURnRwuqYFoXw5nKQNe9T5Rk8B9k+RWV6jfYWiFqJDwON9KWKrsp75Nhl1vdFnA29JkflmTbaVgjrOoxXwaorYg9r4aTF1zwD21sbfyjBD2ltVBq4UyLzp3CPEeNwdfr9TGz9QhbHOqklEmbPPauMfzaunUqKdVe8n3bv9UEKmV3uC5CzkMRd5bX070VU/uu0ZwGF5nMKVjdHOqn2ILoBKw6KYuu9kd6/4SmfV20o2+QbaSG7DX0NaRyZUORkuFcisSPDLxV0WdVnJ9s7sULY5ZB6IUQnRzqp5RBeNpOHymLonAFPjZEN8X13YlIXs0ol2Vp2ILMly/lA3vrPqIOKvRBvEamdVHRP6IDY5q94lESyVaGfVccAageh64v8YM1LqjQK+XpL5HxbKbZSQXRrR4Sv/O6H41ITET8k34XJn1elF26+NX0NyzOBQbHRWdUzAL4XokOvxmFB8jjZ+Q0JnB4BTBDrucVZNLrofIWbwY0LxDcHBHItCiXZW7eOsWo48oWYl6dH0DwGSjcKjzqqPFNkfAG38/cCpQvH1SWQXmSw0BfhFhirPAMdIdnjOqjHIrtkBWKCN/2FR/Wqz4RTkwTtvSu8rIv3tQ8CNyPJMBrEDGKmNfzlDO2kxee34LTA7b+JoBxumkhCcGYPx7XuCvAmdY4FPAlfltDk1FSGmTYiInpWh2kqgXxsvTZ2T2PFRouFMgl1kpxId/HqTgaOJNg7S8SoOLwBH580HDyG9G4HROar3A78Efq2N395FH7IOkxO08eta4VTsEcB007gAPwNmdhtt5Kw6ENntik3CEX1EAddlk7xAGz+jiJCukM4wpgp2CsTaPuC8Eht4mmicKnQVEHLIRwIbutVVFfqAsSXpnq6NP7KbKPlOCOP8ROCakuwvFC1n1RPApIL0bQYu1MZXGizurPog0URX9hl1brRybDSG4nWig507il63ZoWz6lSiEIVuLm4pBYNX/SwCrhXW+RNRqMBq4OEybwHLi3CaNgO4AFmadNlY0H5l5h5EOSR7El2ZuQ14TRv/RN1WFoHwP1VGEQ0vRZzx3ALsLZD7nDZ+ee1hu8MNzqoDgC3AWwXin9XGr4AGxEcPJ4Scwy1El7qk4TPa+B8NfugRLUQg+TlkIRrztfE3tj/oES1A2PY/j2xs/7Q2/qahD3tEpyDj2cr52vhY91th11j+PyJ44aUkn6eNX5VU2HujExDGZOl9HPO08Td3EqgzJqLpkPo952rjf5wm1CM6GScLZOZo40VXtfWIzo9ztfHSSwdrz8pqMjo5js/JQjL0iO6EpKFjdp6Mg/8BnyK8vsRAFR4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMDQtMThUMjA6MzM6MTErMDg6MDA+rwILAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTA0LTE4VDIwOjMzOjExKzA4OjAwT/K6twAAAABJRU5ErkJggg==)}#playDetail .playDetail-bottom .play-operateBox .detail-list{margin-right:.8333rem}#playDetail .playDetail-bottom .play-operateBox .detail-list .icon-list{width:.8333rem;height:.8333rem;background-size:contain;background-repeat:no-repeat;background-position:50%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAQAAACQ9RH5AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAHdElNRQfhBQ4VJRzf7Hf3AAABiElEQVRYw+2YsUtCQRzHv78QpP6JIFQiNKilxmp0bbLNoEkIacy1lhpcoiEwmppqqxahoaUWKYdXRFO2SEtjQ8O3wZfe3aMwO0+S+yzyuzvuc7/zeO/3DvB4PEMEwRwDkgFzdKiN84Ydrhnvr0/a4jKKWk9ZNpRljWIdcxZ8F6iIvp2MoPXWaYsDM+PI3yqiLsviJgsAjNib8Hd0Ky5bM66FeX/FzONQG5CXIzXkLCYsaOvyFGljSTkCm9Yy/AZRA45jGSk84lQa/RZ7PJ6hgtNshs+tJjPutBnjvZl2JW4Y4metd9FSGfDBydaM7guBdxkbjHhgFchr66cjfjAG3GvRvCXtS6RWZdI4Bklne8AE70LpLRPOtB6P59+if0mksYIpBDiWwNkKCO4qD8ydft+CdF6LBexpPQXZ1xa2gJQFX01qZsY/X0VUrF1FFM2Mh74QCOlWfGXNGF5qxNoN2yhpA7a0aAmrmLGgvZQTo4UxnitH4IyxnibuDWZZ5RurzDqUejyev/IJ1BzRVK0tQ/YAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMDUtMTRUMjE6Mzc6MjgrMDg6MDCBBWpUAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTA1LTE0VDIxOjM3OjI4KzA4OjAw8FjS6AAAAABJRU5ErkJggg==)}[data-dpr="3"] #playDetail .playDetail-bottom .play-operateBox .detail-list .icon-list{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAABaCAQAAACSoYmJAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAHdElNRQfhBQsWExf0unFhAAACzklEQVRo3u2aT0hUQRzHv6OrsGVSLXapaJMQIvPgxQ7BFkHXhMpLpxA6ZlBZBtJFCsIKOhRBdJeC6FRph7IF6xRkZUESBVmIEf1B2SS+XXbZ2TfzJltz5hG/z2l2fr/fe5+3++b9mR1AEARBEAQhKGzhIGdIznCQLaFtFiKc4kVWcoG1oa3cyoq3aXKLKrRZJRU67MY1a1a3um49yN3YiZQHy2e4qeas0qxHIbasXs1HhOsxig4PwiWa1dtSs0br3uwoMWNHvSoDw+WmLu26UpixAa/KwCa7tOsqYcZqEAx915OOPDN23rPpbLmpD8S0HoiwTB+9AMBGTGG5R+l29bTU1C5Yao59OGstOBlVBtQ3ZnAQnajzIPwYl9WHmBjr+MJyc3lOH2LVwwbejSjfoc+ToGrxHO8XhUeYC20jCIIgCIIQGuuMBtdiG9ZgGmNqKrTggmAbJ7UH0zdsC230Z+VOy0vAntBWbuVW2tkS2ixeGdaXLZIct+Z30R/naJ+y4HpH0Toje4dHZZI8U9637r/V8TO0Gj2XPJ8IfXbp1Y6SjNHjOsQlRpeeduSZsbFkSI878sxYj2fT3ph+PooZBA+t2bs8DsMjjDsYNseUbPT8rf4dzFmUkz9hwyyHNeF73BDayMT+lAdsRwafkU/Y31qCIAiCIAhJgOB+vi8+473jPi5+k0uunOFE5Gn6JTOL3+5SKqf51fIS8IXp0GaVVC6bOo1GS85K9OOU5RBX4RgOeFmF8AADaqL8UV/v0YDvsWUr1I+IcpNzyuHfk1OjpWb1M0y+1zBpMwK6tOudO2v0HPIsraFLFxx5P42eWQRDl37lyDNjJzybPik39YGYwnxsSUr9quxgLYaw15tyAVn1ySINsAtD1pIudcPWzXZ0wMea39dqJDZG8Krl5nIl4bdygr0R5eMJVy6KN7GHeX5knofZFNpGEARBEIT/it8P/kBZCgxG5QAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wNS0xMVQyMjoxOToyMyswODowMAI0BgQAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDUtMTFUMjI6MTk6MjMrMDg6MDBzab64AAAAAElFTkSuQmCC)}#playDetail .playDetail-bottom .play-operateBox .detail-list .active-list{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABF1BMVEUAAADRyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ7RyQ4AAAB4qZgOAAAAW3RSTlMAAFnV1VkAB8jHB4+OCXeuZAHRmo5kYWAyVCV7eh9ymHEeLujoLS0s4uFB+yj26dbVN+IgJSUk0M8kLIawhSsAi4oAa2pAZjOdcMGOhIMDZZZUBLW0BEq777tKGEdHvQAAAAFiS0dEAIgFHUgAAAAHdElNRQfhBQ4VKChL9v0PAAABBUlEQVRIx2NgGAUjBzAyMUezsLKRpZedIxoEOLnI0cwdDQE8EC4vHz9BICAI0xwNAxCuUDQRQBiHZmL0wtRSV7MIMXpFYZrFoALiUL6EJEEghQhuabBeGTJTiaycvIIimXpHwcgASsrABKaiSpZeNWjaVidHswZUsyaEq0VEptLWgWkmJz/rUqKZopJED6ZZHypgAOEaEqHXyBim2QQqYkpWXJmZA7VaWJKZSkbByABW1ja2duRptXcAp05HJ3I0O0PTtguU7+pGELjDNaNlSQ9isqQnDs20LwxwafYiRq83TLMPVMAXwvXzDyAIAuEBFhQM1hsSSl5Mh4VHREaRp3UUDAUAAKVBEynjG7JHAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA1LTE0VDIxOjQwOjQwKzA4OjAwv2IjTgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNS0xNFQyMTo0MDo0MCswODowMM4/m/IAAAAASUVORK5CYII=)}[data-dpr="3"] #playDetail .playDetail-bottom .play-operateBox .detail-list .active-list{background-image:url(data:image/png;base64,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)}#playDetail .playDetail-bottom .play-operateBox .detail-list .play-list{color:#fff;position:fixed;right:.8333rem;bottom:1.9444rem;width:6.6667rem;border-radius:4px;max-height:10rem;overflow-y:scroll;background-color:rgba(0,0,0,.9)}#playDetail .playDetail-bottom .play-operateBox .detail-list .play-list li{padding:.1944rem .2778rem;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}#playDetail .playDetail-bottom .play-operateBox .detail-list .play-list li.active{color:#2ca2f9}.playDetail-mark{background-color:rgba(0,0,0,.6)}#player.fade{-webkit-animation:fade .5s;animation:fade .5s}#player .footer-play{background-color:rgba(0,0,0,.8);position:fixed;bottom:0;left:0;width:100%;padding:.2778rem;-ms-flex-align:center}#player .footer-play,#player .footer-play .footer-left{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;align-items:center}#player .footer-play .footer-left{-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-align:center}#player .footer-play .footer-left .footer-singer{width:1.4444rem;height:1.4444rem;border-radius:100%;overflow:hidden}#player .footer-play .footer-left .footer-singer img{display:block;width:100%;height:100%}#player .footer-play .footer-left .rotate{-webkit-animation-name:rotate;animation-name:rotate;-webkit-animation-duration:6s;animation-duration:6s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}#player .footer-play .footer-left .paused{-webkit-animation-play-state:paused;animation-play-state:paused}#player .footer-play .footer-left .footer-playerInfo{-webkit-box-flex:1;-ms-flex:1;flex:1;color:#fff;margin:0 .2778rem;font-size:15px}[data-dpr="2"] #player .footer-play .footer-left .footer-playerInfo{font-size:30px}[data-dpr="3"] #player .footer-play .footer-left .footer-playerInfo{font-size:45px}[data-dpr="4"] #player .footer-play .footer-left .footer-playerInfo{font-size:60px}#player .footer-play .footer-left .footer-playerInfo p{width:4.4444rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#player .footer-play .footer-left .footer-playerInfo .singer-name{color:#888;margin-top:.1389rem;font-size:13px}[data-dpr="2"] #player .footer-play .footer-left .footer-playerInfo .singer-name{font-size:26px}[data-dpr="3"] #player .footer-play .footer-left .footer-playerInfo .singer-name{font-size:39px}[data-dpr="4"] #player .footer-play .footer-left .footer-playerInfo .singer-name{font-size:52px}@-webkit-keyframes fade{0%{opacity:0}to{opacity:1}}@keyframes fade{0%{opacity:0}to{opacity:1}}@-webkit-keyframes rotate{form{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rotate{form{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}#suspend-lyric{opacity:0;visibility:hidden;-webkit-transition:opacity .3s;transition:opacity .3s;-ms-touch-action:none;touch-action:none;position:fixed;top:0;left:10%;width:80%;z-index:99;color:#fff;border-radius:4px;padding:.2778rem;background-color:rgba(0,0,0,.5);font-size:15px}#suspend-lyric.fadeIn{opacity:1;visibility:visible}[data-dpr="2"] #suspend-lyric{font-size:30px}[data-dpr="3"] #suspend-lyric{font-size:45px}[data-dpr="4"] #suspend-lyric{font-size:60px}#suspend-lyric .close{width:.4167rem;height:.4167rem;position:absolute;right:.2778rem;top:.2778rem;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAABdUlEQVRIS8WW4VHDMAyF35sAOglsAJ2gsEEZgUlgAxihnaCMAJu0E5hTzwbHVWxJvV7zM7noy5Osl0dc6eKVuPgDp5TWAN4A3ALYAHghuT/nw1JKUusDwBMAqfVK8lNq1mB5cFOBvgEso/AM3QG4r2ruSS5acFLUheAz0GN5kkextWJp7+pceA8KYEtS2j4Byzy+ANxF4QPoD4DHMrrJqc4vhuAe6ERxURmBe6EqWG564BHoLNgKz11qV6Y0bzLT9tx0ncugXOrVe2qCdhUbZ64ZW1dpecHk1QPlNdwENSl2KDdDI+C5gyS1XPbqaXUPWhpjhg/Bgz3VDpcJblmn3p4KOOTts2CLI2W5IW9XwRZo+csYTEYNEydgD9S4aurMtd9iyHu9yusEIkEgBI0or8FySB6U/XA50kD5huRzG320sOeCWpRrYa+NtyHoAH4gKSM9CfTvOVtvAayjmbqBS4CX9HqQYE9SRvoP1rzvkveGXn0p+C94nPofIpc9sgAAAABJRU5ErkJggg==);background-size:contain;background-repeat:no-repeat;background-position:50%}[data-dpr="3"] #suspend-lyric .close{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAACpklEQVRoQ+3bi00cMRAG4H8qSKggoYLQAVwnKSGpBDpIKYEKoAOODpIKHE3kRWbP3rU9D1u6s4SQ7mB3vhuv7fXOEc6s0Zl5cQGnGQ8hfAXwBcAbER1n7g21sWYzHEK4AfALAP9e2iOAn0T0MhM8xnoP4K4m1hNwPMBzAfUHwGEWdIz1N4DPmXizsebArwC4K5faFOgd7HuvJKJDCvkA3slu+n9D0ZXYJd7rdPxZg/k64C5S04agG7Hs4EuQx5//bQ3mrsxdura5ojuw7LgiIo7zFMyvhBD407itFQNwQXdin4goHb1PFx7xwIz+NAu6E/uXp9X1+mFrHp4CLcDe5abP4tJyhkxrY08GrXUXHom2wO6C4yDGy0vX7m2FrQJ7oy2x1WAvtDW2CWyN9sA2g63QXtgusDbaE9sN1kJ7Y0VgKTrO+aWb99KqlpeL2RVU7TJYvIknWJxwjLmdCjOsOMNLZJ3o2qTw34kzu5xMnGEHtBpWLcOGaFWsOlgwkOW6tzrWBKyENsGagYVoM6wHuHWe5ZhM98jURun0IuxcQaWHMEOrgxWwC9wErQpWxJqh1cAGWBO0CtgQq44WgzuxPPVwc9/sF4EF2OXxh+tuqGgelmCXJwKdd1mi0bsrwxpY4Q1HN7oZrIkdgW4CW2C90dVgS6wnugrsgfVC74I9sR7oTfAIrDV674F46/2s6s27xTy9VfIwFGuV6VLp4RRYC3QOzNgPpT65LcXkNdVuXDpXZ/d+3Cs9bC1Mc8EKM10uTAshtJQeumIFaJXSwyHYTnS5uDTuJ3MB+LeN63YothEtLj2cAluJzsa6NQ8/rIpMnwB8n+27D3H0zsX6o6n0MHZvHrX55yUtwd2Zpoa8Hb/kwbEet5Kye/MwJHrDk17Ahh/uFIf+B4H/IFtSazAGAAAAAElFTkSuQmCC)}#suspend-lyric>p{height:1rem;line-height:1rem}#suspend-lyric>p:last-child{text-align:right}#content .list ul{padding:0 .2778rem}#content .list ul li{display:-webkit-box;display:-ms-flexbox;display:flex;font-size:14px;padding:.5556rem .2778rem;border-bottom:1px solid #ddd;-webkit-box-align:center;-ms-flex-align:center;align-items:center}[data-dpr="2"] #content .list ul li{font-size:28px}[data-dpr="3"] #content .list ul li{font-size:42px}[data-dpr="4"] #content .list ul li{font-size:56px}#content .list ul li.active{color:#2ca2f9}#content .list ul li .filename{-webkit-box-flex:1;-ms-flex:1;flex:1}#content .noSongData{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;height:100%}#content .noSongData p{font-size:16px}[data-dpr="2"] #content .noSongData p{font-size:32px}[data-dpr="3"] #content .noSongData p{font-size:48px}[data-dpr="4"] #content .noSongData p{font-size:64px}html{outline:none;overflow-x:hidden;-webkit-tap-highlight-color:transparent;-webkit-overflow-scrolling:touch;-webkit-text-size-adjust:none;-webkit-touch-callout:none;-webkit-user-select:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{font-family:Avenir,Helvetica,Arial,sans-serif;color:#333;font-size:12px}[data-dpr="2"] body{font-size:24px}[data-dpr="3"] body{font-size:36px}[data-dpr="4"] body{font-size:48px}body,html{position:relative;height:100%}a,b,body,button,dd,div,dl,dt,em,form,h1,h2,h3,h4,h5,h6,html,i,img,input,label,li,ol,p,select,span,strong,table,tbody,td,textarea,tfoot,th,thead,tr,ul{padding:0;margin:0;font-style:normal;font-weight:400;border:none}a,body,button,dd,div,dl,dt,h1,h2,h3,h4,h5,h6,html,input,li,ol,p,select,textarea,ul{-webkit-box-sizing:border-box;box-sizing:border-box}button,input,select,textarea{border:none;outline:none;line-height:normal;background:none;-webkit-appearance:none}textarea{resize:none;padding:0 .1111rem}input[type=checkbox]{-webkit-appearance:checkbox}input[type=radio]{-webkit-appearance:radio}input[type=email],input[type=number],input[type=password],input[type=tel],input[type=text]{padding:0 .125rem;font-size:14px}[data-dpr="2"] input[type=email],[data-dpr="2"] input[type=number],[data-dpr="2"] input[type=password],[data-dpr="2"] input[type=tel],[data-dpr="2"] input[type=text]{font-size:28px}[data-dpr="3"] input[type=email],[data-dpr="3"] input[type=number],[data-dpr="3"] input[type=password],[data-dpr="3"] input[type=tel],[data-dpr="3"] input[type=text]{font-size:42px}[data-dpr="4"] input[type=email],[data-dpr="4"] input[type=number],[data-dpr="4"] input[type=password],[data-dpr="4"] input[type=tel],[data-dpr="4"] input[type=text]{font-size:56px}li,ol,ul{list-style:none}a,a:active,a:focus,a:link,a:visited{color:#343434;text-decoration:none}#root{padding-top:2.6944rem} --------------------------------------------------------------------------------