dsh-music-player 0.3.1 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/lib/client.js +75 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -14,7 +14,7 @@ DeepSeek Harness 本地音乐/小说播放插件。
14
14
  - 顺序播放、单曲循环、乱序播放三种模式
15
15
  - 实时 7 段频谱可视化(解码音频包络驱动)
16
16
  - 播放时申请屏幕唤醒锁,防止听歌时熄屏/休眠(支持 Wake Lock 的浏览器,如 Chrome/Edge)
17
- - 播放列表面板可自由拖动,位置跨刷新记忆
17
+ - 播放列表面板可自由拖动,右下角可拖拽调整大小,位置与尺寸跨刷新记忆
18
18
  - AI 讲书:本地 `.txt` 小说经 MiMo TTS 合成朗读,自动识别**书名/前言/章节/尾声**结构,播放条带**章节目录**跳转、章节切歌,可选 4 种中文 AI 声音(默认白桦)
19
19
  - `music_play` 模型工具:agent 可按关键词播放本地音乐,也可按小说名启动 AI 讲书
20
20
  - 支持的格式:`mp3 / m4a / m4b / aac / flac / wav / ogg / opus / webm / aiff`(自动递归扫描子目录,上限 500 首)
package/lib/client.js CHANGED
@@ -101,14 +101,24 @@ window.__ModuleLoader__.load({
101
101
  }
102
102
  return best;
103
103
  }
104
- // Restore the playback-panel position ({x,y,h}) previously saved by dragging, if any.
104
+ // Playback-panel geometry: default CSS width (must match .dsh-music-panel),
105
+ // resize bounds, and the viewport-height fraction cap when user-resized.
106
+ const PANEL_W = 380;
107
+ const PANEL_MIN_W = 280;
108
+ const PANEL_MAX_W = 640;
109
+ const PANEL_MIN_H = 200;
110
+ const PANEL_MAX_H_VH = 0.8;
111
+ // Restore the playback-panel position ({x,y,w,h}) previously saved by
112
+ // dragging/resizing, if any. Old saves ({x,y,h}) migrate with a default width.
105
113
  function loadPanelPos() {
106
114
  const raw = loadPref(PREF_PANEL_POS);
107
115
  if (raw === null) return null;
108
116
  try {
109
117
  const p = JSON.parse(raw);
110
118
  if (p && typeof p.x === 'number' && typeof p.y === 'number'
111
- && typeof p.h === 'number' && p.h > 0) return p;
119
+ && typeof p.h === 'number' && p.h > 0) {
120
+ return { x: p.x, y: p.y, w: (typeof p.w === 'number' && p.w > 0) ? p.w : PANEL_W, h: p.h };
121
+ }
112
122
  } catch (e) {}
113
123
  return null;
114
124
  }
@@ -1631,9 +1641,11 @@ window.__ModuleLoader__.load({
1631
1641
  const isBook = s.currentId !== null && String(s.currentId).startsWith('book:');
1632
1642
  const listRef = useRef(null);
1633
1643
  const panelRef = useRef(null);
1634
- // Draggable panel position ({x, y, h} left/top/height once dragged; null = default right/bottom)
1644
+ // Draggable panel position + size ({x, y, w, h} left/top/width/height once
1645
+ // dragged or resized; null = default right/bottom, 380px, auto height).
1635
1646
  const [pos, setPos] = useState(loadPanelPos);
1636
- const dragRef = useRef(null);
1647
+ const dragRef = useRef(null); // head-drag state
1648
+ const resizeRef = useRef(null); // corner-resize state
1637
1649
  // 曲库每行「+」打开的「加入歌单」菜单:{track, x, y}(锚点=按钮右上角视口坐标)。
1638
1650
  const [addMenu, setAddMenu] = useState(null);
1639
1651
  const openAddMenu = (track, e) => {
@@ -1641,11 +1653,11 @@ window.__ModuleLoader__.load({
1641
1653
  setAddMenu({ track, x: r.right, y: r.top });
1642
1654
  };
1643
1655
 
1644
- // Once the panel is dragged we switch from CSS right/bottom anchoring to an
1645
- // explicit left/top/height. Locking the height matters: with only top+left set
1646
- // and no height, a fixed element whose CSS also sets bottom collapses to fit
1647
- // the leftover space, so the panel's height jumps while dragging.
1648
- const style = pos === null ? null : { left: pos.x, top: pos.y, height: pos.h };
1656
+ // Once the panel is dragged/resized we switch from CSS right/bottom anchoring
1657
+ // to explicit left/top/width/height. Locking height and clearing max-height
1658
+ // matters: with only top+left and the CSS max-height:72vh still applying, a
1659
+ // fixed element whose CSS also sets bottom would collapse/clamp while dragging.
1660
+ const style = pos === null ? null : { left: pos.x, top: pos.y, width: pos.w, height: pos.h, maxHeight: 'none' };
1649
1661
 
1650
1662
  const onHeadDown = (e) => {
1651
1663
  if (e.button !== undefined && e.button !== 0) return;
@@ -1654,15 +1666,16 @@ window.__ModuleLoader__.load({
1654
1666
  const el = panelRef.current;
1655
1667
  if (el === null) return;
1656
1668
  const rect = el.getBoundingClientRect();
1669
+ const w = pos !== null ? pos.w : PANEL_W;
1657
1670
  const h = pos !== null ? pos.h : rect.height;
1658
- const next = { x: pos !== null ? pos.x : rect.left, y: pos !== null ? pos.y : rect.top, h };
1671
+ const next = { x: pos !== null ? pos.x : rect.left, y: pos !== null ? pos.y : rect.top, w, h };
1659
1672
  dragRef.current = {
1660
1673
  startX: e.clientX, startY: e.clientY,
1661
- originX: next.x, originY: next.y, h,
1674
+ originX: next.x, originY: next.y, w, h,
1662
1675
  };
1663
1676
  setPos(next);
1664
1677
  savePref(PREF_PANEL_POS, JSON.stringify(next));
1665
- e.currentTarget.setPointerCapture(e.pointerId);
1678
+ if (typeof e.currentTarget.setPointerCapture === 'function') e.currentTarget.setPointerCapture(e.pointerId);
1666
1679
  };
1667
1680
  const onHeadMove = (e) => {
1668
1681
  const d = dragRef.current;
@@ -1674,13 +1687,56 @@ window.__ModuleLoader__.load({
1674
1687
  x = Math.max(0, Math.min(x, window.innerWidth - el.offsetWidth));
1675
1688
  y = Math.max(0, Math.min(y, window.innerHeight - el.offsetHeight));
1676
1689
  }
1677
- const next = { x, y, h: d.h };
1690
+ const next = { x, y, w: d.w, h: d.h };
1678
1691
  setPos(next);
1679
1692
  savePref(PREF_PANEL_POS, JSON.stringify(next));
1680
1693
  };
1681
1694
  const onHeadUp = (e) => {
1682
1695
  dragRef.current = null;
1683
- if (e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
1696
+ if (typeof e.currentTarget.releasePointerCapture === 'function'
1697
+ && typeof e.currentTarget.hasPointerCapture === 'function'
1698
+ && e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
1699
+ };
1700
+
1701
+ // Corner drag-to-resize: grow/shrink width & height from the bottom-right
1702
+ // handle, clamped to [min, max] and kept inside the viewport. The panel's
1703
+ // top-left (x/y) is untouched by a resize.
1704
+ const onResizeDown = (e) => {
1705
+ if (e.button !== undefined && e.button !== 0) return;
1706
+ const el = panelRef.current;
1707
+ if (el === null) return;
1708
+ const rect = el.getBoundingClientRect();
1709
+ const cur = {
1710
+ x: pos !== null ? pos.x : rect.left,
1711
+ y: pos !== null ? pos.y : rect.top,
1712
+ w: pos !== null ? pos.w : PANEL_W,
1713
+ h: pos !== null ? pos.h : rect.height,
1714
+ };
1715
+ resizeRef.current = { startX: e.clientX, startY: e.clientY, originW: cur.w, originH: cur.h };
1716
+ setPos(cur);
1717
+ savePref(PREF_PANEL_POS, JSON.stringify(cur));
1718
+ if (typeof e.currentTarget.setPointerCapture === 'function') e.currentTarget.setPointerCapture(e.pointerId);
1719
+ };
1720
+ const onResizeMove = (e) => {
1721
+ const d = resizeRef.current;
1722
+ if (d === null) return;
1723
+ const el = panelRef.current;
1724
+ const x = pos !== null ? pos.x : (el !== null ? el.getBoundingClientRect().left : 0);
1725
+ const y = pos !== null ? pos.y : (el !== null ? el.getBoundingClientRect().top : 0);
1726
+ const vw = window.innerWidth, vh = window.innerHeight;
1727
+ const maxW = Math.min(PANEL_MAX_W, Math.max(PANEL_MIN_W, vw - x));
1728
+ const maxH = Math.min(Math.floor(vh * PANEL_MAX_H_VH), Math.max(PANEL_MIN_H, vh - y));
1729
+ const w = Math.max(PANEL_MIN_W, Math.min(d.originW + (e.clientX - d.startX), maxW));
1730
+ const h = Math.max(PANEL_MIN_H, Math.min(d.originH + (e.clientY - d.startY), maxH));
1731
+ const next = { x, y, w, h };
1732
+ setPos(next);
1733
+ savePref(PREF_PANEL_POS, JSON.stringify(next));
1734
+ };
1735
+ const onResizeUp = (e) => {
1736
+ resizeRef.current = null;
1737
+ if (typeof e.currentTarget.releasePointerCapture === 'function'
1738
+ && typeof e.currentTarget.hasPointerCapture === 'function'
1739
+ && e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
1684
1740
  };
1685
1741
 
1686
1742
  useEffect(() => {
@@ -1782,7 +1838,8 @@ window.__ModuleLoader__.load({
1782
1838
  // panel (novel status shows on the playback bar instead).
1783
1839
  !isBook && s.error ? React.createElement('div', { className: 'dsh-music-error' }, s.error) : null,
1784
1840
  !isBook && s.loading ? React.createElement('div', { className: 'dsh-music-loading' }, '\u626b\u63cf\u4e2d\u2026') : null,
1785
- React.createElement('div', { className: 'dsh-music-list', ref: (el) => { listRef.current = el; } }, listBody),
1841
+ React.createElement('div', { className: 'dsh-music-list', style: pos === null ? null : { maxHeight: 'none' }, ref: (el) => { listRef.current = el; } }, listBody),
1842
+ React.createElement('div', { className: 'dsh-music-resize', title: '拖动调整面板大小', onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp }),
1786
1843
  addMenu ? React.createElement(AddToPlaylistMenu, {
1787
1844
  track: addMenu.track, anchor: { x: addMenu.x, y: addMenu.y },
1788
1845
  onClose: () => setAddMenu(null),
@@ -2188,6 +2245,9 @@ window.__ModuleLoader__.load({
2188
2245
  '.dsh-music-bar .dsh-music-mode-trigger svg { flex: none; }\n' +
2189
2246
  '.dsh-music-bar .dsh-music-mode-menu { align-self: center; }\n' +
2190
2247
  '.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' +
2248
+ '.dsh-music-resize { position: absolute; right: 0; bottom: 0; width: 16px; height: 16px; cursor: nwse-resize; touch-action: none; z-index: 5; }\n' +
2249
+ '.dsh-music-resize::after { content: ""; position: absolute; right: 4px; bottom: 4px; width: 5px; height: 5px; border-right: 2px solid var(--dsw-alias-label-secondary, #8a8f98); border-bottom: 2px solid var(--dsw-alias-label-secondary, #8a8f98); opacity: 0.7; }\n' +
2250
+ '.dsh-music-resize:hover::after { opacity: 1; }\n' +
2191
2251
  '.dsh-music-panel-head { display: flex; align-items: center; gap: 6px; }\n' +
2192
2252
  '.dsh-music-tabs { display: flex; gap: 4px; }\n' +
2193
2253
  '.dsh-music-tab { flex: 1; padding: 5px 0; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #8a8f98); cursor: pointer; font-size: 12px; }\n' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-music-player",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "DeepSeek Harness 本地音乐 + AI 讲书插件:Host 扫描音乐目录并以 HTTP 流式提供音频、解析 .txt 小说结构并经 MiMo TTS 合成朗读,浏览器侧提供播放条/播放面板/章节目录跳转/多声音选择/实时频谱,并注册 music_play 模型工具",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",