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 +9 -0
- package/lib/client.js +66 -2
- package/package.json +1 -1
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
|
+

|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+

|
|
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', {
|
|
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' +
|