@theoplayer/react-native-ui
Version:
A React Native UI for @theoplayer/react-native
136 lines (135 loc) • 5.15 kB
JavaScript
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { StyleSheet, View } from 'react-native';
import { PlayerContext } from '../util/PlayerContext';
import { Slider } from '@miblanchard/react-native-slider';
import { useChaptersTrack, useDebounce, useDuration, useSeekable } from '../../hooks/barrel';
import { SingleThumbnailView } from './thumbnail/SingleThumbnailView';
import { useSlider } from './useSlider';
import { TestIDs } from '../../utils/TestIDs';
import { PlayerEventType } from 'react-native-theoplayer';
import { fuzzyEquals } from '../../utils/NumberUtils';
import { jsx as _jsx } from "react/jsx-runtime";
const SEEKED_TOLERANCE = 1e3;
const WAIT_FOR_SEEKED_TIMEOUT = 8e3;
/**
* The delay in milliseconds before an actual seek is executed while scrubbing the SeekBar.
*/
const DEBOUNCE_SEEK_DELAY = 250;
const renderThumbnailView = (isScrubbing, scrubberTime, seekBarWidth) => {
return isScrubbing && scrubberTime !== undefined && /*#__PURE__*/_jsx(SingleThumbnailView, {
currentTime: scrubberTime,
seekBarWidth: seekBarWidth
});
};
export const SeekBar = props => {
const {
onScrubbing,
renderAboveThumbComponent: customRenderAboveThumbComponent
} = props;
const {
player,
style: theme,
adInProgress
} = useContext(PlayerContext);
const [width, setWidth] = useState(0);
const [seekTarget, setSeekTarget] = useState(undefined);
const duration = useDuration();
const seekable = useSeekable();
const [sliderTime, isScrubbing, setIsScrubbing] = useSlider();
const chapters = useChaptersTrack();
const chapterMarkerTimes = chapters?.cues?.map(cue => cue.endTime).slice(0, -1) ?? [];
// Do not continuously seek while dragging the slider
const debounceSeek = useDebounce(value => {
player.currentTime = value;
}, DEBOUNCE_SEEK_DELAY);
const onSlidingStart = useCallback(([value]) => {
setIsScrubbing(true);
debounceSeek(value);
}, [setIsScrubbing, debounceSeek]);
const onSlidingValueChange = useCallback(([value]) => {
setSeekTarget(value);
if (isScrubbing) {
if (onScrubbing) onScrubbing(value);
debounceSeek(value);
}
}, [isScrubbing, onScrubbing, debounceSeek, setSeekTarget]);
const onSlidingComplete = useCallback(([value]) => {
setSeekTarget(value);
debounceSeek(value, true);
}, [setSeekTarget, debounceSeek]);
useEffect(() => {
if (seekTarget === undefined) return;
const onSeeked = event => {
if (!fuzzyEquals(event.currentTime, seekTarget, SEEKED_TOLERANCE)) {
return;
}
cleanup();
setIsScrubbing(false);
setSeekTarget(undefined);
};
const cleanup = () => {
clearTimeout(timeout);
player.removeEventListener(PlayerEventType.SEEKED, onSeeked);
};
player.addEventListener(PlayerEventType.SEEKED, onSeeked);
const timeout = setTimeout(cleanup, WAIT_FOR_SEEKED_TIMEOUT);
return cleanup;
}, [player, seekTarget, setIsScrubbing]);
const normalizedDuration = normalizedTime(duration);
const seekableRange = {
start: seekable.length > 0 ? seekable[0].start : 0,
end: seekable.length > 0 ? seekable[seekable.length - 1].end : normalizedDuration
};
const renderAboveThumbComponent = (_index, value) => {
if (customRenderAboveThumbComponent) {
return customRenderAboveThumbComponent(isScrubbing, value, width);
}
return renderThumbnailView(isScrubbing, value, width);
};
/**
* Disable the seekbar:
* - while playing an ad;
* - if duration === 0;
* - if seekable length === 0;
*
* Do not disable seekbar for live content.
*/
const isLive = duration === Infinity;
const disabled = !(normalizedDuration > 0 || isLive) && seekable.length > 0 || adInProgress;
return /*#__PURE__*/_jsx(View, {
style: [props.style ?? {
flex: 1
}],
testID: props.testID ?? TestIDs.SEEK_BAR,
onLayout: event => {
setWidth(event.nativeEvent.layout.width);
},
children: /*#__PURE__*/_jsx(Slider, {
disabled: disabled,
minimumValue: normalizedTime(seekableRange.start),
maximumValue: normalizedTime(seekableRange.end),
containerStyle: props.sliderContainerStyle ?? {
marginHorizontal: 8
},
minimumTrackStyle: props.sliderMinimumTrackStyle ?? {},
maximumTrackStyle: props.sliderMaximumTrackStyle ?? {},
step: 1000,
renderAboveThumbComponent: renderAboveThumbComponent,
onSlidingStart: onSlidingStart,
onValueChange: onSlidingValueChange,
onSlidingComplete: onSlidingComplete,
value: sliderTime,
minimumTrackTintColor: theme.colors.seekBarMinimum,
maximumTrackTintColor: theme.colors.seekBarMaximum,
thumbTintColor: theme.colors.seekBarDot,
thumbStyle: StyleSheet.flatten(props.thumbStyle),
thumbTouchSize: props.thumbTouchSize,
renderTrackMarkComponent: chapterMarkerTimes.length ? props.chapterMarkers : undefined,
trackMarks: chapterMarkerTimes
})
});
};
function normalizedTime(time) {
return isNaN(time) || !isFinite(time) ? 0 : Math.max(0, time);
}
//# sourceMappingURL=SeekBar.js.map