audioList.vue 8.85 KB
<template>
  <div class="audio-list-page">
    <van-config-provider :theme-vars="themeVars">
      <van-floating-panel v-model:height="info_height" :anchors="anchors" @height-change="onHeightChange">
        <template #header>
          <div style="display: flex; align-items: center; justify-content: space-between; padding: 1.25rem 1rem 0.5rem 1rem;">
            <van-icon name="https://cdn.ipadbiz.cn/bieyuan/map/icon/Group%2054@3x.png" color="#DD7850" size="1.25rem" />
            <van-icon @click="onClose" name="cross" color="#DD7850" size="1.25rem" />
          </div>
        </template>
        <div class="van-hairline--top" style="padding: 1rem;">
          <div v-for="(item, index) in audio_list" :key="index" class="van-hairline--bottom audio-item">
            <div :class="['point', audio_index === index ? 'checked' : '']"></div>
            <div :class="['text', audio_index === index ? 'checked' : '', 'van-ellipsis']">
              {{ index + 1 }}. {{ item.title }}<span v-if="item.play" class="text-center">正在播放</span>

            </div>
            <div class="progress-ring">
              <div class="circle">
                <div class="pause-icon">
                  <van-icon @click="handleAudioPause(item, index)" v-if="item.play" name="https://cdn.ipadbiz.cn/bieyuan/map/icon/pauseButton1@3x.png" size="2.2rem" />
                  <van-icon @click="handleAudioPlay(item, index)" v-else name="https://cdn.ipadbiz.cn/bieyuan/map/icon/play1@3x.png" size="2.2rem" />
                </div>
                <div :class="['progress', item.play ? 'checked' : '']"></div>
              </div>
            </div>
          </div>
        </div>
      </van-floating-panel>
    </van-config-provider>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router';
import $ from 'jquery';
import { storeToRefs } from 'pinia'
import { mainStore } from '@/store';

const store = mainStore();
const { audio_status, audio_entity } = storeToRefs(store);

const props = defineProps({
  height: Number,
  status: String,
});

const themeVars = ref({
  floatingPanelHeaderHeight: 0,
  floatingPanelBorderRadius: '1.25rem'
})

const anchors = ref([0, (0.2 * window.innerHeight),  (0.5 * window.innerHeight)]);

const onHeightChange = ({ height }) => { // 监听高度变化
  if (!height) { // 关闭
    onClose();
  }
}

const info_height = ref(0);



watch(
  () => props.height,
  (v) => {
    info_height.value = v;
  }
)
// watch(
//   () => props.status,
//   (v) => {
//     if (v) { // 监听详情页播放状态,关闭浮层音频播放
//     }
//   }
// )

const onClose = () => { // 关闭列表回调
  audio.value.pause(); // 暂停音频播放
  pauseProgress();
  // 播放进度条
  $('.progress').css('background', 'conic-gradient(#f07142 0%, transparent 0%)');
  audio_list.value.forEach(item => item.play = false);
  audio_index.value = null;
  emit('close');
}

const emit = defineEmits(['close', 'status']);

const audio_index = ref();

const audio_list = ref([]);

onMounted(() => {
  audio_list.value = [
    {
      title: '3',
      src: 'https://img.tukuppt.com/newpreview_music/01/62/01/63b515415b482633.mp3',
      play: false
    },
    {
      title: '2',
      src: 'https://img.tukuppt.com/newpreview_music/01/66/20/63c0c3db3f8de739.mp3',
      play: false
    },
    {
      title: '风纺声(测试)(风的声音)风纺声(测试)(风的声音)',
      src: 'https://img.tukuppt.com/newpreview_music/01/65/86/63c0264040bd4441.mp3',
      play: false
    },
    {
      title: '3',
      src: 'https://img.tukuppt.com/newpreview_music/01/62/01/63b515415b482633.mp3',
      play: false
    },
    {
      title: '2',
      src: 'https://img.tukuppt.com/newpreview_music/01/66/20/63c0c3db3f8de739.mp3',
      play: false
    },
    {
      title: '风纺声(测试)(风的声音)风纺声(测试)(风的声音)',
      src: 'https://img.tukuppt.com/newpreview_music/01/65/86/63c0264040bd4441.mp3',
      play: false
    },
  ];
});

/**
 * 音频播放模块
 */

const audio = ref(new Audio());
const duration = ref(0);      // 音频总时长
// const currentTime = ref(0);   // 当前播放时间
let elapsedTime = 0;    // 已播放的时间
let intervalId = null;  // 保存setInterval的ID

/**
* 更新进度条
*/
const updateProgress = (audioDuration, index) => {
  let progressPercent = (elapsedTime / audioDuration) * 100;
  document.querySelector('.progress.checked').style.background =
    `conic-gradient(#f07142 ${progressPercent}%, transparent ${progressPercent}%)`;

  if (elapsedTime >= audioDuration) {
    pauseProgress(); // 音频播放完成,停止更新
    // 切换到下一个音频
    let idx = index + 1;
    if (idx >= audio_list.value.length) {
      idx = 0;
    }
    // 播放下一个音频
    handleAudioPlay(audio_list.value[idx], idx);
  }
}

/**
* 启动进度更新
*/
const startProgress = (audioDuration, index) => {
  intervalId = setInterval(() => {
    elapsedTime++;
    updateProgress(audioDuration, index);
  }, 1000);
}

/**
* 暂停进度更新
*/
const pauseProgress = () => {
  clearInterval(intervalId);
}

/**
 * 暂停播放回调
 * @param item
 * @param index
 */
const handleAudioPause = (item, index) => {
  item.play = false; // 更新播放状态
  audio.value.pause(); // 暂停音频播放
  pauseProgress(); // 暂停进度更新
}

const updateDuration = () => {
  duration.value = Math.floor(audio.value.duration); // 获取音频时长
};

// const updateTime = () => {
//   currentTime.value = Math.floor(audio.value.currentTime); // 获取当前播放时间
// };

// 监听 'loadedmetadata' 事件,确保在音频元数据加载后获取时长
audio.value.addEventListener('loadedmetadata', updateDuration);

// 监听 'timeupdate' 事件,实时更新当前播放时间
// audio.value.addEventListener('timeupdate', updateTime);

/*********************************** END *************************************/
/**
 * 播放音频回调
 * @param item
 * @param index
 */
const handleAudioPlay = (item, index) => {
  audio_list.value.forEach(item => item.play = false); // 清空播放状态
  if (audio_index.value !== index) {
    audio.value.src = item.src;
  }
  // 后台有播放器运行时,先暂停
  if (audio_status.value === 'play'){
    audio_entity.value.pause();
  }
  $('.progress').css('background', 'conic-gradient(#f07142 0%, transparent 0%)'); // 清空进度条
  pauseProgress(); // 暂停进度更新
  let play_status = audio.value.play() // 播放
  if (play_status) {
    play_status.then(() => {
      item.play = true;
      // 存放到pinia里面控制
      store.changeAudio(audio.value);
      store.changeAudioSrc(audio.value.src);
      store.changeAudioStatus('play');
      //
      startProgress(duration.value, index); // 开始更新进度
      if (audio_index.value !== index) { // 点击非同一音频
        audio_index.value = index;
        elapsedTime = 0; // 重置已播放时间
      }
      emit('status', 'play');
    }).catch((e) => {
      // 失败
      console.log('Operation is too fast, audio play fails')
    })
  }
}

const voicePause = () => {
  audio.value.pause();
  pauseProgress(); // 暂停进度更新
  store.changeAudioStatus('pause');
}

watch(
  () => audio_status.value,
  (v) => {
    if (v === 'pause') {
      voicePause();
      audio_list.value.forEach(item => item.play = false);
    }
  },
  { immediate: true }
);

onUnmounted(() => { // 离开页面时关闭音频播放
  onClose();
  store.changeAudioStatus('pause');
})
</script>

<style lang="less" scoped>
.audio-list-page {
  .audio-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem 0;
    .point {
      padding: 0.25rem;
      width: 1rem;
      height: 1rem;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      margin-right: 1rem;
      &.checked {
        border: 1px solid rgba(221, 120, 80, 0.5);
      }
      &::after {
        content: '';
        width: 1rem;
        height: 1rem;
        border-radius: 50%;
        background-color: #DD7850;
      }
    }
    .text {
      flex: 1;
      // display: flex;
      align-items: center;
      color: #47525F99;
      &.checked {
        color: #47525F;
      }
      .text-center {
        color: #DD7850;
        font-size: 0.85rem;
        margin-left: 1rem;
        line-height: 1.25;
      }
    }
  }

.progress-ring {
  position: relative;
  width: 2.5rem;
  height: 2.5rem;
}

.circle {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  // background-color: lightgray;
}

.pause-icon {
  z-index: 10;
  position: absolute;
  left:0.15rem;
  top: 0.15rem;
}

.progress {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  border-radius: 50%;
  background: conic-gradient(#f07142 0%, #f07142 0%, transparent 0%);
  z-index: 5;
  transform: rotate(0deg); /* 让进度条从顶部开始 */
}
}
</style>