@netless/slide 1.4.55-alpha.1 → 1.4.55-alpha.3

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.
package/README.md CHANGED
@@ -2,9 +2,40 @@
2
2
 
3
3
  ## [示例及具体文档参考](https://github.com/netless-io/netless-slide-demo)
4
4
 
5
+ ## interactive 客户端的同步队列追最新策略
6
+
7
+ `@netless/slide` 默认按 FIFO 顺序执行所有接收到的同步事件。对于需要在弱网积压时优先追赶最新页面的 interactive 客户端,可以显式启用 `latest-pending-render`;可写端和只读端都可以使用:
8
+
9
+ ```ts
10
+ const slide = new Slide({
11
+ // 其他配置
12
+ syncEventQueuePolicy: "latest-pending-render",
13
+ });
14
+
15
+ slide.setSyncEventQueuePolicy("fifo");
16
+ slide.setSyncEventQueuePolicy("latest-pending-render");
17
+ ```
18
+
19
+ 该策略只处理尚未开始执行的 pending 事件:
20
+
21
+ - 新的 `renderSlide` 到达时,删除同一资源段内排在它之前的 pending 翻页、`nextStep`、`prevStep`、交互动画和媒体操作。
22
+ - 保留最新 `renderSlide`,以及它之后到达的所有操作,并继续按 FIFO 执行。
23
+ - `setResource` 和未知事件是硬边界,不会被删除,也不会跨越边界压缩。
24
+ - 已经开始执行的事件不会被取消;该策略不保证完全不显示 running 中间页。
25
+ - 动态切换策略本身不会清空或重排已有队列;切换后新到达的 `renderSlide` 才会触发压缩。
26
+ - latest 策略下开始执行远端 `renderSlide`,或远端事件形成 pending 积压后,Slide 会暂时阻止本地 `nextStep()`、`prevStep()`、`renderSlide()`、`doRenderSlide()` 和画布交互;同步接收、状态恢复和队列执行不受影响。本机事件优先通过现有 Lock 的 UUID 回包识别,并以 `clientId` 作为补充,不会锁住正在操作的当前控制者。
27
+ - 临时输入锁在同步队列重新空闲后解除,并恢复调用方最近一次通过 `setInteractive()` 设置的权限;不会把原本的只读端恢复成可写。
28
+
29
+ 该能力不会根据 `interactive` 自动开启。调用方应通过自己的灰度开关显式选择;关闭策略时可以恢复为 `fifo`,不会修改调用方原有的本地交互权限。
30
+
31
+ 该策略只在 Slide 的 `mode: "interactive"` 下生效;`sync`、`local` 和 `addLink` mode 会强制使用 `fifo`。这项 mode 保护不能替代调用方的只读权限与 feature flag 判断。
32
+
33
+ 实际发生压缩时,SDK 会通过现有 logger/tracker 记录丢弃分类、保留页码、目标事件接收时间、对应渲染完成时间和追赶输入锁状态;记录中不包含事件 UUID、资源 URL 或 token。
34
+
5
35
  ## changelog
6
36
 
7
37
  ### 未发布
38
+ * 增加 `latest-pending-render` 同步队列策略,弱网积压时丢弃最后翻页前尚未执行的中间页面操作,并在追赶期间阻止本地输入
8
39
  * 修复 PPT 图形内阴影(`innerShadow`)被错误渲染为不透明黑边的问题
9
40
  * 按转换数据中的颜色、模糊半径、距离和方向渲染柔化内阴影
10
41
  * 使用 SDF 判定形状内边缘,并扩展滤镜与 SDF 边界,避免阴影被裁剪
@@ -0,0 +1,13 @@
1
+ export interface FrozenMediaPlaybackState {
2
+ type: "pause" | "play";
3
+ time: number;
4
+ }
5
+ export declare type FrozenMediaTimes = {
6
+ [key: string]: number;
7
+ };
8
+ export declare function captureFrozenMediaTimes(mediaState: {
9
+ [key: string]: FrozenMediaPlaybackState;
10
+ }, frozenTime: number): FrozenMediaTimes;
11
+ export declare function applyFrozenMediaTimes(mediaState: {
12
+ [key: string]: FrozenMediaPlaybackState;
13
+ }, frozenMediaTimes: FrozenMediaTimes, releaseTime: number): void;
package/lib/Lock.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  export declare class Lock {
2
2
  private autoUnlock;
3
+ private localKeyAutoRemove;
4
+ private localKeys;
3
5
  private locks;
4
6
  private available;
5
7
  constructor(available: boolean);
6
8
  addLock(type: string, key: string): void;
7
- unlock(type: string, key: string | null | undefined): void;
9
+ unlock(type: string, key: string | null | undefined): boolean;
8
10
  isLocked(type: string): boolean;
9
11
  }
package/lib/Lock.js CHANGED
@@ -4,6 +4,8 @@
4
4
  var Lock = /** @class */ (function () {
5
5
  function Lock(available) {
6
6
  this.autoUnlock = Object.create(null);
7
+ this.localKeyAutoRemove = Object.create(null);
8
+ this.localKeys = Object.create(null);
7
9
  this.locks = Object.create(null);
8
10
  this.available = false;
9
11
  this.available = available;
@@ -14,18 +16,32 @@ var Lock = /** @class */ (function () {
14
16
  return;
15
17
  }
16
18
  this.locks[type] = key;
19
+ this.localKeys[key] = true;
20
+ this.localKeyAutoRemove[key] = window.setTimeout(function () {
21
+ delete _this.localKeys[key];
22
+ delete _this.localKeyAutoRemove[key];
23
+ }, 60 * 1000);
17
24
  this.autoUnlock[type] = window.setTimeout(function () {
18
25
  delete _this.locks[type];
26
+ delete _this.autoUnlock[type];
19
27
  }, 3000);
20
28
  };
21
29
  Lock.prototype.unlock = function (type, key) {
22
30
  if (!this.available) {
23
- return;
31
+ return false;
32
+ }
33
+ var isLocalEvent = !!key && !!this.localKeys[key];
34
+ if (key && isLocalEvent) {
35
+ window.clearTimeout(this.localKeyAutoRemove[key]);
36
+ delete this.localKeys[key];
37
+ delete this.localKeyAutoRemove[key];
24
38
  }
25
39
  if (key && this.locks[type] && this.locks[type] === key) {
26
40
  window.clearTimeout(this.autoUnlock[type]);
27
41
  delete this.locks[type];
42
+ delete this.autoUnlock[type];
28
43
  }
44
+ return isLocalEvent;
29
45
  };
30
46
  Lock.prototype.isLocked = function (type) {
31
47
  if (!this.available) {
package/lib/Slide.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { SlideError, VolumeAdjuster } from "@netless/ppt-player";
2
2
  import { IPlugin } from "@netless/plugin-system";
3
+ import type { SyncEventQueuePolicy } from "./SyncTaskManager";
3
4
  import { AliTrackLogger } from "./AliTrackLogger";
4
5
  import { preloadResource } from "./global";
5
6
  export { ErrorType, PreloadCancelledError } from "@netless/ppt-player";
6
- export declare const LOCAL_GIF_RUNTIME_CAPABILITY = "netless-slide-local-gif-production-client-geometry";
7
+ export type { SyncEventQueuePolicy } from "./SyncTaskManager";
7
8
  export declare type UrlInterrupter = (url: string) => Promise<string>;
8
9
  export interface ILoaderDelegate {
9
10
  /**
@@ -178,10 +179,6 @@ export interface ISlideRenderOptions {
178
179
  */
179
180
  transitionResolutionLevel?: number;
180
181
  antialias?: boolean;
181
- /** Maximum compressed bytes accepted for one client-decoded GIF. */
182
- localGifMaxCompressedBytes?: number;
183
- /** Maximum estimated GIF working set for one cached slide. */
184
- localGifMaxWorkingSetBytes?: number;
185
182
  }
186
183
  export interface INavigatorDelegate {
187
184
  gotoPage?: (index: number) => void;
@@ -213,6 +210,12 @@ export interface ISlideConfig {
213
210
  * "local" Local mode, no synchronization events will be triggered
214
211
  */
215
212
  mode: "interactive" | "sync" | "local" | "addLink";
213
+ /**
214
+ * Queue policy for received synchronization events.
215
+ * Default is "fifo". "latest-pending-render" lets interactive clients skip pending intermediate pages.
216
+ * Non-interactive modes always use "fifo".
217
+ */
218
+ syncEventQueuePolicy?: SyncEventQueuePolicy;
216
219
  /**
217
220
  * Whether to enable global clicking, after turning on, as long as you click within the ppt viewport range,
218
221
  * and do not trigger interactive elements inside ppt, it will trigger the next action.
@@ -281,13 +284,18 @@ export interface ISlideConfig {
281
284
  skipActionWhenFrozen?: boolean;
282
285
  enableAutoForward?: boolean;
283
286
  customLinks?: CustomLink[];
287
+ /**
288
+ * Disable the built-in ResizeObserver and rely on external notifyFrameResize() calls.
289
+ * Set this to true when using window-manager's boxSizeChange event as the resize signal.
290
+ * @default false
291
+ */
292
+ disableFrameResizeObserver?: boolean;
284
293
  resourceMaxRetries?: number;
285
294
  onResourceMaxRetries?: (url: string, error: Error) => void;
286
295
  }
287
296
  interface MediaState {
288
297
  type: "pause" | "play";
289
298
  time: number;
290
- frozenTime?: number;
291
299
  fullscreen?: boolean;
292
300
  }
293
301
  interface TimeNodeSeqState {
@@ -438,12 +446,14 @@ export declare class Slide extends Slide_base {
438
446
  private userInputTime;
439
447
  private isSyncingSlideState;
440
448
  private frozenTaskManager;
449
+ private frozenMediaTimes;
441
450
  private randomId;
442
451
  private resize;
443
452
  private isAnimating;
444
453
  private renderingTaskManager;
445
454
  private isLoading;
446
455
  private interactive;
456
+ private syncCatchUpInputBlocked;
447
457
  private anchor;
448
458
  private tracker;
449
459
  private player?;
@@ -459,6 +469,7 @@ export declare class Slide extends Slide_base {
459
469
  private enableGlobalClick;
460
470
  private lastEmitedState;
461
471
  private syncQueue;
472
+ private selfSyncEvents;
462
473
  private playerController;
463
474
  private lock;
464
475
  private isInitResized;
@@ -482,6 +493,10 @@ export declare class Slide extends Slide_base {
482
493
  private get dispatchIncrId();
483
494
  constructor(initConfig: ISlideConfig);
484
495
  private _updateVolumeByStaticAdjuster;
496
+ private handleSyncQueueCompact;
497
+ private handleSyncQueueCompactRenderComplete;
498
+ private handleSyncQueueCatchUpStateChange;
499
+ private updatePlayerInteractive;
485
500
  private recoverHandler;
486
501
  private reportError;
487
502
  initSlideConfig(config: ISlideConfig): ISlideConfig;
@@ -500,6 +515,12 @@ export declare class Slide extends Slide_base {
500
515
  private updateStageCountLimit;
501
516
  private setMedianControllerAttribute;
502
517
  private frameResizeHandler;
518
+ /**
519
+ * Notify the slide that its frame container may have changed size.
520
+ * Call this when using an external resize signal (e.g. window-manager's boxSizeChange event)
521
+ * instead of the built-in ResizeObserver.
522
+ */
523
+ notifyFrameResize(): void;
503
524
  /**
504
525
  * Update ppt fixed height and width, this method only takes effect when the fixedFrameSize configuration item is set.
505
526
  * @param width Width value that needs to be updated, unit px.
@@ -622,6 +643,7 @@ export declare class Slide extends Slide_base {
622
643
  * @param isForward Whether to flip forward or backward, affecting the page transition animation.
623
644
  */
624
645
  doRenderSlide(index: number, isForward?: boolean): Promise<void>;
646
+ private enqueueRenderSlide;
625
647
  getSnapshot(): string | null;
626
648
  /**
627
649
  * 执行下一个主序列动画
@@ -647,6 +669,11 @@ export declare class Slide extends Slide_base {
647
669
  * @param val boolean
648
670
  */
649
671
  setInteractive(val: boolean): void;
672
+ /**
673
+ * Set how received synchronization events are scheduled.
674
+ * Changing the policy does not reorder or clear the current pending queue by itself.
675
+ */
676
+ setSyncEventQueuePolicy(policy: SyncEventQueuePolicy): void;
650
677
  /**
651
678
  * Pause playback, asynchronous operation.
652
679
  */