|
@@ -0,0 +1,318 @@
|
|
|
+<template>
|
|
|
+ <div class="audio-wrapper" :key="vkey">
|
|
|
+ <audio ref="audio" :key="vkey">
|
|
|
+ <source :src="myAudioUrl+`?_=${idleft}`" type="audio/mp3" />
|
|
|
+ </audio>
|
|
|
+ <div class="UI-wrapper" :class="{'UI-wrapper--playing': !notPlaying}" @click="myPlay">
|
|
|
+ <div class="UI" :ref="`_${vkey}`">
|
|
|
+ <div :id="idleft" class="left"></div>
|
|
|
+ <div :id="idright" class="right"></div>
|
|
|
+ <div class="circle"></div>
|
|
|
+ <img v-if="notPlaying" :src="require('@/assets/images/icons/play.svg')" />
|
|
|
+ <img v-if="!notPlaying" :src="require('@/assets/images/icons/pause.svg')" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="showTime" class="namecon">
|
|
|
+ <div class="audio-name">{{name}}</div>
|
|
|
+ <div class="audio-right">
|
|
|
+ <div class="audio-time">
|
|
|
+ <span class="audio-length-current" ref="audioCurTime">00:00</span>
|
|
|
+ <span class="hang">/</span>
|
|
|
+ <span class="audio-length-total" ref="duration">00:00</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: "myAudio",
|
|
|
+ props: ["myAudioUrl","autoplay", "showTime","name", "idleft", "idright","vkey",'bofang'],
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ audio: "",
|
|
|
+ notPlaying: true,
|
|
|
+ progress: 0,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.init();
|
|
|
+ this.$bus.on('pausedAudio',data=>{
|
|
|
+ if (this.audio == data) {
|
|
|
+ this.notPlaying = true;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ // 因为本组件是放到table组件的slot里的,table组件各个slot又是用v-for从数组渲染出来的,所以数组里删除某一项并不会导致本组件被销毁,只会导致props变化,此时要重新初始化组件。
|
|
|
+ this.$bus.on('deletedAudio', () => {
|
|
|
+ this.init()
|
|
|
+ })
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ loadCircle() {
|
|
|
+ let leftEl = document.querySelector(`#${this.idleft}`);
|
|
|
+ let rightEl = document.querySelector(`#${this.idright}`);
|
|
|
+ if (!rightEl && !leftEl) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (this.progress) {
|
|
|
+ if (this.progress > 0 && this.progress < 50) {
|
|
|
+ rightEl.style.transform = "rotate(" + 3.6 * this.progress + "deg)";
|
|
|
+ } else {
|
|
|
+ rightEl.style.transform = "rotate(0)";
|
|
|
+ rightEl.style.background = "#FF9F2B";
|
|
|
+ leftEl.style.transform =
|
|
|
+ "rotate(" + 3.6 * (this.progress - 50) + "deg)";
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ rightEl.style.transform = "rotate(0deg)";
|
|
|
+ leftEl.style.transform = "rotate(0deg)";
|
|
|
+ rightEl.style.background = "#fff";
|
|
|
+ }
|
|
|
+ },
|
|
|
+ init() {
|
|
|
+ this.$nextTick(() => { // props的变化并不会立刻反映到html element的attribute上
|
|
|
+ if (this.$refs.audio) {
|
|
|
+ this.audio = this.$refs.audio;
|
|
|
+ this.audio.load();
|
|
|
+ this.audio.pause();
|
|
|
+ this.updateProgress();
|
|
|
+ this.notPlaying = true;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ stopOtherAudio() {
|
|
|
+ Array.from($("audio")).forEach((item) => {
|
|
|
+ if (this.audio != item) {
|
|
|
+ if (!item.paused) {
|
|
|
+ item.pause();
|
|
|
+ this.$bus.emit('pausedAudio',item)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 点击播放/暂停图片时,控制音乐的播放与暂停
|
|
|
+ myPlay() {
|
|
|
+ this.stopOtherAudio()
|
|
|
+ setTimeout(() => {
|
|
|
+ if (this.audio.paused) {
|
|
|
+ // 开始播放当前点击的音频
|
|
|
+ this.audio.play();
|
|
|
+ this.notPlaying = false;
|
|
|
+ } else {
|
|
|
+ this.audio.pause();
|
|
|
+ this.notPlaying = true;
|
|
|
+ }
|
|
|
+ this.updateProgress();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 更新进度条与当前播放时间
|
|
|
+ updateProgress() {
|
|
|
+ let value = this.audio.currentTime / this.audio.duration;
|
|
|
+ this.progress = value * 100;
|
|
|
+ // 初始时间
|
|
|
+ this.$refs.audioCurTime &&
|
|
|
+ (this.$refs.audioCurTime.innerHTML = this.transTime(
|
|
|
+ this.audio.currentTime
|
|
|
+ ));
|
|
|
+ // 总共时长
|
|
|
+ let audioElement = new Audio(this.myAudioUrl);
|
|
|
+ this.loadCircle();
|
|
|
+ let self = this;
|
|
|
+ audioElement.addEventListener("loadedmetadata", function () {
|
|
|
+ let duration2 = (audioElement.duration / 60)
|
|
|
+ .toFixed(2)
|
|
|
+ .replace(".", ":");
|
|
|
+ self.$refs.duration &&
|
|
|
+ (self.$refs.duration.innerHTML = self.formatTime(duration2));
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 播放结束时
|
|
|
+ audioEnded() {
|
|
|
+ this.progress = 0;
|
|
|
+ this.notPlaying = true;
|
|
|
+ this.$emit('audioEnded')
|
|
|
+ this.loadCircle();
|
|
|
+ },
|
|
|
+ // 播放时间换算
|
|
|
+ transTime(value) {
|
|
|
+ let time = "";
|
|
|
+ let h = parseInt(value / 3600);
|
|
|
+ value %= 3600;
|
|
|
+ let m = parseInt(value / 60);
|
|
|
+ let s = parseInt(value % 60);
|
|
|
+ if (h > 0) {
|
|
|
+ time = this.formatTime(h + ":" + m + ":" + s);
|
|
|
+ } else {
|
|
|
+ time = this.formatTime(m + ":" + s);
|
|
|
+ }
|
|
|
+ return time;
|
|
|
+ },
|
|
|
+ // * 格式化时间显示,补零对齐
|
|
|
+ // * eg:2:4 --> 02:04
|
|
|
+ formatTime(value) {
|
|
|
+ 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;
|
|
|
+ },
|
|
|
+ // 点击进度条跳到指定点播放
|
|
|
+ handledown() {
|
|
|
+ // 只有音乐开始播放后才可以调节,已经播放过但暂停了的也可以
|
|
|
+ if (!this.audio.paused || this.audio.currentTime != 0) {
|
|
|
+ let pgsWidth = this.$refs.progressBarBg.offsetWidth;
|
|
|
+ let rate = event.offsetX / pgsWidth;
|
|
|
+ this.audio.currentTime = this.audio.duration * rate;
|
|
|
+ this.updateProgress();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ autoplay:{
|
|
|
+ deep:true,
|
|
|
+ immediate:true,
|
|
|
+ handler:function (newVal) {
|
|
|
+ if (newVal) {
|
|
|
+ this.myPlay()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ bofang:{
|
|
|
+ deep:true,
|
|
|
+ handler:function () {
|
|
|
+ this.myPlay()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ audio: {
|
|
|
+ deep: true,
|
|
|
+ handler: function () {
|
|
|
+ let self = this;
|
|
|
+ self.audio.addEventListener(
|
|
|
+ "timeupdate",
|
|
|
+ function () {
|
|
|
+ self.updateProgress();
|
|
|
+ },
|
|
|
+ false
|
|
|
+ );
|
|
|
+ self.audio.addEventListener(
|
|
|
+ "ended",
|
|
|
+ function () {
|
|
|
+ self.audioEnded();
|
|
|
+ },
|
|
|
+ false
|
|
|
+ );
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less" scoped>
|
|
|
+.audio-wrapper {
|
|
|
+ display: flex;
|
|
|
+ margin-left: 4px;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.audio-name {
|
|
|
+ font-size: 14px;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ overflow: hidden;
|
|
|
+ white-space: nowrap;
|
|
|
+ flex: 3;
|
|
|
+ margin-right: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.audio-right {
|
|
|
+ height: 100%;
|
|
|
+ flex: 2;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+.audio-time {
|
|
|
+ .hang {
|
|
|
+ display: inline-block;
|
|
|
+ margin: 0 4px;
|
|
|
+ color: #000;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.audio-length-total {
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.audio-length-current {
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+@raduiW: 18px;
|
|
|
+@pcolor: #fff;
|
|
|
+@rW: 14px;
|
|
|
+
|
|
|
+.UI-wrapper {
|
|
|
+ position: relative;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-position: center;
|
|
|
+ background-size: contain;
|
|
|
+ background-image: url('~@/assets/images/icons/icon-music@2x.png');
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ cursor: pointer;
|
|
|
+ &:hover, &--playing {
|
|
|
+ background-image: url('~@/assets/images/icons/icon-music-hover@2x.png');
|
|
|
+ .UI {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.UI {
|
|
|
+ display: none;
|
|
|
+ position: absolute;
|
|
|
+ top: 40%;
|
|
|
+ left: 25%;
|
|
|
+ width: @raduiW;
|
|
|
+ height: @raduiW;
|
|
|
+ min-width: @raduiW;
|
|
|
+ background: #FF9F2B;
|
|
|
+ border-radius: 50%;
|
|
|
+ .left {
|
|
|
+ border-radius: 50%;
|
|
|
+ background: @pcolor;
|
|
|
+ clip: rect(0, @raduiW / 2, @raduiW, 0);
|
|
|
+ }
|
|
|
+ .right {
|
|
|
+ border-radius: 50%;
|
|
|
+ background: @pcolor;
|
|
|
+ clip: rect(0, @raduiW, @raduiW, @raduiW / 2);
|
|
|
+ }
|
|
|
+ .circle {
|
|
|
+ width: @rW;
|
|
|
+ height: @rW;
|
|
|
+ background: rgb(199, 100, 63);
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+ * {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ bottom: 0;
|
|
|
+ right: 0;
|
|
|
+ margin: auto;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.namecon {
|
|
|
+ margin-left: 20px;
|
|
|
+ display: flex;
|
|
|
+ width: calc(100% - 60px);
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+</style>
|