Skip to content

Commit a879e45

Browse files
committed
feat: 播放器页面添加拖拽功能
1 parent 799d867 commit a879e45

5 files changed

Lines changed: 112 additions & 4 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "dtv",
33
"private": true,
4-
"version": "3.0.1",
4+
"version": "3.0.2",
55
"type": "module",
66
"scripts": {
77
"dev": "pnpm -C web dev",

src-tauri/src/platforms/douyin/danmu/gen/douyin.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ pub struct ChatMessage {
107107
#[prost(bool, tag = "18")]
108108
pub intercom_hide_user_card: bool,
109109
/// repeated chatTagsList = 19;
110+
/// NOTE: Field 20 wire type has been observed as Varint in live traffic (2026-04),
111+
/// so keep it as an integer for forward compatibility.
110112
#[prost(uint32, tag = "20")]
111113
pub chat_by: u32,
112114
#[prost(uint32, tag = "21")]

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "DTV",
4-
"version": "3.0.1",
4+
"version": "3.0.2",
55
"identifier": "com.dtv.app",
66
"build": {
77
"beforeDevCommand": "pnpm -C web dev",

web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "dtv-web",
33
"private": true,
4-
"version": "3.0.1",
4+
"version": "3.0.2",
55
"type": "module",
66
"scripts": {
77
"dev": "next dev -p 2896",

web/src/components/player/MainPlayer.tsx

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,30 @@ declare global {
4444

4545
const qualityOptions = ["原画", "高清", "标清"] as const;
4646

47+
const PLAYER_DRAG_EXCLUDED_SELECTOR = [
48+
// App chrome / topbar (主播信息栏 & 关闭/关注按钮等)
49+
".player-topbar",
50+
".player-window-controls",
51+
// Stream error overlay (中间刷新按钮)
52+
".retry-btn",
53+
// Generic interactive elements
54+
"button",
55+
"a",
56+
"input",
57+
"textarea",
58+
"select",
59+
"[role='button']",
60+
"[role='link']",
61+
"[contenteditable='true']",
62+
// xgplayer controls / popups (播放器控制栏及其菜单)
63+
".xgplayer-controls",
64+
".xgplayer-controls *",
65+
".xgplayer-danmu-block-panel",
66+
".xgplayer-danmu-settings-panel",
67+
".xgplayer-quality-dropdown",
68+
".xgplayer-line-dropdown"
69+
].join(", ");
70+
4771
const DEFAULT_DANMU_SETTINGS: DanmuUserSettings = {
4872
color: "#ffffff",
4973
strokeColor: "#444444",
@@ -161,6 +185,8 @@ export function MainPlayer({
161185
const { setIsland, clearIsland, setFullscreen } = usePlayerUi();
162186
const { ensureProxyStarted, getAvatarSrc } = useImageProxy();
163187
const pageRef = useRef<HTMLDivElement | null>(null);
188+
const dragStartArmedRef = useRef(false);
189+
const dragCandidateRef = useRef<null | { pointerId: number; x: number; y: number }>(null);
164190

165191
const playerContainerRef = useRef<HTMLDivElement | null>(null);
166192
const playerRef = useRef<any>(null);
@@ -354,6 +380,79 @@ export function MainPlayer({
354380
}
355381
}, []);
356382

383+
const startWindowDragging = useCallback(async () => {
384+
try {
385+
const { getCurrentWindow } = await import("@tauri-apps/api/window");
386+
await getCurrentWindow().startDragging();
387+
} catch {
388+
// ignore
389+
}
390+
}, []);
391+
392+
const isDragExcludedTarget = useCallback((target: HTMLElement) => {
393+
return !!target.closest(PLAYER_DRAG_EXCLUDED_SELECTOR);
394+
}, []);
395+
396+
const onPlayerPointerDownCapture = useCallback(
397+
(e: React.PointerEvent) => {
398+
if (e.button !== 0) return;
399+
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
400+
const target = e.target as HTMLElement | null;
401+
if (!target) return;
402+
403+
const root = pageRef.current;
404+
if (!root || !root.contains(target)) return;
405+
if (isDragExcludedTarget(target)) return;
406+
407+
// Defer dragging until the pointer moves a bit, so normal "click-to-toggle" behaviors still work.
408+
dragCandidateRef.current = { pointerId: e.pointerId, x: e.clientX, y: e.clientY };
409+
},
410+
[isDragExcludedTarget]
411+
);
412+
413+
const onPlayerPointerMoveCapture = useCallback(
414+
(e: React.PointerEvent) => {
415+
const candidate = dragCandidateRef.current;
416+
if (!candidate) return;
417+
if (candidate.pointerId !== e.pointerId) return;
418+
if (dragStartArmedRef.current) return;
419+
420+
const dx = e.clientX - candidate.x;
421+
const dy = e.clientY - candidate.y;
422+
if (dx * dx + dy * dy < 36) return; // 6px threshold
423+
424+
const target = e.target as HTMLElement | null;
425+
const root = pageRef.current;
426+
if (!target || !root || !root.contains(target)) {
427+
dragCandidateRef.current = null;
428+
return;
429+
}
430+
431+
if (isDragExcludedTarget(target)) {
432+
dragCandidateRef.current = null;
433+
return;
434+
}
435+
436+
dragStartArmedRef.current = true;
437+
dragCandidateRef.current = null;
438+
e.preventDefault();
439+
440+
void startWindowDragging().finally(() => {
441+
window.setTimeout(() => {
442+
dragStartArmedRef.current = false;
443+
}, 300);
444+
});
445+
},
446+
[isDragExcludedTarget, startWindowDragging]
447+
);
448+
449+
const onPlayerPointerUpCapture = useCallback((e: React.PointerEvent) => {
450+
const candidate = dragCandidateRef.current;
451+
if (!candidate) return;
452+
if (candidate.pointerId !== e.pointerId) return;
453+
dragCandidateRef.current = null;
454+
}, []);
455+
357456
useEffect(() => {
358457
const armHide = () => {
359458
if (hideChromeTimerRef.current) window.clearTimeout(hideChromeTimerRef.current);
@@ -1217,7 +1316,14 @@ export function MainPlayer({
12171316
}, [platform, playerAnchorName, playerAvatar, playerTitle, roomId]);
12181317

12191318
return (
1220-
<div className={`player-page${chromeHiddenClass}`} ref={pageRef}>
1319+
<div
1320+
className={`player-page${chromeHiddenClass}`}
1321+
ref={pageRef}
1322+
onPointerDownCapture={onPlayerPointerDownCapture}
1323+
onPointerMoveCapture={onPlayerPointerMoveCapture}
1324+
onPointerUpCapture={onPlayerPointerUpCapture}
1325+
onPointerCancelCapture={onPlayerPointerUpCapture}
1326+
>
12211327
{isWindows ? (
12221328
<div className="player-window-controls" data-tauri-drag-region="false" aria-label="窗口控制">
12231329
<button type="button" className="window-btn" data-tauri-drag-region="false" aria-label="最小化" onClick={() => void minimizeWindow()}>

0 commit comments

Comments
 (0)