Skip to content

Commit 6d51b1b

Browse files
authored
Merge pull request #113 from THEOplayer/bugfix/scrub-video-timeline
Scrubbing video timeline on iOS Safari
2 parents 6e41cde + 10b7435 commit 6d51b1b

4 files changed

Lines changed: 56 additions & 21 deletions

File tree

.changeset/tricky-teeth-rest.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@theoplayer/react-native-ui': patch
3+
---
4+
5+
Fixed an issue where the playhead on the `<SeekBar>` would occasionally snap back after scrubbing on iOS Safari.

src/ui/components/seekbar/SeekBar.tsx

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useChaptersTrack, useDebounce, useDuration, useSeekable } from '../../h
66
import { SingleThumbnailView } from './thumbnail/SingleThumbnailView';
77
import { useSlider } from './useSlider';
88
import { TestIDs } from '../../utils/TestIDs';
9+
import { SeekBarTouchHandler } from './SeekBarTouchHandler';
910
import { PlayerEventType, SeekedEvent } from 'react-native-theoplayer';
1011
import { fuzzyEquals } from '../../utils/NumberUtils';
1112

@@ -164,27 +165,29 @@ export const SeekBar = (props: SeekBarProps) => {
164165
onLayout={(event: LayoutChangeEvent) => {
165166
setWidth(event.nativeEvent.layout.width);
166167
}}>
167-
<Slider
168-
disabled={disabled}
169-
minimumValue={normalizedTime(seekableRange.start)}
170-
maximumValue={normalizedTime(seekableRange.end)}
171-
containerStyle={props.sliderContainerStyle ?? { marginHorizontal: 8 }}
172-
minimumTrackStyle={props.sliderMinimumTrackStyle ?? {}}
173-
maximumTrackStyle={props.sliderMaximumTrackStyle ?? {}}
174-
step={1000}
175-
renderAboveThumbComponent={renderAboveThumbComponent}
176-
onSlidingStart={onSlidingStart}
177-
onValueChange={onSlidingValueChange}
178-
onSlidingComplete={onSlidingComplete}
179-
value={sliderTime}
180-
minimumTrackTintColor={theme.colors.seekBarMinimum}
181-
maximumTrackTintColor={theme.colors.seekBarMaximum}
182-
thumbTintColor={theme.colors.seekBarDot}
183-
thumbStyle={StyleSheet.flatten(props.thumbStyle)}
184-
thumbTouchSize={props.thumbTouchSize}
185-
renderTrackMarkComponent={chapterMarkerTimes.length ? props.chapterMarkers : undefined}
186-
trackMarks={chapterMarkerTimes}
187-
/>
168+
<SeekBarTouchHandler>
169+
<Slider
170+
disabled={disabled}
171+
minimumValue={normalizedTime(seekableRange.start)}
172+
maximumValue={normalizedTime(seekableRange.end)}
173+
containerStyle={props.sliderContainerStyle ?? { marginHorizontal: 8 }}
174+
minimumTrackStyle={props.sliderMinimumTrackStyle ?? {}}
175+
maximumTrackStyle={props.sliderMaximumTrackStyle ?? {}}
176+
step={1000}
177+
renderAboveThumbComponent={renderAboveThumbComponent}
178+
onSlidingStart={onSlidingStart}
179+
onValueChange={onSlidingValueChange}
180+
onSlidingComplete={onSlidingComplete}
181+
value={sliderTime}
182+
minimumTrackTintColor={theme.colors.seekBarMinimum}
183+
maximumTrackTintColor={theme.colors.seekBarMaximum}
184+
thumbTintColor={theme.colors.seekBarDot}
185+
thumbStyle={StyleSheet.flatten(props.thumbStyle)}
186+
thumbTouchSize={props.thumbTouchSize}
187+
renderTrackMarkComponent={chapterMarkerTimes.length ? props.chapterMarkers : undefined}
188+
trackMarks={chapterMarkerTimes}
189+
/>
190+
</SeekBarTouchHandler>
188191
</View>
189192
);
190193
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import React, { PropsWithChildren } from 'react';
2+
3+
/**
4+
* Native passthrough component - no touch event handling needed on native platforms.
5+
*/
6+
export const SeekBarTouchHandler = ({ children }: PropsWithChildren) => {
7+
return <>{children}</>;
8+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React, { PropsWithChildren, useCallback } from 'react';
2+
import { GestureResponderEvent, View } from 'react-native';
3+
4+
/**
5+
* Web-specific wrapper that calls preventDefault on touchstart and touchend events.
6+
* This prevents iOS Safari from firing synthetic mouse events after touch events,
7+
* which would cause the slider to snap back to its original position during scrubbing.
8+
*/
9+
export const SeekBarTouchHandler = ({ children }: PropsWithChildren) => {
10+
const onTouchEvent = useCallback((e: GestureResponderEvent) => {
11+
e.preventDefault();
12+
}, []);
13+
14+
return (
15+
<View onTouchStart={onTouchEvent} onTouchEnd={onTouchEvent}>
16+
{children}
17+
</View>
18+
);
19+
};

0 commit comments

Comments
 (0)