mirror of
https://github.com/danog/telegram-tt.git
synced 2024-11-26 20:34:44 +01:00
Media Viewer: Better display for time ranges (#1779)
This commit is contained in:
parent
9ceea488f9
commit
1c50550079
@ -233,7 +233,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.seekline-buffered-progress,
|
||||
.seekline-play-progress {
|
||||
position: absolute;
|
||||
height: 2px;
|
||||
@ -252,8 +251,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.seekline-buffered-progress i {
|
||||
background-color: var(--color-interactive-buffered) !important;
|
||||
.seekline-buffered-progress {
|
||||
height: 2px;
|
||||
border-radius: 2px;
|
||||
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
|
||||
background-color: var(--color-interactive-buffered);
|
||||
}
|
||||
|
||||
.seekline-thumb {
|
||||
@ -352,6 +357,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.has-replies .Audio[dir="rtl"] {
|
||||
margin-bottom: 1.625rem;
|
||||
.has-replies .Audio {
|
||||
margin-bottom: 1rem;
|
||||
[dir="rtl"] {
|
||||
margin-bottom: 1.625rem;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import { getFileSizeString } from './helpers/documentInfo';
|
||||
import { decodeWaveform, interpolateArray } from '../../util/waveform';
|
||||
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import useBuffering from '../../hooks/useBuffering';
|
||||
import useBuffering, { BufferedRange } from '../../hooks/useBuffering';
|
||||
import useAudioPlayer from '../../hooks/useAudioPlayer';
|
||||
import useLang, { LangFn } from '../../hooks/useLang';
|
||||
import { captureEvents } from '../../util/captureEvents';
|
||||
@ -116,7 +116,7 @@ const Audio: FC<OwnProps> = ({
|
||||
}, []);
|
||||
|
||||
const {
|
||||
isBuffered, bufferedProgress, bufferingHandlers, checkBuffering,
|
||||
isBuffered, bufferedRanges, bufferingHandlers, checkBuffering,
|
||||
} = useBuffering();
|
||||
|
||||
const {
|
||||
@ -296,7 +296,7 @@ const Audio: FC<OwnProps> = ({
|
||||
<span className="duration with-seekline" dir="auto">
|
||||
{playProgress < 1 && `${formatMediaDuration(duration * playProgress, duration)}`}
|
||||
</span>
|
||||
{renderSeekline(playProgress, bufferedProgress, seekerRef)}
|
||||
{renderSeekline(playProgress, bufferedRanges, seekerRef)}
|
||||
</div>
|
||||
)}
|
||||
{!withSeekline && renderSecondLine()}
|
||||
@ -354,7 +354,7 @@ const Audio: FC<OwnProps> = ({
|
||||
duration,
|
||||
isPlaying,
|
||||
playProgress,
|
||||
bufferedProgress,
|
||||
bufferedRanges,
|
||||
seekerRef,
|
||||
(isDownloading || isUploading),
|
||||
date,
|
||||
@ -375,7 +375,7 @@ function renderAudio(
|
||||
duration: number,
|
||||
isPlaying: boolean,
|
||||
playProgress: number,
|
||||
bufferedProgress: number,
|
||||
bufferedRanges: BufferedRange[],
|
||||
seekerRef: React.Ref<HTMLElement>,
|
||||
showProgress?: boolean,
|
||||
date?: number,
|
||||
@ -396,7 +396,7 @@ function renderAudio(
|
||||
<span className="duration with-seekline" dir="auto">
|
||||
{formatMediaDuration(duration * playProgress, duration)}
|
||||
</span>
|
||||
{renderSeekline(playProgress, bufferedProgress, seekerRef)}
|
||||
{renderSeekline(playProgress, bufferedRanges, seekerRef)}
|
||||
</div>
|
||||
)}
|
||||
{!showSeekline && showProgress && (
|
||||
@ -499,7 +499,7 @@ function useWaveformCanvas(
|
||||
|
||||
function renderSeekline(
|
||||
playProgress: number,
|
||||
bufferedProgress: number,
|
||||
bufferedRanges: BufferedRange[],
|
||||
seekerRef: React.Ref<HTMLElement>,
|
||||
) {
|
||||
return (
|
||||
@ -507,11 +507,12 @@ function renderSeekline(
|
||||
className="seekline no-selection"
|
||||
ref={seekerRef as React.Ref<HTMLDivElement>}
|
||||
>
|
||||
<span className="seekline-buffered-progress">
|
||||
<i
|
||||
style={`transform: translateX(${bufferedProgress * 100}%)`}
|
||||
{bufferedRanges.map(({ start, end }) => (
|
||||
<div
|
||||
className="seekline-buffered-progress"
|
||||
style={`left: ${start * 100}%; right: ${100 - end * 100}%`}
|
||||
/>
|
||||
</span>
|
||||
))}
|
||||
<span className="seekline-play-progress">
|
||||
<i
|
||||
style={`transform: translateX(${playProgress * 100}%)`}
|
||||
|
@ -66,7 +66,9 @@ const VideoPlayer: FC<OwnProps> = ({
|
||||
|
||||
const [isFullscreen, setFullscreen, exitFullscreen] = useFullscreenStatus(videoRef, setIsPlayed);
|
||||
|
||||
const { isBuffered, bufferedProgress, bufferingHandlers } = useBuffering();
|
||||
const {
|
||||
isBuffered, bufferedRanges, bufferingHandlers, bufferedProgress,
|
||||
} = useBuffering();
|
||||
const {
|
||||
shouldRender: shouldRenderSpinner,
|
||||
transitionClassNames: spinnerClassNames,
|
||||
@ -229,6 +231,7 @@ const VideoPlayer: FC<OwnProps> = ({
|
||||
{!isGif && !shouldRenderSpinner && (
|
||||
<VideoPlayerControls
|
||||
isPlayed={isPlayed}
|
||||
bufferedRanges={bufferedRanges}
|
||||
bufferedProgress={bufferedProgress}
|
||||
isBuffered={isBuffered}
|
||||
currentTime={currentTime}
|
||||
|
@ -126,6 +126,7 @@
|
||||
}
|
||||
|
||||
&-buffered {
|
||||
position: absolute;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { formatMediaDuration } from '../../util/dateFormat';
|
||||
import formatFileSize from './helpers/formatFileSize';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import { BufferedRange } from '../../hooks/useBuffering';
|
||||
import { captureEvents } from '../../util/captureEvents';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
@ -18,6 +19,7 @@ import MenuItem from '../ui/MenuItem';
|
||||
import './VideoPlayerControls.scss';
|
||||
|
||||
type OwnProps = {
|
||||
bufferedRanges: BufferedRange[];
|
||||
bufferedProgress: number;
|
||||
currentTime: number;
|
||||
duration: number;
|
||||
@ -54,6 +56,7 @@ const PLAYBACK_RATES = [
|
||||
const HIDE_CONTROLS_TIMEOUT_MS = 1500;
|
||||
|
||||
const VideoPlayerControls: FC<OwnProps> = ({
|
||||
bufferedRanges,
|
||||
bufferedProgress,
|
||||
currentTime,
|
||||
duration,
|
||||
@ -156,7 +159,7 @@ const VideoPlayerControls: FC<OwnProps> = ({
|
||||
className={buildClassName('VideoPlayerControls', isForceMobileVersion && 'mobile', isVisible && 'active')}
|
||||
onClick={stopEvent}
|
||||
>
|
||||
{renderSeekLine(currentTime, duration, bufferedProgress, seekerRef)}
|
||||
{renderSeekLine(currentTime, duration, bufferedRanges, seekerRef)}
|
||||
<div className="buttons">
|
||||
<Button
|
||||
ariaLabel={lang('AccActionPlay')}
|
||||
@ -243,18 +246,19 @@ function renderFileSize(loadedPercent: number, totalSize: number) {
|
||||
}
|
||||
|
||||
function renderSeekLine(
|
||||
currentTime: number, duration: number, bufferedProgress: number, seekerRef: React.RefObject<HTMLDivElement>,
|
||||
currentTime: number, duration: number, bufferedRanges: BufferedRange[], seekerRef: React.RefObject<HTMLDivElement>,
|
||||
) {
|
||||
const percentagePlayed = (currentTime / duration) * 100;
|
||||
const percentageBuffered = bufferedProgress * 100;
|
||||
|
||||
return (
|
||||
<div className="player-seekline" ref={seekerRef}>
|
||||
<div className="player-seekline-track">
|
||||
<div
|
||||
className="player-seekline-buffered"
|
||||
style={`width: ${percentageBuffered || 0}%`}
|
||||
/>
|
||||
{bufferedRanges.map(({ start, end }) => (
|
||||
<div
|
||||
className="player-seekline-buffered"
|
||||
style={`left: ${start * 100}%; right: ${100 - end * 100}%`}
|
||||
/>
|
||||
))}
|
||||
<div
|
||||
className="player-seekline-played"
|
||||
style={`width: ${percentagePlayed || 0}%`}
|
||||
|
@ -8,9 +8,15 @@ const MIN_READY_STATE = 3;
|
||||
// Avoid flickering when re-mounting previously buffered video
|
||||
const DEBOUNCE = 200;
|
||||
|
||||
/**
|
||||
* Time range relative to the duration [0, 1]
|
||||
*/
|
||||
export type BufferedRange = { start: number; end: number };
|
||||
|
||||
const useBuffering = (noInitiallyBuffered = false) => {
|
||||
const [isBuffered, setIsBuffered] = useState(!noInitiallyBuffered);
|
||||
const [bufferedProgress, setBufferedProgress] = useState(0);
|
||||
const [bufferedRanges, setBufferedRanges] = useState<BufferedRange[]>([]);
|
||||
|
||||
const setIsBufferedDebounced = useMemo(() => {
|
||||
return debounce(setIsBuffered, DEBOUNCE, false, true);
|
||||
@ -21,7 +27,10 @@ const useBuffering = (noInitiallyBuffered = false) => {
|
||||
|
||||
if (!isSafariPatchInProgress(media)) {
|
||||
if (media.buffered.length) {
|
||||
setBufferedProgress(media.buffered.end(0) / media.duration);
|
||||
const ranges = getTimeRanges(media.buffered, media.duration);
|
||||
setBufferedRanges(ranges);
|
||||
const bufferedLength = ranges.reduce((acc, { start, end }) => acc + end - start, 0);
|
||||
setBufferedProgress(bufferedLength / media.duration);
|
||||
}
|
||||
|
||||
setIsBufferedDebounced(media.readyState >= MIN_READY_STATE || media.currentTime > 0);
|
||||
@ -40,6 +49,7 @@ const useBuffering = (noInitiallyBuffered = false) => {
|
||||
return {
|
||||
isBuffered,
|
||||
bufferedProgress,
|
||||
bufferedRanges,
|
||||
bufferingHandlers,
|
||||
checkBuffering(element: HTMLMediaElement) {
|
||||
setIsBufferedDebounced(element.readyState >= MIN_READY_STATE);
|
||||
@ -47,4 +57,15 @@ const useBuffering = (noInitiallyBuffered = false) => {
|
||||
};
|
||||
};
|
||||
|
||||
function getTimeRanges(ranges: TimeRanges, duration: number) {
|
||||
const result: BufferedRange[] = [];
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
result.push({
|
||||
start: ranges.start(i) / duration,
|
||||
end: ranges.end(i) / duration,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export default useBuffering;
|
||||
|
Loading…
Reference in New Issue
Block a user