ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

vue3+ts+vant制作音乐播放器(进度条拖拽、倍速切换、上一曲、下一曲)完整版

2022-06-07 12:34:39  阅读:275  来源: 互联网

标签:height audio value audioInfo 一曲 rem 倍速 完整版 any


1、进度条的用的是vant的Progress组件,比手写进度条方便很多,有自带的事件

2、H5页面兼容pc

效果展示

 

上代码

一、template模块

<template lang="pug">
.audioPlay
  main
    .audioBox
      .imgBox
        van-image.songImg(
          width="4.6rem",
          height="4.6rem",
          fit="cover",
          style="border-radius: 5px; overflow: hidden",
          :src="require('@/assets/images/media/song.png')"
        )
      .titBox 文稿
      .titText {{ title }}
      .audioControl
        audio(
          :src="audioSrc",
          @canplay="getDuration",
          @timeupdate="updateTime",
          v-show="false",
          controls,
          ref="audio"
        )
        van-slider.audioSlider(
          v-model="sliderValue",
          @update:model-value="sliderOnChange",
          active-color="#D8BE98",
          inactive-color="#E0E0E0",
          :disabled="isSlide > 0 ? false : true"
        )
          template(#button)
            .custom-button {{ currentDuration }}/{{ duration }}
        ul.handleUl
          li.handleLi(@click="handleBack")
            van-image.handleImg(
              width="0.44rem",
              height="0.44rem",
              fit="cover",
              :src="require('@/assets/images/media/houtui.png')"
            )
          li.handleLi(@click="prevPlay(isPlayNum)")
            van-image.handleImg(
              width="0.44rem",
              height="0.44rem",
              fit="cover",
              :src="isPlayNum > 1 ? require('@/assets/images/media/prevL.png') : require('@/assets/images/media/prev.png')"
            )
          li.handleLi(@click="handlePauseOrPlay")
            van-image.handleImg(
              width="0.98rem",
              height="0.98rem",
              fit="cover",
              :src="paused ? require('@/assets/images/media/play.png') : require('@/assets/images/media/stop.png')"
            )
          li.handleLi(@click="nextPlay(isPlayNum)")
            van-image.handleImg(
              width="0.44rem",
              height="0.44rem",
              fit="cover",
              :src="isPlayNum < catalogArray.length ? require('@/assets/images/media/nextL.png') : require('@/assets/images/media/next.png')"
            )
          li.handleLi(@click="handleForward")
            van-image.handleImg(
              width="0.44rem",
              height="0.44rem",
              fit="cover",
              :src="require('@/assets/images/media/qianjin.png')"
            ) 
        ul.funUl
          li.funLi(@click="sheetShowCli('mulu', '选集')")
            van-image.funImg(
              width="0.54rem",
              height="0.54rem",
              fit="cover",
              :src="require('@/assets/images/media/mulu.png')"
            )
            p.funtext 目录
          li.funLi(@click="sheetShowCli('beisu', '倍速')")
            van-image.funImg(
              width="0.54rem",
              height="0.54rem",
              fit="cover",
              :src="require('@/assets/images/media/beisu.png')"
            )
            p.funtext 倍速
          li.funLi(
            @click="sheetShowCli('pinglun', '评论')"
          )
            van-image.funImg(
              width="0.54rem",
              height="0.54rem",
              fit="cover",
              :src="require('@/assets/images/media/pinglun.png')"
            )
            p.funtext 评论
          li.funLi
            van-image.funImg(
              width="0.54rem",
              height="0.54rem",
              fit="cover",
              :src="require('@/assets/images/media/shoucang.png')"
            )
            p.funtext 收藏
          li.funLi
            van-image.funImg(
              width="0.54rem",
              height="0.54rem",
              fit="cover",
              :src="require('@/assets/images/media/dianzan.png')"
            ) 
            p.funtext 点赞
    van-action-sheet.audioSheet(v-model:show="sheetShow", :title="sheetTit")
      .sheetCon
        .multipW(v-if="sheetConActive == 'beisu'")
          .multipInfo(
            v-for="(item, index) in multipleArray",
            :key="index",
            @click="multipSelect(item.num, index)"
          )
            .multipText(:class="item.isSelected ? 'isSelect' : ''") {{ item.text }}
            van-icon.multipIcon(
              :name="require('@/assets/images/media/isSelected.png')",
              size="0.7rem",
              v-if="item.isSelected"
            )
        .catalogW(v-if="sheetConActive == 'mulu'")
          .catalogInfo(
            v-for="(item, index) in catalogArray",
            :key="index",
            @click="catalogSelect(index)"
          )
            .catalogCon(:class="item.isPlay ? 'isPlay' : ''")
              .con_box
                .con_boxTit {{ index + 1 }}.{{ item.title }}
                .con_boxDesc {{ item.desc }}
                .con_boxIcon
                  van-icon.timeIcon(name="clock-o", size="0.2rem")
                  span.timeSpan {{ item.audioDurat }}
              van-image.con_Img(
                width="0.6rem",
                height="0.6rem",
                fit="cover",
                :src="item.isPlay ? require('@/assets/images/media/playing.gif') : require('@/assets/images/media/playMl.png')"
              )
        .reviewW(v-if="sheetConActive == 'pinglun'")
          .reviewInfo 评论内容
</template>

 

 二、ts部分

<script lang="ts">
import {
  defineComponent,
  onBeforeMount,
  reactive,
  ref,
  toRefs,
} from "vue";


interface control {
  audioUrl: string;
  play: boolean;
}

export default defineComponent({
  name: "audioPlay",
  setup() {
    const audioControl: control = reactive({ audioUrl: "", play: false });
    onBeforeMount(() => {
      audioInfo.audioSrc = (catalogArray as any).value[0].audioSrc;
      audioInfo.title = (catalogArray as any).value[0].title;
      audioInfo.duration = (catalogArray as any).value[0].audioDurat;
      setTimeout(() => {
        audioInfo.isSlide = audio.value.duration;
        console.log("audio.value.duration22", typeof audio.value.duration);
        // alert(audio.value.duration);
      }, 100);
    });


    //暂停播放
    const handlePlayer = (): void => {
      audioControl.play = !audioControl.play;
      audioControl.play
        ? (audio.value as any).play()
        : (audio.value as any).pause();
    };

   
    const audioInfo = reactive({
      audioSrc: "",
      backSecond: 15, //后退秒数
      forwardSecond: 15, //前进秒数
      duration: "00:00", //音频总时长
      currentDuration: "00:00", //音频当前播放时长
      title: "",
      paused: true,
      isPlayNum: 1, //上下集用到-正在播放的第几集
      isSlide: 0, //判断滑块是否可以滑动
    });
    const audio = ref();
    const sliderValue = ref();
    //后退
    const handleBack = (): void => {
      if (audio.value.currentTime > audioInfo.backSecond) {
        audio.value.currentTime =
          audio.value.currentTime - audioInfo.backSecond;
      }
    };
    //前进
    const handleForward = (): void => {
      if (
        audio.value.duration - audio.value.currentTime >
        audioInfo.forwardSecond
      ) {
        audio.value.currentTime =
          audio.value.currentTime + audioInfo.forwardSecond;
      }
    };
    //暂停或播放
    const handlePauseOrPlay = (): void => {
      console.log("audio.value.duration22", typeof audio.value.duration);
      setTimeout(() => {
        audio.value.paused ? audio.value.play() : audio.value.pause();
        audioInfo.paused = !audioInfo.paused;
      }, 200);
    };
    //视频在可以播放时触发
    const getDuration = (): void => {
      setTimeout(() => {
        (audioInfo.duration as any) = timeFormat(audio.value.duration);
      }, 200);
    };
    //将单位为秒的的时间转换成mm:ss的形式
    const timeFormat = (number: Number) => {
      let minute = parseInt((<number>number / 60) as any);
      let second = parseInt((<number>number % 60) as any);
      (minute as any) = minute >= 10 ? minute : "0" + minute;
      (second as any) = second >= 10 ? second : "0" + second;
      return minute + ":" + second;
    };
    //进度条发生变化时触发
    const updateTime = (): void => {
      audioInfo.currentDuration = timeFormat(audio.value.currentTime);
      sliderValue.value = (
        (audio.value.currentTime * 100) /
        audio.value.duration
      ).toFixed(3);
      audioInfo.isSlide = audio.value.duration;
      // 播放完毕按钮变回
      if (audioInfo.currentDuration == audioInfo.duration) {
        audioInfo.paused = true;
      }
    };
    //滑动进度条
    const sliderOnChange = (value: any): void => {
      console.log("value", timeFormat((audio.value.duration * value) / 100));
      //   设置播放时间
      audioInfo.currentDuration = timeFormat(
        (audio.value.duration * value) / 100
      );
      audio.value.currentTime = parseInt(
        ((audio.value.duration * value) / 100) as any
      );
    };
    // 点击右侧功能
    const sheetShow = ref(false);
    const sheetTit = ref("");
    const sheetConActive = ref("");
    const multipleArray = ref([
      { num: 0.75, text: "0.75X", isSelected: false },
      { num: 1, text: "1.0X(正常倍速)", isSelected: true },
      { num: 1.25, text: "1.25X", isSelected: false },
      { num: 1.5, text: "1.5X", isSelected: false },
      { num: 2, text: "2X", isSelected: false },
    ]);
    const catalogArray = ref([
      {
        title: "音频播放器第一曲",
        desc: "第一站《大宪章》纪念碑1/4",
        audioSrc: require("@/assets/images/media/audio/music1.mp3"),
        audioDurat: "04:07",
        isPlay: true,
      },
      {
        title: "测试二未过时的未过时仍未未过时的未过时过时的未过时的未过",
        desc: "第一站测试二未过时的未过时仍未未过时的未过时过时的未过时的",
        audioSrc: require("@/assets/images/media/audio/music2.mp3"),
        audioDurat: "02:06",
        isPlay: false,
      },
      {
        title: "测试三过时未过时的未过时",
        desc: "第一站测试三未过时的未过时仍未未过时的未过时过时的未过时的/4",
        audioSrc: require("@/assets/images/media/audio/music3.mp3"),
        audioDurat: "04:56",
        isPlay: false,
      },
    ]);
    
    // 下方功能唤起面板
    const sheetShowCli = (val: any, tit: any): void => {
      sheetConActive.value = val;
      sheetTit.value = tit;
      sheetShow.value = true;
    };
    // 倍速选择
    const multipSelect = (num: Number, index: number): void => {
      audio.value.playbackRate = num;
      multipleArray.value.forEach((item: any) => {
        item.isSelected = false;
      });
      multipleArray.value[index].isSelected = true;
      sheetShow.value = false;
    };
    // 选集功能
    const catalogSelect = (index: number): void => {
      catalogArray.value.forEach((item: any) => {
        item.isPlay = false;
      });
      sliderValue.value = 0;
      audio.value.currentTime = 0;
      audioInfo.paused = true;
      audioInfo.audioSrc = (catalogArray as any).value[index].audioSrc;
      audioInfo.title = (catalogArray as any).value[index].title;
      audioInfo.currentDuration = "00:00";
      audioInfo.duration = (catalogArray as any).value[index].audioDurat;
      catalogArray.value[index].isPlay = true;
      sheetShow.value = false;
      audioInfo.isPlayNum = index + 1;
      handlePauseOrPlay();
    };
    // 上一集
    const prevPlay = (num: any): void => {
      if (num > 1) {
        catalogArray.value.forEach((item: any) => {
          item.isPlay = false;
        });
        sliderValue.value = 0;
        audio.value.currentTime = 0;
        audioInfo.paused = true;
        audioInfo.audioSrc = (catalogArray as any).value[num - 2].audioSrc;
        audioInfo.title = (catalogArray as any).value[num - 2].title;
        audioInfo.currentDuration = "00:00";
        audioInfo.duration = (catalogArray as any).value[num - 2].audioDurat;
        catalogArray.value[num - 2].isPlay = true;
        audioInfo.isPlayNum = num - 1;
        sheetShow.value = false;
        handlePauseOrPlay();
      }
    };
    // 下一集
    const nextPlay = (num: any): void => {
      if (num < catalogArray.value.length) {
        catalogArray.value.forEach((item: any) => {
          item.isPlay = false;
        });
        sliderValue.value = 0;
        audio.value.currentTime = 0;
        audioInfo.paused = true;
        audioInfo.audioSrc = (catalogArray as any).value[num].audioSrc;
        audioInfo.title = (catalogArray as any).value[num].title;
        audioInfo.currentDuration = "00:00";
        audioInfo.duration = (catalogArray as any).value[num].audioDurat;
        catalogArray.value[num].isPlay = true;
        audioInfo.isPlayNum = num + 1;
        sheetShow.value = false;
        handlePauseOrPlay();
      }
    };
    return {
      ...toRefs(audioControl),
      handlePlayer,

      ...toRefs(audioInfo),
      handleBack,
      handleForward,
      handlePauseOrPlay,
      getDuration,
      updateTime,
      audio,
      sliderValue,
      sliderOnChange,
      sheetShow,
      multipleArray,
      sheetShowCli,
      sheetTit,
      sheetConActive,
      multipSelect,
      catalogArray,
      catalogSelect,
      prevPlay,
      nextPlay,
    };
  },
});
</script>

 

三、样式部分

<style lang="scss">
.audioSlider {
  .van-slider__bar {
    z-index: 1111;
  }
  &.van-slider--disabled {
    opacity: 1;
  }
}
.isPlay {
  .con_Img {
    .van-image__img {
      width: 0.4rem;
      height: 0.4rem;
    }
  }
}
</style>
<style lang="scss" scoped>
.audioPlay {
  height: 100vh;
  display: flex;
  flex-direction: column;
  .content {
    @include Padding(0.2rem, 0px);
    @include Position(relative, 0, -0.9rem);
    width: 100%;
  }
  main {
    flex: 1;
    overflow: auto;
    .audioBox {
      .imgBox {
        width: 100%;
        text-align: center;
        margin-top: 1.1rem;
      }
    }
    .titBox {
      width: 1.8rem;
      height: 0.6rem;
      border-radius: 0.3rem;
      border: 1px solid #d8be98;
      text-align: center;
      margin: 0.6rem auto 0;
      font-size: 0.32rem;
      line-height: 0.6rem;
      color: #d8be98;
      box-sizing: border-box;
    }
    .titText {
      width: 80%;
      margin: 0.3rem auto 0;
      @include textEllipsis();
      font-size: 0.36rem;
      line-height: 0.4rem;
      color: #333;
      text-align: center;
    }
    .audioControl {
      position: relative;
      &::before {
        position: absolute;
        top: 0;
        left: 0;
        width: 50%;
        content: "";
        height: 2px;
        background: #d8be98;
      }
      &::after {
        position: absolute;
        top: 0;
        right: 0;
        width: 50%;
        content: "";
        height: 2px;
        background: #e0e0e0;
      }
      width: 90%;
      position: absolute;
      bottom: 0.6rem;
      left: 50%;
      transform: translateX(-50%);
      .audioSlider {
        width: 80%;
        margin: 0 auto;

        .custom-button {
          background: #d8be98;
          padding: 0 0.14rem;
          height: 0.36rem;
          line-height: 0.4rem;
          border-radius: 0.18rem;
          font-size: 0.24rem;
          color: #000;
          transform: scale(0.9);
        }
      }
      .handleUl {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 0 0.1rem;
        box-sizing: border-box;
        margin-top: 0.4rem;
        .handleLi {
          line-height: 0;
        }
      }
    }
    .funUl {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-top: 0.7rem;
      .funLi {
        line-height: 0;

        .funtext {
          color: #999999;
          font-size: 0.24rem;
          line-height: 0.4rem;
        }
      }
    }
  }
  .audioSheet {
    .sheetCon {
      border-top: 1px solid #dfdfdf;
      .multipW {
        padding: 0 0.32rem 1rem;
        .multipInfo {
          border-bottom: 1px solid #dfdfdf;
          height: 0.86rem;
          display: flex;
          justify-content: space-between;
          align-items: center;
          .multipText {
            color: #666666;
            font-size: 0.32rem;
            &.isSelect {
              color: #caaa7c;
            }
            .multipIcon {
              color: #caaa7c;
            }
          }
        }
      }
      .catalogW {
        padding: 0 0.32rem 0.6rem;
        .catalogInfo {
          border-bottom: 1px solid #e6e6e6;
          .catalogCon {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 0.24rem 0.2rem 0.24rem 0;
            box-sizing: border-box;
            .con_box {
              width: 80%;
              .con_boxTit {
                width: 100%;
                @include textEllipsis();
                font-size: 0.32rem;
                color: #000000;
              }
              .con_boxDesc {
                font-size: 0.28rem;
                color: #999999;
                @include textEllipsis();
                margin-top: 0.1rem;
              }
              .con_boxIcon {
                color: #cccccc;
                font-size: 0.24rem;
                margin-top: 0.2rem;
                .timeIcon {
                  margin-right: 0.1rem;
                }
              }
            }
            &.isPlay {
              .con_box {
                .con_boxTit {
                  color: #caaa7c;
                }
                .con_boxDesc {
                  color: #e0ccb1;
                }
                .con_boxIcon {
                  color: #e0ccb1;
                }
              }
            }
            .con_Img {
              background: #f7f1e8;
              width: 0.6rem;
              height: 0.6rem;
              border-radius: 50%;
              display: flex;
              align-items: center;
              justify-content: center;
            }
          }
        }
      }
    }
    .reviewW {
      padding: 0 0.32rem 0.6rem;
      .reviewInfo {
        border-bottom: 1px solid #e6e6e6;
        padding: 0.4rem 0;
        .reviewCon {
          display: flex;
          justify-content: space-between;
          .con_box {
            width: calc(100% - 0.78rem);
            .con_boxTit {
              font-size: 0.28rem;
              color: #666666;
            }
            .con_boxTime {
              color: #b3b3b3;
              font-size: 0.24rem;
              line-height: 0.4rem;
            }
            .con_boxText {
              font-size: 0.32rem;
              color: #333333;
              line-height: 0.48rem;
            }
            .con_boxReply {
              background: #f8f8f8;
              width: 100%;
              padding: 0.1rem 0.2rem 0.2rem;
              box-sizing: border-box;
              margin-top: 0.2rem;
              .replyList {
                display: flex;
                font-size: 0.28rem;
                line-height: 0.36rem;
                margin-top: 0.1rem;
                .replyName {
                  color: #666;
                  white-space: nowrap;
                  margin-right: 0.1rem;
                }
                .replyText {
                  color: #333;
                }
              }
            }
          }
        }
      }
    }
  }
}
@media only screen and (min-width: 750px) {
  .audioPlay {
    @include boxSize(750px, 100vh);
    margin: 0 auto;
  }
}
</style>

做个笔记

标签:height,audio,value,audioInfo,一曲,rem,倍速,完整版,any
来源: https://www.cnblogs.com/whqbk/p/16351304.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有