UNPKG

@theoplayer/react-native-ui

Version:

A React Native UI for @theoplayer/react-native

136 lines (135 loc) 5.15 kB
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