@midscene/visualizer 1.0.1-beta-20251029093754.0 → 1.0.1-beta-20251103074550.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
6
6
  import "./index.css";
7
7
  import { mouseLoading, mousePointer } from "../../utils/index.mjs";
8
8
  import { CaretRightOutlined, DownloadOutlined, ExportOutlined, LoadingOutlined } from "@ant-design/icons";
9
- import { Dropdown, Spin, Switch, Tooltip } from "antd";
9
+ import { Dropdown, Spin, Switch, Tooltip, message } from "antd";
10
10
  import global_perspective from "../../icons/global-perspective.mjs";
11
11
  import player_setting from "../../icons/player-setting.mjs";
12
12
  import show_marker from "../../icons/show-marker.mjs";
@@ -83,17 +83,32 @@ class RecordingSession {
83
83
  mediaRecorder.ondataavailable = (event)=>{
84
84
  if (event.data.size > 0) this.chunks.push(event.data);
85
85
  };
86
+ mediaRecorder.onerror = (event)=>{
87
+ console.error('MediaRecorder error:', event);
88
+ message.error('Video recording failed. Please try again.');
89
+ this.recording = false;
90
+ this.mediaRecorder = null;
91
+ };
86
92
  this.mediaRecorder = mediaRecorder;
87
93
  this.recording = true;
88
94
  return this.mediaRecorder.start();
89
95
  }
90
96
  stop() {
91
- var _this_mediaRecorder;
92
97
  if (!this.recording || !this.mediaRecorder) return void console.warn('not recording');
93
98
  this.mediaRecorder.onstop = ()=>{
99
+ if (0 === this.chunks.length) {
100
+ console.error('No video data captured');
101
+ message.error('Video export failed: No data captured.');
102
+ return;
103
+ }
94
104
  const blob = new Blob(this.chunks, {
95
105
  type: 'video/webm'
96
106
  });
107
+ if (0 === blob.size) {
108
+ console.error('Video blob is empty');
109
+ message.error('Video export failed: Empty file.');
110
+ return;
111
+ }
97
112
  const url = URL.createObjectURL(blob);
98
113
  const a = document.createElement('a');
99
114
  a.href = url;
@@ -101,7 +116,7 @@ class RecordingSession {
101
116
  a.click();
102
117
  URL.revokeObjectURL(url);
103
118
  };
104
- null == (_this_mediaRecorder = this.mediaRecorder) || _this_mediaRecorder.stop();
119
+ this.mediaRecorder.stop();
105
120
  this.recording = false;
106
121
  this.mediaRecorder = null;
107
122
  }
@@ -453,9 +468,15 @@ function Player(props) {
453
468
  };
454
469
  const [isRecording, setIsRecording] = useState(false);
455
470
  const recorderSessionRef = useRef(null);
456
- const handleExport = ()=>{
471
+ const cancelAnimationRef = useRef(null);
472
+ const handleExport = async ()=>{
457
473
  if (recorderSessionRef.current) return void console.warn('recorderSession exists');
458
474
  if (!app.canvas) return void console.warn('canvas is not initialized');
475
+ if (cancelAnimationRef.current) {
476
+ cancelAnimationRef.current();
477
+ cancelAnimationRef.current = null;
478
+ await new Promise((resolve)=>setTimeout(resolve, 100));
479
+ }
459
480
  recorderSessionRef.current = new RecordingSession(app.canvas);
460
481
  setIsRecording(true);
461
482
  triggerReplay();
@@ -467,6 +488,7 @@ function Player(props) {
467
488
  if (!scripts) throw new Error("scripts is required");
468
489
  const { frame, cancel, timeout } = frameKit();
469
490
  cancelFn = cancel;
491
+ cancelAnimationRef.current = cancel;
470
492
  const allImages = scripts.filter((item)=>!!item.img).map((item)=>item.img);
471
493
  await Promise.all([
472
494
  ...allImages,
@@ -535,9 +557,20 @@ function Player(props) {
535
557
  }
536
558
  })().catch((e)=>{
537
559
  console.error('player error', e);
560
+ if (recorderSessionRef.current) {
561
+ try {
562
+ recorderSessionRef.current.stop();
563
+ } catch (stopError) {
564
+ console.error('Error stopping recorder:', stopError);
565
+ }
566
+ recorderSessionRef.current = null;
567
+ }
568
+ setIsRecording(false);
569
+ message.error('Failed to export video. Please try again.');
538
570
  }));
539
571
  return ()=>{
540
572
  null == cancelFn || cancelFn();
573
+ cancelAnimationRef.current = null;
541
574
  };
542
575
  };
543
576
  useEffect(()=>{
@@ -123,17 +123,32 @@ class RecordingSession {
123
123
  mediaRecorder.ondataavailable = (event)=>{
124
124
  if (event.data.size > 0) this.chunks.push(event.data);
125
125
  };
126
+ mediaRecorder.onerror = (event)=>{
127
+ console.error('MediaRecorder error:', event);
128
+ external_antd_namespaceObject.message.error('Video recording failed. Please try again.');
129
+ this.recording = false;
130
+ this.mediaRecorder = null;
131
+ };
126
132
  this.mediaRecorder = mediaRecorder;
127
133
  this.recording = true;
128
134
  return this.mediaRecorder.start();
129
135
  }
130
136
  stop() {
131
- var _this_mediaRecorder;
132
137
  if (!this.recording || !this.mediaRecorder) return void console.warn('not recording');
133
138
  this.mediaRecorder.onstop = ()=>{
139
+ if (0 === this.chunks.length) {
140
+ console.error('No video data captured');
141
+ external_antd_namespaceObject.message.error('Video export failed: No data captured.');
142
+ return;
143
+ }
134
144
  const blob = new Blob(this.chunks, {
135
145
  type: 'video/webm'
136
146
  });
147
+ if (0 === blob.size) {
148
+ console.error('Video blob is empty');
149
+ external_antd_namespaceObject.message.error('Video export failed: Empty file.');
150
+ return;
151
+ }
137
152
  const url = URL.createObjectURL(blob);
138
153
  const a = document.createElement('a');
139
154
  a.href = url;
@@ -141,7 +156,7 @@ class RecordingSession {
141
156
  a.click();
142
157
  URL.revokeObjectURL(url);
143
158
  };
144
- null == (_this_mediaRecorder = this.mediaRecorder) || _this_mediaRecorder.stop();
159
+ this.mediaRecorder.stop();
145
160
  this.recording = false;
146
161
  this.mediaRecorder = null;
147
162
  }
@@ -493,9 +508,15 @@ function Player(props) {
493
508
  };
494
509
  const [isRecording, setIsRecording] = (0, external_react_namespaceObject.useState)(false);
495
510
  const recorderSessionRef = (0, external_react_namespaceObject.useRef)(null);
496
- const handleExport = ()=>{
511
+ const cancelAnimationRef = (0, external_react_namespaceObject.useRef)(null);
512
+ const handleExport = async ()=>{
497
513
  if (recorderSessionRef.current) return void console.warn('recorderSession exists');
498
514
  if (!app.canvas) return void console.warn('canvas is not initialized');
515
+ if (cancelAnimationRef.current) {
516
+ cancelAnimationRef.current();
517
+ cancelAnimationRef.current = null;
518
+ await new Promise((resolve)=>setTimeout(resolve, 100));
519
+ }
499
520
  recorderSessionRef.current = new RecordingSession(app.canvas);
500
521
  setIsRecording(true);
501
522
  triggerReplay();
@@ -507,6 +528,7 @@ function Player(props) {
507
528
  if (!scripts) throw new Error("scripts is required");
508
529
  const { frame, cancel, timeout } = frameKit();
509
530
  cancelFn = cancel;
531
+ cancelAnimationRef.current = cancel;
510
532
  const allImages = scripts.filter((item)=>!!item.img).map((item)=>item.img);
511
533
  await Promise.all([
512
534
  ...allImages,
@@ -575,9 +597,20 @@ function Player(props) {
575
597
  }
576
598
  })().catch((e)=>{
577
599
  console.error('player error', e);
600
+ if (recorderSessionRef.current) {
601
+ try {
602
+ recorderSessionRef.current.stop();
603
+ } catch (stopError) {
604
+ console.error('Error stopping recorder:', stopError);
605
+ }
606
+ recorderSessionRef.current = null;
607
+ }
608
+ setIsRecording(false);
609
+ external_antd_namespaceObject.message.error('Failed to export video. Please try again.');
578
610
  }));
579
611
  return ()=>{
580
612
  null == cancelFn || cancelFn();
613
+ cancelAnimationRef.current = null;
581
614
  };
582
615
  };
583
616
  (0, external_react_namespaceObject.useEffect)(()=>{
@@ -8,7 +8,7 @@ export declare function usePlaygroundState(playgroundSDK: PlaygroundSDKLike | nu
8
8
  setLoading: import("react").Dispatch<import("react").SetStateAction<boolean>>;
9
9
  infoList: InfoListItem[];
10
10
  setInfoList: import("react").Dispatch<import("react").SetStateAction<InfoListItem[]>>;
11
- actionSpace: DeviceAction<unknown>[];
11
+ actionSpace: DeviceAction<unknown, any>[];
12
12
  actionSpaceLoading: boolean;
13
13
  uiContextPreview: UIContext | undefined;
14
14
  setUiContextPreview: import("react").Dispatch<import("react").SetStateAction<UIContext | undefined>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/visualizer",
3
- "version": "1.0.1-beta-20251029093754.0",
3
+ "version": "1.0.1-beta-20251103074550.0",
4
4
  "repository": "https://github.com/web-infra-dev/midscene",
5
5
  "homepage": "https://midscenejs.com/",
6
6
  "types": "./dist/types/index.d.ts",
@@ -60,10 +60,10 @@
60
60
  "antd": "^5.21.6",
61
61
  "buffer": "6.0.3",
62
62
  "dayjs": "^1.11.11",
63
- "@midscene/core": "1.0.1-beta-20251029093754.0",
64
- "@midscene/shared": "1.0.1-beta-20251029093754.0",
65
- "@midscene/web": "1.0.1-beta-20251029093754.0",
66
- "@midscene/playground": "1.0.1-beta-20251029093754.0"
63
+ "@midscene/core": "1.0.1-beta-20251103074550.0",
64
+ "@midscene/playground": "1.0.1-beta-20251103074550.0",
65
+ "@midscene/shared": "1.0.1-beta-20251103074550.0",
66
+ "@midscene/web": "1.0.1-beta-20251103074550.0"
67
67
  },
68
68
  "license": "MIT",
69
69
  "scripts": {