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

IOS safari 播放 mp4 遇到的坎儿

起因

事情的起因是调试 IOS 手机下播放服务器接口返回的 mp4 文件流失败。对于没调试过移动端和 Safari 的我来说着实费了些功夫,网上和AI也没有讲明白。好在最终大概理清楚了,在这里整理出来供有缘人参考。

问题

因为直接用 IOS 手机的浏览器打开页面去播放也能复现,所以问题出现在 IOS上的 Safari 浏览器上。

问题主要分两类:

  • 一、视频能播放,但是不主动设置 poster,就不显示默认的 poster
  • 二、视频不能播放

首先文件是没有问题的,是主流浏览器都支持的视频编码格式为 H.264(AVC).mp4 文件,由文件格式导致的播放失败问题,网上很多,这里就不赘述了。

问题复现和解决

因为其他浏览器可以正常播放,所以用来对比差异时,我直接在PC端(Edge)查看信息,而 Safari 是在手机上访问项目页面,以及局域网下访问本地运行的 web 服务页面。

用到的工具如下:

  • Live Server 用来运行本地 web 页面
  • node + nodemon + Express 用来运行文件服务,模拟后端接口,nodemon 方便实时同步运行修改内容
  • video.js 播放视频的插件,直接用 CDN。
  • vConsole 移动端查看控制台,直接用 CDN

编写简单 demo

排查问题的时候我是一点一点将实现方式还原到最原始的方式:

  1. 后端接口 + video.js
  2. 后端接口 + video标签
  3. 本地运行的接口 + video标签
  4. 本地文件 + video标签

所以这里再梳理,就可以反向从最简单的方式排查,在本地写个用本地视频文件 + video标签的demo,为了后续排查方便,将事件日志也打印出来。

视频资源我用的 https://vjs.zencdn.net/v/oceans.mp4 下载到本地,不过它的开头是黑屏淡入的,为了方便区分 “不显示poster”,我将开头的几秒黑屏裁剪掉了。
也可以用安卓手机录制一个,默认就是支持播放的 mp4 文件。(PS:别用QQ录屏,编码不对)

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script><script>var vConsole = new window.VConsole()</script><style>#video {width: 480px;height: 25vh;max-width: 100%;/* 背景色用于在视频不显示poster时查看占位 */background: pink;border: 4px solid pink;}</style></head><body><div class="video-box"><video id="video" src="oceans.mp4" controls preload="auto" playsinline="true" muted></video></div><div id="log"><h2>日志:方便手机上的 Safari 查看</h2><div class="log__content"></div></div><script>// 打印日志function log(msg) {// 页面上打印日志const logContentEl = document.querySelector('.log__content')const pEl = document.createElement('p')pEl.textContent = msglogContentEl.appendChild(pEl)// 控制台打印日志console.log(msg)}const video = document.getElementById('video')// 主要关心的事件const eventNames = [{ name: 'abort', desc: '当音频/视频的加载已放弃时触发' },{ name: 'canplay', desc: '当浏览器可以开始播放音频/视频时触发' },{ name: 'canplaythrough', desc: '当浏览器预计能够在不因缓冲而停顿的情况下持续播放指定的音频/视频时触发' },{ name: 'durationchange', desc: '当音频/视频的时长已更改时触发' },{ name: 'error', desc: '当在音频/视频加载期间发生错误时触发' },{ name: 'loadeddata', desc: '当浏览器已加载音频/视频的当前帧时触发' },{ name: 'loadedmetadata', desc: '当浏览器已加载音频/视频的元数据时触发' },{ name: 'loadstart', desc: '当浏览器开始查找音频/视频时触发' },{ name: 'play', desc: '当音频/视频已开始或不再暂停时触发' },{ name: 'playing', desc: '当音频/视频在因缓冲而暂停或停止后已就绪时触发' },{ name: 'progress', desc: '当浏览器正在下载音频/视频时触发' },{ name: 'timeupdate', desc: '当音频/视频的播放位置发生改变时触发' },{ name: 'waiting', desc: '当视频由于需要缓冲下一帧而停止,等待时触发' }]// 注册video事件监听器eventNames.forEach(v => {video.addEventListener(v.name, () => {// 打印日志log(`【readyState: ${video.readyState}${v.name}: ${v.desc}`)})})</script></body>
</html>

preload 默认是 metadata,和 auto 的日志结果一样。为了排除一些可能性,我将其设置了 auto
playsinline="true" 防止 IOS 播放视频时自动打开全屏。

问题1 不显示预览图

demo 页面加载后发现,Safari 浏览器没有显示视频的预览图:

在这里插入图片描述
Edge 显示了预览图:

在这里插入图片描述
通过日志发现,Safari 加载完元数据后(loadedmetadata)就不会继续加载了。

我们知道,视频的预览图就是 video 标签的 poster 属性,当 poster 有值时就会显示指定的图片,当 poster 没有值时,浏览器就会自动处理,而不同浏览器的处理方式也不一样,可能会有这几种情况:

  1. 如果配置了预加载,显示加载后的第一帧,或第二、三桢。
  2. 什么都不显示,或显示默认的播放器背景样式

从日志可以看到,Safari 只加载了元数据,并没有加载画面。查看官方文档,得到了解答:

在这里插入图片描述
再看 poster 的说明:
在这里插入图片描述
既然如此,那只能提供一个 poster 来解决了。

问题2 视频不能播放

问题复现

播放 demo 示例里的视频,是可以正常播放的。

将播放方式改成 video.js + 本地文件,播放是正常的。代码如下:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><link href="https://unpkg.com/video.js@7.10.2/dist/video-js.min.css" rel="stylesheet" /><script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script><script>var vConsole = new window.VConsole()</script><style>#video {width: 480px;max-width: 100%;height: 25vh;/* 背景色用于在视频不显示poster时查看占位 */background: pink;border: 4px solid pink;}</style></head><body><video id="video" class="video-js" controls preload="auto" playsinline="true" muted><source src="oceans.mp4" type="video/mp4" /></video><div id="log"><h3>日志:方便手机上的 Safari 查看</h3><div class="log__content"></div></div><script src="https://unpkg.com/video.js@7.10.2/dist/video.min.js"></script><script>// 打印日志function log(msg) {// 页面上打印日志const logContentEl = document.querySelector('.log__content')const pEl = document.createElement('p')pEl.textContent = msglogContentEl.appendChild(pEl)// 控制台打印日志console.log(msg)}// 主要关心的 videojs 事件const eventNames = [{ name: 'abort', desc: '当音频/视频的加载已放弃时触发' },{ name: 'canplay', desc: '当浏览器可以开始播放音频/视频时触发' },{ name: 'canplaythrough', desc: '当浏览器预计能够在不因缓冲而停顿的情况下持续播放指定的音频/视频时触发' },{ name: 'durationchange', desc: '当音频/视频的时长已更改时触发' },{ name: 'error', desc: '当在音频/视频加载期间发生错误时触发' },{ name: 'loadeddata', desc: '当浏览器已加载音频/视频的当前帧时触发' },{ name: 'loadedmetadata', desc: '当浏览器已加载音频/视频的元数据时触发' },{ name: 'loadstart', desc: '当浏览器开始查找音频/视频时触发' },{ name: 'play', desc: '当音频/视频已开始或不再暂停时触发' },{ name: 'playing', desc: '当音频/视频在因缓冲而暂停或停止后已就绪时触发' },{ name: 'progress', desc: '当浏览器正在下载音频/视频时触发' },{ name: 'timeupdate', desc: '当音频/视频的播放位置发生改变时触发' },{ name: 'waiting', desc: '当视频由于需要缓冲下一帧而停止,等待时触发' }]const player = videojs('video')player.ready(() => {// 注册video事件监听器eventNames.forEach(v => {player.on(v.name, () => {// 打印日志log(`【readyState: ${player.readyState()}${v.name}: ${v.desc}`)})})})</script></body>
</html>

然后换成后端接口地址,Edge 可以播放,但是 Safari 就失败了。

video 标签方式:
在这里插入图片描述
video.js 方式:报错 The media could not be loaded, either because the server or network failed or because the format is not supported.
在这里插入图片描述

我肯定不是服务器网络故障,也不是文件格式不支持,所以原因只能是接口了。

本地模拟接口

为了不麻烦后端同事,我只能在本地搭建一个服务器,模拟项目接口。

搭建服务

创建 app.js 文件

const express = require('express')
const fs = require('fs')
const path = require('path')const app = express()app.get('/', (req, res) => {res.send('Stupid IOS')
})app.listen(3000, () => {const ip = 192.169.3.7 // 我的局域网ipconsole.log(`server is running on http://${ip}:3000`)
})
# 安装依赖
npm i express
# 已安装 nodemon,所以直接使用
nodemon app.js

访问 http://192.169.3.7:3000/。把 oceans.mp4 视频文件放到 app.js 同目录下,后面就编写接口就行。

方式1 使用 express 封装好的方法返回文件流
// 方式1: 使用封装好的方法返回文件流
app.get('/type1/:file', (req, res) => {const fileName = req.params.fileconst filePath = path.join(__dirname, fileName)res.sendFile(filePath)
})

文件地址:http://192.169.3.7:3000/type1/oceans.mp4

video标签和 video.js 播放都正常。

方式2 手动读取全部文件流并返回

同样是接口,本地模拟的正常,项目接口不能播放,那就继续更细致的模拟,手动读取文件流,设置相同的响应头。

查看项目接口的响应头,排除一些范围,只模拟有可能影响的:

// 方式2: 手动读取全部文件流并返回
app.get('/type2/:file', (req, res) => {const fileName = req.params.fileconst filePath = path.join(__dirname, fileName)// 打印请求头console.log(req.headers)fs.stat(filePath, (err, stats) => {if (err) {return res.status(404).send('file not found')}// 设置响应头res.set({'Accept-Ranges': 'bytes', // 支持 Range 请求'Cache-Control': 'no-cache, no-store', // 不缓存'Content-Type': 'video/mp4;charset=UTF-8','Content-Range': `bytes 0-${stats.size - 1}/${stats.size}`,'Content-Length': stats.size,'Content-Disposition': 'attachment;filename="oceans.mp4"',ETag: `"${stats.ino.toString()}-${stats.size.toString()}-${Date.now().toString()}"`,'Last-Modified': stats.mtime.toUTCString(),'Pragma': 'no-cache'})const stream = fs.createReadStream(filePath)stream.pipe(res)})
})

文件地址:http://192.169.3.7:3000/type2/oceans.mp4

问题依旧存在,于是查看官方文档,找到一段说明:
在这里插入图片描述

Range 请求就是范围请求或分块传输,客户端通过请求头 Range 指定当前请求想要获取的数据子节范围,服务器根据这个范围,读取文件流,并将读取的内容返回给客户端。
通常,服务器会返回 206 状态码,表示范围请求的响应结果。并且需要在响应头中包含 Content-Range 字段,指明实际返回的数据范围,以及整个资源的总大小。

可是我加了支持 range 请求的响应头啊:'Accept-Ranges': 'bytes'

继续看文档,下面介绍了如何确认服务器是否支持 range 请求:
在这里插入图片描述
大概意思就是主动发送一个指定范围 100 bytes 的请求,看返回的数据是100 bytes,那就是支持,如果返回了整个文件,那就是不支持。

查看之前服务器中打印的请求头,Range 请求头的值:

  • Edge 是 0-,表示获取整个资源
  • Safari 是 0-1:表示获取位置01 的子节的资源,注意可不是从开头到第1个子节,这个范围的请求数是2个子节。

因为我每次都返回的完整的文件流,没有按照 Safari 的要求范围处理,所以属于不支持。

看来仅仅配置响应头是不行的,还要正确处理请求头中的指定范围。

方式3 手动读取指定范围的文件流并返回-分块传输

原来 Safari 会先发送一个获取范围为 bytes=0-1 的请求,以测试服务器是否支持 range 请求。

于是我手动改了下响应头,去掉那些没有影响的,还是返回整个文件流:

    // 设置响应头res.set({'Accept-Ranges': 'bytes', // 支持 Range 请求'Cache-Control': 'no-cache, no-store', // 不缓存'Content-Type': 'video/mp4;charset=UTF-8','Content-Range': `bytes 0-1/${stats.size}`,'Content-Length': stats.size,})

看来仅仅伪造响应头还是不行,还要返回正确大小的文件流,那就编写分块传输的接口:

// 方式3: 手动读取指定范围的文件流并返回-分块传输
app.get('/type3/:file', (req, res) => {const fileName = req.params.fileconst filePath = path.join(__dirname, fileName)// 查看请求头的范围console.log(req.headers.range)fs.stat(filePath, (err, stats) => {if (err) {return res.status(404).send('file not found')}// 设置响应头res.set({'Accept-Ranges': 'bytes', // 支持 Range 请求'Cache-Control': 'no-cache, no-store', // 不缓存'Content-Type': 'video/mp4'})const range = req.headers.rangelet partslet start = 0let end = stats.size - 1if (range) {parts = range.replace(/bytes=/, '').split('-')start = parseInt(parts[0], 10)end = parts[1] ? parseInt(parts[1], 10) : stats.size - 1}if (start >= stats.size || end > stats.size) {// 如果Range请求超出文件大小,返回416状态码return res.status(416).end()}if (start >= 0 && end >= 0) {// 处理 Range 请求res.set({'Content-Range': `bytes ${start}-${end}/${stats.size}`,'Content-Length': end - start + 1})// 部分内容状态码,返回200浏览器也能正常处理res.status(206)}const stream = fs.createReadStream(filePath, { start, end })stream.pipe(res)})
})

终于视频可以正常播放了,video.js 也可以播放。

Safari 的校验还挺严格,如果服务器正确处理了这个2子节的请求,Safari 就会开始正式发送正常范围的请求。

服务器的日志可以体现出来:

bytes=0-1
bytes=0-51883958
bytes=196608-51883958
bytes=458752-51883958

最终查看了后端代码,果然接口没有处理 range 请求,在修改逻辑后,功能终于正常。

继续伪造 range 接口

为了搞清楚 Safari 的校验到底有多严格,我再次尝试模拟了一下第一次请求的响应:

	const range = req.headers.rangelet partslet start = 0let end = stats.size - 1// 增加逻辑 start-----------------------------------------if (range === 'bytes=0-1') {// 设置响应头res.set({'Accept-Ranges': 'bytes', // 支持 Range 请求'Cache-Control': 'no-cache, no-store', // 不缓存'Content-Type': 'video/mp4;charset=UTF-8','Content-Range': `bytes 3-3/${stats.size}`, // 随便写个范围'Content-Length': 2})const stream = fs.createReadStream(filePath, { start:100, end:200 }) // 随便获取个范围,但不能少于2stream.pipe(res)return res.status(206) // 随便返回个状态码}// 增加逻辑 end-----------------------------------------if (range) {parts = range.replace(/bytes=/, '').split('-')start = parseInt(parts[0], 10)end = parts[1] ? parseInt(parts[1], 10) : stats.size - 1}

再次提醒,bytes=0-1 表示请求的位置是01的子节,是2个子节,而不是 1-0=1 的子节数量。

不断测试下,发现只要满足这几个要求,Safari 就认为接口支持 range 请求:

  1. Content-Length 要正确。
  2. Content-Range 的范围要合理。
  3. 返回了不少于 Range 请求头要求大小的文件流数据。

而下面这几点,不影响 Safari 的校验结果:

  1. Content-Range 的范围和 Range 的指定范围不一样
  2. 读取的数据范围和 Range 的指定范围不一样
  3. 返回任意状态码

总结

  1. IOS 上的 Safari 不支持 video 预加载(preload),浏览器不会自动提取帧画面作为默认的 poster 预览图
  2. Safari 上使用 video 播放视频,必须支持并正确处理 Range 范围请求,浏览器会先发送 bytes=0-1 范围的请求来测试服务器是否支持 Range 请求,如果校验成功,就会继续发送正常范围的 Range 请求。否则不再请求资源。

相关文章:

IOS safari 播放 mp4 遇到的坎儿

起因 事情的起因是调试 IOS 手机下播放服务器接口返回的 mp4 文件流失败。对于没调试过移动端和 Safari 的我来说着实费了些功夫&#xff0c;网上和AI也没有讲明白。好在最终大概理清楚了&#xff0c;在这里整理出来供有缘人参考。 问题 因为直接用 IOS 手机的浏览器打开页面…...

LLM常见面试题(26-30题)--langchain篇

26&#xff0c;什么是langchain? 一个局域语言模型的框架&#xff0c;用于构建聊天机器人、生成式回答、摘要等功能。 核心思想是将不同的组件“链”在一起&#xff0c;连接大语言模型和外部资源&#xff0c;以创建更高级的语言模型应用。使得开发人员可以快速开发原型系统和…...

【Python运维】构建基于Python的自动化运维平台:用Flask和Celery

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 在现代IT运维中,自动化运维平台扮演着至关重要的角色,它能够显著提高运维效率,减少人为错误,并且增强系统的可维护性。本文将引导读者如…...

socket编程(C++/Windows)

相关文章推荐&#xff1a; Socket 编程基础 面试官&#xff0c;不要再问我三次握手和四次挥手 TCP的三次握手与四次挥手 参考视频&#xff1a; https://www.bilibili.com/video/BV1aW4y1w7Ui/?spm_id_from333.337.search-card.all.click TCP通信流程 服务端 #include<…...

Spring Boot介绍、入门案例、环境准备、POM文件解读

文章目录 1.Spring Boot(脚手架)2.微服务3.环境准备3.1创建SpringBoot项目3.2导入SpringBoot相关依赖3.3编写一个主程序&#xff1b;启动Spring Boot应用3.4编写相关的Controller、Service3.5运行主程序测试3.6简化部署 4.Hello World探究4.1POM文件4.1.1父项目4.1.2父项目的父…...

【hackmyvm】deba靶机wp

tags: HMVnodejs反序列化CVE-2017-5941wine命令定时任务 1. 基本信息^toc 文章目录 1. 基本信息^toc2. 信息收集2.1. 端口扫描2.2. 目录扫描 3. nodejs反序列化 (CVE-2017-5941)4. www-data提权low用户5. 定时任务提权6. wine命令 提权root6.1. 利用CS获取root 靶机链接 http…...

新浪微博大数据面试题及参考答案(数据开发和数据分析)

介绍一下你所掌握的计算机网络和操作系统相关知识 计算机网络:计算机网络是将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。我掌握了网络协议…...

去除 el-input 输入框的边框(element-ui@2.15.13)

dgqdgqdeMac-mini spid-admin % yarn list --pattern element-ui yarn list v1.22.22 └─ element-ui2.15.13 ✨ Done in 0.23s.dgqdgqdeMac-mini spid-admin % yarn list vue yarn list v1.22.22 warning Filtering by arguments is deprecated. Please use the pattern opt…...

Qt 应用程序转换为服务

一、在 Windows 上将 Qt 应用程序转换为服务 方法1&#xff1a; 创建一个 Windows 服务应用程序&#xff1a; Windows 服务应用程序是一个没有用户界面的后台进程&#xff0c;通常由 Win32 Service 模板创建&#xff0c;或者直接编写 main() 函数以实现服务逻辑。 修改 Qt 应…...

C/C++ 数据结构与算法【树和森林】 树和森林 详细解析【日常学习,考研必备】带图+详细代码

一、树的存储结构 1&#xff09;双亲表示法实现&#xff1a; 定义结构数组存放树的结点&#xff0c;每个结点含两个域: 数据域&#xff1a;存放结点本身信息。双亲域&#xff1a;指示本结点的双亲结点在数组中的位置。 特点&#xff1a;找双亲简单&#xff0c;找孩子难 C语…...

潇洒郎:部署Dify, 安装Ollama,Ollama下载模型,Dify配置模型

Ollama 1、安装ollama Windows版本下载&#xff1a;2024WindowsOllama最新0.5.4版本资源-CSDN文库, 下载速度超快&#xff0c;官网太慢了 双击安装&#xff0c;安装成功后&#xff0c;托盘区有Ollama图标 右键View logs打开目录 C:\Users\Administrator\AppData\Local\Oll…...

如何在 Linux 中设置 SSH 免密码登录

一般情况下&#xff0c;我们通过一台 Linux 服务器远程另外一台服务器时&#xff0c;每次远程都需要输入对端机器的密码。 ssh root192.168.21.20 有没有这样一种情况&#xff0c;我们不用输入密码就可以远程别的服务器呢&#xff1f; 答案是肯定的&#xff0c;如果我们在 Li…...

网络安全词云图与技术浅谈

网络安全词云图与技术浅谈 一、网络安全词云图生成 为了直观地展示网络安全领域的关键术语&#xff0c;我们可以通过词云图&#xff08;Word Cloud&#xff09;的形式来呈现。词云图是一种数据可视化工具&#xff0c;它通过字体大小和颜色的差异来突出显示文本中出现频率较高…...

Linux | Ubuntu零基础安装学习cURL文件传输工具

目录 介绍 检查安装包 下载安装 手册 介绍 ‌cURL是一个利用URL语法在命令行下工作的文件传输工具&#xff0c;首次发行于1997年‌‌12。cURL支持多种协议&#xff0c;包括FTP、FTPS、HTTP、HTTPS、TFTP、SFTP、Gopher、SCP、Telnet、DICT、FILE、LDAP、LDAPS、IMAP、POP3…...

重启ubuntu服务器,如何让springboot服务自动运行

文章目录 1. 使用 systemd 服务步骤&#xff1a; 2. 使用 cron 的 reboot 任务步骤&#xff1a; 3. 使用 init.d 脚本&#xff08;适用于较旧版本&#xff09;步骤&#xff1a; 推荐方案 为了确保在重启Ubuntu服务器后&#xff0c;让springboot的服务test.jar象 nohup java -ja…...

MySQL 查询大偏移量(LIMIT)问题分析

大偏移量查询缓慢?LIMIT: 会进行两步操作 性能消耗在哪里了?OFFSET操作问题 2 LIMIT 操作 如何优化? 大偏移量查询缓慢? 示例:(假设age字段有索引) SELECT * FROM test WHERE age>18 LIMIT 10000000 ,10;分析MySQL的 LIMIT 10000000 , 10 LIMIT: 会进行两步操作 OFF…...

深入探究C++pow函数的优势和劣势 原理

优势 功能强大 支持多种数据类型&#xff1a; C 的pow()函数在<cmath>头文件中定义&#xff0c;它能够灵活地处理不同的数据类型。对于整数类型&#xff0c;如int、long long等&#xff0c;它能准确地计算幂次方。以计算棋盘上的麦粒数为例&#xff0c;传说国际象棋棋盘…...

Vue中动态样式绑定+CSS变量实现切换明暗主题功能——从入门到进阶

1.直接借助Vue的动态绑定样式绑定 Vue动态样式绑定 在Vue中&#xff0c;动态样式绑定是一种强大的功能&#xff0c;它允许开发者根据数据的变化动态地更新元素的样式。以下是对Vue动态样式绑定的详细知识梳理与详解&#xff1a; 一、基础知识 Vue的动态样式绑定主要通过v-b…...

Linux 硬盘扩容 分区 挂载

Linux 硬盘扩容 分区 & 挂载 1. 添加分区 1.1. 查看新添加的硬盘 fdisk -l假设当前未挂载的盘符是/dev/sdb&#xff0c;后文中所有操作都按挂载/dev/sdb 操作 1.2. 分区管理 小硬盘 fdisk /dev/sdb大硬盘&#xff08;2TB以上&#xff09; gdisk /dev/sdb1.3. 编辑分…...

Microsoft word@【标题样式】应用不生效(主要表现为在导航窗格不显示)

背景 随笔。Microsoft word 2013基础使用&#xff0c;仅做参考和积累。 问题 Microsoft word 2013&#xff0c;对段落标题文字应用【标题样式】不生效&#xff08;主要表现为在导航窗格不显示&#xff09;。 图1 图2 观察图1和图2&#xff0c;发现图1的文字在应用【标题一】样…...

react中实现拖拽排序

效果图&#xff1a;如下 效果说明&#xff1a; 1. 点击“选择”按钮&#xff0c;打开弹窗 2. 左侧数据是调接口回显来的 3. 点击左侧某条数据&#xff0c;这条被点击的数据就会被添加到右侧 4. 右侧的数据可以上下拖动换位置 5. 右侧有数据时&#xff0c;点击"确定"…...

云效流水线自动化部署web静态网站

云效流水线部署静态网站 背景新建流水线配置流水线运行流水线总结 背景 配置流水线以前&#xff0c;每次更新导航网站都要登进去宝塔后台&#xff0c;删掉旧的目录和文件&#xff0c;再上传最新的文件&#xff0c;太麻烦啦 网上的博客基本都是分享vue项目&#xff0c;这一篇是…...

【Docker命令】如何使用 `docker cp` 命令拷贝容器文件到宿主机

在使用 Docker 时&#xff0c;我们经常需要将容器内的文件或目录复制到宿主机上。下面是使用 docker cp 命令进行操作的简要指南。 步骤一&#xff1a;定位容器内文件或目录 首先&#xff0c;确定你想要从容器中拷贝的文件或目录路径。 步骤二&#xff1a;选择宿主机目标路径…...

leetcode hot 100 二叉搜索

35. 搜索插入位置 已解答 简单 相关标签 相关企业 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solut…...

WebRTC服务质量(09)- Pacer机制(01) 流程概述

WebRTC服务质量&#xff08;01&#xff09;- Qos概述 WebRTC服务质量&#xff08;02&#xff09;- RTP协议 WebRTC服务质量&#xff08;03&#xff09;- RTCP协议 WebRTC服务质量&#xff08;04&#xff09;- 重传机制&#xff08;01) RTX NACK概述 WebRTC服务质量&#xff08;…...

MIGRATE_ISOLATE浅析

在linux内核的migratetype中&#xff0c;有MIGRATE_ISOLATE这样一个内存迁移类型&#xff0c;注释里写着can’t allocate from here&#xff0c;这个是怎么做到的呢&#xff1f;他又有什么作用呢&#xff1f; 本文分析基于linux4.19.195 可以看到内核里&#xff0c;是函数set_…...

[bug]java导出csv用Microsoft Office Excel打开乱码解决

[bug]java导出csv用Microsoft Office Excel打开乱码 ‍ 现象 首先这个csv文件用macbook自带的 "Numbers表格" 软件打开是不乱码的, 但是使用者是Windows系统,他的电脑没有"Numbers表格"工具, ​​ 他用Microsoft Office Excel打开之后出现乱码,如下图…...

Ftrans数据摆渡系统 搭建安全便捷跨网文件传输通道

一、专业数据摆渡系统对企业的意义 专业的数据摆渡系统对企业具有重要意义&#xff0c;主要体现在以下几个方面‌&#xff1a; 1、‌数据安全性‌&#xff1a;数据摆渡系统通过加密传输、访问控制和审计日志等功能&#xff0c;确保数据在传输和存储过程中的安全性。 2、‌高…...

LabVIEW条件配置对话框

条件配置对话框&#xff08;Configure Condition Dialog Box&#xff09; 要求&#xff1a;Base Development System 当右键单击**条件禁用结构&#xff08;Conditional Disable Structure&#xff09;**并选择以下选项时&#xff0c;会显示此对话框&#xff1a; Add Subdiagr…...

【MySQL学习笔记】关于索引

文章目录 【MySQL学习笔记】关于索引1.索引数据结构2.索引存储3.联合索引3.1 联合索引的b树结构3.2 索引覆盖&#xff1f;回表&#xff1f;3.3 联合索引最左匹配原则3.5 索引下推 4.索引失效 【MySQL学习笔记】关于索引 1.索引数据结构 索引是一种能提高查询速度的数据结构。…...

面试经典 150 题——数组/字符串(一)

文章目录 1、合并两个有序数组1.1 题目链接1.2 题目描述1.3 解题代码1.4 解题思路 2、移除元素2.1 题目链接2.2 题目描述2.3 解题代码2.4 解题思路 3、删除有序数组中的重复项3.1 题目链接3.2 题目描述3.3 解题代码3.4 解题思路 4、删除有序数组中的重复项 II4.1 题目链接4.2 题…...

Ingress-Nginx Annotations 指南:配置要点全方面解读(下)

文章目录 1.HTTP2 Push Preload2.Server Alias3.Server snippet4.Client Body Buffer Size5.External Authentication6.Global External Authentication7.Rate Limiting8.Global Rate Limiting9.Permanent Redirect10.Permanent Redirect Code11.Temporal Redirect12.SSL Passt…...

Ramfs, rootfs 和 initramfs

什么是ramfs Ramfs 是一个非常简单的文件系统&#xff0c;它将 Linux 的磁盘缓存机制&#xff08;页面缓存和 dentry 缓存&#xff09;导出为可动态调整大小的基于 RAM 的文件系统。 通常&#xff0c;Linux 会将所有文件缓存在内存中。从后备存储&#xff08;通常是文件系统所…...

Xdebug

1、开启xdebug扩展 2、修改一下php.ini文件 xdebug.remote_enable 1 xdebug.remote_autostart 13、vscode安装插件php debug 4、生成launch.json文件&#xff0c;好像啥都不用改 5、vscode没有配置php路径的&#xff0c;需要去配置&#xff1a; 6、发起请求 8、代码断…...

LinkedList类 (链表)

目录 一. LinkedList 基本介绍 二. LinkedList 中的法及其应用 1. 添加元素 (1) add() (2) addAll() (3) addFirst() (4) addLast() 2. 删除元素 (1) remove() (2) removeAll() (3) removeFirst() (4) removeLast() 3. 遍历元素 (1) for 循环遍历 (2) for - each …...

删除拼排序链表中的重复元素(最优解)

题目来源 82. 删除排序链表中的重复元素 II - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head…...

arm架构 uos操作系统离线安装k8s

目录 操作系统信息 安装文件准备 主机准备 主机配置 配置hosts&#xff08;所有节点&#xff09; 关闭防火墙、selinux、swap、dnsmasq(所有节点) 系统参数设置(所有节点) 配置ipvs功能(所有节点) 安装docker&#xff08;所有节点&#xff09; 卸载老版本 安装docke…...

影视仓最新接口+内置本包方法的研究(2024.12.27)

近日喜欢上了研究影视的本地仓库内置&#xff0c;也做了一个分享到了群里。 内置本地仓库包的好处很明显&#xff0c;当前线路接口都是依赖网络上的代码站存放&#xff0c;如果维护者删除那就GG。 虽然有高手制作了很多本地包&#xff0c;但推送本地包到APP&#xff0c;难倒一片…...

Unity开发AR之Vuforia-MultiTarget笔记

前言 在增强现实(AR)技术蓬勃发展的今天,越来越多的开发者开始探索如何将AR应用于各种场景中。Vuforia作为一个领先的AR开发平台,为开发者提供了强大的工具和功能,使得创建AR体验变得更加简单和直观。本文将为您介绍Vuforia的基本概念、特点,以及如何配置和使用MultiTar…...

软体机器人研究报告:设计方法、材料与驱动、感知与控制

软体机器人因其出色的可变形性和高适应性受到了广泛关注&#xff0c;这些特性使其在医疗、救援、探测等复杂场景中展现出独特的优势和巨大的应用潜力。研究人员对软体机器人的设计方法、材料与驱动技术、感知与控制策略等方面进行深入研究&#xff0c;取得了一系列成果。 本文汇…...

XL系列433芯片、2.4G收发芯片 通讯对码说明

XL系列433芯片对码说明&#xff1a; 发射芯片 XL4456 通过数据脚接收高低电平然后经过调制将波形发出&#xff0c;而接收芯片 XL520 通过接收波形后进行解调&#xff0c;数据脚输出高低电平。至于具体的通信协议&#xff0c;需要用户自定义&#xff0c;一般而言&#xff0c;使…...

Redis的持久化机制

目录 RDB 触发机制 bgsave命令执行流程 RDB的文件处理 RDB的优缺点 AOF AOF工作流程 AOF缓冲区同步文件策略 AOF重写机制 AOF重写触发机制 AOF重写流程 在这里我们知道&#xff0c;redis存储的数据是存储在缓存中的&#xff0c;重启服务器数据就不存在了。要想持久化…...

LeetCode 83 :删除排链表中的重复元素

题目&#xff1a; 地址&#xff1a;https://leetcode.cn/problems/remove-duplicates-from-sorted-list/ 方法一&#xff1a; 方法二&#xff1a; package com.zy.leetcode.LeetCode_04;/*** Author: zy* Date: 2024-12-25-15:19* Description: 删除排链表中的里复元素* …...

复习打卡大数据篇——Hadoop MapReduce

目录 1. MapReduce基本介绍 2. MapReduce原理 1. MapReduce基本介绍 什么是MapReduce MapReduce是一个分布式运算程序的编程框架&#xff0c;核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序&#xff0c;并发运行在Hadoop集群上。 MapRed…...

无法验证服务器身份是什么意思?

当你尝试访问某个网站或连接到服务器时&#xff0c;系统突然弹出一个提示&#xff0c;告诉你“无法验证服务器身份”?这到底是什么意思?在如今这个网络安全日益重要的时代&#xff0c;了解这种提示的含义以及背后的原因是非常必要的。今天&#xff0c;我们就来了解一下“无法…...

用友-友数聚科技CPAS审计管理系统V4 getCurserIfAllowLogin存在SQL注入漏洞

免责声明: 本文旨在提供有关特定漏洞的深入信息,帮助用户充分了解潜在的安全风险。发布此信息的目的在于提升网络安全意识和推动技术进步,未经授权访问系统、网络或应用程序,可能会导致法律责任或严重后果。因此,作者不对读者基于本文内容所采取的任何行为承担责任。读者在…...

Java 深拷贝全面解析

1. 引言 在 Java 编程中&#xff0c;对象之间的复制是一个常见的需求。根据复制的深度不同&#xff0c;我们可以将复制分为浅拷贝和深拷贝。本文将深入探讨 深拷贝&#xff08;Deep Copy&#xff09; 的概念、应用场景、具体实现方法及其优缺点&#xff0c;并提供一些实用的建…...

极狐GitLab 17.7正式发布,可从 GitLab 丝滑迁移至极狐GitLab【一】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…...

云原生架构中的中间件容器化:优劣势与实践探索

在云原生架构逐步推进的过程中&#xff0c;许多企业已经开始将应用和服务容器化&#xff0c;以充分利用云计算带来的弹性和自动化。随着容器技术的发展&#xff0c;容器化不仅仅限于应用层&#xff0c;越来越多的中间件也被考虑纳入容器化范畴&#xff0c;包括Redis、Kafka、Ra…...

Go+chromedp实现Web UI自动化测试

1.为什么使用go进行UI自动化测试&#xff1f; 速度&#xff1a;Go速度很快&#xff0c;这在运行包含数百个UI测试的测试套件时是一个巨大的优势 并发性&#xff1a;可以利用Go的内置并发性(goroutines)来并行化测试执行 简单&#xff1a;Go的简约语法允许您编写可读且可维护…...