Vue3实现视频播放弹窗组件,支持全屏播放,音量控制,进度条自定义样式,适配浏览器小窗播放,视频大小自适配,缓冲loading,代码复制即用

效果图

组件所需VUE3代码

TypeScript 复制代码
<template>
  <div class="video-dialog" :class="fullScreen && 'video-dialog-full-screen'">
    <el-dialog
      v-model="props.visible"
      draggable
      :show-close="false"
      title=""
      center
      align-center
      @close="changeVisible"
    >
      <!-- 视频容器 -->
      <div
        class="video-content"
        :style="{
          backgroundImage: firstFrameUrl ? `url(${firstFrameUrl})` : 'none',
        }"
      >
        <!-- 背景磨砂层 -->
        <div class="video-backdrop" v-if="firstFrameUrl"></div>

        <!-- 视频加载中状态 -->
        <div class="video-loading" v-if="isLoading">
          <div class="loading-spinner"></div>
          <p v-if="loadingText">{{ loadingText }}</p>
        </div>

        <!-- 视频标签 -->
        <video
          ref="videoRef"
          :src="videoSrc"
          @timeupdate="updateProgress"
          @ended="handleEnded"
          @click="togglePlay"
          @volumechange="updateVolume"
          @seeked="updateBackground"
          @loadedmetadata="handleMetadataLoaded"
          @error="handleVideoError"
          @waiting="handleVideoBuffering"
          @playing="handleVideoPlaying"
        ></video>

        <!-- 播放错误提示 -->
        <div class="video-error" v-if="videoError">
          <img :src="DataUrl.ErrorIcon || 'https://picsum.photos/48/48'" alt="播放错误" class="error-icon" />
          <p class="error-text">{{ errorMessage }}</p>
          <p class="error-tips">建议使用MP4格式(H.264编码)重新上传</p>
        </div>

        <!-- 视频数据 -->
        <ul class="video-data" v-if="previewInfo">
          <li>
            <img :src="DataUrl.ZanIcon" alt="" class="data-icon i-1" />
            <p>760</p>
          </li>
          <li>
            <img :src="DataUrl.PingIcon" alt="" class="data-icon i-2" />
            <p>670</p>
          </li>
          <li>
            <img :src="DataUrl.LookEyeIcon" alt="" class="data-icon i-3" />
            <p>890</p>
          </li>
        </ul>

        <!-- 视频基本信息 -->
        <div class="video-base-info" v-if="previewInfo">
          <div class="title flex">
            <h1>网红名称</h1>
            <span></span>
            <p>2025.03.30</p>
          </div>
          <p class="note">
            风掠过耳际时突然懂了:原来旅行不是赶路,是让山川湖海,把心里的褶皱慢慢烫平。
          </p>
        </div>

        <img
          @click="changeVisible"
          :src="DataUrl.CloseRadiuIcon"
          alt=""
          class="close-icon"
        />
      </div>

      <!-- 视频进度条 -->
      <div class="video-progress">
        <div class="progress-bg" @click="seekToPosition">
          <div class="progress-value" :style="{ width: progress + '%' }"></div>
          <div class="progress-pointer" :style="{ left: progress + '%' }"></div>
        </div>
      </div>

      <!-- 视频控制栏 -->
      <div class="video-footer-tool">
        <div class="flex">
          <!-- 播放按钮 -->
          <img
            :src="isPlaying ? DataUrl.PauseIcon : DataUrl.PlayZIcon"
            alt=""
            class="icon-1"
            @click="togglePlay"
          />
          <!-- 下一个按钮 -->
          <img
            :src="DataUrl.PlayNextIcon"
            alt=""
            class="icon-2"
            @click="playNextVideo"
          />
          <!-- 音量控制 -->
          <div class="volume-container">
            <img
              :src="isMuted ? DataUrl.MuteIcon : DataUrl.YlIcon"
              alt=""
              class="icon-3"
              @click="toggleMute"
            />
            <div class="hover-mute-content" @mouseenter="showVolumeSlider">
              <div
                class="hover-mute-content-value"
                :style="{ width: volumePercent + '%' }"
                @mousedown="startVolumeAdjust"
              ></div>
            </div>
          </div>
          <!-- 视频时间 -->
          <p>
            {{ currentTime }} /<span>{{ durationTime }}</span>
          </p>
        </div>
        <!-- 全屏按钮 -->
        <div>
          <img
            :src="fullScreen ? DataUrl.UnFdIcon : DataUrl.FdIcon"
            alt=""
            class="icon-4"
            @click="fullScreen = !fullScreen"
          />
        </div>
      </div>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, watch, nextTick } from "vue"
import DataUrl from "@/config/data-url.js"

// 定义props
const props = defineProps({
  visible: {
    type: Boolean,
    default: false,
  },
  previewInfo: {
    type: Boolean,
    default: true,
  },
  videoInfo: {
    type: Object,
    default: {},
  },
})

// 视频源
const videoSrc = ref(
  props.videoInfo?.url ||
    "https://umi-rise.oss-ap-southeast-1.aliyuncs.com/20250707/WeChat_20250707174726.mp4"
)

// 组件状态
const videoRef = ref(null)
const isPlaying = ref(false)
const isMuted = ref(false)
const progress = ref(0)
const currentTime = ref("00:00")
const durationTime = ref("00:00")
const showControls = ref(true)
const timer = ref(null)
const fullScreen = ref(false)

// 音量控制状态
const volumePercent = ref(70)
const isAdjustingVolume = ref(false)
const isVolumeSliderVisible = ref(false)

// 背景控制状态
const showLeftBackground = ref(false)
const showRightBackground = ref(false)
const videoAspectRatio = ref(0)
const firstFrameUrl = ref("")
const isFirstFrameLoaded = ref(false)

// 播放错误状态
const videoError = ref(false)
const errorMessage = ref("")

// 新增:加载状态
const isLoading = ref(true)
const loadingText = ref("准备播放...")

const emit = defineEmits(["update-visible", "confim"])

// 关闭对话框
const changeVisible = () => {
  if (videoRef.value) {
    videoRef.value.pause();
    isPlaying.value = false; // 重置播放状态
  }
  emit("update-visible", false)
}

// 播放/暂停视频
const togglePlay = () => {
  if (videoRef.value) {
    if (isPlaying.value) {
      videoRef.value.pause()
    } else {
      videoRef.value.play().catch((err) => {
        console.log("自动播放失败,需要用户交互后才能播放", err)
      })
    }
    isPlaying.value = !isPlaying.value
  }
}

// 更新进度条
const updateProgress = () => {
  if (videoRef.value) {
    const percent = (videoRef.value.currentTime / videoRef.value.duration) * 100
    progress.value = Math.min(100, percent)
    currentTime.value = formatTime(videoRef.value.currentTime)

    if (videoRef.value.duration > 0 && durationTime.value === "00:00") {
      durationTime.value = formatTime(videoRef.value.duration)
    }
  }
}

// 格式化时间
const formatTime = (seconds: number) => {
  const minutes = Math.floor(seconds / 60)
  const secs = Math.floor(seconds % 60)
  return `${minutes.toString().padStart(2, "0")}:${secs
    .toString()
    .padStart(2, "0")}`
}

// 进度条点击跳转
const seekToPosition = (e: MouseEvent) => {
  if (videoRef.value) {
    const progressBar = e.currentTarget as HTMLElement
    const rect = progressBar.getBoundingClientRect()
    const clickX = e.clientX - rect.left
    const percent = (clickX / rect.width) * 100
    videoRef.value.currentTime = (percent / 100) * videoRef.value.duration
  }
}

// 音量控制
const toggleMute = () => {
  if (videoRef.value) {
    isMuted.value = !isMuted.value
    videoRef.value.muted = isMuted.value
    volumePercent.value = isMuted.value ? 0 : volumePercent.value
  }
}

const showVolumeSlider = () => {
  isVolumeSliderVisible.value = true
}

const startVolumeAdjust = (e: MouseEvent) => {
  isAdjustingVolume.value = true
  adjustVolume(e)
  document.addEventListener("mousemove", adjustVolume)
  document.addEventListener("mouseup", endVolumeAdjust)
  e.preventDefault()
}

const adjustVolume = (e: MouseEvent) => {
  if (!isAdjustingVolume.value || !videoRef.value) return

  const volumeBar = document.querySelector(".hover-mute-content") as HTMLElement
  if (!volumeBar) return

  const rect = volumeBar.getBoundingClientRect()
  const clickX = e.clientX - rect.left
  let percent = (clickX / rect.width) * 100
  percent = Math.max(0, Math.min(100, percent))

  volumePercent.value = percent
  videoRef.value.volume = percent / 100

  if (isMuted.value) {
    isMuted.value = false
    videoRef.value.muted = false
  }
}

const endVolumeAdjust = () => {
  isAdjustingVolume.value = false
  document.removeEventListener("mousemove", adjustVolume)
  document.removeEventListener("mouseup", endVolumeAdjust)
}

// 更新音量显示
const updateVolume = () => {
  if (videoRef.value) {
    if (videoRef.value.muted !== isMuted.value) {
      isMuted.value = videoRef.value.muted
    }

    if (!isMuted.value) {
      volumePercent.value = Math.round(videoRef.value.volume * 100)
    }
  }
}

// 更新背景显示
const updateBackground = () => {
  if (!videoRef.value || !videoRef.value.videoWidth) return
  // 计算视频宽高比
  videoAspectRatio.value =
    videoRef.value.videoWidth / videoRef.value.videoHeight
  // 获取容器尺寸
  const container = videoRef.value.parentElement
  if (!container) return
  const containerWidth = container.offsetWidth
  const containerHeight = container.offsetHeight
  // 计算容器宽高比
  const containerAspectRatio = containerWidth / containerHeight

  // 根据宽高比差异调整视频显示逻辑
  const isVideoWider = videoAspectRatio.value > containerAspectRatio
  showLeftBackground.value = isVideoWider
  showRightBackground.value = isVideoWider
}

// 加载第一帧
const loadFirstFrame = async () => {
  if (!videoRef.value) return

  try {
    // 尝试获取视频编码信息(部分浏览器支持)
    const videoTracks = videoRef.value.videoTracks
    if (videoTracks && videoTracks.length > 0) {
      const codec = videoTracks[0].codec || videoTracks[0].kind
      if (codec && !codec.includes("avc1") && !codec.includes("h264")) {
        console.warn("检测到非H.264编码,可能无法播放:", codec)
        // 可在这里显示警告提示
      }
    }

    // 尝试直接使用视频帧
    await captureFrame()
  } catch (error) {
    console.error("直接捕获失败:", error)

    // 尝试通过Fetch和Blob URL绕过跨域
    try {
      const response = await fetch(videoSrc.value)
      const blob = await response.blob()
      const blobUrl = URL.createObjectURL(blob)

      const tempVideo = document.createElement("video")
      tempVideo.crossOrigin = "anonymous"
      tempVideo.src = blobUrl

      await new Promise((resolve, reject) => {
        tempVideo.onloadedmetadata = resolve
        tempVideo.onerror = reject
      })

      tempVideo.currentTime = 0.1

      await new Promise((resolve, reject) => {
        tempVideo.onseeked = resolve
        tempVideo.onerror = reject
      })

      const canvas = document.createElement("canvas")
      canvas.width = tempVideo.videoWidth
      canvas.height = tempVideo.videoHeight

      const ctx = canvas.getContext("2d")
      ctx.drawImage(tempVideo, 0, 0, canvas.width, canvas.height)

      firstFrameUrl.value = canvas.toDataURL("image/jpeg", 0.8)
      isFirstFrameLoaded.value = true

      // 清理资源
      URL.revokeObjectURL(blobUrl)
      tempVideo.remove()
    } catch (error) {
      console.error("Blob方法失败:", error)
      // 回退到默认占位图
      firstFrameUrl.value =
        "https://umi-rise.oss-ap-southeast-1.aliyuncs.com/20250707/cover-video-00008.png"
      isFirstFrameLoaded.value = true
    }
  }
}

const captureFrame = () => {
  return new Promise((resolve, reject) => {
    if (!videoRef.value || !videoRef.value.videoWidth) {
      reject(new Error("视频未加载"))
      return
    }

    const video = videoRef.value
    video.currentTime = 0.5

    const handleSeeked = () => {
      const canvas = document.createElement("canvas")
      canvas.width = video.videoWidth
      canvas.height = video.videoHeight

      const ctx = canvas.getContext("2d")
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height)

      try {
        firstFrameUrl.value = canvas.toDataURL("image/jpeg", 0.8)
        isFirstFrameLoaded.value = true
        resolve()
      } catch (error) {
        reject(error)
      } finally {
        video.removeEventListener("seeked", handleSeeked)
      }
    }

    video.addEventListener("seeked", handleSeeked)
  })
}

// 处理视频元数据加载完成
const handleMetadataLoaded = () => {
  loadingText.value = "加载中..."
  // 视频元数据加载完成,但可能还需要缓冲
  // 不立即隐藏loading,等待canplay或playing事件
}

// 处理视频缓冲
const handleVideoBuffering = () => {
  if (!isLoading.value) {
    isLoading.value = true
    loadingText.value = "缓冲中..."
  }
}

// 处理视频开始播放
const handleVideoPlaying = () => {
  // 视频真正开始播放时,隐藏loading
  isLoading.value = false
  isPlaying.value = true
}

// 处理视频播放错误
const handleVideoError = () => {
  if (!videoRef.value) return
  const error = videoRef.value.error
  if (!error) return

  // 根据错误码判断原因
  switch (error.code) {
    case error.MEDIA_ERR_ABORTED:
      errorMessage.value = "视频加载被中断"
      break
    case error.MEDIA_ERR_NETWORK:
      errorMessage.value = "网络错误,无法加载视频"
      break
    case error.MEDIA_ERR_DECODE:
      errorMessage.value = "视频编码不支持,无法播放"
      break
    case error.MEDIA_ERR_SRC_NOT_SUPPORTED:
      errorMessage.value = "视频格式不支持"
      break
    default:
      errorMessage.value = "播放失败,请重试"
  }

  // 显示错误提示,隐藏第一帧背景和loading
  videoError.value = true
  firstFrameUrl.value = ""
  isLoading.value = false
  isPlaying.value = false
}

// 播放下一个视频
const playNextVideo = () => {
  console.log("播放下一个视频")
}

// 视频播放结束
const handleEnded = () => {
  isPlaying.value = false
  console.log("视频播放结束")
}

// 显示/隐藏控制栏
const toggleControls = () => {
  showControls.value = !showControls.value
  if (timer.value) clearTimeout(timer.value)
  timer.value = setTimeout(() => {
    showControls.value = false
  }, 3000)
}

const playVideo = () => {
  videoError.value = false
  isLoading.value = true // 开始加载
  loadingText.value = "准备播放..."
  
  videoRef.value.removeEventListener("click", toggleControls)
  videoRef.value.addEventListener("click", toggleControls)
  videoRef.value.play().catch((err) => {
    console.log("自动播放失败", err)
    isLoading.value = false // 加载失败,隐藏loading
  })
  
  videoRef.value.addEventListener("click", toggleControls)
  nextTick(updateProgress)
  nextTick(updateBackground)

  // 延迟加载第一帧,确保视频元素已初始化
  setTimeout(loadFirstFrame, 100)
}

// 生命周期钩子
onMounted(() => {
  if (videoRef.value) {
    playVideo()
  }

  // 监听窗口大小变化
  window.addEventListener("resize", updateBackground)
})

// 监听全屏状态变化
watch(
  () => fullScreen.value,
  () => {
    nextTick(updateBackground)
  }
)

// 监听视频源变化
watch(
  () => props.videoInfo?.url,
  (newUrl, oldUrl) => {
    if (newUrl && newUrl !== oldUrl) {
      // 更新视频源
      videoSrc.value = newUrl

      // 重置视频状态
      videoError.value = false
      isPlaying.value = false
      progress.value = 0
      currentTime.value = "00:00"
      
      // 加载新视频时显示loading
      isLoading.value = true
      loadingText.value = "准备播放..."

      // 重新加载并播放新视频
      if (videoRef.value) {
        videoRef.value.src = newUrl
        videoRef.value.load()
        videoRef.value.play().catch((err) => {
          console.log("切换视频播放失败:", err)
          isPlaying.value = false
          isLoading.value = false // 加载失败,隐藏loading
        })
        
        // 重新加载第一帧
        loadFirstFrame()
      }
    }
  },
  { immediate: true }
)

// 组件卸载时清理
onUnmounted(() => {
  if (timer.value) clearTimeout(timer.value)
  window.removeEventListener("resize", updateBackground)
})
</script>

<style lang="scss" scoped>
.video-dialog,
.video-dialog-full-screen {
  :deep(.el-overlay) {
    background: rgba(0, 0, 0, 0.66);
  }

  :deep(.el-dialog) {
    width: 900px;
    --el-dialog-border-radius: 20px;
    --el-dialog-padding-primary: 0;
    background: transparent;
    border-radius: 12px;
    padding: 0;
    overflow: hidden;

    .video-content {
      width: 100%;
      height: 503px;
      position: relative;
      overflow: hidden;
      background-size: cover;
      background-position: center;

      // 背景磨砂层
      .video-backdrop {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.4);
        backdrop-filter: blur(10px);
        z-index: 1;
      }

      video {
        max-width: 100%;
        max-height: 100%;
        height: 100%;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        object-fit: contain;
        z-index: 2;
      }

      // 加载状态样式
      .video-loading {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 5; // 高于视频但低于错误提示
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        background: rgba(0, 0, 0, 0.6);
        
        .loading-spinner {
          width: 48px;
          height: 48px;
          border: 4px solid rgba(255, 255, 255, 0.3);
          border-radius: 50%;
          border-top-color: #ff3f81;
          animation: spin 1s linear infinite;
          margin-bottom: 16px;
        }
        
        p {
          color: white;
          font-size: 16px;
          font-family: 'PingFang SC', sans-serif;
        }
      }
      
      @keyframes spin {
        to { transform: rotate(360deg); }
      }
      
      // 播放错误提示
      .video-error {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        z-index: 10; // 最高层级
        text-align: center;
        width: 80%;

        .error-icon {
          width: 48px;
          height: 48px;
          margin-bottom: 16px;
        }

        .error-text {
          font-size: 18px;
          color: #ff4d4f;
          margin-bottom: 8px;
        }

        .error-tips {
          font-size: 14px;
          color: rgba(255, 255, 255, 0.7);
        }
      }

      // 视频数据
      .video-data {
        position: absolute;
        right: 24px;
        bottom: 49px;
        z-index: 3;
        li {
          margin-bottom: 17px;
          &:last-child {
            margin-bottom: 0;
          }
        }
        .data-icon {
          width: 24px;
          height: 24px;
          display: block;
          margin: auto;
        }
        p {
          font-family: DIN, DIN;
          color: #ffffff;
          text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
          margin-top: 15px;
          font-weight: 400;
          font-size: 16px;
        }
      }

      .close-icon {
        position: absolute;
        right: 24px;
        top: 35px;
        width: 50px;
        height: 50px;
        z-index: 2;
        cursor: pointer;
      }

      .video-base-info {
        position: absolute;
        left: 24px;
        bottom: 12px;
        z-index: 3;
        .title {
          align-items: center;
          h1 {
            font-family: PingFang SC, PingFang SC;
            font-weight: 500;
            font-size: 18px;
            color: #ffffff;
            text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
          }
          span {
            margin: 0 8px;
            display: inline-block;
            width: 2px;
            height: 2px;
            background: #ffffff;
            box-shadow: 1px 1px 2px 0px rgba(0, 0, 0, 0.6);
          }
          p {
            font-family: DIN, DIN;
            font-weight: 400;
            font-size: 12px;
            color: #eae9e8;
            text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
          }
        }
        .note {
          margin-top: 2px;
          width: 234px;
          font-family: PingFang SC, PingFang SC;
          font-weight: 400;
          font-size: 14px;
          color: #ffffff;
          text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
          line-height: 1.5;
        }
      }
    }

    .video-content::before {
      content: "";
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      backdrop-filter: blur(8px);
      background: rgba(255, 255, 255, 0.1);
      z-index: 2;
      pointer-events: none;
    }

    // 进度条样式
    .video-progress {
      position: absolute;
      bottom: 56px;
      left: 0;
      right: 0;
      height: 4px;
      z-index: 3;
      .progress-bg {
        width: 100%;
        height: 100%;
        background: rgba(255, 255, 255, 0.2);
        border-radius: 2px;
        cursor: pointer;
        .progress-value {
          height: 100%;
          background: #ff3f81;
          border-radius: 2px;
          width: 0;
          transition: width 0.1s;
          position: relative;
        }
        .progress-value::after {
          content: "";
          position: absolute;
          right: 0;
          top: 50%;
          transform: translateY(-50%);
          width: 12px;
          height: 12px;
          background: #ff3f81;
          border-radius: 50%;
        }
        .progress-pointer {
          position: absolute;
          top: 50%;
          transform: translateY(-50%);
          width: 12px;
          height: 12px;
          background: #ff3f81;
          border-radius: 50%;
          margin-left: -6px;
          box-shadow: 0 0 8px rgba(255, 63, 129, 0.5);
          display: none;
        }
      }
    }

    // 底部控制栏样式
    .video-footer-tool {
      height: 56px;
      width: 100%;
      display: flex;
      justify-content: space-between;
      align-items: center;
      background: #16110c;
      padding: 0 24px;

      .icon-1,
      .icon-2,
      .icon-3 {
        width: 14.22px;
        height: 16px;
        margin-right: 24px;
        cursor: pointer;
        object-fit: contain;
      }

      .icon-3 {
        width: 16px;
        margin-right: 0;
      }

      .icon-4 {
        width: 18px;
        height: 18px;
        cursor: pointer;
      }

      p {
        font-family: PingFang SC, PingFang SC;
        font-weight: 500;
        font-size: 14px;
        color: #ffffff;
        span {
          color: rgba($color: #ffffff, $alpha: 0.65);
        }
      }

      // 音量控制样式
      .volume-container {
        display: flex;
        align-items: center;
        margin-right: 24px;
        .hover-mute-content {
          width: 68px;
          height: 6px;
          border-radius: 24px;
          background: rgba(255, 255, 255, 0.37);
          cursor: pointer;
          position: relative;
          display: none;
          margin-left: 24px;
          .hover-mute-content-value {
            position: absolute;
            top: 0;
            left: 0;
            width: 50%;
            height: 100%;
            background: #ffffff;
            border-radius: 24px;
            transition: width 0.1s;

            &::after {
              content: "";
              position: absolute;
              right: 0;
              top: 50%;
              transform: translateY(-50%);
              width: 12px;
              height: 12px;
              background: #ffffff;
              border-radius: 50%;
              box-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
            }
          }
        }
      }
      .volume-container:hover {
        width: calc(72px + 68px - 24px);
        max-width: calc(72px + 68px - 24px);
        .hover-mute-content {
          display: block;
        }
      }
    }
  }
}

// 全屏模式样式
.video-dialog-full-screen {
  :deep(.el-dialog) {
    width: 100%;
    height: 100%;
    --el-dialog-border-radius: 0;
    background: transparent;
    border-radius: 0;

    .video-content {
      width: 100%;
      height: calc(100vh - 56px);
      video {
        max-width: 100%;
        max-height: 100%;
        height: 100%;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        object-fit: contain;
        z-index: 2;
      }
    }
  }
}
</style>

DataUrl Icon文件

新建或者自定义一个相关文件,将以下ICON.base64代码引入,这边用的是js文件

javascript 复制代码
const dataUrl = {
    "ErrorIcon": ``,    "ZanIcon":`data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAbCAYAAACN1PRVAAAAAXNSR0IArs4c6QAAAjdJREFUSEu9lb2O00AUhe/ZH1Z2sOQNsWhSUKRyqpVfwC7SQ+EaqNPAE2DeAJ4A+jTwBPgBUqRKFQkKd5FgkRWH3Q2+5BrbOMbC+TEZKYo947nfnLln7oCO2HBEFm0NY+Z3RPSMiHwieg7gy64L3QrGzDYRfSoEvyaiq12B28IyVUUxbwG82EVdLYyZHxHR54qgPgCnaViVKmE0C0tVSa5EXbk1DhP3ibKqJiaZlAak7yOA94X+EyKK5f2fOWNmyVWVqrpUvQTwRj6yLOt8PB6vAPAGjJlfEZE4TK+LVjN+DeBSvul0OprjONFoNPqZw1KQdyAknw4gia1pWicMQ9neVRGWbZkDwGdmAYvSfdoEwJVMbLVaDxeLxVciuivCOEliuqIDYWKSx8eCvV6vOUmJpmkPLMv67vv+xjY2qewJgA8C03VdHwwGYdkgTcKSvAvMMIz7w+Ew8jwv/l85u1wXaXEgdbtdJQiCH2uz/Tlna+s3pSw/YwLr9XoXs9nsZqOCMPO39DDLPTU5wI3lmnmPiG7LsEPOVfEsyi2e10bbts/EiX/VRmaWevZ0z3IlOZILdaMKua57Kk6sLcT7lI7yHGaWOvHbD00E3DZGESb3zrn8NE27CMPwjIhOFUXBcrlM4imKIuWMoyiCqqpi4GQs7Y+jKFq12+0bVVXvgiC4ZeY4U1WlDK7rnsznc/i+D9M088VMp1MyTTOBZs/yL0365dm27dgwDO73++x5nmxdsn1ZO+o2/gKK/QUrGuu3NgAAAABJRU5ErkJggg==`,
    "PingIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAALlJREFUSEvtld0NgzAMhM8blA3KZt2kZRM2Kxu0GxwxSioURcqVEvqCJV5OyF/8b2hs1tg/FgDJJ4DrzrDJzPoE4M7OF3fmFiM4AcUM/z1FbwD+rbtL1aQi9xHwWsWvanVAqcNULT2oVoMOwAWAD2EyVatHAGCKAIckUzUJ8PNw11J0Ao5ddm3vQV5Nko9wh+6ZPoYVP5iZt6psxZOZATY5/kxz6SkkfcHdwqIbv31x7u+Yoy8ndMOPM/JLdRkQa0DJAAAAAElFTkSuQmCC`,
    "LookEyeIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAU1JREFUSEvFVQtRw1AQ3HWABFAAOKAKGBQACmgV0DpoFVAUgAPigKKASqiDI8vcg7R5nySdwM1kJp93t2/33V6IkYMj18f/A5jZKYBbAHcAdK/Y+LUguc2pkGRgZicAHgFMCzLOSS5Sa6IAvusXABcdz0gsJjE2LQAv/taQoyMGoiB7AEcUD5togRwCvGdkqQB8eKXrDMMNycuA+ANgZjrQeUIPdcveNzPTs3JisSQ504dvADO7AiDdY7Eleebr1KogufZn5Sg3Fjr0KgB8ZiivSE7NTMWfvNK9QOpWXgJ4yG3szwByElUkJwmJcsx/JfLkHN2WW7s2RZ82VY+/ut5yeOpw423qLDTMhri4m9GOBCmPirCNASND4/um07Br9nTBrWHpShOg9sou5ofiH83ZaCycN+aU5Hiuzbke/MNJuLP36yKD3hUPEkYH+AKsa5kZ8eiKFwAAAABJRU5ErkJggg==`,
    "CloseRadiuIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAAAXNSR0IArs4c6QAADFlJREFUeF7tnUloE98fwCdbJ1ubpGl/dscqRYKgFlQKpaAIimCKWC140Vs9KEFPXixW9KAnJerBXAo9KLgUaQVP4kELRXKoihSpoKSLabqlbdLsyY9v/n3+xvyTZmYyy5v0DYikM/PevO/nfZe3qyhylbUEVGVdOlI4igAu80pAABPAZS6BMi8e0WACuMwlUObFIxpMAJe5BMq8eGWjwZlMhlmW3HIVKmcmh++f3yqVKveeIquCYgEzgEIZVB6PR/3s2TM6GAxqo9GoJpFIaOLxuBaeS6fTmpwKQAFAtVqd0mq1KSCn1+sTer0+1dzcHD916lSir68vTVEUQM6CVipwRQHehJQF6nK5tB8+fDCEQiFdNBqtSKVSWgARiUSoaDT6l7bl/kY39Xr9X8/Bb4PBkP0bwNfr9fGampqow+GIDw4OJhBwJcHGHjBD89Td3d301NQUQNUDUCbMQhBLsasAHEEH4AaDIeZwOMIjIyMxpcDGFjDSVo/Ho3n06JE5GAwaksmkDkEVA+hWlSEXdlNT09rhw4djbrc7q9m4ajV2gBHY3t5e+uvXr8a1tTXTxsaGCoBKDbUQcCZsk8m0cfLkyTVcQWMDGIF1uVy60dFRWywWo0Fbg8FgKVZW9HetVmvWb+MKWnbAyMd6PB7t/fv3q1ZXV81KAJtbc0CrbTYbdqBlBbwJV93e3l4ZCASqwBTjrrHFTAJotMlkSoGPHh8fD1MUlZbTP8sCGGmty+WqQOb49+/fxWSnqPv19fUUTdMxp9O5Av5ZpVJBu1ryS3LA5ai1haiBNhuNxnR9ff2a1+sNyaHNkgIGuExfW25aWwg0aLPFYglNTk5CxCipyZYMMMAFk/z69Wv7+vq6Tum+lqutRb75zJkzC263Oy6VX5YEMII7PDxcGw6HNdsNLqoMckAWHTDA7e3t1Y+NjdWWQ5TMVXPzNaeqq6vTx48fXxgcHIyJrcmiAmbCXV5ezvZGkSs7ckVJBVk0wEy4c3NzouWj1AojFWRRBI987qtXr3YQuIWrIILc09MTECvwEhwwCai42RSAbLfbU2JF14ICRp0YTU1N9ds5WuaGmKIguq6srEwMDQ0Fjhw5khIy8BIMMILrcDisfr8fxm+5lnNbPw+Q6+rqsp0hKpUqO41IiEtQwO3t7Zb5+XnLdumhEgIAMw3o8WpoaAh6vd51ofquBQFMgirhUDc0NKSFDLpKBoxMc2tra20wGIRZjcKVdhumBEFXXV1d7OfPnwtCmGohAKs7OjrMPp/PRkyzMDUS/PHu3bsFMdUlAWY2iWZnZzXCFI+kgtrHN2/e9Pf19SVLiapLBaxua2uzLy4uGolpFrZiMqLqlVICLt6Akfa+fPmyjphmYeGi1CCqPnv2rL+UXq5SABPtFYfrn1RBi61WKwRcAb5azAsw0V6RyTKSBy0+ceLEPN+hRb6As9o7MzNjJEOA4sIu1RdzBky0V1yg+VKHzg++ETUfwNl27/fv320kcpYGNmhxc3Pz2pcvX1a5+mI+gDUwWrS0tKQh5lkawNAurq2tTfh8vnmuvVucAIN57u7uNni93lrSNJIGLrPJdPDgwYWRkZEIl44ProBJ00harn81mTaHEzl1fHAFjI15PnfunGn//v1Gs9ms8fv98adPn676fD5BxlH37t2rczqdlTabTRcKhVITExPh0dHRiExss9mimR8zMzO/uZhp1oDRJLqPHz/+I7d5djqdhmPHjlmYAo/FYpmhoaHlb9++wYJs3hfAvXDhQjVN03/JZmxsbP3FixewmEy2i0/PFhfA2ETP/f39NXa7Pbsnh5CQC8GFPECTb9y4sSAbXep/U3u4jjJxArxr1y4IrvRyR8/37t3bkathSPB8NXkruCjtq1ev+uUEDGa6qalpY2pqaoltc4kL4Kz/xWFYsL+/v9put1cUEjZXyGzggp+/e/fuspyAIe/GxsYUFz/MCjBaFTgwMNAgt/+FQrIBwhYy27QeP368KFQQV0olAT88MDAwx3acmDVg3Nq/bMFsFXgJkUYpsPi8C374wIEDS+/fvw+zaQ+zBQzbLFT9+vXLglP3ZCmASnmXDxih3uEaaLEG7HA4bDjOd+YDis87QgEqNR2uo0usAbe2tv7j9/tpuSPofALiAgzez9fOFbK5VSrErd6HSLqxsTHy48ePRTaRdFkA5hJ4wbOFmlhwj21wJibEYoC5DDywBaxpbm6uW1xchJ1c5Spb0XzZaPJWieAOF307l6YSa8CNjY2NSlgKyheyUuACZJgAMDs7O8emT5o14IaGhiYc2sBF1ZhlO1kpPjdfeaEtPDc3N7NtAbP1yUrwuQRwAZVma6qVZJpRUbe9BrOFiwSmNMiiACZBFhvvL80zogRZpJkkDTw2uYjSTGppadmxsLAAB2Cw+QbJn2FjlsEUl0NHB5f1w2ybSWpcBvtJVyW3BeKsAZPBBsmNUt4MRRtsIMOF+ADmMi+LrQarjh49apqYmLDjMh7M1ueW44B/V1cX6wnwrABD3X3y5IkOlyk7LS0tmsuXL9cIMSokREWRUrfRlJ1Lly6xmh7MGnAmk8FmRGm7TrrjM/mdC2Bslq08ePCgrpDW8O2VKqbJkO7169fnpdTW3Ly4DvbD+5wA4xJo3blzpxaWrOQKgC9clM5WkJeWlpK3b99elBMwRNB79uxZGR8fD7GZzcEVcPbMBRw2XYF1SZ2dnZViDPkVgvzu3btVudcn8dnOgbUGgzBx8sOwPunQoUMmmqY1oVAoMTw8vF7quiRUYSCIu3jxogUm18OSlc+fP2/IvS6Jj//lpMGbgNW4dnjIaTqlyBvMc01NDadlK3wAkwXgUtDMkweYZ9EXgCMzjfvAg0wMRMuWr3nmrMHITO/bt88yPT1dhUuvlmiSxSRhPtEz+nROQRZ6CXq1bt26Va+EWZaYMOL9GejYWtjS8OHDh3C0PKeLF+BMJkOCLU5i5v8wnzXBzNz4As6eZobDdg78RaeMN/ls21AyYOSLYb0S2eVdvIrC0N5lNnOg830JLw3eBIxNz5Z4IpYvZabvlWU7YaTFpONDnEoghPbyaiYxi4O2doCImhw+KRxo5mlofCJnQXwwSgQialxGmYQTsbwpQWDV0tLCadSo0Bfz9sE5mqyBY3VwXSAuLy5uuWN3rA4z4ILTRomp5gaU+fTmaSuZnp6e+VICK0FNdK6pJkfb8QOMomYsj7ZjQNaQwyn5A965cye+h1OiYkE/NUypwXmZCz8E4r1VymhRsa8SJMjKbTrB1J7h4eFasit8MfH/t02wIg6IZvpjl8ulI0HX1oBzgqoE24l0xavNf08IrsFMyL29vfTY2Fgtiaz/HwmC29nZufD8+fOYGHBL7skqVpOgE4RAlg+u6IA328gEMoOxVJqLshTNROcEXmrwyds98GL2MbvdblF8bq69kAQw0mQEORwOa7bTfC4ACxdsQXj69OklqeBKYqJzm1AURcF0H+vq6qp5ZWWFwnVLiGLxBdv7qIfKYrGErl27tsZ2I2+26Rd7TjINzgGtaW9vrwwEAlXlHGEjf1tfX7/q9XphPZEgx/4Ug8q8LwtgpskeHR21xWIxupy0GcDCPzj71+l0rkhpkmXzwflqHUwYAJPd0dFhmpmZqQLfDCZbqWYb+drq6uoM0lqKotJstt7nopVcnpVNg/NF2W/fvgXIRqVpMwJrs9kohq9NidV5oTjAjN4vjcvl0jJBwz1cNZoJlqZpZI6TcmstFj64UC1EZpsJOhKJZCHjAhr5WKPRmKmqqgp3dHSEBwcHYc8MWc1xPpliYaK38s8A+tOnTzT46HQ6rZELNtJW+L+ysjJhtVojV65cCfX19aVxBCtpTxYXn1EAthq2m+ju7qYnJydNkUiEZsIWw4wzgUL6ZrM5aTabo21tbZGRkRFYI5TBwccWky22GryFVsM3w9KZCp/PpwsEAoZ4PK5DwAvBzjXvCCAzH/Q3g8FAaTSapF6vj5vN5kRXV1fE7XaDb4W9LgFsds9LJVyKApwTeaNvB+2mPB6P+s2bN7rp6emKaDQKzS0d/D2ZTGoA/qZv/5MEQFKr1RDpZrRabYqmaQCaslqtyfPnz8c2TS88DyaYUhJUrIOsUrSCATGr5TzSysJUMtDcMvMRAg+5kVfkkgABLJfkJcqXAJZI0HJlQwDLJXmJ8iWAJRK0XNkQwHJJXqJ8CWCJBC1XNgSwXJKXKF8CWCJBy5XNv8fuLQCAD5e6AAAAAElFTkSuQmCC`,
    "PauseIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAhCAYAAADOHBvaAAAAAXNSR0IArs4c6QAAAMtJREFUWEdjZBggwIhs761btwwYGRkFkMVYWFgeKCoqPiDHfffv3xf48+ePAbpeVVXVA3CLb926dZ6BgQFDEUjTv3//CjU0NCaQYvnNmzcDGBkZ1+PQcwFs8e3btx3+//+/H4/BB9TU1BxJsfjWrVsg8xxw6Rm1GFfIjAY1wXQ2mrjQg2g0O41mJ1gIjBYgowUIRgiMFpmjReZoJTFaSYxWEgQrB5iCwV1kQruT9////4/SRUXy3gI1NbVEor3LwMBw69YtUO8yH5ceADqDdhRm0VQiAAAAAElFTkSuQmCC`,
    "PlayZIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAYAAADud3N8AAAAAXNSR0IArs4c6QAAARpJREFUSEvd1t0NgjAQwHEuAZ5doQ+lMI4jOIG6AZ1A3QA3cATmgJC4gSzQnoGIUcNHC1ce7HOTX67/hytUVbXXWqeIWCPiUQhx8xwfKIoCf4wsCALJGLu7svvQ1gKA1Pf9qwt8EH1N2UwroyjKKKeeQjvrDgA7znlOgZuinUXS2xYl6T0LXdp7CTq7NwVq3ZsSNe5Njpr0doWO9naN9vZeC3335pzLVdFGRsTtf6MAUGutL0KIdK1Jc6XULkmS9mPgGs0BQP6uRCdo85RKKRnH8blv/5KiXbcwDM+MsXpo4VOiX93GfhgUaG83J+hUN1LUtBslatyNArXuNhtd0s0apeg2ipZl+UDEzcclkm5T6AERT57nkXYbQ59nnvPXuWPpvAAAAABJRU5ErkJggg==`,
    "PlayNextIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAYAAADud3N8AAAAAXNSR0IArs4c6QAAAZNJREFUSEvV18ttwkAQANAZy+tzSogPtuBGCUkHSQehAuQO7A6gglACHcQdhCtYlinBd38mGhRHRmziZT9I2RMCvI8dZsZjBACoquohDMOaX7tYZVmu+r5Piaj2PC/F4/G4AIBPADgBQBbH8dYmXFXVomka3n9YNaNvAPA+enMrhMjCMOQfYbyKongioo/xRjKUP6/7vs9ms9naVL0FHSw+7Wscx3tdXAcdLO2Qm6CMayWaKfoTciHEs2qi2UIHfC2E2EzhtlGlkLtAz6cmol0QBIns1M7QId6ImEZRlI3Lyzn6jV1k+b3Qi9pu2/ZRtQ3qNqCr6xBxR0QvKr3XGirb6LeG/39RROTBIL9nePOu65a+798lkU6IuIyiKOf/yHnJEFEWBMF6PG+5RM+hnM/nVyOOdZQThYiSv4Y52+hGCJFOja620BwRsyFRpgraCOVQdl1384RogiqFUnZqHXSPiIlqKI1Q3VBqo3wrats2kdXcVNIoo6OYX7QvHUB2jeQBCpC/6PpR8XA4pJ7nrdjiCvgCWcuQkZ35fVAAAAAASUVORK5CYII=`,
    "MuteIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAhCAYAAAC4JqlRAAAAAXNSR0IArs4c6QAAA7dJREFUWEfFV11S2zAQ1or4nZ6geIbI8FQ4QckJCicoOQHNCZKcAHIC4ATACUhPQHhiLME4NyB9dqxtVmN5ZEXOD2WmfshDoux+2v2+b9fA/vMD/5pfSnkURdEsjuPpR2J9GECWZbvz+byPiL8oMQB02u32eFsQFQClVF9rfQ4A4yiKenEcz5qCZVm2l+f5HWPsyDkzEkIYMNs8BsDr6+up1poCmgcR75MkOQsFUkqdIOI1Y2zP+/1GCNHdJrmpHH1IKc8ZYxS0egBg0G63h+53b29vF0VRXDUk+TgA6mee549eSZnWundwcGASKqUubb8/HQAFfHl52dvZ2SEQtdISuRhjF4h4uqa8wQqkaXrKOb+mtkZRNPTVUlMBSQoAHhFx10k2AwDiw533vY8nCEBKSZc6KQ9PoyjquCCWZBjiA2NsCgBdRKRgTU8QgFLqFyJeOn8iEMdWZUEfSNN0AAB9L9MYAB68YO6RRhL68VyVWRVQ6U3ZEbFPXoCIX6j3HogrRPwTAEfHVqrAB2GNC8iAEHHg15Vzfq61JmNxzcYoY0HW7wFSVgCojQDwrdVqjdx+K6WIR5bMYyFEB6SU2NDUGSJ2AYD6V1MGIp6VVXDBGQBZlh3lef7kkK7qNymt1Wo9WTJTFVYBoBiGfAEFGGV4jmgBkKe820sh4jBJkqrCUkryFdva0ToAFGfCOR+6Vm1vt+BDr9Q48adqgcf8WRRFsWW9Z/uTTQAYgi1u/BxQwIQxNiptvEZCpdS74xtdIcSNY3hZeYnZpgBo3A7WKOOrO4w8A6oAUGKXdxsDoD+SAjjnPxxnMxchcIyx3+4+0ASgnDsVR7YCUCbrhMYxyXZ/f/+WzpT7gi0zfXUshKB20VCjcW4ddWMOuEptVMaiCh1KJKWk5Fa6UyFEbAO4BNVaP2xdAasAznkvpAxftkVRxIeHh9W+6IHrEgAX7YpZs/RT02yYAMAtKSbgAbXFh8BB6U50eBcAaA+kHrnjeBUoMhWaJ/7MWJoLxIv5fF65oPWNpWnYtJg0oVilDLvSlaR0l51pURQdak1wHDtVWbcFGVzlbFiaGeVAeyYrd+cJnU+S5N6oalV9G/aC0F9Cs4HOLQ00nxdrX0wCG00TZrJasmU7Ce25SrZFUQztkmt/XAugtE4au7UyBlAY4jWsdJNyF1x62dkIgDNElrZmB0jF/IbWBTemjQE4IPxXMouhliBNU5qgP91KueTbqgV+ub2lIggg9LLzaQAoY6DMtZEbaJvZAf3L/AVCj3VLrOF/ZQAAAABJRU5ErkJggg==`,
    "YlIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAgCAYAAACcuBHKAAAAAXNSR0IArs4c6QAAAnNJREFUWEfllsFx6jAQhiVsc3YJ+MB6uL10gDsgFSSpIOkgpIK8VJB0kHTw6CDcGEswpoNwxpjNrEdiZI3tyGHM5emWIFmf9P/a/Tk7c2RZNsrz/JUxNkXE6ziOP7p+knddYM5P03Q2GAxeETFU/18AQNL1m7+GkFI+IuLc3hAAOn+z84Isy8I8z58ZY7d1J+4dQun/jzE2arryXiGklFPG2Luhfy1HEwTJxxgLfd9/iaJoay52kmOz2dwXRfHXxXB1EEIIko5eEI1tEASJCdIK8ZP+rp5Yr9ez4/H4bsyvgDRCKP1p4R+XG9BzmuRI03TOOSdJ9FgCwBX9UQshhKCNCaDRgL8xppTyARHpZZUDEZ/iOJ6XEGbV63LqNjlIyv1+Px0Oh0tTfyEEeeterd0FQRCVENYPZ3FoOaSU9JJmthGVzz71LdNtlBBpmr5xzm/O2l0t1hBCiMyQs1LOLVkWvUHYL4JznozH44WSn6rulz50bxBKZqquVORovAHAnd5YCEGSlC+vbwizSFUkEUKcAP8LCCrVutu+AMDDReWw+gWzjElpjF5POXqTwzQeIn7EcXxt3ILplWWfENp426IokslkcmrfVg25KyFWq9XI87zWsOJayMwGRrXC87xFFEU7vd6MhZzz3eFwuKptYOdAtSUrO5dUGljdKQnE9/1nVf9dL4K1JSsrGG8BIDoZs22HmhzQClQHURdqTJ84xTs7B7RRuMQ726hOEKoPOAWdH5JVGATB3DSqkxzmqV0M22vk1zAqlFD0092xos5FIIyqZ8a0E8hFIWjXtgTt/Kab0naXD6hG9cg5DxExAYBll/U09xtsmJNzr7Qi0AAAAABJRU5ErkJggg==`,
    "UnFdIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAAAXNSR0IArs4c6QAAAadJREFUWEftWN1RwkAQ3h3IOyWQhwR4ix1AB1qCHWgFQgWOFVgCYwXQgXljcvdwdGCeScjKOsbJREz2wMzocPd6e9lvv9x9+4MgXMaYYZZlSwCIEHEeBMGi6ajW+gEA7ogo9Txv5vv+VuIKJUZso5RaAcC0Yn8VhmF87LwxJsqy7LXcK4riZTweX0t8nQwIEWdBEKyPOdFaT4mIAyjXOgzDmQNUMuAYYibcHaq/iPqzvxyGWIF3u90jIg6adAIRIyL6srFkKAWAoyJa9el53i0qpQwADCWiVbWxBCT9fIxa67dq5NKTHQECTJJkjoicCK1WF4CIaPGRy5RSnMEb71Adbb/fj33f57vxbRljBnmeRzYRclXAyVqcXG0+fo6tA9TGnmPofzL0p549dwdENG+jsr7fhTByN8O5jGzBnFCgSV2kDIjbFStV7RDQFjebzbDX6z23hXBO+YGIKRE1lh9sk+f5vViHLrdibPtV5b5jqI0px5Bj6AcGxDqktV4exK06dBIPrA6Drt+fD30q+oqbgaIonkajUWNCrnQz8X6/v5lMJqKR3jvTkq+FkbX/lwAAAABJRU5ErkJggg==`,
    "FdIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAmCAYAAACoPemuAAAAAXNSR0IArs4c6QAAAg5JREFUWEftWMtxwjAQlQD7TAn4gIAbdBA6SCpIqCChEpIKQiqADkIH4caMBGOX4LuRNlmPbBzQGAnyIRnraO2unp6lXe2j5EIHNeEKw7CVJMmVaQ4AYt/3l0EQRC57CsOwmSTJtW3MT8A0oGdCiBFUIWjked4gCILYBpyO+0oIaZXZA8Dc9/0xbjoHZutcCDxijE1tgK3X62ul1MzGlhCCmx7mwIQQMwAwUm0KSCkdttvthc1iQogrAEDGbMciBabZCotelNJYKXXwq/T3l263+2i7CtoJIR4A4BYAmvt+tVqtuf89BWagOpZSDnq9ntMBdwFatF2tVq1Go/FWBJcC45zfEULw0GcjYowFpy50ih/nHP9YfjkqYMdYrBg7xtD+vC1jS8bYwDX4Ofac8zdCSD+LkeUxrGM4kd4KpdTYNU+dA6qQ5yY6zq4kYS7xPK+PRdo2o58LZt9fVwjEMTe+Lr56wVPiVcBcWasYqxhzZcDVvjpj/4+xrO+jlEa/WZKUUk3sW4vNSN73XUwRNzQjl/HsqZqRkqtp+4Kt2reMxD/PWKxlph+RCFA72W63hxIBvvfr9bq1qPLRtEwZY08uZWaz2dxLKe+cRBWtX2CCPSbY5Vi+U4ZCAS9/XWjWjqp+GTIAuOl0OnMb1gx5sswtklLuhDu01HLQxELAS51tZSpdh/O+tQTVQko5wrjvF5yGMz+76SwAAAAASUVORK5CYII=`,
}

export default dataUrl
相关推荐
庄毕楠2 分钟前
【Chrome】下载chromedriver的地址
前端·chrome
大猫会长2 分钟前
关闭chrome自带的跨域限制,简化本地开发
前端·chrome
excel9 分钟前
JavaScript 中使用 Set 对数组去重并排序的简洁示例
前端
不断努力的根号七1 小时前
qt框架,使用webEngine如何调试前端
开发语言·前端·qt
德育处主任1 小时前
p5.js 线段的用法
javascript·数据可视化·canvas
伍哥的传说2 小时前
React性能优化终极指南:memo、useCallback、useMemo全解析
前端·react.js·性能优化·usecallback·usememo·react.memo·react devtools
JuneXcy3 小时前
leetcode933最近的请求次数
开发语言·javascript·ecmascript
saadiya~3 小时前
Vue + WebSocket 实时数据可视化实战:多源融合与模拟数据双模式设计
vue.js·websocket·信息可视化
midsummer_woo9 小时前
基于springboot的在线教育系统(源码+论文)
vue.js·spring boot·mysql