工作记录 音乐播放例子

<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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值