<template>
测试哈哈
<audio
ref="audio"
:src="source"
:autoplay="auto"
@durationchange="durationchange"
@canplay="canplay"
@play="play"
@pause="pause"
@ended="ended"
@error="error"
/>
</template>
<script lang="ts">
import { defineComponent, computed, watch, ref, onMounted } from 'vue'
import { useStore } from 'vuex'
export default defineComponent({
name: 'Player',
setup() {
const store = useStore()
const audio:any = ref(null)
const source = ref('')
const auto = ref(false)
let rand = [0]
let isFirst = true // 当前歌曲是否恢复的数据
const musicList = computed(() => store.state.musicList)
const currMusic = computed(() => store.state.currMusic)
const waitNum = computed(() => store.state.waitNum)
// 播放记录
const records = computed(() => store.state.records)
/* ------Audio------*/
// 播放状态:暂停or播放
const state = computed(() => store.state.audio.state)
// 音量
const volume = computed(() => store.state.audio.volume)
// 播放模式:顺序、循环、随机、单曲
const mode = computed(() => store.state.audio.mode)
// 是否静音
const mute = computed(() => store.state.audio.mute)
// 是否跳到某一时刻播放
const jump = computed(() => store.state.audio.jump)
// 是否切换至上一曲
const isPrev = computed(() => store.state.audio.prev)
// 是否切换至下一曲
const isNext = computed(() => store.state.audio.next)
watch(currMusic, (newVal, oldVal) => {
if (waitNum.value > 0 && newVal.index !== oldVal + 1) {
store.commit('setWaitNum', 0)
}
source.value = `https://music.163.com/song/media/outer/url?id=${currMusic.value?.id}.mp3`
if (isFirst) {
isFirst = false
} else {
auto.value = true
}
setMediaMetadata()
})
watch(state, () => {
if (audio.value) {
if (state.value) {
audio.value.play()
} else {
audio.value.pause()
}
}
})
watch(jump, () => {
if (jump.value >= 0) {
audio.value.currentTime = jump.value
if (audio.value.readyState && audio.value.paused) {
audio.value.play()
}
const param = { prop: 'jump', value: -1 }
store.commit('setAudio', param)
}
})
watch(isPrev, () => {
if (isPrev.value) {
prev()
const param = { prop: 'prev', value: false }
store.commit('setAudio', param)
}
})
watch(isNext, () => {
if (isNext.value) {
next()
const param = { prop: 'next', value: false }
store.commit('setAudio', param)
}
})
watch(volume, () => {
audio.value.volume = volume.value
})
watch(mute, () => {
audio.value.muted = mute.value
})
const durationchange = () => {
const param = { prop: 'duration', value: audio.value.duration }
store.commit('setAudio', param)
}
const progress = () => {
const param = { prop: 'buffered', value: audio.value.buffered.end(audio.value.buffered.length - 1) }
store.commit('setAudio', param)
}
const canplay = () => {
audio.value.volume = store.state.audio.volume
audio.value.ontimeupdate = function() {
const param = { prop: 'currentTime', value: audio.value.currentTime }
store.commit('setAudio', param)
}
}
const play = () => {
console.log('播放')
if (!state.value) {
const param = { prop: 'state', value: true }
store.commit('setAudio', param)
}
setRecords(currMusic.value.id)
}
const pause = () => {
if (state.value) {
const param = { prop: 'state', value: false }
store.commit('setAudio', param)
}
}
const ended = () => {
let index = currMusic.value?.index
if (mode.value <= 1) {
if (index !== musicList.value.length - 1) {
index++
} else if (mode.value === 0) {
index = 0
}
} else if (mode.value === 2) {
index = Math.floor(Math.random() * musicList.value.length)
while (rand.includes(index)) {
index = Math.floor(Math.random() * musicList.value.length)
}
rand.push(index)
if (rand.length === musicList.value.length) {
rand = []
}
} else {
if (audio !== null) {
audio.value.play()
}
}
store.commit('setCurrMusic', musicList.value[index])
}
const error = () => {
next()
}
const prev = () => {
let index = currMusic.value?.index
if (index > 0) {
index--
}
store.commit('setCurrMusic', musicList.value[index])
}
const next = () => {
let index = currMusic.value?.index
if (mode.value === 2 && waitNum.value === 0) {
index = Math.floor(Math.random() * musicList.value.length)
while (rand.includes(index)) {
index = Math.floor(Math.random() * musicList.value.length)
}
rand.push(index)
if (rand.length === musicList.value.length) {
rand = []
}
} else {
if (waitNum.value > 1) {
store.commit('setWaitNum', waitNum.value - 1)
}
if (index !== musicList.value.length - 1) {
index++
} else {
index = 0
}
}
store.commit('setCurrMusic', musicList.value[index])
}
const setRecords = (id:string) => {
const list = records.value
const index = list.indexOf(id)
if (index === -1) {
list.push(id)
} else {
list.splice(index, 1)
list.push(id)
}
store.commit('setRecords', list)
}
const initMediaSession = () => {
const mediaNavigator:any = navigator
if ('mediaSession' in navigator) {
mediaNavigator.mediaSession.setActionHandler('play', () => {
play()
})
mediaNavigator.mediaSession.setActionHandler('pause', () => {
pause()
})
mediaNavigator.mediaSession.setActionHandler('previoustrack', () => {
prev()
})
mediaNavigator.mediaSession.setActionHandler('nexttrack', () => {
next()
})
// navigator.mediaSession.setActionHandler('seekbackward', () => {
// })
// navigator.mediaSession.setActionHandler('seekforward', () => {
// })
}
}
const setMediaMetadata = () => {
if ('mediaSession' in navigator) {
if (currMusic.value?.artist) {
const artists = []
for (const item of currMusic.value.artist) {
artists.push(item.name)
}
const mediaNavigator:any = navigator
mediaNavigator.mediaSession.metadata = new window.MediaMetadata({
title: currMusic.value.name,
artist: artists.join('/'),
album: currMusic.value.album.name,
artwork: [{ src: currMusic.value.album.picUrl }]
})
}
}
}
const monitorKeyboard = () => {
// 全局监听键盘事件
window.onkeypress = (e: { code: string }) => {
if (e && e.code === 'Space') {
const param = { prop: 'state', value: !store.state.audio.state }
store.commit('setAudio', param)
}
}
window.onkeydown = (e: { ctrlKey: any; key: any }) => {
const param = { prop: 'volume', value: store.state.audio.volume }
const volume = Number(param.value.toFixed(1))
if (e && e.ctrlKey) {
switch (e.key) {
case 'ArrowLeft':
prev()
break
case 'ArrowRight':
next()
break
case 'ArrowUp':
if (volume < 1) {
param.value = volume + 0.1
store.commit('setAudio', param)
param.prop = 'mute'
param.value = false
store.commit('setAudio', param)
}
break
case 'ArrowDown':
if (volume > 0) {
param.value = volume - 0.1
store.commit('setAudio', param)
}
break
}
}
}
}
// 关闭页面或刷新前保存数据
const saveState = () => {
localStorage.setItem('musicList', JSON.stringify(store.state.musicList))
const currMusic = store.state.currMusic
if (currMusic) {
localStorage.setItem('currMusic', JSON.stringify(currMusic))
}
localStorage.setItem('audio', JSON.stringify(store.state.audio))
}
// 恢复上次保存的数据
const initState = () => {
const musicListStr = localStorage.getItem('musicList')
const currMusicStr = localStorage.getItem('currMusic')
const audioStr = localStorage.getItem('audio')
if (musicListStr) {
store.commit('setMusicList', JSON.parse(musicListStr))
}
if (currMusicStr) {
store.commit('setCurrMusic', JSON.parse(currMusicStr))
}
if (audioStr) {
const audio = JSON.parse(audioStr)
const param = { prop: 'key', value: 'value' }
param.prop = 'duration'
param.value = audio.duration
store.commit('setAudio', param)
if (audio.volume < 1 && audio.volume > 0) {
param.prop = 'volume'
param.value = audio.volume
store.commit('setAudio', param)
}
param.prop = 'mode'
param.value = audio.mode
store.commit('setAudio', param)
}
}
onMounted(() => {
initMediaSession()
monitorKeyboard()
setTimeout(initState, 0)
window.onbeforeunload = () => {
saveState()
}
})
return {
audio,
source,
auto,
durationchange,
progress,
canplay,
play,
pause,
ended,
error
}
}
})
</script>
app.vue
<template>
<div style="position: relative;width: 100vw;height: 100vh;overflow-y: hidden">
<Player ref="player" />
<playView id="playView" :style="showPlayView?'':'transform:translateY(110%)'" />
<router-view />
<LoginDialog v-if="showDialog===0" class="login-dialog" />
<UserDialog v-if="showDialog===1" class="user-dialog" />
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
import { useStore } from 'vuex'
import Player from '@/components/Player.vue'
import playView from '@/pages/playView/playView.vue'
import LoginDialog from '@/components/LoginDialog.vue'
import UserDialog from '@/components/UserDialog.vue'
// document.body.style.setProperty('--main-color', '#1DCF9F')
export default defineComponent({
name: 'App',
components: {
Player,
playView,
LoginDialog,
UserDialog
},
setup() {
const store = useStore()
const showPlayView = computed(() => store.state.showPlayView)
const showDialog = computed(() => store.state.showDialog)
return {
showPlayView,
showDialog
}
}
})
</script>
<style>
:root{
--primary-color: #1DCF9F;
--light-color: rgba(29, 207, 159, 0.1);
/*--primary-color: rgb(244, 93, 93);*/
--block-size: 160px;
--block-num: 5;
--page-width: 1000px
}
::-webkit-scrollbar {
width: 6px; /* 纵向滚动条*/
height: 6px; /* 横向滚动条 */
background-color: #fff;
}
/*定义滑块*/
::-webkit-scrollbar-thumb {
background-color: #cccccc;
border-radius: 20px;
}
#playView{
position: absolute;
min-width: 1200px;
width: 100%;
height: 100%;
background: #404040;
z-index: 10000;
transition: 0.5s;
/*background: #fafafa;*/
}
.login-dialog{
min-width: 1200px;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 10000;
}
.user-dialog{
min-width: 200px;
position: absolute;
top: 60px;
right: 30px;
z-index: 10000;
}
.discolour {
cursor: pointer;
color: rgb(102, 102, 102);
}
.discolour:hover {
color: var(--primary-color);
}
</style>
组件
<ControlBar @showDrawer="showDrawer" />
<template>
<div>
<ProgressBar origin-key="controlBar" @jumpTo="jumpTo" />
<div class="control-bar">
<div class="bar-left">
<div style="position: relative" class="cover" @click="showPlayView">
<Image :src="imgUrl" :type="0" class="cover-image" />
<div class="cover-mask">
<svg-icon name="upShow" style="width: 100%;height: 100%;padding: 30%" />
</div>
</div>
<div style="display: flex;flex-direction: column;margin-left: 10px;width: 100%">
<div style="display: flex;align-items:center;width: 100%">
<span class="music-name">{{ currMusic?.name??'Ping音乐' }}</span>
<svg-icon
v-if="currMusic"
name="love"
:class="likeList.indexOf(currMusic?.id)!==-1?'like-active':'like'"
@click="setLike"
/>
</div>
<div>
<span v-if="!currMusic">FuYH</span>
<div v-else class="artist">
<span
v-for="(item,index) of currMusic?.artist"
:key="item.id"
style="font-size: 14px;cursor: pointer"
>
<span v-artist="item?.id" class="discolour">{{ item?.name }}</span>
<span v-if="index!==currMusic?.artist.length-1">/</span>
</span>
<span> - </span>
<span v-album="currMusic?.album?.id" class="discolour">{{ currMusic?.album?.name }}</span>
</div>
</div>
</div>
</div>
<div class="bar-center">
<div>
<svg-icon
:name="modeList[mode]"
class="discolour"
style="font-size: 20px;"
@click="switchMode"
/>
</div>
<svg-icon
name="prev"
class="discolour prev-button"
style="font-size: 22px"
@click="prev"
/>
<svg-icon
:name="state ? 'pause' : 'play'"
style="font-size: 40px; color: var(--primary-color);cursor: pointer"
@click="changeState"
/>
<svg-icon
name="next"
class="discolour next-button"
style="font-size: 22px"
@click="next"
/>
<div class="volume">
<svg-icon
:name="mute?'volume_mute':'volume'"
class="discolour"
style="font-size: 20px"
@click="volumeMute"
/>
<VolumeBar v-show="!mute" class="volumeBar" origin-key="controlBar" />
</div>
</div>
<div class="bar-right">
<span style="margin-right: 10px">{{ currFormat }} / {{ totalFormat }}</span>
<svg-icon
name="lyric"
class="discolour"
style="font-size: 19px; margin: 0 10px"
/>
<svg-icon name="music-list" class="discolour" style="font-size: 22px" @click="showDrawer" />
<span style="font-size: 10px">{{ musicList.length }}</span>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, watch, ref } from 'vue'
import { useStore } from 'vuex'
import ProgressBar from '@/components/ProgressBar.vue'
import VolumeBar from '@/components/VolumeBar.vue'
import coverImage from '@/assets/image/cover.png'
import timeFormat from '@/utils/timeFormat'
import Image from '@/components/global/Image.vue'
import { likeMusic } from '@/api/music'
export default defineComponent({
name: 'ControlBar',
components: {
Image,
ProgressBar,
VolumeBar
},
setup(props, ctx) {
const store = useStore()
const currFormat = ref('00:00')
const totalFormat = ref('00:00')
const jump = ref(0)
const modeList = ['order', 'loop', 'random', 'single']
const imgUrl = ref(coverImage)
const state = computed(() => store.state.audio.state)
const currentDura = computed(() => store.state.audio.currentTime)
const totalDura = computed(() => store.state.audio.duration)
const mode = computed(() => store.state.audio.mode)
const mute = computed(() => store.state.audio.mute)
const musicList = computed(() => store.state.musicList)
const currMusic = computed(() => store.state.currMusic)
const likeList = computed(() => store.state.likeList)
watch(currentDura, () => {
currFormat.value = timeFormat(currentDura.value)
})
watch(totalDura, () => {
totalFormat.value = timeFormat(totalDura.value)
})
watch(currMusic, () => {
imgUrl.value = currMusic.value?.album.picUrl + '?param=800y800'
})
const showPlayView = () => {
store.commit('setShowPlayView', true)
}
const switchMode = () => {
let param = {}
if (mode.value !== modeList.length - 1) {
param = { prop: 'mode', value: mode.value + 1 }
} else {
param = { prop: 'mode', value: 0 }
}
store.commit('setAudio', param)
}
const changeState = () => {
const param = { prop: 'state', value: !state.value }
store.commit('setAudio', param) //控制播放,触发音频
}
const jumpTo = (value:number) => {
jump.value = value
}
const volumeMute = () => {
const param = { prop: 'mute', value: !mute.value }
store.commit('setAudio', param)
}
const prev = () => {
const param = { prop: 'prev', value: true }
store.commit('setAudio', param)
}
const next = () => {
const param = { prop: 'next', value: true }
store.commit('setAudio', param)
}
const showDrawer = () => {
ctx.emit('showDrawer')
}
const setLike = () => {
const param = {
id: currMusic.value?.id,
like: false
}
const index = likeList.value.indexOf(currMusic.value?.id)
if (index === -1) {
param.like = true
}
likeMusic(param).then((res:any) => {
if (res.code === 200) {
const ids:any = [].concat(likeList.value)
if (index === -1) {
ids.push(param.id)
} else {
ids.splice(index, 1)
}
store.commit('setLikeList', ids)
}
})
}
return {
state,
imgUrl,
currMusic,
modeList,
mode,
mute,
jump,
musicList,
currFormat,
totalFormat,
likeList,
showPlayView,
changeState,
switchMode,
jumpTo,
volumeMute,
prev,
next,
showDrawer,
setLike
}
}
})
</script>
<style scoped>
.control-bar {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
.bar-left {
min-width: calc((100% - 300px) / 2);
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.bar-center {
width: 250px;
min-width: 250px;
margin: 0 25px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.bar-right {
min-width: calc((100% - 300px) / 2);
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.cover{
border: 1px solid #ededed;
overflow: hidden;
min-width: 50px;
min-height: 50px;
max-width: 50px;
max-height: 50px;
}
.cover-image{
transition: 0.3s;
/*border-radius: 2px;*/
}
.cover-mask{
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
/*background: #fafafa;*/
color: #FFFFFF;
transition: 0.3s;
opacity: 0;
}
.cover:hover .cover-image{
filter: blur(3px);
}
.cover:hover .cover-mask{
opacity: 1;
}
.music-name{
max-width: 90%;
font-size: 18px;
font-weight: bolder;
overflow: hidden;
text-overflow: ellipsis;
white-space:nowrap;
}
.artist{
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space:nowrap;
}
.like{
margin-left: 20px;
color: #cccccc;
cursor: pointer;
}
.like:hover{
color: var(--primary-color);
}
.like-active{
margin-left: 20px;
color: red;
cursor: pointer;
}
.prev-button {
color: #3f3f3f;
margin: 0 20px 0 30px;
}
.next-button {
color: #3f3f3f;
margin: 0 30px 0 20px;
}
.volume{
/*width: 150px;*/
/*display: flex;*/
/*align-items: center;*/
position: relative;
}
.volumeBar{
width: 0;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
z-index: 10;
transition: 0.3s;
}
.volume:hover .volumeBar{
width: 100px;
}
</style>
工作记录 音乐播放例子
最新推荐文章于 2024-09-30 16:19:08 发布