mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(packages): add pauseOnDrag to time slider (#1596)
Co-authored-by: shiminshen <16914659+shiminshen@users.noreply.github.com>
This commit is contained in:
co-authored by
shiminshen
parent
058fb8cfdd
commit
131e176dde
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { MediaBufferState, MediaTimeState } from '../../../media/state';
|
||||
import type { MediaBufferState, MediaPlaybackState, MediaTimeState } from '../../../media/state';
|
||||
import type { SliderInput } from '../../slider/slider-core';
|
||||
import { TimeSliderCore } from '../time-slider-core';
|
||||
|
||||
@@ -43,6 +43,7 @@ describe('TimeSliderCore', () => {
|
||||
min: 0,
|
||||
max: 100,
|
||||
changeThrottle: 100,
|
||||
pauseOnDrag: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -242,4 +243,76 @@ describe('TimeSliderCore', () => {
|
||||
expect(state.orientation).toBe('vertical');
|
||||
});
|
||||
});
|
||||
|
||||
describe('startDrag/endDrag', () => {
|
||||
function createPlaybackState(overrides: Partial<MediaPlaybackState> = {}): MediaPlaybackState {
|
||||
return {
|
||||
paused: false,
|
||||
ended: false,
|
||||
started: true,
|
||||
waiting: false,
|
||||
play: vi.fn(async () => {}),
|
||||
pause: vi.fn(),
|
||||
togglePaused: vi.fn(() => false),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
it('does nothing when pauseOnDrag is false (default)', () => {
|
||||
const core = new TimeSliderCore();
|
||||
const playback = createPlaybackState();
|
||||
|
||||
core.startDrag(playback);
|
||||
expect(playback.pause).not.toHaveBeenCalled();
|
||||
|
||||
core.endDrag(playback);
|
||||
expect(playback.play).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('pauses on startDrag and resumes on endDrag when playing', () => {
|
||||
const core = new TimeSliderCore({ pauseOnDrag: true });
|
||||
const playback = createPlaybackState();
|
||||
|
||||
core.startDrag(playback);
|
||||
expect(playback.pause).toHaveBeenCalledTimes(1);
|
||||
|
||||
core.endDrag(playback);
|
||||
expect(playback.play).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not resume on endDrag when playback was already paused', () => {
|
||||
const core = new TimeSliderCore({ pauseOnDrag: true });
|
||||
const playback = createPlaybackState({ paused: true });
|
||||
|
||||
core.startDrag(playback);
|
||||
expect(playback.pause).not.toHaveBeenCalled();
|
||||
|
||||
core.endDrag(playback);
|
||||
expect(playback.play).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('resumes on endDrag even if pauseOnDrag is turned off mid-drag', () => {
|
||||
const core = new TimeSliderCore({ pauseOnDrag: true });
|
||||
const playback = createPlaybackState();
|
||||
|
||||
core.startDrag(playback);
|
||||
expect(playback.pause).toHaveBeenCalledTimes(1);
|
||||
|
||||
core.setProps({ pauseOnDrag: false });
|
||||
|
||||
core.endDrag(playback);
|
||||
expect(playback.play).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not resume on a second endDrag', () => {
|
||||
const core = new TimeSliderCore({ pauseOnDrag: true });
|
||||
const playback = createPlaybackState();
|
||||
|
||||
core.startDrag(playback);
|
||||
core.endDrag(playback);
|
||||
core.endDrag(playback);
|
||||
|
||||
expect(playback.play).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defaults } from '@videojs/utils/object';
|
||||
import { formatTimeAsPhrase } from '@videojs/utils/time';
|
||||
import type { NonNullableObject } from '@videojs/utils/types';
|
||||
|
||||
import type { MediaBufferState, MediaTimeState } from '../../media/state';
|
||||
import type { MediaBufferState, MediaPlaybackState, MediaTimeState } from '../../media/state';
|
||||
import { SliderCore, type SliderProps, type SliderState } from '../slider/slider-core';
|
||||
|
||||
export interface TimeSliderProps extends SliderProps {
|
||||
@@ -14,6 +14,11 @@ export interface TimeSliderProps extends SliderProps {
|
||||
max?: number | undefined;
|
||||
/** Leading+trailing throttle (ms) for `onValueChange` during drag. */
|
||||
changeThrottle?: number | undefined;
|
||||
/**
|
||||
* When true, pause playback while the user is dragging the thumb,
|
||||
* resuming on release if it was playing before.
|
||||
*/
|
||||
pauseOnDrag?: boolean | undefined;
|
||||
}
|
||||
|
||||
export interface TimeSliderState extends SliderState, Pick<MediaTimeState, 'currentTime' | 'duration' | 'seeking'> {
|
||||
@@ -27,10 +32,12 @@ export class TimeSliderCore extends SliderCore {
|
||||
...SliderCore.defaultProps,
|
||||
label: 'Seek',
|
||||
changeThrottle: 100,
|
||||
pauseOnDrag: false,
|
||||
};
|
||||
|
||||
#props = { ...TimeSliderCore.defaultProps };
|
||||
#media: (MediaTimeState & MediaBufferState) | null = null;
|
||||
#wasPlayingBeforeDrag = false;
|
||||
|
||||
constructor(props?: TimeSliderProps) {
|
||||
super();
|
||||
@@ -72,6 +79,32 @@ export class TimeSliderCore extends SliderCore {
|
||||
return super.getLabel(state) || 'Seek';
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause playback when a drag begins if `pauseOnDrag` is enabled, remembering
|
||||
* whether media was playing so `endDrag` can resume it.
|
||||
*/
|
||||
startDrag(playback: MediaPlaybackState | null | undefined): void {
|
||||
this.#wasPlayingBeforeDrag = false;
|
||||
if (this.#props.pauseOnDrag && playback && !playback.paused) {
|
||||
this.#wasPlayingBeforeDrag = true;
|
||||
playback.pause();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume playback if `startDrag` paused it. Resume depends only on the intent
|
||||
* captured at drag start, so it survives `pauseOnDrag` being toggled mid-drag.
|
||||
* Safe to call on teardown — a no-op unless a drag paused playback.
|
||||
*/
|
||||
endDrag(playback: MediaPlaybackState | null | undefined): void {
|
||||
if (this.#wasPlayingBeforeDrag) {
|
||||
playback?.play().catch(() => {
|
||||
// Resume play() can reject (autoplay policy, etc.) — surface via existing error feature.
|
||||
});
|
||||
}
|
||||
this.#wasPlayingBeforeDrag = false;
|
||||
}
|
||||
|
||||
override getAttrs(state: TimeSliderState) {
|
||||
const base = super.getAttrs(state);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user