垃圾vite 换成webpack
30
package.json
@ -2,17 +2,15 @@
|
|||||||
"name": "blog-admin-web",
|
"name": "blog-admin-web",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vue-cli-service serve",
|
||||||
"prebuild": "vue-tsc --noEmit --skipLibCheck",
|
"build": "vue-cli-service build"
|
||||||
"build": "vite build",
|
|
||||||
"serve": "vite preview"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.0.10",
|
"@element-plus/icons-vue": "^2.0.10",
|
||||||
"aplayer": "^1.10.1",
|
|
||||||
"axios": "^0.22.0",
|
"axios": "^0.22.0",
|
||||||
"echarts": "^5.2.1",
|
"echarts": "^5.2.1",
|
||||||
"element-plus": "^2.2.28",
|
"element-plus": "^2.2.28",
|
||||||
|
"hls.js": "^1.3.0",
|
||||||
"hyperdown": "^2.4.29",
|
"hyperdown": "^2.4.29",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"pretty-bytes": "^5.6.0",
|
"pretty-bytes": "^5.6.0",
|
||||||
@ -24,11 +22,19 @@
|
|||||||
"vuex": "^4.0.2"
|
"vuex": "^4.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^16.10.3",
|
"@vue/cli-plugin-babel": "~5.0.0",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vue/cli-plugin-router": "~5.0.0",
|
||||||
"less": "^4.1.1",
|
"@vue/cli-plugin-typescript": "~5.0.0",
|
||||||
"typescript": "^4.4.3",
|
"@vue/cli-plugin-vuex": "~5.0.0",
|
||||||
"vite": "^4.0.4",
|
"@vue/cli-service": "~5.0.0",
|
||||||
"vue-tsc": "^1.0.24"
|
"less": "^4.0.0",
|
||||||
}
|
"less-loader": "^8.0.0",
|
||||||
|
"typescript": "~4.5.5"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not dead",
|
||||||
|
"not ie 11"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,5 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -10,10 +10,10 @@ const service = axios.create({
|
|||||||
timeout: 10000
|
timeout: 10000
|
||||||
})
|
})
|
||||||
|
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
import { ElMessage, ElLoading } from 'element-plus'
|
import { ElMessage, ElLoading } from 'element-plus'
|
||||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
|
||||||
// 添加请求拦截器
|
// 添加请求拦截器
|
||||||
service.interceptors.request.use(config => {
|
service.interceptors.request.use(config => {
|
||||||
// 在发送请求之前添加token到请求头
|
// 在发送请求之前添加token到请求头
|
||||||
|
|||||||
@ -27,10 +27,10 @@ export interface MusicLyricModel {
|
|||||||
|
|
||||||
export interface MusicPlayerItem {
|
export interface MusicPlayerItem {
|
||||||
id?: number
|
id?: number
|
||||||
name: string
|
title: string
|
||||||
artist: string | undefined
|
artist: string | undefined
|
||||||
album: string | undefined
|
album?: string | undefined
|
||||||
url: string
|
src: string
|
||||||
cover: string
|
pic: string
|
||||||
lrc?: string
|
lrc?: string
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@
|
|||||||
<div class="layout-content">
|
<div class="layout-content">
|
||||||
<router-view class="main-view"></router-view>
|
<router-view class="main-view"></router-view>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-copy">2016-{{currentYear}} © colorfulsweet</div>
|
<div class="layout-copy">2016-{{currentYear}} © colorfulsweet 上次更新:{{ version }}</div>
|
||||||
</el-main>
|
</el-main>
|
||||||
</el-container>
|
</el-container>
|
||||||
</el-container>
|
</el-container>
|
||||||
@ -58,6 +58,7 @@ import menus from '../config/menu'
|
|||||||
components: { ElContainer, ElHeader, ElMain, ElAside, ElDropdown, ElDropdownMenu, ElDropdownItem, ElButton, ElMenu, ElSubMenu, ElMenuItem, ElBreadcrumb, ElBreadcrumbItem }
|
components: { ElContainer, ElHeader, ElMain, ElAside, ElDropdown, ElDropdownMenu, ElDropdownItem, ElButton, ElMenu, ElSubMenu, ElMenuItem, ElBreadcrumb, ElBreadcrumbItem }
|
||||||
})
|
})
|
||||||
export default class Home extends Vue{
|
export default class Home extends Vue{
|
||||||
|
public version?: string = process.env.VERSION
|
||||||
currentYear = new Date().getFullYear()
|
currentYear = new Date().getFullYear()
|
||||||
// 菜单项
|
// 菜单项
|
||||||
menus = menus
|
menus = menus
|
||||||
|
|||||||
@ -142,7 +142,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<el-drawer v-model="musicPlaying" :close-on-click-modal="false" size="40%" title="播放音乐">
|
<el-drawer v-model="musicPlaying" :close-on-click-modal="false" size="40%" title="播放音乐">
|
||||||
<music-player v-if="musicPlaying" :audio="musicList" :lrcType="3" ></music-player>
|
<a-player autoplay showLrc :list="musicList" :music="musicList[0]"/>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -153,14 +153,14 @@ import BaseList from '../../model/baselist'
|
|||||||
import { MsgResult, Page } from '../../model/common.dto'
|
import { MsgResult, Page } from '../../model/common.dto'
|
||||||
import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElRadioGroup, ElRadio, ElDrawer, ElUpload, ElMessage, ElMessageBox } from 'element-plus'
|
import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElRadioGroup, ElRadio, ElDrawer, ElUpload, ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { MusicModel, MusicLibModel, MusicLyricModel, MusicPlayerItem } from '../../model/api/music'
|
import { MusicModel, MusicLibModel, MusicLyricModel, MusicPlayerItem } from '../../model/api/music'
|
||||||
import MusicPlayer from './MusicPlayer.vue'
|
import APlayer from './aplayer/vue-aplayer.vue'
|
||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
import { VForm } from '../../types'
|
import { VForm } from '../../types'
|
||||||
|
|
||||||
let selectedIds: string[] = []
|
let selectedIds: string[] = []
|
||||||
@Options({
|
@Options({
|
||||||
name: 'Music',
|
name: 'Music',
|
||||||
components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElRadioGroup, ElRadio, ElDrawer, ElUpload, MusicPlayer }
|
components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElRadioGroup, ElRadio, ElDrawer, ElUpload, APlayer }
|
||||||
})
|
})
|
||||||
export default class Music extends BaseList<MusicPage> {
|
export default class Music extends BaseList<MusicPage> {
|
||||||
search = new MusicPage()
|
search = new MusicPage()
|
||||||
@ -217,14 +217,13 @@ export default class Music extends BaseList<MusicPage> {
|
|||||||
const data = await this.$http.get<any, any>('/api/v1/music/list/all', {params: selectedIds.length ? {ids: selectedIds} : this.search})
|
const data = await this.$http.get<any, any>('/api/v1/music/list/all', {params: selectedIds.length ? {ids: selectedIds} : this.search})
|
||||||
this.musicList = data.map((item: MusicModel) => {
|
this.musicList = data.map((item: MusicModel) => {
|
||||||
const musicItem: MusicPlayerItem = {
|
const musicItem: MusicPlayerItem = {
|
||||||
name: item.title || item.name,
|
title: item.title || item.name,
|
||||||
artist: item.artist,
|
artist: item.artist,
|
||||||
album: item.album,
|
src: `/api/v2/common/music/load/${item._id}`,
|
||||||
url: `/api/v2/common/music/load/${item._id}`,
|
pic: `/api/v2/common/music/album/${item._id}`,
|
||||||
cover: `/api/v2/common/music/album/${item._id}`,
|
|
||||||
}
|
}
|
||||||
if(item.lyric_id) {
|
if(item.lyric_id) {
|
||||||
musicItem.lrc = `/api/v1/common/music/lyric/${item.lyric_id}`
|
musicItem.lrc = `${location.origin}/api/v1/common/music/lyric/${item.lyric_id}`
|
||||||
}
|
}
|
||||||
return musicItem
|
return musicItem
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,683 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div ref="player"></div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { Options, Vue } from 'vue-class-component'
|
|
||||||
// @ts-ignore
|
|
||||||
import APlayer from 'aplayer'
|
|
||||||
|
|
||||||
@Options({
|
|
||||||
name: 'MusicPlayer',
|
|
||||||
props: {
|
|
||||||
audio: { type: Array, require: true }, // 歌曲列表
|
|
||||||
fixed: { type: Boolean, default: false }, // 固定模式
|
|
||||||
mini: { type: Boolean, default: false }, // 迷你模式
|
|
||||||
autoplay: { type: Boolean, default: false }, // 自动播放
|
|
||||||
theme: { type: String, default: '#b7daff' }, // 主题色
|
|
||||||
loop: { type: String, default: 'all' }, // 循环方式 'all', 'one', 'none'
|
|
||||||
order: { type: String, default: 'list' }, // 播放顺序 'list', 'random'
|
|
||||||
preload: { type: String, default: 'auto' }, // 预加载 'none', 'metadata', 'auto'
|
|
||||||
volume: { type: Number, default: 0.7 }, // 音量
|
|
||||||
lrcType: { type: Number, default: 0 }, // 歌词类型
|
|
||||||
}
|
|
||||||
})
|
|
||||||
export default class MusicPlayer extends Vue {
|
|
||||||
// 播放器初始化option
|
|
||||||
audio!: Audio[]
|
|
||||||
fixed!: boolean
|
|
||||||
mini!: boolean
|
|
||||||
autoplay!: boolean
|
|
||||||
theme!: string
|
|
||||||
loop!: 'all' | 'one' | 'none'
|
|
||||||
order!: 'list' | 'random'
|
|
||||||
preload!: 'none' | 'metadata' | 'auto'
|
|
||||||
volume!: number
|
|
||||||
lrcType!: number
|
|
||||||
private ap: any
|
|
||||||
mounted() {
|
|
||||||
this.ap = new APlayer({
|
|
||||||
container: this.$refs.player,
|
|
||||||
fixed: this.fixed,
|
|
||||||
mini: this.mini,
|
|
||||||
autoplay: this.autoplay,
|
|
||||||
theme: this.theme,
|
|
||||||
loop: this.loop,
|
|
||||||
order: this.order,
|
|
||||||
preload: this.preload,
|
|
||||||
volume: this.volume,
|
|
||||||
lrcType: this.lrcType,
|
|
||||||
audio: this.audio
|
|
||||||
})
|
|
||||||
}
|
|
||||||
beforeUnmount() {
|
|
||||||
// 销毁播放器
|
|
||||||
this.ap.destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Audio {
|
|
||||||
name: string,
|
|
||||||
artist: string,
|
|
||||||
url: string,
|
|
||||||
cover: string,
|
|
||||||
lrc: string,
|
|
||||||
theme: string,
|
|
||||||
type: string
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="less" scoped>
|
|
||||||
@aplayer-height: 66px;
|
|
||||||
@lrc-height: 30px;
|
|
||||||
@aplayer-height-lrc: @aplayer-height + @lrc-height - 6;
|
|
||||||
|
|
||||||
.aplayer::v-deep {
|
|
||||||
background: #fff;
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
margin: 5px;
|
|
||||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.07), 0 1px 5px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 2px;
|
|
||||||
overflow: hidden;
|
|
||||||
user-select: none;
|
|
||||||
line-height: initial;
|
|
||||||
position: relative;
|
|
||||||
* {
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
|
||||||
svg {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
path,circle {
|
|
||||||
fill: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.aplayer-withlist {
|
|
||||||
.aplayer-info {
|
|
||||||
border-bottom: 1px solid #e9e9e9;
|
|
||||||
}
|
|
||||||
.aplayer-list {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.aplayer-info .aplayer-controller .aplayer-time .aplayer-icon.aplayer-icon-menu {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
.aplayer-icon-order {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.aplayer-withlrc {
|
|
||||||
.aplayer-pic {
|
|
||||||
height: @aplayer-height-lrc;
|
|
||||||
width: @aplayer-height-lrc;
|
|
||||||
}
|
|
||||||
.aplayer-info {
|
|
||||||
margin-left: @aplayer-height-lrc;
|
|
||||||
height: @aplayer-height-lrc;
|
|
||||||
padding: 10px 7px 0 7px;
|
|
||||||
}
|
|
||||||
.aplayer-lrc {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.aplayer-narrow {
|
|
||||||
width: @aplayer-height;
|
|
||||||
.aplayer-info {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.aplayer-list {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.aplayer-pic,.aplayer-body {
|
|
||||||
height: @aplayer-height;
|
|
||||||
width: @aplayer-height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.aplayer-fixed {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
margin: 0;
|
|
||||||
z-index: 99;
|
|
||||||
overflow: visible;
|
|
||||||
max-width: 400px;
|
|
||||||
box-shadow: none;
|
|
||||||
.aplayer-list {
|
|
||||||
margin-bottom: 65px;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
.aplayer-body {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
margin: 0;
|
|
||||||
z-index: 99;
|
|
||||||
background: #fff;
|
|
||||||
padding-right: 18px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
.aplayer-lrc {
|
|
||||||
display: block;
|
|
||||||
position: fixed;
|
|
||||||
bottom: 10px;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
margin: 0;
|
|
||||||
z-index: 98;
|
|
||||||
pointer-events: none;
|
|
||||||
text-shadow: -1px -1px 0 #fff;
|
|
||||||
&:before,&:after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-info {
|
|
||||||
transform: scaleX(1);
|
|
||||||
transform-origin: 0 0;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
border-bottom: none;
|
|
||||||
border-top: 1px solid #e9e9e9;
|
|
||||||
.aplayer-music {
|
|
||||||
width: calc(100% - 105px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-miniswitcher {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
&.aplayer-narrow {
|
|
||||||
.aplayer-info {
|
|
||||||
display: block;
|
|
||||||
transform: scaleX(0);
|
|
||||||
}
|
|
||||||
.aplayer-body {
|
|
||||||
width: @aplayer-height !important;
|
|
||||||
}
|
|
||||||
.aplayer-miniswitcher .aplayer-icon {
|
|
||||||
transform: rotateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-icon-back,
|
|
||||||
.aplayer-icon-play,
|
|
||||||
.aplayer-icon-forward,
|
|
||||||
.aplayer-icon-lrc {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.aplayer-icon-back,
|
|
||||||
.aplayer-icon-play,
|
|
||||||
.aplayer-icon-forward,
|
|
||||||
.aplayer-icon-menu {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 27px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
.aplayer-icon-back {
|
|
||||||
right: 75px;
|
|
||||||
}
|
|
||||||
.aplayer-icon-play {
|
|
||||||
right: 50px;
|
|
||||||
}
|
|
||||||
.aplayer-icon-forward {
|
|
||||||
right: 25px;
|
|
||||||
}
|
|
||||||
.aplayer-icon-menu {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.aplayer-mobile {
|
|
||||||
.aplayer-icon-volume-down {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.aplayer-arrow {
|
|
||||||
.aplayer-icon-order,
|
|
||||||
.aplayer-icon-loop {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.aplayer-loading {
|
|
||||||
.aplayer-info .aplayer-controller .aplayer-loading-icon {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.aplayer-info .aplayer-controller .aplayer-bar-wrap .aplayer-bar .aplayer-played .aplayer-thumb {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-body {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.aplayer-icon {
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
outline: none;
|
|
||||||
cursor: pointer;
|
|
||||||
opacity: .8;
|
|
||||||
vertical-align: middle;
|
|
||||||
padding: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
margin: 0;
|
|
||||||
display: inline-block;
|
|
||||||
path {
|
|
||||||
transition: all .2s ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-icon-order,
|
|
||||||
.aplayer-icon-back,
|
|
||||||
.aplayer-icon-play,
|
|
||||||
.aplayer-icon-forward,
|
|
||||||
.aplayer-icon-lrc {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.aplayer-icon-lrc-inactivity {
|
|
||||||
svg {
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-icon-forward {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
.aplayer-lrc-content {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.aplayer-pic {
|
|
||||||
position: relative;
|
|
||||||
float: left;
|
|
||||||
height: @aplayer-height;
|
|
||||||
width: @aplayer-height;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover .aplayer-button {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.aplayer-button {
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 50%;
|
|
||||||
opacity: 0.8;
|
|
||||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
|
||||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
transition: all 0.1s ease;
|
|
||||||
path {
|
|
||||||
fill: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-hide {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.aplayer-play {
|
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
border: 2px solid #fff;
|
|
||||||
bottom: 50%;
|
|
||||||
right: 50%;
|
|
||||||
margin: 0 -15px -15px 0;
|
|
||||||
svg {
|
|
||||||
position: absolute;
|
|
||||||
top: 3px;
|
|
||||||
left: 4px;
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-pause {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border: 2px solid #fff;
|
|
||||||
bottom: 4px;
|
|
||||||
right: 4px;
|
|
||||||
svg {
|
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
left: 2px;
|
|
||||||
height: 12px;
|
|
||||||
width: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-info {
|
|
||||||
margin-left: @aplayer-height;
|
|
||||||
padding: 14px 7px 0 10px;
|
|
||||||
height: @aplayer-height;
|
|
||||||
box-sizing: border-box;
|
|
||||||
.aplayer-music {
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
margin: 0 0 13px 5px;
|
|
||||||
user-select: text;
|
|
||||||
cursor: default;
|
|
||||||
padding-bottom: 2px;
|
|
||||||
height: 20px;
|
|
||||||
.aplayer-title {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.aplayer-author {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-controller {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
.aplayer-bar-wrap {
|
|
||||||
margin: 0 0 0 5px;
|
|
||||||
padding: 4px 0;
|
|
||||||
cursor: pointer !important;
|
|
||||||
flex: 1;
|
|
||||||
&:hover {
|
|
||||||
.aplayer-bar .aplayer-played .aplayer-thumb {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-bar {
|
|
||||||
position: relative;
|
|
||||||
height: 2px;
|
|
||||||
width: 100%;
|
|
||||||
background: #cdcdcd;
|
|
||||||
.aplayer-loaded {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: #aaa;
|
|
||||||
height: 2px;
|
|
||||||
transition: all 0.5s ease;
|
|
||||||
}
|
|
||||||
.aplayer-played {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
height: 2px;
|
|
||||||
.aplayer-thumb {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 5px;
|
|
||||||
margin-top: -4px;
|
|
||||||
margin-right: -10px;
|
|
||||||
height: 10px;
|
|
||||||
width: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all .3s ease-in-out;
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-time {
|
|
||||||
position: relative;
|
|
||||||
right: 0;
|
|
||||||
bottom: 4px;
|
|
||||||
height: 17px;
|
|
||||||
color: #999;
|
|
||||||
font-size: 11px;
|
|
||||||
padding-left: 7px;
|
|
||||||
.aplayer-time-inner {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.aplayer-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
path {
|
|
||||||
fill: #666;
|
|
||||||
}
|
|
||||||
&.aplayer-icon-loop {
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
path {
|
|
||||||
fill: #000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.aplayer-icon-menu {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.aplayer-time-narrow {
|
|
||||||
.aplayer-icon-mode, .aplayer-icon-menu {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-volume-wrap {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 3px;
|
|
||||||
cursor: pointer !important;
|
|
||||||
&:hover .aplayer-volume-bar-wrap {
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
.aplayer-volume-bar-wrap {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 15px;
|
|
||||||
right: -3px;
|
|
||||||
width: 25px;
|
|
||||||
height: 0;
|
|
||||||
z-index: 99;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: all .2s ease-in-out;
|
|
||||||
&.aplayer-volume-bar-wrap-active {
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
.aplayer-volume-bar {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
right: 10px;
|
|
||||||
width: 5px;
|
|
||||||
height: 35px;
|
|
||||||
background: #aaa;
|
|
||||||
border-radius: 2.5px;
|
|
||||||
overflow: hidden;
|
|
||||||
.aplayer-volume {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 5px;
|
|
||||||
transition: all 0.1s ease;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-loading-icon {
|
|
||||||
display: none;
|
|
||||||
svg {
|
|
||||||
position: absolute;
|
|
||||||
animation: rotate 1s linear infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-lrc {
|
|
||||||
display: none;
|
|
||||||
position: relative;
|
|
||||||
height: @lrc-height;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: -10px 0 7px;
|
|
||||||
&:before {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1;
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
|
||||||
height: 10%;
|
|
||||||
content: ' ';
|
|
||||||
background: -moz-linear-gradient(top, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%);
|
|
||||||
background: -webkit-linear-gradient(top, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%);
|
|
||||||
background: linear-gradient(to bottom, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#00ffffff',GradientType=0 );
|
|
||||||
}
|
|
||||||
&:after {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 1;
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
|
||||||
height: 33%;
|
|
||||||
content: ' ';
|
|
||||||
background: -moz-linear-gradient(top, rgba(255,255,255,0) 0%, rgba(255,255,255,0.8) 100%);
|
|
||||||
background: -webkit-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,0.8) 100%);
|
|
||||||
background: linear-gradient(to bottom, rgba(255,255,255,0) 0%,rgba(255,255,255,0.8) 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00ffffff', endColorstr='#ccffffff',GradientType=0 );
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
line-height: 16px !important;
|
|
||||||
height: 16px !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
transition: all 0.5s ease-out;
|
|
||||||
opacity: 0.4;
|
|
||||||
overflow: hidden;
|
|
||||||
&.aplayer-lrc-current {
|
|
||||||
opacity: 1;
|
|
||||||
overflow: visible;
|
|
||||||
height: initial !important;
|
|
||||||
min-height: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.aplayer-lrc-hide {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.aplayer-lrc-contents {
|
|
||||||
width: 100%;
|
|
||||||
transition: all 0.5s ease-out;
|
|
||||||
user-select: text;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-list {
|
|
||||||
overflow: auto;
|
|
||||||
transition: all 0.5s ease;
|
|
||||||
will-change: height;
|
|
||||||
display: none;
|
|
||||||
overflow: hidden;
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 5px;
|
|
||||||
}
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
&::-webkit-scrollbar-thumb:hover {
|
|
||||||
background-color: #ccc;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
position: relative;
|
|
||||||
height: 32px;
|
|
||||||
line-height: 32px;
|
|
||||||
padding: 0 15px;
|
|
||||||
font-size: 12px;
|
|
||||||
border-top: 1px solid #e9e9e9;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 0;
|
|
||||||
&:first-child {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
background: #efefef;
|
|
||||||
}
|
|
||||||
&.aplayer-list-light {
|
|
||||||
background: #e9e9e9;
|
|
||||||
.aplayer-list-cur {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-list-cur {
|
|
||||||
display: none;
|
|
||||||
width: 3px;
|
|
||||||
height: 22px;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.aplayer-list-index {
|
|
||||||
color: #666;
|
|
||||||
margin-right: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.aplayer-list-author {
|
|
||||||
color: #666;
|
|
||||||
float: right;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ol {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aplayer-notice {
|
|
||||||
opacity: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
font-size: 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 5px 10px;
|
|
||||||
transition: all .3s ease-in-out;
|
|
||||||
overflow: hidden;
|
|
||||||
color: #fff;
|
|
||||||
pointer-events: none;
|
|
||||||
background-color: #f4f4f5;
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
.aplayer-miniswitcher {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
height: 100%;
|
|
||||||
background: #e6e6e6;
|
|
||||||
width: 18px;
|
|
||||||
border-radius: 0 2px 2px 0;
|
|
||||||
.aplayer-icon {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
transform: rotateY(180deg);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
path {
|
|
||||||
fill: #666;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
path {
|
|
||||||
fill: #000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes aplayer-roll {
|
|
||||||
0%{left:0}
|
|
||||||
100%{left: -100%}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes rotate {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0)
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
3
src/views/api/aplayer/assets/loading.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32">
|
||||||
|
<path class="aplayer-fill" d="M4 16c0-6.6 5.4-12 12-12s12 5.4 12 12c0 1.2-0.8 2-2 2s-2-0.8-2-2c0-4.4-3.6-8-8-8s-8 3.6-8 8 3.6 8 8 8c1.2 0 2 0.8 2 2s-0.8 2-2 2c-6.6 0-12-5.4-12-12z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 274 B |
3
src/views/api/aplayer/assets/lrc.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32">
|
||||||
|
<path class="aplayer-fill" d="M26.667 5.333h-21.333c-0 0-0.001 0-0.001 0-1.472 0-2.666 1.194-2.666 2.666 0 0 0 0.001 0 0.001v-0 16c0 0 0 0.001 0 0.001 0 1.472 1.194 2.666 2.666 2.666 0 0 0.001 0 0.001 0h21.333c0 0 0.001 0 0.001 0 1.472 0 2.666-1.194 2.666-2.666 0-0 0-0.001 0-0.001v0-16c0-0 0-0.001 0-0.001 0-1.472-1.194-2.666-2.666-2.666-0 0-0.001 0-0.001 0h0zM5.333 16h5.333v2.667h-5.333v-2.667zM18.667 24h-13.333v-2.667h13.333v2.667zM26.667 24h-5.333v-2.667h5.333v2.667zM26.667 18.667h-13.333v-2.667h13.333v2.667z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 611 B |
3
src/views/api/aplayer/assets/menu.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-5 0 32 32">
|
||||||
|
<path class="aplayer-fill" d="M20.8 14.4q0.704 0 1.152 0.48t0.448 1.12-0.48 1.12-1.12 0.48h-19.2q-0.64 0-1.12-0.48t-0.48-1.12 0.448-1.12 1.152-0.48h19.2zM1.6 11.2q-0.64 0-1.12-0.48t-0.48-1.12 0.448-1.12 1.152-0.48h19.2q0.704 0 1.152 0.48t0.448 1.12-0.48 1.12-1.12 0.48h-19.2zM20.8 20.8q0.704 0 1.152 0.48t0.448 1.12-0.48 1.12-1.12 0.48h-19.2q-0.64 0-1.12-0.48t-0.48-1.12 0.448-1.12 1.152-0.48h19.2z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 494 B |
3
src/views/api/aplayer/assets/no_repeat.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32">
|
||||||
|
<path class="aplayer-fill" d="M2.667 7.027l1.707-1.693 22.293 22.293-1.693 1.707-4-4h-11.64v4l-5.333-5.333 5.333-5.333v4h8.973l-8.973-8.973v0.973h-2.667v-3.64l-4-4zM22.667 17.333h2.667v5.573l-2.667-2.667v-2.907zM22.667 6.667v-4l5.333 5.333-5.333 5.333v-4h-10.907l-2.667-2.667h13.573z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 378 B |
3
src/views/api/aplayer/assets/pause.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-8 0 32 32">
|
||||||
|
<path class="aplayer-fill" d="M14.080 4.8q2.88 0 2.88 2.048v18.24q0 2.112-2.88 2.112t-2.88-2.112v-18.24q0-2.048 2.88-2.048zM2.88 4.8q2.88 0 2.88 2.048v18.24q0 2.112-2.88 2.112t-2.88-2.112v-18.24q0-2.048 2.88-2.048z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 310 B |
3
src/views/api/aplayer/assets/play.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-8 0 32 32">
|
||||||
|
<path class="aplayer-fill" d="M15.552 15.168q0.448 0.32 0.448 0.832 0 0.448-0.448 0.768l-13.696 8.512q-0.768 0.512-1.312 0.192t-0.544-1.28v-16.448q0-0.96 0.544-1.28t1.312 0.192z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 273 B |
3
src/views/api/aplayer/assets/repeat_all.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32">
|
||||||
|
<path class="aplayer-fill" d="M9.333 9.333h13.333v4l5.333-5.333-5.333-5.333v4h-16v8h2.667v-5.333zM22.667 22.667h-13.333v-4l-5.333 5.333 5.333 5.333v-4h16v-8h-2.667v5.333z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 261 B |
3
src/views/api/aplayer/assets/repeat_one.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32">
|
||||||
|
<path class="aplayer-fill" d="M9.333 9.333h13.333v4l5.333-5.333-5.333-5.333v4h-16v8h2.667v-5.333zM22.667 22.667h-13.333v-4l-5.333 5.333 5.333 5.333v-4h16v-8h-2.667v5.333zM17.333 20v-8h-1.333l-2.667 1.333v1.333h2v5.333h2z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 315 B |
3
src/views/api/aplayer/assets/shuffle.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32">
|
||||||
|
<path class="aplayer-fill" d="M22.667 4l7 6-7 6 7 6-7 6v-4h-3.653l-3.76-3.76 2.827-2.827 2.587 2.587h2v-8h-2l-12 12h-6v-4h4.347l12-12h3.653v-4zM2.667 8h6l3.76 3.76-2.827 2.827-2.587-2.587h-4.347v-4z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 293 B |
3
src/views/api/aplayer/assets/skip.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32">
|
||||||
|
<path class="aplayer-fill" d="M25.468 6.947c-0.326-0.172-0.724-0.151-1.030 0.057l-6.438 4.38v-3.553c0-0.371-0.205-0.71-0.532-0.884-0.326-0.172-0.724-0.151-1.030 0.057l-12 8.164c-0.274 0.186-0.438 0.496-0.438 0.827s0.164 0.641 0.438 0.827l12 8.168c0.169 0.115 0.365 0.174 0.562 0.174 0.16 0 0.321-0.038 0.468-0.116 0.327-0.173 0.532-0.514 0.532-0.884v-3.556l6.438 4.382c0.169 0.115 0.365 0.174 0.562 0.174 0.16 0 0.321-0.038 0.468-0.116 0.327-0.173 0.532-0.514 0.532-0.884v-16.333c0-0.371-0.205-0.71-0.532-0.884z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 606 B |
3
src/views/api/aplayer/assets/volume_down.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32">
|
||||||
|
<path class="aplayer-fill" d="M13.728 6.272v19.456q0 0.448-0.352 0.8t-0.8 0.32-0.8-0.32l-5.952-5.952h-4.672q-0.48 0-0.8-0.352t-0.352-0.8v-6.848q0-0.48 0.352-0.8t0.8-0.352h4.672l5.952-5.952q0.32-0.32 0.8-0.32t0.8 0.32 0.352 0.8zM20.576 16q0 1.344-0.768 2.528t-2.016 1.664q-0.16 0.096-0.448 0.096-0.448 0-0.8-0.32t-0.32-0.832q0-0.384 0.192-0.64t0.544-0.448 0.608-0.384 0.512-0.64 0.192-1.024-0.192-1.024-0.512-0.64-0.608-0.384-0.544-0.448-0.192-0.64q0-0.48 0.32-0.832t0.8-0.32q0.288 0 0.448 0.096 1.248 0.48 2.016 1.664t0.768 2.528z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 625 B |
3
src/views/api/aplayer/assets/volume_off.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32">
|
||||||
|
<path class="aplayer-fill" d="M13.728 6.272v19.456q0 0.448-0.352 0.8t-0.8 0.32-0.8-0.32l-5.952-5.952h-4.672q-0.48 0-0.8-0.352t-0.352-0.8v-6.848q0-0.48 0.352-0.8t0.8-0.352h4.672l5.952-5.952q0.32-0.32 0.8-0.32t0.8 0.32 0.352 0.8z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 322 B |
3
src/views/api/aplayer/assets/volume_up.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32">
|
||||||
|
<path class="aplayer-fill" d="M13.728 6.272v19.456q0 0.448-0.352 0.8t-0.8 0.32-0.8-0.32l-5.952-5.952h-4.672q-0.48 0-0.8-0.352t-0.352-0.8v-6.848q0-0.48 0.352-0.8t0.8-0.352h4.672l5.952-5.952q0.32-0.32 0.8-0.32t0.8 0.32 0.352 0.8zM20.576 16q0 1.344-0.768 2.528t-2.016 1.664q-0.16 0.096-0.448 0.096-0.448 0-0.8-0.32t-0.32-0.832q0-0.384 0.192-0.64t0.544-0.448 0.608-0.384 0.512-0.64 0.192-1.024-0.192-1.024-0.512-0.64-0.608-0.384-0.544-0.448-0.192-0.64q0-0.48 0.32-0.832t0.8-0.32q0.288 0 0.448 0.096 1.248 0.48 2.016 1.664t0.768 2.528zM25.152 16q0 2.72-1.536 5.056t-4 3.36q-0.256 0.096-0.448 0.096-0.48 0-0.832-0.352t-0.32-0.8q0-0.704 0.672-1.056 1.024-0.512 1.376-0.8 1.312-0.96 2.048-2.4t0.736-3.104-0.736-3.104-2.048-2.4q-0.352-0.288-1.376-0.8-0.672-0.352-0.672-1.056 0-0.448 0.32-0.8t0.8-0.352q0.224 0 0.48 0.096 2.496 1.056 4 3.36t1.536 5.056zM29.728 16q0 4.096-2.272 7.552t-6.048 5.056q-0.224 0.096-0.448 0.096-0.48 0-0.832-0.352t-0.32-0.8q0-0.64 0.704-1.056 0.128-0.064 0.384-0.192t0.416-0.192q0.8-0.448 1.44-0.896 2.208-1.632 3.456-4.064t1.216-5.152-1.216-5.152-3.456-4.064q-0.64-0.448-1.44-0.896-0.128-0.096-0.416-0.192t-0.384-0.192q-0.704-0.416-0.704-1.056 0-0.448 0.32-0.8t0.832-0.352q0.224 0 0.448 0.096 3.776 1.632 6.048 5.056t2.272 7.552z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
201
src/views/api/aplayer/components/aplayer-controller-progress.vue
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="aplayer-bar-wrap"
|
||||||
|
@mousedown="onThumbMouseDown"
|
||||||
|
@touchstart="onThumbTouchStart"
|
||||||
|
ref="barWrap"
|
||||||
|
>
|
||||||
|
<div class="aplayer-bar">
|
||||||
|
<div
|
||||||
|
class="aplayer-loaded"
|
||||||
|
:style="{width: `${loadProgress * 100}%`}">
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="aplayer-played"
|
||||||
|
:style="{width: `${playProgress * 100}%`, background: theme}"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
ref="thumb"
|
||||||
|
@mouseover="thumbHovered = true"
|
||||||
|
@mouseout="thumbHovered = false"
|
||||||
|
class="aplayer-thumb"
|
||||||
|
:style="{borderColor: theme, backgroundColor: thumbHovered ? theme : '#fff'}"
|
||||||
|
>
|
||||||
|
<span class="aplayer-loading-icon"
|
||||||
|
:style="{backgroundColor: theme }"
|
||||||
|
>
|
||||||
|
<icon type="loading"/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {getElementViewLeft} from '../utils'
|
||||||
|
import Icon from './aplayer-icon.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Icon
|
||||||
|
},
|
||||||
|
props: ['loadProgress', 'playProgress', 'theme'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
thumbHovered: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onThumbMouseDown (e) {
|
||||||
|
const barWidth = this.$refs.barWrap.clientWidth
|
||||||
|
let percentage = (e.clientX - getElementViewLeft(this.$refs.barWrap)) / barWidth
|
||||||
|
percentage = percentage > 0 ? percentage : 0
|
||||||
|
percentage = percentage < 1 ? percentage : 1
|
||||||
|
|
||||||
|
this.$emit('dragbegin', percentage)
|
||||||
|
document.addEventListener('mousemove', this.onDocumentMouseMove)
|
||||||
|
document.addEventListener('mouseup', this.onDocumentMouseUp)
|
||||||
|
},
|
||||||
|
onDocumentMouseMove (e) {
|
||||||
|
const barWidth = this.$refs.barWrap.clientWidth
|
||||||
|
let percentage = (e.clientX - getElementViewLeft(this.$refs.barWrap)) / barWidth
|
||||||
|
percentage = percentage > 0 ? percentage : 0
|
||||||
|
percentage = percentage < 1 ? percentage : 1
|
||||||
|
|
||||||
|
this.$emit('dragging', percentage)
|
||||||
|
},
|
||||||
|
onDocumentMouseUp (e) {
|
||||||
|
document.removeEventListener('mouseup', this.onDocumentMouseUp)
|
||||||
|
document.removeEventListener('mousemove', this.onDocumentMouseMove)
|
||||||
|
|
||||||
|
const barWidth = this.$refs.barWrap.clientWidth
|
||||||
|
let percentage = (e.clientX - getElementViewLeft(this.$refs.barWrap)) / barWidth
|
||||||
|
percentage = percentage > 0 ? percentage : 0
|
||||||
|
percentage = percentage < 1 ? percentage : 1
|
||||||
|
this.$emit('dragend', percentage)
|
||||||
|
},
|
||||||
|
onThumbTouchStart (e) {
|
||||||
|
const barWidth = this.$refs.barWrap.clientWidth
|
||||||
|
let percentage = (e.clientX - getElementViewLeft(this.$refs.barWrap)) / barWidth
|
||||||
|
percentage = percentage > 0 ? percentage : 0
|
||||||
|
percentage = percentage < 1 ? percentage : 1
|
||||||
|
|
||||||
|
this.$emit('dragbegin', percentage)
|
||||||
|
document.addEventListener('touchmove', this.onDocumentTouchMove)
|
||||||
|
document.addEventListener('touchend', this.onDocumentTouchEnd)
|
||||||
|
},
|
||||||
|
onDocumentTouchMove (e) {
|
||||||
|
const touch = e.changedTouches[0]
|
||||||
|
const barWidth = this.$refs.barWrap.clientWidth
|
||||||
|
let percentage = (touch.clientX - getElementViewLeft(this.$refs.barWrap)) / barWidth
|
||||||
|
percentage = percentage > 0 ? percentage : 0
|
||||||
|
percentage = percentage < 1 ? percentage : 1
|
||||||
|
|
||||||
|
this.$emit('dragging', percentage)
|
||||||
|
},
|
||||||
|
onDocumentTouchEnd (e) {
|
||||||
|
document.removeEventListener('touchend', this.onDocumentTouchEnd)
|
||||||
|
document.removeEventListener('touchmove', this.onDocumentTouchMove)
|
||||||
|
|
||||||
|
const touch = e.changedTouches[0]
|
||||||
|
const barWidth = this.$refs.barWrap.clientWidth
|
||||||
|
let percentage = (touch.clientX - getElementViewLeft(this.$refs.barWrap)) / barWidth
|
||||||
|
percentage = percentage > 0 ? percentage : 0
|
||||||
|
percentage = percentage < 1 ? percentage : 1
|
||||||
|
this.$emit('dragend', percentage)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
|
||||||
|
.aplayer-bar-wrap {
|
||||||
|
margin: 0 0 0 5px;
|
||||||
|
padding: 4px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.aplayer-bar {
|
||||||
|
position: relative;
|
||||||
|
height: 2px;
|
||||||
|
width: 100%;
|
||||||
|
background: #cdcdcd;
|
||||||
|
|
||||||
|
.aplayer-loaded {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: #aaa;
|
||||||
|
height: 2px;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
|
||||||
|
will-change: width;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-played {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 2px;
|
||||||
|
transition: background-color .3s;
|
||||||
|
will-change: width;
|
||||||
|
|
||||||
|
.aplayer-thumb {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 5px;
|
||||||
|
margin-top: -5px;
|
||||||
|
margin-right: -10px;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border: 1px solid;
|
||||||
|
transform: scale(.8);
|
||||||
|
will-change: transform;
|
||||||
|
transition: transform 300ms, background-color .3s, border-color .3s;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
.aplayer-loading-icon {
|
||||||
|
display: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
position: absolute;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
fill: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-loading {
|
||||||
|
.aplayer-bar-wrap .aplayer-bar .aplayer-thumb .aplayer-loading-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-info .aplayer-controller .aplayer-bar-wrap .aplayer-bar .aplayer-played .aplayer-thumb {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0)
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
128
src/views/api/aplayer/components/aplayer-controller-volume.vue
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<template>
|
||||||
|
<div class="aplayer-volume-wrap">
|
||||||
|
<icon-button
|
||||||
|
:class="`aplayer-icon-${volumeIcon}`"
|
||||||
|
:icon="volumeIcon"
|
||||||
|
@click="$emit('togglemute')"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="aplayer-volume-bar-wrap"
|
||||||
|
@mousedown="onBarMouseDown"
|
||||||
|
>
|
||||||
|
<div class="aplayer-volume-bar" ref="bar">
|
||||||
|
<div
|
||||||
|
class="aplayer-volume"
|
||||||
|
:style="{
|
||||||
|
height: muted ? 0 : `${Math.trunc(volume * 100)}%`,
|
||||||
|
background: theme
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import IconButton from './aplayer-iconbutton.vue'
|
||||||
|
import {getElementViewTop} from '../utils'
|
||||||
|
|
||||||
|
const barHeight = 40
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
IconButton,
|
||||||
|
},
|
||||||
|
props: ['volume', 'muted', 'theme'],
|
||||||
|
computed: {
|
||||||
|
volumeIcon () {
|
||||||
|
if (this.muted || this.volume <= 0) return 'volume_off'
|
||||||
|
if (this.volume >= 1) return 'volume_up'
|
||||||
|
return 'volume_down'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
adjustVolume (e) {
|
||||||
|
let percentage = (barHeight - e.clientY + getElementViewTop(this.$refs.bar)) / barHeight
|
||||||
|
percentage = percentage > 0 ? percentage : 0
|
||||||
|
percentage = percentage < 1 ? percentage : 1
|
||||||
|
this.$emit('setvolume', percentage)
|
||||||
|
},
|
||||||
|
onBarMouseDown () {
|
||||||
|
document.addEventListener('mousemove', this.onDocumentMouseMove)
|
||||||
|
document.addEventListener('mouseup', this.onDocumentMouseUp)
|
||||||
|
},
|
||||||
|
onDocumentMouseMove (e) {
|
||||||
|
let percentage = (barHeight - e.clientY + getElementViewTop(this.$refs.bar)) / barHeight
|
||||||
|
percentage = percentage > 0 ? percentage : 0
|
||||||
|
percentage = percentage < 1 ? percentage : 1
|
||||||
|
this.$emit('setvolume', percentage)
|
||||||
|
},
|
||||||
|
onDocumentMouseUp (e) {
|
||||||
|
document.removeEventListener('mouseup', this.onDocumentMouseUp)
|
||||||
|
document.removeEventListener('mousemove', this.onDocumentMouseMove)
|
||||||
|
|
||||||
|
let percentage = (barHeight - e.clientY + getElementViewTop(this.$refs.bar)) / barHeight
|
||||||
|
percentage = percentage > 0 ? percentage : 0
|
||||||
|
percentage = percentage < 1 ? percentage : 1
|
||||||
|
this.$emit('setvolume', percentage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
|
||||||
|
.aplayer-volume-wrap {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 0;
|
||||||
|
|
||||||
|
&:hover .aplayer-volume-bar-wrap {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-volume-bar-wrap {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 15px;
|
||||||
|
left: -4px;
|
||||||
|
right: -4px;
|
||||||
|
height: 40px;
|
||||||
|
z-index: -1;
|
||||||
|
transition: all .2s ease;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -16px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 62px;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.07), 0 0 5px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-volume-bar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 11px;
|
||||||
|
width: 5px;
|
||||||
|
height: 40px;
|
||||||
|
background: #aaa;
|
||||||
|
border-radius: 2.5px;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.aplayer-volume {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
transition: height 0.1s ease, background-color .3s;
|
||||||
|
will-change: height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
147
src/views/api/aplayer/components/aplayer-controller.vue
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<template>
|
||||||
|
<div class="aplayer-controller">
|
||||||
|
<v-progress
|
||||||
|
:loadProgress="loadProgress"
|
||||||
|
:playProgress="playProgress"
|
||||||
|
:theme="theme"
|
||||||
|
@dragbegin="val => $emit('dragbegin', val)"
|
||||||
|
@dragend="val => $emit('dragend', val)"
|
||||||
|
@dragging="val => $emit('dragging', val)"
|
||||||
|
/>
|
||||||
|
<div class="aplayer-time">
|
||||||
|
<div class="aplayer-time-inner">
|
||||||
|
- <span class="aplayer-ptime">{{secondToTime(stat.playedTime)}}</span> / <span
|
||||||
|
class="aplayer-dtime">{{secondToTime(stat.duration)}}</span>
|
||||||
|
</div>
|
||||||
|
<volume
|
||||||
|
v-if="!$parent.isMobile"
|
||||||
|
:volume="volume"
|
||||||
|
:theme="theme"
|
||||||
|
:muted="muted"
|
||||||
|
@togglemute="$emit('togglemute')"
|
||||||
|
@setvolume="v => $emit('setvolume', v)"
|
||||||
|
/>
|
||||||
|
<icon-button
|
||||||
|
class="aplayer-icon-mode"
|
||||||
|
icon="shuffle"
|
||||||
|
:class="{ 'inactive': !shuffle }"
|
||||||
|
@click="$emit('toggleshuffle')"
|
||||||
|
/>
|
||||||
|
<icon-button
|
||||||
|
class="aplayer-icon-mode"
|
||||||
|
:icon="repeat === 'repeat_one' ? 'repeat_one' : 'repeat_all'"
|
||||||
|
:class="{ 'inactive': repeat === 'no_repeat'}"
|
||||||
|
@click="$emit('nextmode')"
|
||||||
|
/>
|
||||||
|
<icon-button
|
||||||
|
class="aplayer-icon-menu"
|
||||||
|
icon="menu"
|
||||||
|
:class="{ 'inactive': !$parent.showList }"
|
||||||
|
@click="$emit('togglelist')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import IconButton from './aplayer-iconbutton.vue'
|
||||||
|
import VProgress from './aplayer-controller-progress.vue'
|
||||||
|
import Volume from './aplayer-controller-volume.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
IconButton,
|
||||||
|
VProgress,
|
||||||
|
Volume,
|
||||||
|
},
|
||||||
|
props: ['shuffle', 'repeat', 'stat', 'theme', 'volume', 'muted'],
|
||||||
|
computed: {
|
||||||
|
loadProgress () {
|
||||||
|
if (this.stat.duration === 0) return 0
|
||||||
|
return this.stat.loadedTime / this.stat.duration
|
||||||
|
},
|
||||||
|
playProgress () {
|
||||||
|
if (this.stat.duration === 0) return 0
|
||||||
|
return this.stat.playedTime / this.stat.duration
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
secondToTime (second) {
|
||||||
|
if (isNaN(second)) {
|
||||||
|
return '00:00'
|
||||||
|
}
|
||||||
|
const pad0 = (num) => {
|
||||||
|
return num < 10 ? '0' + num : '' + num
|
||||||
|
}
|
||||||
|
|
||||||
|
const min = Math.trunc(second / 60)
|
||||||
|
const sec = Math.trunc(second - min * 60)
|
||||||
|
const hours = Math.trunc(min / 60)
|
||||||
|
const minAdjust = Math.trunc((second / 60) - (60 * Math.trunc((second / 60) / 60)))
|
||||||
|
return second >= 3600 ? pad0(hours) + ':' + pad0(minAdjust) + ':' + pad0(sec) : pad0(min) + ':' + pad0(sec)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
|
||||||
|
.aplayer-controller {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.aplayer-time {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
height: 17px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 11px;
|
||||||
|
padding-left: 7px;
|
||||||
|
|
||||||
|
.aplayer-volume-wrap {
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
margin-left: 4px;
|
||||||
|
|
||||||
|
&.inactive {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-fill {
|
||||||
|
fill: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.aplayer-fill {
|
||||||
|
fill: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.aplayer-icon-menu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.aplayer-volume-wrap + .aplayer-icon {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.aplayer-time-narrow {
|
||||||
|
.aplayer-icon-mode {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-icon-menu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
27
src/views/api/aplayer/components/aplayer-icon.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<img :src="svg" :style="style" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const requireAssets = require.context('../assets', false, /\.svg$/)
|
||||||
|
const SVGs = requireAssets.keys().reduce((svgs, path) => {
|
||||||
|
const svgFile = requireAssets(path)
|
||||||
|
svgs[path.match(/^.*\/(.+?)\.svg$/)[1]] = svgFile
|
||||||
|
return svgs
|
||||||
|
}, {})
|
||||||
|
export default {
|
||||||
|
props: ['type'],
|
||||||
|
computed: {
|
||||||
|
svg () {
|
||||||
|
return SVGs[this.type] || {}
|
||||||
|
},
|
||||||
|
style () {
|
||||||
|
if (this.type === 'next') {
|
||||||
|
return {
|
||||||
|
transform: 'rotate(180deg)',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
45
src/views/api/aplayer/components/aplayer-iconbutton.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="aplayer-icon"
|
||||||
|
>
|
||||||
|
<icon :type="icon"/>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Icon from './aplayer-icon.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Icon,
|
||||||
|
},
|
||||||
|
props: ['icon'],
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.aplayer-icon {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: .8;
|
||||||
|
vertical-align: middle;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 0;
|
||||||
|
display: inline;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-fill {
|
||||||
|
transition: all .2s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
166
src/views/api/aplayer/components/aplayer-list.vue
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="slide-v">
|
||||||
|
<div
|
||||||
|
class="aplayer-list"
|
||||||
|
:style="listHeightStyle"
|
||||||
|
ref="list"
|
||||||
|
v-show="show"
|
||||||
|
>
|
||||||
|
<ol
|
||||||
|
ref="ol"
|
||||||
|
:style="listHeightStyle"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="(aMusic, index) of musicList"
|
||||||
|
:key="index"
|
||||||
|
:class="{'aplayer-list-light': aMusic === currentMusic}"
|
||||||
|
@click="$emit('selectsong', aMusic)"
|
||||||
|
>
|
||||||
|
<span class="aplayer-list-cur" :style="{background: theme}"></span>
|
||||||
|
<span class="aplayer-list-index">{{ index + 1}}</span>
|
||||||
|
<span class="aplayer-list-title">{{ aMusic.title || 'Untitled' }}</span>
|
||||||
|
<span class="aplayer-list-author">{{ aMusic.artist || aMusic.author || 'Unknown' }}</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
currentMusic: Object,
|
||||||
|
musicList: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
playIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
theme: String,
|
||||||
|
listmaxheight: String,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
listHeightStyle () {
|
||||||
|
return {
|
||||||
|
height: `${33 * this.musicList.length - 1}px`,
|
||||||
|
maxHeight: this.listmaxheight || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
|
||||||
|
.aplayer-list {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.slide-v-enter-active,
|
||||||
|
&.slide-v-leave-active {
|
||||||
|
transition: height 500ms ease;
|
||||||
|
will-change: height;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.slide-v-enter,
|
||||||
|
&.slide-v-leave-to {
|
||||||
|
height: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
li.aplayer-list-light:not(:hover) {
|
||||||
|
background-color: inherit;
|
||||||
|
transition: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:hover) {
|
||||||
|
li.aplayer-list-light {
|
||||||
|
transition: background-color .6s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
position: relative;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
padding: 0 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-top: 1px solid #e9e9e9;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
text-align: start;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #efefef;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.aplayer-list-light {
|
||||||
|
background: #efefef;
|
||||||
|
|
||||||
|
.aplayer-list-cur {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-list-cur {
|
||||||
|
display: none;
|
||||||
|
width: 3px;
|
||||||
|
height: 22px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 5px;
|
||||||
|
transition: background-color .3s;
|
||||||
|
}
|
||||||
|
.aplayer-list-index {
|
||||||
|
color: #666;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
.aplayer-list-title {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.aplayer-list-author {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #666;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
164
src/views/api/aplayer/components/aplayer-lrc.vue
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
<template>
|
||||||
|
<div class="aplayer-lrc">
|
||||||
|
<div
|
||||||
|
class="aplayer-lrc-contents"
|
||||||
|
:style="transformStyle"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
v-for="(line, index) of lrcLines"
|
||||||
|
:key="index"
|
||||||
|
:class="{ 'aplayer-lrc-current': index === currentLineIndex }"
|
||||||
|
>
|
||||||
|
{{ line[1] }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {parseLrc} from '../utils'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
currentMusic: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
playStat: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
displayLrc: '',
|
||||||
|
currentLineIndex: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
lrcLines () {
|
||||||
|
return parseLrc(this.displayLrc)
|
||||||
|
},
|
||||||
|
currentLine () {
|
||||||
|
if (this.currentLineIndex > this.lrcLines.length - 1) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return this.lrcLines[this.currentLineIndex]
|
||||||
|
},
|
||||||
|
transformStyle () {
|
||||||
|
// transform: translateY(0); -webkit-transform: translateY(0);
|
||||||
|
return {
|
||||||
|
transform: `translateY(${-this.currentLineIndex * 16}px)`,
|
||||||
|
webkitTransform: `translateY(${-this.currentLineIndex * 16}px)`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
applyLrc (lrc) {
|
||||||
|
if (/^https?:\/\//.test(lrc)) {
|
||||||
|
this.fetchLrc(lrc)
|
||||||
|
} else {
|
||||||
|
this.displayLrc = lrc
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchLrc (src) {
|
||||||
|
fetch(src)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then((lrc) => {
|
||||||
|
this.displayLrc = lrc
|
||||||
|
})
|
||||||
|
},
|
||||||
|
hideLrc () {
|
||||||
|
this.displayLrc = ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
currentMusic: {
|
||||||
|
immediate: true,
|
||||||
|
handler (music) {
|
||||||
|
this.currentLineIndex = 0
|
||||||
|
if (music.lrc) {
|
||||||
|
this.applyLrc(music.lrc)
|
||||||
|
} else {
|
||||||
|
this.hideLrc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'playStat.playedTime' (playedTime) {
|
||||||
|
for (let i = 0; i < this.lrcLines.length; i++) {
|
||||||
|
const line = this.lrcLines[i]
|
||||||
|
const nextLine = this.lrcLines[i + 1]
|
||||||
|
if (playedTime >= line[0] && (!nextLine || playedTime < nextLine[0])) {
|
||||||
|
this.currentLineIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
@import "../less/variables";
|
||||||
|
|
||||||
|
.aplayer-lrc {
|
||||||
|
position: relative;
|
||||||
|
height: @lrc-height;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 10%;
|
||||||
|
content: ' ';
|
||||||
|
background: -moz-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
|
||||||
|
background: -webkit-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
|
||||||
|
background: linear-gradient(to bottom, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#00ffffff', GradientType=0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 33%;
|
||||||
|
content: ' ';
|
||||||
|
background: -moz-linear-gradient(top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 100%);
|
||||||
|
background: -webkit-linear-gradient(top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 100%);
|
||||||
|
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 100%);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffffff', endColorstr='#ccffffff', GradientType=0);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 16px;
|
||||||
|
height: 16px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
transition: all 0.5s ease-out;
|
||||||
|
opacity: 0.4;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.aplayer-lrc-current {
|
||||||
|
opacity: 1;
|
||||||
|
overflow: visible;
|
||||||
|
height: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-lrc-contents {
|
||||||
|
width: 100%;
|
||||||
|
transition: all 0.5s ease-out;
|
||||||
|
user-select: text;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
151
src/views/api/aplayer/components/aplayer-thumbnail.vue
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="aplayer-pic"
|
||||||
|
:style="currentPicStyleObj"
|
||||||
|
@mousedown="onDragBegin"
|
||||||
|
@click="onClick"
|
||||||
|
>
|
||||||
|
<div class="aplayer-button" :class="playing ? 'aplayer-pause' : 'aplayer-play'">
|
||||||
|
<icon-button
|
||||||
|
:icon="playing ? 'pause' : 'play'"
|
||||||
|
:class="playing ? 'aplayer-icon-pause' : 'aplayer-icon-play'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import IconButton from './aplayer-iconbutton.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
IconButton,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
pic: String,
|
||||||
|
theme: String,
|
||||||
|
playing: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
enableDrag: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
hasMovedSinceMouseDown: false,
|
||||||
|
dragStartX: 0,
|
||||||
|
dragStartY: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentPicStyleObj () {
|
||||||
|
if (!this.pic) return {}
|
||||||
|
return {
|
||||||
|
backgroundImage: `url(${this.pic})`,
|
||||||
|
backgroundColor: this.theme
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onDragBegin (e) {
|
||||||
|
if (this.enableDrag) {
|
||||||
|
this.hasMovedSinceMouseDown = false
|
||||||
|
this.$emit('dragbegin')
|
||||||
|
this.dragStartX = e.clientX
|
||||||
|
this.dragStartY = e.clientY
|
||||||
|
document.addEventListener('mousemove', this.onDocumentMouseMove)
|
||||||
|
document.addEventListener('mouseup', this.onDocumentMouseUp)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDocumentMouseMove (e) {
|
||||||
|
this.hasMovedSinceMouseDown = true
|
||||||
|
this.$emit('dragging', {offsetLeft: e.clientX - this.dragStartX, offsetTop: e.clientY - this.dragStartY})
|
||||||
|
},
|
||||||
|
onDocumentMouseUp (e) {
|
||||||
|
document.removeEventListener('mouseup', this.onDocumentMouseUp)
|
||||||
|
document.removeEventListener('mousemove', this.onDocumentMouseMove)
|
||||||
|
|
||||||
|
this.$emit('dragend')
|
||||||
|
},
|
||||||
|
onClick () {
|
||||||
|
if (!this.hasMovedSinceMouseDown) {
|
||||||
|
this.$emit('toggleplay')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
@import "../less/variables";
|
||||||
|
|
||||||
|
.aplayer-float {
|
||||||
|
.aplayer-pic:active {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-pic {
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
height: @aplayer-height;
|
||||||
|
width: @aplayer-height;
|
||||||
|
background-image: url(../default.jpg);
|
||||||
|
background-size: cover;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
.aplayer-button {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.aplayer-button {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
opacity: 0.8;
|
||||||
|
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||||
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
transition: all 0.1s ease;
|
||||||
|
|
||||||
|
.aplayer-fill {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-play {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
bottom: 50%;
|
||||||
|
right: 50%;
|
||||||
|
margin: 0 -15px -15px 0;
|
||||||
|
.aplayer-icon-play {
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: 4px;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-pause {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
bottom: 4px;
|
||||||
|
right: 4px;
|
||||||
|
.aplayer-icon-pause {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
height: 12px;
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
BIN
src/views/api/aplayer/default.jpg
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
3
src/views/api/aplayer/less/variables.less
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@aplayer-height: 66px;
|
||||||
|
@lrc-height: 30px;
|
||||||
|
@aplayer-height-lrc: @aplayer-height + @lrc-height - 6;
|
||||||
79
src/views/api/aplayer/utils.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Parse lrc, suppose multiple time tag
|
||||||
|
* @see https://github.com/MoePlayer/APlayer/blob/master/src/js/lrc.js#L83
|
||||||
|
* @author DIYgod(https://github.com/DIYgod)
|
||||||
|
*
|
||||||
|
* @param {String} lrc_s - Format:
|
||||||
|
* [mm:ss]lyric
|
||||||
|
* [mm:ss.xx]lyric
|
||||||
|
* [mm:ss.xxx]lyric
|
||||||
|
* [mm:ss.xx][mm:ss.xx][mm:ss.xx]lyric
|
||||||
|
* [mm:ss.xx]<mm:ss.xx>lyric
|
||||||
|
*
|
||||||
|
* @return {String} [[time, text], [time, text], [time, text], ...]
|
||||||
|
*/
|
||||||
|
export function parseLrc (lrc_s) {
|
||||||
|
if (lrc_s) {
|
||||||
|
lrc_s = lrc_s.replace(/([^\]^\n])\[/g, (match, p1) => p1 + '\n[')
|
||||||
|
const lyric = lrc_s.split('\n')
|
||||||
|
const lrc = []
|
||||||
|
const lyricLen = lyric.length
|
||||||
|
for (let i = 0; i < lyricLen; i++) {
|
||||||
|
// match lrc time
|
||||||
|
const lrcTimes = lyric[i].match(/\[(\d{2}):(\d{2})(\.(\d{2,3}))?]/g)
|
||||||
|
// match lrc text
|
||||||
|
const lrcText = lyric[i].replace(/.*\[(\d{2}):(\d{2})(\.(\d{2,3}))?]/g, '').replace(/<(\d{2}):(\d{2})(\.(\d{2,3}))?>/g, '').replace(/^\s+|\s+$/g, '')
|
||||||
|
|
||||||
|
if (lrcTimes) {
|
||||||
|
// handle multiple time tag
|
||||||
|
const timeLen = lrcTimes.length
|
||||||
|
for (let j = 0; j < timeLen; j++) {
|
||||||
|
const oneTime = /\[(\d{2}):(\d{2})(\.(\d{2,3}))?]/.exec(lrcTimes[j])
|
||||||
|
const min2sec = oneTime[1] * 60
|
||||||
|
const sec2sec = parseInt(oneTime[2])
|
||||||
|
const msec2sec = oneTime[4] ? parseInt(oneTime[4]) / ((oneTime[4] + '').length === 2 ? 100 : 1000) : 0
|
||||||
|
const lrcTime = min2sec + sec2sec + msec2sec
|
||||||
|
lrc.push([lrcTime, lrcText])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// sort by time
|
||||||
|
lrc.sort((a, b) => a[0] - b[0])
|
||||||
|
return lrc
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function warn (message) {
|
||||||
|
return console.warn(`[Vue-APlayer] ${message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deprecatedProp (name, sinceVersion, alternative) {
|
||||||
|
return warn(`'${name}' is deprecated since v${sinceVersion}, and will be removed in future releases, use '${alternative}' instead`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getElementViewLeft (element) {
|
||||||
|
let actualLeft = element.offsetLeft
|
||||||
|
let current = element.offsetParent
|
||||||
|
let elementScrollLeft
|
||||||
|
while (current !== null) {
|
||||||
|
actualLeft += current.offsetLeft
|
||||||
|
current = current.offsetParent
|
||||||
|
}
|
||||||
|
elementScrollLeft = document.body.scrollLeft + document.documentElement.scrollLeft
|
||||||
|
return actualLeft - elementScrollLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getElementViewTop (element) {
|
||||||
|
let actualTop = element.offsetTop
|
||||||
|
let current = element.offsetParent
|
||||||
|
let elementScrollTop
|
||||||
|
while (current !== null) {
|
||||||
|
actualTop += current.offsetTop
|
||||||
|
current = current.offsetParent
|
||||||
|
}
|
||||||
|
elementScrollTop = document.body.scrollTop + document.documentElement.scrollTop
|
||||||
|
return actualTop - elementScrollTop
|
||||||
|
}
|
||||||
984
src/views/api/aplayer/vue-aplayer.vue
Normal file
@ -0,0 +1,984 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="aplayer"
|
||||||
|
:class="{
|
||||||
|
'aplayer-narrow': isMiniMode,
|
||||||
|
'aplayer-withlist' : !isMiniMode && musicList.length > 0,
|
||||||
|
'aplayer-withlrc': !isMiniMode && (!!$slots.display || shouldShowLrc),
|
||||||
|
'aplayer-float': isFloatMode,
|
||||||
|
'aplayer-loading': isPlaying && isLoading
|
||||||
|
}"
|
||||||
|
:style="floatStyleObj"
|
||||||
|
>
|
||||||
|
<div class="aplayer-body">
|
||||||
|
<thumbnail
|
||||||
|
:pic="currentMusic.pic"
|
||||||
|
:playing="isPlaying"
|
||||||
|
:enable-drag="isFloatMode"
|
||||||
|
:theme="currentTheme"
|
||||||
|
@toggleplay="toggle"
|
||||||
|
@dragbegin="onDragBegin"
|
||||||
|
@dragging="onDragAround"
|
||||||
|
/>
|
||||||
|
<div class="aplayer-info" v-show="!isMiniMode">
|
||||||
|
<div class="aplayer-music">
|
||||||
|
<span class="aplayer-title">{{ currentMusic.title || 'Untitled' }}</span>
|
||||||
|
<span class="aplayer-author">{{ currentMusic.artist || currentMusic.author || 'Unknown' }}</span>
|
||||||
|
</div>
|
||||||
|
<slot name="display" :current-music="currentMusic" :play-stat="playStat">
|
||||||
|
<lyrics :current-music="currentMusic" :play-stat="playStat" v-if="shouldShowLrc" />
|
||||||
|
</slot>
|
||||||
|
<controls
|
||||||
|
:shuffle="shouldShuffle"
|
||||||
|
:repeat="repeatMode"
|
||||||
|
:stat="playStat"
|
||||||
|
:volume="audioVolume"
|
||||||
|
:muted="isAudioMuted"
|
||||||
|
:theme="currentTheme"
|
||||||
|
@toggleshuffle="shouldShuffle = !shouldShuffle"
|
||||||
|
@togglelist="showList = !showList"
|
||||||
|
@togglemute="toggleMute"
|
||||||
|
@setvolume="setAudioVolume"
|
||||||
|
@dragbegin="onProgressDragBegin"
|
||||||
|
@dragend="onProgressDragEnd"
|
||||||
|
@dragging="onProgressDragging"
|
||||||
|
@nextmode="setNextMode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<audio ref="audio"></audio>
|
||||||
|
<music-list
|
||||||
|
:show="showList && !isMiniMode"
|
||||||
|
:current-music="currentMusic"
|
||||||
|
:music-list="musicList"
|
||||||
|
:play-index="playIndex"
|
||||||
|
:listmaxheight="listmaxheight || listMaxHeight"
|
||||||
|
:theme="currentTheme"
|
||||||
|
@selectsong="onSelectSong"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Thumbnail from './components/aplayer-thumbnail.vue'
|
||||||
|
import MusicList from './components/aplayer-list.vue'
|
||||||
|
import Controls from './components/aplayer-controller.vue'
|
||||||
|
import Lyrics from './components/aplayer-lrc.vue'
|
||||||
|
import { deprecatedProp, warn } from './utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* memorize self-adapting theme for cover image urls
|
||||||
|
* @type {Object.<url, rgb()>}
|
||||||
|
*/
|
||||||
|
const picThemeCache = {}
|
||||||
|
|
||||||
|
// mutex playing instance
|
||||||
|
let activeMutex = null
|
||||||
|
|
||||||
|
|
||||||
|
const REPEAT = {
|
||||||
|
NONE: 'none',
|
||||||
|
MUSIC: 'music',
|
||||||
|
LIST: 'list',
|
||||||
|
NO_REPEAT: 'no_repeat',
|
||||||
|
REPEAT_ONE: 'repeat_one',
|
||||||
|
REPEAT_ALL: 'repeat_all',
|
||||||
|
};
|
||||||
|
|
||||||
|
const VueAPlayer = {
|
||||||
|
name: 'APlayer',
|
||||||
|
disableVersionBadge: false,
|
||||||
|
components: {
|
||||||
|
Thumbnail,
|
||||||
|
Controls,
|
||||||
|
MusicList,
|
||||||
|
Lyrics,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
music: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
validator (song) {
|
||||||
|
if (song.url) {
|
||||||
|
deprecatedProp('music.url', '1.4.0', 'music.src')
|
||||||
|
}
|
||||||
|
if (song.author) {
|
||||||
|
deprecatedProp('music.author', '1.4.1', 'music.artist')
|
||||||
|
}
|
||||||
|
return song.src || song.url
|
||||||
|
},
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mini: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
showLrc: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
mutex: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
type: String,
|
||||||
|
default: '#41b883',
|
||||||
|
},
|
||||||
|
|
||||||
|
listMaxHeight: String,
|
||||||
|
/**
|
||||||
|
* @since 1.4.1
|
||||||
|
* Fold playlist initially
|
||||||
|
*/
|
||||||
|
listFolded: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.2.0 Float mode
|
||||||
|
*/
|
||||||
|
float: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Audio attributes as props
|
||||||
|
// since 1.4.0
|
||||||
|
// autoplay controls muted preload volume
|
||||||
|
// autoplay is not observable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.4.0
|
||||||
|
* not observable
|
||||||
|
*/
|
||||||
|
autoplay: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.4.0
|
||||||
|
* whether to show native audio controls below Vue-APlayer
|
||||||
|
* only work in development environment and not mini mode
|
||||||
|
*
|
||||||
|
* observable
|
||||||
|
*/
|
||||||
|
controls: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.4.0
|
||||||
|
* observable, sync
|
||||||
|
*/
|
||||||
|
muted: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @since 1.4.0
|
||||||
|
* observable
|
||||||
|
*/
|
||||||
|
preload: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.4.0
|
||||||
|
* observable, sync
|
||||||
|
*/
|
||||||
|
volume: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.8,
|
||||||
|
validator (value) {
|
||||||
|
return value >= 0 && value <= 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// play order control
|
||||||
|
// since 1.5.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 1.5.0
|
||||||
|
* @see https://support.apple.com/en-us/HT207230
|
||||||
|
* twoWay
|
||||||
|
*/
|
||||||
|
shuffle: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @since 1.5.0
|
||||||
|
* @see https://support.apple.com/en-us/HT207230
|
||||||
|
* twoWay
|
||||||
|
*/
|
||||||
|
repeat: {
|
||||||
|
type: String,
|
||||||
|
default: REPEAT.NO_REPEAT,
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// deprecated props
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated since 1.1.2, use listMaxHeight instead
|
||||||
|
*/
|
||||||
|
listmaxheight: {
|
||||||
|
type: String,
|
||||||
|
validator (value) {
|
||||||
|
if (value) {
|
||||||
|
deprecatedProp('listmaxheight', '1.1.2', 'listMaxHeight')
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @deprecated since 1.1.2, use mini instead
|
||||||
|
*/
|
||||||
|
narrow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
validator (value) {
|
||||||
|
if (value) {
|
||||||
|
deprecatedProp('narrow', '1.1.2', 'mini')
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @deprecated since 1.2.2
|
||||||
|
*/
|
||||||
|
showlrc: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
validator (value) {
|
||||||
|
if (value) {
|
||||||
|
deprecatedProp('showlrc', '1.2.2', 'showLrc')
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @deprecated and REMOVED since 1.5.0
|
||||||
|
*/
|
||||||
|
// mode: {
|
||||||
|
// type: String,
|
||||||
|
// default: 'circulation',
|
||||||
|
// validator (value) {
|
||||||
|
// if (value) {
|
||||||
|
// deprecatedProp('mode', '1.5.0', 'shuffle and repeat')
|
||||||
|
// }
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
internalMusic: this.music,
|
||||||
|
isPlaying: false,
|
||||||
|
isSeeking: false,
|
||||||
|
wasPlayingBeforeSeeking: false,
|
||||||
|
isMobile: /mobile/i.test(window.navigator.userAgent),
|
||||||
|
playStat: {
|
||||||
|
duration: 0,
|
||||||
|
loadedTime: 0,
|
||||||
|
playedTime: 0,
|
||||||
|
},
|
||||||
|
showList: !this.listFolded,
|
||||||
|
|
||||||
|
// handle Promise returned from audio.play()
|
||||||
|
// @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play
|
||||||
|
audioPlayPromise: Promise.resolve(),
|
||||||
|
|
||||||
|
|
||||||
|
// @since 1.2.0 float mode
|
||||||
|
|
||||||
|
floatOriginX: 0,
|
||||||
|
floatOriginY: 0,
|
||||||
|
floatOffsetLeft: 0,
|
||||||
|
floatOffsetTop: 0,
|
||||||
|
|
||||||
|
|
||||||
|
// @since 1.3.0 self adapting theme
|
||||||
|
selfAdaptingTheme: null,
|
||||||
|
|
||||||
|
|
||||||
|
// @since 1.4.0
|
||||||
|
// sync muted, volume
|
||||||
|
|
||||||
|
internalMuted: this.muted,
|
||||||
|
internalVolume: this.volume,
|
||||||
|
|
||||||
|
// @since 1.4.1
|
||||||
|
// Loading indicator
|
||||||
|
isLoading: false,
|
||||||
|
|
||||||
|
|
||||||
|
// @since 1.5.1
|
||||||
|
// sync shuffle, repeat
|
||||||
|
internalShuffle: this.shuffle,
|
||||||
|
internalRepeat: this.repeat,
|
||||||
|
// for shuffling
|
||||||
|
shuffledList: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// alias for $refs.audio
|
||||||
|
audio () {
|
||||||
|
return this.$refs.audio
|
||||||
|
},
|
||||||
|
|
||||||
|
// sync music
|
||||||
|
currentMusic: {
|
||||||
|
get () {
|
||||||
|
return this.internalMusic
|
||||||
|
},
|
||||||
|
set (val) {
|
||||||
|
this.$emit('update:music', val)
|
||||||
|
this.internalMusic = val
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// compatible for deprecated props
|
||||||
|
isMiniMode () {
|
||||||
|
return this.mini || this.narrow
|
||||||
|
},
|
||||||
|
shouldShowLrc () {
|
||||||
|
return this.showLrc || this.showlrc
|
||||||
|
},
|
||||||
|
|
||||||
|
// props wrappers
|
||||||
|
|
||||||
|
currentTheme () {
|
||||||
|
return this.selfAdaptingTheme || this.currentMusic.theme || this.theme
|
||||||
|
},
|
||||||
|
isFloatMode () {
|
||||||
|
return this.float && !this.isMobile
|
||||||
|
},
|
||||||
|
shouldAutoplay () {
|
||||||
|
if (this.isMobile) return false
|
||||||
|
return this.autoplay
|
||||||
|
},
|
||||||
|
musicList () {
|
||||||
|
return this.list
|
||||||
|
},
|
||||||
|
shouldShowNativeControls () {
|
||||||
|
return process.env.NODE_ENV !== 'production' &&
|
||||||
|
this.controls &&
|
||||||
|
!this.isMiniMode
|
||||||
|
},
|
||||||
|
|
||||||
|
// useful
|
||||||
|
|
||||||
|
floatStyleObj () {
|
||||||
|
// transform: translate(floatOffsetLeft, floatOffsetY)
|
||||||
|
return {
|
||||||
|
transform: `translate(${this.floatOffsetLeft}px, ${this.floatOffsetTop}px)`,
|
||||||
|
webkitTransform: `translate(${this.floatOffsetLeft}px, ${this.floatOffsetTop}px)`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currentPicStyleObj () {
|
||||||
|
if (this.currentMusic && this.currentMusic.pic) {
|
||||||
|
return {
|
||||||
|
backgroundImage: `url(${this.currentMusic.pic})`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
loadProgress () {
|
||||||
|
if (this.playStat.duration === 0) return 0
|
||||||
|
return this.playStat.loadedTime / this.playStat.duration
|
||||||
|
},
|
||||||
|
playProgress () {
|
||||||
|
if (this.playStat.duration === 0) return 0
|
||||||
|
return this.playStat.playedTime / this.playStat.duration
|
||||||
|
},
|
||||||
|
playIndex: {
|
||||||
|
get () {
|
||||||
|
return this.shuffledList.indexOf(this.currentMusic)
|
||||||
|
},
|
||||||
|
set (val) {
|
||||||
|
this.currentMusic = this.shuffledList[val % this.shuffledList.length]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldRepeat () {
|
||||||
|
return this.repeatMode !== REPEAT.NO_REPEAT
|
||||||
|
},
|
||||||
|
|
||||||
|
// since 1.4.0
|
||||||
|
// sync muted, volume
|
||||||
|
|
||||||
|
isAudioMuted: {
|
||||||
|
get () {
|
||||||
|
return this.internalMuted
|
||||||
|
},
|
||||||
|
set (val) {
|
||||||
|
this.$emit('update:muted', val)
|
||||||
|
this.internalMuted = val
|
||||||
|
},
|
||||||
|
},
|
||||||
|
audioVolume: {
|
||||||
|
get () {
|
||||||
|
return this.internalVolume
|
||||||
|
},
|
||||||
|
set (val) {
|
||||||
|
this.$emit('update:volume', val)
|
||||||
|
this.internalVolume = val
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// since 1.5.0
|
||||||
|
// sync shuffle, repeat
|
||||||
|
shouldShuffle: {
|
||||||
|
get () {
|
||||||
|
return this.internalShuffle
|
||||||
|
},
|
||||||
|
set (val) {
|
||||||
|
this.$emit('update:shuffle', val)
|
||||||
|
this.internalShuffle = val
|
||||||
|
},
|
||||||
|
},
|
||||||
|
repeatMode: {
|
||||||
|
get () {
|
||||||
|
switch (this.internalRepeat) {
|
||||||
|
case REPEAT.NONE:
|
||||||
|
case REPEAT.NO_REPEAT:
|
||||||
|
return REPEAT.NO_REPEAT
|
||||||
|
case REPEAT.MUSIC:
|
||||||
|
case REPEAT.REPEAT_ONE:
|
||||||
|
return REPEAT.REPEAT_ONE
|
||||||
|
default:
|
||||||
|
return REPEAT.REPEAT_ALL
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set (val) {
|
||||||
|
this.$emit('update:repeat', val)
|
||||||
|
this.internalRepeat = val
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// Float mode
|
||||||
|
|
||||||
|
onDragBegin () {
|
||||||
|
this.floatOriginX = this.floatOffsetLeft
|
||||||
|
this.floatOriginY = this.floatOffsetTop
|
||||||
|
},
|
||||||
|
onDragAround ({ offsetLeft, offsetTop }) {
|
||||||
|
this.floatOffsetLeft = this.floatOriginX + offsetLeft
|
||||||
|
this.floatOffsetTop = this.floatOriginY + offsetTop
|
||||||
|
},
|
||||||
|
|
||||||
|
// functions
|
||||||
|
|
||||||
|
setNextMode () {
|
||||||
|
if (this.repeatMode === REPEAT.REPEAT_ALL) {
|
||||||
|
this.repeatMode = REPEAT.REPEAT_ONE
|
||||||
|
} else if (this.repeatMode === REPEAT.REPEAT_ONE) {
|
||||||
|
this.repeatMode = REPEAT.NO_REPEAT
|
||||||
|
} else {
|
||||||
|
this.repeatMode = REPEAT.REPEAT_ALL
|
||||||
|
}
|
||||||
|
},
|
||||||
|
thenPlay () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.play()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// controls
|
||||||
|
|
||||||
|
// play/pause
|
||||||
|
|
||||||
|
toggle () {
|
||||||
|
if (!this.audio.paused) {
|
||||||
|
this.pause()
|
||||||
|
} else {
|
||||||
|
this.play()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
play () {
|
||||||
|
if (this.mutex) {
|
||||||
|
if (activeMutex && activeMutex !== this) {
|
||||||
|
activeMutex.pause()
|
||||||
|
}
|
||||||
|
activeMutex = this
|
||||||
|
}
|
||||||
|
// handle .play() Promise
|
||||||
|
const audioPlayPromise = this.audio.play()
|
||||||
|
if (audioPlayPromise) {
|
||||||
|
return this.audioPlayPromise = new Promise((resolve, reject) => {
|
||||||
|
// rejectPlayPromise is to force reject audioPlayPromise if it's still pending when pause() is called
|
||||||
|
this.rejectPlayPromise = reject
|
||||||
|
audioPlayPromise.then((res) => {
|
||||||
|
this.rejectPlayPromise = null
|
||||||
|
resolve(res)
|
||||||
|
}).catch(warn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pause () {
|
||||||
|
this.audioPlayPromise
|
||||||
|
.then(() => {
|
||||||
|
this.audio.pause()
|
||||||
|
})
|
||||||
|
// Avoid force rejection throws Uncaught
|
||||||
|
.catch(() => {
|
||||||
|
this.audio.pause()
|
||||||
|
})
|
||||||
|
|
||||||
|
// audioPlayPromise is still pending
|
||||||
|
if (this.rejectPlayPromise) {
|
||||||
|
// force reject playPromise
|
||||||
|
this.rejectPlayPromise()
|
||||||
|
this.rejectPlayPromise = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// progress bar
|
||||||
|
|
||||||
|
onProgressDragBegin (val) {
|
||||||
|
this.wasPlayingBeforeSeeking = this.isPlaying
|
||||||
|
this.pause()
|
||||||
|
this.isSeeking = true
|
||||||
|
|
||||||
|
// handle load failures
|
||||||
|
if (!isNaN(this.audio.duration)) {
|
||||||
|
this.audio.currentTime = this.audio.duration * val
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onProgressDragging (val) {
|
||||||
|
if (isNaN(this.audio.duration)) {
|
||||||
|
this.playStat.playedTime = 0
|
||||||
|
} else {
|
||||||
|
this.audio.currentTime = this.audio.duration * val
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onProgressDragEnd (val) {
|
||||||
|
this.isSeeking = false
|
||||||
|
|
||||||
|
if (this.wasPlayingBeforeSeeking) {
|
||||||
|
this.thenPlay()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// volume
|
||||||
|
|
||||||
|
toggleMute () {
|
||||||
|
this.setAudioMuted(!this.audio.muted)
|
||||||
|
},
|
||||||
|
setAudioMuted (val) {
|
||||||
|
this.audio.muted = val
|
||||||
|
},
|
||||||
|
setAudioVolume (val) {
|
||||||
|
this.audio.volume = val
|
||||||
|
if (val > 0) {
|
||||||
|
this.setAudioMuted(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// playlist
|
||||||
|
|
||||||
|
getShuffledList () {
|
||||||
|
if (!this.list.length) {
|
||||||
|
return [this.internalMusic]
|
||||||
|
}
|
||||||
|
let unshuffled = [...this.list]
|
||||||
|
if (!this.internalShuffle || unshuffled.length <= 1) {
|
||||||
|
return unshuffled
|
||||||
|
}
|
||||||
|
|
||||||
|
let indexOfCurrentMusic = unshuffled.indexOf(this.internalMusic)
|
||||||
|
if (unshuffled.length === 2 && indexOfCurrentMusic !== -1) {
|
||||||
|
if (indexOfCurrentMusic === 0) {
|
||||||
|
return unshuffled
|
||||||
|
} else {
|
||||||
|
return [this.internalMusic, unshuffled[0]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// shuffle list
|
||||||
|
// @see https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array
|
||||||
|
for (let i = unshuffled.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1))
|
||||||
|
const tmp = unshuffled[i]
|
||||||
|
unshuffled[i] = unshuffled[j]
|
||||||
|
unshuffled[j] = tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
// take currentMusic to first
|
||||||
|
if (indexOfCurrentMusic !== -1) {
|
||||||
|
indexOfCurrentMusic = unshuffled.indexOf(this.internalMusic)
|
||||||
|
if (indexOfCurrentMusic !== 0) {
|
||||||
|
[unshuffled[0], unshuffled[indexOfCurrentMusic]] = [unshuffled[indexOfCurrentMusic], unshuffled[0]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unshuffled
|
||||||
|
},
|
||||||
|
|
||||||
|
onSelectSong (song) {
|
||||||
|
if (this.currentMusic === song) {
|
||||||
|
this.toggle()
|
||||||
|
} else {
|
||||||
|
this.currentMusic = song
|
||||||
|
this.thenPlay()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// event handlers
|
||||||
|
// for keeping up with audio states
|
||||||
|
|
||||||
|
onAudioPlay () {
|
||||||
|
this.isPlaying = true
|
||||||
|
},
|
||||||
|
onAudioPause () {
|
||||||
|
this.isPlaying = false
|
||||||
|
},
|
||||||
|
onAudioWaiting () {
|
||||||
|
this.isLoading = true
|
||||||
|
},
|
||||||
|
onAudioCanplay () {
|
||||||
|
this.isLoading = false
|
||||||
|
},
|
||||||
|
onAudioDurationChange () {
|
||||||
|
if (this.audio.duration !== 1) {
|
||||||
|
this.playStat.duration = this.audio.duration
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onAudioProgress () {
|
||||||
|
if (this.audio.buffered.length) {
|
||||||
|
this.playStat.loadedTime = this.audio.buffered.end(this.audio.buffered.length - 1)
|
||||||
|
} else {
|
||||||
|
this.playStat.loadedTime = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onAudioTimeUpdate () {
|
||||||
|
this.playStat.playedTime = this.audio.currentTime
|
||||||
|
},
|
||||||
|
onAudioSeeking () {
|
||||||
|
this.playStat.playedTime = this.audio.currentTime
|
||||||
|
},
|
||||||
|
onAudioSeeked () {
|
||||||
|
this.playStat.playedTime = this.audio.currentTime
|
||||||
|
},
|
||||||
|
onAudioVolumeChange () {
|
||||||
|
this.audioVolume = this.audio.volume
|
||||||
|
this.isAudioMuted = this.audio.muted
|
||||||
|
},
|
||||||
|
onAudioEnded () {
|
||||||
|
// determine next song according to shuffle and repeat
|
||||||
|
if (this.repeatMode === REPEAT.REPEAT_ALL) {
|
||||||
|
if (this.shouldShuffle && this.playIndex === this.shuffledList.length - 1) {
|
||||||
|
this.shuffledList = this.getShuffledList()
|
||||||
|
}
|
||||||
|
this.playIndex++
|
||||||
|
this.thenPlay()
|
||||||
|
} else if (this.repeatMode === REPEAT.REPEAT_ONE) {
|
||||||
|
this.thenPlay()
|
||||||
|
} else {
|
||||||
|
this.playIndex++
|
||||||
|
if (this.playIndex !== 0) {
|
||||||
|
this.thenPlay()
|
||||||
|
} else if (this.shuffledList.length === 1) {
|
||||||
|
this.audio.currentTime = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
initAudio () {
|
||||||
|
|
||||||
|
// since 1.4.0 Audio attributes as props
|
||||||
|
|
||||||
|
this.audio.controls = this.shouldShowNativeControls
|
||||||
|
this.audio.muted = this.muted
|
||||||
|
this.audio.preload = this.preload
|
||||||
|
this.audio.volume = this.volume
|
||||||
|
|
||||||
|
|
||||||
|
// since 1.4.0 Emit as many native audio events
|
||||||
|
// @see https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Media_events
|
||||||
|
|
||||||
|
const mediaEvents = [
|
||||||
|
'abort',
|
||||||
|
'canplay', 'canplaythrough',
|
||||||
|
'durationchange',
|
||||||
|
'emptied', 'encrypted', 'ended', 'error',
|
||||||
|
'interruptbegin', 'interruptend',
|
||||||
|
'loadeddata', 'loadedmetadata', 'loadstart',
|
||||||
|
'mozaudioavailable',
|
||||||
|
'pause', 'play', 'playing', 'progress',
|
||||||
|
'ratechange',
|
||||||
|
'seeked', 'seeking', 'stalled', 'suspend',
|
||||||
|
'timeupdate',
|
||||||
|
'volumechange',
|
||||||
|
'waiting',
|
||||||
|
]
|
||||||
|
mediaEvents.forEach(event => {
|
||||||
|
this.audio.addEventListener(event, e => this.$emit(event, e))
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// event handlers
|
||||||
|
// they don't emit native media events
|
||||||
|
|
||||||
|
this.audio.addEventListener('play', this.onAudioPlay)
|
||||||
|
this.audio.addEventListener('pause', this.onAudioPause)
|
||||||
|
this.audio.addEventListener('abort', this.onAudioPause)
|
||||||
|
this.audio.addEventListener('waiting', this.onAudioWaiting)
|
||||||
|
this.audio.addEventListener('canplay', this.onAudioCanplay)
|
||||||
|
this.audio.addEventListener('progress', this.onAudioProgress)
|
||||||
|
this.audio.addEventListener('durationchange', this.onAudioDurationChange)
|
||||||
|
this.audio.addEventListener('seeking', this.onAudioSeeking)
|
||||||
|
this.audio.addEventListener('seeked', this.onAudioSeeked)
|
||||||
|
this.audio.addEventListener('timeupdate', this.onAudioTimeUpdate)
|
||||||
|
this.audio.addEventListener('volumechange', this.onAudioVolumeChange)
|
||||||
|
this.audio.addEventListener('ended', this.onAudioEnded)
|
||||||
|
|
||||||
|
|
||||||
|
if (this.currentMusic) {
|
||||||
|
this.audio.src = this.currentMusic.src || this.currentMusic.url
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setSelfAdaptingTheme () {
|
||||||
|
// auto theme according to current music cover image
|
||||||
|
if ((this.currentMusic.theme || this.theme) === 'pic') {
|
||||||
|
const pic = this.currentMusic.pic
|
||||||
|
// use cache
|
||||||
|
if (picThemeCache[pic]) {
|
||||||
|
this.selfAdaptingTheme = picThemeCache[pic]
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
new ColorThief().getColorAsync(pic, ([r, g, b]) => {
|
||||||
|
picThemeCache[pic] = `rgb(${r}, ${g}, ${b})`
|
||||||
|
this.selfAdaptingTheme = `rgb(${r}, ${g}, ${b})`
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
warn('color-thief is required to support self-adapting theme')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.selfAdaptingTheme = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
music (music) {
|
||||||
|
this.internalMusic = music
|
||||||
|
},
|
||||||
|
|
||||||
|
currentMusic: {
|
||||||
|
handler (music) {
|
||||||
|
// async
|
||||||
|
this.setSelfAdaptingTheme()
|
||||||
|
|
||||||
|
const src = music.src || music.url
|
||||||
|
// HLS support
|
||||||
|
if (/\.m3u8(?=(#|\?|$))/.test(src)) {
|
||||||
|
if (this.audio.canPlayType('application/x-mpegURL') || this.audio.canPlayType('application/vnd.apple.mpegURL')) {
|
||||||
|
this.audio.src = src
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const Hls = require('hls.js')
|
||||||
|
if (Hls.isSupported()) {
|
||||||
|
if (!this.hls) {
|
||||||
|
this.hls = new Hls()
|
||||||
|
}
|
||||||
|
this.hls.loadSource(src)
|
||||||
|
this.hls.attachMedia(this.audio)
|
||||||
|
} else {
|
||||||
|
warn('HLS is not supported on your browser')
|
||||||
|
this.audio.src = src
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
warn('hls.js is required to support m3u8')
|
||||||
|
this.audio.src = src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.audio.src = src
|
||||||
|
}
|
||||||
|
// self-adapting theme color
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// since 1.4.0
|
||||||
|
// observe controls, muted, preload, volume
|
||||||
|
|
||||||
|
shouldShowNativeControls (val) {
|
||||||
|
this.audio.controls = val
|
||||||
|
},
|
||||||
|
isAudioMuted (val) {
|
||||||
|
this.audio.muted = val
|
||||||
|
},
|
||||||
|
preload (val) {
|
||||||
|
this.audio.preload = val
|
||||||
|
},
|
||||||
|
audioVolume (val) {
|
||||||
|
this.audio.volume = val
|
||||||
|
},
|
||||||
|
|
||||||
|
// sync muted, volume
|
||||||
|
|
||||||
|
muted (val) {
|
||||||
|
this.internalMuted = val
|
||||||
|
},
|
||||||
|
volume (val) {
|
||||||
|
this.internalVolume = val
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// sync shuffle, repeat
|
||||||
|
shuffle (val) {
|
||||||
|
this.internalShuffle = val
|
||||||
|
},
|
||||||
|
repeat (val) {
|
||||||
|
this.internalRepeat = val
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.shuffledList = this.getShuffledList()
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.initAudio()
|
||||||
|
this.setSelfAdaptingTheme()
|
||||||
|
if (this.autoplay) this.play()
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
if (activeMutex === this) {
|
||||||
|
activeMutex = null
|
||||||
|
}
|
||||||
|
if (this.hls) {
|
||||||
|
this.hls.destroy()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VueAPlayer
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
@import "./less/variables";
|
||||||
|
|
||||||
|
.aplayer {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
color: #000;
|
||||||
|
background-color: #fff;
|
||||||
|
margin: 5px;
|
||||||
|
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.07), 0 1px 5px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
|
line-height: initial;
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-lrc-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-body {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
.aplayer-info {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
text-align: start;
|
||||||
|
padding: 14px 7px 0 10px;
|
||||||
|
height: @aplayer-height;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.aplayer-music {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-left: 5px;
|
||||||
|
user-select: text;
|
||||||
|
cursor: default;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
|
||||||
|
.aplayer-title {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-author {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-lrc {
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audio[controls] {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mini mode
|
||||||
|
&.aplayer-narrow {
|
||||||
|
width: @aplayer-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.aplayer-withlrc {
|
||||||
|
.aplayer-body {
|
||||||
|
.aplayer-pic {
|
||||||
|
height: @aplayer-height-lrc;
|
||||||
|
width: @aplayer-height-lrc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-info {
|
||||||
|
height: @aplayer-height-lrc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-info {
|
||||||
|
padding: 10px 7px 0 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.aplayer-withlist {
|
||||||
|
.aplayer-body {
|
||||||
|
.aplayer-info {
|
||||||
|
border-bottom: 1px solid #e9e9e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aplayer-controller .aplayer-time .aplayer-icon.aplayer-icon-menu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* floating player on top */
|
||||||
|
position: relative;
|
||||||
|
&.aplayer-float {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes aplayer-roll {
|
||||||
|
0% {
|
||||||
|
left: 0
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: -100%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -10,7 +10,8 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"lib": ["esnext", "dom"]
|
"lib": ["esnext", "dom"],
|
||||||
|
"allowJs": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,45 +0,0 @@
|
|||||||
import { defineConfig } from 'vite'
|
|
||||||
import path from 'path'
|
|
||||||
import vue from '@vitejs/plugin-vue'
|
|
||||||
import ElementPlus from 'unplugin-element-plus/vite'
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
vue(),
|
|
||||||
ElementPlus(),
|
|
||||||
],
|
|
||||||
base: './',
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': path.resolve(__dirname, "./src"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
port: 8080,
|
|
||||||
proxy: {
|
|
||||||
'^/api': {
|
|
||||||
// target: 'http://localhost:3301'
|
|
||||||
target: 'https://www.colorfulsweet.site',
|
|
||||||
changeOrigin: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
outDir: 'dist',
|
|
||||||
assetsDir: 'assets', // 静态资源的存放路径, 相对于outDir
|
|
||||||
cssCodeSplit: true, // 是否拆分css
|
|
||||||
rollupOptions: {
|
|
||||||
output: {
|
|
||||||
manualChunks(id) {
|
|
||||||
if (id.includes('element-plus')) {
|
|
||||||
return 'element-plus'
|
|
||||||
} else if(id.includes('echarts')) {
|
|
||||||
return 'echarts'
|
|
||||||
} else if(id.includes('node_modules')) {
|
|
||||||
return 'vendor'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
28
vue.config.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const { defineConfig } = require('@vue/cli-service')
|
||||||
|
const { DefinePlugin } = require('webpack')
|
||||||
|
const { execSync } = require('child_process')
|
||||||
|
|
||||||
|
const commitInfo = execSync('git show -s --format=%cs(%h)').toString().trim()
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
publicPath: './',
|
||||||
|
transpileDependencies: true,
|
||||||
|
productionSourceMap: false,
|
||||||
|
configureWebpack: {
|
||||||
|
plugins: [
|
||||||
|
new DefinePlugin({
|
||||||
|
'process.env.VERSION': `'${commitInfo}'`
|
||||||
|
})
|
||||||
|
]
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
port: 8080,
|
||||||
|
proxy: {
|
||||||
|
'^/api': {
|
||||||
|
// target: 'http://localhost:3301'
|
||||||
|
target: 'https://www.colorfulsweet.site',
|
||||||
|
changeOrigin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||