dsh-music-player 0.1.1 → 0.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
@@ -9,9 +9,18 @@ DeepSeek Harness 本地音乐库播放器插件(bundle)。
9
9
  - 本地音频流式播放,支持 seek / 断点续播
10
10
  - 顺序播放、单曲循环、乱序播放三种模式
11
11
  - 实时 7 段频谱可视化(解码音频包络驱动)
12
+ - 播放列表面板可自由拖动,位置跨刷新记忆
12
13
  - `music_play` 模型工具:agent 可按关键词让浏览器播放
13
14
  - 支持的格式:`mp3 / m4a / m4b / aac / flac / wav / ogg / opus / webm / aiff`(自动递归扫描子目录,上限 500 首)
14
15
 
16
+ ## 截图
17
+
18
+ ![播放条](assets/screenshot-bar.png)
19
+
20
+ ![播放面板](assets/screenshot-panel.png)
21
+
22
+ ![实时频谱](assets/screenshot-spectrum.png)
23
+
15
24
  ## 安装
16
25
 
17
26
  需要已安装 `dsh` CLI。
package/lib/client.js CHANGED
@@ -28,9 +28,21 @@ window.__ModuleLoader__.load({
28
28
  const PREF_VOL = 'dsh-music-volume';
29
29
  const PREF_PLAYBACK = 'dsh-music-playback';
30
30
  const PREF_ROOT = 'dsh-music-root';
31
+ const PREF_PANEL_POS = 'dsh-music-panel-pos';
31
32
  const loadPref = (k) => { try { return localStorage.getItem(k); } catch (e) { return null; } };
32
33
  const savePref = (k, v) => { try { localStorage.setItem(k, v); } catch (e) {} };
33
34
  const clearPref = (k) => { try { localStorage.removeItem(k); } catch (e) {} };
35
+ // Restore the playback-panel position ({x,y,h}) previously saved by dragging, if any.
36
+ function loadPanelPos() {
37
+ const raw = loadPref(PREF_PANEL_POS);
38
+ if (raw === null) return null;
39
+ try {
40
+ const p = JSON.parse(raw);
41
+ if (p && typeof p.x === 'number' && typeof p.y === 'number'
42
+ && typeof p.h === 'number' && p.h > 0) return p;
43
+ } catch (e) {}
44
+ return null;
45
+ }
34
46
  const jsonGet = (url) => fetch(url, { cache: 'no-store' }).then((r) => r.json());
35
47
 
36
48
  // ---- engine + shared store (React re-renders on set) ----
@@ -627,6 +639,52 @@ window.__ModuleLoader__.load({
627
639
  const s = useStore();
628
640
  const listRef = useRef(null);
629
641
  const panelRef = useRef(null);
642
+ // Draggable panel position ({x, y, h} left/top/height once dragged; null = default right/bottom)
643
+ const [pos, setPos] = useState(loadPanelPos);
644
+ const dragRef = useRef(null);
645
+
646
+ // Once the panel is dragged we switch from CSS right/bottom anchoring to an
647
+ // explicit left/top/height. Locking the height matters: with only top+left set
648
+ // and no height, a fixed element whose CSS also sets bottom collapses to fit
649
+ // the leftover space, so the panel's height jumps while dragging.
650
+ const style = pos === null ? null : { left: pos.x, top: pos.y, height: pos.h };
651
+
652
+ const onHeadDown = (e) => {
653
+ if (e.button !== undefined && e.button !== 0) return;
654
+ // don't start a drag from the close button
655
+ if (e.target.closest && e.target.closest('.dsh-music-icon-btn')) return;
656
+ const el = panelRef.current;
657
+ if (el === null) return;
658
+ const rect = el.getBoundingClientRect();
659
+ const h = pos !== null ? pos.h : rect.height;
660
+ const next = { x: pos !== null ? pos.x : rect.left, y: pos !== null ? pos.y : rect.top, h };
661
+ dragRef.current = {
662
+ startX: e.clientX, startY: e.clientY,
663
+ originX: next.x, originY: next.y, h,
664
+ };
665
+ setPos(next);
666
+ savePref(PREF_PANEL_POS, JSON.stringify(next));
667
+ e.currentTarget.setPointerCapture(e.pointerId);
668
+ };
669
+ const onHeadMove = (e) => {
670
+ const d = dragRef.current;
671
+ if (d === null) return;
672
+ let x = d.originX + (e.clientX - d.startX);
673
+ let y = d.originY + (e.clientY - d.startY);
674
+ const el = panelRef.current;
675
+ if (el !== null) {
676
+ x = Math.max(0, Math.min(x, window.innerWidth - el.offsetWidth));
677
+ y = Math.max(0, Math.min(y, window.innerHeight - el.offsetHeight));
678
+ }
679
+ const next = { x, y, h: d.h };
680
+ setPos(next);
681
+ savePref(PREF_PANEL_POS, JSON.stringify(next));
682
+ };
683
+ const onHeadUp = (e) => {
684
+ dragRef.current = null;
685
+ if (e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
686
+ };
687
+
630
688
  useEffect(() => {
631
689
  if (!s.panelOpen) return;
632
690
  // Close the playlist panel when the user clicks outside it
@@ -658,8 +716,12 @@ window.__ModuleLoader__.load({
658
716
  React.createElement('span', { className: 'dsh-music-track-size' }, t.size ? Math.round(t.size / 1024 / 1024 * 10) / 10 + ' MB' : ''),
659
717
  );
660
718
  });
661
- return React.createElement('div', { className: 'dsh-music-panel', ref: panelRef },
662
- React.createElement('div', { className: 'dsh-music-panel-head' },
719
+ return React.createElement('div', { className: 'dsh-music-panel', ref: panelRef, style },
720
+ React.createElement('div', {
721
+ className: 'dsh-music-panel-head dsh-music-panel-drag',
722
+ onPointerDown: onHeadDown, onPointerMove: onHeadMove, onPointerUp: onHeadUp,
723
+ },
724
+ React.createElement('span', { className: 'dsh-music-panel-grip', 'aria-hidden': true }, '\u283f'),
663
725
  React.createElement('span', { className: 'dsh-music-panel-title' }, '\u64ad\u653e\u5217\u8868'),
664
726
  React.createElement('button', { className: 'dsh-music-icon-btn', title: '\u5173\u95ed', onClick: () => set({ panelOpen: false }) }, '\u2715')),
665
727
  React.createElement(DirectorySetting, null),
@@ -824,6 +886,8 @@ window.__ModuleLoader__.load({
824
886
  '.dsh-music-bar .dsh-music-mode-menu { align-self: center; }\n' +
825
887
  '.dsh-music-panel { position: fixed; right: 24px; bottom: 84px; width: 380px; max-height: 72vh; display: flex; flex-direction: column; gap: 8px; padding: 12px; background: var(--dsw-alias-bg-overlay, #1e1f22); border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35)); border-radius: 12px; box-shadow: 0 12px 32px rgba(0,0,0,0.35); color: var(--dsw-alias-label-primary, #e6e6e6); font-size: 13px; z-index: 1000; pointer-events: auto; overflow: hidden; }\n' +
826
888
  '.dsh-music-panel-head { display: flex; align-items: center; gap: 6px; }\n' +
889
+ '.dsh-music-panel-drag { cursor: move; touch-action: none; user-select: none; }\n' +
890
+ '.dsh-music-panel-grip { color: var(--dsw-alias-label-secondary, #8a8f98); font-size: 12px; letter-spacing: -1px; opacity: 0.7; }\n' +
827
891
  '.dsh-music-panel-title { font-weight: 600; margin-right: auto; }\n' +
828
892
  '.dsh-music-icon-btn { background: transparent; border: none; color: var(--dsw-alias-label-secondary, #8a8f98); cursor: pointer; font-size: 14px; padding: 2px 6px; border-radius: 6px; }\n' +
829
893
  '.dsh-music-icon-btn:hover { color: var(--dsw-alias-label-primary, #e6e6e6); background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); }\n' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-music-player",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "DeepSeek Harness 本地音乐库播放器:Host 扫描音乐目录并以 HTTP 流式提供音频,浏览器侧提供播放条/播放面板/设置页与实时频谱,并注册 music_play 模型工具",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",