当前位置: 首页 > news >正文

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: stringsrc: stringtime: stringmp3Name: 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 = vif (v < 0) {num = 0} else if (v > 100) {num = 100}defaultVolume = num / 100return defaultVolume}/*** 计时器:回调-更新显示-MP3的播放时间*/const _intervalUpdatePlayTime = () => {const player = musicPlayer.valueif (!player) returncurrentTime.value = player.currentTime}/*** 计时器:清除*/const _clearTimer = () => {if (!timer) returnclearInterval(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 = nullgainNode = nullaudioCtx = nullmusicPlayer.value = null}/*** 音乐:初始化audio与AudioContext的绑定* 目的是为了 IOS 上能调整音量*/const _init = () => {if (!audioContextAttr) return// 先暂停已有的播放pause()// 对已创建的绑定关系进行解绑_destroyConnect()// 若在body中找得到对应的dom,则进行移除const findDom = document.getElementById('musicPlayerAudio') as HTMLAudioElementif (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) returnsource = 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.valueif (!player) returnif (v === 0) {player.muted = true} else {player.muted = false}if (!gainNode || !gainNode.gain) returngainNode.gain.value = v}/*** 音乐:播放器-暂停*/const pause = () => {const player = musicPlayer.valueif (!musicIsPlaying.value || !player) {return}musicIsPlaying.value = falseplayer.pause()_clearTimer()}/*** 音乐:播放器-播放*/const playByLast = () => {const player = musicPlayer.valueif (!player || !player.src) returnif (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.valueif (!player) {return}// 重置当前播放了的时长currentTime.value = 0// 更新要播放的下标musicPlayingIndex.value = indexif (!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].srcplayByLast()}/*** 音乐:随机播放*/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 + 1playByIndex(index)}}/*** 音乐:播放器-上一首*/const playPrev = () => {if (musicPlayMode.value === PlayMode.RANDOM) {randomPlay()} else {const index: number =musicPlayingIndex.value < 1 ? musicList.length - 1 : musicPlayingIndex.value - 1playByIndex(index)}}/*** 回调:播放结束后,下一首播放什么*/const onAudioEnded = () => {switch (musicPlayMode.value) {case PlayMode.REPEAT:playNext()breakcase PlayMode.SINGLE_CYCLE:playByIndex(musicPlayingIndex.value)breakcase PlayMode.RANDOM:randomPlay()breakdefault: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"><divv-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><svgv-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"><rectid="bar1"transform="translate(0.500000, 6.000000) rotate(180.000000) translate(-0.500000, -6.000000) "x="0"y="5"width="1"height="2px"></rect><rectid="bar2"transform="translate(3.500000, 4.500000) rotate(180.000000) translate(-3.500000, -4.500000) "x="3"y="2"width="1"height="5"></rect><rectid="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><svgv-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"><divclass="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"/><divclass="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"><divv-if="musicPlayer.musicPlayMode.value === PlayMode.REPEAT"class="music-btn playRepeat"@touchstart.passive="onTouchEvent"@touchend.passive="onTouchEvent"@click="nextPlayMode"/><divv-if="musicPlayer.musicPlayMode.value === PlayMode.SINGLE_CYCLE"class="music-btn singleCycle"@touchstart.passive="onTouchEvent"@touchend.passive="onTouchEvent"@click="nextPlayMode"/><divv-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) % 3switch (musicPlayer.musicPlayMode.value) {case PlayMode.REPEAT:console.log('循环播放')breakcase PlayMode.RANDOM:console.log('随机播放')breakcase PlayMode.SINGLE_CYCLE:console.log('单曲循环')breakdefault:break}}/** 事件:当点击按钮时的过渡效果 */const onTouchEvent = (event: Event) => {const tg = event.currentTarget as HTMLElementif (!tg) returnif (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.valueif (!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.valueif (!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.valueif (!audioProgressWrap.value || !audioProgress.value || !player) {return}const offsetLeft =(player.currentTime / player.duration) * audioProgressWrap.value.offsetWidthaudioProgress.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>

最后

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

相关文章:

H5: 使用Web Audio API播放音乐

简介 记录关于自己使用 Web Audio API 的 AudioContext 播放音乐的知识点。 需求分析 1.列表展示音乐&#xff1b; 2.上/下一首、播放/暂停/续播&#xff1b; 3.播放模式切换&#xff1a;循环播放、单曲循环、随机播放&#xff1b; 4.播放状态显示&#xff1a;当前播放的音乐…...

Parasoft C/C++test:汽车网络安全ISO 21434最佳实践

为什么汽车网络安全很重要Why Automotive Cybersecurity Is Important 许多汽车公司向电子道路车辆的转变从根本上改变了整个行业&#xff0c;提高了汽车的互联性和智能性。随着电子汽车变得更加互联和智能&#xff0c;它们也越来越依赖软件来实现车辆操作&#xff0c;驱动更多…...

如何卸载干净 IDEA(图文讲解)windows和Mac教程

大家好&#xff0c;我是sun~ 很多小伙伴会问 Windows / Mac 系统上要怎么彻底卸载 IDEA 呢&#xff1f; 本文通过图片文字&#xff0c;详细讲解具体步骤&#xff1a; 如何卸载干净 IDEA&#xff08;图文讲解&#xff09; Windows1、卸载 IDEA 程序2、注册表清理3、残留清理 M…...

Docker搭建Gitlab

拉取镜像&#xff1a;docker pull gitlab/gitlab-ce创建映射目录&#xff1a; mkdir -p /usr/local/gitlab/config mkdir -p /usr/local/gitlab/data mkdir -p /usr/local/gitlab/logs运行容器&#xff1a; docker run -d -p 443:443 -p 8000:8000 -p 222:22 --name gitlab …...

STM32F4X SDIO(四) SDIO控制器

STM32F4X SDIO&#xff08;四&#xff09; SDIO控制器 STM32F4X SDIO控制器SDIO控制器框图SDIO控制器时钟适配器寄存器FIFO控制单元命令路径数据路径 SDIO寄存器SDIO控制相关寄存器SDIO电源控制寄存器 (SDIO_POWER)SDIO时钟控制寄存器 (SDIO_CLKCR)SDIO_CK相位 SDIO命令响应相关…...

【flink】Task 故障恢复详解以及各重启策略适用场景说明

文章目录 一. 重启策略种类&#xff08;Restart Strategies&#xff09;1. Fixed Delay Restart Strategy2. Failure Rate Restart Strategy3. Fallback Restart Strategy4. No Restart Strategy 二. 故障恢复策略&#xff08;Failover Strategies&#xff09;1. &#xff08;全…...

一个计算机高手的成长3

这是转在茶余的帖子。文中绝大部分技术术语我不懂&#xff0c;所以无资格评论他的技术价值。但文章强烈的逻辑说服力&#xff0c;和通篇流露的进取精神&#xff0c;使我觉得这是篇有价值的帖子&#xff0c;至少值得一读。 就像我开始从MIS转到通信一样&#xff0c;我看过大量通…...

2023应届生能力考试含解析(Java后端开发)——(1)

1.以下代码的循环次数是 ( ) public class Test {public static void main(String[] args) {int i 7;do {System.out.println(--i);--i;} while (i ! 0);System.out.println(i);} } A 0 B 1 C 7 D 无限次 这段代码会导致无限循环的原因是在 do-while 循环中&#…...

Ansible中的任务执行控制

循环 简单循环 {{item}} 迭代变量名称 loop: - value1 - value2 - ... //赋值列表{{item}} //迭代变量名称循环散列或字典列表 - name: create filehosts: host1tasks:- name: file moudleservice:name: "{{ item.name }}"state: "{{…...

利用maven的dependency插件分析工程的依赖

dependency:analyze https://maven.apache.org/plugins/maven-dependency-plugin/analyze-mojo.html 分析项目的依赖&#xff0c;确定哪些&#xff1a;用了并且声明了、用了但没有声明、没有使用但声明了。 dependency:analyze可以单独使用&#xff0c;所以它总是会执行test-…...

【广州华锐互动】VR野外求生技能学习,让你感受真实的冒险之旅!

随着科技的迅速发展&#xff0c;虚拟现实(VR)技术为人们提供了一个全新的、身临其境的探险体验。通过将用户带入一个仿真的、沉浸式的虚拟环境&#xff0c;VR互动体验让人们在安全的氛围中感受到野外探险的乐趣。本文将从视觉呈现、沉浸式体验、交互性和应用范围四个方面&#…...

k8s、调度约束

Kubernetes 是通过 List-Watch **** 的机制进行每个组件的协作&#xff0c;保持数据同步的&#xff0c;每个组件之间的设计实现了解耦 用户是通过 kubectl 根据配置文件&#xff0c;向 APIServer 发送命令&#xff0c;在 Node 节点上面建立 Pod 和 Container。 APIS…...

Redis的介绍,以及Redis的安装(本机windows版,虚拟机Linux版)和Redis常用命令的介绍

目录 一. Redis简介 二. Redis的安装 2.1 Linux版安装 2.2 windows版安装 三. Redis的常用命令 一. Redis简介 Redis是一个开源&#xff08;BSD许可&#xff09;&#xff0c;内存存储的数据结构服务器&#xff0c;可用作数据库&#xff0c;高速缓存和消息队列代理。 它…...

电子器件 MOS管的参数、选型与使用技巧

一、电路符号 MOS管分为 G&#xff08;栅极&#xff09;、S&#xff08;源极&#xff09;、D&#xff08;漏极&#xff09; 三极&#xff0c;在图中 S 极有两条线&#xff0c;D 极只有一条线。 1.1 NMOS 和 PMOS 下图中&#xff0c;左侧是 PMOS&#xff0c;右侧是 NMOS。箭头…...

EtherCAT主站SOEM -- 2 -- SOEM之ethercatbase.h/c文件解析

EtherCAT主站SOEM -- 2 -- SOEM之ethercatbase.h/c文件解析 一 ethercatbase.h/c文件功能预览&#xff1a;二 ethercatbase.h/c 文件的主要函数的作用&#xff1a;2.1 ecx_writedatagramdata&#xff1a;2.2 ecx_setupdatagram&#xff1a;2.3 ecx_adddatagram&#xff1a;2.4 …...

Spring集成高性能队列Disruptor

Disruptor简介 Disruptor&#xff08;中文翻译为“破坏者”或“颠覆者”&#xff09;是一种高性能、低延迟的并发编程框架&#xff0c;最初由LMAX Exchange开发。它的主要目标是解决在金融交易系统等需要高吞吐量和低延迟的应用中的并发问题。 Disruptor特点 无锁并发&#x…...

C++——类和对象(中)完结

赋值运算符重载 运算符重载 C 为了增强代码的可读性引入了运算符重载 &#xff0c; 运算符重载是具有特殊函数名的函数 &#xff0c;也具有其 返回值类型&#xff0c;函数名字以及参数列表&#xff0c;其返回值类型与参数列表与普通的函数类似。 函数名字为&#xff1a;关键…...

Sqoop的安装和使用

目录 一.安装 二.导入 1.全量导入 一.MySQL导入HDFS 二.MySQL导入Hive 2.增量导入 一.过滤导入hdfs/hive 二.导出 一.安装 1.下载地址&#xff1a;sqoop下载地址 2.解压 tar -zxvf ./sqoop-1.4.7.bin__hadoop-2.6.0.tar.gz -C ../module/ 3.改名和配置归属权限 #改名…...

java毕业设计基于springboot+vue的村委会管理系统

项目介绍 采用JAVA语言&#xff0c;结合SpringBoot框架与Vue框架以及MYSQL数据库设计并实现的。本村委会管理系统主要包括个人中心、村民管理、村委会管理、村民信息管理、土地变更管理、农业补贴管理、党员信息管理等多个模块。它帮助村委会管理实现了信息化、网络化&#xf…...

【C++】多态 ⑪ ( 纯虚函数和抽象类 | 纯虚函数语法 | 抽象类和实现 | 代码示例 )

文章目录 一、纯虚函数和抽象类1、纯虚函数2、纯虚函数语法3、抽象类和实现 二、完整代码示例 一、纯虚函数和抽象类 1、纯虚函数 纯虚函数 : 在 C 语言中 , " 纯虚函数 " 是 特殊类型的 虚函数 , " 纯虚函数 " 在 父类 中 声明 , 但是没有实现 ; 抽象类 …...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

2025盘古石杯决赛【手机取证】

前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来&#xff0c;实在找不到&#xff0c;希望有大佬教一下我。 还有就会议时间&#xff0c;我感觉不是图片时间&#xff0c;因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

2023赣州旅游投资集团

单选题 1.“不登高山&#xff0c;不知天之高也&#xff1b;不临深溪&#xff0c;不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...