dsh-music-player 0.6.5 → 0.6.6
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/lib/client.js +125 -24
- package/lib/index.js +8 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -169,6 +169,10 @@ window.__ModuleLoader__.load({
|
|
|
169
169
|
const PREF_QQ_FAV = 'dsh-music-qq-fav'; // QQ「我喜欢」收藏 songid/songmid(Host 兜底)
|
|
170
170
|
const PREF_QQ_HISTORY = 'dsh-music-qq-history'; // QQ 搜索历史(最近在前,最多 10 条)
|
|
171
171
|
const PREF_QQ_UI = 'dsh-music-qq-ui'; // QQ 面板所在层/歌单 UI 状态
|
|
172
|
+
const PREF_SHOW_LYRIC = 'dsh-music-show-lyric'; // 播放条歌词显示开关(默认开)
|
|
173
|
+
const PREF_SHOW_VIZ = 'dsh-music-show-viz'; // 播放条频谱显示开关(默认开)
|
|
174
|
+
const PREF_SHOW_PROGRESS = 'dsh-music-show-progress'; // 播放条进度条显示开关(默认开)
|
|
175
|
+
const PREF_IMMERSE = 'dsh-music-immerse'; // 沉浸感:播放条闲置态透明度 0..1(默认 0.5)
|
|
172
176
|
// Legacy single-book progress key (pre-0.2.1); migrated into the per-book
|
|
173
177
|
// map on upgrade so very old browser copies are not silently dropped.
|
|
174
178
|
const PREF_LEGACY_BOOK = 'dsh-music-book-playback';
|
|
@@ -177,6 +181,7 @@ window.__ModuleLoader__.load({
|
|
|
177
181
|
const PREF_KEYS = new Set([
|
|
178
182
|
PREF_MODE, PREF_VOL, PREF_VOICE, PREF_SCOPE, PREF_PANEL_POS,
|
|
179
183
|
PREF_PLAYBACK, PREF_BOOKS_PLAYBACK, PREF_QQ_FAV, PREF_QQ_HISTORY, PREF_QQ_UI,
|
|
184
|
+
PREF_SHOW_LYRIC, PREF_SHOW_VIZ, PREF_SHOW_PROGRESS, PREF_IMMERSE,
|
|
180
185
|
]);
|
|
181
186
|
let serverPrefs = null; // null = Host snapshot not fetched yet
|
|
182
187
|
let serverPrefsFetched = false; // distinguishes "not fetched" from an early savePref
|
|
@@ -333,6 +338,16 @@ window.__ModuleLoader__.load({
|
|
|
333
338
|
if (Number.isFinite(v)) { store.volume = Math.min(1, Math.max(0, v)); audio.volume = store.volume; }
|
|
334
339
|
const voice = loadPref(PREF_VOICE);
|
|
335
340
|
if (typeof voice === 'string' && voice !== '') store.voice = voice;
|
|
341
|
+
// 系统配置开关:默认开启(缺省即 true)。
|
|
342
|
+
const showLyric = loadPref(PREF_SHOW_LYRIC);
|
|
343
|
+
if (showLyric === '0') store.showLyric = false;
|
|
344
|
+
const showViz = loadPref(PREF_SHOW_VIZ);
|
|
345
|
+
if (showViz === '0') store.showViz = false;
|
|
346
|
+
const showProgress = loadPref(PREF_SHOW_PROGRESS);
|
|
347
|
+
if (showProgress === '0') store.showProgress = false;
|
|
348
|
+
// 沉浸感:0..1,缺省 0.5。钳制到合法区间防止脏数据。
|
|
349
|
+
const immerse = parseFloat(loadPref(PREF_IMMERSE));
|
|
350
|
+
if (Number.isFinite(immerse)) store.immerse = Math.min(1, Math.max(0, immerse));
|
|
336
351
|
} catch (e) {}
|
|
337
352
|
}
|
|
338
353
|
// ---- per-book novel progress (independent from music) ----
|
|
@@ -377,7 +392,7 @@ window.__ModuleLoader__.load({
|
|
|
377
392
|
}
|
|
378
393
|
// Playback-panel geometry: default CSS width (must match .dsh-music-panel),
|
|
379
394
|
// resize bounds, and the viewport-height fraction cap when user-resized.
|
|
380
|
-
const PANEL_W =
|
|
395
|
+
const PANEL_W = 600;
|
|
381
396
|
const PANEL_MIN_W = 320;
|
|
382
397
|
const PANEL_MAX_W = 720;
|
|
383
398
|
const PANEL_MIN_H = 200;
|
|
@@ -475,6 +490,10 @@ window.__ModuleLoader__.load({
|
|
|
475
490
|
// 歌词/字幕:当前行文本(音乐 = 当前歌词行,讲书 = 当前句子)。空串 = 无歌词
|
|
476
491
|
// 不渲染。播放条"频谱后、时长前"位置、仅非使用态显示。
|
|
477
492
|
lyricText: '',
|
|
493
|
+
// 系统配置:播放条歌词 / 频谱 / 进度条显示开关(默认开启,存 Host prefs)。
|
|
494
|
+
showLyric: true, showViz: true, showProgress: true,
|
|
495
|
+
// 沉浸感:播放条闲置态透明度(0..1,默认 0.5),存 Host prefs。
|
|
496
|
+
immerse: 0.5,
|
|
478
497
|
// 播放模式弹层是否打开(portal 到 body 时让播放条按钮保持展开、不因移出而收起)。
|
|
479
498
|
modeMenuOpen: false,
|
|
480
499
|
// AI 讲书 TTS voice: available voices come from /manifest, the selection
|
|
@@ -505,6 +524,10 @@ window.__ModuleLoader__.load({
|
|
|
505
524
|
if ('volume' in patch) savePref(PREF_VOL, String(patch.volume));
|
|
506
525
|
if ('voice' in patch) savePref(PREF_VOICE, patch.voice);
|
|
507
526
|
if ('scope' in patch) savePref(PREF_SCOPE, JSON.stringify(patch.scope));
|
|
527
|
+
if ('showLyric' in patch) savePref(PREF_SHOW_LYRIC, patch.showLyric ? '1' : '0');
|
|
528
|
+
if ('showViz' in patch) savePref(PREF_SHOW_VIZ, patch.showViz ? '1' : '0');
|
|
529
|
+
if ('showProgress' in patch) savePref(PREF_SHOW_PROGRESS, patch.showProgress ? '1' : '0');
|
|
530
|
+
if ('immerse' in patch) savePref(PREF_IMMERSE, String(Math.min(1, Math.max(0, patch.immerse))));
|
|
508
531
|
for (const fn of [...listeners]) fn();
|
|
509
532
|
}
|
|
510
533
|
function useStore() {
|
|
@@ -1862,6 +1885,11 @@ window.__ModuleLoader__.load({
|
|
|
1862
1885
|
const url = bookUrl(id, from);
|
|
1863
1886
|
if (url === null) { clearStuck(); failBook('书籍信息缺失'); return; }
|
|
1864
1887
|
bookFromRef = from;
|
|
1888
|
+
// 与音乐切歌(startPlay)一致:先拆除旧的实时频谱监听(captureStream 探针),
|
|
1889
|
+
// 再切换到讲书块。音乐播放时建立的探针挂在同一个 <audio> 上,若不拆除,切换到
|
|
1890
|
+
// 需要冷合成的讲书块时媒体流水线会冲突(响一下→没声音、字幕冻住/输出失败)。
|
|
1891
|
+
// 切块后 onPlaying 会为当前块重新建立探针,频谱不受影响。
|
|
1892
|
+
closeLiveViz();
|
|
1865
1893
|
audio.src = url;
|
|
1866
1894
|
audio.load();
|
|
1867
1895
|
// 讲书实时字幕:拉取当前块文本并按句切分,随播放推进逐句显示。
|
|
@@ -1974,6 +2002,8 @@ window.__ModuleLoader__.load({
|
|
|
1974
2002
|
const id = currentBookId();
|
|
1975
2003
|
const chunkUrl = bookUrl(id, bookFromRef);
|
|
1976
2004
|
if (chunkUrl !== null && audio.currentSrc !== new URL(chunkUrl, window.location.href).href) {
|
|
2005
|
+
// 同样先拆除旧实时频谱监听再切换 src,避免音乐探针残留导致讲书输出冲突。
|
|
2006
|
+
closeLiveViz();
|
|
1977
2007
|
audio.src = chunkUrl;
|
|
1978
2008
|
audio.load();
|
|
1979
2009
|
}
|
|
@@ -2873,6 +2903,9 @@ window.__ModuleLoader__.load({
|
|
|
2873
2903
|
// 无内容(点击停止 / 插件刚安装)时恒定工作态:不透明度 100%、控件组展开,无任何特效。
|
|
2874
2904
|
const active = barHover || anyPopOpen || !hasTrack;
|
|
2875
2905
|
const barDimmed = !active;
|
|
2906
|
+
// 沉浸感由系统配置驱动:数值越大越「沉浸」(播放条越透明融入背景)。
|
|
2907
|
+
// 传给播放条的 opacity 是 1-immersee:沉浸 0% → 不透明(1),沉浸 100% → 全透明(0)。
|
|
2908
|
+
const barStyle = { '--dsh-music-immerse': String(1 - s.immerse) };
|
|
2876
2909
|
// 播放进度细线:音乐按 position/duration(单曲时长),讲书按「已读字符/全书字符」
|
|
2877
2910
|
// 的 bookProgress——全书总时长在合成本书前不可知,用时长占比会切块回退,字符占比
|
|
2878
2911
|
// 才稳定且只增不减。
|
|
@@ -2881,15 +2914,15 @@ window.__ModuleLoader__.load({
|
|
|
2881
2914
|
: ((hasTrack && s.duration > 0) ? Math.min(100, Math.max(0, (s.position / s.duration) * 100)) : 0);
|
|
2882
2915
|
return React.createElement('div', { className: 'dsh-music-bar-wrap' },
|
|
2883
2916
|
React.createElement('div',
|
|
2884
|
-
{ className: 'dsh-music-bar' + (isBook ? ' book' : '') + (barDimmed ? ' dimmed' : ''), onMouseEnter: showBarBtns, onMouseLeave: onBarLeave },
|
|
2917
|
+
{ className: 'dsh-music-bar' + (isBook ? ' book' : '') + (barDimmed ? ' dimmed' : ''), style: barStyle, onMouseEnter: showBarBtns, onMouseLeave: onBarLeave },
|
|
2885
2918
|
hasTrack
|
|
2886
2919
|
? React.createElement('span', { className: 'dsh-music-bar-name', title: displayName + (artistText ? ' - ' + artistText : '') + (chapterEl ? ' - ' + s.currentSection : '') }, note, ' ', displayName, artistEl, sourceBadge, localQualityBadge, chapterEl, afterName)
|
|
2887
2920
|
: React.createElement('span', { className: 'dsh-music-bar-idle' }, note, ' DSH音乐播放器'),
|
|
2888
|
-
!isBook && hasTrack && s.playing ? React.createElement('canvas', { className: 'dsh-music-viz', width: 60, height: 20, ref: (el) => { barCanvasNode = el; } }) : null,
|
|
2921
|
+
!isBook && hasTrack && s.playing && s.showViz ? React.createElement('canvas', { className: 'dsh-music-viz', width: 60, height: 20, ref: (el) => { barCanvasNode = el; } }) : null,
|
|
2889
2922
|
vizBadge,
|
|
2890
2923
|
// 歌词/字幕:位于频谱之后、时长之前;仅"非使用态"(控件组已折叠、播放条
|
|
2891
2924
|
// 半透明)显示——正在操作时收起,不给滑入的按钮组让路。
|
|
2892
|
-
s.lyricText && barDimmed && hasTrack
|
|
2925
|
+
s.lyricText && barDimmed && hasTrack && s.showLyric
|
|
2893
2926
|
? React.createElement('span', { className: 'dsh-music-bar-lyric', title: s.lyricText }, s.lyricText)
|
|
2894
2927
|
: null,
|
|
2895
2928
|
// 时长 + 右侧控制按钮是一个组合:右对齐(margin-left:auto)。鼠标进入播放条
|
|
@@ -2958,7 +2991,7 @@ window.__ModuleLoader__.load({
|
|
|
2958
2991
|
)) : null,
|
|
2959
2992
|
// 播放进度细线:绝对定位在播放条底部,与播放条等宽、高约 1px,随播放实时填充。
|
|
2960
2993
|
// 音乐按单曲时长,讲书按「已读字符/全书字符」的 bookProgress(见 progressPct)。
|
|
2961
|
-
hasTrack && (isBook || s.duration > 0)
|
|
2994
|
+
hasTrack && (isBook || s.duration > 0) && s.showProgress
|
|
2962
2995
|
? React.createElement('div', { className: 'dsh-music-bar-progress' },
|
|
2963
2996
|
React.createElement('div', { className: 'dsh-music-bar-progress-fill', style: { width: progressPct + '%' } }))
|
|
2964
2997
|
: null,
|
|
@@ -4227,24 +4260,32 @@ window.__ModuleLoader__.load({
|
|
|
4227
4260
|
const listBody = React.createElement('div', { className: 'dsh-music-list-body' },
|
|
4228
4261
|
React.createElement('div', { style: paneStyle('music') }, musicBody),
|
|
4229
4262
|
React.createElement('div', { className: 'dsh-music-qq-pane', style: paneStyle('qq') }, React.createElement(QQOnlinePanel, { panelRef })),
|
|
4230
|
-
React.createElement('div', { style: paneStyle('book') }, bookEmptyBody)
|
|
4263
|
+
React.createElement('div', { style: paneStyle('book') }, bookEmptyBody),
|
|
4264
|
+
React.createElement('div', { style: paneStyle('config') }, React.createElement(SystemSetting, null)));
|
|
4231
4265
|
return React.createElement('div', { className: 'dsh-music-panel', ref: panelRef, style: rootStyle },
|
|
4232
4266
|
React.createElement('div', {
|
|
4233
4267
|
className: 'dsh-music-panel-head dsh-music-panel-drag',
|
|
4234
4268
|
onPointerDown: onHeadDown, onPointerMove: onHeadMove, onPointerUp: onHeadUp,
|
|
4235
4269
|
},
|
|
4236
4270
|
React.createElement('span', { className: 'dsh-music-panel-grip', 'aria-hidden': true }, '⠿'),
|
|
4237
|
-
React.createElement('span', { className: 'dsh-music-panel-title' }, '
|
|
4271
|
+
React.createElement('span', { className: 'dsh-music-panel-title' }, 'DeepSeek Harness 音乐播放器'),
|
|
4238
4272
|
React.createElement('button', { className: 'dsh-music-icon-btn', title: '关闭', onClick: () => set({ panelOpen: false }) }, '✕')),
|
|
4239
|
-
React.createElement('div', { className: 'dsh-music-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4273
|
+
React.createElement('div', { className: 'dsh-music-panel-body' },
|
|
4274
|
+
// Tab 标签竖排在窗口左侧(侧边栏),内容区在其右侧。
|
|
4275
|
+
React.createElement('div', { className: 'dsh-music-tabs' },
|
|
4276
|
+
tabBtn('music', '本地音乐'), tabBtn('qq', 'QQ音乐'), tabBtn('book', 'AI讲书'), tabBtn('config', '系统配置')),
|
|
4277
|
+
React.createElement('div', { className: 'dsh-music-panel-content' },
|
|
4278
|
+
s.tab === 'qq' || s.tab === 'config' ? null : React.createElement(DirectorySetting, { panelRef }),
|
|
4279
|
+
s.tab === 'music' ? musicSubTabs : null,
|
|
4280
|
+
// While a novel is playing, keep music-only errors/scanning out of the
|
|
4281
|
+
// panel (novel status shows on the playback bar instead).
|
|
4282
|
+
// 音乐/小说统一在主列表区上方显示 error(设置块不再重复/分模式显示)。
|
|
4283
|
+
// 系统配置页不显示曲库扫描相关的错误/加载提示。
|
|
4284
|
+
s.error && s.tab !== 'config' ? React.createElement('div', { className: 'dsh-music-error' }, s.error) : null,
|
|
4285
|
+
!isBook && s.tab !== 'config' && s.loading ? React.createElement('div', { className: 'dsh-music-loading' }, '扫描中…') : null,
|
|
4286
|
+
React.createElement('div', { className: 'dsh-music-list', style: pos === null ? null : { maxHeight: 'none' }, ref: (el) => { listRef.current = el; } }, listBody),
|
|
4287
|
+
),
|
|
4288
|
+
),
|
|
4248
4289
|
React.createElement('div', { className: 'dsh-music-resize', title: '拖动调整面板大小', onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp }),
|
|
4249
4290
|
addMenu ? React.createElement(AddToPlaylistMenu, {
|
|
4250
4291
|
track: addMenu.track, anchor: { x: addMenu.x, y: addMenu.y },
|
|
@@ -4409,6 +4450,40 @@ window.__ModuleLoader__.load({
|
|
|
4409
4450
|
saveRoot(p, isBook ? 'book' : 'music');
|
|
4410
4451
|
}
|
|
4411
4452
|
}
|
|
4453
|
+
// 系统配置面板(「系统配置」tab):播放条歌词 / 频谱显示开关,持久化到 Host prefs。
|
|
4454
|
+
function SystemSetting() {
|
|
4455
|
+
const s = useStore();
|
|
4456
|
+
// 通用开关行:右侧一个开关按钮,点击切换并保存。
|
|
4457
|
+
const toggleRow = (label, desc, value, onChange) => React.createElement('div', { className: 'dsh-music-config-row' },
|
|
4458
|
+
React.createElement('div', { className: 'dsh-music-config-info' },
|
|
4459
|
+
React.createElement('span', { className: 'dsh-music-config-label' }, label),
|
|
4460
|
+
desc ? React.createElement('span', { className: 'dsh-music-config-desc' }, desc) : null),
|
|
4461
|
+
React.createElement('button', {
|
|
4462
|
+
className: 'dsh-music-toggle' + (value ? ' on' : ''),
|
|
4463
|
+
role: 'switch',
|
|
4464
|
+
'aria-checked': value,
|
|
4465
|
+
onClick: () => onChange(!value),
|
|
4466
|
+
}, React.createElement('span', { className: 'dsh-music-toggle-knob' })));
|
|
4467
|
+
// 沉浸感:鼠标移出后播放条变半透明、与背景融合。拖动滑块调节透明度。
|
|
4468
|
+
const immersePct = Math.round(s.immerse * 100);
|
|
4469
|
+
const immerseRow = React.createElement('div', { className: 'dsh-music-config-row' },
|
|
4470
|
+
React.createElement('div', { className: 'dsh-music-config-info' },
|
|
4471
|
+
React.createElement('span', { className: 'dsh-music-config-label' }, '沉浸感'),
|
|
4472
|
+
React.createElement('span', { className: 'dsh-music-config-desc' }, '鼠标移出后播放条透明度、与背景融合程度')),
|
|
4473
|
+
React.createElement('div', { className: 'dsh-music-config-slider' },
|
|
4474
|
+
React.createElement('input', {
|
|
4475
|
+
className: 'dsh-music-config-range', type: 'range', min: 0, max: 100, step: 5,
|
|
4476
|
+
value: immersePct,
|
|
4477
|
+
onChange: (e) => set({ immerse: Number(e.target.value) / 100 }),
|
|
4478
|
+
}),
|
|
4479
|
+
React.createElement('span', { className: 'dsh-music-config-val' }, immersePct + '%')));
|
|
4480
|
+
return React.createElement('div', { className: 'dsh-music-config' },
|
|
4481
|
+
toggleRow('歌词显示', '播放条上显示当前歌词 / 讲书字幕', s.showLyric, (v) => set({ showLyric: v })),
|
|
4482
|
+
toggleRow('频谱显示', '播放条上显示实时音频频谱', s.showViz, (v) => set({ showViz: v })),
|
|
4483
|
+
toggleRow('进度条显示', '播放条底部显示播放进度条(音乐 / 讲书)', s.showProgress, (v) => set({ showProgress: v })),
|
|
4484
|
+
immerseRow,
|
|
4485
|
+
);
|
|
4486
|
+
}
|
|
4412
4487
|
// 「加入歌单」弹层:曲库每行「+」点击后出现,列出所有歌单(含我最喜欢)并可新建。
|
|
4413
4488
|
// 用 fixed 定位(锚点为按钮视口坐标),避免被面板滚动列表裁剪。
|
|
4414
4489
|
function AddToPlaylistMenu({ track, anchor, onClose }) {
|
|
@@ -4730,7 +4805,7 @@ window.__ModuleLoader__.load({
|
|
|
4730
4805
|
'body { --dsh-music-accent: var(--dsw-alias-brand-primary, #2f9e6e); --dsh-music-accent-fg: var(--dsw-alias-label-primary-foreground, #fff); }\n' +
|
|
4731
4806
|
'.dsh-music-bar-wrap { box-sizing: border-box; width: 100%; padding: 0 var(--dsh-composer-side-clearance, 16px); }\n' +
|
|
4732
4807
|
'.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; cursor: default; user-select: none; position: relative; overflow: hidden; transition: opacity 0.3s ease; }\n' +
|
|
4733
|
-
'.dsh-music-bar.dimmed { opacity: 0.5; }\n' +
|
|
4808
|
+
'.dsh-music-bar.dimmed { opacity: var(--dsh-music-immerse, 0.5); }\n' +
|
|
4734
4809
|
// 播放进度细线:绝对定位在播放条底部(占满其宽度),高 1px、视觉上是一条细线;
|
|
4735
4810
|
// 轨道用低透明度衬底色,填充部分用主题色,随 position/duration 实时前进
|
|
4736
4811
|
// (宽度 0.12s 平滑过渡)。播放条容器已 overflow:hidden,细线两端会被裁剪到
|
|
@@ -4788,15 +4863,24 @@ window.__ModuleLoader__.load({
|
|
|
4788
4863
|
'.dsh-music-bar .dsh-music-mode-trigger { width: 24px; height: 24px; }\n' +
|
|
4789
4864
|
'.dsh-music-bar .dsh-music-mode-trigger svg { flex: none; }\n' +
|
|
4790
4865
|
'.dsh-music-bar .dsh-music-mode-menu { align-self: center; }\n' +
|
|
4791
|
-
'.dsh-music-panel { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); width:
|
|
4866
|
+
'.dsh-music-panel { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 600px; 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' +
|
|
4792
4867
|
'.dsh-music-resize { position: absolute; right: 0; bottom: 0; width: 16px; height: 16px; cursor: nwse-resize; touch-action: none; z-index: 5; }\n' +
|
|
4793
4868
|
'.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' +
|
|
4794
4869
|
'.dsh-music-resize:hover::after { opacity: 1; }\n' +
|
|
4795
4870
|
'.dsh-music-panel-head { display: flex; align-items: center; gap: 6px; }\n' +
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
'.dsh-music-
|
|
4799
|
-
|
|
4871
|
+
// 面板主体:左右布局——左侧 Tab 侧边栏,右侧内容区。两侧紧贴(gap:0),
|
|
4872
|
+
// 选中 tab 就能与内容区无缝连成整体。
|
|
4873
|
+
'.dsh-music-panel-body { display: flex; flex-direction: row; gap: 0; flex: 1; min-height: 0; }\n' +
|
|
4874
|
+
// 内容区:不设背景,透出面板自然底色;与左侧深色侧边栏靠明暗对比区分。
|
|
4875
|
+
'.dsh-music-panel-content { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; min-height: 0; padding-left: 12px; }\n' +
|
|
4876
|
+
// Tab 标签竖排在窗口左侧(侧边栏):背景比右侧略深。左右无内边距保证
|
|
4877
|
+
// 选中项撑满整列并与内容区无缝连接;上下内边距加大,让标签组与上方标题、
|
|
4878
|
+
// 下方边缘留出呼吸空间。
|
|
4879
|
+
'.dsh-music-tabs { display: flex; flex-direction: column; gap: 4px; flex: none; width: 88px; padding: 48px 0; background: rgba(0,0,0,0.28); }\n' +
|
|
4880
|
+
'.dsh-music-tab { flex: none; width: 100%; padding: 16px 8px; border: none; background: transparent; color: var(--dsw-alias-label-secondary, #8a8f98); cursor: pointer; font-size: 12px; }\n' +
|
|
4881
|
+
// 选中的 tab:用面板底色填充,与右侧透明内容区同色、右缘直通(无缝隙)连成整体;
|
|
4882
|
+
// 左缘一条强调色竖条 + 加粗,指示当前所在项。
|
|
4883
|
+
'.dsh-music-tab.active { background: var(--dsw-alias-bg-overlay, #1e1f22); color: var(--dsw-alias-label-primary, #e6e6e6); font-weight: 600; box-shadow: inset 3px 0 0 var(--dsh-music-accent, #2f9e6e); }\n' +
|
|
4800
4884
|
'.dsh-music-panel-drag { cursor: move; touch-action: none; user-select: none; }\n' +
|
|
4801
4885
|
'.dsh-music-panel-grip { color: var(--dsw-alias-label-secondary, #8a8f98); font-size: 12px; letter-spacing: -1px; opacity: 0.7; }\n' +
|
|
4802
4886
|
'.dsh-music-panel-title { font-weight: 600; margin-right: auto; }\n' +
|
|
@@ -4849,6 +4933,23 @@ window.__ModuleLoader__.load({
|
|
|
4849
4933
|
'.dsh-music-empty { padding: 12px; text-align: center; color: var(--dsw-alias-label-secondary, #8a8f98); font-size: 12px; }\n' +
|
|
4850
4934
|
'.dsh-music-error { color: var(--dsw-alias-state-error-primary, #e5534b); font-size: 12px; }\n' +
|
|
4851
4935
|
'.dsh-music-loading { color: var(--dsw-alias-label-secondary, #8a8f98); font-size: 12px; }\n' +
|
|
4936
|
+
// 系统配置面板:开关行(标签 + 描述 + 右侧开关)。
|
|
4937
|
+
'.dsh-music-config { display: flex; flex-direction: column; gap: 12px; }\n' +
|
|
4938
|
+
'.dsh-music-config-row { display: flex; align-items: center; gap: 12px; padding: 12px; border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.25)); border-radius: 8px; background: var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.04)); }\n' +
|
|
4939
|
+
'.dsh-music-config-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }\n' +
|
|
4940
|
+
'.dsh-music-config-label { color: var(--dsw-alias-label-primary, #e6e6e6); font-size: 13px; font-weight: 600; }\n' +
|
|
4941
|
+
'.dsh-music-config-desc { color: var(--dsw-alias-label-secondary, #8a8f98); font-size: 11px; }\n' +
|
|
4942
|
+
// 开关:右侧胶囊。用固定强调色而非随主题反转的 brand-primary,
|
|
4943
|
+
// 让深/浅主题下都读作「绿色=开、灰=关」;旋钮恒白保证两种主题下对比清晰。
|
|
4944
|
+
'.dsh-music-toggle { position: relative; flex: none; width: 40px; height: 22px; padding: 0; border: none; border-radius: 22px; background: var(--dsw-alias-border-l1, rgba(128,128,128,0.35)); cursor: pointer; transition: background 0.15s; }\n' +
|
|
4945
|
+
'.dsh-music-toggle .dsh-music-toggle-knob { position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,0.35); transition: left 0.15s; }\n' +
|
|
4946
|
+
// 开启:固定绿色强调色(不随主题反转),旋钮右移。
|
|
4947
|
+
'.dsh-music-toggle.on { background: #2f9e6e; }\n' +
|
|
4948
|
+
'.dsh-music-toggle.on .dsh-music-toggle-knob { left: 20px; }\n' +
|
|
4949
|
+
// 沉浸感滑块行:右侧 range + 百分比数值。
|
|
4950
|
+
'.dsh-music-config-slider { flex: none; display: flex; align-items: center; gap: 8px; }\n' +
|
|
4951
|
+
'.dsh-music-config-range { width: 140px; accent-color: #2f9e6e; cursor: pointer; }\n' +
|
|
4952
|
+
'.dsh-music-config-val { min-width: 34px; text-align: right; font-size: 12px; color: var(--dsw-alias-label-secondary, #8a8f98); font-variant-numeric: tabular-nums; }\n' +
|
|
4852
4953
|
'.dsh-music-settings { display: flex; flex-direction: column; gap: 10px; }\n' +
|
|
4853
4954
|
'.dsh-music-settings-row { display: flex; gap: 8px; align-items: center; }\n' +
|
|
4854
4955
|
'.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' +
|
|
@@ -4898,7 +4999,7 @@ window.__ModuleLoader__.load({
|
|
|
4898
4999
|
'.dsh-music-qq-login-status { font-size: 14px; color: var(--dsw-alias-label-primary, #e6e6e6); text-align: center; }\n' +
|
|
4899
5000
|
'.dsh-music-qq-login-actions { display: flex; gap: 8px; }\n' +
|
|
4900
5001
|
'.dsh-music-qq-viewtabs { display: flex; gap: 6px; }\n' +
|
|
4901
|
-
'.dsh-music-qq-viewtab { padding: 5px 12px; border-radius: 8px; border: none; background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); color: var(--dsw-alias-label-secondary, #8a8f98); cursor: pointer; font-size: 13px; }\n' +
|
|
5002
|
+
'.dsh-music-qq-viewtab { flex: none; white-space: nowrap; padding: 5px 12px; border-radius: 8px; border: none; background: var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.06)); color: var(--dsw-alias-label-secondary, #8a8f98); cursor: pointer; font-size: 13px; }\n' +
|
|
4902
5003
|
'.dsh-music-qq-viewtab.active { background: var(--dsh-music-accent, #2f9e6e); color: var(--dsh-music-accent-fg, #fff); }\n' +
|
|
4903
5004
|
'.dsh-music-qq-cats { display: flex; flex-wrap: wrap; gap: 6px; }\n' +
|
|
4904
5005
|
'.dsh-music-qq-cat { padding: 4px 10px; border-radius: 12px; border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,0.3)); background: transparent; color: var(--dsw-alias-label-primary, #e6e6e6); cursor: pointer; font-size: 12px; }\n' +
|
|
@@ -4926,7 +5027,7 @@ window.__ModuleLoader__.load({
|
|
|
4926
5027
|
'.dsh-music-qq-now-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n' +
|
|
4927
5028
|
'.dsh-music-qq-now-artist { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary, #8a8f98); margin-left: 4px; }\n' +
|
|
4928
5029
|
'.dsh-music-qq-now-src { flex: 0 0 auto; color: var(--dsw-alias-label-secondary, #8a8f98); margin-left: auto; }\n' +
|
|
4929
|
-
'.dsh-music-qq-toolbar { display: flex; justify-content: space-between; gap: 8px; align-items: center; }\n' +
|
|
5030
|
+
'.dsh-music-qq-toolbar { display: flex; justify-content: space-between; gap: 8px; align-items: center; margin-bottom: 12px; }\n' +
|
|
4930
5031
|
'.dsh-music-qq-login { flex: 1; min-height: 200px; display: flex; align-items: center; justify-content: center; }\n' +
|
|
4931
5032
|
'.dsh-music-qq-login-center { display: flex; flex-direction: column; gap: 12px; align-items: center; max-width: 320px; }\n' +
|
|
4932
5033
|
// 醒目提示:QQ 扫码登录已验证 OK。
|
package/lib/index.js
CHANGED
|
@@ -1176,6 +1176,8 @@ export function apply(ctx) {
|
|
|
1176
1176
|
'dsh-music-mode', 'dsh-music-volume', 'dsh-music-voice', 'dsh-music-scope',
|
|
1177
1177
|
'dsh-music-panel-pos', 'dsh-music-playback', 'dsh-music-books-playback',
|
|
1178
1178
|
'dsh-music-qq-fav', 'dsh-music-qq-history', 'dsh-music-qq-ui',
|
|
1179
|
+
'dsh-music-show-lyric', 'dsh-music-show-viz', 'dsh-music-show-progress',
|
|
1180
|
+
'dsh-music-immerse',
|
|
1179
1181
|
])
|
|
1180
1182
|
const PREF_VALUE_MAX = 256 * 1024 // 单键上限(books 进度 map / QQ 队列可能较大)
|
|
1181
1183
|
const sanitizePrefs = (input) => {
|
|
@@ -1191,6 +1193,12 @@ export function apply(ctx) {
|
|
|
1191
1193
|
out[k] = String(Math.min(1, Math.max(0, n)))
|
|
1192
1194
|
continue
|
|
1193
1195
|
}
|
|
1196
|
+
if (k === 'dsh-music-immerse') {
|
|
1197
|
+
const n = Number(v)
|
|
1198
|
+
if (!Number.isFinite(n)) continue
|
|
1199
|
+
out[k] = String(Math.min(1, Math.max(0, n)))
|
|
1200
|
+
continue
|
|
1201
|
+
}
|
|
1194
1202
|
if (k === 'dsh-music-mode') {
|
|
1195
1203
|
if (v !== 'single' && v !== 'order' && v !== 'shuffle') continue
|
|
1196
1204
|
out[k] = v
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-music-player",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"description": "DeepSeek Harness 本地音乐 + AI 讲书插件:Host 扫描音乐目录并以 HTTP 流式提供音频、解析 .txt 小说结构并经 MiMo TTS 合成朗读,浏览器侧提供播放条/播放面板/章节目录跳转/多声音选择/实时频谱,并注册 music_play 模型工具",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|