dsh-music-player 0.3.0 → 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.
- package/README.md +5 -3
- package/lib/client.js +83 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://awesome-dsh-plugin.com)
|
|
4
4
|
|
|
5
|
-
DeepSeek Harness
|
|
5
|
+
DeepSeek Harness 本地音乐/小说播放插件。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
写代码写累了、想摸鱼又不想切窗口?这个插件就是你的**摸鱼神器**——直接在 DeepSeek Harness 的网页里塞进一个本地音乐播放器:扫一下你电脑上的音乐目录(默认 `~/Music`)就能在浏览器里听歌,带播放条和可拖拽的播放面板,还能自己建歌单。
|
|
8
|
+
|
|
9
|
+
光听歌还不够,它还能**听书**:把本地 `.txt` 小说丢给 AI 朗读,想听哪章点哪章、声音随便挑。最绝的是它还注册了 `music_play` 模型工具——你连鼠标都不用动,在对话框里跟 agent 说句「播放周杰伦的歌」,音乐分分钟响起来,摸鱼摸出新境界。
|
|
8
10
|
|
|
9
11
|
## 特性
|
|
10
12
|
|
|
@@ -12,7 +14,7 @@ DeepSeek Harness 本地音乐 + AI 讲书插件(bundle)。
|
|
|
12
14
|
- 顺序播放、单曲循环、乱序播放三种模式
|
|
13
15
|
- 实时 7 段频谱可视化(解码音频包络驱动)
|
|
14
16
|
- 播放时申请屏幕唤醒锁,防止听歌时熄屏/休眠(支持 Wake Lock 的浏览器,如 Chrome/Edge)
|
|
15
|
-
-
|
|
17
|
+
- 播放列表面板可自由拖动,右下角可拖拽调整大小,位置与尺寸跨刷新记忆
|
|
16
18
|
- AI 讲书:本地 `.txt` 小说经 MiMo TTS 合成朗读,自动识别**书名/前言/章节/尾声**结构,播放条带**章节目录**跳转、章节切歌,可选 4 种中文 AI 声音(默认白桦)
|
|
17
19
|
- `music_play` 模型工具:agent 可按关键词播放本地音乐,也可按小说名启动 AI 讲书
|
|
18
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
|
-
//
|
|
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)
|
|
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
|
|
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
|
|
1645
|
-
// explicit left/top/height. Locking
|
|
1646
|
-
//
|
|
1647
|
-
//
|
|
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 (
|
|
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),
|
|
@@ -2151,7 +2208,7 @@ window.__ModuleLoader__.load({
|
|
|
2151
2208
|
// :root (html) it cannot see body's tokens and would always fall back to
|
|
2152
2209
|
// green. Declared on body, the reference resolves and children inherit
|
|
2153
2210
|
// the theme's actual brand color.
|
|
2154
|
-
'body { --dsh-music-accent: var(--dsw-alias-brand-primary, #2f9e6e); }\n' +
|
|
2211
|
+
'body { --dsh-music-accent: var(--dsw-alias-brand-primary, #2f9e6e); --dsh-music-accent-fg: var(--dsw-alias-label-primary-foreground, #fff); }\n' +
|
|
2155
2212
|
'.dsh-music-bar-wrap { box-sizing: border-box; width: 100%; padding: 0 var(--dsh-composer-side-clearance, 16px); }\n' +
|
|
2156
2213
|
'.dsh-music-bar { box-sizing: border-box; display: flex; align-items: center; gap: 8px; width: 100%; max-width: var(--dsh-composer-card-max-width, 780px); margin: 0 auto; padding: 4px 10px; font-size: 12px; color: var(--dsw-alias-label-secondary, #8a8f98); background: var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.04)); border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.2)); border-radius: 8px; }\n' +
|
|
2157
2214
|
'.dsh-music-bar-idle { color: var(--dsw-alias-label-primary, #e6e6e6); font-weight: 500; display: inline-flex; align-items: center; }\n' +
|
|
@@ -2188,11 +2245,14 @@ 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' +
|
|
2194
2254
|
'.dsh-music-tab:hover { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); }\n' +
|
|
2195
|
-
'.dsh-music-tab.active { background: var(--dsh-music-accent, #2f9e6e); color: #fff; }\n' +
|
|
2255
|
+
'.dsh-music-tab.active { background: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); }\n' +
|
|
2196
2256
|
'.dsh-music-panel-drag { cursor: move; touch-action: none; user-select: none; }\n' +
|
|
2197
2257
|
'.dsh-music-panel-grip { color: var(--dsw-alias-label-secondary, #8a8f98); font-size: 12px; letter-spacing: -1px; opacity: 0.7; }\n' +
|
|
2198
2258
|
'.dsh-music-panel-title { font-weight: 600; margin-right: auto; }\n' +
|
|
@@ -2202,11 +2262,11 @@ window.__ModuleLoader__.load({
|
|
|
2202
2262
|
'.dsh-music-mode-menu { position: relative; flex: none; }\n' +
|
|
2203
2263
|
'.dsh-music-mode-menu.right { margin-left: auto; }\n' +
|
|
2204
2264
|
'.dsh-music-mode-trigger { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 50%; border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.25)); background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.05)); color: var(--dsh-music-accent, #2f9e6e); cursor: pointer; }\n' +
|
|
2205
|
-
'.dsh-music-mode-trigger:hover, .dsh-music-mode-trigger.active { background: var(--dsh-music-accent, #2f9e6e); color: #fff; }\n' +
|
|
2265
|
+
'.dsh-music-mode-trigger:hover, .dsh-music-mode-trigger.active { background: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); }\n' +
|
|
2206
2266
|
'.dsh-music-mode-pop { position: absolute; left: 50%; transform: translateX(-50%); bottom: calc(100% + 6px); z-index: 60; display: flex; flex-direction: column; gap: 4px; padding: 6px; background: var(--dsw-alias-bg-overlay, #1e1f22); border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35)); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.3); }\n' +
|
|
2207
2267
|
'.dsh-music-mode-item { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #8a8f98); cursor: pointer; }\n' +
|
|
2208
2268
|
'.dsh-music-mode-item:hover { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); color: var(--dsh-music-accent, #2f9e6e); }\n' +
|
|
2209
|
-
'.dsh-music-mode-item.active { background: var(--dsh-music-accent, #2f9e6e); color: #fff; }\n' +
|
|
2269
|
+
'.dsh-music-mode-item.active { background: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); }\n' +
|
|
2210
2270
|
'.dsh-music-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; min-height: 60px; max-height: 40vh; }\n' +
|
|
2211
2271
|
'.dsh-music-track { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; padding: 6px 8px; border: none; background: transparent; border-radius: 6px; color: var(--dsw-alias-label-primary, #e6e6e6); cursor: pointer; font-size: 12px; }\n' +
|
|
2212
2272
|
'.dsh-music-track:hover { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); }\n' +
|
|
@@ -2219,7 +2279,7 @@ window.__ModuleLoader__.load({
|
|
|
2219
2279
|
'.dsh-music-settings { display: flex; flex-direction: column; gap: 10px; }\n' +
|
|
2220
2280
|
'.dsh-music-settings-row { display: flex; gap: 8px; align-items: center; }\n' +
|
|
2221
2281
|
'.dsh-music-settings-cur { flex: 1; min-width: 0; padding: 6px 10px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.3)); background: var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.04)); color: var(--dsw-alias-label-primary, #e6e6e6); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n' +
|
|
2222
|
-
'.dsh-music-settings-btn { padding: 6px 12px; border-radius: 8px; border: none; background: var(--dsh-music-accent, #2f9e6e); color: #fff; cursor: pointer; font-size: 13px; white-space: nowrap; }\n' +
|
|
2282
|
+
'.dsh-music-settings-btn { padding: 6px 12px; border-radius: 8px; border: none; background: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); cursor: pointer; font-size: 13px; white-space: nowrap; }\n' +
|
|
2223
2283
|
'.dsh-music-settings-btn.ghost { background: transparent; border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.3)); color: var(--dsw-alias-label-secondary, #8a8f98); }\n' +
|
|
2224
2284
|
'.dsh-music-picker-overlay { position: absolute; inset: 0; z-index: 70; display: flex; overflow: auto; padding: 16px; background: rgba(0,0,0,0.45); }\n' +
|
|
2225
2285
|
'.dsh-music-picker { box-sizing: border-box; width: 88%; max-height: 100%; margin: auto; 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; color: var(--dsw-alias-label-primary, #e6e6e6); }\n' +
|
|
@@ -2243,13 +2303,13 @@ window.__ModuleLoader__.load({
|
|
|
2243
2303
|
'.dsh-music-toc-item:hover { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); }\n' +
|
|
2244
2304
|
'.dsh-music-toc-item.active { color: var(--dsh-music-accent, #2f9e6e); }\n' +
|
|
2245
2305
|
'.dsh-music-toc-type { flex: none; font-size: 10px; padding: 1px 5px; border-radius: 4px; background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.08)); color: var(--dsw-alias-label-secondary, #8a8f98); }\n' +
|
|
2246
|
-
'.dsh-music-toc-item.active .dsh-music-toc-type { background: var(--dsh-music-accent, #2f9e6e); color: #fff; }\n' +
|
|
2306
|
+
'.dsh-music-toc-item.active .dsh-music-toc-type { background: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); }\n' +
|
|
2247
2307
|
'.dsh-music-toc-heading { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n' +
|
|
2248
2308
|
// 自建歌单:音乐页子标签 / 歌单详情 / 文件多选 / 播放条收藏
|
|
2249
2309
|
'.dsh-music-subtabs { display: flex; gap: 4px; flex-wrap: wrap; }\n' +
|
|
2250
2310
|
'.dsh-music-subtab { flex: none; padding: 4px 10px; border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.3)); background: transparent; border-radius: 16px; color: var(--dsw-alias-label-secondary, #8a8f98); cursor: pointer; font-size: 12px; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n' +
|
|
2251
2311
|
'.dsh-music-subtab:hover { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); }\n' +
|
|
2252
|
-
'.dsh-music-subtab.active { background: var(--dsh-music-accent, #2f9e6e); border-color: var(--dsh-music-accent, #2f9e6e); color: #fff; }\n' +
|
|
2312
|
+
'.dsh-music-subtab.active { background: var(--dsh-music-accent, #2f9e6e); border-color: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); }\n' +
|
|
2253
2313
|
'.dsh-music-subtab.add { width: 30px; padding: 4px 0; text-align: center; color: var(--dsh-music-accent, #2f9e6e); }\n' +
|
|
2254
2314
|
'.dsh-music-playlist { display: flex; flex-direction: column; flex: 1; }\n' +
|
|
2255
2315
|
'.dsh-music-playlist-empty { flex: 1; display: flex; align-items: center; justify-content: center; }\n' +
|
|
@@ -2267,7 +2327,7 @@ window.__ModuleLoader__.load({
|
|
|
2267
2327
|
'.dsh-music-file-item:hover { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); }\n' +
|
|
2268
2328
|
'.dsh-music-file-item.checked { background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.1)); color: var(--dsh-music-accent, #2f9e6e); }\n' +
|
|
2269
2329
|
'.dsh-music-file-check { flex: none; width: 14px; height: 14px; border-radius: 3px; border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.4)); display: inline-flex; align-items: center; justify-content: center; font-size: 10px; }\n' +
|
|
2270
|
-
'.dsh-music-file-item.checked .dsh-music-file-check { background: var(--dsh-music-accent, #2f9e6e); border-color: var(--dsh-music-accent, #2f9e6e); color: #fff; }\n' +
|
|
2330
|
+
'.dsh-music-file-item.checked .dsh-music-file-check { background: var(--dsh-music-accent, #2f9e6e); border-color: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); }\n' +
|
|
2271
2331
|
'.dsh-music-file-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n' +
|
|
2272
2332
|
// 曲库每行:track 按钮 + 行尾「+」(加入歌单)
|
|
2273
2333
|
'.dsh-music-track-row { display: flex; align-items: center; gap: 4px; }\n' +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-music-player",
|
|
3
|
-
"version": "0.3.
|
|
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",
|