“UniApp的音频播放——点击视频进入空白+解决视频播放器切换视频时一直加载的问题”——video.js、video-js.css
今天,又解决了一个单子“UniApp的音频播放——点击视频进入空白+解决视频播放器切换视频时一直加载的问题”
一、问题描述
在开发一个基于 video.js
的视频播放器时,用户通过上下滑动切换视频时,视频一直处于加载状态,无法正常播放。通过日志可以看到,视频源地址和索引更新是正确的,但视频无法播放。具体表现为:
-
视频加载卡住:切换视频时,播放器一直显示加载动画,无法播放视频。
-
日志显示正常:日志中显示的视频源地址和索引更新是正确的,例如:
即将更新视频源为: http://127.0.0.1:8000/media/m3u8/30bd5d2225919b1724ca69d07633beb1/index.m3u8 currentIndex: 1 videos长度: 4
-
播放器未正确响应:尽管视频源地址更新了,但播放器未能正确加载和播放新视频。
二、问题复现步骤
-
初始化播放器:加载第一个视频,播放器正常工作。
-
滑动切换视频:用户通过上下滑动切换到下一个视频。
-
视频加载卡住:播放器显示加载动画,但视频无法播放。
-
日志输出:日志显示视频源地址和索引更新正确,但播放器未响应。
三、来请看代码,各位客官
<template><!-- <view@click="handleVideoClick"@touchstart="handleTouchStart"@touchmove="handleTouchMove"@touchend="handleTouchEnd"> --><!-- 根据 isAdVideo 的值决定显示广告视频还是常规视频 --><!-- 对于广告视频,不显示控制条,自动播放 --><!-- <video v-if="isAdVideo" :src="videoSrc" :controls="false" autoplay></video> --><!-- 对于常规视频,显示控制条,自动播放 --><!-- <video v-else :src="videoSrc" controls autoplay></video> --><!-- </view> -->
<view><div id="app"><div class="video-js" ref="videos"></div></div>
</view>
</template><script>import { baseUrl } from '@/common/api.js'
export default {data() {return {// 存储当前视频文件的路径videoSrc: '',// 标记当前视频是否为广告视频isAdVideo: false,// 存储广告的 URLadUrl: '',// 存储当前视频在视频列表中的索引currentIndex: 0,// 存储所有视频的数组videos: [],// 存储触摸开始时的 Y 坐标touchStartY: 0,// 存储触摸结束时的 Y 坐标touchEndY: 0,// video.js 的播放器实例player: null,};},onLoad(options) {// 从传入的参数中获取视频文件路径const videoFile = options.videoFile;// 判断是否为广告视频,将字符串 'true' 转换为布尔值const isAd = options.isAd === 'true';// 从传入的参数中获取广告 URL,并进行解码const adUrl = options.adUrl? decodeURIComponent(options.adUrl) : '';// 从传入的参数中获取视频列表,并将其从 JSON 字符串转换为数组const videos = options.videos? JSON.parse(decodeURIComponent(options.videos)) : [];// 将视频文件路径存储到 data 中,修改错误点 1// this.videoFile = videoFile;// 将是否为广告视频的状态存储到 data 中this.isAdVideo = isAd;// 将广告 URL 存储到 data 中this.adUrl = adUrl;// 将视频列表存储到 data 中this.videos = videos;// 根据是否为广告视频来确定视频源的路径if (isAd) {// 假设广告视频的文件名直接作为参数传递,提取文件名const adVideoPath = `${videoFile}`;console.log('1111',adVideoPath);// 拼接完整的广告视频源路径this.videoSrc = `${baseUrl}${adVideoPath}`;} else {// 对于常规视频,在视频列表中查找匹配的视频文件const video = videos.find(v => {console.log('当前视频的 m3u8_url:', v.m3u8_url); // 打印每个视频的 m3u8_urlreturn v.m3u8_url === videoFile;});console.log('222',videoFile);// const video = videos.find(v => v.m3u8_url === videoFile);// console.log('222',v =>v.m3u8_url,videoFile);if (video) {// 拼接完整的常规视频源路径this.videoSrc = `${baseUrl}${video.m3u8_url}`;} else {// 如果未找到对应的视频文件,打印错误信息并退出方法console.error('未找到对应的视频文件路径');return;}}// 查找当前视频在视频列表中的索引this.currentIndex = this.videos.findIndex(v => {if (this.isAdVideo) {const asa =v.ad && v.ad.m3u8_url === videoFile;// 对于广告视频,通过广告视频文件查找索引console.log('当前视频的 vad:', v.ad.m3u8_url,asa);console.log('videoFile',videoFile)return asa ;}// 对于常规视频,通过常规视频文件查找索引return v.m3u8_url === videoFile;});console.log('this.videoFile',videos.find(v => v.m3u8_url === videoFile).m3u8_url);// 打印初始的视频文件路径console.log('Initial video file:', this.videoSrc);},
// beforeDestroy() {
// var playerElement = document.getElementById('video');
// var player = videojs.getInstance(playerElement);
// if (player) {
// player.dispose();
// } // },mounted() {this.initplayer();},beforeDestroy() {// 使用 $refs 来查找 video 元素const videoElement = this.$refs.videos.querySelector('video');if (videoElement) {const player = videojs.getPlayer(videoElement);if (player) {console.log('播放器正在销毁');player.dispose();} else {console.log('未找到播放器实例,可能未初始化');}} else {console.log('未找到 video 元素');}},methods: {initplayer(){// const videoElement = this.$refs.videos.querySelector('video');// const player = videojs.getPlayer(videoElement);// player.dispose();// if (this.player) {// // 如果播放器已经初始化,直接设置新的视频源// this.player.src({ src: this.videoSrc, type: 'application/x-mpegURL' });// this.player.play();// return;// }let video = document.createElement('video');video.id = 'video';// video.style = 'width: 100%; height: 100%;';// video.controls = true;video.preload = "auto"video.setAttribute('playsinline', true) //IOS微信浏览器支持小窗内播放video.setAttribute('webkit-playsinline', true) //这个bai属性是ios 10中设置可以让视频在小du窗内播放,也就是不是全zhi屏播放的video标签的一个属性video.setAttribute('x5-video-player-type', 'h5') //安卓 声明启用同层H5播放器 可以在video上面加东西// const ada='http://127.0.0.1:8000/media\\m3u8\\caba10d1b61e5f2aa1e068bebeb55663\\index.m3u8';let source = document.createElement('source');// source.src = ada;source.src = this.videoSrc;video.appendChild(source);// returnthis.$refs.videos.appendChild(video);let that = this;let player = this.$video('video', {autoDisable: true,preload: 'none', //auto - 当页面加载后载入整个视频 meta - 当页面加载后只载入元数据 none - 当页面加载后不载入视频language: 'zh-CN',fluid: true, // 自适应宽高muted: false, // 是否静音aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")controls: true, //是否拥有控制条 【默认true】,如果设为false ,那么只能通过api进行控制了。也就是说界面上不会出现任何控制按钮autoplay: false, //如果true,浏览器准备好时开始回放。 autoplay: "muted", // //自动播放属性,muted:静音播放loop: true, // 导致视频一结束就重新开始。 视频播放结束后,是否循环播放controlBar: {volumePanel: { //声音样式inline: true // 不使用水平方式},timeDivider: true, // 时间分割线durationDisplay: true, // 总时间progressControl: true, // 进度条remainingTimeDisplay: true, //当前以播放时间fullscreenToggle: true, //全屏按钮pictureInPictureToggle: false, //画中画}}, function() {this.on('error', function(err) { //请求数据时遇到错误console.log("请求数据时遇到错误", err)});this.on('stalled', function(stalled) { //网速失速console.log("网速失速", stalled)});});},// 处理触摸开始事件,记录触摸开始时的 Y 坐标handleTouchStart(event) {this.touchStartY = event.touches[0].clientY;},// 处理触摸移动事件,目前不做任何处理,可添加优化逻辑handleTouchMove(event) {// 例如,可以添加代码防止快速滑动时的抖动效果},// 处理触摸结束事件,记录触摸结束时的 Y 坐标,并调用 handleSwipe 方法handleTouchEnd(event) {this.touchEndY = event.changedTouches[0].clientY;this.handleSwipe();},// 处理滑动操作handleSwipe() {// 计算触摸的垂直距离const distance = this.touchEndY - this.touchStartY;// 如果滑动距离小于 30 像素,不进行任何操作if (Math.abs(distance) < 30) {return;}// 如果滑动距离大于 0,表示向下滑动,调用 handleSwipeDown 方法if (distance > 0) {this.handleSwipeDown();} else {// 否则表示向上滑动,调用 handleSwipeUp 方法this.handleSwipeUp();}},// 处理向上滑动,切换到下一个视频handleSwipeUp() {// 如果不是最后一个视频if (this.currentIndex < this.videos.length - 1) {// 增加当前视频索引this.currentIndex++;// 更新视频信息this.updateVideo();// 打印下一个视频的文件路径console.log('Swipe Up: Next video file:', this.videoSrc);// 打印是否为广告视频console.log('是否广告', this.isAdVideo);} else {// 已到达最后一个视频,打印提示信息console.log("已经是最后一个视频了");}},// 处理向下滑动,切换到上一个视频handleSwipeDown() {// 如果不是第一个视频if (this.currentIndex > 0) {// 减小当前视频索引this.currentIndex--;// 更新视频信息this.updateVideo();// 打印上一个视频的文件路径console.log('Swipe Down: Previous video file:', this.videoSrc);} else {// 已到达第一个视频,打印提示信息console.log("已经是第一个视频了");}},// 更新视频信息,包括视频源和广告 URLupdateVideo() {// 获取当前索引对应的下一个视频const nextVideo = this.videos[this.currentIndex];console.log('11111', this.videos, nextVideo, this.currentIndex);// 判断下一个视频是否为广告视频this.isAdVideo =!!nextVideo.ad;if (this.isAdVideo) {// 如果是广告视频,更新视频源为广告视频源并打印console.log('1111:', nextVideo.ad.m3u8_url);this.videoSrc = `${baseUrl}${nextVideo.ad.m3u8_url}`;} else {// 如果是常规视频,更新视频源为常规视频源并打印this.videoSrc = `${baseUrl}${nextVideo.m3u8_url}`;console.log('2222:', nextVideo.m3u8_url);}// 根据是否为广告视频更新广告 URL,修改错误点 3if (this.isAdVideo) {this.adUrl = nextVideo.ad.urll;} else {this.adUrl = '';}// 当视频源更新时,更新播放器的 srcif (this.player) {this.player.src({ src: this.videoSrc });}},// 处理视频点击事件handleVideoClick() {// 如果是广告视频且有广告 URLif (this.isAdVideo && this.adUrl) {// 根据不同的平台,使用不同的跳转方式打开广告 URLif (process.env.VUE_APP_PLATFORM === 'h5') {// 在 H5 平台使用 window.open 打开广告 URLwindow.open(this.adUrl, '_blank');} else {// 在小程序或其他平台使用 uni.navigateTo 进行跳转uni.navigateTo({url: `/pages/webview/webview?url=${encodeURIComponent(this.adUrl)}`});}}}}
};
</script>
三、可能的原因
-
视频源路径格式问题:
-
视频路径中使用了反斜杠
\
,例如:http://127.0.0.1:8000\media\m3u8\30bd5d2225919b1724ca69d07633beb1\index.m3u8
。 -
反斜杠在某些环境下可能导致路径解析错误。
-
-
播放器未正确销毁和重新初始化:
-
在切换视频时,旧的播放器实例可能未正确销毁,导致新的播放器实例无法正常初始化。
-
-
视频加载超时或失败:
-
视频文件可能无法加载,或者加载时间过长,导致播放器一直处于加载状态。
-
-
用户交互限制:
-
某些浏览器要求视频播放必须在用户交互后触发,如果未正确处理用户交互,可能导致视频无法播放。
-
-
播放器初始化问题:
-
播放器初始化逻辑中,
this.$video
未定义,可能导致播放器无法正确初始化。
-
-
广告视频逻辑问题:
-
广告视频的逻辑中,
nextVideo.ad.urll
拼写错误,导致广告 URL 无法正确更新。
-
四、问题分析
1. 视频路径格式问题
-
问题:视频路径中使用了反斜杠
\
,例如:http://127.0.0.1:8000\media\m3u8\30bd5d2225919b1724ca69d07633beb1\index.m3u8
。 -
影响:在某些环境下,反斜杠可能导致路径解析错误,视频无法加载。
-
解决方案:将反斜杠替换为正斜杠
/
。
this.videoSrc = `${baseUrl}${videoFile}`.replace(/\\/g, '/');
2. 播放器未正确销毁和重新初始化
-
问题:在切换视频时,旧的播放器实例可能未正确销毁,导致新的播放器实例无法正常初始化。
-
影响:切换视频时,播放器可能卡在加载状态或无法播放。
-
解决方案:在切换视频时,销毁旧的播放器实例并重新初始化新的播放器实例。
updateVideo() {const nextVideo = this.videos[this.currentIndex];this.isAdVideo = !!nextVideo.ad;if (this.isAdVideo) {this.videoSrc = `${baseUrl}${nextVideo.ad.m3u8_url}`.replace(/\\/g, '/');} else {this.videoSrc = `${baseUrl}${nextVideo.m3u8_url}`.replace(/\\/g, '/');}this.adUrl = this.isAdVideo ? nextVideo.ad.url : '';console.log('即将更新视频源为:', this.videoSrc);this.destroyPlayer(); // 销毁旧的播放器实例this.$nextTick(() => {this.initplayer(); // 重新初始化播放器});
}
3. 视频加载超时或失败
-
问题:视频文件可能无法加载,或者加载时间过长,导致播放器一直处于加载状态。
-
影响:用户可能会看到视频一直加载,无法播放。
-
解决方案:设置超时机制,防止长时间停留在加载状态。
async updateVideoSource() {if (!this.player) return;console.log('正在更新视频源:', this.videoSrc);try {this.player.pause(); // 暂停当前播放this.player.src({ src: this.videoSrc, type: 'application/x-mpegURL' });this.player.load();// 监听 loadeddata 事件,确保视频数据加载完成后再尝试播放this.player.one('loadeddata', () => {console.log('视频数据加载完成');this.isPlaying = false;this.player.play().then(() => {this.isPlaying = true;}).catch(error => {console.error('播放失败:', error);});});// 设置一个超时机制,防止长时间停留在加载状态const timeoutId = setTimeout(() => {console.warn('视频加载超时');// 尝试重新加载视频this.player.src({ src: this.videoSrc, type: 'application/x-mpegURL' });this.player.load();}, 10000); // 10秒超时// 当视频加载完成时清除超时this.player.on('loadeddata', () => clearTimeout(timeoutId));} catch (error) {console.error('更新视频源并准备播放失败:', error);} }
4. 用户交互限制
-
问题:某些浏览器要求视频播放必须在用户交互后触发,如果未正确处理用户交互,可能导致视频无法播放。
-
影响:视频无法自动播放,用户需要手动点击播放按钮。
-
解决方案:在用户交互后触发视频播放。
handleTouchEnd(event) {this.touchEndY = event.changedTouches[0].clientY;this.handleSwipe();this.isUserInteracted = true; // 标记用户交互if (this.player && this.isUserInteracted) {this.player.play().catch(error => {console.error('播放失败:', error);});}
}
5. 播放器初始化问题
-
问题:播放器初始化逻辑中,
this.$video
未定义,可能导致播放器无法正确初始化。 -
影响:播放器无法正常工作。
-
解决方案:使用
videojs
直接初始化播放器。initplayer() {let video = document.createElement('video');video.id = 'video';video.preload = "auto";video.setAttribute('playsinline', true);video.setAttribute('webkit-playsinline', true);video.setAttribute('x5-video-player-type', 'h5');let source = document.createElement('source');source.src = this.videoSrc;video.appendChild(source);this.$refs.videos.appendChild(video);this.player = videojs(video, {autoplay: false,controls: !this.isAdVideo,sources: [{ src: this.videoSrc, type: 'application/x-mpegURL' }]});this.player.on('error', (error) => {console.error('视频加载错误:', error);}); }
6. 播放器销毁问题
-
问题:在
beforeDestroy
钩子中,播放器销毁逻辑可能无法正确执行。 -
影响:播放器实例可能未正确销毁,导致内存泄漏。
-
解决方案:确保播放器实例被正确销毁。
beforeDestroy() {if (this.player) {console.log('播放器正在销毁');this.player.dispose();this.player = null;} else {console.log('未找到播放器实例,可能未初始化');} }
7. 日志输出不足
-
问题:日志输出较少,难以定位问题。
-
影响:调试困难。
-
解决方案:在关键步骤添加日志输出。
console.log('即将更新视频源为:', this.videoSrc); console.log('currentIndex:', this.currentIndex); console.log('videos长度:', this.videos.length);
8. 广告视频逻辑问题
-
问题:广告视频的逻辑中,
nextVideo.ad.urll
拼写错误。 -
影响:广告 URL 无法正确更新。
-
解决方案:修正拼写错误。
if (this.isAdVideo) {this.adUrl = nextVideo.ad.url; // 修正拼写错误 } else {this.adUrl = ''; }
-
五、完整代码
-
<template><view @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd"><!-- 视频容器 --><div id="app"><div class="video-js" ref="videos"></div></div></view> </template><script> import { baseUrl } from '@/common/api.js'; import videojs from 'video.js'; import 'video.js/dist/video-js.css'; // 引入默认样式export default {name: 'VideoPlayer',data() {return {videoSrc: '',isAdVideo: false,adUrl: '',currentIndex: 0,videos: [],touchStartY: 0,touchEndY: 0,player: null, // video.js 的播放器实例uniqueKey: Date.now(), // 用于强制刷新组件isPlaying: false, // 跟踪播放状态isUserInteracted: false, // 跟踪用户交互状态};},onLoad(options) {this.initFromOptions(options);},activated() {// 当组件被激活时(从缓存中恢复),重新初始化播放器this.$nextTick(() => this.initplayer());},deactivated() {// 当组件被停用时(进入缓存),销毁播放器this.destroyPlayer();},mounted() {// 确保在挂载时初始化播放器this.$nextTick(() => this.initplayer());},methods: {async initFromOptions(options) {const videoFile = options.videoFile;const isAd = options.isAd === 'true';const adUrl = options.adUrl ? decodeURIComponent(options.adUrl) : '';const videos = options.videos ? JSON.parse(decodeURIComponent(options.videos)) : [];this.isAdVideo = isAd;this.adUrl = adUrl;this.videos = videos;if (isAd) {this.videoSrc = `${baseUrl}${videoFile}`.replace(/\\/g, '/');} else {const video = videos.find(v => v.m3u8_url === videoFile);if (video) {this.videoSrc = `${baseUrl}${video.m3u8_url}`.replace(/\\/g, '/');} else {console.error('未找到对应的视频文件路径');return;}}this.currentIndex = this.videos.findIndex(v => {if (this.isAdVideo) {return v.ad && v.ad.m3u8_url === videoFile;}return v.m3u8_url === videoFile;});console.log('Initial video file:', this.videoSrc);await this.initplayer(); // 确保播放器初始化完成},async initplayer() {// 如果播放器已经存在,则更新源而不是重新创建if (this.player) {await this.updateVideoSource();return;}let videoElement = document.createElement('video');videoElement.id = 'video';videoElement.preload = "auto";videoElement.setAttribute('playsinline', true);videoElement.setAttribute('webkit-playsinline', true);videoElement.setAttribute('x5-video-player-type', 'h5');let source = document.createElement('source');source.src = this.videoSrc;videoElement.appendChild(source);this.$refs.videos.appendChild(videoElement);// 使用 Vue 的 nextTick 方法确保 DOM 更新完成后才初始化 Video.js 播放器this.$nextTick(() => {this.player = videojs(videoElement,{autoplay: false,controls: !this.isAdVideo,sources: [{ src: this.videoSrc, type: 'application/x-mpegURL' }]},async function onPlayerReady() {console.log("播放器已准备好");try {if (this.isUserInteracted) {await this.play(); // 使用 async/await 确保 play() 完成}} catch (error) {console.error('播放失败:', error);}}.bind(this));// 监听错误事件this.player.on('error', (error) => {console.error('视频加载错误:', error);console.error('错误详情:', this.player.error()); // 获取详细的错误信息});});},destroyPlayer() {if (this.player) {console.log('播放器正在销毁');this.player.dispose();this.player = null; // 清除 player 实例引用} else {console.log('未找到播放器实例,可能未初始化');}},async updateVideoSource() {if (!this.player) return;console.log('正在更新视频源:', this.videoSrc);try {this.player.pause(); // 暂停当前播放this.player.src({ src: this.videoSrc, type: 'application/x-mpegURL' });this.player.load();// 监听 loadeddata 事件,确保视频数据加载完成后再尝试播放this.player.one('loadeddata', () => {console.log('视频数据加载完成');this.isPlaying = false;this.player.play().then(() => {this.isPlaying = true;}).catch(error => {console.error('播放失败:', error);// 可以在这里添加重试逻辑或提示用户});});// 设置一个超时机制,防止长时间停留在加载状态const timeoutId = setTimeout(() => {console.warn('视频加载超时');// 尝试重新加载视频this.player.src({ src: this.videoSrc, type: 'application/x-mpegURL' });this.player.load();}, 10000); // 10秒超时// 当视频加载完成时清除超时this.player.on('loadeddata', () => clearTimeout(timeoutId));} catch (error) {console.error('更新视频源并准备播放失败:', error);// 可以在这里添加重试逻辑或提示用户}},handleTouchStart(event) {this.touchStartY = event.touches[0].clientY;},handleTouchMove(event) {// 这里可以添加优化逻辑,但目前保持原样},handleTouchEnd(event) {this.touchEndY = event.changedTouches[0].clientY;this.handleSwipe();this.isUserInteracted = true; // 标记用户交互if (this.player && this.isUserInteracted) {this.player.play().catch(error => {console.error('播放失败:', error);});}},handleSwipe() {const distance = this.touchEndY - this.touchStartY;if (Math.abs(distance) < 30) return;if (distance > 0) {this.handleSwipeDown();} else {this.handleSwipeUp();}},handleSwipeUp() {if (this.currentIndex < this.videos.length - 1) {this.currentIndex++;this.updateVideo();console.log('Swipe Up: Next video file:', this.videoSrc);console.log('是否广告', this.isAdVideo);} else {console.log("已经是最后一个视频了");}},handleSwipeDown() {if (this.currentIndex > 0) {this.currentIndex--;this.updateVideo();console.log('Swipe Down: Previous video file:', this.videoSrc);} else {console.log("已经是第一个视频了");}},updateVideo() {const nextVideo = this.videos[this.currentIndex];this.isAdVideo = !!nextVideo.ad;if (this.isAdVideo) {this.videoSrc = `${baseUrl}${nextVideo.ad.m3u8_url}`.replace(/\\/g, '/');} else {this.videoSrc = `${baseUrl}${nextVideo.m3u8_url}`.replace(/\\/g, '/');}this.adUrl = this.isAdVideo ? nextVideo.ad.url : '';console.log('即将更新视频源为:', this.videoSrc); // 添加日志输出console.log('currentIndex:', this.currentIndex); // 添加日志输出console.log('videos长度:', this.videos.length); // 添加日志输出this.destroyPlayer(); // 销毁旧的播放器实例this.$nextTick(() => {this.initplayer(); // 重新初始化播放器});},handleVideoClick() {if (this.isAdVideo && this.adUrl) {if (process.env.VUE_APP_PLATFORM === 'h5') {window.open(this.adUrl, '_blank');} else {uni.navigateTo({url: `/pages/webview/webview?url=${encodeURIComponent(this.adUrl)}`});}}}},watch: {// 监听路由变化并强制刷新组件$route(to, from) {this.uniqueKey = Date.now(); // 改变 key 来强制刷新组件this.$nextTick(() => this.initplayer()); // 确保播放器在路由变化后重新初始化}} }; </script><style scoped> /* 添加样式 */ #app {width: 100%;height: 100%; } .video-js {width: 100%;height: 100%; } </style>
-
总结
通过修复视频路径格式、确保播放器正确销毁和重新初始化、处理视频加载超时、确保用户交互后播放、修正播放器初始化逻辑以及修正广告视频逻辑,可以有效解决视频切换时一直加载的问题。如果问题仍然存在,建议进一步检查视频源的有效性和网络状态,并使用浏览器的开发者工具查看网络请求和错误日志。
相关文章:
“UniApp的音频播放——点击视频进入空白+解决视频播放器切换视频时一直加载的问题”——video.js、video-js.css
今天,又解决了一个单子“UniApp的音频播放——点击视频进入空白解决视频播放器切换视频时一直加载的问题” 一、问题描述 在开发一个基于 video.js 的视频播放器时,用户通过上下滑动切换视频时,视频一直处于加载状态,无法正常播放…...
如何让openhands始终输出中文?
在本地创建一个文件./user_prompt.j2 添加一行 Always respond in 中文你可以直接: echo "Always respond in 中文" > ./user_prompt.j2使用官方文档的docker命令启动容器时-v挂载一个文件/app/openhands/agenthub/codeact_agent/prompts/user_promp…...
CSS 溢出问题及解决方案:实用案例与技巧
在网页开发中,CSS 的布局和样式起着至关重要的作用,但经常会遇到一个棘手的问题——溢出问题。溢出是指元素内的内容超出了其设定的容器大小,这不仅会影响页面的美观,还可能干扰用户体验。本文将详细探讨 CSS 溢出问题的案例&…...
vue3使用音频audio标签
文章目录 一、背景二、页面三、标签介绍四、代码五、代码说明场景1:针对加载固定格式的比如MP3文件,可直接使用\<audio>标签场景2:针对播放告警内容,比如中文或者英文词条情况 一、背景 项目使用vue3,需求针对告…...
【useCallback Hook】在多次渲染中缓存组件中的函数,避免重复创建函数
文章目录 什么是 useCallback?基本语法 为什么需要 useCallback?示例1. 避免子组件重复创建函数2. 作为 useEffect 的依赖项 注意事项总结 在 React 开发中,性能优化是一个重要的主题。随着应用规模的增长,组件的重新渲染可能会变…...
Vue2+OpenLayers添加缩放、滑块缩放、拾取坐标、鹰眼、全屏控件(提供Gitee源码)
目录 一、案例截图 二、安装OpenLayers库 三、代码实现 四、Gitee源码 一、案例截图 二、安装OpenLayers库 npm install ol 三、代码实现 废话不多说,直接给完整代码,替换成自己的KEY即可运行: <template><div><div i…...
feign调用跳过HTTPS的SSL证书校验配置详解
一、问题抛出 如果不配置跳过SSL证书校验,当Feign客户端尝试连接到一个使用自签名证书的服务器时,可能会抛出类似以下的异常: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building faile…...
spring @EnableAspectJAutoProxy @Aspect的使用和源码流程
目录 测试代码EnableAspectJAutoProxyAspectJAutoProxyRegistrarAnnotationAwareAspectJAutoProxyCreatororg.springframework.context.support.AbstractApplicationContext#registerBeanPostProcessors 实例化AnnotationAwareAspectJAutoProxyCreator bean "a"的代理…...
项目实战--网页五子棋(游戏大厅)(3)
我们的游戏大厅界面主要需要包含两个功能,一是显示用户信息,二是匹配游戏按钮 1. 页面实现 hall.html <!DOCTYPE html> <html lang"ch"> <head><meta charset"UTF-8"><meta name"viewport"…...
『 实战项目 』Cloud Backup System - 云备份
文章目录 云备份项目服务端功能服务端功能模块划分客户端功能客户端模块划分 项目条件Jsoncpp第三方库Bundle第三方库httplib第三方库Request类Response类Server类Client类搭建简单服务器搭建简单客户端 服务端工具类实现 - 文件实用工具类服务器配置信息模块实现- 系统配置信息…...
【机器学习实战入门】使用OpenCV和Keras的驾驶员疲劳检测系统
嗜睡驾驶者警报系统 防止司机疲劳驾驶警报系统 中级 Python 项目 - 司机疲劳检测系统 疲劳检测是一种安全技术,能够预防因司机在驾驶过程中入睡而造成的事故。 本中级 Python 项目的目标是建立一个疲劳检测系统,该系统将检测到一个人的眼睛闭合了一段时…...
使用 spring boot 2.5.6 版本时缺少 jvm 配置项
2.5.6我正在使用带有版本和springfox-boot-starter版本的Spring Boot 项目3.0.0。我的项目还包括一个WebSecurityConfig扩展WebSecurityConfigurerAdapter并实现WebMvcConfigurer的类。但是,我面临的问题是指标在端点jvm_memory_usage_after_gc_percent中不可见/act…...
【强化学习】Soft Actor-Critic (SAC) 算法
📢本篇文章是博主强化学习(RL)领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅…...
2024年博客之星主题创作|Android 开发:前沿技术、跨领域融合与就业技能展望
目录 引言 一、推动 Android 应用创新的核心力量 1.1 人工智能与机器学习的崛起 1.2 增强现实(AR)与虚拟现实(VR)的应用扩展 1.3 5G技术的推动 1.4 跨平台开发技术的成熟 1.4.1 React Native 1.4.2 Flutter 1.4.3 Taro …...
Spring Boot--@PathVariable、@RequestParam、@RequestBody
目录 声明!! 什么是RESTful? RESTful 的基本原则 无状态性(Stateless) 统一接口(Uniform Interface) 分层系统(Layered System) 缓存(Cacheable&#…...
网站HTTP改成HTTPS
您不仅需要知道如何将HTTP转换为HTTPS,还必须在不妨碍您的网站自成立以来建立的任何搜索排名权限的情况下进行切换。 为什么应该从HTTP转换为HTTPS? 与非安全HTTP于不同,安全域使用SSL(安全套接字层)服务器上的加密代…...
Spring Boot + Netty + WebSocket 实现消息推送
1、关于Netty Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。 2、Maven依赖 <dependencies><!-- https://mvnrepository.com/artifact/io.netty/netty-all --><dependency><gr…...
AI之HardWare:英伟达NvidiaGPU性价比排名(消费级/专业级/中高端企业级)以及据传英伟达Nvidia2025年将推出RTX 5090/5080、华为2025年推出910C/910D
AI之HardWare:英伟达NvidiaGPU性价比排名(消费级/专业级/中高端企业级)以及据传英伟达Nvidia2025年将推出RTX 5090/5080、华为2025年推出910C/910D 目录 英伟达NvidiaGPU性能排名(消费级/专业级/中高端企业级) NVIDIA中消费级和专业级 GPU NVIDIA中高端企业GPU …...
ESP32云开发二( http + led + lcd)
文章目录 前言先上效果图platformio.iniwokwi.tomldiagram.json源代码编译编译成功上传云端完结撒花⭐⭐⭐⭐⭐ 前言 阅读此篇前建议先看 此片熟悉下wokwi https://blog.csdn.net/qq_20330595/article/details/144289986 先上效果图 Column 1Column 2 platformio.ini wokwi…...
JavaScript语言的软件工程
JavaScript语言的软件工程 引言 在当今软件开发的浪潮中,JavaScript已不仅仅是一个简单的前端脚本语言。它的位置已经升华为全栈开发的重要语言之一,借助Node.js等技术,JavaScript不仅可以用于浏览器环境,还可以在后端服务器中运…...
【Qt】04-Lambda表达式
前言一、概念引入二、使用方法2.1 基本用法代码示例2.2 捕获外部变量2.3 参数列表 三、完整代码mywidget.cppsecondwidget.cppmywidget.hsecondwidget.h 总结 前言 一、概念引入 Lambda表达式(Lambda Expressions)是C11标准引入的一种匿名函数对象&…...
Golang 生态学习
1. Go 语言基础 在深入 Go 语言的生态之前,首先需要掌握 Go 语言本身的核心特性。 • Go 语言官方文档:https://golang.org/doc/ Go 官方文档是学习语言基础和标准库的首选资源。 • 学习内容: • 基础语法:数据类型、控制流…...
Arcgis Pro安装完成后启动失败的解决办法
场景 之前安装的Arcgis Pro 今天突然不能使用了,之前是可以使用的,自从系统更新了以后就出现了这个问题。 环境描述 Arcgis Pro 3.0 Windows 10 问题描述 打开Arcgis Pro,页面也不弹出来,打开任务管理器可以看到进程创建之后&…...
支持向量机SVM的应用案例
支持向量机(Support Vector Machine,SVM)是一种强大的监督学习算法,广泛应用于分类和回归任务。 基本原理 SVM的主要目标是周到一个最优的超平面,该超平面能够将不同类别的数据点尽可能分开,并且使离该超平面最近的数…...
Linux中的Iptables介绍
文章目录 iptables1. 概述2. **工作原理**3. 数据包处理流程与规则匹配顺序4. 常用的匹配条件5. 动作类型6. 基本命令7. 高级功能 iptables 1. 概述 Iptables 是一个用于配置 Linux 内核防火墙的用户空间工具。它能够对进出服务器的网络数据包进行过滤、修改和转发等操作&…...
14天学习微服务-->第2天:Spring Cloud深入与实践
第2天:Spring Cloud深入与实践 一、Spring Cloud核心组件深入 在微服务架构中,Spring Cloud 提供了一系列核心组件来支持服务的注册与发现、配置管理、负载均衡等功能。今天我们将深入学习其中的三个关键组件:Eureka/Nacos(服务…...
使用 Box2D 库开发愤怒的小鸟游戏
使用 Box2D 库开发愤怒的小鸟游戏 Box2D 是一个开源的 2D 物理引擎,广泛应用于游戏开发中,特别是在模拟物体的运动、碰撞、重力等方面。在本文中,我们将利用 Box2D 库开发一个简化版的 愤怒的小鸟 游戏。我们将一步步展示如何实现物理引擎的…...
C# ComboBox 控件属性
ComboBox 的基本属性 在C#中,ComboBox控件具有多种属性,这些属性可以帮助开发者更好地控制和管理控件的各个方面。以下是一些基本的ComboBox属性及其功能: 公共属性 AccessibilityObject:获取分配给该控件的AccessibleObject。 Ac…...
《keras 3 内卷神经网络》
keras 3 内卷神经网络 作者:Aritra Roy Gosthipaty 创建日期:2021/07/25 最后修改时间:2021/07/25 描述:深入研究特定于位置和通道无关的“内卷”内核。 (i) 此示例使用 Keras 3 在 Colab 中查看 GitHub …...
Linux:文件描述符fd、系统调用open
目录 一、文件基础认识 二、C语言操作文件的接口 1.> 和 >> 2.理解“当前路径” 三、相关系统调用 1.open 2.文件描述符 3.一切皆文件 4.再次理解重定向 一、文件基础认识 文件 内容 属性。换句话说,如果在电脑上新建了一个空白文档࿰…...
ToDesk设置临时密码和安全密码都可以当做连接密码使用
ToDesk 在各领域办公都已经是非常常见了 为了安全 ToDesk 设置了连接密码,想连接 需要输入远程码和连接密码 我们刚打开 系统默认给我们用的是临时密码,安全性确实很强 和定时Tokey一样,固定时间切换。 但是 如果我们要经常连接这个电脑&a…...
C#防止重复提交
C#防止重复提交 文章目录 C#防止重复提交前言防止重复提交的思路Web API 防止重复提交代码实现代码讲解使用方法 MVC防止重复提交总结 前言 当用户在前端进行提交数据时,如果网络出现卡顿和前端没有给出响应的话顾客通常都会狂点提交按钮,这样就很容易导…...
递归算法学习v2.2
46. 全排列 class Solution {List<List<Integer>> ret;List<Integer> path;boolean[] check;public List<List<Integer>> permute(int[] nums) {ret new ArrayList<>();path new ArrayList<>();check new boolean[nums.length…...
unity插件Excel转换Proto插件-ExcelToProtobufferTool
unity插件Excel转换Proto插件-ExcelToProtobufferTool **ExcelToProtobufTool 插件文档****1. 插件概述****2. 默认配置类:DefaultIProtoPathConfig****属性说明** **3. 自定义配置类****定义规则****示例代码** **4. 使用方式****4.1 默认路径****4.2 自定义路径**…...
manim(manimgl)安装教学-win11(2024-08)
manim 目前的两种版本:★★ 稍微捋一捋【项目中的 readme.md 十分重要】 manimgl 是 Grant Sanderson(YouTube频道 3Blue1Brown的作者)等人开发。 现在为 manimgl,在维护中。 manimCE 是2020年后的 manim 分支 manim community e…...
【语言处理和机器学习】概述篇(基础小白入门篇)
前言 自学笔记,分享给语言学/语言教育学方向的,但对语言数据处理感兴趣但是尚未入门,却需要在论文中用到的小伙伴,欢迎大佬们补充或绕道。ps:本文不涉及公式讲解(文科生小白友好体质)ÿ…...
脚本工具:PYTHON
Python 是一种高级编程语言,以其简洁清晰的语法和强大的功能被广泛应用于各种领域,包括自动化脚本编写、数据分析、机器学习、Web开发等。以下是一些关于使用 Python 编写脚本工具的基本介绍、常用库以及一些实用技巧总结。 这里写目录标题 基础知识安装…...
一文讲解Redis常见使用方式
1. 单机模式部署 适用场景: • 开发和测试环境,或者对高可用性要求不高的小型项目。 部署步骤: 1. 拉取 Redis 镜像: docker pull redis:latest 2. 运行 Redis 容器: docker run -d --name redis-single -p 637…...
Gin 源码概览 - 路由
本文基于gin 1.1 源码解读 https://github.com/gin-gonic/gin/archive/refs/tags/v1.1.zip 1. 注册路由 我们先来看一段gin代码,来看看最终得到的一颗路由树长啥样 func TestGinDocExp(t *testing.T) {engine : gin.Default()engine.GET("/api/user", f…...
【计算机网络】传输层协议TCP与UDP
传输层 传输层位于OSI七层网络模型的第四层,主要负责端到端通信,可靠性保障(TCP),流量控制(TCP),拥塞控制(TCP),数据分段与分组,多路复用与解复用等,通过TCP与UDP协议实现…...
iOS UIScrollView的一个特性
1如果UIScrollView 的contentSize.height > scrollView.bounds.size.height - scrollView.contentInset.top - scrollView.contentInset.bottom , 则scrollView就可以滚动,否则无法滚动 并且最大的滚动范围就是 contentSize.height - ( s…...
Docker 实现MySQL 主从复制
一、拉取镜像 docker pull mysql:5.7相关命令: 查看镜像:docker images 二、启动镜像 启动mysql01、02容器: docker run -d -p 3310:3306 -v /root/mysql/node-1/config:/etc/mysql/ -v /root/mysql/node-1/data:/var/lib/mysql -e MYS…...
python爬虫入门(实践)
python爬虫入门(实践) 一、对目标网站进行分析 二、博客爬取 获取博客所有h2标题的路由 确定目标,查看源码 代码实现 """ 获取博客所有h2标题的路由 """url "http://www.crazyant.net"import re…...
通过Ukey或者OTP动态口令实现windows安全登录
通过 安当SLA(System Login Agent)实现Windows安全登录认证,是一种基于双因素认证(2FA)的解决方案,旨在提升 Windows 系统的登录安全性。以下是详细的实现方法和步骤: 1. 安当SLA的核心功能 安…...
C 语言雏启:擘画代码乾坤,谛观编程奥宇之初瞰
大家好啊,我是小象٩(๑ω๑)۶ 我的博客:Xiao Xiangζั͡ޓއއ 很高兴见到大家,希望能够和大家一起交流学习,共同进步。* 这一课主要是让大家初步了解C语言,了解我们的开发环境,main函数,库…...
【Linux系统编程】—— 进程替换及其在操作系统中的应用与实现
文章目录 什么是进程替换?进程替换当中的接口单进程替换多进程的替换详解exec接口execlexeclpexecv 前言: 本篇博客将深入探讨进程替换的概念及其在操作系统中的作用。我们将介绍进程替换的基本原理,探讨操作系统如何通过进程的切换来实现任务…...
“裸奔”时代下该如何保护网络隐私
网络隐私的保护之道 引言 在这个信息爆炸的时代,网络已经深入到我们生活的每一个角落。你是否曾想过,在享受这些便利时,你的个人隐私正面临着严峻的挑战?网络隐私的现状警示着我们,信息泄露的事件屡见不鲜࿰…...
分类问题(二元,多元逻辑回归,费歇尔判别分析)spss实操
分类模型: 二分类和多分类: 对于二分类模型 ,我们将介绍逻辑回归和Fisher线性判别分析两种分类算法; 对于多分类模型,我们将简单介绍Spss中的多分类线性判别分析和多分类逻辑回归的操作步骤 二分类: 基于广义线性模型&#x…...
推荐一个开源的轻量级任务调度器!TaskScheduler!
大家好,我是麦鸽。 这次推荐一款轻量级的嵌入式任务调度器,目前已经有1.4K的star,这个项目比较轻量化,只有5个源文件,可以作为学习的一个开源项目。 核心文件 项目概述: 这是一个轻量级的协作式多任务处理&…...
Spring 核心技术解析【纯干货版】- IV:Spring 切面编程模块 Spring-Aop 模块精讲
随着软件开发技术的不断进步,面向切面编程(AOP)作为一种重要的编程思想,已经在现代开发中占据了重要地位。它通过将横切逻辑从业务逻辑中分离出来,使得代码更加清晰、易于维护。Spring AOP 作为 Spring 框架的核心模块…...