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

web获取媒体流

1. 下面例子演示了录屏和截图功能:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>webRTC</title>
</head>
<body><!-- 录屏 --><video id="localVideo" autoplay playsinline muted></video><!-- 截图列表 --><div id="imgContainer"></div><button id="screenshotBtn" style="position: fixed;right: 0;top: 50%;">截图</button><script>const constraints = {audio: true,video: { width: 1280, height: 720 }}getLocalStream(constraints)// 获取本地音视频流async function getLocalStream(constraints) {// 获取媒体流const stream = await navigator.mediaDevices.getUserMedia(constraints)// 将媒体流设置到 video 标签上播放playLocalStream(stream)}// 播放本地视频流function playLocalStream(stream) {const videoEl = document.getElementById('localVideo')videoEl.srcObject = stream}</script><script>var imgList = []var screenshotBtn = document.getElementById('screenshotBtn')screenshotBtn.addEventListener('click', generatePhotos)// 拍照function generatePhotos () {// 添加滤镜var filterList = ['blur(5px)', // 模糊'brightness(0.5)', // 亮度'contrast(200%)', // 对比度'grayscale(100%)', // 灰度'hue-rotate(90deg)', // 色相旋转'invert(100%)', // 反色'opacity(90%)', // 透明度'saturate(200%)', // 饱和度'saturate(20%)', // 饱和度'sepia(100%)', // 褐色'drop-shadow(4px 4px 8px blue)', // 阴影]var videoEl = document.getElementById('localVideo')var canvas = document.createElement('canvas')canvas.width = videoEl.videoWidthcanvas.height = videoEl.videoHeightvar ctx = canvas.getContext('2d')// 原图ctx.drawImage(videoEl, 0, 0, canvas.width, canvas.height)imgList.push(canvas.toDataURL('image/png'))for (let i = 0; i < filterList.length; i++) {ctx.filter = filterList[i]ctx.drawImage(videoEl, 0, 0, canvas.width, canvas.height)imgList.push(canvas.toDataURL('image/png'))}insertImgs(imgList)}function insertImgs(list) {const container = document.createDocumentFragmentvar imgContainer  = document.getElementById('imgContainer'); var fragment = document.createDocumentFragment();list.forEach((img) => {var imgEl = document.createElement('img');imgEl.src = img;fragment.appendChild(imgEl);});imgContainer.appendChild(fragment)}</script>
</body>
</html>

2. 下面的例子演示了录屏的功能

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>webRTC</title>
</head>
<body><h2>录屏</h2><video id="screenVideo" autoplay playsinline></video><button id="recorderStartBtn" style="position: fixed;right: 0;top: 46%;">开始录制</button><button id="recorderStopBtn" style="position: fixed;right: 0;top: 48%;">停止录制</button><button id="recorderPauseBtn" style="position: fixed;right: 0;top: 52%;">暂停录制</button><button id="recorderResumeBtn" style="position: fixed;right: 0;top: 54%;">继续录制</button><script>// 录屏// 获取视频流var localStreamshareScreen()async function shareScreen() {localStream =  await navigator.mediaDevices.getDisplayMedia({audio: true,video: true,})// 播放录屏视频流playStream(localStream)}// 在视频标签中播放视频流function playStream(stream) {const video = document.querySelector('#screenVideo')video.srcObject = stream}</script><script>// 录屏保存// 这里我们使用 MediaRecorder 来进行录制,它是一个用于录制媒体流的 API,它可以将媒体流中的数据进行录制,然后将录制的数据保存成一个文件。// 由于 MediaRecorder api 的对 mimeType 参数的支持是有限的。所以我们需要通过 MediaRecorder.isTypeSupported 来判断当前浏览器是否支持我们需要的 mimeTypefunction getSupportedMimeTypes() {const media = 'video'// 常用的视频格式const types = ['webm','mp4','ogg','mov','avi','wmv','flv','mkv','ts','x-matroska',]// 常用的视频编码const codecs = ['vp9', 'vp9.0', 'vp8', 'vp8.0', 'avc1', 'av1', 'h265', 'h264']// 支持的媒体类型const supported = []const isSupported = MediaRecorder.isTypeSupported// 遍历判断所有的媒体类型types.forEach((type) => {const mimeType = `${media}/${type}`codecs.forEach((codec) =>[`${mimeType};codecs=${codec}`,`${mimeType};codecs=${codec.toUpperCase()}`,].forEach((variation) => {if (isSupported(variation)) supported.push(variation)}),)if (isSupported(mimeType)) supported.push(mimeType)})return supported}console.log(getSupportedMimeTypes())function getRecorder () {const kbps = 1024const Mbps = kbps * kbpsconst options = {audioBitsPerSecond: 128000,videoBitsPerSecond: 2500000,mimeType: 'video/webm; codecs="vp8,opus"',}// https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorderreturn new MediaRecorder(localStream, options)}var mediaRecorder = getRecorder()// 开始录制var recorderStartBtn = document.getElementById('recorderStartBtn')recorderStartBtn.addEventListener('click', handleRecorderStart)// 暂停录制var recorderPauseBtn = document.getElementById('recorderPauseBtn')recorderPauseBtn.addEventListener('click', handleRecorderPause)// 继续录制var recorderResumeBtn = document.getElementById('recorderResumeBtn')recorderResumeBtn.addEventListener('click', handleRecorderResume)// 停止录制var recorderStopBtn = document.getElementById('recorderStopBtn')recorderStopBtn.addEventListener('click', handleRecorderStop)// 录制媒体流function handleRecorderStart() {// 开始录制媒体,这个方法调用时可以通过给timeslice参数设置一个毫秒值,如果设置这个毫秒值,那么录制的媒体会按照你设置的值进行分割成一个个单独的区块,而不是以默认的方式录制一个非常大的整块内容。mediaRecorder.start()// 调用它用来处理 dataavailable 事件,该事件可用于获取录制的媒体资源 (在事件的 data 属性中会提供一个可用的 Blob 对象.)mediaRecorder.ondataavailable = (e) => {// 将录制的数据合并成一个 Blob 对象// const blob = new Blob([e.data], { type: e.data.type })// 🌸重点是这个地方,我们不要把获取到的 e.data.type设置成 blob 的 type,而是直接改成 mp4const blob = new Blob([e.data], { type: 'video/mp4' })downloadBlob(blob)}mediaRecorder.onstart = (e) => {console.log("开始录制", e)}mediaRecorder.onstop = (e) => {console.log("停止录制", e)}mediaRecorder.onpause = (e) => {console.log("暂停录制", e)}mediaRecorder.onresume = (e) => {console.log("继续录制", e)}}function handleRecorderPause() {mediaRecorder.pause()}function handleRecorderResume() {mediaRecorder.resume()}function handleRecorderStop() {mediaRecorder.stop()}// 下载 Blobfunction downloadBlob(blob) {// 将 Blob 对象转换成一个 URL 地址const url = URL.createObjectURL(blob)const a = document.createElement('a')// 设置 a 标签的 href 属性为刚刚生成的 URL 地址a.href = url// 设置 a 标签的 download 属性为文件名a.download = `${new Date().getTime()}.${blob.type.split('/')[1]}`// 模拟点击 a 标签a.click()// 释放 URL 地址URL.revokeObjectURL(url)}</script>
</body>
</html>

3. 下面的例子演示了保存(下载)录像流(录屏流也可)到本地

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>webRTC</title>
</head>
<body><h1>录像</h1><video id="cameraVideo" autoplay playsinline muted></video><button id="recorderStartBtn" style="position: fixed;right: 0;top: 52%;">开始录制</button><button id="recorderPauseBtn" style="position: fixed;right: 0;top: 54%;">暂停录制</button><button id="recorderResumeBtn" style="position: fixed;right: 0;top: 56%;">继续录制</button><button id="recorderStopBtn" style="position: fixed;right: 0;top: 58%;">停止录制</button><script>// 录像const constraints = {audio: true,video: { width: 1280, height: 720 }}var cameraStreamgetLocalStream(constraints)// 获取本地音视频流async function getLocalStream(constraints) {// 获取媒体流cameraStream = await navigator.mediaDevices.getUserMedia(constraints)// 将媒体流设置到 video 标签上播放playLocalStream(cameraStream)}// 播放本地视频流function playLocalStream(stream) {const videoEl = document.getElementById('cameraVideo')videoEl.srcObject = stream}</script><script>// 录屏保存// 这里我们使用 MediaRecorder 来进行录制,它是一个用于录制媒体流的 API,它可以将媒体流中的数据进行录制,然后将录制的数据保存成一个文件。// 由于 MediaRecorder api 的对 mimeType 参数的支持是有限的。所以我们需要通过 MediaRecorder.isTypeSupported 来判断当前浏览器是否支持我们需要的 mimeTypefunction getSupportedMimeTypes() {const media = 'video'// 常用的视频格式const types = ['webm','mp4','ogg','mov','avi','wmv','flv','mkv','ts','x-matroska',]// 常用的视频编码const codecs = ['vp9', 'vp9.0', 'vp8', 'vp8.0', 'avc1', 'av1', 'h265', 'h264']// 支持的媒体类型const supported = []const isSupported = MediaRecorder.isTypeSupported// 遍历判断所有的媒体类型types.forEach((type) => {const mimeType = `${media}/${type}`codecs.forEach((codec) =>[`${mimeType};codecs=${codec}`,`${mimeType};codecs=${codec.toUpperCase()}`,].forEach((variation) => {if (isSupported(variation)) supported.push(variation)}),)if (isSupported(mimeType)) supported.push(mimeType)})return supported}console.log(getSupportedMimeTypes())var mediaRecorderfunction getRecorder () {const kbps = 1024const Mbps = kbps * kbpsconst options = {audioBitsPerSecond: 128000,videoBitsPerSecond: 2500000,mimeType: 'video/webm; codecs="vp8,opus"',}// https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorderreturn new MediaRecorder(cameraStream, options)}// 开始录制var recorderStartBtn = document.getElementById('recorderStartBtn')recorderStartBtn.addEventListener('click', handleRecorderStart)// 暂停录制var recorderPauseBtn = document.getElementById('recorderPauseBtn')recorderPauseBtn.addEventListener('click', handleRecorderPause)// 继续录制var recorderResumeBtn = document.getElementById('recorderResumeBtn')recorderResumeBtn.addEventListener('click', handleRecorderResume)// 停止录制var recorderStopBtn = document.getElementById('recorderStopBtn')recorderStopBtn.addEventListener('click', handleRecorderStop)// 录制媒体流function handleRecorderStart() {mediaRecorder = getRecorder()// 开始录制媒体,这个方法调用时可以通过给timeslice参数设置一个毫秒值,如果设置这个毫秒值,那么录制的媒体会按照你设置的值进行分割成一个个单独的区块,而不是以默认的方式录制一个非常大的整块内容。mediaRecorder.start()// 调用它用来处理 dataavailable 事件,该事件可用于获取录制的媒体资源 (在事件的 data 属性中会提供一个可用的 Blob 对象.)mediaRecorder.ondataavailable = (e) => {// 将录制的数据合并成一个 Blob 对象// const blob = new Blob([e.data], { type: e.data.type })// 🌸重点是这个地方,我们不要把获取到的 e.data.type设置成 blob 的 type,而是直接改成 mp4const blob = new Blob([e.data], { type: 'video/mp4' })downloadBlob(blob)}mediaRecorder.onstart = (e) => {console.log("开始录制", e)}mediaRecorder.onstop = (e) => {console.log("停止录制", e)}mediaRecorder.onpause = (e) => {console.log("暂停录制", e)}mediaRecorder.onresume = (e) => {console.log("继续录制", e)}}function handleRecorderPause() {mediaRecorder.pause()}function handleRecorderResume() {mediaRecorder.resume()}function handleRecorderStop() {mediaRecorder.stop()}// 下载 Blobfunction downloadBlob(blob) {// 将 Blob 对象转换成一个 URL 地址const url = URL.createObjectURL(blob)const a = document.createElement('a')// 设置 a 标签的 href 属性为刚刚生成的 URL 地址a.href = url// 设置 a 标签的 download 属性为文件名a.download = `${new Date().getTime()}.${blob.type.split('/')[1]}`// 模拟点击 a 标签a.click()// 释放 URL 地址URL.revokeObjectURL(url)}</script>
</body>
</html>

相关文章:

web获取媒体流

1. 下面例子演示了录屏和截图功能&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport"…...

代码随想录算法训练营第四十二天 | 01背包问题,你该了解这些、01背包问题,你该了解这些 滚动数组、 416. 分割等和子集

打卡第42天&#xff0c;搞搞01背包。 今日任务 01背包问题&#xff0c;你该了解这些&#xff01;01背包问题&#xff0c;你该了解这些&#xff01; 滚动数组416.分割等和子集 背包问题1.0 &#xff1a;0-1 背包 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weig…...

【Android】JNI静态与动态注册介绍

JNI的两种注册机制&#xff1a;静态注册和动态注册. 一、JNI介绍 JNI(Java Native Interface)&#xff0c;即Java本地接口&#xff0c;JNI是Java调用Native 语言的一种特性。通过JNI可以使得Java与C/C机型交互. 方式&#xff1a; 静态注册动态注册&#xff1a;需要提供Java中…...

【算法题解】22. 接雨水

这是一道 困难 题 题目来自&#xff1a; https://leetcode.cn/problems/trapping-rain-water/ 题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,…...

集合详解之(四)集合的遍历

文章目录&#x1f412;个人主页&#x1f3c5;JavaSE系列专栏&#x1f4d6;前言&#xff1a;&#x1f380;ArrayList集合forEach()方法遍历&#x1f380;for循环遍历&#xff08;针对List集合&#xff09;&#x1fa85;增强for循环&#xff08;也支持Set集合&#xff09;&#x…...

【I2C】通用驱动i2c-dev分析

文章目录1. 前言2. i2c-dev驱动的注册过程3. open_i2c_dev函数分析4. set_slave_addr函数分析5. i2c_read_bytes函数分析1. 前言 前面分析i2c-tool测试工具就是基于drivers/i2c/i2c-dev.c驱动来实现的。i2c-dev驱动在加载时会遍历所有的I2C总线(i2c_bus_type)上所有注册的adap…...

用GPT-4写代码不用翻墙了?Cursor告诉你:可以~~

目录 一、介绍 二、使用方法 三、其他实例 1.正则表达式 2.自动化测试脚本 3.聊聊技术 一、介绍 Cursor主要功能是根据用户的描述写代码或者进行对话&#xff0c;对话的范围仅限技术方面。优点是不用翻墙、不需要账号。Cursor基于GPT模型&#xff0c;具体什么版本不祥&#…...

硬件语言Verilog HDL牛客刷题day03 时序逻辑部分

1.VL21 根据状态转移表实现时序电路 1.题目&#xff1a; 某同步时序电路转换表如下&#xff0c;请使用D触发器和必要的逻辑门实现此同步时序电路&#xff0c;用Verilog语言描述。 2.解题思路 2.1 首先同步时序电路 &#xff0c; 时钟上升沿触发&#xff0c; 复位信号rst 低电…...

day31 ● 455.分发饼干 ● 376. 摆动序列 ● 53. 最大子序和

● 455.分发饼干 ● 376. 摆动序列 ● 53. 最大子序和 在本次的题目中&#xff0c;我们使用了贪心算法来解决三个问题&#xff1a;分发饼干、摆动序列、最大子序和。这三个问题都可以使用贪心算法来解决&#xff0c;而且贪心算法的时间复杂度相对较低&#xff0c;能够在较短的…...

MobTech 秒验|本机号码一键登录会泄露隐私吗

本机号码一键登录是一种新型的应用登录方式&#xff0c;它可以利用运营商的数据网关认证能力&#xff0c;实现手机号免密登录&#xff0c;提高用户体验和转化率&#xff0c;降低验证成本和流失率。本机号码一键登录支持三大运营商号码认证&#xff0c;3秒内完成手机号验证&…...

2023年供销合作社研究报告

第一章 行业概况 1.1 供销合作社概述 中华全国供销合作总社&#xff0c;是中华人民共和国全国供销合作社的联合组织。中华全国供销合作总社的前身可以追溯到1949年11月成立的中央合作事业管理局。在新中国成立初期&#xff0c;供销合作社就基本形成了自上而下、覆盖全国的组织…...

【ansible】实施任务控制

目录 实施任务控制 一&#xff0c;循环&#xff08;迭代&#xff09;--- loop 1&#xff0c;利用loop----item循环迭代任务 2&#xff0c;item---loop循环案例 1&#xff0c;定义item循环列表 2&#xff0c;通过变量应用列表格式 3&#xff0c;字典列表&#xff08;迭代嵌套子…...

49天精通Java,第11天,java接口和抽象类的异同,default关键字

目录一、什么是接口二、接口的特点三、接口和类的区别四、接口和抽象类的区别五、接口的声明方式六、default默认方法大家好&#xff0c;我是哪吒。 一、什么是接口 Java接口是一系列方法的声明&#xff0c;是一些方法特征的集合&#xff0c;一个接口只有方法的特征没有方法的…...

JAVA练习99-逆波兰表达式求值

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、题目-逆波兰表达式求值 1.题目描述 2.思路与代码 2.1 思路 2.2 代码 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 4月5…...

恶意软件、恶意软件反杀技术以及反病毒技术的详细介绍

1.恶意软件简单介绍恶意软件是指在计算机系统上执行恶意任务的病毒、蠕虫和特洛伊木马的程序&#xff0c;通过破坏软件进程来实施控制。腾讯移动安全实验室发布的数据显示&#xff0c;恶意软件由多种威胁组成&#xff0c;会不断弹出&#xff0c;所以需要采取多种方法和技术来进…...

【数据库运维】mysql备份恢复练习

目录 数据库备份&#xff0c;数据库为school&#xff0c;素材如下 1.创建student和score表 2.为student表和score表增加记录 3.备份数据库school到/backup目录 4.备份MySQL数据库为带删除表的格式&#xff0c;能够让该备份覆盖已有数据库而不需要手动删除原有数据库 5.直接将My…...

刷题30-对称的二叉树

对称的二叉树 思路&#xff1a;用递归&#xff0c;首先明白递归中止的条件是什么 搬用别人的看法&#xff1a; 做递归思考三步&#xff1a; 1.递归的函数要干什么&#xff1f; 函数的作用是判断传入的两个树是否镜像。 输入&#xff1a;TreeNode left, TreeNode right 输出…...

精选简历模板

1.应届生通用简历模板&#xff08;.docx) 适用于应届生找工作的学生群体 https://download.csdn.net/download/weixin_43042683/87652099https://download.csdn.net/download/weixin_43042683/87652099 部分缩略图如下&#xff1a; 2.研究生通用简历模板&#xff08;.docx)…...

蓝桥杯嵌入式第十三届客观题解析

文章目录 前言一、题目1二、题目2三、题目3四、题目4五、题目5六、题目6七、题目7八、题目8九、题目9十、题目10总结前言 本篇文章将带大家来学习蓝桥杯嵌入式的客观题了,蓝桥杯嵌入式的客观题涉及到模电,数电,单片机等知识,需要非常扎实的基础,客观题不能急于求成只能脚…...

【Redis】线程问题

文章目录单线程版本演化工作流程为什么逐渐又加入了多线程特性?影响Redis性能的主要因素->网络I/O多线程工作流程Unix网络编程中的五种I/O模型I/O多路复用工作原理&#xff1a;select、poll、epoll为什么Redis快单线程与多线程的比较配置文件开启多线程单线程 版本演化 Re…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)

一、OpenBCI_GUI 项目概述 &#xff08;一&#xff09;项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台&#xff0c;其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言&#xff0c;首次接触 OpenBCI 设备时&#xff0c;往…...

日常一水C

多态 言简意赅&#xff1a;就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过&#xff0c;当子类和父类的函数名相同时&#xff0c;会隐藏父类的同名函数转而调用子类的同名函数&#xff0c;如果要调用父类的同名函数&#xff0c;那么就需要对父类进行引用&#…...

Python 训练营打卡 Day 47

注意力热力图可视化 在day 46代码的基础上&#xff0c;对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

区块链技术概述

区块链技术是一种去中心化、分布式账本技术&#xff0c;通过密码学、共识机制和智能合约等核心组件&#xff0c;实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点&#xff1a;数据存储在网络中的多个节点&#xff08;计算机&#xff09;&#xff0c;而非…...