如何用 Vue3 和 Element-Plus 打造一个交互式音乐播放器

自定义音乐播放器

先看效果:
请添加图片描述
项目中需要引入 element-plus,因为我们用到了其中的 el-popover 组件来实现播放倍数的选择弹窗。

播放器

需要用到的设置

const speedVisible = ref<boolean>(false); // 设置音频播放速度弹窗
const audioRef = ref(); // 音频标签对象
const activeSpeed = ref(1); // 音频播放速度
const audioDuration = ref(0); // 音频总时长
const audioCurrent = ref(0); // 音频当前播放时间
const audioVolume = ref(1); // 音频声音,范围 0-1
const playStatus = ref<boolean>(false); // 音频播放状态:true 播放,false 暂停
const playProgress = ref(0); // 音频播放进度
const timeInterval = ref(); // 获取音频播放进度定时器

首先呢需要两张图片一个暂停一个开始

这里的话需要用定位将两张图片重叠起来

<img src="../assets/image/start.png" alt="" class="play-icon" />
<img src="../assets/image/stop.png" alt="" class="play-icon" />

其次需要准备音乐时间的展示数字,进度条,声音以及设置的音乐倍数

<!-- 时间展示 -->
<span class="play-time">
    {{ transTime(audioCurrent) }}/{{ transTime(audioDuration) }}
</span>
<!-- 进度条 -->
<div class="play-progress">
    <div class="play-current-progress" :style="{ width: `${playProgress}%` }"></div>
</div>
<!-- 音量和静音图标 -->
<img src="../assets/image/bofang.png" alt="" class="play-voice" v-if="audioVolume === 1" @click="onSetVolume(0)" />
<img src="../assets/image/jingy.png" alt="" class="play-voice" v-else @click="onSetVolume(1)" />
<!-- 倍数弹窗 -->
<el-popover v-model:visible="speedVisible" placement="top" :width="50">
    <div v-for="item in speedList" :key="item.value" @click="onChangeSpeed(item.value)" style="margin-bottom: 17px; cursor: pointer; text-align: center">
        <span>{{ item.label }}</span>
    </div>
    <template #reference>
        <span class="play-speed" @click="onHandleSpeed">{{ activeSpeed }}x</span>
    </template>
</el-popover>
<!-- 音频 -->
<audio ref="audioRef" :src="musicSource" @canplay="onCanplay"></audio>

样式部分:

:deep(.van-nav-bar) {
  background-color: transparent !important;
}

.audio-player {
  width: 90%;
  height: 52px;
  background: linear-gradient(180deg, #505572 0%, #383b4f 100%);
  border-radius: 8px;
  padding: 9px 11px;
  margin: 0 auto;
  box-sizing: border-box;
  display: flex;
  align-items: center;

  .play-icon {
    width: 34px;
    height: 34px;
    margin-right: 7px;
    cursor: pointer;
  }

  .play-time {
    width: 72px;
    color: #35bda5;
    font-size: 14px;
    display: inline-block;
    margin-right: 16px;
  }

  .play-progress {
    width: 160px;
    height: 4px;
    background-color: #323547;
    box-shadow: inset 0px 1px 0px 0px #20222d;
    border-radius: 2px;
    margin-right: 16px;
    position: relative;
    .play-current-progress {
      height: 4px;
      background: #2385bf;
      border-radius: 2px;
      position: absolute;
      top: 0;
      left: 0;
    }
  }

  .play-voice {
    width: 20px;
    height: 20px;
    margin-right: 14px;
    cursor: pointer;
  }

  .play-speed {
    cursor: pointer;
    color: #35bda5;
    font-size: 16px;
  }
}
:deep(.van-nav-bar__text) {
  color: #000;
}

将音乐先写出来

 <audio ref="audioRef" :src="musicSource" @canplay="onCanplay"></audio>

在点击播放按钮的时候,我们需要先判断音频是否已经加载完毕,只有在音频加载完毕后,才能开始播放。

const onPlay = async () => {
  // 音频播放完后,重新播放
  if (playProgress.value === 100) audioRef.value.currentTime = 0;

  await audioRef.value.play();
  playStatus.value = true;
  audioDuration.value = audioRef.value.duration;

  timeInterval.value = setInterval(() => {
    audioCurrent.value = audioRef.value.currentTime;
    playProgress.value = (audioCurrent.value / audioDuration.value) * 100;
    if (playProgress.value === 100) onPause();
  }, 100);
};
const onPause = () => {
  audioRef.value.pause();
  playStatus.value = false;
  /* 用完释放定时器 */
  clearInterval(timeInterval.value);
};

在组件销毁前,我们确保清除定时器,避免内存泄漏:

onBeforeMount(() => {
  clearInterval(timeInterval.value);
});

设置倍数
需要一个数组来存放更新播放的倍数

const speedList = [
  {
    label: "2x",
    value: 2,
  },
  {
    label: "1.5x",
    value: 1.5,
  },
  {
    label: "1x",
    value: 1,
  },
  {
    label: "0.75x",
    value: 0.75,
  },
];
const onChangeSpeed = (value: number) => {
  activeSpeed.value = value;
  // 设置倍速
  audioRef.value.playbackRate = value;
  speedVisible.value = false;
};
const onHandleSpeed = () => {
  speedVisible.value = !speedVisible.value;
};
设置音量
// 设置音量
const onSetVolume = (value: number) => {
  audioRef.value.volume = value;
  audioVolume.value = value;
};
时间格式化换算

transTime 用来将音频的时间转化为 mm:ss 或 hh:mm:ss 格式,formatTime 用于确保时间格式补零对齐:

// 音频时间格式化换算
const transTime = (value: number) => {
  let time = "";
  let h = parseInt(String(value / 3600));
  value %= 3600;
  let m = parseInt(String(value / 60));
  let s = parseInt(String(value % 60));
  if (h > 0) {
    time = formatTime(h + ":" + m + ":" + s);
  } else {
    time = formatTime(m + ":" + s);
  }
  return time;
};

格式化时间显示,补全对其

// 格式化时间显示,补零对齐
const formatTime = (value: string) => {
  let time = "";
  let s = value.split(":");
  let i = 0;
  for (; i < s.length - 1; i++) {
    time += s[i].length == 1 ? "0" + s[i] : s[i];
    time += ":";
  }
  time += s[i].length == 1 ? "0" + s[i] : s[i];

  return time;
};

播放时间、进度条和音量控制

我们接下来要添加显示音频时间、播放进度条和音量控制的部分。音频的当前播放时间和总时长使用 transTime 函数格式化显示。进度条通过绑定 playProgress 来实现动态更新:

<span class="play-time">
    {{ transTime(audioCurrent) }}/{{ transTime(audioDuration) }}
</span>
<div class="play-progress">
    <div class="play-current-progress" :style="{ width: `${playProgress}%` }"></div>
</div>
<img src="../assets/image/bofang.png" alt="" class="play-voice" v-if="audioVolume === 1" @click="onSetVolume(0)" />
<img src="../assets/image/jingy.png" alt="" class="play-voice" v-else @click="onSetVolume(1)" />
倍速控制弹窗

我们用 el-popover 来展示倍速选择弹窗,用户可以选择不同的播放倍速:

<el-popover v-model:visible="speedVisible" placement="top" :width="50">
    <div v-for="item in speedList" :key="item.value" @click="onChangeSpeed(item.value)" style="margin-bottom: 17px; cursor: pointer; text-align: center">
        <span>{{ item.label }}</span>
    </div>
    <template #reference>
        <span class="play-speed" @click="onHandleSpeed">{{ activeSpeed }}x</span>
    </template>
</el-popover>
音频标签

音频标签通过 :src=“musicSource” 动态绑定音频源,音频加载完后触发 onCanplay 事件,用于初始化播放器:

<audio ref="audioRef" :src="musicSource" @canplay="onCanplay"></audio>
播放与暂停

通过 onPlay 和 onPause 函数来控制音频的播放与暂停。播放时启动定时器,定时更新播放进度,暂停时清除定时器:

const onPlay = async () => {
  if (playProgress.value === 100) audioRef.value.currentTime = 0;
  await audioRef.value.play();
  playStatus.value = true;
  audioDuration.value = audioRef.value.duration;

  timeInterval.value = setInterval(() => {
    audioCurrent.value = audioRef.value.currentTime;
    playProgress.value = (audioCurrent.value / audioDuration.value) * 100;
    if (playProgress.value === 100) onPause();
  }, 100);
};

const onPause = () => {
  audioRef.value.pause();
  playStatus.value = false;
  clearInterval(timeInterval.value);
};
清除定时器

在组件销毁前,我们确保清除定时器,避免内存泄漏:

onBeforeMount(() => {
  clearInterval(timeInterval.value);
});
设置播放倍数

播放倍速设置通过 speedList 数组存储不同倍速的选项,onChangeSpeed 函数用来更新播放倍速:

const speedList = [
  { label: "2x", value: 2 },
  { label: "1.5x", value: 1.5 },
  { label: "1x", value: 1 },
  { label: "0.75x", value: 0.75 },
];

const onChangeSpeed = (value: number) => {
  activeSpeed.value = value;
  audioRef.value.playbackRate = value;
  speedVisible.value = false;
};
设置音量

音量控制通过 onSetVolume 函数进行设置:

const onSetVolume = (value: number) => {
  audioRef.value.volume = value;
  audioVolume.value = value;
};
时间格式化

transTime 用来将音频的时间转化为 mm:ss 或 hh:mm:ss 格式,formatTime 用于确保时间格式补零对齐:

const transTime = (value: number) => {
  let time = "";
  let h = parseInt(String(value / 3600));
  value %= 3600;
  let m = parseInt(String(value / 60));
  let s = parseInt(String(value % 60));
  if (h > 0) {
    time = formatTime(h + ":" + m + ":" + s);
  } else {
    time = formatTime(m + ":" + s);
  }
  return time;
};

const formatTime = (value: string) => {
  let time = "";
  let s = value.split(":");
  let i = 0;
  for (; i < s.length - 1; i++) {
    time += s[i].length == 1 ? "0" + s[i] : s[i];
    time += ":";
  }
  time += s[i].length == 1 ? "0" + s[i] : s[i];
  return time;
};

获取音乐数据

通过接口获取音乐数据并更新播放器的状态,确保音频文件和歌手信息渲染正确:

onMounted(() => {
  axios.get("音乐接口").then((res) => {
    musicSource.value = res.data.music;
    wenan.value = res.data.singer;
    sing.value = res.data.name;
    picture.value = res.data.Picture;
  })
  .catch((error) => {})
  .finally(() => {
    status.loading = false;
  });
});

到这里播放器就做好了,接下来就是将接口的图片以及歌手歌曲名称渲染在盒子里排一下版就可以了

<div style="display: flex;justify-content: space-around;align-items: center;margin-bottom: 5px;">
    <div style="width: 50%;5px;height: 100px;text-align: center;">
        <van-image round width="100px" height="100px" :src="Picture" />
    </div>
    <p style="font-family:FMXRDMH;display: flex;flex-wrap: wrap;font-size: 18px;"><span> 歌手:{{wenan}}</span><span> 歌曲:{{sing}}</span></p>
</div>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Southern Wind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值