wallpaper-engine-dsh 2.1.1 → 2.1.2

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
@@ -73,6 +73,7 @@ Media serving is defensive by design: tokens are unguessable 72-bit values, stab
73
73
  - Scene and application wallpapers are not listed: they are Wallpaper Engine's private `.pkg` scene packages, and their live animation can only be rendered by Wallpaper Engine itself on the desktop.
74
74
  - Web wallpapers run isolated from the page's storage, so ones that persist state across loads won't keep it.
75
75
  - Web wallpapers with sound may stay silent until you interact with the page — browsers gate audio in sandboxed documents on a user gesture, and wallpapers that rely on Wallpaper Engine's system-audio feed (visualizers) have no signal to draw from.
76
+ - Video codecs are bounded by what the browser can decode. H.264/AVC, VP8/VP9, and AV1 play natively; workshop videos compressed with H.265/HEVC cannot be software-decoded on most machines — previously a solid black screen, now detected as "playing but zero decoded frames" and automatically demoted to the preview still with a status note. Transcoding to H.264 mp4 or VP9 webm restores full playback.
76
77
  - The glass effect reads DSH design tokens; if a future shell redesign renames them, frosting degrades to plain transparency.
77
78
 
78
79
  ## Development
package/README.zh.md CHANGED
@@ -73,6 +73,7 @@ bundle 分为两半:
73
73
  - 场景与应用壁纸不出现在列表中:它们是 Wallpaper Engine 私有的 `.pkg` 场景包,动态渲染只能由桌面端的 Wallpaper Engine 完成。
74
74
  - 网页壁纸与页面存储隔离,需要跨加载保存状态的壁纸无法保留状态。
75
75
  - 带声音的网页壁纸可能在你与页面交互前保持静默——浏览器对沙箱文档的音频有自动播放限制;依赖 Wallpaper Engine 系统音频输入的壁纸(如音频可视化)在浏览器中没有信号可画。
76
+ - 视频编码受浏览器解码能力约束。浏览器可以直接播放 H.264/AVC、VP8/VP9 与 AV1;创意工坊中用 H.265/HEVC 压缩的视频在多数机器上无法软解——之前表现为只有一片黑色,现在检测到「声称在播放但解不出任何帧」时会自动降级为预览图并在状态行说明。手工转码为 H.264 的 mp4 或 VP9 的 webm 即可完整播放。
76
77
  - 玻璃效果读 DSH 设计令牌;若未来外壳重构重命名令牌,磨砂会退化为普通半透明。
77
78
 
78
79
  ## 开发
package/lib/client.js CHANGED
@@ -729,32 +729,64 @@ window.__ModuleLoader__.load({
729
729
  });
730
730
  }
731
731
 
732
+ function handleMediaFailure(sel) {
733
+ const id = sel.id;
734
+ const count = (mediaFailures.get(id) || 0) + 1;
735
+ mediaFailures.set(id, count);
736
+ const w = selection.inventory.wallpapers.find((x) => x.id === id);
737
+ if (count === 1) {
738
+ // Maybe just a dead token: refetch fresh URLs and remount. Throttled
739
+ // PER WALLPAPER - a global window would strand a video whose first
740
+ // error lands soon after an unrelated recovery (no remount, no second
741
+ // error, blank layer until a manual refresh).
742
+ const now = Date.now();
743
+ if (now - (lastRecoveryAt.get(id) || 0) < 10000) return;
744
+ lastRecoveryAt.set(id, now);
745
+ forceRemount = true;
746
+ loadInventory(true);
747
+ } else if (w && w.preview && !demotedToPreview.has(id)) {
748
+ // The media itself is unusable here - fall back to the still preview.
749
+ demotedToPreview.add(id);
750
+ applySelection(id);
751
+ } else {
752
+ // Unrenderable here with no preview to fall back on (or the entry has
753
+ // vanished from the inventory entirely): clear the selection rather
754
+ // than leave a dead element on the layer.
755
+ applySelection("");
756
+ }
757
+ }
758
+
732
759
  function attachMediaError(media, sel) {
733
- media.addEventListener("error", () => {
734
- const id = sel.id;
735
- const count = (mediaFailures.get(id) || 0) + 1;
736
- mediaFailures.set(id, count);
737
- const w = selection.inventory.wallpapers.find((x) => x.id === id);
738
- if (count === 1) {
739
- // Maybe just a dead token: refetch fresh URLs and remount. Throttled
740
- // PER WALLPAPER — a global window would strand a video whose first
741
- // error lands soon after an unrelated recovery (no remount, no second
742
- // error, blank layer until a manual refresh).
743
- const now = Date.now();
744
- if (now - (lastRecoveryAt.get(id) || 0) < 10000) return;
745
- lastRecoveryAt.set(id, now);
746
- forceRemount = true;
747
- loadInventory(true);
748
- } else if (w && w.preview && !demotedToPreview.has(id)) {
749
- // The media itself is unusable here — fall back to the still preview.
750
- demotedToPreview.add(id);
751
- applySelection(id);
752
- } else {
753
- // Unrenderable here with no preview to fall back on (or the entry has
754
- // vanished from the inventory entirely): clear the selection rather
755
- // than leave a dead element on the layer.
756
- applySelection("");
757
- }
760
+ media.addEventListener("error", () => handleMediaFailure(sel));
761
+ if (sel.type === "video") attachZeroFrameDetector(media, sel);
762
+ }
763
+
764
+ // Zero-decoded-frames detector: Chromium can report a video as PLAYING
765
+ // without ever decoding a frame - no error event, currentTime advancing,
766
+ // picture permanently black. Typical with HEVC/H.265 content when the
767
+ // machine has no usable hardware decode. getVideoPlaybackQuality exposes
768
+ // the truth (totalVideoFrames stays 0); a genuinely dark wallpaper is
769
+ // unaffected because its frames DO decode. Wired into the same recovery
770
+ // ladder as a media error: first hit retries with fresh tokens, second
771
+ // hit demotes to the preview still (or clears without one).
772
+ function attachZeroFrameDetector(media, sel) {
773
+ media.addEventListener("playing", () => {
774
+ // One probe ~2s into claimed playback: enough for even a slow
775
+ // hardware pipeline to produce its first frames.
776
+ schedule(() => {
777
+ // Stale guard: crossfade may have swapped the element out already,
778
+ // and the probe must never run while the user paused playback.
779
+ const layer = document.getElementById(LAYER_ID);
780
+ if (!layer || layer.querySelector("video") !== media) return;
781
+ if (media.paused || !(media.currentTime > 0)) return;
782
+ const q = typeof media.getVideoPlaybackQuality === "function"
783
+ ? media.getVideoPlaybackQuality() : null;
784
+ const frames = q ? q.totalVideoFrames
785
+ : (typeof media.webkitDecodedFrameCount === "number"
786
+ ? media.webkitDecodedFrameCount : null);
787
+ if (frames === null) return; // no probe available -> cannot detect
788
+ if (frames === 0) handleMediaFailure(sel);
789
+ }, 2000);
758
790
  });
759
791
  }
760
792
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wallpaper-engine-dsh",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "description": "Wallpaper Engine library as the DSH web GUI background — video/web/still wallpapers, rotation lists, search, resource monitor, bilingual UI.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",