|  | @@ -0,0 +1,266 @@
 | 
	
		
			
				|  |  | +import {
 | 
	
		
			
				|  |  | +  computed,
 | 
	
		
			
				|  |  | +  defineComponent,
 | 
	
		
			
				|  |  | +  reactive,
 | 
	
		
			
				|  |  | +  watch,
 | 
	
		
			
				|  |  | +  ref,
 | 
	
		
			
				|  |  | +  PropType,
 | 
	
		
			
				|  |  | +  onMounted
 | 
	
		
			
				|  |  | +} from 'vue';
 | 
	
		
			
				|  |  | +import styles from './index.module.less';
 | 
	
		
			
				|  |  | +import { Cell, Icon, Image, Popup, Slider } from 'vant';
 | 
	
		
			
				|  |  | +import musicBg from '../../images/music_bg.png';
 | 
	
		
			
				|  |  | +import iconMenu from '../../images/icon-menu.png';
 | 
	
		
			
				|  |  | +import iconPause from '../../images/icon-pause.png';
 | 
	
		
			
				|  |  | +import iconPlay from '../../images/icon-play.png';
 | 
	
		
			
				|  |  | +import songPrev from '../../images/song-prev.png';
 | 
	
		
			
				|  |  | +import songDown from '../../images/song-down.png';
 | 
	
		
			
				|  |  | +import songPause from '../../images/song-pause.png';
 | 
	
		
			
				|  |  | +import songPlay from '../../images/song-play.png';
 | 
	
		
			
				|  |  | +import { getSecondRPM } from '@/helpers/utils';
 | 
	
		
			
				|  |  | +import { nextTick } from 'process';
 | 
	
		
			
				|  |  | +import { usePageVisibility } from '@vant/use';
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +export default defineComponent({
 | 
	
		
			
				|  |  | +  name: 'audio-player',
 | 
	
		
			
				|  |  | +  props: {
 | 
	
		
			
				|  |  | +    musicList: {
 | 
	
		
			
				|  |  | +      type: Array as PropType<any>,
 | 
	
		
			
				|  |  | +      default: () => []
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +  setup(props) {
 | 
	
		
			
				|  |  | +    const pageVisibility = usePageVisibility();
 | 
	
		
			
				|  |  | +    const state = reactive({
 | 
	
		
			
				|  |  | +      songPopup: false,
 | 
	
		
			
				|  |  | +      playState: 'pause',
 | 
	
		
			
				|  |  | +      audioObj: {} as any,
 | 
	
		
			
				|  |  | +      listActive: 0,
 | 
	
		
			
				|  |  | +      dragStatus: false // 是否开始拖动
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +    let timer = null as any;
 | 
	
		
			
				|  |  | +    const audioData = reactive({
 | 
	
		
			
				|  |  | +      isFirst: true,
 | 
	
		
			
				|  |  | +      duration: 0.1,
 | 
	
		
			
				|  |  | +      currentTime: 0
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +    const audioRef = ref();
 | 
	
		
			
				|  |  | +    /** 加载成功 */
 | 
	
		
			
				|  |  | +    const onLoadedmetadata = () => {
 | 
	
		
			
				|  |  | +      audioData.duration = audioRef.value.duration;
 | 
	
		
			
				|  |  | +      if (audioData.isFirst) {
 | 
	
		
			
				|  |  | +        audioData.isFirst = false;
 | 
	
		
			
				|  |  | +        return;
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      if (state.playState === 'play') {
 | 
	
		
			
				|  |  | +        audioRef.value.play();
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    };
 | 
	
		
			
				|  |  | +    /** 改变时间 */
 | 
	
		
			
				|  |  | +    const handleChangeTime = (val: number) => {
 | 
	
		
			
				|  |  | +      audioRef.value.pause();
 | 
	
		
			
				|  |  | +      audioData.currentTime = val;
 | 
	
		
			
				|  |  | +      clearTimeout(timer);
 | 
	
		
			
				|  |  | +      timer = setTimeout(() => {
 | 
	
		
			
				|  |  | +        audioRef.value.currentTime = val;
 | 
	
		
			
				|  |  | +        if (state.playState === 'play') {
 | 
	
		
			
				|  |  | +          audioRef.value.play();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        timer = null;
 | 
	
		
			
				|  |  | +      }, 300);
 | 
	
		
			
				|  |  | +    };
 | 
	
		
			
				|  |  | +    const time = computed(() => {
 | 
	
		
			
				|  |  | +      return `${getSecondRPM(audioData.currentTime)} / ${getSecondRPM(
 | 
	
		
			
				|  |  | +        audioData.duration
 | 
	
		
			
				|  |  | +      )}`;
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /** 播放曲目 */
 | 
	
		
			
				|  |  | +    const handlePlay = (item: any) => {
 | 
	
		
			
				|  |  | +      const index = props.musicList.findIndex(
 | 
	
		
			
				|  |  | +        (_item: any) => _item.id === item.id
 | 
	
		
			
				|  |  | +      );
 | 
	
		
			
				|  |  | +      if (index > -1) {
 | 
	
		
			
				|  |  | +        if (state.listActive === index) {
 | 
	
		
			
				|  |  | +          state.playState = state.playState === 'play' ? 'pause' : 'play';
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +          state.playState = 'play';
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        state.listActive = index;
 | 
	
		
			
				|  |  | +        state.audioObj = props.musicList[index];
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /** 音频控制 */
 | 
	
		
			
				|  |  | +    const handleChangeAudio = (type: 'play' | 'pause' | 'pre' | 'next') => {
 | 
	
		
			
				|  |  | +      if (type === 'play') {
 | 
	
		
			
				|  |  | +        state.playState = 'play';
 | 
	
		
			
				|  |  | +      } else if (type === 'pause') {
 | 
	
		
			
				|  |  | +        state.playState = 'pause';
 | 
	
		
			
				|  |  | +      } else if (type === 'pre') {
 | 
	
		
			
				|  |  | +        if (props.musicList[state.listActive - 1]) {
 | 
	
		
			
				|  |  | +          handlePlay(props.musicList[state.listActive - 1]);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      } else if (type === 'next') {
 | 
	
		
			
				|  |  | +        if (props.musicList[state.listActive + 1]) {
 | 
	
		
			
				|  |  | +          handlePlay(props.musicList[state.listActive + 1]);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    watch(
 | 
	
		
			
				|  |  | +      () => pageVisibility.value,
 | 
	
		
			
				|  |  | +      val => {
 | 
	
		
			
				|  |  | +        if (val === 'hidden') {
 | 
	
		
			
				|  |  | +          audioRef.value.pause();
 | 
	
		
			
				|  |  | +          state.playState = 'pause';
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    );
 | 
	
		
			
				|  |  | +    watch(
 | 
	
		
			
				|  |  | +      () => state.playState,
 | 
	
		
			
				|  |  | +      val => {
 | 
	
		
			
				|  |  | +        if (val === 'play') {
 | 
	
		
			
				|  |  | +          audioRef.value.play();
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +          audioRef.value.pause();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    );
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    onMounted(() => {
 | 
	
		
			
				|  |  | +      nextTick(() => {
 | 
	
		
			
				|  |  | +        const musicList = props.musicList;
 | 
	
		
			
				|  |  | +        if (musicList.length > 0) {
 | 
	
		
			
				|  |  | +          state.audioObj = musicList[0];
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      });
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +    return () => (
 | 
	
		
			
				|  |  | +      <div class={styles.audioPlayer}>
 | 
	
		
			
				|  |  | +        <div class={styles.playerHeader}>
 | 
	
		
			
				|  |  | +          <div class={styles.musicInfo}>
 | 
	
		
			
				|  |  | +            <div
 | 
	
		
			
				|  |  | +              class={[
 | 
	
		
			
				|  |  | +                styles.cover,
 | 
	
		
			
				|  |  | +                state.playState === 'pause' && styles.imgRotate
 | 
	
		
			
				|  |  | +              ]}>
 | 
	
		
			
				|  |  | +              <Image src={musicBg} class={styles.musicBg} />
 | 
	
		
			
				|  |  | +            </div>
 | 
	
		
			
				|  |  | +            <div class={styles.musicName}>{state.audioObj?.name || '--'}</div>
 | 
	
		
			
				|  |  | +          </div>
 | 
	
		
			
				|  |  | +          <div class={styles.controls}>
 | 
	
		
			
				|  |  | +            <Icon
 | 
	
		
			
				|  |  | +              name={songPrev}
 | 
	
		
			
				|  |  | +              class={styles.icon}
 | 
	
		
			
				|  |  | +              onClick={() => {
 | 
	
		
			
				|  |  | +                handleChangeAudio('pre');
 | 
	
		
			
				|  |  | +              }}
 | 
	
		
			
				|  |  | +            />
 | 
	
		
			
				|  |  | +            <Icon
 | 
	
		
			
				|  |  | +              name={state.playState === 'play' ? iconPlay : iconPause}
 | 
	
		
			
				|  |  | +              class={[styles.icon, styles.iconPlay]}
 | 
	
		
			
				|  |  | +              onClick={() => {
 | 
	
		
			
				|  |  | +                if (state.playState === 'pause') {
 | 
	
		
			
				|  |  | +                  handleChangeAudio('play');
 | 
	
		
			
				|  |  | +                } else {
 | 
	
		
			
				|  |  | +                  handleChangeAudio('pause');
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +              }}
 | 
	
		
			
				|  |  | +            />
 | 
	
		
			
				|  |  | +            <Icon
 | 
	
		
			
				|  |  | +              name={songDown}
 | 
	
		
			
				|  |  | +              class={styles.icon}
 | 
	
		
			
				|  |  | +              onClick={() => {
 | 
	
		
			
				|  |  | +                handleChangeAudio('next');
 | 
	
		
			
				|  |  | +              }}
 | 
	
		
			
				|  |  | +            />
 | 
	
		
			
				|  |  | +            <Icon
 | 
	
		
			
				|  |  | +              name={iconMenu}
 | 
	
		
			
				|  |  | +              class={[styles.icon, styles.iconMenu]}
 | 
	
		
			
				|  |  | +              onClick={() => {
 | 
	
		
			
				|  |  | +                state.songPopup = true;
 | 
	
		
			
				|  |  | +              }}
 | 
	
		
			
				|  |  | +            />
 | 
	
		
			
				|  |  | +          </div>
 | 
	
		
			
				|  |  | +        </div>
 | 
	
		
			
				|  |  | +        <div class={styles.playerFooter}>
 | 
	
		
			
				|  |  | +          <Slider
 | 
	
		
			
				|  |  | +            step={0.01}
 | 
	
		
			
				|  |  | +            class={styles.timeProgress}
 | 
	
		
			
				|  |  | +            v-model={audioData.currentTime}
 | 
	
		
			
				|  |  | +            max={audioData.duration}
 | 
	
		
			
				|  |  | +            onUpdate:modelValue={val => {
 | 
	
		
			
				|  |  | +              handleChangeTime(val);
 | 
	
		
			
				|  |  | +            }}
 | 
	
		
			
				|  |  | +            onDragStart={() => {
 | 
	
		
			
				|  |  | +              state.dragStatus = true;
 | 
	
		
			
				|  |  | +            }}
 | 
	
		
			
				|  |  | +            onDragEnd={() => {
 | 
	
		
			
				|  |  | +              state.dragStatus = false;
 | 
	
		
			
				|  |  | +            }}
 | 
	
		
			
				|  |  | +          />
 | 
	
		
			
				|  |  | +          <div class={styles.playerTimer}>{time.value}</div>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          <audio
 | 
	
		
			
				|  |  | +            ref={audioRef}
 | 
	
		
			
				|  |  | +            src={state.audioObj.url}
 | 
	
		
			
				|  |  | +            onLoadedmetadata={onLoadedmetadata}
 | 
	
		
			
				|  |  | +            onEnded={() => {
 | 
	
		
			
				|  |  | +              handleChangeAudio('pause');
 | 
	
		
			
				|  |  | +            }}
 | 
	
		
			
				|  |  | +            onTimeupdate={() => {
 | 
	
		
			
				|  |  | +              if (timer) return;
 | 
	
		
			
				|  |  | +              // 开始拖动时也不能更新
 | 
	
		
			
				|  |  | +              if (state.dragStatus) return;
 | 
	
		
			
				|  |  | +              audioData.currentTime = audioRef.value?.currentTime;
 | 
	
		
			
				|  |  | +            }}></audio>
 | 
	
		
			
				|  |  | +        </div>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        <Popup
 | 
	
		
			
				|  |  | +          v-model:show={state.songPopup}
 | 
	
		
			
				|  |  | +          round
 | 
	
		
			
				|  |  | +          position="bottom"
 | 
	
		
			
				|  |  | +          class={styles.songPopup}>
 | 
	
		
			
				|  |  | +          <div class={styles.songContainer}>
 | 
	
		
			
				|  |  | +            <div class={styles.songTitle}>代表作</div>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            <div class={styles.songCellGroup}>
 | 
	
		
			
				|  |  | +              {props.musicList.map((item: any, index: number) => (
 | 
	
		
			
				|  |  | +                <Cell
 | 
	
		
			
				|  |  | +                  border={false}
 | 
	
		
			
				|  |  | +                  class={[
 | 
	
		
			
				|  |  | +                    styles.songCell,
 | 
	
		
			
				|  |  | +                    index === state.listActive && styles.active
 | 
	
		
			
				|  |  | +                  ]}
 | 
	
		
			
				|  |  | +                  center
 | 
	
		
			
				|  |  | +                  onClick={() => {
 | 
	
		
			
				|  |  | +                    if (index === state.listActive) return;
 | 
	
		
			
				|  |  | +                    state.audioObj = props.musicList[index];
 | 
	
		
			
				|  |  | +                    state.listActive = index;
 | 
	
		
			
				|  |  | +                    state.playState = 'play';
 | 
	
		
			
				|  |  | +                  }}>
 | 
	
		
			
				|  |  | +                  {{
 | 
	
		
			
				|  |  | +                    icon: () => <Image src={musicBg} class={styles.songImg} />,
 | 
	
		
			
				|  |  | +                    title: () => <div class={styles.songName}>{item.name}</div>,
 | 
	
		
			
				|  |  | +                    value: () => (
 | 
	
		
			
				|  |  | +                      <Icon
 | 
	
		
			
				|  |  | +                        name={
 | 
	
		
			
				|  |  | +                          index === state.listActive &&
 | 
	
		
			
				|  |  | +                          state.playState === 'play'
 | 
	
		
			
				|  |  | +                            ? songPlay
 | 
	
		
			
				|  |  | +                            : songPause
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                        class={[styles.iconSong]}
 | 
	
		
			
				|  |  | +                      />
 | 
	
		
			
				|  |  | +                    )
 | 
	
		
			
				|  |  | +                  }}
 | 
	
		
			
				|  |  | +                </Cell>
 | 
	
		
			
				|  |  | +              ))}
 | 
	
		
			
				|  |  | +            </div>
 | 
	
		
			
				|  |  | +          </div>
 | 
	
		
			
				|  |  | +        </Popup>
 | 
	
		
			
				|  |  | +      </div>
 | 
	
		
			
				|  |  | +    );
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +});
 |