<script lang="ts" setup>
import { computed, nextTick, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue'
import useClipboard from 'vue-clipboard3'
import Scroller from './components/Scroller.vue'
import Loading from './components/Loading.vue'
import { audioTheme, formatTime } from './utils'
import lyricParser from './utils/lrcparse'
import { getSongDetail } from './api'
import useMusic from './hooks/index'
import defaultImg from './assets/img/noalbum.png'

const props = defineProps({
  musicId: [String, Number],
  musicSrc: String,
  theme: {
    type: [String, Array],
    default: 'apple'
  }
})

const { currentSong, currentTime, playing, setCurrentTime, setPlayingState } = useMusic()
const { toClipboard } = useClipboard()
const WHEEL_TYPE = 'wheel'
const SCROLL_TYPE = 'scroll'
// 恢复自动滚动的定时器时间
const AUTO_SCROLL_RECOVER_TIME = 1000

const isLoading = ref(false)
// const href = window.location.href
const albumImg = ref(defaultImg)
const nolyric = false
const lyricData = ref([])
const songTitle = ref('')
const albumName = ref(defaultImg)
const signer = ref('')
const songReady = ref(false)
const progress = ref('0%')
const totalTime = ref('')
const panelIsLyric = ref(true)
const httpEnd = ref(true)
let sourceAudio = null
let contextAudio = null
let analyserAudio = null
const timer = ref()
const scrollerRef = ref(null)
const lyricRef = ref([])

const lyricScrolling = {
  [WHEEL_TYPE]: false,
  [SCROLL_TYPE]: false
}
const lyricTimer = {
  [WHEEL_TYPE]: null as any,
  [SCROLL_TYPE]: null as any
}

async function getSone() {
  httpEnd.value = false
  const result: any = await getSongDetail(props.musicId)
  httpEnd.value = true

  const { cover, lyric, album, artist, title } = result
  songTitle.value = title
  signer.value = artist
  albumName.value = album
  
  albumImg.value = cover?.replace('250y250', '400y400') || ''
  lyricData.value = lyricParser(lyric).lyric
}

// 解决ios设备上无法播放问题
const userAgent = navigator.userAgent
const isIOS = !!userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
if (isIOS) {
  const musicDom: HTMLAudioElement = document.getElementById('audio') as never
  musicDom?.load()
}

onMounted(() => {
  if (props.musicId) 
    getSone()
})
onBeforeUnmount(() => {
  setCurrentTime(0)
  setPlayingState(false)
})

const audioRef = ref(null)
const audio = computed(() => audioRef)
const lyricWithTranslation: any = computed(() => {
  let ret: any = []
  // 空内容的去除
  const lyricFiltered = lyricData.value.filter(({ content }) => Boolean(content))
  // content统一转换数组形式
  if (lyricFiltered.length) {
    lyricFiltered.forEach((l) => {
      const { time, content } = l
      const lyricItem = { time, content, contents: [content] }
      ret.push(lyricItem)
    })
  }
  else {
    ret = lyricFiltered.map(({ time, content }) => ({
      time,
      content,
      contents: [content]
    }))
  }
  return ret
})
const activeLyricIndex = computed(() => {
  const data = lyricWithTranslation.value
  const temp = data.length
    ? data.findIndex((l: any, index: number) => {
      const nextLyric = data[index + 1]
      return (
        currentTime.value >= l.time
              && (nextLyric ? currentTime.value < nextLyric.time : true)
      )
    })
    : -1
  return temp
})

watch(activeLyricIndex, (newIndex, oldIndex) => {
  if (
    newIndex !== oldIndex
        && !lyricScrolling[WHEEL_TYPE]
        && !lyricScrolling[SCROLL_TYPE]
  ) 
    scrollToActiveLyric()
})
watch(currentSong, (newSong) => {
  // 清空了歌曲
  if (!newSong.id) {
    audioRef.value?.pause()
    audioRef.value.currentTime = 0
  }
  songReady.value = false
  if (timer.value) 
    // @ts-expect-error todo
    clearTimeout(timer)
      
  timer.value = setTimeout(() => {
    play()
  }, 1000)
})
watch(playing, async (newPlaying) => {
  await nextTick()
  newPlaying ? play() : pause()
})
watch(songReady, (n) => {
  if (n && httpEnd.value) 
    isLoading.value = false
  else 
    isLoading.value = true
})
watch(httpEnd, (n) => {
  if (n && songReady.value) 
    isLoading.value = false
  else 
    isLoading.value = true
})
function setLyricRef(el) {
  lyricRef.value.push(el)
}

function updateTime(e: any) {
  const time = e.target.currentTime
  progress.value = `${Math.ceil(time / audioRef.value?.duration * 100)}%`
  setCurrentTime(time)
}
function ready() {
  songReady.value = true
  totalTime.value = audioRef.value?.duration
}
function pause() {
  audioRef.value?.pause()
  setPlayingState(false)
}
function end() {
  pause()
}
async function play() {
  if (songReady.value) {
    progress.value = `${Math.ceil(currentTime.value / audioRef.value.duration * 100)}%`
    try {
      await audioRef.value?.play()
      onLoadAudio()
    }
    catch (error) {
      setPlayingState(false)
    }
  }
}
function onClickSong() {
  setPlayingState(!playing.value)
}
function clearTimer(type: 'wheel' | 'scroll') {
  lyricTimer[type] && clearTimeout(lyricTimer[type])
}
function getActiveCls(index: number) {
  return activeLyricIndex.value === index ? 'active' : ''
}
function onInitScroller(scoller: any) {
  const onScrollStart = (type: 'wheel' | 'scroll') => {
    clearTimer(type)
    lyricScrolling[type] = true
  }
  const onScrollEnd = (type: 'wheel' | 'scroll') => {
    // 滚动结束后两秒 歌词开始自动滚动
    clearTimer(type)
    lyricTimer[type] = setTimeout(() => {
      lyricScrolling[type] = false
    }, AUTO_SCROLL_RECOVER_TIME)
  }
  scoller.on('scrollStart', onScrollStart.bind(null, SCROLL_TYPE))
  scoller.on('scrollEnd', onScrollEnd.bind(null, SCROLL_TYPE))
}
function scrollToActiveLyric() {
  if (activeLyricIndex.value !== -1) {
    const lyArr = toRaw(lyricRef.value)
    const targetDom = lyArr[activeLyricIndex.value]
    if (lyArr.length && targetDom) 
      scrollerRef.value?.getScroller()?.scrollToElement(targetDom, 200, 0, true)
  }
}
function onLoadAudio() {
  if (!contextAudio) {
    // 创建AudioContext，关联音频输入，进行解码、控制音频播放和暂停
    contextAudio = new (window.AudioContext)()
  }
  if (!analyserAudio) {
    // 创建analyser，获取音频的频率数据（FrequencyData）和时域数据（TimeDomainData）
    analyserAudio = contextAudio.createAnalyser()
    // fftSize：快速傅里叶变换，信号样本的窗口大小，区间为32-32768，默认2048
    analyserAudio.fftSize = 512
  }

  if (!sourceAudio) {
    // 创建音频源
    sourceAudio = contextAudio.createMediaElementSource(audioRef.value)
    // 音频源关联到分析器
    sourceAudio.connect(analyserAudio)
    // 分析器关联到输出设备（耳机、扬声器等）
    analyserAudio.connect(contextAudio.destination)
  }

  // 获取频率数组
  const bufferLength = analyserAudio.frequencyBinCount
  const dataArray = new Uint8Array(bufferLength)
  const canvas = document.getElementById('canvas') as HTMLCanvasElement
  canvas.width = 435
  canvas.height = 250
  const ctx = canvas.getContext('2d')
  const WIDTH = canvas.width
  const HEIGHT = canvas.height

  const barWidth = WIDTH / bufferLength
  let barHeight
  // 主题色
  let canvasTheme = null
  if (typeof props.theme === 'string') 
    canvasTheme = audioTheme[props.theme as keyof typeof audioTheme]
      
  else 
    canvasTheme = props.theme

  const renderFrame = () => {
    requestAnimationFrame(renderFrame)

    // 将当前频率数据复制到传入的Uint8Array，更新频率数据
    analyserAudio.getByteFrequencyData(dataArray)
    ctx.clearRect(0, 0, WIDTH, HEIGHT)
    // bufferLength表示柱形图中矩形的个数，当前是128个
    for (let i = 0, x = 0; i < bufferLength; i++) {
      barHeight = dataArray[i]
      const gradient = ctx.createLinearGradient(0, 0, 0, 250)
      colorPick(gradient, canvasTheme)
      ctx.fillStyle = gradient
      ctx.fillRect(x, 250 - barHeight, barWidth, barHeight)
      x += barWidth + 2
    }
  }
  renderFrame()
}
function colorPick(gradient: any, arr: any) {
  arr.forEach((item: any) => {
    const { pos, color } = item
    gradient.addColorStop(pos, color)
  })
}
async function handleShare() {
  try {
    await toClipboard(window.location.href)
    alert('复制成功')
  }
  catch (e) {
    alert('您的浏览器不支持复制：')
  }
}
</script>

<template>
  <div class="music_player">
    <div v-if="isLoading" class="loading">
      <Loading />
    </div>
    <div class="left">
      <img class="point" src="./assets/img/point.png" alt="">
      <img class="bar" :class="[playing ? 'play' : '']" src="./assets/img/bar.png" alt="">
      <div class="img-outer-container">
        <div class="img-outer" :class="[playing ? '' : 'paused']">
          <div class="img-album">
            <img :src="albumImg" alt="">
          </div>
        </div>
      </div>
      <div class="progress-bar-wrap">
        <div class="time">
          {{ formatTime(currentTime) }} / {{ formatTime(totalTime) }}
        </div>
        <div class="progress-bar" :style="{ width: progress }" />
      </div>
      <div class="control">
        <div class="btn" :class="[playing ? 'play' : 'pause']" title="播放/暂停" @click="onClickSong">
          <svg v-show="!playing" t="1640174711530" class="svg" viewBox="0 0 900 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="801" width="20" height="20">
            <path d="M780.8 475.733333L285.866667 168.533333c-27.733333-17.066667-64 4.266667-64 36.266667v614.4c0 32 36.266667 53.333333 64 36.266667l492.8-307.2c29.866667-14.933333 29.866667-57.6 2.133333-72.533334z" p-id="802" fill="#ffffff" />
          </svg>
          <svg v-show="playing" t="1640174831312" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1138" width="20" height="20">
            <path d="M349.866667 149.333333h-14.933334c-21.333333 0-36.266667 14.933333-36.266666 34.133334v654.933333c0 19.2 14.933333 34.133333 34.133333 34.133333h14.933333c19.2 0 34.133333-14.933333 34.133334-34.133333V183.466667c2.133333-19.2-12.8-34.133333-32-34.133334z m341.333333 0h-14.933333c-21.333333 0-36.266667 14.933333-36.266667 34.133334v654.933333c0 19.2 14.933333 34.133333 34.133333 34.133333h14.933334c19.2 0 34.133333-14.933333 34.133333-34.133333V183.466667c2.133333-19.2-12.8-34.133333-32-34.133334z" p-id="1139" fill="#ffffff" />
          </svg>
        </div>
        <div class="btn share" title="分享" @click="handleShare">
          <svg t="1640253998462" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2826" width="15" height="15"><path d="M512 42.666667a37.12 37.12 0 1 1 0 74.026666H227.84a111.573333 111.573333 0 0 0-111.146667 111.146667v568.32a111.573333 111.573333 0 0 0 111.146667 111.146667h568.32a111.573333 111.573333 0 0 0 111.146667-111.146667V512A37.12 37.12 0 1 1 981.333333 512v284.16A185.6 185.6 0 0 1 796.16 981.333333H227.84A185.6 185.6 0 0 1 42.666667 796.16V227.84A185.6 185.6 0 0 1 227.84 42.666667z m432.213333 0A37.12 37.12 0 0 1 981.333333 79.786667V277.333333a37.12 37.12 0 1 1-74.026666 0V169.173333L525.866667 550.613333a37.12 37.12 0 0 1-52.48 0 37.12 37.12 0 0 1 0-52.48L854.826667 116.693333H746.666667A37.12 37.12 0 1 1 746.666667 42.666667z" fill="#ffffff" p-id="2827" /><path d="M796.16 986.666667H227.84a192 192 0 0 1-190.506667-190.506667V227.84a192 192 0 0 1 190.506667-190.506667H512a42.666667 42.666667 0 1 1 0 85.333334H227.84a106.666667 106.666667 0 0 0-106.666667 106.666666v566.826667a106.666667 106.666667 0 0 0 106.666667 106.666667h568.32a106.666667 106.666667 0 0 0 106.666667-106.666667V512a42.666667 42.666667 0 1 1 85.333333 0v284.16a192 192 0 0 1-192 190.506667z m-568.32-938.666667a180.48 180.48 0 0 0-179.84 179.84v568.32a180.48 180.48 0 0 0 179.84 179.84h568.32a180.48 180.48 0 0 0 179.84-179.84V512a31.786667 31.786667 0 1 0-64 0v284.16a117.12 117.12 0 0 1-116.48 116.48H227.84a117.12 117.12 0 0 1-116.48-116.48V227.84a117.12 117.12 0 0 1 116.48-116.48H512a31.786667 31.786667 0 1 0 0-64z m271.786667 518.826667a42.666667 42.666667 0 0 1-42.666667-42.666667 42.666667 42.666667 0 0 1 12.373333-29.866667L842.026667 122.026667H746.666667a42.666667 42.666667 0 1 1 0-85.333334h197.546666a42.666667 42.666667 0 0 1 42.666667 42.666667V277.333333a42.666667 42.666667 0 1 1-85.333333 0V181.973333L529.706667 554.666667a42.666667 42.666667 0 0 1-30.08 12.16zM867.626667 111.36l-390.4 390.613333a31.36 31.36 0 0 0 0 44.8 32.426667 32.426667 0 0 0 44.8 0L912.64 156.16V277.333333a31.786667 31.786667 0 1 0 64 0V79.786667a31.786667 31.786667 0 0 0-31.786667-31.786667H746.666667a31.786667 31.786667 0 1 0 0 64z" fill="#ffffff" p-id="2828" /></svg>
        </div>
        <div class="btn switch" title="切换面板" @click="panelIsLyric = !panelIsLyric">
          <svg t="1641741992711" class="icon" viewBox="0 0 1137 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="57234" width="20" height="20"><path d="M775.509333 843.320889V364.032a119.751111 119.751111 0 0 0-119.808-119.751111H299.406222V119.751111C299.406222 53.646222 353.052444 0 419.157333 0h598.869334C1084.131556 0 1137.777778 53.646222 1137.777778 119.751111v603.761778a119.751111 119.751111 0 0 1-119.751111 119.808h-242.517334z" p-id="57235" fill="#ffffff" /><path d="M0 301.169778m119.751111 0l479.118222 0q119.751111 0 119.751111 119.751111l0 483.328q0 119.751111-119.751111 119.751111l-479.118222 0q-119.751111 0-119.751111-119.751111l0-483.328q0-119.751111 119.751111-119.751111Z" p-id="57236" fill="#ffffff" /></svg>
        </div>
      </div>
    </div>
    <div class="right">
      <canvas id="canvas" :style="{ transition: 'all .2s ease-in-out', opacity: !panelIsLyric ? 1 : 0 }" />
      <div class="lyric-container" :style="{ transition: 'all .2s ease-in-out', opacity: panelIsLyric ? 1 : 0 }">
        <div class="music-name">
          <p>{{ songTitle }}</p>
          <p>歌手：{{ signer }}&emsp;&emsp;专辑：{{ albumName }}</p>
        </div>
        <p v-if="nolyric" class="noLyric">
          还没有歌词哦~
        </p>
        <Scroller
          v-else
          ref="scrollerRef"
          :data="lyricData"
          :options="{ disableTouch: true }"
          class="lyric-wrap"
          @init="onInitScroller"
        >
          <div>
            <div
              v-for="(l, index) in lyricWithTranslation"
              :key="index"
              :ref="setLyricRef"
              :class="getActiveCls(index)"
              class="lyric-item"
            >
              <p
                v-for="(content, contentIndex) in l.contents"
                :key="contentIndex"
                class="lyric-text"
              >
                {{ content }}
              </p>
            </div>
          </div>
        </Scroller>
      </div>
    </div>
    <audio
      id="audio"
      ref="audioRef"
      :src="musicSrc"
      crossOrigin="anonymous"
      @canplay="ready"
      @ended="end"
      @timeupdate="updateTime"
    />
  </div>
</template>

<style lang="scss" scoped>
.vp-doc p{
  margin:0;
  line-height: inherit;
}
.music_player{
  width: 800px;
  display: flex;
  overflow: hidden;
  font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,"\5FAE\8F6F\96C5\9ED1",Arial,sans-serif !important;
  position: relative;
  .loading{
    position: absolute;
    width:100%;
    height:100%;
    display:flex;
    justify-content: center;
    align-items: center;
    z-index: 100;
  }
  .left{
    display: flex;
    flex-direction: column;
    position: relative;
    padding: 80px 120px 0 15px;
    justify-content: center;
    .point{
      position: absolute;
      left: 180px;
      top: -12px;
      width: 30px;
      height: 30px;
      z-index: 2;
    }
    .bar{
      position: absolute;
      top: 0;
      left: 190px;
      width: 100px;
      height: 145px;
      z-index: 1;
      transform-origin: 0 0;
      transform: rotate(-30deg);
      transition: all .3s;
      &.play{
        transform: rotate(5deg);
      }
    }
    .img-outer-container{
      border-radius: 50%;
      display: flex;
      flex-direction: row;
      align-items: center;
      justify-content: center;
      // background: #E6E5E6;
      width: 320px;
      height: 320px;
      .img-outer{
        width: 300px;
        height: 300px;
        background: #000;
        background: linear-gradient(-45deg,#333540,#070708,#333540);
        animation: rotate 20s linear infinite;
        border-radius: 50%;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: row;
        &.paused{
          animation-play-state: paused;
        }
        .img-album{
          width: 200px;
          height: 200px;
          flex-shrink: 0;
          img{
            width: 100%;
            height: 100%;
            border-radius: 50%;
          }
        }
      }
    }
    .progress-bar-wrap{
      margin: 40px auto 0px;
      width: 100%;
      background: #f1f1f1;
      position: relative;
      .time{
        position: absolute;
        right:0;
        bottom: 8px;
        font-size: 12px;
        color: #5c5c5c;
      }
      .progress-bar{
       background: #D33A31;
        height: 2px;
        position: relative;
        width: 100%;
        transition: width .2s ease;
        &:after{
          position: absolute;
          content:"";
          display:block;
          width:6px;
          height: 6px;
          background: #D33A31;
          border-radius: 50%;
          top: 1px;
          right:-1px;
          transform: translate(0%, -50%);
        }
      }
    }
    .control{
      padding: 20px 40px 0;
      display: flex;
      justify-content: center;
      align-items: center;
      div{
        cursor: pointer;
      }
      .btn{
        margin:0 auto;
        border-radius: 50%;
        width: 45px;
        height: 45px;
        background: #d33a31;
        display: flex;
        justify-content: center;
        align-items: center;
      }
    }
  }
  .right{
    flex: 1;
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
    canvas {
      position: absolute;
      left: 0;
      bottom: 140px;
      // width: 350px;
      height: 200px;
      // width: 100%;
      // height: 100%;
      z-index: 99999;
    }
    .noLyric{
      text-align: center;
    }
    .music-name{
      font-size: 14px;
      color: #7e7b7b;
      padding-bottom: 20px;
      p:first-child{
        // color:#333;
        font-size: 22px;
        // font-weight:
        padding-bottom: 10px;;
      }
    }
    .scroller{
      position: relative;
      overflow: hidden;
      &.lyric-wrap{
        width: 350px;
        height: 350px;
        mask-image: linear-gradient(180deg,hsla(0,0%,100%,0),hsla(0,0%,100%,.6) 15%,#fff 25%,#fff 75%,hsla(0,0%,100%,.6) 85%,hsla(0,0%,100%,0));
        .lyric-item {
          margin: 15px 0;
          font-size: 14px;
          &.active {
            font-size: 16px;
            font-weight: bold
          }
        }
      }
    }
  }
}
@keyframes rotate {
  0% {
    transform: rotate(0);
  }
  100% {
    transform: rotate(1turn);
  }
}
html.dark{
  .lyric-item.active{
    color: white !important;;
  }
  .music-name{
    p:first-child{
      color:#fefefe
    }
  }
  .lyric-item{
    color:#aaa;
  }
}
html{
  .lyric-item.active{
    color: #333 !important;;
  }
  .music-name{
    p:first-child{
      color:#333;
      font-weight: bold;
    }
  }
}
</style>
