
全景展示绕不开 Krpano。最近项目里来了个新需求把现场拍摄的 360° 视频接入到已有的漫游站点里用户可以在浏览器里拖拽视角、切换场景、看视频里的热点介绍。折腾完这圈踩了不少坑这篇随笔趁热记录下来。市面上的思路大致两条用 three.js 这类前端 3D 库自己写播放器或者用 krpano 这种成熟的全景方案。我选择了后者原因后面细说。这篇文章主要面向已经在用 krpano、但还没搞过视频的人也适合那些视频接入后遇到编码、多分辨率、热点同步问题的同行。内容按 krpano 1.20 系列的用法来讲老版本个别属性名会有一点差异查一下对应版本文档就行。1. 全景视频到底解决什么问题先泼一盆冷水不是所有场景都该上全景视频。静态的空间展示全景图更合适——文件小、加载快、随便一台旧手机都能流畅看。但如果你要表达的是过程、动作、光线变化、人群流动全景图就废了。全景视频的价值在于多了一个时间维度。我这次接的需求里有几个典型场景售楼处的周边环境预览。窗外不是一张静态效果图而是傍晚时分的车流、江面的波光这些静止画面传递不了。工厂车间关键工位的巡检记录。机器在转、机械臂在动客户想看的是真实运行状态不是一张摆拍的 360° 照片。活动全程回放。舞台灯光、现场人群、主持人走位静态照片只能抓到一瞬间。判断标准很简单画面里有“必须动起来”的内容就上视频没有就老老实实用图。全景视频的成本比全景图高不少素材体积大、编码讲究、播放性能敏感这些问题后面都会碰到。提前想清楚场景边界能省下大量返工时间。1.1 全景图与全景视频的应用边界全景图和全景视频本质上都是“贴在一个球体内部的实时图像”。区别在于全景图是单帧全景视频是连续帧序列。这个区别带来三个连锁问题体积、解码、交互。体积方面一张 4K 全景图压缩后大概几 MB一段 4K 全景视频哪怕压缩成 H.264几秒钟就超过一张图的体积。解码方面全景视频播放时浏览器要把视频帧实时映射到球面上再配合鼠标或陀螺仪旋转对 GPU 和 CPU 的压力远高于普通视频。交互方面全景图加热点是静态坐标点哪跳哪全景视频里热点要么跟着时间轴走要么固定在屏幕上否则很容易和画面内容错位。所以我的经验是能用全景图表达的坚决不硬塞视频。只有用户确实需要“看到动态过程”时才引入全景视频。比如给某个展厅做线上展示展厅本身就是静止建筑全景图足够但展厅里如果有一块正在播放产品宣传的屏幕或者一台上电运行的设备那这部分就值得单独用视频场景来表达。1.2 为什么是 Krpano而不是自己用 WebGL 写前端做全景视频自己写也不是不行。用 three.js 加 videoTexture把视频贴到球体内部鼠标拖拽、惯性、陀螺仪、热点叠加全部手写至少两周起步而且各种兼容性问题会占掉大量时间。Krpano 的优势在于这些能力已经封装成 XML 标签你只需要关心配置和交互逻辑。我用 Krpano 还有个更实际的原因漫游项目往往不只一个页面。全景图场景、全景视频场景、普通视频弹窗、场景切换、地图导航这些需要混在一个项目里。Krpano 的场景机制天然支持图片和视频混排而且热点、自动旋转、视角过渡、皮肤控制条这些都是一体的。用自研方案做单个视频播放器不难难的是把视频、图片、热点、场景串成一套完整的参观动线。当然Krpano 是商业产品商用要确认授权。个人学习用免费版也能跑起来但发布到商业项目之前记得把授权这件事纳入成本这是每个正规项目都要核对的。我本地的测试环境一直用的是官方免费许可真正交付时再按项目购买授权流程上比较稳妥。2. 视频素材与编码一半的坑都在这里全景视频接入后出现黑屏、卡顿、音画不同步多半不是 Krpano 的问题而是素材编码不符合浏览器要求。这个板块是重灾区值得单独写。2.1 拍摄与拼接先拿到 2:1 的等距柱状视频Krpano 直接吃等距柱状投影equirectangular格式。你可以把它想象成一张世界地图图片长宽比固定为 2:1图片中间是正前方上下边缘分别对应头顶和脚下左右边缘缝合起来是正后方。全景相机如果支持直出一般会输出这种格式如果拍的是多段鱼眼视频需要先用拼接软件做全景合成再导出 2:1 的视频。这里我踩过一个大坑某次拿到的素材是“四路鱼眼视频”而不是拼接好的全景视频我图省事直接拖进 Krpano结果画面里出现四个圆形鱼眼画面完全没法看。原因很简单Krpano 默认认为视频已经是等距柱状投影不会帮你去鱼眼化。后来老老实实把四段视频拼接成全景后再导入问题立刻解决。素材分辨率方面我建议桌面端至少准备 4K3840x1920移动端用 1080p1920x960做低档位。你别想着用 1080p 源头糊弄全景视频要铺满整个视野原始分辨率如果只有 1080p放大到全屏之后糊得没法看尤其是 VR 模式下观感会非常差。帧率建议 25fps 或 30fps。除非拍摄内容有明显的快速运动需要丝滑效果否则别轻易上 60fps——体积翻倍解码压力翻倍但大部分用户看不出来差异。2.2 转码参数怎么定才不至于黑屏或卡顿浏览器播放全景视频兼容性最好的编码方案是 H.264 MP4音频用 AAC。H.265/HEVC 虽然压缩率更高但浏览器支持不统一很多安卓机不支持硬解一量码率高一点就卡成幻灯片。所以我给客户交付的素材一律 H.264不追求极致压缩比追求稳定播放。转码参数我一般这样定清晰度档位分辨率H.264 Profile/Level参考码率移动端标清1920x960High / 4.04-8 Mbps桌面高清3840x1920High / 5.115-30 Mbps高码率旗舰3840x1920High / 5.230-50 Mbps立体声是 50但你最终定的码率要看素材动态程度。室内谈话场景可以调低户外快速移动镜头必须给足。一个经验是码率宁可高一点也不要低全景视频一旦出现块状压缩模糊用户旋转视角时会非常明显比普通视频的劣化刺眼得多。下面这条 ffmpeg 命令是我常用的 4K 档位转码模板ffmpeg -i input_equirect.mov \ -vf scale3840:1920 \ -c:v libx264 -preset medium -tune film \ -profile:v high -level 5.2 \ -pix_fmt yuv420p -r 30 \ -g 60 -keyint_min 60 -sc_threshold 0 \ -c:a aac -b:a 192k -ac 2 -ar 48000 \ -movflags faststart \ output_4k.mp4拆开解释一下关键点-pix_fmt yuv420p强制 8bit 4:2:0 采样这是浏览器兼容性最好的像素格式。-r 30固定帧率。如果源素材是可变帧率VFR播放到某些片段时音画会逐渐不同步这一步直接强制为 CFR。-g 60每 60 帧一个关键帧也就是 2 秒一个关键帧方便用户拖进度条时快速定位。-sc_threshold 0禁止编码器因为场景切换而打乱关键帧节奏防止 seek 时响应变慢。-movflags faststart把 moov 元数据移到文件头部网页播放时不用等整个文件下载完就能开始解析。这条命令还有一个好处压缩出来的文件拖进 Krpano 后即使不加多分辨率方案普通桌面浏览器跑 4K 也能流畅播放。前提是显卡支持硬解后面在排查章节会展开讲。2.3 帧率、关键帧与音频同步帧率问题我专门拎出来说是因为全景视频对“帧率一致性”比普通视频敏感。普通视频短暂掉帧人眼可能注意不到全景视频一旦掉帧旋转视角时会出现画面“拉扯感”很容易头晕。所以转码时一定要确认输出固定帧率。终端有可能给你的是一个带 B 帧、VFR、甚至交错扫描的素材这些都要在转码阶段处理掉。B 帧本身问题不大交给解码器处理就行但 VFR 必须转成 CFR。音频方面AAC 双声道 192kbps 对于全景视频足够。全景声Ambisonics在浏览器端的支持还比较混乱如果客户没明确要求别主动上。另一个容易忽略的点音频和视频要同步封装在同一条 MP4 里Krpano 播放视频时默认按封装时间轴走如果音视频轨道各自开头时间戳不一致会出现“画面比声音晚半秒”这种怪问题。要快速检查素材信息可以用 ffprobeffprobe -v error -select_streams v:0 -show_entries streamcodec_name,profile,level,pix_fmt,r_frame_rate -of defaultnoprint_wrappers1 output_4k.mp43. 接入 Krpano核心 XML 配置拆解素材准备好之后真正的技术活是写 XML 配置。Krpano 的玩法是XML 定义项目结构JS 负责运行时交互我这次只讲 XML 部分。3.1 最简视频配置video 标签与文件目录先看目录结构。我习惯这样组织项目文件project/ tour.xml video/ preview.jpg video_1080.mp4 video_4k.mp4 skin/ ...所有视频文件单独放一个目录预览图单独放一个文件避免和图片素材混在一起。路径全部用相对路径不要写绝对路径或者带盘符的路径后面迁移到服务器或者打包给别人时不会出问题。最简的 video 配置是这样的krpano version1.20 include urlskin/vtour.xml / video autostartfalse looptrue posterurlvideo/preview.jpg volume100 source typevideo/mp4 srcvideo/video_1080.mp4 / source typevideo/mp4 srcvideo/video_4k.mp4 / /video view hlookat0 vlookat0 fov100 / /krpanoposterurl是视频加载时的预览图很重要。没有它视频流还没就绪时浏览器会显示一片黑用户会以为页面坏了。预览图用静态全景图的第一帧导出就行但要压到几百 KB 以内。autostart在桌面浏览器里设置为 true 通常有效但在 iOS Safari 和部分安卓浏览器里自动播放会被浏览器策略拦截。为了保险我一般设置成 false让用户点击播放按钮或场景时再触发播放这样最稳妥。多 source 标签的意义Krpano 会自行判断当前设备能流畅播放哪个档位优先选最优的那一个。如果只写一个高分辨率移动端大概率卡死如果只写一个低分辨率桌面端画质吃亏。一个项目至少准备两档是多花不了多少时间的。3.2 初始化视角与 VR 模式全景视频的初始视角用view标签控制。hlookat是水平视角角度0 度代表正前方vlookat是垂直视角角度0 度代表水平线fov是视野范围数值越大看到的范围越广越小越接近望远镜效果。我推荐视频场景的初始视角设置在 0/0视野在 100 到 110 之间。原因很简单全景视频不同于全景图可以慢慢旋转观察它有自己的叙事时间线如果一上来就歪着头看用户很容易错过重点内容。让用户先看到画面主体再自己旋转探索体验会好很多。VR 模式是 Krpano 自带的强项。只要页面运行在支持 WebXR 的浏览器里并且部署在 HTTPS 环境下就能直接进入 VR 模式。注意陀螺仪和 VR 相关 API 很多浏览器要求 HTTPS否则会被拦截纯本地打开文件一般只能鼠标拖拽手机上的陀螺仪大概率不生效。开发阶段可以用本地 HTTP 服务配合手机调试正式发布一定走 HTTPS。这些不是 Krpano 的 bug而是浏览器安全策略。遇到手机陀螺仪不工作先检查部署环境别上来就怀疑配置写错了。3.3 控制条与自定义按钮如果你用的是 Krpano 自带的 vtourskin启用了视频之后皮肤会自动出现一个视频控制条播放/暂停/进度条/音量基本都全的。我很少再用自带皮肤客户项目一般要定制 UI所以我会自己挂按钮。自己写控制按钮的核心是调用 Krpano 内置的 videointerface 系列动作plugin nameplaypause urlbtn_play.png alignbottom edgebottom y40 onclicktoggleplay() / action nametoggleplay videointerface_toggleplay(); /actionvideointerface_toggleplay()会在播放和暂停之间切换。类似的接口还有videointerface_pause()、videointerface_stop()、videointerface_play()以及用于跳转进度的videointerface_seek(seconds)。不同版本里部分接口命名会有差异写的时候对着你那版 Krpano 的 doc 文件夹确认一下不要照抄我这里的名字就完事。音量调节也是一样用setvolume相关接口。我建议把默认音量控制在 70% 左右。全景视频音量开满会很突兀尤其展厅里突然“轰”一声客户体验非常差。加一个静音按钮比调音量按钮更实用很多现场用户只想安静地看画面。4. 多分辨率与流媒体让网差的人也能看一个 4K 全景 MP4 可能上百 MB用户网速不行时加载过程就像一个黑洞。多分辨率和流媒体方案就是用来解决这个问题的。4.1 为什么一个 MP4 不够用单 MP4 的问题有三个加载慢浏览器必须下载足够的数据才能开始播放4K 文件体积大首帧等待时间长。内存爆炸Krpano 解码全景视频时要同时处理多帧缓冲高分辨率视频在低端手机上极容易卡顿甚至崩溃。拖动进度条慢如果关键帧分布不好用户拖进度条要等几秒才能播体验很割裂。所以我的原则是真正交付的项目至少准备两个清晰度档位。移动端 1080p桌面端 4K由 Krpano 根据设备自动选择。如果客户对清晰度有执念再额外加一个 2K 中间档位尽量不搞一档走天下。4.2 用 ffmpeg 切出 HLS 素材Krpano 的多 source 支持直接加载 HLSHTTP Live Streaming流。HLS 的好处是能把视频切成一段一段的小切片播放器边下边播拖动进度条时只加载附近片段大幅节省流量。用 ffmpeg 切 HLS 的命令大致如下ffmpeg -i video_source.mp4 \ -vf scale1920:960 \ -c:v libx264 -b:v 8M \ -preset medium \ -g 60 -keyint_min 60 -sc_threshold 0 \ -hls_time 6 \ -hls_playlist_type vod \ -hls_segment_filename segments/1080p_%04d.ts \ index_1080p.m3u8几个关键参数-hls_time 6每个切片 6 秒。切片太短会增加请求次数太长则拖动进度条的粒度不够细。6 秒是比较均衡的值。-hls_playlist_type vod明确这是点播流不是直播播放器可以预加载更多数据。-hls_segment_filename切片文件命名规则后续维护方便。如果要做多档位就把scale和-b:v换成对应分辨率跑出多套 m3u8 ts 文件然后分别放目录。切完之后记得检查一下 m3u8 里的切片路径是不是相对路径如果是绝对路径或者带本地盘符部署到服务器上也会挂。4.3 在 XML 里声明多档位HLS 素材接入 XML 时把 source 的 type 改成application/x-mpegURLvideo autostartfalse loopfalse posterurlvideo/preview.jpg source typeapplication/x-mpegURL srcvideo/index_1080p.m3u8 / source typeapplication/x-mpegURL srcvideo/index_4k.m3u8 / /videoKrpano 拿到多档 HLS 后会结合设备的解码能力和网络条件选择最合适的档位。注意它不是“永远选最高码率”网速不够时会自动切低档位这一点实测下来比较聪明。这里有个特别容易踩的坑HLS 切片文件分布在另一个域名比如 CDN时必须配置 CORS 跨域头否则浏览器会直接拦截请求Krpano 表现就是视频黑屏但控件又能点。我在本地联调时遇到过一次排查了半天最后发现是服务器少了Access-Control-Allow-Origin响应头加上之后就正常了。如果你的视频和页面在同一个域名下一般不会触发这个问题这也是我建议项目文件尽量同域部署的原因。如果你觉得 HLS 切片太麻烦也可以不做直接放两个普通 MP4 让 Krpano 多 source 选择。代价是拖动进度条时体验稍差但实现成本最低。我的经验是小项目用双 MP4大项目或客户明确要求“打开快、拖动快”时再上 HLS。5. 热点交互让视频能“点”全景视频如果只是“能播放能旋转”那它还是一个视频播放器。真正把视频变成“漫游产品”靠的是热点交互。我这次做了三类热点分别解决不同问题。5.1 常规热点点击跳转场景第一种是固定的球面坐标热点。所谓球面坐标是指热点固定在当前视角的某个经纬度上视频画面转动时它跟着画面一起转动。Krpano 中定义方式如下hotspot namenextscene urlarrow.png ath30 atv-10 distortedfalse onclickloadscene(scene_2, null, MERGE, BLEND(0.5)); /ath是水平角度atv是垂直角度。比如 30 度向右、-10 度向下热点就悬浮在那个位置。点击之后通过loadscene切换到另一个场景实现从视频场景跳转到展厅全景图场景或者反过来。这类热点适合“换场景”操作比如视频到了一个时间节点屏幕右下方出现“进入下一站”的箭头。但要注意Krpano 的全景热点是绑在视角上的不是绑在视频画面里的某个物体上。视频画面本身在播放中不断变化一个门把手刚才在画面左侧下一秒就跑到画面右侧了热点并不会跟着物体移动。因为没有外部位置跟踪数据前端无法知道视频每一帧里物体对应的球面坐标。所以全景视频里的热点绝大多数用“屏幕固定位置”或者“按时间显隐”来做而不是试图钉在画面里的某个真实物体上。这个认知能帮你省掉很多无效折腾。5.2 按时间轴控制热点显隐第二种是时间轴热点这是全景视频最实用的交互方式。视频播放到某个时间点时显示对应热点过了这个时间点自动隐藏。实现思路是定时器循环刷新 读取当前播放时间。大致结构如下events onloadsetinterval(refresh_video_hotspots, 250); / action namerefresh_video_hotspots if(videotime GT 5 AND videotime LT 15, set(hotspot_product.visible, true); set(hotspot_product.visible, false); ); /actionvideotime是当前播放时间单位秒。上述动作的意思是视频播到第 5 秒到第 15 秒之间时显示hotspot_product这个热点其他时间隐藏。当用户点击热点时可以用loadscene跳转也可以弹出一个图片说明甚至可以切换视角聚焦到某个方向。这段逻辑如果只是写个示意逻辑并不复杂但实际项目里要注意几个细节时间判断不是“点触发”而是“区间触发”。用户可能会从第 20 秒拖回第 10 秒如果你只在特定时间点设置一次显隐拖回进度条时状态不会刷新。所以每次定时器刷新都要根据当前时间重新计算所有热点的显隐状态。定时器间隔不要太短250ms 足够太频繁会导致 CPU 徒增。热点显隐最好配合一点透明度动画否则突然出现会打断观看让用户出戏。时间轴热点很适合做解说型内容视频讲到某个设备时画面边缘浮出“设备参数”按钮点击查看详情。我这次给车间巡检项目就是这么用的客户反馈很好。5.3 播放进度埋点与统计第三种不是用户能看见的但对项目方很值钱播放进度埋点。全景视频项目往往要写验收报告用户到底看了哪些内容、看到百分之几关掉了这些数据最容易被量化成交付成果。埋点思路和解说热点类似周期性地记录当前播放时间然后传给后端接口。Krpano 里可以用js()动作调用页面里的 JavaScript 函数把数据发出去。简单示意action namerefresh_progress if(videotime GT 30 AND videotime LT 31, js(reportProgress(30)); ); /action这里只会在视频播到 30 秒到 31 秒之间时上报一次“看到第 30 秒”避免每 250ms 上报一次把后端打爆。实际项目里我一般采集几个百分比节点播放开始、25%、50%、75%、100%以及最后的跳转行为足够了。埋点要部署在 HTTPS 页面下否则混合内容会被拦截。另外后端接口要做简单的频率限制防止用户反复拖动进度条产生大量垃圾请求。6. 常见问题与排查技巧实录这部分是我最想写给同行的因为全景视频的坑非常隐蔽出问题时第一反应经常是“Krpano 配置写错了”但真正原因往往是素材、服务器或者浏览器策略。6.1 高频问题速查表现象常见原因处理办法视频黑屏无画面视频编码不是 H.264 或像素格式不是 yuv420p用 ffmpeg 重编码强制 H.264 Main/High Profile yuv420p有画面没声音音频编码或轨道参数不被浏览器支持音频统一转 AAC双声道 48kHz不要用 AC-3 或 DTS手机端卡顿严重视频分辨率太高或 H.265 无硬解给移动端准备 1080p 档位不要一台设备通吃 4KiOS 自动播放被拦浏览器策略限制自动播放不要依赖 autostart让用户点击后触发播放视频拖进度条很慢关键帧间隔太长或 HLS 切片粒度太大每 2 秒一个关键帧HLS 切片时长控制在 6 秒左右热点显示但点不到热点被其他元素遮挡或屏幕坐标出界检查 zindex 排序热点尺寸和 hitarea 也要设大一点跨域加载失败视频文件与页面不在同一域名且没有 CORS 头配置Access-Control-Allow-Origin或者全部文件同域部署视角一开始歪的视频源素材的拼接中心不在正前方调整 view 的 hlookat/vlookat或重新导出等距柱状投影时设置中心点这些问题的共性是Krpano 本身是没错的错在“喂给它的数据不对”。排查顺序我建议是先 ffprobe 看素材编码参数再确认服务器响应头最后才怀疑 XML 配置。6.2 移动端优化与真机测试移动端是最容易翻车的环节。桌面浏览器解码能力普遍强4K 全景视频都能跑手机端解码能力差异巨大尤其是一些中低端安卓机H.264 的 4K 硬解支持都不完整。我的优化策略是三档降级检测到移动端通过 JS 判断 userAgent 或设备类型时优先使用 1080p 视频源如果移动端还卡降低默认 fov减少当前帧需要渲染的像素数关闭移动端的自动旋转避免陀螺仪与鼠标拖拽同时运算导致发热卡顿。真机测试也有一套固定流程准备一台旧款 iPhone 和一台中低端安卓机用 4G 网络而不是 WiFi 测试。WiFi 下带宽充足问题不容易暴露4G 网络才能看出加载速度和卡顿情况。我在交付前至少会跑三轮WiFi 桌面、WiFi 手机、4G 手机。每一轮都盯着首帧时间、拖动进度响应和整体发热情况有问题当场改。另一个容易忽略的点是内存。Krpano 全景视频在低内存手机上容易被系统杀后台所以视频场景不要同时加载太多热点图片和插件。热点图尽量用 PNG 压缩后的尺寸不要放一个大几千像素的雪碧图进去。6.3 几条实际踩坑后的心得写到最后分享几条从项目里磨出来的经验都是文档里不会写的内容。第一调试时先用低分辨率代理视频。开发阶段先用一段几 MB 的 540p 测试视频把交互逻辑全部跑通确认热点、跳转、进度条没问题之后再换成正式的高清素材。如果一上来就用 4K 大文件调试每次改 XML 都要等加载效率会低到怀疑人生。第二所有局部变量尽量用 Krpano 的 trace 日志排查。Krpano 浏览器端调试时可以打开 console 面板XML 里也可以用trace()输出变量值。热点不显示时先 trace 一下热点 visible 状态和 videotime 当前值往往一眼就能看出来是时间区间写错了还是热点自身问题。第三视频和 XML 文件全部放同域。无论是普通 MP4 还是 HLS跨域都可能带来额外的问题。如果你必须把视频放在 CDN优先用 HLS 并配好 CORS比直接放 MP4 省心得多。第四视频封面图一定要做而且不要太大。我遇到过一次客户说“打开项目黑屏”排查到最后发现是 4K 预览图太大在手机上加载太慢所以首屏一直黑。后来封面图压到 200KB 以内问题消失。第五最后再说一个小技巧如果用自定义播放按钮记得给按钮加一个onover状态放大或者改变透明度否则用户很难发现那是可以点的。全景视频场景里信息层级本来就比普通页面多交互元素的视觉反馈一定要明显不然按钮就像隐形的一样。全景视频这个模块素材编解码占四成问题XML 配置占三成剩下的都是设备适配和交互细节。把这几块理顺了交付一个流畅稳定的全景视频漫游项目并不难。后面如果碰到新的坑我再开一篇随笔补上。