H5: 使用Web Audio API播放音乐

news2024/10/6 6:02:08

简介

记录关于自己使用 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>

最后

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

适合女生的副业有哪些?整理了六个靠谱副业,女生必看

在这个互联网时代下&#xff0c;女生对于经济独立变得越来越看重。她们与男生一样&#xff0c;对于工作认真努力、追求进步&#xff0c;并且对于副业有着强烈的渴望和热爱。事实上&#xff0c;她们在副业领域的表现要远远超过很多男生&#xff0c;这一点不可否认。 女生在副业方…

Linux Crontab 定时任务

crond 服务 Linux 通过 crond 服务来支持 crontab。 查看 crond 服务是否已经安装 输入下面命令确认 crond 服务是否已安装。 systemctl list-unit-files | grep crond 如果为 enabled&#xff0c;表示服务正运行。 crontab 文件 crontab 要执行的定时任务都被保存在 /etc…

PostgreSQL 进阶 - 使用foreign key,使用 subqueries 插入,inner joins,outer joins

1. 使用foreign key 创建 table CREATE TABLE orders( order_id SERIAL PRIMARY KEY, purchase_total NUMERIC, timestamp TIMESTAMPTZ, customer_id INT REFERENCES customers(customer_id) ON DELETE CASCADE);“order_id”&#xff1a;作为主键的自增序列&#xff0c;使用 …

ElasticSearch集群环境搭建

1、准备三台服务器 这里准备三台服务器如下: IP地址主机名节点名192.168.225.65linux1node-1192.168.225.66linux2node-2192.168.225.67linux3node-3 2、准备elasticsearch安装环境 (1)编辑/etc/hosts&#xff08;三台服务器都执行&#xff09; vim /etc/hosts 添加如下内…

uniapp subNvue 写的视频播放

文件下载地址 https://download.csdn.net/download/weixin_47517731/88500016https://download.csdn.net/download/weixin_47517731/88500016 1:在pages.json中配置视频播放页面 {/* 视频详情页面 */"path": "pages/detail-video/detail","style&q…

一键解决 AirPods Pro 的沙沙声

每次我都以为是因为耳机受潮了&#xff0c;但每次这个方法都有效 [笑哭] 1、打开苹果手机&#xff0c;蓝牙连接 AirPods Pro 后&#xff0c;打开“设置”找到&#xff1a; 2、点进去&#xff0c;点击“关闭”&#xff1a; 瞬间&#xff0c;整个世界安静了&#xff01;

[已解决]AttributeError: module ‘numpy‘ has no attribute ‘float‘

1、问题&#xff1a; AttributeError: module numpy has no attribute float np.float was a deprecated alias for the builtin float. To avoid this error in existing code, use float by itself. Doing this will not modify any behavior and is safe. If you specifica…

基本微信小程序的拼车自助服务小程序-网约车拼车系统

项目介绍 拼车自助服务小程序的设计与开发的开发利用现有的成熟技术参考&#xff0c;以源代码为模板&#xff0c;分析功能调整与拼车自助服务小程序的设计与开发的实际需求相结合&#xff0c;讨论了拼车自助服务小程序的设计与开发的使用。 开发环境 开发说明&#xff1a;前…

虹科荣誉 | 喜讯!虹科成功入选“广州首届百家新锐企业”!!

文章来源&#xff1a;虹科品牌部 阅读原文&#xff1a;虹科荣誉 | 喜讯&#xff01;虹科成功入选“广州首届百家新锐企业”&#xff01;&#xff01; 近日&#xff0c;由中共广州市委统战部、广州市工商业联合会、广州市工业和信息化局、广州市人民政府国有资产监督管理委员会…

第G7周:Semi-Supervised GAN 理论与实战

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客 &#x1f366; 参考文章&#xff1a;365天深度学习训练营-第G7周&#xff1a;Semi-Supervised GAN 理论与实战&#xff08;训练营内部成员可读&#xff09; &#x1f356; 原作者&#xff1a;K同学啊|接…

非递归方法实现二叉树前、中、后序遍历

文章目录 非递归实现二叉树前、中、后序遍历一、非递归实现前序遍历1.思路2.代码 二、非递归实现二叉树的中序遍历1.思路2.代码 三、非递归实现二叉树的后序遍历1.思路2.代码 非递归实现二叉树前、中、后序遍历 一、非递归实现前序遍历 1.思路 前序遍历的顺序是 &#xff1a;根…

【Linux】僵尸进程、孤儿进程的理解与验证

僵尸进程 概念 僵尸进程&#xff08;Zombie Process&#xff09;是指一个已经终止执行的子进程&#xff0c;但其父进程尚未调用 wait() 或 waitpid() 函数来获取子进程的退出状态。 Linux 中&#xff0c;僵尸进程会保留一些资源&#xff0c;如进程 ID、进程表项和一些系统资源…

C++11 initializer_list 轻量级初始化列表的使用场景(让自定义类可以用初始化列表的形式来实例化对象)

initializer_list 是 C11 中的一个特性&#xff0c;它允许你使用花括号 {} 中的值列表来初始化容器或数组。通常用于初始化标准库容器&#xff0c;比如 std::vector、std::set、std::map 以及数组。 场景一&#xff1a;用初始化列表初始化容器 std::vector<int> arr {…

JavaScript基础知识点速通

0 前言 本文是近期我学习JavaScript网课的笔记&#xff0c;一是方便自己速查回忆&#xff0c;二是希望帮到同样有需求的朋友们。 1 介绍 1.1 基本情况 JavaScript是一种编程语言&#xff0c;运行在客户端&#xff08;浏览器&#xff09;上&#xff0c;实现人机交互效果&…

【扩散模型】不同组件搭积木,获得新模型

学习地址&#xff1a; https://github.com/huggingface/diffusion-models-class/tree/main/unit3 VAE The Tokenizer and Text Encoder UNet In-Painting 例如&#xff1a;基于contrlnet做的校徽转图片

vue项目使用vite设置proxy代理,vite.config.js配置,解决本地跨域问题

vue3vite4项目&#xff0c;配置代理实现本地开发跨域问题 非同源请求&#xff0c;也就是协议(protocol)、端口(port)、主机(host)其中一项不相同的时候&#xff0c;这时候就会产生跨域 vite的proxy代理和vue-cli的proxy大致相同&#xff0c;需要在vite.config.js文件中配置&…

一、Linux开机、重启、关机和用户登录注销

1.【关机】 shutdown shutdown now 表示立即关机 shutdown -h now 表示立即关机 shutdown -h 1 表示1分钟后关机 halt 用来关闭正在运行的Linux操作系统 2.【重启】 shutdown -r now 表示立即重启 reboot 重启系统 sync …

设计模式之装饰模式--优雅的增强

目录 概述什么是装饰模式为什么使用装饰模式关键角色基本代码应用场景 版本迭代版本一版本二版本三—装饰模式 装饰模式中的巧妙之处1、被装饰对象和装饰对象共享相同的接口或父类2、当调用装饰器类的装饰方法时&#xff0c;会先调用被装饰对象的同名方法3、子类方法与父类方法…

中国人民大学与加拿大女王大学金融硕士—重要的是,你一直在努力

人虽然生下来就分三六九等&#xff0c;不同的人过着不同的生活&#xff0c;我的生活没办法选择&#xff0c;我只能尽我所能的让自己变得优秀。中国人民大学与加拿大女王大学金融硕士是我们无论怎样都可以变优秀的优质渠道。V13146152701 那么我们为什么要读研&#xff0c;读研…