“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 的视频播放器时,用户通过上下滑动切换视频时,视频一直处于加载状态,无法正常播放…...
【Pandas】pandas Series transform
Pandas2.2 Series Function application, GroupBy & window 方法描述Series.apply()用于将一个函数应用到 Series 的每个元素或整个 SeriesSeries.agg()用于对 Series 数据进行聚合操作Series.aggregate()用于对 Series 数据进行聚合操作Series.transform()用于对 Series…...

【博客之星2024年度总评选】年度回望:我的博客之路与星光熠熠
【个人主页】Francek Chen 【人生格言】征途漫漫,惟有奋斗! 【热门专栏】大数据技术基础 | 数据仓库与数据挖掘 | Python机器学习 文章目录 前言一、个人成长与盘点(一)机缘与开端(二)收获与分享 二、年度创…...

飞牛 使用docker部署Watchtower 自动更新 Docker 容器
Watchtower是一款开源的Docker容器管理工具,其主要功能在于自动更新运行中的Docker容器 Watchtower 支持以下功能: 自动拉取镜像并更新容器。 配置邮件通知。 定时执行容器更新任务。 compose搭建Watchtower 1、新建文件夹 先在任意位置创建一个 w…...

【Block总结】TAdaConv时序自适应卷积,轻量高效的时间建模卷积|即插即用
论文解读:Temporally-Adaptive Models for Efficient Video Understanding 论文信息 标题:Temporally-Adaptive Models for Efficient Video Understanding 发表时间:2023年 作者:黄子渊等 论文链接:arXiv 论文 代…...

Spring Boot 项目启动报错 “找不到或无法加载主类” 解决笔记
一、问题描述 在使用 IntelliJ IDEA 开发基于 Spring Boot 框架的 Java 程序时,原本项目能够正常启动。但在后续编写代码并重建项目后,再次尝试运行却出现了 “错误:找不到或无法加载主类 com.example.springboot.SpringbootApplication” 的…...

CSS 网络安全字体
适用于 HTML 和 CSS 的最佳 Web 安全字体 下面列出了适用于 HTM L和 CSS 的最佳 Web 安全字体: Arial (sans-serif)Verdana (sans-serif)Helvetica (sans-serif)Tahoma (sans-serif)Trebuchet MS (sans-serif)Times New Roman (serif)Georgia (serif)Garamond (se…...

Linux高并发服务器开发 第十五天(fork函数)
目录 1.fork 函数 1.1创建子进程 1.2getpid 函数 1.3getppid 函数 1.4getgid函数 1.5循环创建 n 个子进程 1.6fork后父子进程异同 1.6.1读时共享,写时复制 1.6.2fork后父子进程共享 1.6.3gdb调试父子进程 1.fork 函数 pid_t fork(void); 成功:…...
【人工智能】Python中的自动化机器学习(AutoML):如何使用TPOT优化模型选择
《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 随着机器学习在各行业的广泛应用,模型选择和优化成为了数据科学家面临的主要挑战之一。自动化机器学习&am…...
探秘自然地理:从太阳到地球的奇妙之旅与灾害预警
在浩瀚无垠的宇宙中,我们的地球与太阳紧密相连,它们的奥秘和变化,时刻影响着我们的生活。今天,就让我们一同深入探索自然地理的基础知识,揭开太阳与地球的神秘面纱,同时了解那些可能给我们带来巨大影响的自…...

go语言zero框架通过chromedp实现网页在线截图的设计与功能实现
在 GoZero 框架中实现网页在线截图的功能,可以通过集成 chromedp 库来控制 Chrome 浏览器进行截图。chromedp 是一个基于 Chrome DevTools 协议的 Go 包,可以用来在 Go 程序中模拟浏览器操作,如页面截图、DOM 操作、表单提交等。 下面是一个…...
AI发展困境:技术路径与实践约束的博弈
标题:AI发展困境:技术路径与实践约束的博弈 文章信息摘要: AI技术发展路径主要受实践约束驱动,而非纯理论优势。大型AI实验室的成功更依赖优质执行力和资源优势,而非独特技术创新。当前AI发展面临评估体系与实际应用脱…...
[前端算法]排序算法
在js中一般用到sort方法 arr.sort((a,b)>{return a-b })基础排序 冒泡排序 function bubbleSort(arr) {let len arr.length;for (let i 0; i < len; i) {for(let j0;j<len-i-1;j){if(arr[j]>arr[j1]){[arr[j],arr[j1]] [arr[j1],arr[j]]}}}console.log(arr);…...

Zemax STAR 模块的入门设置
Zemax OpticStudio 中的 STAR 模块允许直接导入来自有限元分析 (FEA) 软件的变形数据,从而将光学设计与热和结构分析联系起来。这种集成可以分析实际环境因素(如热和机械应力)对光学性能的影响。该模块有助于了解光学系…...

知识图谱的语义叙事:构建智慧的连贯之路
目录 前言1. 什么是知识图谱的语义叙事1.1 语义清晰性1.2 叙事连贯性1.3 背景关联性 2. 知识图谱语义叙事的核心功能2.1 增强信息的可理解性2.2 提供上下文支持2.3 支持推理与发现2.4 提升知识可视化效果 3. 语义叙事的关键实现技术3.1 自然语言处理(NLP)…...

Oracle graph 图数据库体验-安装篇
服务端安装 环境准备 安装数据库 DOCKER 安装23AI FREE ,参考: https://container-registry.oracle.com/ords/f?p113:4:111381387896144:::4:P4_REPOSITORY,AI_REPOSITORY,AI_REPOSITORY_NAME,P4_REPOSITORY_NAME,P4_EULA_ID,P4_BUSINESS_AREA_ID:1…...
Nginx:从入门到实战使用教程
全方位解析Nginx:从入门到实战使用教程 Nginx安装、配置详细教程 文章目录 全方位解析Nginx:从入门到实战使用教程导语一、Nginx简介二、Nginx安装与配置 1. 在CentOS系统上安装Nginx:2. 在Ubuntu系统上安装Nginx:3. Nginx配置文…...

网络安全:信息时代的守护者
随着互联网的快速发展,网络安全问题日益成为全球关注的焦点。无论是个人用户、企业组织还是政府部门,网络安全都已成为保障信息安全、保护隐私、确保社会秩序的基石。在这个数字化时代,如何应对复杂多变的网络安全威胁,成为了我们…...
Visual Studio Code + Stm32 (IAR)
记录一下, 以前看别人在 vsc 下配置 stm32 工程非常麻烦。 最近,突然发现, iar 官方出了两个插件, iar build 、 iar C-Spy 安装之后,配置一下 iar 软件路径。 然后,直接打开工程目录,编译…...
JavaScript语言的正则表达式
JavaScript语言的正则表达式详解 正则表达式(Regular Expression,简称Regex或RegExp)是一种强大的文本处理工具,可以在字符串中执行模式匹配和替换操作。在JavaScript中,正则表达式是处理字符串时不可或缺的部分&…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...

前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...