H5: 使用Web Audio API播放音乐

简介

记录关于自己使用 Web Audio API 的 AudioContext 播放音乐的知识点。

需求分析

在这里插入图片描述

1.列表展示音乐;
2.上/下一首、播放/暂停/续播;
3.播放模式切换:循环播放、单曲循环、随机播放;
4.播放状态显示:当前播放的音乐名、播放时间、总时间、进度条效果;
5.播放控制器显示在底部区域;
6.支持音量调节;
7.浏览器隐藏、显示的交互后,也能正常有效播放(播放、声音)。

注意

安卓IOS上有不同的兼容性,所以采用了 Web Audio API 的 AudioContext ,兼容性强大(但是截止写文章前,IOS17+版本不支持,没有声音)。

稍微复杂点点的逻辑就是AudioContext与手机系统的关联,可以看看 AudioContext: createMediaElementSource。

在这里插入图片描述

具体实现

test/music/musicPlayer/musics.ts
test/music/musicPlayer/useMusicPlayer.ts
test/music/index.vue

1.test/music/musicPlayer/musics.ts

interface musicItem {
  title: string
  src: string
  time: string
  mp3Name: string
}
const musicList: musicItem[] = [
  {
    title: 'How to Love',
    src: '',
    time: '03:39',
    mp3Name: 'sx_music_HowtoLove_CashCash'
  },
  {
    title: '空空如也',
    src: '',
    time: '03:34',
    mp3Name: 'sx_music_kongkongruye'
  },
  {
    title: '2 Soon',
    src: '',
    time: '03:19',
    mp3Name: 'sx_music_Soon_JonYoung'
  },
  {
    title: '孤勇者',
    src: '',
    time: '04:16',
    mp3Name: 'sx_music_guyongzhe'
  },
  { title: '秒针', src: '', time: '02:58', mp3Name: 'sx_music_miaozhen' },
  {
    title: '热爱105˚的你',
    src: '',
    time: '03:15',
    mp3Name: 'sx_music_reai105dudeni'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  },
  {
    title: '她会魔法吧',
    src: '',
    time: '03:01',
    mp3Name: 'sx_music_tahuimofaba'
  }
] // 音乐列表信息

export { type musicItem, musicList }

2.test/music/musicPlayer/useMusicPlayer.ts

import { ref, nextTick } from 'vue'
import { musicList } from './musics'

enum PlayMode {
  REPEAT, // 循环播放
  SINGLE_CYCLE, // 单曲循环
  RANDOM // 随机播放
}

const musicPlayer = ref<HTMLAudioElement | null>()
const musicPlayingIndex = ref(-1) // 播放的音乐的下标
const musicIsPlaying = ref(false) // 是否播放中
const currentTime = ref(0) // 正在播放的音乐时间点
const musicPlayMode = ref(PlayMode.REPEAT) // 播放模式
const progressInterval = 500 // 计时器触发的频率
let defaultVolume = 1 // 音量 0-1
let timer: NodeJS.Timer | null = null // 计时器  ---此处需要在 .eslintrc.js/.cjs 文件中配置 globals: { NodeJS: true }
let source: MediaElementAudioSourceNode | null = null
let audioCtx: AudioContext | null = null
let gainNode: GainNode | null = null
let audioContextAttr: string | null = null
if ('AudioContext' in window) {
  audioContextAttr = 'AudioContext'
} else if ('webkitAudioContext' in window) {
  audioContextAttr = 'webkitAudioContext'
}

const useMusicPlayer = () => {
  const _getMusicFile = (mp3Name: string) => {
    // 此处需要相对路径
    // vite项目
    // return new URL(`../../../assets/music/${mp3Name}.mp3`, import.meta.url).href
    // webpack项目
    return require(`../../../assets/music/${mp3Name}.mp3`)
  }

  /** 设置:音量百分比 0-100 变为 0-1
   * @param v number 0-100
   */
  const _saveDefaultVolume = (v: number) => {
    let num = v
    if (v < 0) {
      num = 0
    } else if (v > 100) {
      num = 100
    }
    defaultVolume = num / 100
    return defaultVolume
  }

  /**
   * 计时器:回调-更新显示-MP3的播放时间
   */
  const _intervalUpdatePlayTime = () => {
    const player = musicPlayer.value
    if (!player) return
    currentTime.value = player.currentTime
  }

  /**
   * 计时器:清除
   */
  const _clearTimer = () => {
    if (!timer) return
    clearInterval(timer)
    timer = null
  }

  /**
   * 计时器:绑定&开始
   */
  const _startTimer = () => {
    _clearTimer()
    timer = setInterval(_intervalUpdatePlayTime, progressInterval)
  }

  /**
   * 方法:取两个值之间的随机数
   */
  const _random = (min = 0, max = 100) => Math.floor(Math.random() * (max - min + 1)) + min

  /**
   * 销毁:断开audio与AudioContext之间的链接
   */
  const _destroyConnect = () => {
    if (source) {
      source.disconnect()
    }
    if (gainNode) {
      gainNode.disconnect()
    }
    if (audioCtx) {
      audioCtx.close()
    }
    source = null
    gainNode = null
    audioCtx = null
    musicPlayer.value = null
  }

  /**
   * 音乐:初始化audio与AudioContext的绑定
   * 目的是为了 IOS 上能调整音量
   */
  const _init = () => {
    if (!audioContextAttr) return
    // 先暂停已有的播放
    pause()
    // 对已创建的绑定关系进行解绑
    _destroyConnect()
    // 若在body中找得到对应的dom,则进行移除
    const findDom = document.getElementById('musicPlayerAudio') as HTMLAudioElement
    if (findDom) {
      findDom.remove()
    }
    // 创建audio,加入body中
    const dom = document.createElement('audio')
    dom.id = 'musicPlayerAudio'
    document.body.appendChild(dom)
    // 给audio绑定播放结束的回调函数
    dom.onended = onAudioEnded
    // 创建AudioContext、source、gainNode,进行关联(便于IOS控制音量)
    const UseAudioContext = (window as any)[audioContextAttr]
    audioCtx = new UseAudioContext()
    if (!audioCtx) return
    source = audioCtx.createMediaElementSource(dom)
    gainNode = audioCtx.createGain()
    source.connect(gainNode)
    gainNode.connect(audioCtx.destination)
    // 设置音量
    if (defaultVolume === 0) {
      dom.muted = true
    } else {
      dom.muted = false
    }
    gainNode.gain.value = defaultVolume
    // 存储dom,便于后续访问audio对应的属性
    musicPlayer.value = dom
    // 若播放控制器的状态未启动,则启动
    if (audioCtx && audioCtx.state === 'suspended') {
      audioCtx.resume()
    }
  }

  /**
   * 音乐:播放器-音量调整
   */
  const setVolume = (volume: number) => {
    const v = _saveDefaultVolume(volume)
    const player = musicPlayer.value
    if (!player) return
    if (v === 0) {
      player.muted = true
    } else {
      player.muted = false
    }
    if (!gainNode || !gainNode.gain) return
    gainNode.gain.value = v
  }

  /**
   * 音乐:播放器-暂停
   */
  const pause = () => {
    const player = musicPlayer.value
    if (!musicIsPlaying.value || !player) {
      return
    }
    musicIsPlaying.value = false
    player.pause()
    _clearTimer()
  }

  /**
   * 音乐:播放器-播放
   */
  const playByLast = () => {
    const player = musicPlayer.value
    if (!player || !player.src) return
    if (audioCtx && audioCtx.state === 'suspended') {
      audioCtx.resume()
    }
    nextTick(() => {
      // play触发时,会先自动加载资源
      player.play().then(() => {
        musicIsPlaying.value = true
        _startTimer()
      })
    })
  }

  /**
   * 音乐:播放器-播放-通过下标
   */
  const playByIndex = (index: number) => {
    if (index < 0 || index + 1 > musicList.length) {
      return
    }
    musicIsPlaying.value = false
    // 重新初始化,便于释放上一个播放器所占用的内存
    _init()
    const player = musicPlayer.value
    if (!player) {
      return
    }
    // 重置当前播放了的时长
    currentTime.value = 0
    // 更新要播放的下标
    musicPlayingIndex.value = index
    if (!musicList[index].src) {
      // 若资源路径不存在,则进行对应的路径引入
      musicList[index].src = _getMusicFile(musicList[index].mp3Name)
    }
    if (!musicList[index].src) {
      console.error('find music file failed')
      return
    }
    player.src = musicList[index].src
    playByLast()
  }

  /**
   * 音乐:随机播放
   */
  const randomPlay = () => {
    const index = _random(0, musicList.length - 1)
    playByIndex(index)
  }

  /**
   * 音乐:播放器-下一首
   */
  const playNext = () => {
    if (musicPlayMode.value === PlayMode.RANDOM) {
      randomPlay()
    } else {
      const index: number =
        musicPlayingIndex.value + 1 === musicList.length ? 0 : musicPlayingIndex.value + 1
      playByIndex(index)
    }
  }

  /**
   * 音乐:播放器-上一首
   */
  const playPrev = () => {
    if (musicPlayMode.value === PlayMode.RANDOM) {
      randomPlay()
    } else {
      const index: number =
        musicPlayingIndex.value < 1 ? musicList.length - 1 : musicPlayingIndex.value - 1
      playByIndex(index)
    }
  }

  /**
   * 回调:播放结束后,下一首播放什么
   */
  const onAudioEnded = () => {
    switch (musicPlayMode.value) {
      case PlayMode.REPEAT:
        playNext()
        break
      case PlayMode.SINGLE_CYCLE:
        playByIndex(musicPlayingIndex.value)
        break
      case PlayMode.RANDOM:
        randomPlay()
        break
      default:
        break
    }
    return true
  }

  /** 自动播放音乐 */
  const startPlayInRoom = () => {
    // 用户第一次点击时,自动播放音乐
    const initMusicAutoPlayOnReload = () => {
      document.removeEventListener('click', initMusicAutoPlayOnReload, true)
      playByIndex(0)
    }
    document.addEventListener('click', initMusicAutoPlayOnReload, true)
  }

  return {
    musicList,
    musicPlayer,
    musicPlayingIndex,
    musicIsPlaying,
    currentTime,
    musicPlayMode,
    setVolume,
    pause,
    playByIndex,
    playByLast,
    playPrev,
    playNext,
    _clearTimer,
    startPlayInRoom
  }
}

export { PlayMode, useMusicPlayer }

3.test/music/index.vue

<template>
  <div class="music-box">
    <!-- 音乐列表 -->
    <div class="music-list">
      <div
        v-for="(music, index) in musicList"
        :key="index"
        class="music-item"
        :class="{ 'music-item-active': musicPlayer.musicPlayingIndex.value === index }"
        @click.stop="switchAudio(index)"
      >
        <div class="item-left">
          <div class="item-left-title">
            {{ music.title }}
          </div>
          <svg
            v-if="musicPlayer.musicPlayingIndex.value === index && musicPlayer.musicIsPlaying.value"
            id="equalizer"
            width="13px"
            height="11px"
            viewBox="0 0 10 7"
            version="1.1"
            xmlns="http://www.w3.org/2000/svg"
            xmlns:xlink="http://www.w3.org/1999/xlink"
          >
            <g fill="#3994f9">
              <rect
                id="bar1"
                transform="translate(0.500000, 6.000000) rotate(180.000000) translate(-0.500000, -6.000000) "
                x="0"
                y="5"
                width="1"
                height="2px"
              ></rect>
              <rect
                id="bar2"
                transform="translate(3.500000, 4.500000) rotate(180.000000) translate(-3.500000, -4.500000) "
                x="3"
                y="2"
                width="1"
                height="5"
              ></rect>
              <rect
                id="bar3"
                transform="translate(6.500000, 3.500000) rotate(180.000000) translate(-6.500000, -3.500000) "
                x="6"
                y="0"
                width="1"
                height="7"
              ></rect>
            </g>
          </svg>
          <svg
            v-else-if="musicPlayer.musicPlayingIndex.value === index"
            id="equalizer"
            width="13px"
            height="11px"
            viewBox="0 0 10 7"
            version="1.1"
            xmlns="http://www.w3.org/2000/svg"
            xmlns:xlink="http://www.w3.org/1999/xlink"
          >
            <g fill="#3994f9">
              <rect x="0" y="5" width="1" height="2px"></rect>
              <rect x="3" y="2" width="1" height="5"></rect>
              <rect x="6" y="0" width="1" height="7"></rect>
            </g>
          </svg>
        </div>
        <div class="item-right">
          {{ music.time }}
        </div>
      </div>
    </div>
    <!-- 播放控制 -->
    <div class="music-control">
      <div class="control-content">
        <div class="control-content-left">
          <div
            class="music-btn prev"
            @touchstart.passive="onTouchEvent"
            @touchend.passive="onTouchEvent"
            @click="prev"
          />
          <div
            :class="['music-btn', musicPlayer.musicIsPlaying.value ? 'pause' : 'play']"
            @touchstart.passive="onTouchEvent"
            @touchend.passive="onTouchEvent"
            @click="togglePlayer"
          />
          <div
            class="music-btn next"
            @touchstart.passive="onTouchEvent"
            @touchend.passive="onTouchEvent"
            @click="next"
          />
        </div>
        <div class="control-content-center">
          <div class="center-title">
            {{ currentMusicTitle || '-' }}
          </div>
          <div ref="audioProgressWrap" class="center-progress-wrap">
            <div ref="audioProgress" class="center-progress-wrap-active" />
          </div>
          <div class="center-time">
            <div class="center-time-now">
              {{ formatSecond(musicPlayer.currentTime.value) }}
            </div>
            <div class="center-time-total">
              {{ currentMusicTotalTimeStr }}
            </div>
          </div>
        </div>
        <div class="control-content-right">
          <div
            v-if="musicPlayer.musicPlayMode.value === PlayMode.REPEAT"
            class="music-btn playRepeat"
            @touchstart.passive="onTouchEvent"
            @touchend.passive="onTouchEvent"
            @click="nextPlayMode"
          />
          <div
            v-if="musicPlayer.musicPlayMode.value === PlayMode.SINGLE_CYCLE"
            class="music-btn singleCycle"
            @touchstart.passive="onTouchEvent"
            @touchend.passive="onTouchEvent"
            @click="nextPlayMode"
          />
          <div
            v-if="musicPlayer.musicPlayMode.value === PlayMode.RANDOM"
            class="music-btn playRandom"
            @touchstart.passive="onTouchEvent"
            @touchend.passive="onTouchEvent"
            @click="nextPlayMode"
          />
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
  import { ref, computed, watch } from 'vue'
  import { PlayMode, useMusicPlayer } from './musicPlayer/useMusicPlayer'
  import { musicList } from './musicPlayer/musics'

  const systemSoundMode = ref(true) // 该变量应该在store中,便于设置页面控制全局声音的开启与否
  const musicPlayer = useMusicPlayer()

  const audioProgressWrap = ref()
  const audioProgress = ref()

  /** 当前播放的音乐名 */
  const currentMusicTitle = computed(() =>
    musicPlayer.musicPlayingIndex.value + 1 > 0
      ? musicList[musicPlayer.musicPlayingIndex.value].title
      : ''
  )

  /** 当前播放的音乐总时间 */
  const currentMusicTotalTimeStr = computed(() =>
    musicPlayer.musicPlayingIndex.value + 1 > 0
      ? musicList[musicPlayer.musicPlayingIndex.value].time
      : '00:00'
  )

  /** 操作:切换播放模式 */
  const nextPlayMode = () => {
    musicPlayer.musicPlayMode.value = (musicPlayer.musicPlayMode.value + 1) % 3
    switch (musicPlayer.musicPlayMode.value) {
      case PlayMode.REPEAT:
        console.log('循环播放')
        break
      case PlayMode.RANDOM:
        console.log('随机播放')
        break
      case PlayMode.SINGLE_CYCLE:
        console.log('单曲循环')
        break
      default:
        break
    }
  }

  /** 事件:当点击按钮时的过渡效果 */
  const onTouchEvent = (event: Event) => {
    const tg = event.currentTarget as HTMLElement
    if (!tg) return
    if (event.type === 'touchstart') {
      tg.classList.add('touch')
    }
    if (event.type === 'touchend') {
      tg.classList.remove('touch')
    }
  }

  /** 格式化:秒数=>ss:mm */
  const formatSecond = (second: number) => {
    let hourStr = `${Math.floor(second / 60)}`
    let secondStr = `${Math.ceil(second % 60)}`
    if (hourStr.length === 1) {
      hourStr = `0${hourStr}`
    }
    if (secondStr.length === 1) {
      secondStr = `0${secondStr}`
    }
    return `${hourStr}:${secondStr}`
  }

  /** 操作:播放所选音乐 */
  const switchAudio = (index: number) => {
    const player = musicPlayer.musicPlayer.value
    if (!systemSoundMode.value) {
      window.alert('所有声音已关闭')
    }
    if (player?.src && player?.src.includes(musicList[index].mp3Name)) {
      if (musicPlayer.musicIsPlaying.value) {
        return
      }
      musicPlayer.playByLast()
    } else {
      musicPlayer.playByIndex(index)
    }
  }

  /** 操作:上一首 */
  const prev = () => {
    if (!systemSoundMode.value) {
      window.alert('所有声音已关闭')
    }
    musicPlayer.playPrev()
  }

  /** 操作:下一首 */
  const next = () => {
    if (!systemSoundMode.value) {
      window.alert('所有声音已关闭')
    }
    musicPlayer.playNext()
  }

  /** 操作:播放/暂停 */
  const togglePlayer = () => {
    const player = musicPlayer.musicPlayer.value
    if (!systemSoundMode.value) {
      window.alert('所有声音已关闭')
    }
    if (musicPlayer.musicIsPlaying.value && player?.src) {
      // 正在播放,则暂停
      musicPlayer.pause()
    } else if (!player?.src) {
      // 未开始播放,则播放第一首
      musicPlayer.playByIndex(0)
    } else {
      // 暂停了,则继续播放刚才的
      musicPlayer.playByLast()
    }
  }

  /** 监听:当前播放中的音乐的进度时间=>进度条变化 */
  watch(
    () => musicPlayer.currentTime.value,
    () => {
      const player = musicPlayer.musicPlayer.value
      if (!audioProgressWrap.value || !audioProgress.value || !player) {
        return
      }
      const offsetLeft =
        (player.currentTime / player.duration) * audioProgressWrap.value.offsetWidth
      audioProgress.value.style.width = `${offsetLeft}px`
    }
  )
</script>

<style lang="less" scoped>
  @bottomHeight: 97px;
  @controlHeight: 63px;
  @controlBottom: 34px;
  .music-box {
    width: 100%;
    height: 100%;
    background-color: #141624;
    position: relative;
    display: flex;
    flex-direction: column;
  }
  .music-list {
    flex: 1;
    overflow-y: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    &::-webkit-scrollbar {
      display: none;
    }
    .music-item:nth-of-type(1) {
      margin-top: 7px;
    }
    .music-item {
      padding: 12px 20px 19px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      font-size: 14px;
      font-weight: 500;
      line-height: 120%;
      color: #8f9095;
      .item-left {
        display: flex;
        align-items: center;
        .item-left-title {
          height: 17px;
          margin-right: 10px;
        }
        #equalizer {
          position: relative;
        }
        #bar1 {
          animation: bar1 1.2s infinite linear;
        }
        #bar2 {
          animation: bar2 0.8s infinite linear;
        }
        #bar3 {
          animation: bar3 1s infinite linear;
        }
        #bar4 {
          animation: bar4 0.7s infinite linear;
        }
        @keyframes bar1 {
          0% {
            height: 2px;
          }
          50% {
            height: 7px;
          }
          100% {
            height: 2px;
          }
        }
        @keyframes bar2 {
          0% {
            height: 5px;
          }
          40% {
            height: 1px;
          }
          80% {
            height: 7px;
          }
          100% {
            height: 5px;
          }
        }
        @keyframes bar3 {
          0% {
            height: 7px;
          }
          50% {
            height: 0;
          }
          100% {
            height: 7px;
          }
        }
        @keyframes bar4 {
          0% {
            height: 2px;
          }
          50% {
            height: 7px;
          }
          100% {
            height: 2px;
          }
        }
      }
    }
    .music-item-active {
      .item-left {
        .item-left-title {
          color: #3994f9;
        }
      }
      .item-right {
        color: #3994f9;
      }
    }
  }
  .music-control {
    height: @bottomHeight;
    padding: 0 10px;
    background-color: #141624;
    .control-content {
      height: @controlHeight;
      border-radius: 7px;
      background-color: #1b1d2a;
      display: flex;
      align-items: center;
      justify-content: space-between;
      .control-content-left {
        display: flex;
        align-items: center;
        .prev,
        .pause,
        .play {
          margin-right: 10px;
        }
        .next {
          margin-right: 17px;
        }
      }
      .control-content-center {
        margin-top: 1px;
        flex: 1;
        .center-title {
          margin-bottom: 5px;
          line-height: 120%;
          font-size: 13px;
          font-weight: 500;
          color: #fff;
        }
        .center-progress-wrap {
          width: 100%;
          height: 2px;
          background-color: #3e404e;
          .center-progress-wrap-active {
            width: 0;
            height: 100%;
            background-color: #3994f9;
          }
        }
        .center-time {
          height: 50%;
          margin-top: 10px;
          display: flex;
          justify-content: space-between;
          align-items: center;
          .center-time-now,
          .center-time-total {
            font-size: 10px;
            font-weight: 400;
            line-height: 12px;
            color: #3994f9;
          }
          .center-time-total {
            color: #8f9095;
          }
        }
      }
      .control-content-right {
        padding-left: 10px;
      }
      .music-btn {
        width: 33px;
        height: 33px;
        &.prev {
          background: url('../../assets/images/music/music-prev.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-prev-touch.png');
          }
        }
        &.play {
          background: url('../../assets/images/music/music-play.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-play-touch.png');
          }
        }
        &.pause {
          background: url('../../assets/images/music/music-pause.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-pause-touch.png');
          }
        }
        &.next {
          background: url('../../assets/images/music/music-next.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-next-touch.png');
          }
        }
        &.playRepeat {
          background: url('../../assets/images/music/music-repeat.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-repeat-touch.png');
          }
        }
        &.singleCycle {
          background: url('../../assets/images/music/music-single-cycle.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-single-cycle-touch.png');
          }
        }
        &.playRandom {
          background: url('../../assets/images/music/music-random.png');
          background-size: 100%;
          background-repeat: no-repeat;
          &.touch {
            background: url('../../assets/images/music/music-random-touch.png');
          }
        }
      }
    }
  }
</style>

最后

觉得有用的朋友请用你的金手指点一下赞,或者评论留言一起探讨技术!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/119534.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何在macbook上删除文件?Mac删除文件的多种方法

在使用MacBook电脑时&#xff0c;桌面上经常会积累大量的文件&#xff0c;而这些文件可能已经不再需要或已经过时。为了保持桌面的整洁和提高电脑性能&#xff0c;我们需要及时删除这些文件。本文将介绍MacBook怎么删除桌面文件&#xff0c;以及macbook删除桌面文件快捷键。 一…

三维虚拟沙盘数字全景沙盘M3DGIS系统开发教程第18课

三维虚拟沙盘数字全景沙盘M3DGIS系统开发教程第18课 上一节我们实现了模型的移动控制。这次我们来实现模型的材质控制&#xff0c;首先我们找一个模型。在3dmax中如下&#xff1a; 可以看到这个模型很复杂。分成了很多层。我们先不管它。导入SDK后如下图&#xff1a; 有贴图还…

CentOS7安装部署StarRocks

文章目录 CentOS7安装部署StarRocks一、前言1.简介2.环境 二、正文1.StarRocks基础1&#xff09;架构图2&#xff09;通讯端口 2.部署服务器3.安装基础环境1&#xff09;安装JDK 112&#xff09;修改机器名3&#xff09;安装GCC4&#xff09;关闭交换分区&#xff08;swap&…

多模态论文阅读之BLIP

BLIP泛读 TitleMotivationContributionModel Title BLIP: Bootstrapping Language-Image Pre-training for Unified Vision-Language Understanding and Generation Motivation 模型角度&#xff1a;clip albef等要么采用encoder-base model 要么采用encoder-decoder model.…

基于javaweb+mysql的jsp+servlet学生成绩管理系统(管理员、教师、学生)

博主24h在线&#xff0c;想要源码文档部署视频直接私聊&#xff0c;9.9元拿走&#xff01; 基于javawebmysql的jspservlet学生成绩管理系统(管理员、教师、学生)(javajspservletjavabeanmysqltomcat) 运行环境 Java≥8、MySQL≥5.7、Tomcat≥8 开发工具 eclipse/idea/myecl…

CS免杀姿势

一&#xff1a;环境 1.公网vps一台 2.Cobalt Strike 4.7 3.免杀脚本 二&#xff1a;生成payload 生成一个payload c格式的x64位payload 三&#xff1a;免杀 下载免杀脚本 .c打开是这样的 把双引号里面的内容复制出来&#xff0c;放到脚本目录下的1.txt 运行生成器.…

BI数据可视化:不要重复做报表,只需更新数据

BI数据可视化是一种将大量数据转化为视觉形式的过程&#xff0c;使得用户可以更容易地理解和分析数据。然而&#xff0c;传统的报表制作过程往往需要手动操作&#xff0c;不仅耗时还容易出错。为了解决这个问题&#xff0c;BI数据可视化工具通常会提供一些自动化的数据更新功能…

3D 线激光相机的激光条纹中心提取方法

论文地址:Excellent-Paper-For-Daily-Reading/application/centerline at main 类别:应用——中心线 时间:2023/11/06 摘要 线激光条纹中心提取是实现线激光相机三维扫描的关键,根据激光三角测量法研制了线激光相机,基于传统 Steger 法对其进行优化并提出一种适用于提…

行情分析——加密货币市场大盘走势(11.7)

大饼昨日下跌过后开始有回调的迹象&#xff0c;现在还是在做指标修复&#xff0c;大饼的策略保持逢低做多。稳健的依然是不碰&#xff0c;目前涨不上去&#xff0c;跌不下来。 以太昨天给的策略&#xff0c;依然有效&#xff0c;现在以太坊开始回调。 目前来看&#xff0c;回踩…

渗透实战靶机3wp

0x00 简介 目标IP&#xff1a;xxxx.95 测试IP&#xff1a;xxxx.96 测试环境&#xff1a;win10、kali等 测试时间&#xff1a;2021.7.23-2021.7.26 测试人员&#xff1a;ruanruan 0x01 信息收集 1、端口扫描 21&#xff0c;ftp&#xff0c;ProFTPD&#xff0c;1.3.3c22&a…

LSTM缓解梯度消失问题

关于LSTM https://easyai.tech/ai-definition/lstm/ https://towardsdatascience.com/illustrated-guide-to-lstms-and-gru-s-a-step-by-step-explanation-44e9eb85bf21 为何LSTM缓解梯度消失问题 为什么LSTM会减缓梯度消失&#xff1f; - 知乎 LSTM引入长短期记忆&#xf…

TikTok小店运营的三大技巧!跨境电商必看

众所周知&#xff0c;国内的抖音早已风生水起&#xff0c;抖音给了很多普通人一夜暴富的机会。而Tiktok也跟随着抖音开启了商业模式&#xff0c;目前流量与机会都是不可小觑的。在店铺申请通过&#xff0c;成功入驻之后&#xff0c;又该如何运营&#xff1f;这篇文章为大家解答…

Webpack 中 loader 的作用是什么?常用 loader 有哪些?

说说webpack中常见的Loader&#xff1f;解决了什么问题&#xff1f;- 题目详情 - 前端面试题宝典 1、loader 是什么 loader是 webpack 最重要的部分之一。 通过使用不同的 loader&#xff0c;我们能够调用外部的脚本或者工具&#xff0c;实现对不同格式文件的处理。 loader…

Uniapp实现多语言切换

前言 之前做项目过程中&#xff0c;也做过一次多语言切换&#xff0c;大致思想都是一样的&#xff0c;想了解的可以看下之前的文章C#WinForm实现多语言切换 使用i18n插件 安装插件 npm install vue-i18n --saveMain.js配置 // 引入 多语言包 import VueI18n from vue-i18n…

王干娘和西门庆-UMLChina建模知识竞赛第4赛季第18轮

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 参考潘加宇在《软件方法》和UMLChina公众号文章中发表的内容作答。在本文下留言回答。 只要最先全部答对前3题&#xff0c;即可获得本轮优胜。 所有题目的回答必须放在同一条消息中&…

二十、泛型(3)

本章概要 构建复杂模型泛型擦除 C 的方式迁移兼容性擦除的问题边界处的动作 构建复杂模型 泛型的一个重要好处是能够简单安全地创建复杂模型。例如&#xff0c;我们可以轻松地创建一个元组列表&#xff1a; TupleList.java import java.util.ArrayList;public class TupleL…

简单工厂模式、工厂方法模式、抽象工厂模式

简介 将实例化代码提取出来&#xff0c;放到一个类中统一管理和维护&#xff0c;达到和主项目依赖关系的解耦&#xff0c;从而提高项目的扩展性和维护性。 工厂模式将复杂的对象创建工作隐藏起来&#xff0c;而仅仅暴露出一个接口供客户使用&#xff0c;具体的创建工作由工厂管…

51基于matlab模拟退火算法矩形排样

基于matlab模拟退火算法矩形排样&#xff0c;基于最低水平线算法完成矩形板材下料优化&#xff0c;输出最优剩料率和最后的水平线&#xff0c;可替换自己的数据进行优化&#xff0c;程序已调通&#xff0c;可直接运行。 51matlab模拟退火算法矩形排样 (xiaohongshu.com)

提升设备可靠性:人工智能(AI)在设备维护中的应用

当今社会&#xff0c;人工智能&#xff08;AI&#xff09;已从遥不可及的概念转变为现实&#xff0c;并被广泛地讨论和应用。AI技术已经渗透到各个领域&#xff0c;包括工业领域的设备维护。在现代工业领域&#xff0c;设备可靠性是企业持续运营和保持竞争力的关键因素之一。随…

正点原子嵌入式linux驱动开发——Linux Regmap驱动

在前面学习I2C和SPI驱动的时候&#xff0c;针对I2C和SPI设备寄存器的操作都是通过相关的API函数进行操作的。这样Linux内核中就会充斥着大量的重复、冗余代码&#xff0c;但是这些本质上都是对寄存器的操作&#xff0c;所以为了方便内核开发人员统一访问I2C/SPI设备的时候&…