@chenzy-design/core 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-chat-dialogue.d.ts +300 -0
- package/dist/ai-chat-dialogue.d.ts.map +1 -0
- package/dist/ai-chat-dialogue.js +699 -0
- package/dist/ai-chat-dialogue.js.map +1 -0
- package/dist/ai-chat-dialogue.test.d.ts +2 -0
- package/dist/ai-chat-dialogue.test.d.ts.map +1 -0
- package/dist/ai-chat-dialogue.test.js +338 -0
- package/dist/ai-chat-dialogue.test.js.map +1 -0
- package/dist/ai-chat-input.d.ts +191 -0
- package/dist/ai-chat-input.d.ts.map +1 -0
- package/dist/ai-chat-input.js +274 -0
- package/dist/ai-chat-input.js.map +1 -0
- package/dist/ai-chat-input.test.d.ts +2 -0
- package/dist/ai-chat-input.test.d.ts.map +1 -0
- package/dist/ai-chat-input.test.js +339 -0
- package/dist/ai-chat-input.test.js.map +1 -0
- package/dist/audio-player.d.ts +154 -0
- package/dist/audio-player.d.ts.map +1 -0
- package/dist/audio-player.js +254 -0
- package/dist/audio-player.js.map +1 -0
- package/dist/audio-player.test.d.ts +2 -0
- package/dist/audio-player.test.d.ts.map +1 -0
- package/dist/audio-player.test.js +275 -0
- package/dist/audio-player.test.js.map +1 -0
- package/dist/back-top.d.ts +4 -1
- package/dist/back-top.d.ts.map +1 -1
- package/dist/back-top.js +5 -2
- package/dist/back-top.js.map +1 -1
- package/dist/back-top.test.js +3 -2
- package/dist/back-top.test.js.map +1 -1
- package/dist/calendar.d.ts +132 -44
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +413 -70
- package/dist/calendar.js.map +1 -1
- package/dist/calendar.test.js +160 -76
- package/dist/calendar.test.js.map +1 -1
- package/dist/chat.d.ts +172 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +210 -0
- package/dist/chat.js.map +1 -0
- package/dist/chat.test.d.ts +2 -0
- package/dist/chat.test.d.ts.map +1 -0
- package/dist/chat.test.js +181 -0
- package/dist/chat.test.js.map +1 -0
- package/dist/code-highlight.d.ts +30 -0
- package/dist/code-highlight.d.ts.map +1 -0
- package/dist/code-highlight.js +37 -0
- package/dist/code-highlight.js.map +1 -0
- package/dist/code-highlight.test.d.ts +2 -0
- package/dist/code-highlight.test.d.ts.map +1 -0
- package/dist/code-highlight.test.js +36 -0
- package/dist/code-highlight.test.js.map +1 -0
- package/dist/collapsible.d.ts +41 -0
- package/dist/collapsible.d.ts.map +1 -0
- package/dist/collapsible.js +33 -0
- package/dist/collapsible.js.map +1 -0
- package/dist/collapsible.test.d.ts +2 -0
- package/dist/collapsible.test.d.ts.map +1 -0
- package/dist/collapsible.test.js +57 -0
- package/dist/collapsible.test.js.map +1 -0
- package/dist/color-value.d.ts +60 -0
- package/dist/color-value.d.ts.map +1 -0
- package/dist/color-value.js +165 -0
- package/dist/color-value.js.map +1 -0
- package/dist/color-value.test.d.ts +2 -0
- package/dist/color-value.test.d.ts.map +1 -0
- package/dist/color-value.test.js +104 -0
- package/dist/color-value.test.js.map +1 -0
- package/dist/config-provider.d.ts +76 -37
- package/dist/config-provider.d.ts.map +1 -1
- package/dist/config-provider.js +107 -32
- package/dist/config-provider.js.map +1 -1
- package/dist/config-provider.test.js +123 -60
- package/dist/config-provider.test.js.map +1 -1
- package/dist/cropper.d.ts +164 -0
- package/dist/cropper.d.ts.map +1 -0
- package/dist/cropper.js +841 -0
- package/dist/cropper.js.map +1 -0
- package/dist/cropper.test.d.ts +2 -0
- package/dist/cropper.test.d.ts.map +1 -0
- package/dist/cropper.test.js +259 -0
- package/dist/cropper.test.js.map +1 -0
- package/dist/drag-move.d.ts +119 -0
- package/dist/drag-move.d.ts.map +1 -0
- package/dist/drag-move.js +237 -0
- package/dist/drag-move.js.map +1 -0
- package/dist/drag-move.test.d.ts +2 -0
- package/dist/drag-move.test.d.ts.map +1 -0
- package/dist/drag-move.test.js +304 -0
- package/dist/drag-move.test.js.map +1 -0
- package/dist/editable.d.ts +24 -4
- package/dist/editable.d.ts.map +1 -1
- package/dist/editable.js +18 -0
- package/dist/editable.js.map +1 -1
- package/dist/editable.test.js +55 -2
- package/dist/editable.test.js.map +1 -1
- package/dist/ellipsis.d.ts +13 -0
- package/dist/ellipsis.d.ts.map +1 -1
- package/dist/ellipsis.js +36 -0
- package/dist/ellipsis.js.map +1 -1
- package/dist/ellipsis.test.js +37 -1
- package/dist/ellipsis.test.js.map +1 -1
- package/dist/form-path.d.ts +19 -0
- package/dist/form-path.d.ts.map +1 -0
- package/dist/form-path.js +97 -0
- package/dist/form-path.js.map +1 -0
- package/dist/form-path.test.d.ts +2 -0
- package/dist/form-path.test.d.ts.map +1 -0
- package/dist/form-path.test.js +57 -0
- package/dist/form-path.test.js.map +1 -0
- package/dist/form-validate.d.ts +13 -0
- package/dist/form-validate.d.ts.map +1 -0
- package/dist/form-validate.js +204 -0
- package/dist/form-validate.js.map +1 -0
- package/dist/form.d.ts +91 -18
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +170 -115
- package/dist/form.js.map +1 -1
- package/dist/form.test.js +349 -76
- package/dist/form.test.js.map +1 -1
- package/dist/highlight.d.ts +42 -32
- package/dist/highlight.d.ts.map +1 -1
- package/dist/highlight.js +67 -41
- package/dist/highlight.js.map +1 -1
- package/dist/highlight.test.js +73 -32
- package/dist/highlight.test.js.map +1 -1
- package/dist/hotkeys.d.ts +151 -0
- package/dist/hotkeys.d.ts.map +1 -0
- package/dist/hotkeys.js +188 -0
- package/dist/hotkeys.js.map +1 -0
- package/dist/hotkeys.test.d.ts +2 -0
- package/dist/hotkeys.test.d.ts.map +1 -0
- package/dist/hotkeys.test.js +170 -0
- package/dist/hotkeys.test.js.map +1 -0
- package/dist/index.d.ts +33 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -13
- package/dist/index.js.map +1 -1
- package/dist/input.d.ts +34 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +54 -0
- package/dist/input.js.map +1 -0
- package/dist/input.test.d.ts +2 -0
- package/dist/input.test.d.ts.map +1 -0
- package/dist/input.test.js +56 -0
- package/dist/input.test.js.map +1 -0
- package/dist/markdown-render.d.ts +49 -0
- package/dist/markdown-render.d.ts.map +1 -0
- package/dist/markdown-render.js +61 -0
- package/dist/markdown-render.js.map +1 -0
- package/dist/markdown-render.test.d.ts +2 -0
- package/dist/markdown-render.test.d.ts.map +1 -0
- package/dist/markdown-render.test.js +106 -0
- package/dist/markdown-render.test.js.map +1 -0
- package/dist/notification.d.ts +40 -31
- package/dist/notification.d.ts.map +1 -1
- package/dist/notification.js +86 -70
- package/dist/notification.js.map +1 -1
- package/dist/notification.test.js +62 -39
- package/dist/notification.test.js.map +1 -1
- package/dist/numeral.d.ts +32 -0
- package/dist/numeral.d.ts.map +1 -0
- package/dist/numeral.js +67 -0
- package/dist/numeral.js.map +1 -0
- package/dist/numeral.test.d.ts +2 -0
- package/dist/numeral.test.d.ts.map +1 -0
- package/dist/numeral.test.js +59 -0
- package/dist/numeral.test.js.map +1 -0
- package/dist/overflow-list.d.ts +9 -40
- package/dist/overflow-list.d.ts.map +1 -1
- package/dist/overflow-list.js +9 -70
- package/dist/overflow-list.js.map +1 -1
- package/dist/overflow-list.test.js +1 -122
- package/dist/overflow-list.test.js.map +1 -1
- package/dist/pagination.d.ts +17 -0
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +48 -0
- package/dist/pagination.js.map +1 -1
- package/dist/pagination.test.js +40 -1
- package/dist/pagination.test.js.map +1 -1
- package/dist/pincode.d.ts +79 -0
- package/dist/pincode.d.ts.map +1 -0
- package/dist/pincode.js +119 -0
- package/dist/pincode.js.map +1 -0
- package/dist/pincode.test.d.ts +2 -0
- package/dist/pincode.test.d.ts.map +1 -0
- package/dist/pincode.test.js +151 -0
- package/dist/pincode.test.js.map +1 -0
- package/dist/progress.d.ts +32 -30
- package/dist/progress.d.ts.map +1 -1
- package/dist/progress.js +257 -64
- package/dist/progress.js.map +1 -1
- package/dist/progress.test.js +50 -47
- package/dist/progress.test.js.map +1 -1
- package/dist/resizable.d.ts +200 -0
- package/dist/resizable.d.ts.map +1 -0
- package/dist/resizable.js +329 -0
- package/dist/resizable.js.map +1 -0
- package/dist/resizable.test.d.ts +2 -0
- package/dist/resizable.test.d.ts.map +1 -0
- package/dist/resizable.test.js +302 -0
- package/dist/resizable.test.js.map +1 -0
- package/dist/scroll-list.d.ts +69 -62
- package/dist/scroll-list.d.ts.map +1 -1
- package/dist/scroll-list.js +103 -144
- package/dist/scroll-list.js.map +1 -1
- package/dist/scroll-list.test.js +100 -110
- package/dist/scroll-list.test.js.map +1 -1
- package/dist/sidebar.d.ts +46 -0
- package/dist/sidebar.d.ts.map +1 -0
- package/dist/sidebar.js +65 -0
- package/dist/sidebar.js.map +1 -0
- package/dist/sidebar.test.d.ts +2 -0
- package/dist/sidebar.test.d.ts.map +1 -0
- package/dist/sidebar.test.js +88 -0
- package/dist/sidebar.test.js.map +1 -0
- package/dist/spin.d.ts +5 -5
- package/dist/spin.d.ts.map +1 -1
- package/dist/spin.js +13 -39
- package/dist/spin.js.map +1 -1
- package/dist/spin.test.js +2 -26
- package/dist/spin.test.js.map +1 -1
- package/dist/toast.d.ts +33 -30
- package/dist/toast.d.ts.map +1 -1
- package/dist/toast.js +77 -72
- package/dist/toast.js.map +1 -1
- package/dist/toast.test.js +63 -37
- package/dist/toast.test.js.map +1 -1
- package/dist/tree.d.ts +22 -5
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +61 -9
- package/dist/tree.js.map +1 -1
- package/dist/tree.test.js +72 -1
- package/dist/tree.test.js.map +1 -1
- package/dist/user-guide.d.ts +73 -0
- package/dist/user-guide.d.ts.map +1 -0
- package/dist/user-guide.js +95 -0
- package/dist/user-guide.js.map +1 -0
- package/dist/user-guide.test.d.ts +2 -0
- package/dist/user-guide.test.d.ts.map +1 -0
- package/dist/user-guide.test.js +109 -0
- package/dist/user-guide.test.js.map +1 -0
- package/dist/video-player.d.ts +196 -0
- package/dist/video-player.d.ts.map +1 -0
- package/dist/video-player.js +461 -0
- package/dist/video-player.js.map +1 -0
- package/dist/video-player.test.d.ts +2 -0
- package/dist/video-player.test.d.ts.map +1 -0
- package/dist/video-player.test.js +435 -0
- package/dist/video-player.test.js.map +1 -0
- package/package.json +17 -1
- package/dist/banner.d.ts +0 -19
- package/dist/banner.d.ts.map +0 -1
- package/dist/banner.js +0 -20
- package/dist/banner.js.map +0 -1
- package/dist/banner.test.d.ts +0 -2
- package/dist/banner.test.d.ts.map +0 -1
- package/dist/banner.test.js +0 -29
- package/dist/banner.test.js.map +0 -1
- package/dist/breakpoints.d.ts +0 -30
- package/dist/breakpoints.d.ts.map +0 -1
- package/dist/breakpoints.js +0 -45
- package/dist/breakpoints.js.map +0 -1
- package/dist/breakpoints.test.d.ts +0 -2
- package/dist/breakpoints.test.d.ts.map +0 -1
- package/dist/breakpoints.test.js +0 -46
- package/dist/breakpoints.test.js.map +0 -1
- package/dist/lottie-icon.d.ts +0 -88
- package/dist/lottie-icon.d.ts.map +0 -1
- package/dist/lottie-icon.js +0 -84
- package/dist/lottie-icon.js.map +0 -1
- package/dist/lottie-icon.test.d.ts +0 -2
- package/dist/lottie-icon.test.d.ts.map +0 -1
- package/dist/lottie-icon.test.js +0 -88
- package/dist/lottie-icon.test.js.map +0 -1
- package/dist/sider.d.ts +0 -26
- package/dist/sider.d.ts.map +0 -1
- package/dist/sider.js +0 -55
- package/dist/sider.js.map +0 -1
- package/dist/sider.test.d.ts +0 -2
- package/dist/sider.test.d.ts.map +0 -1
- package/dist/sider.test.js +0 -29
- package/dist/sider.test.js.map +0 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* video-player — framework-agnostic state machine for a native <video> player,
|
|
3
|
+
* ported 1:1 from Semi Design's VideoPlayerFoundation + VideoProgressFoundation.
|
|
4
|
+
*
|
|
5
|
+
* ZERO framework deps. The render layer injects an adapter (getVideo /
|
|
6
|
+
* getVideoWrapper + notify callbacks + state setters) and forwards native media
|
|
7
|
+
* events (timeupdate / durationchange / progress / ended / error / canplay /
|
|
8
|
+
* waiting / stalled / leavepictureinpicture) and document keydown /
|
|
9
|
+
* fullscreenchange into the foundation methods. All time/volume clamping,
|
|
10
|
+
* play-rate/quality/route switching, the keyboard map (Space / ← / →) and the
|
|
11
|
+
* progress-bar drag→time math live here so any MVVM layer can reuse them.
|
|
12
|
+
*
|
|
13
|
+
* The foundation NEVER reads the DOM except through the adapter's getVideo /
|
|
14
|
+
* getVideoWrapper accessors (which return the live element or null), matching
|
|
15
|
+
* Semi. No document.* is touched here beyond fullscreen APIs invoked through
|
|
16
|
+
* the video wrapper the adapter hands us — event (un)registration is delegated
|
|
17
|
+
* to the render layer, so this module stays library-agnostic and SSR-safe.
|
|
18
|
+
*
|
|
19
|
+
* See specs Semi videoPlayer.foundation.js / progressFoundation.js.
|
|
20
|
+
*/
|
|
21
|
+
/** A localized string bundle the foundation needs for transient notifications. */
|
|
22
|
+
export interface VideoPlayerLocale {
|
|
23
|
+
/** shown while the media is buffering (native `waiting`) */
|
|
24
|
+
loading: string;
|
|
25
|
+
/** shown when playback stalls (native `stalled`) */
|
|
26
|
+
stall: string;
|
|
27
|
+
/** mirror-on notification */
|
|
28
|
+
mirror: string;
|
|
29
|
+
/** mirror-off notification */
|
|
30
|
+
cancelMirror: string;
|
|
31
|
+
/** rate-change notification template, contains `${rate}` */
|
|
32
|
+
rateChange: string;
|
|
33
|
+
/** quality-change notification template, contains `${quality}` */
|
|
34
|
+
qualityChange: string;
|
|
35
|
+
/** route-change notification template, contains `${route}` */
|
|
36
|
+
routeChange: string;
|
|
37
|
+
}
|
|
38
|
+
/** A `{ label, value }` option for the playback-rate menu. */
|
|
39
|
+
export interface PlaybackRateOption {
|
|
40
|
+
label: string;
|
|
41
|
+
value: number;
|
|
42
|
+
}
|
|
43
|
+
/** A `{ label, value }` option for the quality / route menus. */
|
|
44
|
+
export interface LabeledOption {
|
|
45
|
+
label: string;
|
|
46
|
+
value: string;
|
|
47
|
+
}
|
|
48
|
+
/** A chapter marker on the progress track. */
|
|
49
|
+
export interface VideoMarker {
|
|
50
|
+
/** start time in seconds */
|
|
51
|
+
start: number;
|
|
52
|
+
/** chapter title */
|
|
53
|
+
title?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Adapter the render layer implements. Mirrors Semi's VideoPlayerAdapter:
|
|
57
|
+
* two element accessors, a set of `notify*` callbacks (fired outward to the
|
|
58
|
+
* host's `onXxx` props) and a set of `set*` state mutators (fired inward to
|
|
59
|
+
* the render layer's reactive state).
|
|
60
|
+
*/
|
|
61
|
+
export interface VideoPlayerAdapter {
|
|
62
|
+
getVideo: () => HTMLVideoElement | null;
|
|
63
|
+
getVideoWrapper: () => HTMLElement | null;
|
|
64
|
+
notifyPlay: () => void;
|
|
65
|
+
notifyPause: () => void;
|
|
66
|
+
notifyQualityChange: (quality: string) => void;
|
|
67
|
+
notifyRateChange: (rate: number) => void;
|
|
68
|
+
notifyRouteChange: (route: string) => void;
|
|
69
|
+
notifyVolumeChange: (volume: number) => void;
|
|
70
|
+
setBufferedValue: (bufferedValue: number) => void;
|
|
71
|
+
setCurrentTime: (currentTime: number) => void;
|
|
72
|
+
setIsError: (isError: boolean) => void;
|
|
73
|
+
setIsMirror: (isMirror: boolean) => void;
|
|
74
|
+
setIsPlaying: (isPlaying: boolean) => void;
|
|
75
|
+
setMuted: (muted: boolean) => void;
|
|
76
|
+
setNotificationContent: (content: string) => void;
|
|
77
|
+
setPlaybackRate: (rate: number) => void;
|
|
78
|
+
setQuality: (quality: string) => void;
|
|
79
|
+
setRoute: (route: string) => void;
|
|
80
|
+
setShowControls: (showControls: boolean) => void;
|
|
81
|
+
setShowNotification: (showNotification: boolean) => void;
|
|
82
|
+
setTotalTime: (totalTime: number) => void;
|
|
83
|
+
setVolume: (volume: number) => void;
|
|
84
|
+
}
|
|
85
|
+
/** Reactive state the foundation reads back through `getState`. */
|
|
86
|
+
export interface VideoPlayerState {
|
|
87
|
+
isPlaying: boolean;
|
|
88
|
+
muted: boolean;
|
|
89
|
+
volume: number;
|
|
90
|
+
currentTime: number;
|
|
91
|
+
isMirror: boolean;
|
|
92
|
+
}
|
|
93
|
+
/** Host props the foundation reads through `getProps`. */
|
|
94
|
+
export interface VideoPlayerProps {
|
|
95
|
+
volume: number;
|
|
96
|
+
muted: boolean;
|
|
97
|
+
seekTime: number;
|
|
98
|
+
controlsList: string[];
|
|
99
|
+
}
|
|
100
|
+
export interface VideoPlayerFoundationOptions {
|
|
101
|
+
adapter: VideoPlayerAdapter;
|
|
102
|
+
getState: () => VideoPlayerState;
|
|
103
|
+
getProps: () => VideoPlayerProps;
|
|
104
|
+
/** how long (ms) auto-hide controls waits after the last mouse move (Semi: 3000) */
|
|
105
|
+
controlsHideDelay?: number;
|
|
106
|
+
/** how long (ms) a transient notification stays up (Semi: 1000) */
|
|
107
|
+
notificationDuration?: number;
|
|
108
|
+
}
|
|
109
|
+
/** Default control items, in Semi's order. */
|
|
110
|
+
export declare const DEFAULT_CONTROLS_LIST: string[];
|
|
111
|
+
/** Semi's default 6-tier playback-rate menu (2.0 → 0.75). */
|
|
112
|
+
export declare const DEFAULT_PLAYBACK_RATE_LIST: PlaybackRateOption[];
|
|
113
|
+
export declare const VIDEO_PLAYER_NUMBERS: {
|
|
114
|
+
readonly DEFAULT_VOLUME: 100;
|
|
115
|
+
readonly DEFAULT_SEEK_TIME: 10;
|
|
116
|
+
readonly DEFAULT_VOLUME_STEP: 10;
|
|
117
|
+
readonly DEFAULT_PLAYBACK_RATE: 1;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Create the VideoPlayer foundation. Returns an object of methods the render
|
|
121
|
+
* layer wires to DOM events + UI controls. Pure w.r.t. framework; the only
|
|
122
|
+
* side effects are through the injected adapter, timers, and fullscreen /
|
|
123
|
+
* PiP browser APIs reached via the elements the adapter returns.
|
|
124
|
+
*/
|
|
125
|
+
export declare function createVideoPlayerFoundation(options: VideoPlayerFoundationOptions): {
|
|
126
|
+
init: () => void;
|
|
127
|
+
destroy: () => void;
|
|
128
|
+
clearTimer: () => void;
|
|
129
|
+
shouldShowControlItem: (name: string) => boolean;
|
|
130
|
+
checkFullScreen: () => boolean;
|
|
131
|
+
handleVideoPlay: () => void;
|
|
132
|
+
handleVideoPause: () => void;
|
|
133
|
+
handleCanPlay: () => void;
|
|
134
|
+
handleWaiting: (locale: VideoPlayerLocale) => void;
|
|
135
|
+
handleStalled: (locale: VideoPlayerLocale) => void;
|
|
136
|
+
handleProgress: () => void;
|
|
137
|
+
handleEnded: () => void;
|
|
138
|
+
handleTimeUpdate: () => void;
|
|
139
|
+
handleDurationChange: () => void;
|
|
140
|
+
handleError: () => void;
|
|
141
|
+
handleLeavePictureInPicture: () => void;
|
|
142
|
+
handleMouseMove: () => void;
|
|
143
|
+
handleMouseEnterWrapper: () => void;
|
|
144
|
+
handleMouseLeaveWrapper: () => void;
|
|
145
|
+
handlePlay: () => void;
|
|
146
|
+
handlePause: () => void;
|
|
147
|
+
handlePlayOrPause: () => void;
|
|
148
|
+
handleTimeChange: (value: number) => void;
|
|
149
|
+
handleVolumeChange: (value: number) => void;
|
|
150
|
+
handleVolumeSilent: () => void;
|
|
151
|
+
handleFullscreen: () => void;
|
|
152
|
+
handleFullscreenChange: () => void;
|
|
153
|
+
handleMirror: (locale: VideoPlayerLocale) => void;
|
|
154
|
+
handlePictureInPicture: () => void;
|
|
155
|
+
handleRateChange: (rate: PlaybackRateOption, locale: VideoPlayerLocale) => void;
|
|
156
|
+
handleQualityChange: (quality: LabeledOption, locale: VideoPlayerLocale) => void;
|
|
157
|
+
handleRouteChange: (route: LabeledOption, locale: VideoPlayerLocale) => void;
|
|
158
|
+
handleTemporaryNotification: (content: string) => void;
|
|
159
|
+
handleBodyKeyDown: (e: KeyboardEvent) => void;
|
|
160
|
+
};
|
|
161
|
+
export type VideoPlayerFoundation = ReturnType<typeof createVideoPlayerFoundation>;
|
|
162
|
+
/** Clamp a pointer X to a [0,1] fraction of a slider rect, then to a value. */
|
|
163
|
+
export declare function progressValueFromPointer(clientX: number, rect: {
|
|
164
|
+
left: number;
|
|
165
|
+
width: number;
|
|
166
|
+
}, max: number): {
|
|
167
|
+
percentage: number;
|
|
168
|
+
value: number;
|
|
169
|
+
offset: number;
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Width (as CSS length) of the `value` overlay within one marker segment.
|
|
173
|
+
* Returns `'0%'` before the segment, a clamped `calc(100% - 2px)` past it, and
|
|
174
|
+
* a percentage inside it — matching Semi's getValueWidth.
|
|
175
|
+
*/
|
|
176
|
+
export declare function segmentValueWidth(marker: {
|
|
177
|
+
start: number;
|
|
178
|
+
end: number;
|
|
179
|
+
}, value: number): string;
|
|
180
|
+
/**
|
|
181
|
+
* Build contiguous `[start, end)` marker segments across the whole duration
|
|
182
|
+
* from Semi-style chapter markers (each with only a `start`). The last segment
|
|
183
|
+
* runs to `totalTime`. When there are no markers a single full-width segment is
|
|
184
|
+
* returned so the progress bar always has exactly one track to render.
|
|
185
|
+
*/
|
|
186
|
+
export declare function buildMarkerSegments(markers: VideoMarker[], totalTime: number): Array<{
|
|
187
|
+
start: number;
|
|
188
|
+
end: number;
|
|
189
|
+
title?: string;
|
|
190
|
+
}>;
|
|
191
|
+
/**
|
|
192
|
+
* Format seconds as `MM:SS` / `H:MM:SS`(严格对齐 Semi utils.formatTime):
|
|
193
|
+
* NaN/非法 → '00:00';≥1h → `H:MM:SS`;否则 `MM:SS`(分钟也补零)。
|
|
194
|
+
*/
|
|
195
|
+
export declare function formatTimeDisplay(seconds: number): string;
|
|
196
|
+
//# sourceMappingURL=video-player.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-player.d.ts","sourceRoot":"","sources":["../src/video-player.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* video-player — framework-agnostic state machine for a native <video> player,
|
|
3
|
+
* ported 1:1 from Semi Design's VideoPlayerFoundation + VideoProgressFoundation.
|
|
4
|
+
*
|
|
5
|
+
* ZERO framework deps. The render layer injects an adapter (getVideo /
|
|
6
|
+
* getVideoWrapper + notify callbacks + state setters) and forwards native media
|
|
7
|
+
* events (timeupdate / durationchange / progress / ended / error / canplay /
|
|
8
|
+
* waiting / stalled / leavepictureinpicture) and document keydown /
|
|
9
|
+
* fullscreenchange into the foundation methods. All time/volume clamping,
|
|
10
|
+
* play-rate/quality/route switching, the keyboard map (Space / ← / →) and the
|
|
11
|
+
* progress-bar drag→time math live here so any MVVM layer can reuse them.
|
|
12
|
+
*
|
|
13
|
+
* The foundation NEVER reads the DOM except through the adapter's getVideo /
|
|
14
|
+
* getVideoWrapper accessors (which return the live element or null), matching
|
|
15
|
+
* Semi. No document.* is touched here beyond fullscreen APIs invoked through
|
|
16
|
+
* the video wrapper the adapter hands us — event (un)registration is delegated
|
|
17
|
+
* to the render layer, so this module stays library-agnostic and SSR-safe.
|
|
18
|
+
*
|
|
19
|
+
* See specs Semi videoPlayer.foundation.js / progressFoundation.js.
|
|
20
|
+
*/
|
|
21
|
+
/** Default control items, in Semi's order. */
|
|
22
|
+
export const DEFAULT_CONTROLS_LIST = [
|
|
23
|
+
'play',
|
|
24
|
+
'next',
|
|
25
|
+
'time',
|
|
26
|
+
'volume',
|
|
27
|
+
'playbackRate',
|
|
28
|
+
'quality',
|
|
29
|
+
'route',
|
|
30
|
+
'mirror',
|
|
31
|
+
'fullscreen',
|
|
32
|
+
'pictureInPicture',
|
|
33
|
+
];
|
|
34
|
+
/** Semi's default 6-tier playback-rate menu (2.0 → 0.75). */
|
|
35
|
+
export const DEFAULT_PLAYBACK_RATE_LIST = [
|
|
36
|
+
{ label: '2.0x', value: 2 },
|
|
37
|
+
{ label: '1.5x', value: 1.5 },
|
|
38
|
+
{ label: '1.25x', value: 1.25 },
|
|
39
|
+
{ label: '1.0x', value: 1 },
|
|
40
|
+
{ label: '0.75x', value: 0.75 },
|
|
41
|
+
];
|
|
42
|
+
export const VIDEO_PLAYER_NUMBERS = {
|
|
43
|
+
DEFAULT_VOLUME: 100,
|
|
44
|
+
DEFAULT_SEEK_TIME: 10,
|
|
45
|
+
DEFAULT_VOLUME_STEP: 10,
|
|
46
|
+
DEFAULT_PLAYBACK_RATE: 1,
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Create the VideoPlayer foundation. Returns an object of methods the render
|
|
50
|
+
* layer wires to DOM events + UI controls. Pure w.r.t. framework; the only
|
|
51
|
+
* side effects are through the injected adapter, timers, and fullscreen /
|
|
52
|
+
* PiP browser APIs reached via the elements the adapter returns.
|
|
53
|
+
*/
|
|
54
|
+
export function createVideoPlayerFoundation(options) {
|
|
55
|
+
const { adapter, getState, getProps } = options;
|
|
56
|
+
const controlsHideDelay = options.controlsHideDelay ?? 3000;
|
|
57
|
+
const notificationDuration = options.notificationDuration ?? 1000;
|
|
58
|
+
let controlsTimer = null;
|
|
59
|
+
let notificationTimer = null;
|
|
60
|
+
let mouseMoveThrottleTs = 0;
|
|
61
|
+
let scrollPosition = null;
|
|
62
|
+
function clearTimer() {
|
|
63
|
+
if (controlsTimer !== null) {
|
|
64
|
+
clearTimeout(controlsTimer);
|
|
65
|
+
controlsTimer = null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function clearNotificationTimer() {
|
|
69
|
+
if (notificationTimer !== null) {
|
|
70
|
+
clearTimeout(notificationTimer);
|
|
71
|
+
notificationTimer = null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// --- controls auto-hide (Semi throttles mousemove at 200ms) ---
|
|
75
|
+
function handleMouseMove() {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
if (now - mouseMoveThrottleTs < 200)
|
|
78
|
+
return;
|
|
79
|
+
mouseMoveThrottleTs = now;
|
|
80
|
+
adapter.setShowControls(true);
|
|
81
|
+
clearTimer();
|
|
82
|
+
controlsTimer = setTimeout(() => {
|
|
83
|
+
adapter.setShowControls(false);
|
|
84
|
+
}, controlsHideDelay);
|
|
85
|
+
}
|
|
86
|
+
function handleMouseEnterWrapper() {
|
|
87
|
+
adapter.setShowControls(true);
|
|
88
|
+
}
|
|
89
|
+
function handleMouseLeaveWrapper() {
|
|
90
|
+
if (getState().isPlaying) {
|
|
91
|
+
adapter.setShowControls(false);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// --- native media events → state ---
|
|
95
|
+
function handleVideoPlay() {
|
|
96
|
+
adapter.setIsPlaying(true);
|
|
97
|
+
adapter.notifyPlay();
|
|
98
|
+
}
|
|
99
|
+
function handleVideoPause() {
|
|
100
|
+
adapter.setIsPlaying(false);
|
|
101
|
+
adapter.notifyPause();
|
|
102
|
+
}
|
|
103
|
+
function handleCanPlay() {
|
|
104
|
+
adapter.setShowNotification(false);
|
|
105
|
+
}
|
|
106
|
+
function handleWaiting(locale) {
|
|
107
|
+
adapter.setNotificationContent(locale.loading);
|
|
108
|
+
adapter.setShowNotification(true);
|
|
109
|
+
}
|
|
110
|
+
function handleStalled(locale) {
|
|
111
|
+
adapter.setNotificationContent(locale.stall);
|
|
112
|
+
adapter.setShowNotification(true);
|
|
113
|
+
}
|
|
114
|
+
function handleProgress() {
|
|
115
|
+
const video = adapter.getVideo();
|
|
116
|
+
if (video && video.buffered.length > 0) {
|
|
117
|
+
const bufferedEnd = video.buffered.end(video.buffered.length - 1);
|
|
118
|
+
adapter.setBufferedValue(bufferedEnd);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function handleEnded() {
|
|
122
|
+
adapter.setIsPlaying(false);
|
|
123
|
+
adapter.setShowControls(true);
|
|
124
|
+
}
|
|
125
|
+
function handleTimeUpdate() {
|
|
126
|
+
const video = adapter.getVideo();
|
|
127
|
+
if (!video)
|
|
128
|
+
return;
|
|
129
|
+
adapter.setCurrentTime(video.currentTime);
|
|
130
|
+
}
|
|
131
|
+
function handleDurationChange() {
|
|
132
|
+
const video = adapter.getVideo();
|
|
133
|
+
if (!video)
|
|
134
|
+
return;
|
|
135
|
+
adapter.setTotalTime(video.duration);
|
|
136
|
+
}
|
|
137
|
+
function handleError() {
|
|
138
|
+
adapter.setIsError(true);
|
|
139
|
+
}
|
|
140
|
+
// --- play / pause ---
|
|
141
|
+
function handlePlay() {
|
|
142
|
+
const video = adapter.getVideo();
|
|
143
|
+
if (video) {
|
|
144
|
+
// native onPlay → handleVideoPlay updates isPlaying
|
|
145
|
+
void video.play();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function handlePause() {
|
|
149
|
+
const video = adapter.getVideo();
|
|
150
|
+
if (video) {
|
|
151
|
+
video.pause();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function handlePlayOrPause() {
|
|
155
|
+
const video = adapter.getVideo();
|
|
156
|
+
if (!video)
|
|
157
|
+
return;
|
|
158
|
+
if (video.paused)
|
|
159
|
+
handlePlay();
|
|
160
|
+
else
|
|
161
|
+
handlePause();
|
|
162
|
+
}
|
|
163
|
+
// --- time / seek (with NaN guard, matching Semi) ---
|
|
164
|
+
function handleTimeChange(value) {
|
|
165
|
+
const video = adapter.getVideo();
|
|
166
|
+
if (!video)
|
|
167
|
+
return;
|
|
168
|
+
if (!Number.isNaN(value)) {
|
|
169
|
+
video.currentTime = value;
|
|
170
|
+
adapter.setCurrentTime(value);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// --- volume (0–100 floor + mute derivation, matching Semi) ---
|
|
174
|
+
function handleVolumeChange(value) {
|
|
175
|
+
const video = adapter.getVideo();
|
|
176
|
+
if (!video)
|
|
177
|
+
return;
|
|
178
|
+
const volume = Math.floor(value > 0 ? value : 0);
|
|
179
|
+
video.volume = volume / 100;
|
|
180
|
+
adapter.setVolume(volume);
|
|
181
|
+
adapter.setMuted(volume === 0);
|
|
182
|
+
// NB: Semi's handleVolumeChange does not fire notifyVolumeChange; the
|
|
183
|
+
// onVolumeChange callback is wired by the render layer to the slider's
|
|
184
|
+
// own change event. Kept faithful — no notify here.
|
|
185
|
+
}
|
|
186
|
+
/** Toggle mute: restore previous volume when muted, else drop to 0. */
|
|
187
|
+
function handleVolumeSilent() {
|
|
188
|
+
const video = adapter.getVideo();
|
|
189
|
+
if (!video)
|
|
190
|
+
return;
|
|
191
|
+
const { volume, muted } = getState();
|
|
192
|
+
if (muted) {
|
|
193
|
+
video.volume = volume / 100;
|
|
194
|
+
adapter.setVolume(volume);
|
|
195
|
+
adapter.setMuted(false);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
video.volume = 0;
|
|
199
|
+
adapter.setMuted(true);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// --- fullscreen ---
|
|
203
|
+
function checkFullScreen() {
|
|
204
|
+
const videoWrapper = adapter.getVideoWrapper();
|
|
205
|
+
if (!videoWrapper)
|
|
206
|
+
return false;
|
|
207
|
+
const doc = document;
|
|
208
|
+
const el = videoWrapper;
|
|
209
|
+
return !!(doc.fullscreenElement === videoWrapper ||
|
|
210
|
+
doc.webkitFullscreenElement === videoWrapper ||
|
|
211
|
+
doc.mozFullScreenElement === videoWrapper ||
|
|
212
|
+
doc.msFullscreenElement === videoWrapper ||
|
|
213
|
+
el.webkitDisplayingFullscreen // iOS Safari
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
function handleFullscreen() {
|
|
217
|
+
const videoWrapper = adapter.getVideoWrapper();
|
|
218
|
+
if (!videoWrapper)
|
|
219
|
+
return;
|
|
220
|
+
if (checkFullScreen()) {
|
|
221
|
+
void document.exitFullscreen();
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
scrollPosition = { x: window.scrollX, y: window.scrollY };
|
|
225
|
+
void videoWrapper.requestFullscreen();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function handleFullscreenChange() {
|
|
229
|
+
if (checkFullScreen()) {
|
|
230
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
// Esc or button exit both land here; restore the pre-fullscreen scroll.
|
|
234
|
+
if (scrollPosition) {
|
|
235
|
+
const pos = scrollPosition;
|
|
236
|
+
setTimeout(() => {
|
|
237
|
+
window.scrollTo(pos.x, pos.y);
|
|
238
|
+
scrollPosition = null;
|
|
239
|
+
}, 0);
|
|
240
|
+
}
|
|
241
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// --- mirror ---
|
|
245
|
+
function handleMirror(locale) {
|
|
246
|
+
const { isMirror } = getState();
|
|
247
|
+
const next = !isMirror;
|
|
248
|
+
adapter.setIsMirror(next);
|
|
249
|
+
handleTemporaryNotification(next ? locale.mirror : locale.cancelMirror);
|
|
250
|
+
}
|
|
251
|
+
// --- picture-in-picture ---
|
|
252
|
+
function handlePictureInPicture() {
|
|
253
|
+
const video = adapter.getVideo();
|
|
254
|
+
if (!video)
|
|
255
|
+
return;
|
|
256
|
+
void video.requestPictureInPicture();
|
|
257
|
+
}
|
|
258
|
+
function handleLeavePictureInPicture() {
|
|
259
|
+
const video = adapter.getVideo();
|
|
260
|
+
if (!video)
|
|
261
|
+
return;
|
|
262
|
+
adapter.setIsPlaying(!video.paused);
|
|
263
|
+
}
|
|
264
|
+
// --- rate / quality / route ---
|
|
265
|
+
function handleRateChange(rate, locale) {
|
|
266
|
+
const video = adapter.getVideo();
|
|
267
|
+
if (!video)
|
|
268
|
+
return;
|
|
269
|
+
video.playbackRate = rate.value;
|
|
270
|
+
adapter.setPlaybackRate(rate.value);
|
|
271
|
+
adapter.notifyRateChange(rate.value);
|
|
272
|
+
handleTemporaryNotification(locale.rateChange.replace('${rate}', rate.label));
|
|
273
|
+
}
|
|
274
|
+
function handleQualityChange(quality, locale) {
|
|
275
|
+
adapter.setQuality(quality.value);
|
|
276
|
+
adapter.notifyQualityChange(quality.value);
|
|
277
|
+
handleTemporaryNotification(locale.qualityChange.replace('${quality}', quality.label));
|
|
278
|
+
restorePlayPosition();
|
|
279
|
+
}
|
|
280
|
+
function handleRouteChange(route, locale) {
|
|
281
|
+
adapter.setRoute(route.value);
|
|
282
|
+
adapter.notifyRouteChange(route.value);
|
|
283
|
+
handleTemporaryNotification(locale.routeChange.replace('${route}', route.label));
|
|
284
|
+
restorePlayPosition();
|
|
285
|
+
}
|
|
286
|
+
/** After a src swap, seek back to the previous position and resume if it was playing. */
|
|
287
|
+
function restorePlayPosition() {
|
|
288
|
+
const video = adapter.getVideo();
|
|
289
|
+
if (!video)
|
|
290
|
+
return;
|
|
291
|
+
const wasPlaying = !video.paused;
|
|
292
|
+
const currentTime = video.currentTime;
|
|
293
|
+
const handleLoaded = () => {
|
|
294
|
+
video.currentTime = currentTime;
|
|
295
|
+
if (wasPlaying)
|
|
296
|
+
void video.play();
|
|
297
|
+
video.removeEventListener('loadeddata', handleLoaded);
|
|
298
|
+
};
|
|
299
|
+
video.addEventListener('loadeddata', handleLoaded);
|
|
300
|
+
}
|
|
301
|
+
// --- transient notification ---
|
|
302
|
+
function handleTemporaryNotification(content) {
|
|
303
|
+
adapter.setNotificationContent(content);
|
|
304
|
+
adapter.setShowNotification(true);
|
|
305
|
+
clearNotificationTimer();
|
|
306
|
+
notificationTimer = setTimeout(() => {
|
|
307
|
+
adapter.setShowNotification(false);
|
|
308
|
+
notificationTimer = null;
|
|
309
|
+
}, notificationDuration);
|
|
310
|
+
}
|
|
311
|
+
// --- keyboard (Space play/pause, ←/→ seek by seekTime) ---
|
|
312
|
+
function handleBodyKeyDown(e) {
|
|
313
|
+
const videoWrapper = adapter.getVideoWrapper();
|
|
314
|
+
// Only respond when focus is inside the player, so we don't hijack keys
|
|
315
|
+
// from other interactive elements on the page (Semi behavior).
|
|
316
|
+
if (videoWrapper && !videoWrapper.contains(document.activeElement)) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const { currentTime } = getState();
|
|
320
|
+
const { seekTime } = getProps();
|
|
321
|
+
if (e.key === ' ') {
|
|
322
|
+
handlePlayOrPause();
|
|
323
|
+
}
|
|
324
|
+
else if (e.key === 'ArrowLeft') {
|
|
325
|
+
handleTimeChange(currentTime - seekTime);
|
|
326
|
+
}
|
|
327
|
+
else if (e.key === 'ArrowRight') {
|
|
328
|
+
handleTimeChange(currentTime + seekTime);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// --- lifecycle ---
|
|
332
|
+
function init() {
|
|
333
|
+
const { volume, muted } = getProps();
|
|
334
|
+
const video = adapter.getVideo();
|
|
335
|
+
if (video) {
|
|
336
|
+
adapter.setTotalTime(video.duration);
|
|
337
|
+
handleVolumeChange(muted ? 0 : volume);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function destroy() {
|
|
341
|
+
clearTimer();
|
|
342
|
+
clearNotificationTimer();
|
|
343
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
344
|
+
}
|
|
345
|
+
function shouldShowControlItem(name) {
|
|
346
|
+
return getProps().controlsList.includes(name);
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
init,
|
|
350
|
+
destroy,
|
|
351
|
+
clearTimer,
|
|
352
|
+
shouldShowControlItem,
|
|
353
|
+
checkFullScreen,
|
|
354
|
+
// media events
|
|
355
|
+
handleVideoPlay,
|
|
356
|
+
handleVideoPause,
|
|
357
|
+
handleCanPlay,
|
|
358
|
+
handleWaiting,
|
|
359
|
+
handleStalled,
|
|
360
|
+
handleProgress,
|
|
361
|
+
handleEnded,
|
|
362
|
+
handleTimeUpdate,
|
|
363
|
+
handleDurationChange,
|
|
364
|
+
handleError,
|
|
365
|
+
handleLeavePictureInPicture,
|
|
366
|
+
// controls
|
|
367
|
+
handleMouseMove,
|
|
368
|
+
handleMouseEnterWrapper,
|
|
369
|
+
handleMouseLeaveWrapper,
|
|
370
|
+
handlePlay,
|
|
371
|
+
handlePause,
|
|
372
|
+
handlePlayOrPause,
|
|
373
|
+
handleTimeChange,
|
|
374
|
+
handleVolumeChange,
|
|
375
|
+
handleVolumeSilent,
|
|
376
|
+
handleFullscreen,
|
|
377
|
+
handleFullscreenChange,
|
|
378
|
+
handleMirror,
|
|
379
|
+
handlePictureInPicture,
|
|
380
|
+
handleRateChange,
|
|
381
|
+
handleQualityChange,
|
|
382
|
+
handleRouteChange,
|
|
383
|
+
handleTemporaryNotification,
|
|
384
|
+
handleBodyKeyDown,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
388
|
+
// Progress-bar drag → time math (ported from VideoProgressFoundation).
|
|
389
|
+
// Pure geometry helpers the render layer calls on pointer events; no DOM
|
|
390
|
+
// beyond the ClientRect the caller passes in.
|
|
391
|
+
// ---------------------------------------------------------------------------
|
|
392
|
+
/** Clamp a pointer X to a [0,1] fraction of a slider rect, then to a value. */
|
|
393
|
+
export function progressValueFromPointer(clientX, rect, max) {
|
|
394
|
+
const offset = clientX - rect.left;
|
|
395
|
+
const total = rect.width;
|
|
396
|
+
const percentage = total > 0 ? Math.min(Math.max(offset / total, 0), 1) : 0;
|
|
397
|
+
const value = percentage * max;
|
|
398
|
+
return { percentage, value, offset: offset - rect.width / 2 };
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Width (as CSS length) of the `value` overlay within one marker segment.
|
|
402
|
+
* Returns `'0%'` before the segment, a clamped `calc(100% - 2px)` past it, and
|
|
403
|
+
* a percentage inside it — matching Semi's getValueWidth.
|
|
404
|
+
*/
|
|
405
|
+
export function segmentValueWidth(marker, value) {
|
|
406
|
+
const { start, end } = marker;
|
|
407
|
+
if (value > end)
|
|
408
|
+
return 'calc(100% - 2px)';
|
|
409
|
+
if (value < start)
|
|
410
|
+
return '0%';
|
|
411
|
+
if (end === start)
|
|
412
|
+
return '0%';
|
|
413
|
+
return `${((value - start) / (end - start)) * 100}%`;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Build contiguous `[start, end)` marker segments across the whole duration
|
|
417
|
+
* from Semi-style chapter markers (each with only a `start`). The last segment
|
|
418
|
+
* runs to `totalTime`. When there are no markers a single full-width segment is
|
|
419
|
+
* returned so the progress bar always has exactly one track to render.
|
|
420
|
+
*/
|
|
421
|
+
export function buildMarkerSegments(markers, totalTime) {
|
|
422
|
+
const total = Number.isFinite(totalTime) && totalTime > 0 ? totalTime : 0;
|
|
423
|
+
if (!markers || markers.length === 0) {
|
|
424
|
+
return [{ start: 0, end: total }];
|
|
425
|
+
}
|
|
426
|
+
const sorted = [...markers]
|
|
427
|
+
.filter((m) => Number.isFinite(m.start) && m.start >= 0)
|
|
428
|
+
.sort((a, b) => a.start - b.start);
|
|
429
|
+
if (sorted.length === 0)
|
|
430
|
+
return [{ start: 0, end: total }];
|
|
431
|
+
return sorted.map((m, i) => {
|
|
432
|
+
const next = sorted[i + 1];
|
|
433
|
+
const end = next ? next.start : total;
|
|
434
|
+
const seg = {
|
|
435
|
+
start: m.start,
|
|
436
|
+
end: Math.max(end, m.start),
|
|
437
|
+
};
|
|
438
|
+
if (m.title !== undefined)
|
|
439
|
+
seg.title = m.title;
|
|
440
|
+
return seg;
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Format seconds as `MM:SS` / `H:MM:SS`(严格对齐 Semi utils.formatTime):
|
|
445
|
+
* NaN/非法 → '00:00';≥1h → `H:MM:SS`;否则 `MM:SS`(分钟也补零)。
|
|
446
|
+
*/
|
|
447
|
+
export function formatTimeDisplay(seconds) {
|
|
448
|
+
if (!Number.isFinite(seconds) || seconds < 0)
|
|
449
|
+
return '00:00';
|
|
450
|
+
const total = Math.floor(seconds);
|
|
451
|
+
const h = Math.floor(total / 3600);
|
|
452
|
+
const m = Math.floor((total % 3600) / 60);
|
|
453
|
+
const s = total % 60;
|
|
454
|
+
const ss = String(s).padStart(2, '0');
|
|
455
|
+
const mm = String(m).padStart(2, '0');
|
|
456
|
+
if (h > 0) {
|
|
457
|
+
return `${h}:${mm}:${ss}`;
|
|
458
|
+
}
|
|
459
|
+
return `${mm}:${ss}`;
|
|
460
|
+
}
|
|
461
|
+
//# sourceMappingURL=video-player.js.map
|