全速加载中...
登录
首页
文章
随笔
留言
友链
订阅
关于
更多
湘ICP备2021007748号-4
湘公网安备案湘公网安备43052202000137号
又拍云

Vue音乐播放组件:惊艳你的耳朵与眼睛!

Vue音乐播放组件,组件外观简约,可歌词同步显示,拖拽播放进度。默认为vue3版本,推荐使用方式二引入使用,可以更好的优化组件或修改成vue2版本。

在线体验地址 音乐播放器

方式一(插件):

1,安装

shell shell 复制代码
npm install apple-music-player
yarn add apple-music-player

2,在main.ts中引入

import { createApp } from 'vue' 复制代码
import App from './App.vue'
import AppleMusicPlayer from 'apple-music-player'
createApp(App).use(AppleMusicPlayer).mount('#app')

3,页面中使用

language 复制代码
<template>
  <div style="padding-top:20%;display: flex;
  justify-content: center;
  align-items: center;">
<!--    一般需要有一个div包着,设置div的宽高,播放器跟随父级宽高-->
    <div style="height: 180px;width: 500px;border:1px solid #ccc;padding: 20px">
      <AppleMusicPlayer  progressColor="rgba(211, 16, 16, 0.1)"  diskHW="110px"   :musicList="musicList" :darkTheme="darkTheme" :offsetY="25" >
      <!-- 这些可以换成图标按钮 -->
        <template #next>
          下一首
        </template>
        <template #last>
          上一首
        </template>
        <template #pause>
          暂停
        </template>
        <template #play>
          播放
        </template>

      </AppleMusicPlayer>
    </div>
  </div>
</template>

<script lang="ts" setup>
import {ref} from 'vue'
//主题
const darkTheme=ref(false)
//歌曲列表
const musicList=[
  {title:'好运来',img:'https://img.zhangpingguo.com/VAE_Article_ShouTu/vae/6802ac85-9f22-4cc4-b812-87238f103c36.jpg',src:'https://img.zhangpingguo.com/music/%E7%A5%96%E6%B5%B7%20-%20%E5%A5%BD%E8%BF%90%E6%9D%A5.mp3',lyric:'[00:00.00]好运来-祖海\n' +
        '[00:02.60]词:车行\n' +
        '[00:05.20]曲:戚建波\n' +
        '[00:07.80]好运来祝你好运来\n' +
        '[00:11.19]\n' +
        '[00:11.79]好运带来了喜和爱\n' +
        '[00:16.15]好运来我们好运来\n' +
        '[00:20.29]迎着好运兴旺发达通四海\n' +
        '[00:24.78]\n' +
        '[00:29.17]叠个千纸鹤再系个红飘带\n' +
        '[00:32.72]\n' +
        '[00:33.23]愿善良的人们天天好运来\n' +
        '[00:37.36]你勤劳生活美你健康春常在\n' +
        '[00:41.71]你一生的忙碌为了笑逐颜开\n' +
        '[00:46.71]\n' +
        '[00:47.36]打个中国结请春风剪个彩\n' +
        '[00:51.40]愿祖国的日月年年好运来\n' +
        '[00:55.56]你凤舞太平年你龙腾新时代\n' +
        '[01:00.04]你幸福的家园迎来百花盛开\n' +
        '[01:05.36]好运来祝你好运来\n' +
        '[01:09.68]好运带来了喜和爱\n' +
        '[01:13.91]好运来我们好运来\n' +
        '[01:18.17]迎着好运兴旺发达通四海\n' +
        '[01:24.22]\n' +
        '[01:33.43]叠个千纸鹤再系个红飘带\n' +
        '[01:37.46]愿善良的人们天天好运来\n' +
        '[01:41.64]你勤劳生活美你健康春常在\n' +
        '[01:46.00]你一生的忙碌为了笑逐颜开\n' +
        '[01:50.95]\n' +
        '[01:51.46]打个中国结请春风剪个彩\n' +
        '[01:55.74]愿祖国的日月年年好运来\n' +
        '[01:59.87]你凤舞太平年你龙腾新时代\n' +
        '[02:04.18]你幸福的家园迎来百花盛开\n' +
        '[02:09.62]好运来祝你好运来\n' +
        '[02:14.05]好运带来了喜和爱\n' +
        '[02:18.24]好运来我们好运来\n' +
        '[02:22.52]迎着好运兴旺发达通四海\n' +
        '[02:26.74]好运来祝你好运来\n' +
        '[02:31.10]好运带来了喜和爱\n' +
        '[02:35.35]好运来我们好运来\n' +
        '[02:39.63]迎着好运兴旺发达通四海\n' +
        '[02:45.09]\n' +
        '[02:59.12]好运来祝你好运来\n' +
        '[03:03.26]好运带来了喜和爱\n' +
        '[03:07.43]好运来我们好运来\n' +
        '[03:11.68]迎着好运兴旺发达通四海\n' +
        '[03:16.02]通四海好运来'}
];

</script>

<style scoped>

</style>

方式二(组件源码):

1,创建MusicPlayer组件

language 复制代码
<script lang="ts">
export default {
  name: "MusicPlayer"
}
</script>
<script setup lang="ts">
import {computed, onMounted, ref, toRefs, watch,withDefaults,defineProps} from "vue";
const audioRef = ref<HTMLAudioElement>();
const progressRef=ref('')
const progresscontainerRef=ref('')
interface MusicItem{
  title:string;
  img:string;
  src:string;
  lyric:string;
}
interface AudioPlayerProps {
  musicList?: (MusicItem [] | undefined );
  diskHW?:string;
  progressColor?:string;
  lyricColor?:string;
  lyricSize?:string;
  offsetY?:number;
  darkTheme?:boolean;
}

const  props= withDefaults(defineProps<AudioPlayerProps>(), {
  musicList: () =>undefined,
  diskHW: () =>'120px',
  lyricSize: () =>'14px',
  lyricColor: () =>'#1890ff',
  offsetY: () =>25,
  progressColor: () =>'#1890ff',
  darkTheme: () =>false,
})
const {musicList, offsetY}=toRefs(props);
// 默认从第一首开始
let songIndex = ref(0);
let currentLyc = ref(0);
const lyricList = ref ([]);
const lycStyle = ref({});
//正在播放的音乐
const playSongSrc:any = computed(() => {
  return musicList.value ? musicList.value[songIndex.value]:{}
});
const paused = ref(false);
//获取歌词
const getLyric=()=>{
  let result = playSongSrc.value.lyric+' ';
  if(result){
    let lyricData =[];
    result.split(/[\n]/).forEach((item,index) => {
      let temp = item.split(/\[(.+?)\]/)
      lyricData.push(
          {
            time: temp[1],
            lyc:  temp[2]
          })
    })
    lyricList.value=lyricData.filter(v => v['lyc']);
  }else{
    audioRef.value ? audioRef.value.currentTime = 0 : null;
    currentLyc.value=0;
    lycStyle.value={
      transform: `translateY(-0px)`,
    }
    lyricList.value=[]
    lyricList.value.push(
        {
          time: '00:01:00',
          lyc: playSongSrc.value.title
        })
  }

}
getLyric();

const timeUpdate=()=> {
  if(playSongSrc.value.lyric){
    const currentDate=getTime(audioRef.value.currentTime);
    for (let i = 0; i < lyricList.value.length; i++) {
      if (lyricList.value[i+1]   && currentDate < lyricList.value[i + 1].time && currentDate > lyricList.value[i].time) {
        currentLyc.value=i;
        lycStyle.value={
          transform: `translateY(-${offsetY.value * i+1}px)`,
        }
      }
    }
  }
}

// 秒转换-分:秒的格式
const getTime = time => {
  if (time) {
    const minutes = Math.floor(time / 60);
    const remainingSeconds = time % 60;
    const formattedMinutes = String(minutes).padStart(2, '0');
    const formattedSeconds = remainingSeconds.toFixed(2).replace('.', ':').padStart(5, '0');
    return `${formattedMinutes}:${formattedSeconds}`;
  } else {
    return "00:00";
  }
};
//监听
watch(playSongSrc, function (value, oldvalue) {
  try{
    audioRef.value.src=value?.src;
    getLyric();
    audioRef.value?.play();
    paused.value=true;
  }catch (e) {
    console.log(e)
  }

},)

onMounted(()=>{
  try{
    audioRef.value.src=playSongSrc.value?.src;
    // 播放中添加时间变化监听
    audioRef.value.addEventListener("timeupdate", () => {
      Timeupdate();
    });
    // 当前音乐播放完毕监听
    audioRef.value.addEventListener("ended", () => {
      playEnded();
    })
    audioRef.value.addEventListener('error', function() {
      console.warn('音频文件加载失败');
      return;
    });
  }catch (e) {
    console.log(e)
  }

})
const  playSong=(isplay)=> {
  paused.value=audioRef.value.paused;
  paused.value? audioRef.value?.play():audioRef.value?.pause();
}
const switchSong=(isNext)=>{
  paused.value=audioRef.value.paused;
  let number=songIndex.value;
  if (musicList.value) {
    songIndex.value == musicList.value.length - 1 ? songIndex.value = 0 : isNext ? songIndex.value++ : number == 0 ? songIndex.value = musicList.value.length - 1 : songIndex.value--;
  }
}
const Timeupdate=()=>{
  updateProgress()
  timeUpdate()
}
const playEnded=()=>{
  switchSong(true)
}
const  updateProgress=()=> {
  const {
    duration,
    currentTime
  } = audioRef.value;
  const progressPercent = (currentTime / duration) * 100;
  progressRef.value.style.width = `${progressPercent}%`
}
const setProgress=(clickX,width)=> {
  const duration = audioRef.value.duration
  audioRef.value.currentTime = (clickX / width) * duration
}
const setProgressBtn=(e)=> {
  const width =progresscontainerRef.value.clientWidth;
  const clickX = e.offsetX;
  setProgress(clickX,width)
}
</script>
<template>
  <div style="height: 100%;width: 100%;  display: flex;
  flex-direction: column;">
    <audio ref='audioRef'   ></audio>
    <div class='player'>
      <div class="player_disk" >
        <div class="disk_img" :style="{backgroundImage:`url(${playSongSrc.img})`,opacity:darkTheme?0.8:1}">
        </div>
        <div class="control-style">
          <div class="control_btn " @click="switchSong(false)">
                <span class="play-btn">
                         <slot name="last"></slot>
                  </span>
          </div>
          <div class="control_btn" @click="playSong(!paused)">
                <span class="play-btn" >
                  <div v-show="paused"><slot name="pause"></slot></div>
                  <div v-show="!paused"><slot name="play"></slot></div>
                </span>
          </div>
          <div class="control_btn "  @click="switchSong(true)">
                <span class="play-btn">
                              <slot name="next"></slot>
                </span>
          </div>
        </div>
      </div>
      <div className="player_control">
        <div class="lyricBox" >
          <div :style="{...lycStyle, color:darkTheme?'#bbb8b8':'#212020',fontSize:lyricSize}" class="lyricStyle">
            <p v-for="(item,index) in lyricList"  key={index} :style="{color:currentLyc==index?lyricColor:'',opacity:!darkTheme?1:0.6}">{{item.lyc}}</p>
          </div>
          <div className="progress-container" ref='progresscontainerRef' :style="{opacity:!darkTheme?1:0.6}"  @click="(e)=>{  setProgressBtn(e)}">
            <div ref='progressRef' class="progress" id="progress"  :style="{'backgroundColor':progressColor}"></div>
          </div>
        </div>

      </div>
    </div>
  </div>

</template>







<style  >
.player{
  display: flex;
  height: 100%;
}
.player_disk{
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.control-style{
  flex: 1;
  display: flex;
  text-align: center;
  padding-top: 10px;
}
.control_btn{
  flex: 1;
}
.player_control{
  flex: 2.2;
  margin-left: 10px;
}
.disk_img{
  text-align: center;
  width: 100%;
  height: 100%;
  background-size: 100% 100%;
  border-radius: 5px;
}
.lyricStyle{
  position: absolute;
  left: 0;
  right: 0;
  transition: all 0.3s;

}
.lyricStyle p{
  margin: 7px 0px;
}
.lyricBox{
  height: 100%;
  overflow: hidden;
  position: relative;
  text-align: center;
  padding-bottom: 10px;
}

.control{
  display: flex;
}
.progress {
  height: 100%;
  width: 0%;
  transition: width 0.2s linear;
  border-radius: 5px;
}
.progress-container{
  width: 100%;
  height: 3px;
  position: absolute;
  bottom: 0px;
  cursor:pointer;
  border-radius: 5px;
}

.lyricStyle p{
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

</style>

2,页面中引入使用

language 复制代码
<template>
  <div>
    <br>
<!--      这里的宽高必须设置,根据自身页面适当调整-->
    <div style="height: 200px;width: 500px">
        <MusicPlayer diskHW="80px" lyricSize="16px" progressColor="#36ad6a" lyricColor="#36ad6a"  :musicList="musicList" :offsetY="32" >
          <template #next>
          下一首
          </template>
          <template #last>
           上一首
          </template>
          <template #pause>
          暂停
          </template>
          <template #play>
          播放
          </template>
        </MusicPlayer>
    </div>
  </div>
</template>

<script lang="ts" setup>
import MusicPlayer from '../components/MusicPlayer.vue'

const musicList=[
  {title:'三年二班',img:'https://img.zhangpingguo.com/apple_Article_Head/vae/7e6361a6-58f7-4abb-997e-bdd88dd1dc06.jpg',src:'https://img.zhangpingguo.com/music/%E5%91%A8%E6%9D%B0%E4%BC%A6%20-%20%E4%B8%89%E5%B9%B4%E4%BA%8C%E7%8F%AD.mp3',lyric:'[ti:三年二班]\n' +
        '[ar:周杰伦]\n' +
        '[al:叶惠美]\n' +
        '\n' +
        '[00:00.30]三年二班\n' +
        '[00:01.30]作词:方文山  作曲:周杰伦\n' +
        '[00:02.30]演唱:周杰伦\n' +
        '[00:03.30]\n' +
        '[00:06.45]训导处报告 训导处报告\n' +
        '[00:09.27]三年二班 周杰伦\n' +
        '[00:11.46]马上到训导处来\n' +
        '[00:12.71]\n' +
        '[00:40.05]眼睛你要擦亮 记住我的模样\n' +
        '[00:42.71]表情不用太紧张\n' +
        '[00:44.02]我是 三年二班\n' +
        '[00:45.37]我专心打球的侧脸还满好看\n' +
        '[00:47.52]黑板是吸收知识的地方\n' +
        '[00:49.40]只是教室的阳光\n' +
        '[00:50.66]那颜色我不太喜欢\n' +
        '[00:51.97]没有操场的自然\n' +
        '[00:53.32]为何比较漂亮的都是在隔壁班\n' +
        '[00:56.04]还有考卷的答案\n' +
        '[00:57.34]我刚好都不会算\n' +
        '[00:58.64]没关系 再继续努力 没关系\n' +
        '[01:01.28]为什么上课时举手很难\n' +
        '[01:03.89]为什么拿线上宝物简单\n' +
        '[01:06.69]为什么女生不喜欢太胖\n' +
        '[01:09.22]为什么都别人手机在响\n' +
        '[01:11.81]正手发长球的打法只是初级乒乓\n' +
        '[01:14.71]反手短打再狠狠杀球是高级乒乓\n' +
        '[01:17.27]回转技巧乒乓 前场速攻乒乓\n' +
        '[01:19.95]对墙壁 在练习 乒乓 乒乓\n' +
        '[01:22.64]这第一名到底要多强\n' +
        '[01:25.93](不用问 一定有人向你挑战)\n' +
        '[01:27.94]到底还要过多少关\n' +
        '[01:31.14](不用怕 告诉他们谁是男子汉)\n' +
        '[01:33.46]可不可以不要这个奖\n' +
        '[01:36.51](不想问 我只想要流一点汗)\n' +
        '[01:38.79]我当我自己的裁判\n' +
        '[01:41.86](不想说 选择对手跟要打的仗)\n' +
        '[01:44.11]这第一名到底要多强\n' +
        '[01:46.95](不用问 一定有人向你挑战)\n' +
        '[01:49.24]到底还要过多少关\n' +
        '[01:52.57](不用怕 告诉他们谁是男子汉)\n' +
        '[01:54.80]可不可以不要这个奖\n' +
        '[01:57.84](不想问 我只想要流一点汗)\n' +
        '[02:00.10]我当我自己的裁判\n' +
        '[02:03.10](不想说 选择对手跟要打的仗)\n' +
        '[02:05.28]\n' +
        '[02:08.13]全体师生注意\n' +
        '[02:09.46]今天我要表扬一位同学\n' +
        '[02:11.97]他为校争光\n' +
        '[02:13.44]我们要向他看齐\n' +
        '[02:15.03]\n' +
        '[02:28.06]我不想 就这样一直走\n' +
        '[02:30.34]每天都遇上 充满敌意那种眼光\n' +
        '[02:33.43]等机会 就是要打倒对方\n' +
        '[02:36.10]这种结果我不要 这虚荣的骄傲\n' +
        '[02:38.93]这目的很好笑\n' +
        '[02:40.21]我其实都知道 你只是想炫耀\n' +
        '[02:42.90]我永远做不到 你永远赢不了\n' +
        '[02:45.50]我永远做不到\n' +
        '[02:46.79]你永远赢不了 永远都赢不了\n' +
        '[02:49.58]走下乡 寻找哪有花香\n' +
        '[02:52.07](为什么 这么简单你做不到)\n' +
        '[02:54.88]坐车厢 朝着南下方向\n' +
        '[02:57.32](为什么 这种速度你追不到)\n' +
        '[03:00.31]鸟飞翔 穿过这条小巷\n' +
        '[03:02.80](为什么 这么简单你做不到)\n' +
        '[03:05.60]仔细想 这种生活安详\n' +
        '[03:08.07](为什么 这种速度你追不到)\n' +
        '[03:10.93]不好笑 不好笑 不好笑\n' +
        '[03:12.71]不好笑 不好笑 不好笑\n' +
        '[03:14.57]\n' +
        '[03:32.15]这第一名到底要多强\n' +
        '[03:35.31](不用问 一定有人向你挑战)\n' +
        '[03:37.48]到底还要过多少关\n' +
        '[03:40.59](不用怕 告诉他们谁是男子汉)\n' +
        '[03:42.92]可不可以不要这个奖\n' +
        '[03:45.90](不想问 我只想要流一点汗)\n' +
        '[03:48.14]我当我自己的裁判\n' +
        '[03:51.25](不想说 选择对手跟要打的仗)\n' +
        '[03:53.99]我不想 就这样一直走\n' +
        '[03:56.04]每天都遇上 充满敌意那种眼光\n' +
        '[03:58.79]等机会 就是要打倒对方\n' +
        '[04:01.82]这种结果我不要 这虚荣的骄傲\n' +
        '[04:04.17]我不想 就这样一直走\n' +
        '[04:06.34]每天都遇上 充满敌意那种眼光\n' +
        '[04:09.34]等机会 就是要打倒对方\n' +
        '[04:12.11]这种结果我不要 这虚荣的骄傲\n' +
        '[04:14.81]走下乡 寻找哪有花香\n' +
        '[04:17.14]\n' +
        '[04:20.24]坐车厢 朝着南下方向\n' +
        '[04:22.45]\n' +
        '[04:25.62]鸟飞翔 穿过这条小巷\n' +
        '[04:27.96]\n' +
        '[04:30.80]仔细想 这种生活安详\n' +
        '[04:33.06]'}
];

</script>


<style scoped>

</style>

其他

1,属性参数

参数 说明 类型 默认值
musicList 歌词列表,一个数组对象,对象中需要包含:title(歌名),img(封面),src(音乐地址),lyric(歌词-怎么获取歌词?) 数组 无
progressColor 进度条背景颜色 颜色 #1890ff
offsetY 歌词一行播放结束后,向上偏移多少(防止出现偏移过多出现的高亮问题) 数字 25
lyricSize 歌词字体大小 字符串 14px
lyricColor 歌词高亮背景色 字符串 #1890ff
diskHW 磁盘图片的宽高 字符串 120px
darkTheme 黑夜主题 布尔 false

2,插槽Slots

名称 说明
next 下一首
last 上一首
pause 暂停
play 播放

3,演示地址

注意:本组件暂未怎么测试,复制上方代码操作,简单的播放还是没问题的,后续还将继续优化。

该组件已经在旧版张苹果博客侧边栏使用,效果如下:

使用效果图片
【版权声明】
✨ 本文来自 [张苹果博客] ✨
🌿 你可以:自由转发到社交网络或个人网站。
🌿 你需要:标注作者并附上本文链接(就像给文章留个回家地址~)。
上一篇 下一篇

评论一下

评论列表

 
暂无评论
用户头像
ZhangApple
发布日期:2024年02月22日