@netless/window-manager 1.0.0-canary.13 → 1.0.0-canary.16

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.
@@ -1,5 +1,5 @@
1
1
  import { AnimationMode } from "white-web-sdk";
2
- import { debounce, delay, throttle } from "lodash";
2
+ import { debounce, delay, isEqual, pick, throttle } from "lodash";
3
3
  import type { TeleBoxRect } from "@netless/telebox-insider";
4
4
  import type { Camera, View } from "white-web-sdk";
5
5
  import type { ISize } from "../AttributesDelegate";
@@ -37,7 +37,6 @@ export class CameraSynchronizer {
37
37
  scale = this.rect.height / size.height;
38
38
  }
39
39
  const nextScale = camera.scale * scale;
40
-
41
40
  const moveCamera = () => {
42
41
  this.view?.moveCamera({
43
42
  centerX: camera.centerX,
@@ -46,26 +45,29 @@ export class CameraSynchronizer {
46
45
  animationMode: AnimationMode.Immediately,
47
46
  });
48
47
  }
49
- moveCamera();
48
+ moveCamera();
50
49
  // TODO 直接调用 moveCamera 依然会出现 camera 错误的情况,这里暂时加一个 delay 保证 camera 是对的, 后续需要 SDK 进行修改
51
50
  delay(moveCamera, 50);
52
51
  }
53
52
  }, 10);
54
53
 
55
54
  public onRemoteSizeUpdate(size: ISize) {
56
- if (this.rect) {
57
- const nextScale = this.rect.width / size.width;
55
+ this.remoteSize = size;
56
+ const needMoveCamera = !isEqual(pick(this.rect, ["width", "height"]), pick(size, ["width", "height"]));
57
+ if (this.rect && this.remoteCamera && needMoveCamera) {
58
+ const scale = this.rect.width / size.width;
59
+ const nextScale = this.remoteCamera.scale * scale;
58
60
  const moveCamera = () => {
59
61
  this.view?.moveCamera({
60
62
  scale: nextScale,
61
63
  animationMode: AnimationMode.Immediately,
62
64
  })
63
65
  };
66
+ moveCamera();
64
67
  delay(moveCamera, 50);
65
68
  }
66
69
  }
67
70
 
68
-
69
71
  public onLocalCameraUpdate(camera: Camera) {
70
72
  this.saveCamera(camera);
71
73
  this.remoteCamera = camera;
@@ -1,14 +1,16 @@
1
1
  import { callbacks } from "../callback";
2
- import { CameraSynchronizer } from "./CameraSynchronizer";
3
2
  import { createView } from "./ViewManager";
4
3
  import { debounce, get, isEqual } from "lodash";
5
4
  import { emitter } from "../InternalEmitter";
6
5
  import { Events } from "../constants";
7
6
  import { Fields } from "../AttributesDelegate";
8
- import { reaction } from "white-web-sdk";
7
+ import { reaction, toJS } from "white-web-sdk";
9
8
  import { releaseView, setViewFocusScenePath } from "../Utils/Common";
10
9
  import { SideEffectManager } from "side-effect-manager";
11
- import type { Camera, Size, View } from "white-web-sdk";
10
+ import { Val } from "value-enhancer";
11
+ import { ViewSync } from "./ViewSync";
12
+ import type { ICamera, ISize } from "../AttributesDelegate";
13
+ import type { Size, View } from "white-web-sdk";
12
14
  import type { AppManager } from "../AppManager";
13
15
 
14
16
  export class MainViewProxy {
@@ -16,16 +18,17 @@ export class MainViewProxy {
16
18
  private mainViewIsAddListener = false;
17
19
  private mainView: View;
18
20
  private store = this.manager.store;
19
- private synchronizer: CameraSynchronizer;
20
21
 
21
22
  private sideEffectManager = new SideEffectManager();
22
23
 
24
+ public camera$ = new Val<ICamera | undefined>(undefined);
25
+ public size$ = new Val<ISize | undefined>(undefined);
26
+ public view$ = new Val<View | undefined>(undefined);
27
+
28
+ public viewSync?: ViewSync;
29
+
23
30
  constructor(private manager: AppManager) {
24
- this.synchronizer = new CameraSynchronizer(camera =>
25
- this.store.setMainViewCamera({ ...camera, id: this.manager.uid })
26
- );
27
31
  this.mainView = this.createMainView();
28
- this.moveCameraSizeByAttributes();
29
32
  emitter.once("mainViewMounted").then(() => {
30
33
  this.addMainViewListener();
31
34
  this.start();
@@ -33,20 +36,28 @@ export class MainViewProxy {
33
36
  this.startListenWritableChange();
34
37
  });
35
38
  this.sideEffectManager.add(() => [
36
- emitter.on("containerSizeRatioUpdate", this.onUpdateContainerSizeRatio),
37
39
  emitter.on("startReconnect", () => {
38
40
  releaseView(this.mainView);
39
41
  }),
40
- emitter.on("playgroundSizeChange", rect => {
41
- this.synchronizer.setRect(rect);
42
- })
43
42
  ]);
44
- const rect = this.manager.boxManager?.stageRect;
45
- if (rect) {
46
- this.synchronizer.setRect(rect);
47
- }
43
+ this.createViewSync();
48
44
  }
49
45
 
46
+ public createViewSync = () => {
47
+ if (this.manager.boxManager && !this.viewSync) {
48
+ this.viewSync = new ViewSync({
49
+ uid: this.manager.uid,
50
+ view$: this.view$,
51
+ camera$: this.camera$,
52
+ size$: this.size$,
53
+ stageRect$: this.manager.boxManager?.stageRect$,
54
+ viewMode$: this.manager.windowManger.viewMode$,
55
+ storeCamera: this.storeCamera,
56
+ storeSize: this.storeSize,
57
+ });
58
+ }
59
+ };
60
+
50
61
  private startListenWritableChange = () => {
51
62
  this.sideEffectManager.add(() =>
52
63
  emitter.on("writableChange", isWritable => {
@@ -60,7 +71,18 @@ export class MainViewProxy {
60
71
  public ensureCameraAndSize() {
61
72
  if (!this.mainViewCamera || !this.mainViewSize) {
62
73
  this.manager.dispatchInternalEvent(Events.InitMainViewCamera);
63
- this.setCameraAndSize();
74
+ this.storeCamera({
75
+ id: this.manager.uid,
76
+ ...this.view.camera
77
+ });
78
+ const stageRect = this.manager.boxManager?.stageRect;
79
+ if (stageRect && !this.mainViewSize) {
80
+ this.storeSize({
81
+ id: this.manager.uid,
82
+ width: stageRect.width,
83
+ height: stageRect.height
84
+ });
85
+ }
64
86
  }
65
87
  }
66
88
 
@@ -76,10 +98,6 @@ export class MainViewProxy {
76
98
  return get(this.view, ["didRelease"]);
77
99
  }
78
100
 
79
- private moveCameraSizeByAttributes() {
80
- this.synchronizer.onRemoteUpdate(this.mainViewCamera, this.mainViewSize);
81
- }
82
-
83
101
  public start() {
84
102
  if (this.started) return;
85
103
  this.addCameraListener();
@@ -89,34 +107,64 @@ export class MainViewProxy {
89
107
 
90
108
  public addCameraReaction = () => {
91
109
  this.manager.refresher.add(Fields.MainViewCamera, this.cameraReaction);
110
+ this.manager.refresher.add(Fields.MainViewSize, this.sizeReaction);
92
111
  };
93
112
 
94
- public setCameraAndSize(): void {
95
- const stageSize = this.getStageSize();
96
- if (stageSize) {
97
- const camera = { ...this.mainView.camera, id: this.manager.uid };
98
- const size = { ...stageSize, id: this.manager.uid };
99
- this.store.setMainViewCameraAndSize(camera, size);
113
+ public storeCurrentCamera = () => {
114
+ const iCamera = this.view.camera;
115
+ this.storeCamera({
116
+ id: this.manager.uid,
117
+ ...iCamera
118
+ });
119
+ }
120
+
121
+ public storeCurrentSize = () => {
122
+ const rect = this.manager.boxManager?.stageRect;
123
+ if (rect) {
124
+ this.storeSize({
125
+ id: this.manager.uid,
126
+ width: rect.width,
127
+ height: rect.height
128
+ });
100
129
  }
101
130
  }
102
131
 
132
+ public storeCamera = (camera: ICamera) => {
133
+ this.store.setMainViewCamera(camera);
134
+ };
135
+
136
+ public storeSize = (size: ISize) => {
137
+ this.store.setMainViewSize(size);
138
+ };
139
+
103
140
  private cameraReaction = () => {
104
141
  return reaction(
105
142
  () => this.mainViewCamera,
106
143
  camera => {
107
- if (camera && camera.id !== this.manager.uid) {
108
- this.synchronizer.onRemoteUpdate(camera, this.mainViewSize);
144
+ if (camera) {
145
+ const rawCamera = toJS(camera);
146
+ if (!isEqual(rawCamera, this.camera$.value)) {
147
+ this.camera$.setValue(rawCamera);
148
+ }
109
149
  }
110
150
  },
111
151
  { fireImmediately: true }
112
152
  );
113
153
  };
114
154
 
115
- public onUpdateContainerSizeRatio = () => {
116
- const size = this.store.getMainViewSize();
117
- if (size.id === this.manager.uid) {
118
- this.setCameraAndSize();
119
- }
155
+ private sizeReaction = () => {
156
+ return reaction(
157
+ () => this.mainViewSize,
158
+ size => {
159
+ if (size) {
160
+ const rawSize = toJS(size);
161
+ if (!isEqual(rawSize, this.size$.value)) {
162
+ this.size$.setValue(rawSize);
163
+ }
164
+ }
165
+ },
166
+ { fireImmediately: true }
167
+ );
120
168
  };
121
169
 
122
170
  public get view(): View {
@@ -133,7 +181,7 @@ export class MainViewProxy {
133
181
  if (mainViewScenePath) {
134
182
  setViewFocusScenePath(mainView, mainViewScenePath);
135
183
  }
136
- this.synchronizer.setView(mainView);
184
+ this.view$.setValue(mainView);
137
185
  return mainView;
138
186
  }
139
187
 
@@ -165,21 +213,6 @@ export class MainViewProxy {
165
213
  this.start();
166
214
  }
167
215
 
168
- private onCameraUpdatedByDevice = (camera: Camera) => {
169
- this.synchronizer.onLocalCameraUpdate(camera);
170
- const size = this.getStageSize();
171
- if (size && !isEqual(size, this.mainViewSize)) {
172
- this.setMainViewSize(size);
173
- }
174
- };
175
-
176
- private getStageSize(): Size | undefined {
177
- const stage = this.manager.boxManager?.stageRect;
178
- if (stage) {
179
- return { width: stage.width, height: stage.height };
180
- }
181
- }
182
-
183
216
  public addMainViewListener(): void {
184
217
  if (this.mainViewIsAddListener) return;
185
218
  if (this.view.divElement) {
@@ -212,13 +245,11 @@ export class MainViewProxy {
212
245
  }, 50);
213
246
 
214
247
  private addCameraListener() {
215
- this.view.callbacks.on("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
216
248
  this.view.callbacks.on("onCameraUpdated", this.onCameraOrSizeUpdated);
217
249
  this.view.callbacks.on("onSizeUpdated", this.onCameraOrSizeUpdated);
218
250
  }
219
251
 
220
252
  private removeCameraListener() {
221
- this.view.callbacks.off("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
222
253
  this.view.callbacks.off("onCameraUpdated", this.onCameraOrSizeUpdated);
223
254
  this.view.callbacks.off("onSizeUpdated", this.onCameraOrSizeUpdated);
224
255
  }
@@ -235,6 +266,9 @@ export class MainViewProxy {
235
266
  }
236
267
 
237
268
  public destroy() {
269
+ this.camera$.destroy();
270
+ this.size$.destroy();
271
+ this.view$.destroy();
238
272
  this.removeMainViewListener();
239
273
  this.stop();
240
274
  this.sideEffectManager.flushAll();
@@ -0,0 +1,121 @@
1
+ import { AnimationMode, ViewMode } from "white-web-sdk";
2
+ import { CameraSynchronizer } from "./CameraSynchronizer";
3
+ import { combine } from "value-enhancer";
4
+ import { isEqual } from "lodash";
5
+ import { SideEffectManager } from "side-effect-manager";
6
+ import type { Camera, View } from "white-web-sdk";
7
+ import type { Val, ReadonlyVal } from "value-enhancer";
8
+ import type { ICamera, ISize } from "../AttributesDelegate";
9
+ import type { TeleBoxRect } from "@netless/telebox-insider";
10
+
11
+ export type ViewSyncContext = {
12
+ uid: string;
13
+ // 远端 camera
14
+ camera$: Val<ICamera | undefined, boolean>;
15
+ // 远端 size
16
+ size$: Val<ISize | undefined>;
17
+
18
+ stageRect$: ReadonlyVal<TeleBoxRect>;
19
+
20
+ viewMode$?: Val<ViewMode>;
21
+
22
+ storeCamera: (camera: ICamera) => void;
23
+
24
+ storeSize: (size: ISize) => void;
25
+
26
+ view$: Val<View | undefined>;
27
+ };
28
+
29
+ export class ViewSync {
30
+ private sem = new SideEffectManager();
31
+ private synchronizer: CameraSynchronizer;
32
+
33
+ constructor(private context: ViewSyncContext) {
34
+ this.synchronizer = new CameraSynchronizer((camera: Camera) => {
35
+ const iCamera = {
36
+ id: this.context.uid,
37
+ ...camera,
38
+ }
39
+ this.context.camera$.setValue(iCamera, true);
40
+ const notStoreCamera = this.context.viewMode$ && this.context.viewMode$.value === ViewMode.Freedom;
41
+ if (notStoreCamera) {
42
+ return;
43
+ } else {
44
+ this.context.storeCamera(iCamera);
45
+ }
46
+ });
47
+ this.bindView(this.context.view$.value);
48
+ this.sem.add(() =>
49
+ this.context.view$.subscribe(view => {
50
+ const currentCamera = this.context.camera$.value;
51
+ if (currentCamera && this.context.size$.value) {
52
+ view?.moveCamera({
53
+ scale: 1,
54
+ animationMode: AnimationMode.Immediately,
55
+ });
56
+ this.synchronizer.onRemoteUpdate(currentCamera, this.context.size$.value);
57
+ }
58
+
59
+ this.bindView(view);
60
+ })
61
+ );
62
+ this.sem.add(() =>
63
+ this.context.camera$.subscribe((camera, skipUpdate) => {
64
+ const size = this.context.size$.value;
65
+ if (camera && size && !skipUpdate) {
66
+ this.synchronizer.onRemoteUpdate(camera, size);
67
+ }
68
+ })
69
+ );
70
+ this.sem.add(() =>
71
+ this.context.size$.subscribe(size => {
72
+ if (size) {
73
+ this.synchronizer.onRemoteSizeUpdate(size);
74
+ }
75
+ })
76
+ );
77
+ if (this.context.stageRect$.value) {
78
+ this.synchronizer.setRect(this.context.stageRect$.value);
79
+ this.sem.add(() =>
80
+ this.context.stageRect$.subscribe(rect => {
81
+ if (rect) {
82
+ this.synchronizer.setRect(rect);
83
+ }
84
+ })
85
+ );
86
+ }
87
+ const camera$size$ = combine([this.context.camera$, this.context.size$]);
88
+ camera$size$.subscribe(([camera, size]) => {
89
+ if (camera && size) {
90
+ this.synchronizer.onRemoteUpdate(camera, size);
91
+ camera$size$.destroy();
92
+ }
93
+ });
94
+ }
95
+
96
+ public bindView = (view?: View) => {
97
+ if (!view) return;
98
+ this.synchronizer.setView(view);
99
+ this.sem.flush("view");
100
+ this.sem.add(() => {
101
+ view.callbacks.on("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
102
+ return () =>
103
+ view.callbacks.off("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
104
+ }, "view");
105
+ };
106
+
107
+ private onCameraUpdatedByDevice = (camera: Camera) => {
108
+ this.synchronizer.onLocalCameraUpdate(camera);
109
+ const stage = this.context.stageRect$.value;
110
+ if (stage) {
111
+ const size = { width: stage.width, height: stage.height, id: this.context.uid };
112
+ if (!isEqual(size, this.context.size$.value)) {
113
+ this.context.storeSize(size);
114
+ }
115
+ }
116
+ };
117
+
118
+ public destroy() {
119
+ this.sem.flushAll();
120
+ }
121
+ }
package/src/index.ts CHANGED
@@ -54,6 +54,7 @@ import type { PublicEvent } from "./callback";
54
54
  import type Emittery from "emittery";
55
55
  import type { PageController, AddPageParams, PageState } from "./Page";
56
56
  import { boxEmitter } from "./BoxEmitter";
57
+ import { Val } from "value-enhancer";
57
58
 
58
59
  export type WindowMangerAttributes = {
59
60
  modelValue?: string;
@@ -162,6 +163,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
162
163
  public appManager?: AppManager;
163
164
  public cursorManager?: CursorManager;
164
165
  public viewMode = ViewMode.Broadcaster;
166
+ public viewMode$ = new Val<ViewMode>(ViewMode.Broadcaster);
165
167
  public isReplay = isPlayer(this.displayer);
166
168
  private _pageState?: PageStateImpl;
167
169
 
@@ -592,16 +594,19 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
592
594
  * 设置 ViewMode
593
595
  */
594
596
  public setViewMode(mode: ViewMode): void {
597
+ log("setViewMode", mode);
598
+ const mainViewProxy = this.appManager?.mainViewProxy;
595
599
  if (mode === ViewMode.Broadcaster) {
596
600
  if (this.canOperate) {
597
- this.appManager?.mainViewProxy.setCameraAndSize();
601
+ mainViewProxy?.storeCurrentCamera();
598
602
  }
599
- this.appManager?.mainViewProxy.start();
603
+ mainViewProxy?.start();
600
604
  }
601
605
  if (mode === ViewMode.Freedom) {
602
- this.appManager?.mainViewProxy.stop();
606
+ mainViewProxy?.stop();
603
607
  }
604
608
  this.viewMode = mode;
609
+ this.viewMode$.setValue(mode);
605
610
  }
606
611
 
607
612
  public setBoxState(boxState: TeleBoxState): void {
@@ -764,7 +769,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
764
769
  this.mainView.moveCamera(camera);
765
770
  this.appManager?.dispatchInternalEvent(Events.MoveCamera, camera);
766
771
  setTimeout(() => {
767
- this.appManager?.mainViewProxy.setCameraAndSize();
772
+ this.appManager?.mainViewProxy.storeCurrentCamera();
768
773
  }, 500);
769
774
  }
770
775
 
@@ -777,7 +782,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
777
782
  this.mainView.moveCameraToContain(rectangle);
778
783
  this.appManager?.dispatchInternalEvent(Events.MoveCameraToContain, rectangle);
779
784
  setTimeout(() => {
780
- this.appManager?.mainViewProxy.setCameraAndSize();
785
+ this.appManager?.mainViewProxy.storeCurrentCamera();
781
786
  }, 500);
782
787
  }
783
788
 
package/src/style.css CHANGED
@@ -180,7 +180,6 @@
180
180
  }
181
181
 
182
182
  .window-manager-view-wrapper {
183
- z-index: 5000;
184
183
  width: 100%;
185
184
  height: 100%;
186
185
  position: absolute;
@@ -1,11 +0,0 @@
1
- import type { View } from "white-web-sdk";
2
- import type { AppProxy } from "./AppProxy";
3
- export declare class AppViewSync {
4
- private appProxy;
5
- private sem;
6
- private synchronizer;
7
- constructor(appProxy: AppProxy);
8
- bindView: (view?: View | undefined) => void;
9
- private onCameraUpdatedByDevice;
10
- destroy(): void;
11
- }
@@ -1,73 +0,0 @@
1
- import { CameraSynchronizer } from "../View/CameraSynchronizer";
2
- import { SideEffectManager } from "side-effect-manager";
3
- import type { Camera, View } from "white-web-sdk";
4
- import type { AppProxy } from "./AppProxy";
5
- import { isEqual } from "lodash";
6
- import { combine } from "value-enhancer";
7
-
8
- export class AppViewSync {
9
- private sem = new SideEffectManager();
10
- private synchronizer: CameraSynchronizer;
11
-
12
- constructor(private appProxy: AppProxy) {
13
- this.synchronizer = new CameraSynchronizer((camera: Camera) => {
14
- this.appProxy.storeCamera({
15
- id: this.appProxy.uid,
16
- ...camera,
17
- });
18
- });
19
- this.bindView(appProxy.view);
20
- this.sem.add(() => this.appProxy.camera$.subscribe(camera => {
21
- const size = this.appProxy.size$.value;
22
- if (camera && size) {
23
- this.synchronizer.onRemoteUpdate(camera, size);
24
- }
25
- }));
26
- this.sem.add(() => this.appProxy.size$.subscribe(size => {
27
- if (size) {
28
- this.synchronizer.onRemoteSizeUpdate(size);
29
- }
30
- }));
31
- const box = this.appProxy.box;
32
- if (box && box.contentStageRect) {
33
- this.synchronizer.setRect(box.contentStageRect);
34
- this.sem.add(() =>
35
- box._contentStageRect$.subscribe(rect => {
36
- if (rect) {
37
- this.synchronizer.setRect(rect);
38
- }
39
- }),
40
- );
41
- }
42
- this.sem.add(() => combine([this.appProxy.camera$, this.appProxy.size$]).subscribe(([camera, size]) => {
43
- if (camera && size) {
44
- this.synchronizer.onRemoteUpdate(camera, size);
45
- }
46
- }));
47
- }
48
-
49
- public bindView = (view?: View) => {
50
- if (!view) return;
51
- this.synchronizer.setView(view);
52
- this.sem.add(() => {
53
- view.callbacks.on("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
54
- return () =>
55
- view.callbacks.off("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
56
- });
57
- };
58
-
59
- private onCameraUpdatedByDevice = (camera: Camera) => {
60
- this.synchronizer.onLocalCameraUpdate(camera);
61
- const stage = this.appProxy.box?.contentStageRect;
62
- if (stage) {
63
- const size = { width: stage.width, height: stage.height, id: this.appProxy.uid };
64
- if (!isEqual(size, this.appProxy.size$.value)) {
65
- this.appProxy.storeSize(size);
66
- }
67
- }
68
- };
69
-
70
- public destroy() {
71
- this.sem.flushAll();
72
- }
73
- }