mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(core): implement temporal state management for time-based media controls
Add temporal state mediator with currentTime, duration, and seekable properties ported from media-chrome architecture. Includes proper input validation, media event bindings, and seek request handling. - Add temporal.ts state mediator with time-based functionality - Update StateMediator type to include temporal properties - Integrate temporal module into default media store - Export temporal module from package index 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
Christian Pillsbury
co-authored by
Claude
parent
370762f500
commit
1e12f66f16
@@ -42,6 +42,9 @@ export type StateMediator = {
|
||||
muted: FacadeProp<HTMLMediaElement['muted']>;
|
||||
volume: FacadeProp<HTMLMediaElement['volume']>;
|
||||
volumeLevel: ReadonlyFacadeProp<'high' | 'medium' | 'low' | 'off'>;
|
||||
currentTime: FacadeProp<HTMLMediaElement['currentTime']>;
|
||||
duration: ReadonlyFacadeProp<HTMLMediaElement['duration']>;
|
||||
seekable: ReadonlyFacadeProp<[number, number] | undefined>;
|
||||
};
|
||||
|
||||
export function createMediaStore({
|
||||
|
||||
@@ -2,5 +2,6 @@ export * from './factory';
|
||||
export { createMediaStore } from './media-store';
|
||||
export * from './state-mediators/playable';
|
||||
export * from './state-mediators/audible';
|
||||
export * from './state-mediators/temporal';
|
||||
export * from './component-state-definitions/play-button';
|
||||
export * from './component-state-definitions/mute-button';
|
||||
@@ -1,9 +1,10 @@
|
||||
import { playable } from './state-mediators/playable';
|
||||
import { audible } from './state-mediators/audible';
|
||||
import { temporal } from './state-mediators/temporal';
|
||||
import { createMediaStore as factory } from './factory';
|
||||
// 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)
|
||||
const stateMediator = { ...playable, ...audible };
|
||||
const stateMediator = { ...playable, ...audible, ...temporal };
|
||||
type Params = Partial<Parameters<typeof factory>[0]>;
|
||||
export const createMediaStore = (params: Params = {}) =>
|
||||
factory({ stateMediator, ...params });
|
||||
@@ -0,0 +1,58 @@
|
||||
const isValidNumber = (value: any): value is number => {
|
||||
return typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value);
|
||||
};
|
||||
|
||||
export const temporal = {
|
||||
currentTime: {
|
||||
get(stateOwners: any) {
|
||||
const { media } = stateOwners;
|
||||
return media?.currentTime ?? 0;
|
||||
},
|
||||
set(value: number, stateOwners: any) {
|
||||
const { media } = stateOwners;
|
||||
if (!media || !isValidNumber(value)) return;
|
||||
media.currentTime = value;
|
||||
},
|
||||
mediaEvents: ['timeupdate', 'loadedmetadata'],
|
||||
actions: {
|
||||
/** @TODO Support more sophisticated seeking patterns like seek-to-live, relative seeking, etc. (CJP) */
|
||||
seekrequest: (
|
||||
{ detail }: Pick<CustomEvent<any>, 'detail'> = { detail: 0 },
|
||||
) => +detail,
|
||||
},
|
||||
},
|
||||
|
||||
duration: {
|
||||
get(stateOwners: any) {
|
||||
const { media } = stateOwners;
|
||||
|
||||
// Return 0 if no media or invalid duration
|
||||
if (!media?.duration || Number.isNaN(media.duration) || !Number.isFinite(media.duration)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return media.duration;
|
||||
},
|
||||
mediaEvents: ['loadedmetadata', 'durationchange', 'emptied'],
|
||||
},
|
||||
|
||||
seekable: {
|
||||
get(stateOwners: any): [number, number] | undefined {
|
||||
const { media } = stateOwners;
|
||||
|
||||
if (!media?.seekable?.length) return undefined;
|
||||
|
||||
const start = media.seekable.start(0);
|
||||
const end = media.seekable.end(media.seekable.length - 1);
|
||||
|
||||
// Account for cases where metadata has an "empty" seekable range
|
||||
if (!start && !end) return undefined;
|
||||
|
||||
return [
|
||||
Number(start.toFixed(3)),
|
||||
Number(end.toFixed(3))
|
||||
];
|
||||
},
|
||||
mediaEvents: ['loadedmetadata', 'emptied', 'progress', 'seekablechange'],
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user