Skip to content

Commit eae82e7

Browse files
committed
feat(player): update styles for chapter markers and seek bar, enhance tooltip design
1 parent fc6f3a7 commit eae82e7

File tree

3 files changed

+190
-113
lines changed

3 files changed

+190
-113
lines changed

packages/visualizer/src/component/player/index.less

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
background: #fff;
3333

3434
.canvas-container {
35-
background-color: #f2f4f7;
35+
background-color: #000;
3636
}
3737
}
3838

@@ -45,7 +45,7 @@
4545
align-items: center;
4646
overflow: hidden;
4747
position: relative;
48-
background-color: #fff;
48+
background-color: #000;
4949
border-top-right-radius: 4px;
5050
border-top-left-radius: 4px;
5151

@@ -62,6 +62,7 @@
6262
max-height: 100%;
6363
border: none;
6464
}
65+
6566
}
6667

6768
// Remove border/outline from Remotion control bar buttons
@@ -83,34 +84,31 @@
8384
height: 5px;
8485
pointer-events: none;
8586
z-index: 1;
87+
transition: opacity 0.3s;
8688

8789
.chapter-marker {
8890
position: absolute;
89-
top: 50%;
90-
width: 3px;
91-
height: 12px;
92-
background: rgba(255, 255, 255, 0.9);
93-
transform: translate(-50%, -50%);
94-
border-radius: 1.5px;
91+
top: 0;
92+
width: 2px;
93+
height: 100%;
94+
background: #fff;
95+
transform: translateX(-50%);
9596
pointer-events: auto;
9697
cursor: pointer;
9798

9899
// Larger hit area for easier hovering/clicking
99100
&::before {
100101
content: '';
101102
position: absolute;
102-
top: -6px;
103-
left: -6px;
104-
right: -6px;
105-
bottom: -6px;
106-
}
107-
108-
&:hover {
109-
background: #fff;
110-
height: 16px;
103+
top: -8px;
104+
left: -8px;
105+
right: -8px;
106+
bottom: -8px;
111107
}
112108
}
113109
}
110+
111+
// Remotion seek bar styles are overridden via JS in useEffect (see index.tsx)
114112
}
115113

116114
// Custom controls rendered inside Remotion Player's control bar
@@ -149,6 +147,22 @@
149147
}
150148
}
151149

150+
// Chapter marker tooltip
151+
.chapter-tooltip {
152+
.ant-tooltip-inner {
153+
background: rgba(80, 80, 80, 0.85);
154+
backdrop-filter: blur(8px);
155+
border-radius: 16px;
156+
padding: 6px 12px;
157+
font-size: 12px;
158+
max-width: 360px;
159+
}
160+
161+
.ant-tooltip-arrow::before {
162+
background: rgba(80, 80, 80, 0.85);
163+
}
164+
}
165+
152166
// Dark mode styles
153167
[data-theme='dark'] {
154168
.player-container {
@@ -160,12 +174,12 @@
160174
border-color: #292929;
161175

162176
.canvas-container {
163-
background-color: #141414;
177+
background-color: #000;
164178
}
165179
}
166180

167181
.canvas-container {
168-
background-color: #1f1f1f;
182+
background-color: #000;
169183
}
170184

171185
}

packages/visualizer/src/component/player/index.tsx

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,93 @@ export function Player(props?: {
9696

9797
const playerRef = useRef<PlayerRef>(null);
9898
const lastTaskIdRef = useRef<string | null>(null);
99+
const wrapperRef = useRef<HTMLDivElement>(null);
100+
const markerHoverIntervalRef = useRef<ReturnType<typeof setInterval> | null>(
101+
null,
102+
);
103+
104+
// Override Remotion Player seek bar styles via DOM
105+
useEffect(() => {
106+
const node = (playerRef.current as any)?.getContainerNode?.();
107+
if (!node) return;
108+
109+
const applySeekBarStyles = () => {
110+
// Find seek bar container (has cursor: pointer and touch-action: none)
111+
const seekBarContainer = node.querySelector(
112+
'[style*="touch-action"]',
113+
) as HTMLElement | null;
114+
if (!seekBarContainer) return;
115+
116+
// Bar background: first child of seek bar container
117+
const barBg = seekBarContainer.firstElementChild as HTMLElement | null;
118+
if (barBg) {
119+
barBg.style.setProperty(
120+
'background-color',
121+
'rgba(255, 255, 255, 0.3)',
122+
'important',
123+
);
124+
}
125+
126+
// Fill bar: last child of bar background
127+
const fillBar = barBg?.lastElementChild as HTMLElement | null;
128+
if (fillBar) {
129+
fillBar.style.setProperty(
130+
'background-color',
131+
'rgba(43, 131, 255, 1)',
132+
'important',
133+
);
134+
}
135+
136+
// Knob: last child of seek bar container
137+
const knob = seekBarContainer.lastElementChild as HTMLElement | null;
138+
if (knob && knob !== barBg) {
139+
knob.style.setProperty('opacity', '1', 'important');
140+
knob.style.setProperty('background-color', '#fff', 'important');
141+
knob.style.setProperty('box-shadow', 'none', 'important');
142+
knob.style.setProperty('width', '8px', 'important');
143+
knob.style.setProperty('height', '16px', 'important');
144+
knob.style.setProperty('border-radius', '10px', 'important');
145+
// Vertically center: bar center at 6.5px from container top, knob height 16
146+
knob.style.setProperty('top', '-1.5px', 'important');
147+
}
148+
149+
// Sync chapter markers visibility with control bar
150+
const controlBar = node.querySelector(
151+
'[style*="transition"]',
152+
) as HTMLElement | null;
153+
const markersEl = wrapperRef.current?.querySelector(
154+
'.chapter-markers',
155+
) as HTMLElement | null;
156+
if (controlBar && markersEl) {
157+
markersEl.style.opacity = window.getComputedStyle(controlBar).opacity;
158+
}
159+
};
160+
161+
// Apply initially and observe for re-renders
162+
const timer = setInterval(applySeekBarStyles, 300);
163+
applySeekBarStyles();
164+
return () => clearInterval(timer);
165+
}, [frameMap]);
166+
167+
// Continuously dispatch mousemove on Remotion Player root to keep controls visible
168+
// Remotion uses mouseenter/mouseleave/mousemove to track hover state,
169+
// and hides controls after a timeout with no mouse activity
170+
const onMarkerMouseEnter = useCallback(() => {
171+
const node = (playerRef.current as any)?.getContainerNode?.();
172+
if (!node) return;
173+
const dispatchMove = () => {
174+
node.dispatchEvent(new MouseEvent('mouseenter', { bubbles: false }));
175+
node.dispatchEvent(new MouseEvent('mousemove', { bubbles: true }));
176+
};
177+
dispatchMove();
178+
markerHoverIntervalRef.current = setInterval(dispatchMove, 200);
179+
}, []);
180+
const onMarkerMouseLeave = useCallback(() => {
181+
if (markerHoverIntervalRef.current) {
182+
clearInterval(markerHoverIntervalRef.current);
183+
markerHoverIntervalRef.current = null;
184+
}
185+
}, []);
99186

100187
// Track frame for taskId callback
101188
useEffect(() => {
@@ -391,7 +478,7 @@ export function Player(props?: {
391478
return (
392479
<div className="player-container" data-fit-mode={props?.fitMode}>
393480
<div className="canvas-container">
394-
<div className="player-wrapper">
481+
<div className="player-wrapper" ref={wrapperRef}>
395482
<RemotionPlayer
396483
ref={playerRef}
397484
component={Composition}
@@ -423,10 +510,16 @@ export function Player(props?: {
423510
{chapterMarkers.length > 0 && (
424511
<div className="chapter-markers">
425512
{chapterMarkers.map((marker) => (
426-
<Tooltip key={marker.percent} title={marker.title}>
513+
<Tooltip
514+
key={marker.percent}
515+
title={marker.title}
516+
overlayClassName="chapter-tooltip"
517+
>
427518
<div
428519
className="chapter-marker"
429520
style={{ left: `${marker.percent}%` }}
521+
onMouseEnter={onMarkerMouseEnter}
522+
onMouseLeave={onMarkerMouseLeave}
430523
onClick={() => {
431524
playerRef.current?.seekTo(marker.frame);
432525
}}

0 commit comments

Comments
 (0)