async function autoRecordRealseeVR({
// "9:16" 竖屏;"16:9" 横屏
aspectRatio = "9:16",
portraitWidth = 540,
portraitHeight = 960,
landscapeWidth = 1280,
landscapeHeight = 720,
// 三维模型录制前的放大参数
modelZoomTimes = 3,
modelZoomDelta = -480,
modelZoomIntervalMs = 450,
modelZoomWaitMs = 1200,
// 三维模型和房间旋转时长
modelRotateSeconds = 25,
roomRotateSeconds = 40,
modelLoadSeconds = 3,
roomLoadSeconds = 5,
staySeconds = 1.2,
// 旋转方向相反时改成 1
direction = -1,
rotateFPS = 30,
recordingFPS = 30,
videoBitsPerSecond = 14_000_000,
hideBottomUI = true,
hideTitle = false,
hideMiniMap = false,
excludeRooms = [],
maxRooms = 30
} = {}) {
if (window.REALSEE_AUTO_RECORDER?.running) {
console.warn(
"脚本正在运行。停止请输入:window.REALSEE_AUTO_RECORDER.stop()"
);
return;
}
const sleep = ms =>
new Promise(resolve => setTimeout(resolve, ms));
const normalizeText = value =>
String(value || "")
.replace(/\s+/g, "")
.trim();
const safeFilename = value =>
normalizeText(value)
.replace(/[\\/:*?"<>|]/g, "_") ||
"未命名";
const selectedRatio =
String(aspectRatio)
.replace(":", ":")
.replace(/\s+/g, "") === "16:9"
? "16:9"
: "9:16";
const targetSize =
selectedRatio === "16:9"
? {
width: landscapeWidth,
height: landscapeHeight
}
: {
width: portraitWidth,
height: portraitHeight
};
let stopped = false;
let stream = null;
let activeRecorder = null;
let pageRestored = false;
const completedRooms = new Set();
const failedRooms = new Set();
console.log(
`[如视录制] 启动,比例:${selectedRatio},` +
`尺寸:${targetSize.width}×${targetSize.height}`
);
/* =====================================================
保存网页原始样式
===================================================== */
const originalApp = document.getElementById("app");
const originalVapor = document.getElementById("vapor");
const originalStyles = {
html:
document.documentElement.getAttribute("style"),
body:
document.body.getAttribute("style"),
app:
originalApp?.getAttribute("style") ?? null,
vapor:
originalVapor?.getAttribute("style") ?? null
};
/* =====================================================
清理旧脚本元素
===================================================== */
[
"realsee-clean-recording-style",
"realsee-v3-panel",
"realsee-batch-panel",
"realsee-auto-record-panel",
"realsee-video-status",
"realsee-room-record-panel",
"realsee-click-record-panel",
"realsee-record-panel"
].forEach(id => {
document.getElementById(id)?.remove();
});
/* =====================================================
隐藏无关网页 UI
===================================================== */
const cleanStyle =
document.createElement("style");
cleanStyle.id =
"realsee-clean-recording-style";
cleanStyle.textContent = `
html,
body {
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
background: #000 !important;
}
body {
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
#app,
#vapor {
overflow: hidden !important;
background: #000 !important;
}
#realsee-v3-panel,
#realsee-batch-panel,
#realsee-auto-record-panel,
#realsee-video-status,
#realsee-room-record-panel,
#realsee-click-record-panel,
#realsee-record-panel {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
pointer-events: none !important;
}
${
hideBottomUI
? `
.vapor-bottom-bar,
.vapor-single-bottom-bar-theme-arc_beauty-addition-side,
.vapor-single-bottom-bar-theme-arc_beauty-front,
.vapor-single-bottom-bar-theme-arc_beauty-front-content,
.vapor-avatar-horizontal,
.vapor-overlay
.vapor-h-stack.vapor-h-stack-h-align-start.vapor-h-stack-v-align-center {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
pointer-events: none !important;
}
`
: ""
}
${
hideTitle
? `
.header_header__3KWjy
> .vapor-v-stack-wrap
> .vapor-v-stack
> .vapor-block-wrap
.vapor-h-stack-h-align-start
.vapor-text-warp {
visibility: hidden !important;
}
.header_modeTabs__rvuNw,
.header_modeTabs__rvuNw * {
visibility: visible !important;
}
`
: ""
}
${
hideMiniMap
? `
.minimap_miniViewport__ZZPy2,
[class*="minimap_miniViewport"] {
display: none !important;
}
`
: ""
}
`;
document.head.appendChild(cleanStyle);
/* =====================================================
设置 9:16 / 16:9 页面区域
===================================================== */
const applyAspectRatio = () => {
const app = document.getElementById("app");
const vapor = document.getElementById("vapor");
if (!app || !vapor) {
console.warn("[如视录制] 没有找到 #app 或 #vapor");
return;
}
const targetRatio =
targetSize.width / targetSize.height;
const availableWidth =
window.innerWidth;
const availableHeight =
window.innerHeight;
let displayWidth =
availableWidth;
let displayHeight =
displayWidth / targetRatio;
if (displayHeight > availableHeight) {
displayHeight =
availableHeight;
displayWidth =
displayHeight * targetRatio;
}
Object.assign(app.style, {
width: `${displayWidth}px`,
height: `${displayHeight}px`,
position: "relative",
overflow: "hidden",
flex: "0 0 auto"
});
Object.assign(vapor.style, {
width: `${displayWidth}px`,
height: `${displayHeight}px`,
maxWidth: "none",
maxHeight: "none",
overflow: "hidden"
});
window.dispatchEvent(
new Event("resize")
);
console.log(
`[如视录制] 页面区域:` +
`${Math.round(displayWidth)}×` +
`${Math.round(displayHeight)}`
);
};
try {
const browserChromeWidth =
window.outerWidth -
window.innerWidth;
const browserChromeHeight =
window.outerHeight -
window.innerHeight;
window.resizeTo(
targetSize.width +
browserChromeWidth,
targetSize.height +
browserChromeHeight
);
} catch {}
applyAspectRatio();
const resizeHandler = () => {
applyAspectRatio();
};
window.addEventListener(
"resize",
resizeHandler
);
/* =====================================================
恢复网页
===================================================== */
const restoreAttribute = (
element,
attribute,
value
) => {
if (!element) return;
if (value === null) {
element.removeAttribute(attribute);
} else {
element.setAttribute(
attribute,
value
);
}
};
const restorePage = () => {
if (pageRestored) return;
pageRestored = true;
window.removeEventListener(
"resize",
resizeHandler
);
document
.getElementById(
"realsee-clean-recording-style"
)
?.remove();
restoreAttribute(
document.documentElement,
"style",
originalStyles.html
);
restoreAttribute(
document.body,
"style",
originalStyles.body
);
restoreAttribute(
document.getElementById("app"),
"style",
originalStyles.app
);
restoreAttribute(
document.getElementById("vapor"),
"style",
originalStyles.vapor
);
window.dispatchEvent(
new Event("resize")
);
console.log("[如视录制] 网页布局已恢复");
};
/* =====================================================
顶部菜单
===================================================== */
const findMenuButton = menuName =>
[...document.querySelectorAll("button")]
.find(button =>
normalizeText(
button.textContent
) === menuName
);
const clickMenu = async menuName => {
for (
let attempt = 0;
attempt < 30;
attempt++
) {
const button =
findMenuButton(menuName);
if (button) {
button.click();
await sleep(800);
return true;
}
await sleep(200);
}
throw new Error(
`没有找到菜单:${menuName}`
);
};
const isMenuActive = menuName => {
const button =
findMenuButton(menuName);
if (!button) return false;
return Boolean(
button.closest(
"[class*='active']"
) ||
button.parentElement?.closest(
"[class*='active']"
)
);
};
/* =====================================================
查找主 Canvas
===================================================== */
const isVisible = element => {
if (!(element instanceof HTMLElement)) {
return false;
}
const style =
getComputedStyle(element);
const rect =
element.getBoundingClientRect();
return (
style.display !== "none" &&
style.visibility !== "hidden" &&
rect.width > 20 &&
rect.height > 20
);
};
const getMainCanvas = () =>
[
...document.querySelectorAll(
"#viewports canvas"
)
]
.filter(isVisible)
.sort((a, b) => {
const rectA =
a.getBoundingClientRect();
const rectB =
b.getBoundingClientRect();
return (
rectB.width *
rectB.height -
rectA.width *
rectA.height
);
})[0] || null;
/* =====================================================
鼠标和滚轮事件
===================================================== */
const dispatchPointer = (
target,
type,
x,
y,
buttons,
pointerId = 51
) => {
const options = {
bubbles: true,
cancelable: true,
composed: true,
clientX: x,
clientY: y,
screenX: x,
screenY: y,
button: 0,
buttons,
pointerId,
pointerType: "mouse",
isPrimary: true
};
try {
target.dispatchEvent(
new PointerEvent(
type,
options
)
);
} catch {}
const mouseType = {
pointerdown: "mousedown",
pointermove: "mousemove",
pointerup: "mouseup"
}[type];
if (mouseType) {
try {
target.dispatchEvent(
new MouseEvent(
mouseType,
options
)
);
} catch {}
}
};
const realClick = element => {
if (!element) return false;
const rect =
element.getBoundingClientRect();
const x =
rect.left +
rect.width / 2;
const y =
rect.top +
rect.height / 2;
const common = {
bubbles: true,
cancelable: true,
composed: true,
clientX: x,
clientY: y,
screenX: x,
screenY: y,
button: 0
};
try {
element.dispatchEvent(
new PointerEvent(
"pointerdown",
{
...common,
buttons: 1,
pointerId: 88,
pointerType: "mouse",
isPrimary: true
}
)
);
element.dispatchEvent(
new PointerEvent(
"pointerup",
{
...common,
buttons: 0,
pointerId: 88,
pointerType: "mouse",
isPrimary: true
}
)
);
} catch {}
try {
element.dispatchEvent(
new MouseEvent(
"mousedown",
{
...common,
buttons: 1
}
)
);
element.dispatchEvent(
new MouseEvent(
"mouseup",
{
...common,
buttons: 0
}
)
);
element.dispatchEvent(
new MouseEvent(
"click",
{
...common,
buttons: 0
}
)
);
} catch {}
try {
element.click();
} catch {}
return true;
};
const realClickAt = (
target,
x,
y
) => {
const common = {
bubbles: true,
cancelable: true,
composed: true,
clientX: x,
clientY: y,
screenX: x,
screenY: y,
button: 0
};
try {
target.dispatchEvent(
new PointerEvent(
"pointerdown",
{
...common,
buttons: 1,
pointerId: 99,
pointerType: "mouse",
isPrimary: true
}
)
);
target.dispatchEvent(
new PointerEvent(
"pointerup",
{
...common,
buttons: 0,
pointerId: 99,
pointerType: "mouse",
isPrimary: true
}
)
);
target.dispatchEvent(
new MouseEvent(
"click",
{
...common,
buttons: 0
}
)
);
} catch {}
};
/* =====================================================
三维模型滚轮放大
===================================================== */
const zoomModelByWheel = async ({
times,
delta,
intervalMs,
waitMs
}) => {
const canvas =
getMainCanvas();
if (!canvas) {
throw new Error(
"没有找到三维模型 Canvas"
);
}
const rect =
canvas.getBoundingClientRect();
const x =
rect.left +
rect.width * 0.5;
const y =
rect.top +
rect.height * 0.52;
for (
let index = 0;
index < times &&
!stopped;
index++
) {
try {
canvas.dispatchEvent(
new WheelEvent(
"wheel",
{
bubbles: true,
cancelable: true,
composed: true,
clientX: x,
clientY: y,
screenX: x,
screenY: y,
deltaX: 0,
deltaY: delta,
deltaMode: 0
}
)
);
} catch (error) {
console.warn(
"[如视录制] 模型放大失败:",
error
);
}
console.log(
`[如视录制] 三维模型放大:` +
`${index + 1}/${times}`
);
await sleep(intervalMs);
}
await sleep(waitMs);
};
/* =====================================================
缓慢旋转 360°
===================================================== */
const rotateCanvas360 = async ({
seconds,
title
}) => {
const canvas =
getMainCanvas();
if (!canvas) {
throw new Error(
"没有找到 VR 主画布"
);
}
const segmentCount =
Math.max(
6,
Math.round(
seconds / 5
)
);
const segmentDuration =
seconds *
1000 /
segmentCount;
for (
let segment = 0;
segment <
segmentCount &&
!stopped;
segment++
) {
const rect =
canvas.getBoundingClientRect();
const y =
rect.top +
rect.height * 0.52;
const startX =
direction < 0
? rect.left +
rect.width * 0.74
: rect.left +
rect.width * 0.26;
const endX =
direction < 0
? rect.left +
rect.width * 0.26
: rect.left +
rect.width * 0.74;
const frames =
Math.max(
1,
Math.round(
segmentDuration /
1000 *
rotateFPS
)
);
const pointerId =
100 + segment;
dispatchPointer(
canvas,
"pointerdown",
startX,
y,
1,
pointerId
);
const startedAt =
performance.now();
for (
let frame = 0;
frame <= frames &&
!stopped;
frame++
) {
const progress =
frame / frames;
const smooth =
progress *
progress *
(3 - 2 * progress);
const x =
startX +
(
endX -
startX
) *
smooth;
dispatchPointer(
canvas,
"pointermove",
x,
y,
1,
pointerId
);
const totalProgress =
(
segment +
progress
) /
segmentCount;
console.log(
`[如视录制] ${title}:` +
`${Math.round(
totalProgress * 100
)}%`
);
const targetTime =
startedAt +
(frame + 1) *
(
segmentDuration /
frames
);
await sleep(
Math.max(
1,
targetTime -
performance.now()
)
);
}
dispatchPointer(
canvas,
"pointerup",
endX,
y,
0,
pointerId
);
await sleep(70);
}
await sleep(
staySeconds * 1000
);
};
/* =====================================================
房间名称识别
===================================================== */
const roomNamePattern =
/客餐厅|客厅|餐厅|主卧|次卧|卧室|儿童房|老人房|书房|厨房|卫生间|洗手间|浴室|阳台|玄关|走廊|过道|衣帽间|储藏室|储物间|保姆间|设备间|茶室|多功能房/;
const isRoomName = name =>
Boolean(
name &&
name.length <= 20 &&
roomNamePattern.test(name) &&
!excludeRooms.some(
excluded =>
name.includes(
excluded
)
)
);
const collectAllRoomNames = () => {
const names =
new Set();
document
.querySelectorAll(
".plugin-DoorLabelPlugin-txt"
)
.forEach(element => {
const name =
normalizeText(
element.textContent
);
if (isRoomName(name)) {
names.add(name);
}
});
document
.querySelectorAll(
[
".model-room-label-plugin-container *",
"#viewports span",
"#viewports div",
"[class*='room-label']",
"[class*='roomLabel']",
"[class*='RoomLabel']",
"[class*='model-room']"
].join(",")
)
.forEach(element => {
const text =
normalizeText(
element.textContent
);
if (isRoomName(text)) {
names.add(text);
}
});
return [...names]
.slice(0, maxRooms);
};
/* =====================================================
三维模型房间标签
===================================================== */
const getClickableParent =
element => {
if (!element) {
return null;
}
return (
element.closest(
[
"button",
"[role='button']",
"[onclick]",
"[style*='pointer-events: auto']",
"[class*='room']",
"[class*='Room']",
"[class*='label']",
"[class*='Label']",
"[class*='item']",
"[class*='Item']"
].join(",")
) ||
element.parentElement ||
element
);
};
const findModelRoomCandidates =
roomName => {
const candidates = [];
const elements = [
...document.querySelectorAll(
[
".model-room-label-plugin-container *",
"#viewports span",
"#viewports div",
"[class*='room-label']",
"[class*='roomLabel']",
"[class*='RoomLabel']",
"[class*='model-room']"
].join(",")
)
];
for (
const element of
elements
) {
const text =
normalizeText(
element.textContent
);
if (text !== roomName) {
continue;
}
const clickable =
getClickableParent(
element
);
const rect =
clickable
.getBoundingClientRect();
const style =
getComputedStyle(
clickable
);
candidates.push({
element: clickable,
rect,
score:
(
rect.width > 0
? 10
: 0
) +
(
rect.height > 0
? 10
: 0
) +
(
style.pointerEvents !==
"none"
? 20
: 0
) +
(
Number(
style.opacity
) > 0.4
? 20
: 0
) +
(
rect.left >= 0 &&
rect.top >= 0 &&
rect.right <=
innerWidth &&
rect.bottom <=
innerHeight
? 30
: 0
)
});
}
return candidates.sort(
(a, b) =>
b.score -
a.score
);
};
const clickModelRoom =
async roomName => {
for (
let attempt = 1;
attempt <= 8;
attempt++
) {
if (
!isMenuActive(
"三维模型"
)
) {
await clickMenu(
"三维模型"
);
await sleep(
modelLoadSeconds *
1000
);
}
const candidates =
findModelRoomCandidates(
roomName
);
console.log(
`[如视录制] ${roomName} 候选:`,
candidates
);
for (
const candidate of
candidates
) {
console.log(
`[如视录制] 进入 ${roomName},` +
`尝试 ${attempt}/8`
);
realClick(
candidate.element
);
await sleep(900);
if (
isMenuActive("漫游")
) {
await sleep(
roomLoadSeconds *
1000
);
return true;
}
if (
candidate.rect.width >
0 &&
candidate.rect.height >
0
) {
const canvas =
getMainCanvas();
if (canvas) {
const x =
candidate.rect.left +
candidate.rect.width /
2;
const y =
candidate.rect.top +
candidate.rect.height /
2;
realClickAt(
canvas,
x,
y
);
await sleep(800);
}
}
if (
isMenuActive("漫游")
) {
await sleep(
roomLoadSeconds *
1000
);
return true;
}
}
if (candidates.length) {
await clickMenu(
"漫游"
);
await sleep(
roomLoadSeconds *
1000
);
if (
isMenuActive(
"漫游"
)
) {
return true;
}
}
await sleep(500);
}
return false;
};
/* =====================================================
MP4 检测
===================================================== */
const mp4Types = [
'video/mp4;codecs="avc1.42E01E"',
'video/mp4;codecs="avc1.42001E"',
"video/mp4;codecs=h264",
"video/mp4"
];
const mimeType =
mp4Types.find(type =>
MediaRecorder
.isTypeSupported(type)
);
if (!mimeType) {
restorePage();
throw new Error(
"浏览器不支持直接录制 MP4," +
"请使用最新版 Edge 或 Chrome"
);
}
/* =====================================================
请求一次标签页共享
===================================================== */
console.log(
"[如视录制] 请选择当前 VR 标签页"
);
stream =
await navigator
.mediaDevices
.getDisplayMedia({
video: {
frameRate: {
ideal:
recordingFPS,
max:
recordingFPS
},
width: {
ideal:
targetSize.width
},
height: {
ideal:
targetSize.height
}
},
audio: false,
preferCurrentTab: true,
selfBrowserSurface:
"include",
surfaceSwitching:
"exclude"
});
/* =====================================================
单独录制一个文件
===================================================== */
const recordOneFile =
async (
fileName,
action
) => {
if (stopped) {
return false;
}
const chunks = [];
const recorder =
new MediaRecorder(
stream,
{
mimeType,
videoBitsPerSecond
}
);
activeRecorder =
recorder;
recorder.addEventListener(
"dataavailable",
event => {
if (
event.data?.size >
0
) {
chunks.push(
event.data
);
}
}
);
const savePromise =
new Promise(
(resolve, reject) => {
recorder.addEventListener(
"stop",
() => {
try {
const blob =
new Blob(
chunks,
{
type:
mimeType
}
);
if (!blob.size) {
reject(
new Error(
`${fileName}.mp4 录像为空`
)
);
return;
}
const url =
URL.createObjectURL(
blob
);
const link =
document.createElement(
"a"
);
link.href =
url;
link.download =
`${safeFilename(
fileName
)}.mp4`;
link.style.display =
"none";
document.body
.appendChild(
link
);
link.click();
link.remove();
setTimeout(() => {
URL.revokeObjectURL(
url
);
}, 120000);
console.log(
`[如视录制] 已保存:` +
`${fileName}.mp4,` +
`${(
blob.size /
1024 /
1024
).toFixed(1)} MB`
);
resolve(true);
} catch (error) {
reject(error);
}
},
{
once: true
}
);
}
);
recorder.start(1000);
try {
await action();
} finally {
if (
recorder.state !==
"inactive"
) {
try {
recorder.requestData();
} catch {}
recorder.stop();
}
}
await savePromise;
activeRecorder = null;
await sleep(1200);
return true;
};
/* =====================================================
停止函数
===================================================== */
const stop = () => {
if (stopped) return;
stopped = true;
console.log(
"[如视录制] 正在停止并保存当前视频"
);
try {
if (
activeRecorder &&
activeRecorder.state !==
"inactive"
) {
activeRecorder
.requestData();
activeRecorder.stop();
}
} catch {}
setTimeout(() => {
stream
?.getTracks()
.forEach(track => {
try {
track.stop();
} catch {}
});
restorePage();
}, 800);
if (
window
.REALSEE_AUTO_RECORDER
) {
window
.REALSEE_AUTO_RECORDER
.running = false;
}
};
window.REALSEE_AUTO_RECORDER = {
running: true,
stop,
completedRooms,
failedRooms,
collectAllRoomNames,
findModelRoomCandidates,
aspectRatio:
selectedRatio,
targetSize
};
stream
.getVideoTracks()[0]
?.addEventListener(
"ended",
() => {
stopped = true;
restorePage();
if (
window
.REALSEE_AUTO_RECORDER
) {
window
.REALSEE_AUTO_RECORDER
.running = false;
}
}
);
try {
/* ===================================================
打开三维模型
=================================================== */
console.log(
"[如视录制] 打开三维模型"
);
await clickMenu(
"三维模型"
);
await sleep(
modelLoadSeconds *
1000
);
let roomNames =
collectAllRoomNames();
if (
roomNames.length <
2
) {
await sleep(2500);
roomNames =
collectAllRoomNames();
}
roomNames = [
...new Set(
roomNames
)
].slice(
0,
maxRooms
);
console.log(
"[如视录制] 房间列表:",
roomNames
);
console.table(
roomNames.map(
(name, index) => ({
顺序:
index + 1,
房间:
name
})
)
);
if (!roomNames.length) {
throw new Error(
"没有检测到房间名称"
);
}
/* ===================================================
先放大三维模型,不录制放大过程
=================================================== */
console.log(
`[如视录制] 三维模型放大 ${modelZoomTimes} 次`
);
await zoomModelByWheel({
times:
modelZoomTimes,
delta:
modelZoomDelta,
intervalMs:
modelZoomIntervalMs,
waitMs:
modelZoomWaitMs
});
/* ===================================================
放大后录制三维模型
=================================================== */
await recordOneFile(
"三维模型",
async () => {
await rotateCanvas360({
seconds:
modelRotateSeconds,
title:
"三维模型"
});
}
);
/* ===================================================
逐房间录制
=================================================== */
for (
let index = 0;
index <
roomNames.length &&
!stopped;
index++
) {
const roomName =
roomNames[index];
if (
completedRooms.has(
roomName
)
) {
continue;
}
console.log(
`[如视录制] 准备录制:` +
`${roomName}.mp4,` +
`${index + 1}/` +
`${roomNames.length}`
);
await clickMenu(
"三维模型"
);
await sleep(
modelLoadSeconds *
1000
);
const entered =
await clickModelRoom(
roomName
);
if (!entered) {
console.warn(
`[如视录制] 无法进入:` +
roomName
);
failedRooms.add(
roomName
);
continue;
}
if (
!isMenuActive(
"漫游"
)
) {
await clickMenu(
"漫游"
);
await sleep(
roomLoadSeconds *
1000
);
}
await recordOneFile(
roomName,
async () => {
await rotateCanvas360({
seconds:
roomRotateSeconds,
title:
`${roomName} ` +
`${index + 1}/` +
`${roomNames.length}`
});
}
);
completedRooms.add(
roomName
);
console.log(
`[如视录制] 完成:` +
`${roomName}.mp4`
);
}
/* ===================================================
重试失败房间
=================================================== */
if (
failedRooms.size &&
!stopped
) {
const retryRooms =
[...failedRooms];
for (
let index = 0;
index <
retryRooms.length &&
!stopped;
index++
) {
const roomName =
retryRooms[index];
console.log(
`[如视录制] 重试:` +
roomName
);
await clickMenu(
"三维模型"
);
await sleep(
modelLoadSeconds *
1000 +
1500
);
const entered =
await clickModelRoom(
roomName
);
if (!entered) {
continue;
}
if (
!isMenuActive(
"漫游"
)
) {
await clickMenu(
"漫游"
);
await sleep(
roomLoadSeconds *
1000
);
}
await recordOneFile(
roomName,
async () => {
await rotateCanvas360({
seconds:
roomRotateSeconds,
title:
`重试 ${roomName}`
});
}
);
completedRooms.add(
roomName
);
failedRooms.delete(
roomName
);
}
}
/* ===================================================
全部结束
=================================================== */
stream
.getTracks()
.forEach(track => {
try {
track.stop();
} catch {}
});
window
.REALSEE_AUTO_RECORDER
.running = false;
restorePage();
console.log(
"[如视录制] 全部录制完成"
);
console.log(
"成功房间:",
[...completedRooms]
);
console.log(
"失败房间:",
[...failedRooms]
);
console.log(
`共保存 ${
completedRooms.size + 1
} 个 MP4 视频`
);
} catch (error) {
console.error(
"[如视录制] 执行错误:",
error
);
stop();
}
}
/* =====================================================
最底部直接调用
===================================================== */
autoRecordRealseeVR({
// 默认竖屏;横屏改成 "16:9"
aspectRatio: "9:16",
portraitWidth: 540,
portraitHeight: 960,
landscapeWidth: 1280,
landscapeHeight: 720,
// 三维模型录制前放大三次
modelZoomTimes: 3,
modelZoomDelta: -480,
modelZoomIntervalMs: 450,
modelZoomWaitMs: 1200,
modelRotateSeconds: 25,
roomRotateSeconds: 40,
modelLoadSeconds: 3,
roomLoadSeconds: 5,
staySeconds: 1.2,
direction: -1,
hideBottomUI: true,
hideTitle: false,
hideMiniMap: false,
excludeRooms: [],
maxRooms: 30
});