mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
fix: seek jump back to current time (#22)
This commit is contained in:
@@ -2,7 +2,7 @@ export * from './factory';
|
||||
export { createMediaStore } from './media-store';
|
||||
export * from './state-mediators/playable';
|
||||
export * from './state-mediators/audible';
|
||||
export * from './utils/temporal';
|
||||
export * from './state-mediators/temporal';
|
||||
export * from './component-state-definitions/play-button';
|
||||
export * from './component-state-definitions/mute-button';
|
||||
export * from './component-state-definitions/volume-range';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createMediaStore as factory } from './factory';
|
||||
import { audible } from './state-mediators/audible';
|
||||
import { fullscreenable } from './state-mediators/fullscreenable';
|
||||
import { playable } from './state-mediators/playable';
|
||||
import { temporal } from './utils/temporal';
|
||||
import { temporal } from './state-mediators/temporal';
|
||||
|
||||
// Example of default media store with default state mediator definitions. (CJP)
|
||||
// NOTE: We can also change the API to take an array of stateMediators (or either/both) (CJP)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { audible } from './audible';
|
||||
export { playable } from './playable';
|
||||
export { temporal } from '../utils/temporal';
|
||||
export { temporal } from './temporal';
|
||||
export { fullscreenable } from './fullscreenable';
|
||||
|
||||
+1
-1
@@ -82,5 +82,5 @@ export const temporal = {
|
||||
return () => events.forEach((event) => media.removeEventListener(event, eventHandler));
|
||||
},
|
||||
] as const,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -26,13 +26,15 @@ export class TimeRangeRootBase extends HTMLElement {
|
||||
currentTime: number;
|
||||
duration: number;
|
||||
requestSeek: (time: number) => void;
|
||||
pointerPosition: number | null;
|
||||
hovering: boolean;
|
||||
dragging: boolean;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
_trackElement: HTMLElement | null = null;
|
||||
_pointerPosition: number | null = null;
|
||||
_hovering: boolean = false;
|
||||
_dragging: boolean = false;
|
||||
_seekingTime: number | null = null;
|
||||
_currentTime: number | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -45,6 +47,11 @@ export class TimeRangeRootBase extends HTMLElement {
|
||||
this.addEventListener('pointerleave', this);
|
||||
}
|
||||
|
||||
_setState(state: any): void {
|
||||
this._state = { ...this._state, ...state };
|
||||
this._update(useTimeRangeRootProps(this._state!, this), this._state!);
|
||||
}
|
||||
|
||||
handleEvent(event: Event): void {
|
||||
const { type } = event;
|
||||
const state = this._state;
|
||||
@@ -71,9 +78,11 @@ export class TimeRangeRootBase extends HTMLElement {
|
||||
|
||||
private _handlePointerDown(event: PointerEvent) {
|
||||
event.preventDefault();
|
||||
this._dragging = true;
|
||||
this._setState({ dragging: true });
|
||||
|
||||
const seekTime = calculateSeekTimeFromPointerEvent(event, this._state!.duration);
|
||||
this._state!.requestSeek(seekTime);
|
||||
this._seekingTime = seekTime;
|
||||
|
||||
// Capture pointer events
|
||||
this.setPointerCapture(event.pointerId);
|
||||
@@ -84,30 +93,32 @@ export class TimeRangeRootBase extends HTMLElement {
|
||||
|
||||
const rect = this._trackElement.getBoundingClientRect();
|
||||
const ratio = calculatePointerRatio(event.clientX, rect);
|
||||
this._pointerPosition = ratio;
|
||||
this._setState({ pointerPosition: ratio });
|
||||
|
||||
if (this._dragging) {
|
||||
if (this._state!.dragging) {
|
||||
const seekTime = calculateSeekTimeFromRatio(ratio, this._state!.duration);
|
||||
this._state!.requestSeek(seekTime);
|
||||
this._seekingTime = seekTime;
|
||||
}
|
||||
}
|
||||
|
||||
private _handlePointerUp(event: PointerEvent) {
|
||||
this.releasePointerCapture(event.pointerId);
|
||||
|
||||
if (this._dragging && this._trackElement && this._pointerPosition !== null) {
|
||||
const seekTime = calculateSeekTimeFromRatio(this._pointerPosition, this._state!.duration);
|
||||
if (this._state!.dragging && this._trackElement && this._state!.pointerPosition !== null) {
|
||||
const seekTime = calculateSeekTimeFromRatio(this._state!.pointerPosition, this._state!.duration);
|
||||
this._state!.requestSeek(seekTime);
|
||||
this._seekingTime = seekTime;
|
||||
}
|
||||
this._dragging = false;
|
||||
this._setState({ dragging: false });
|
||||
}
|
||||
|
||||
private _handlePointerEnter() {
|
||||
this._hovering = true;
|
||||
this._setState({ hovering: true });
|
||||
}
|
||||
|
||||
private _handlePointerLeave() {
|
||||
this._hovering = false;
|
||||
this._setState({ hovering: false });
|
||||
}
|
||||
|
||||
get currentTime(): number {
|
||||
@@ -119,24 +130,32 @@ export class TimeRangeRootBase extends HTMLElement {
|
||||
}
|
||||
|
||||
_update(props: any, state: any): void {
|
||||
this._state = state;
|
||||
this._state = { ...this._state, ...state };
|
||||
|
||||
// Find track element
|
||||
this._trackElement = this.querySelector('media-time-range-track') as HTMLElement;
|
||||
|
||||
// Calculate slider fill percentage
|
||||
const sliderFill =
|
||||
this._dragging && this._pointerPosition !== null
|
||||
? this._pointerPosition
|
||||
: state.duration > 0
|
||||
? (state.currentTime / state.duration) * 100
|
||||
: 0;
|
||||
|
||||
// When dragging, use pointer position for immediate feedback;
|
||||
// While seeking, use seeking time so it doesn't jump back to the current time;
|
||||
// Otherwise, use current time;
|
||||
let sliderFill = 0;
|
||||
if (state.dragging && state.pointerPosition !== null) {
|
||||
sliderFill = state.pointerPosition;
|
||||
} else if (state.duration > 0) {
|
||||
if (this._seekingTime !== null && this._currentTime === state.currentTime) {
|
||||
sliderFill = (this._seekingTime / state.duration) * 100;
|
||||
} else {
|
||||
sliderFill = (state.currentTime / state.duration) * 100;
|
||||
this._seekingTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
this._currentTime = state.currentTime;
|
||||
|
||||
// Update CSS custom properties
|
||||
this.style.setProperty('--slider-fill', `${Math.round(sliderFill)}%`);
|
||||
this.style.setProperty(
|
||||
'--slider-pointer',
|
||||
this._hovering && this._pointerPosition !== null ? `${Math.round(this._pointerPosition)}%` : '0%'
|
||||
this._state!.hovering && this._state!.pointerPosition !== null ? `${Math.round(this._state!.pointerPosition)}%` : '0%'
|
||||
);
|
||||
|
||||
// Update ARIA attributes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ConnectedComponent } from '../utils/component-factory';
|
||||
import type { PointerEvent, PropsWithChildren } from 'react';
|
||||
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState, useRef } from 'react';
|
||||
|
||||
import { timeRangeStateDefinition } from '@vjs-10/media-store';
|
||||
import { shallowEqual, useMediaSelector, useMediaStore } from '@vjs-10/react-media-store';
|
||||
@@ -79,13 +79,25 @@ export const useTimeRangeRootProps = (
|
||||
props: PropsWithChildren<{ [k: string]: any }>,
|
||||
state: ReturnType<typeof useTimeRangeRootState>
|
||||
) => {
|
||||
// When dragging, use pointer position for immediate feedback; otherwise use current time
|
||||
const sliderFill =
|
||||
state.dragging && state.pointerPosition !== null
|
||||
? state.pointerPosition
|
||||
: state.duration > 0
|
||||
? (state.currentTime / state.duration) * 100
|
||||
: 0;
|
||||
const seekingTime = useRef<number | null>(null);
|
||||
const currentTime = useRef<number | null>(null);
|
||||
|
||||
// When dragging, use pointer position for immediate feedback;
|
||||
// While seeking, use seeking time so it doesn't jump back to the current time;
|
||||
// Otherwise, use current time;
|
||||
let sliderFill = 0;
|
||||
if (state.dragging && state.pointerPosition !== null) {
|
||||
sliderFill = state.pointerPosition;
|
||||
} else if (state.duration > 0) {
|
||||
if (seekingTime.current !== null && currentTime.current === state.currentTime) {
|
||||
sliderFill = (seekingTime.current / state.duration) * 100;
|
||||
} else {
|
||||
sliderFill = (state.currentTime / state.duration) * 100;
|
||||
seekingTime.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
currentTime.current = state.currentTime;
|
||||
|
||||
const handlePointerDown = useCallback(
|
||||
(e: PointerEvent<HTMLDivElement>) => {
|
||||
@@ -93,6 +105,7 @@ export const useTimeRangeRootProps = (
|
||||
state.setDragging(true);
|
||||
const seekTime = calculateSeekTimeFromPointerEvent(e, state.duration);
|
||||
state.requestSeek(seekTime);
|
||||
seekingTime.current = seekTime;
|
||||
|
||||
// Capture pointer events to ensure we receive move and up events even if pointer leaves element
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
@@ -111,6 +124,7 @@ export const useTimeRangeRootProps = (
|
||||
if (state.dragging) {
|
||||
const seekTime = calculateSeekTimeFromRatio(ratio, state.duration);
|
||||
state.requestSeek(seekTime);
|
||||
seekingTime.current = seekTime;
|
||||
}
|
||||
},
|
||||
[state.trackRef, state.setPointerPosition, state.dragging, state.requestSeek, state.duration]
|
||||
@@ -123,6 +137,7 @@ export const useTimeRangeRootProps = (
|
||||
if (state.dragging && state.trackRef && state.pointerPosition !== null) {
|
||||
const seekTime = calculateSeekTimeFromRatio(state.pointerPosition, state.duration);
|
||||
state.requestSeek(seekTime);
|
||||
seekingTime.current = seekTime;
|
||||
}
|
||||
state.setDragging(false);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user