Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 339

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 392

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 394
Joyber 的博客

✅ 可以,Win11右下角任务栏时间区域显示星期,有两种方案

方案一:系统原生(不用装软件,推荐)

原理:修改短日期格式,会全局影响系统所有地方的日期展示(文件资源管理器、Excel等日期都会带星期),但不需要第三方工具。
  1. Win+R,输入 intl.cpl 回车,直接打开【区域】面板
  2. 点右下角 其他设置 → 切换到【日期】标签页
  3. 修改【短日期】输入框(英文输入法!)

    • ddd:简写,如 周二
    • dddd:完整,如 星期二

示例:

  • 国内默认短日期 yyyy/M/d,改成:yyyy/M/d dddd → 显示 2026/10/6 星期二
  • 想要简写:yyyy/M/d ddd → 2026/10/6 周二
  • 点【应用】→【确定】。如果没立刻生效,重启资源管理器:任务管理器找到explorer.exe右键重启。
⚠️ 缺点:全局生效,所有软件里的短日期都会带上星期。如果不想改动全系统日期格式,用方案二。

方案二:第三方 ElevenClock(推荐开发人员,只改任务栏时钟,不影响系统)

开源免费 Github 工具,专门给Win11任务栏时钟自定义格式,只修改右下角时间显示,不改动系统全局日期格式,可以实现换行、自定义星期。

  1. 微软商店或者 Github 下载 ElevenClock
  2. 安装后右键右下角时钟 → ElevenClock 设置
  3. 开启自定义格式,示例模板:

    HH:mm
    dddd
    yyyy/M/d

    效果:

    14:30
    星期二
    2026/10/6

    这个方案不会干扰文件、Excel、脚本里的日期格式,适合你这种开发环境。

小提示

任务栏如果比较窄,太长的dddd(星期二)可能会被截断,优先试 ddd(周二)。
想恢复默认:回到intl.cpl日期设置页,点【重置】即可一键还原。

calc(20rpx + env(safe‑area‑inset‑bottom)) 详解

env() 是CSS环境变量函数,safe‑area‑inset‑bottom 代表iOS全面屏底部手势条(小黑条)的避让高度,单位px。

你的写法含义:20rpx固定距离 + 系统给的底部安全区px距离,常用于小程序底部fixed悬浮按钮、底部栏的bottom / padding‑bottom。

一、四个安全区环境变量

env(safe-area-inset-top);    /* 顶部刘海/灵动岛高度 px */
env(safe-area-inset-right);  /* 右边避让,横屏生效 px */
env(safe-area-inset-bottom); /* 底部手势条高度 px */
env(safe-area-inset-left);   /* 左边避让,横屏生效 px */
  • 非全面屏手机:safe‑area‑inset‑bottom = 0px,表达式等价于 calc(20rpx + 0),只会保留你的20rpx基础距离。
  • iPhone全面屏:会自动叠加小黑条的高度(34px等)。

二、关键坑点(高频踩坑)

1️⃣ H5网页必须开启 viewport‑fit=cover

不加,safe‑area‑inset‑bottom 永远等于0,完全失效。

<meta name="viewport" content="width=device‑width,initial‑scale=1,viewport‑fit=cover">
微信小程序不需要写meta,框架默认开启cover模式,直接可用。

2️⃣ 历史兼容:constant()(iOS11.2以前旧版本)

旧版Safari用constant(),新版本用env(),constant写在env上方,做降级兼容。

/* 兼容旧iOS */
padding‑bottom: calc(20rpx + constant(safe‑area‑inset‑bottom));
padding‑bottom: calc(20rpx + env(safe‑area‑inset‑bottom));

3️⃣ calc内部符号两侧必须空格,否则直接失效

✅正确:calc(20rpx + env(safe‑area‑inset‑bottom))
❌错误:calc(20rpx+env(safe‑area‑inset‑bottom)) +两边没空格会解析失败。

4️⃣ 单位混用注意(小程序)

env(safe‑area‑inset‑bottom) 返回px;你写的是rpx。

微信小程序内部会自动做单位换算,calc(rpx + env(xxx)) 是支持直接写的,不需要手动转px,小程序引擎自动处理。
H5不支持rpx单位,H5要统一全部改成px。

5️⃣ env支持回退默认值(第二个参数)

设备不支持安全区变量时,使用回退值:

env(safe‑area‑inset‑bottom, 0px);

示例:

padding‑bottom: calc(20rpx + env(safe‑area‑inset‑bottom,0px));

三、两种业务场景示例(微信小程序)

场景1:底部fixed悬浮按钮,bottom定位

.fixed‑btn{
  position: fixed;
  bottom: calc(20rpx + env(safe‑area‑inset‑bottom));
  right: 30rpx;
}

含义:距离底部基础20rpx,iPhone全面屏再叠加手势条高度。普通安卓/非全面屏,就只有20rpx。

场景2:底部栏增加内边距padding‑bottom(推荐,避免内容被小黑条遮挡)

.footer‑bar{
  padding‑bottom: calc(24rpx + constant(safe‑area‑inset‑bottom));
  padding‑bottom: calc(24rpx + env(safe‑area‑inset‑bottom));
}

四、常见错误现象排查清单

  1. env完全不起作用:

    • H5:检查meta有没有加viewport‑fit=cover
    • 符号+忘记加空格;
    • 顺序写反,env写在constant前面。
  2. iPhone底部距离变得巨大:>

    你写的是基础距离 + safe‑area‑inset‑bottom,如果你本身元素已经靠到屏幕最底边,就不要额外再加基础值,直接写 env(safe‑area‑inset‑bottom),否则会双重叠加,留白过大。

五、H5完整可复制样板

.bottom‑wrap {
  padding‑bottom: calc(16px + constant(safe‑area‑inset‑bottom));
  padding‑bottom: calc(16px + env(safe‑area‑inset‑bottom,0px));
}
小程序直接复制,把px替换成rpx即可。

Ubuntu24.04(桌面版,NetworkManager)

方式1:全局断开再重连(最简单,推荐)

sudo nmcli networking off
sudo nmcli networking on

一行合并写法:

sudo nmcli networking off && sudo nmcli networking on

方式2:只重启单个连接(推荐SSH远程,不会断其他网卡)

  1. 先查看所有连接名称

    nmcli connection show

    示例输出:Wired connection 1、MyWiFi

  2. 断开、重连(替换名字)

    sudo nmcli con down "Wired connection 1"
    sudo nmcli con up "Wired connection 1"

方式3:直接重启NetworkManager服务(彻底重置网络)

sudo systemctl restart NetworkManager

方式4:netplan(服务器版,netplan管理)

sudo netplan apply

方式5:底层ip命令(直接操作网卡设备)

# 查看网卡名
ip addr
# 示例网卡 enp0s3
sudo ip link set enp0s3 down
sudo ip link set enp0s3 up

验证是否通网

ping 8.8.8.8
提示:Ubuntu24桌面默认用NetworkManager,优先用nmcli;服务器不带NM才用netplan。
SSH远程操作,尽量用【方式2】,避免全局断网导致连不上机器。

对应右上角菜单「断开网络 → 再连上」两种场景

场景A:点WiFi开关(关闭WiFi硬件,和右上角总WiFi开关一模一样)

# 第一步:关闭WiFi(相当于GUI点关闭WiFi)
nmcli radio wifi off

# 第二步:打开WiFi,系统会自动重连已保存WiFi
nmcli radio wifi on

一行合并:

nmcli radio wifi off && sleep 1 && nmcli radio wifi on

场景B:只断开当前这个WiFi连接(WiFi网卡不关,只是断开AP,就是点WiFi名称里的断开)

  1. 先查看连接名

    nmcli connection show --active

    拿到你的WiFi连接名称,例如 MyHomeWiFi

    # 断开当前WiFi
    nmcli con down "MyHomeWiFi"
    # 重新激活(自动重连)
    nmcli con up "MyHomeWiFi"

场景C:右上角【全部网络总开关】(有线+WiFi一起关掉,最接近右上角那个总网络开关)

sudo nmcli networking off
sudo nmcli networking on

区分重点

  • nmcli radio wifi off:无线网卡射频关闭,右上角WiFi图标消失,对应GUI的WiFi总开关。
  • nmcli con down "xxx":仅仅断开这个WiFi连接,网卡还开着,只是断开路由器,网卡还在扫描。
  • nmcli networking off:全局网络全部关闭,有线+WiFi一起断,GNOME右上角网络全部灰色。
Ubuntu24.04 GNOME桌面右上角点击网络开关,本质调用的就是 nmcli networking 或者 nmcli radio。
sleep 1 是给NetworkManager一点反应时间,防止太快执行导致不生效。

如果你是有线网线(不是wifi),那么用全局:

sudo nmcli networking off && sleep 1 && sudo nmcli networking on

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>摄像头拍照保存</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;font-family:system-ui;}
body{padding:20px;max-width:800px;margin:0 auto;}
#video{width:100%;border:1px solid #ccc;background:#000;transform:scaleX(-1);/*前置预览镜像*/}
#canvas{display:none;}
#preview{margin:15px 0;max-width:100%;border:1px solid #ccc;}
.btns{margin:15px 0;display:flex;gap:10px;flex-wrap:wrap;}
button{padding:10px 18px;font-size:16px;cursor:pointer;}
</style>
</head>
<body>
<h3>摄像头拍照</h3>
<video id="video" autoplay playsinline></video>
<canvas id="canvas"></canvas>
<div class="btns">
<button id="startBtn">开启摄像头</button>
<button id="stopBtn">关闭摄像头</button>
<button id="takeBtn">拍照</button>
<button id="saveBtn">保存图片</button>
</div>
<img id="preview" alt="预览图">

<script>
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const preview = document.getElementById('preview');
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const takeBtn = document.getElementById('takeBtn');
const saveBtn = document.getElementById('saveBtn');

let mediaStream = null;
let imgUrl = '';

// 开启摄像头
startBtn.onclick = async () => {
  try{
    mediaStream = await navigator.mediaDevices.getUserMedia({
      video:{facingMode:'user'}
    });
    video.srcObject = mediaStream;
  }catch(err){
    alert('无法打开摄像头,请授予摄像头权限:' + err.message);
    console.error(err);
  }
};

// 关闭摄像头
stopBtn.onclick = ()=>{
  if(mediaStream){
    mediaStream.getTracks().forEach(track=>track.stop());
    mediaStream = null;
    video.srcObject = null;
  }
}

// 拍照,前置画面镜像翻转
takeBtn.onclick = () => {
  if(!mediaStream){
    alert("请先开启摄像头");
    return;
  }
  if(!video.videoWidth){
    alert("视频还未就绪,请稍等");
    return;
  }
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  // 水平翻转画布,拍出来和预览一样镜像
  ctx.save();
  ctx.scale(-1, 1);
  ctx.drawImage(video, -canvas.width, 0, canvas.width, canvas.height);
  ctx.restore();
  imgUrl = canvas.toDataURL('image/png');
  preview.src = imgUrl;
};

// 保存下载图片
saveBtn.onclick = () => {
  if(!imgUrl){
    alert('请先拍照');
    return;
  }
  const a = document.createElement('a');
  a.href = imgUrl;
  a.download = `photo_${Date.now()}.png`;
  a.click();
};
</script>
</body>
</html>

摄像头视频流怎么推送到后端,做直播视频流

网页摄像头推流到后端做直播:完整技术方案

浏览器端采集摄像头,向后端推送视频流,主流分两大类:

  1. WebRTC(推荐低延迟直播,实时互动,延迟<500ms)
  2. MediaRecorder + HTTP上传(简单HLS分片直播,延迟高,5~20秒,上手简单)
前置基础:还是用 navigator.mediaDevices.getUserMedia 获取摄像头MediaStream,和前面拍照网页是同一个媒体源。

方案1:MediaRecorder + HTTP分片上传(简易直播,适合入门)

原理:浏览器拿到摄像头流,用MediaRecorder编码成小块视频片段(blob),通过fetch持续POST发给后端;后端接收片段,转封装为HLS,前端用video.js播放。

  • 优点:技术栈简单,不需要复杂的信令服务器;后端用Node/Python/PHP都能写接收接口。
  • 缺点:延迟高,不适合实时互动,适合监控、低要求直播。

前端核心代码片段

let mediaStream;
let mediaRecorder;
const chunkList = [];

// 获取摄像头
mediaStream = await navigator.mediaDevices.getUserMedia({
    video: { facingMode: 'user', width: {ideal:1280}, height:{ideal:720} },
    audio: true // 同时采集音频
});

// 实例化录制器,编码格式
mediaRecorder = new MediaRecorder(mediaStream, {
    mimeType: 'video/webm;codecs=vp9,opus'
});

// 每500ms生成一块视频分片
mediaRecorder.ondataavailable = async (e) => {
    if(e.data.size > 0){
        const formData = new FormData();
        formData.append('videoChunk', e.data);
        // POST上传分片到后端接口
        await fetch('/api/upload-chunk', {
            method: 'POST',
            body: formData
        })
    }
}
mediaRecorder.start(500); // 每500ms触发一次ondataavailable

后端职责:

  1. 接收webm分片
  2. 把分片追加到视频文件,或者交给ffmpeg转码,生成HLS m3u8 + ts切片
  3. 对外提供HLS地址给播放器观看
限制:浏览器编码格式有限,大多输出webm;如果要mp4,浏览器原生MediaRecorder不支持mp4实时流式输出。

方案2:WebRTC(低延迟实时直播,首选)

WebRTC可以直接把摄像头MediaStream实时推流到后端,延迟极低。
两种部署模式:

模式A:SFU 媒体转发服务器(最常用直播方案)

浏览器(推流端) ↔ SFU服务器 ↔ 多个观看浏览器

  • 浏览器采集摄像头流,通过SDP信令,把RTP音视频包发给SFU
  • SFU负责接收、转发视频流给所有观众,不做转码(性能好)
  • 常用开源SFU:Mediasoup、Janus、LiveKit、Pion

流程:

  1. 前端getUserMedia拿到mediaStream
  2. 创建RTCPeerConnection
  3. 将摄像头track添加到peer连接
  4. 通过websocket作为信令通道,交换SDP、ICE候选
  5. 建立P2P/服务器中转,RTP数据包持续推送到后端SFU
// WebRTC极简前端示例
const pc = new RTCPeerConnection({
  iceServers: [{urls: 'stun:stun.l.google.com:19302'}]
});
// 将摄像头流加入peer连接
mediaStream.getTracks().forEach(track=>pc.addTrack(track, mediaStream));
// 创建offer,通过websocket发给后端信令服务器
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// 发送offer到后端websocket信令
ws.send(JSON.stringify({type:'offer', sdp:offer.sdp}));

模式B:P2P直连

浏览器直接和观看端P2P,后端只做信令转发,不转发媒体。
缺点:不适合多人直播,网络穿透失败率高,生产直播很少用。

方案3:WebRTC 推流到RTMP服务器(传统直播流)

很多后端直播服务是RTMP(Nginx+rtmp-module,SRS),浏览器本身不原生支持RTMP。
两个解决办法:

  1. 前端WebRTC推流 → 后端SRS/Mediasoup接收WebRTC,内部转RTMP,推到CDN。(推荐,SRS原生支持WebRTC转RTMP)
  2. 用wasm在浏览器内把MediaStream编码成H.264,封装RTMP,这个方案前端CPU占用很高,不推荐生产。
SRS 是国内最常用开源直播服务器:支持 WebRTC 推流、转RTMP、转HLS,部署简单。

关键概念区分

  1. MediaStream:摄像头原始未编码音视频帧(就是你拍照网页里的流),只能在浏览器内部使用,不能直接发给后端,必须编码打包。
  2. MediaRecorder:浏览器内置编码器,把MediaStream编码成webm片段。
  3. WebRTC RTP:实时编码,小包传输,低延迟。
  4. RTMP:传统直播协议,浏览器不原生支持,多用于CDN。

选型建议

  • 低延迟互动直播、远程监控、实时画面:WebRTC + SRS / LiveKit
  • 简单监控,允许10秒以上延迟,快速开发:MediaRecorder分片上传 + ffmpeg转HLS
  • 需要对接阿里云/腾讯云直播CDN:前端WebRTC推SRS,SRS转RTMP推CDN

常见坑

  1. 必须HTTPS/localhost,否则getUserMedia无法打开摄像头。
  2. WebRTC需要信令服务器(websocket),媒体流和信令是两条独立通道。
  3. 带宽:1080p30视频推流,至少需要4Mbps上行带宽。
  4. 编码:浏览器支持H.264 / VP8 / VP9,不同浏览器支持不一样。
  5. 高DPI屏幕不影响视频流本身,视频流分辨率由getUserMedia的constraints指定,不是屏幕分辨率。

拓展:和你之前拍照代码的关联

getUserMedia拿到的MediaStream,既可以给video预览、canvas截图,也可以直接喂给MediaRecorder或者RTCPeerConnection做推流,同一个媒体源复用。

Serena MCP 完整使用教程(适配你的 Cocos+Oops TS游戏项目,解决一次性读大量源码耗token问题)

Serena核心作用:符号级代码检索,AI不再一次性读取整个文件/整个仓库;需要哪个函数、哪个类,才单独拉取那一小段代码,大幅削减token消耗,刚好解决你之前Cursor索引Oops框架直接把额度耗尽的痛点。
原理:基于LSP语言服务器,识别TS的类、函数、接口,而不是把文件当纯文本全文读取。
项目地址:https://github.com/oraios/serena

一、前置依赖安装(Windows/Mac通用)

Serena推荐用uv包管理器(比pip简单很多)

  1. 安装 uv

    # Windows(PowerShell)
    powershell -ExecutionPolicy ByPass -c "irm [https://astral.sh/uv/install.ps1](https://astral.sh/uv/install.ps1) | iex"
    
    # Mac
    curl -LsSf [https://astral.sh/uv/install.sh](https://astral.sh/uv/install.sh) | sh

    安装完成重启终端,让环境变量生效。

  2. 安装 Serena-agent

    uv tool install -p 3.13 serena-agent@latest --prerelease=allow
  3. 初始化Serena(下载语言服务依赖,只需要跑一次)

    serena init

    执行完输入 serena --help,能正常输出帮助就代表安装成功。

二、接入到 Trae(推荐,你准备切换的免费AI编辑器)

Trae支持MCP,直接配置项目级MCP服务:

  1. 打开你的Cocos游戏项目(根目录)
  2. 在项目根新建文件夹 .trae,里面新建 mcp.json

    {
      "servers": [
     {
       "name": "serena",
       "transport": "stdio",
       "command": "serena",
       "args": [
         "start-mcp-server",
         "--context=ide",
         "--project",
         "${workspaceFolder}"
       ]
     }
      ]
    }
  3. 重启Trae,进入项目,打开AI聊天面板 → 设置 → MCP,看到serena状态绿色就是成功。

    ${workspaceFolder} 自动读取当前项目根目录,也就是你的Cocos项目根,Serena会自动索引。

接入Cursor的配置(如果你后续还要用Cursor)

在项目根新建 .cursor/mcp.json

{
  "mcpServers": {
    "serena": {
      "command": "serena",
      "args": [
        "start-mcp-server",
        "--context=ide",
        "--project-from-cwd"
      ]
    }
  }
}

保存,重启Cursor。

三、针对你的《弹力幻境》项目,Serena的配置优化(重点)

Serena会读取项目文件,你需要过滤不需要索引的目录,避免它扫描library、缓存、demo,和.traeignore配合:

  1. 在项目根新建 .serenaignore,内容:

    # Cocos编译缓存
    library/
    temp/
    build/
    dist/
    node_modules/
    
    # Oops示例代码,不需要让serena索引demo
    oops/demo/
    oops/example/
    
    # 只保留 code/业务代码 + oops核心源码
    *.log
    .DS_Store
    Serena会读取这个忽略清单,不会去索引缓存文件,只解析code/下面你的TS业务脚本 + oops框架核心代码,极大减少索引体积。

四、使用方式(对话指令,直接复制丢给AI)

激活成功之后,在AI聊天框直接发指令,Serena会自动调用符号检索工具:

使用serena:查找code/下所有弹力相变相关函数,只读取函数签名和实现,不要读取整个文件。
使用serena:查找oops.message的on和off方法,查看定义。
使用serena:找到GameManager类,分析依赖,新增小球对象池逻辑。

✅ Serena能做到:

  • 查找函数、类、接口定义,按需读取片段,不加载完整文件,省大量token
  • 查找函数调用关系,找到哪里调用了某个方法
  • 重命名类/函数,跨文件批量修改符号(TS类型安全)
  • 查看类型继承关系

❌ Serena不做:写完整大段业务代码、运行游戏、调试Cocos引擎物理。它是代码检索工具,用来减少AI读代码时的token开销。

五、使用工作流(适配你的游戏开发)

  1. 打开Trae,加载Cocos项目,serena自动后台索引(首次索引会慢几十秒,后续走缓存很快)
  2. 写代码时,直接让AI调用serena检索code/目录里的类/函数
  3. AI不再@整个项目,只通过serena符号检索按需拉取代码片段
  4. 配合 .traeignore + .serenaignore 双重过滤缓存目录,从根源解决一次性加载海量源码耗尽额度

六、坑点提醒(必看)

  1. Serena依赖TypeScript语言服务,本地需要安装Node+TS,否则TS解析会失败
  2. 第一次索引会扫描项目,一定要配置.serenaignore,否则还是会扫描library、node_modules,索引慢、耗资源
  3. Serena免费版使用LSP后端,足够TS项目使用;JetBrains后端是付费,你不需要
  4. 它不能替代.traeignore,两者搭配:.traeignore控制Trae内置索引,.serenaignore控制Serena符号索引

备选简化方案:不想装Serena,用Repomix

如果你觉得Serena配置略复杂,Repomix是命令行一次性打包精简代码,适合一次性给AI看架构,不用常驻后台。

repomix --include "code/**/*,oops/**/*" --exclude "oops/demo/**/*,library/**/*,temp/**/*,build/**/*,node_modules/**/*" --compress

--compress AST精简,只保留函数签名,砍掉函数内部实现,token大幅压缩。

推荐你当前的组合方案

Trae + Serena MCP + .traeignore + .serenaignore

  • Trae免费SOLO Agent无硬限额
  • Serena按需读取代码片段,避免一次性读大量源码
  • 双重ignore过滤缓存、demo目录,杜绝爆token的问题
  • 适配 Cocos3.8.8 + Oops + TS微信小游戏