Skip to content

Commit f190651

Browse files
committed
feat(player): new clipStartTime and clipEndTime player props
1 parent 23fd34f commit f190651

14 files changed

Lines changed: 248 additions & 94 deletions

File tree

packages/react/src/hooks/options/use-chapter-options.ts

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
22

33
import { effect, type StopEffect } from 'maverick.js';
4-
import { useReactContext } from 'maverick.js/react';
4+
import { useReactContext, useSignal } from 'maverick.js/react';
55
import type { VTTCue } from 'media-captions';
66
import { formatSpokenTime, formatTime, mediaContext } from 'vidstack';
77

@@ -15,50 +15,58 @@ import { useTextCues } from '../use-text-cues';
1515
export function useChapterOptions(): ChapterOptions {
1616
const media = useReactContext(mediaContext)!,
1717
track = useActiveTextTrack('chapters'),
18-
cues = useTextCues(track);
18+
cues = useTextCues(track),
19+
$startTime = useSignal(media.$state.clipStartTime),
20+
$endTime = useSignal(media.$state.clipEndTime) || Infinity;
1921

2022
useActiveTextCues(track);
2123

2224
return React.useMemo(() => {
2325
const options = track
24-
? cues.map<ChapterOption>((cue, i) => {
25-
let currentRef: HTMLElement | null = null,
26-
stopProgressEffect: StopEffect | undefined;
27-
return {
28-
cue,
29-
label: cue.text,
30-
value: i + '',
31-
startTimeText: formatTime(cue.startTime, false),
32-
durationText: formatSpokenTime(cue.endTime - cue.startTime),
33-
get selected() {
34-
return cue === track.activeCues[0];
35-
},
36-
setProgressVar(ref) {
37-
if (!ref || cue !== track.activeCues[0]) {
38-
stopProgressEffect?.();
39-
stopProgressEffect = undefined;
40-
ref?.style.setProperty('--progress', '0%');
41-
currentRef = null;
42-
return;
43-
}
26+
? cues
27+
.filter((cue) => cue.startTime <= $endTime && cue.endTime >= $startTime)
28+
.map<ChapterOption>((cue, i) => {
29+
let currentRef: HTMLElement | null = null,
30+
stopProgressEffect: StopEffect | undefined;
31+
return {
32+
cue,
33+
label: cue.text,
34+
value: i + '',
35+
startTimeText: formatTime(Math.max(0, cue.startTime - $startTime), false),
36+
durationText: formatSpokenTime(
37+
Math.min($endTime, cue.endTime) - Math.max($startTime, cue.startTime),
38+
),
39+
get selected() {
40+
return cue === track.activeCues[0];
41+
},
42+
setProgressVar(ref) {
43+
if (!ref || cue !== track.activeCues[0]) {
44+
stopProgressEffect?.();
45+
stopProgressEffect = undefined;
46+
ref?.style.setProperty('--progress', '0%');
47+
currentRef = null;
48+
return;
49+
}
50+
51+
if (currentRef === ref) return;
52+
currentRef = ref;
4453

45-
if (currentRef === ref) return;
46-
currentRef = ref;
54+
stopProgressEffect?.();
55+
stopProgressEffect = effect(() => {
56+
const { realCurrentTime } = media.$state,
57+
time = realCurrentTime(),
58+
cueStartTime = Math.max($startTime, cue.startTime),
59+
duration = Math.min($endTime, cue.endTime) - cueStartTime,
60+
progress = (Math.max(0, time - cueStartTime) / duration) * 100;
4761

48-
stopProgressEffect?.();
49-
stopProgressEffect = effect(() => {
50-
const { currentTime } = media.$state,
51-
time = currentTime(),
52-
progress =
53-
Math.min((time - cue.startTime) / (cue.endTime - cue.startTime), 1) * 100;
54-
ref.style.setProperty('--progress', progress.toFixed(3) + '%');
55-
});
56-
},
57-
select(trigger) {
58-
media.remote.seek(cue.startTime, trigger);
59-
},
60-
};
61-
})
62+
ref.style.setProperty('--progress', progress.toFixed(3) + '%');
63+
});
64+
},
65+
select(trigger) {
66+
media.remote.seek(cue.startTime - $startTime, trigger);
67+
},
68+
};
69+
})
6270
: [];
6371

6472
Object.defineProperty(options, 'selectedValue', {
@@ -69,7 +77,7 @@ export function useChapterOptions(): ChapterOptions {
6977
});
7078

7179
return options as ChapterOptions;
72-
}, [cues]);
80+
}, [cues, $startTime, $endTime]);
7381
}
7482

7583
export type ChapterOptions = ChapterOption[] & {

packages/vidstack/mangle.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,5 +621,10 @@
621621
"_onFatalError": "Ak",
622622
"_onFragLoading": "Bk",
623623
"_onNetworkError": "Ck",
624-
"_retryLoadingTimer": "yk"
624+
"_retryLoadingTimer": "yk",
625+
"_watchClipTimes": "Ek",
626+
"_watchMediaDuration": "Fk",
627+
"_resetPlaybackIfNeeded": "Gk",
628+
"_getEndTime": "Hk",
629+
"_appendMediaFragment": "Ik"
625630
}

packages/vidstack/src/components/player.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -496,13 +496,16 @@ export class MediaPlayer
496496

497497
private _queueCurrentTimeUpdate(time: number) {
498498
this.canPlayQueue._enqueue('currentTime', () => {
499-
if (time === peek(this.$state.currentTime)) return;
499+
const { currentTime, clipStartTime, seekableStart, seekableEnd } = this.$state;
500+
501+
if (time === peek(currentTime)) return;
502+
500503
peek(() => {
501504
if (!this._provider) return;
502505

503506
const boundTime = Math.min(
504-
Math.max(this.$state.seekableStart() + 0.1, time),
505-
this.$state.seekableEnd() - 0.1,
507+
Math.max(seekableStart() + 0.1, time + clipStartTime()),
508+
seekableEnd() - 0.1,
506509
);
507510

508511
if (Number.isFinite(boundTime)) this._provider.setCurrentTime(boundTime);

packages/vidstack/src/components/ui/captions/captions.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ export class Captions extends Component<CaptionsProps> {
103103
private _onCueChange() {
104104
this.el!.textContent = '';
105105

106-
const { currentTime, textTrack } = this._media.$state,
107-
time = peek(currentTime),
106+
const { realCurrentTime, textTrack } = this._media.$state,
107+
time = peek(realCurrentTime),
108108
activeCues = peek(textTrack)!.activeCues;
109109

110110
const { renderVTTCueString } = this._lib;
@@ -117,9 +117,9 @@ export class Captions extends Component<CaptionsProps> {
117117
}
118118

119119
private _onUpdateTimedNodes() {
120-
const { currentTime } = this._media.$state,
120+
const { realCurrentTime } = this._media.$state,
121121
{ updateTimedVTTCueNodes } = this._lib;
122-
updateTimedVTTCueNodes(this.el!, currentTime());
122+
updateTimedVTTCueNodes(this.el!, realCurrentTime());
123123
}
124124

125125
private _setupVideoView() {
@@ -139,7 +139,7 @@ export class Captions extends Component<CaptionsProps> {
139139

140140
private _watchMediaTime() {
141141
if (this._isHidden()) return;
142-
const { currentTime } = this._media.$state;
143-
this._renderer.currentTime = currentTime();
142+
const { realCurrentTime } = this._media.$state;
143+
this._renderer.currentTime = realCurrentTime();
144144
}
145145
}

packages/vidstack/src/components/ui/menu/radio-groups/chapters-radio-group.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,17 @@ export class ChaptersRadioGroup extends Component<
8080

8181
@method
8282
getOptions(): ChaptersRadioOption[] {
83+
const { clipStartTime, clipEndTime } = this._media.$state,
84+
startTime = clipStartTime(),
85+
endTime = clipEndTime() || Infinity;
8386
return this._cues().map((cue, i) => ({
8487
cue,
8588
value: i + '',
8689
label: cue.text,
87-
startTime: formatTime(cue.startTime, false),
88-
duration: formatSpokenTime(cue.endTime - cue.startTime),
90+
startTime: formatTime(Math.max(0, cue.startTime - startTime), false),
91+
duration: formatSpokenTime(
92+
Math.min(endTime, cue.endTime) - Math.max(startTime, cue.startTime),
93+
),
8994
}));
9095
}
9196

@@ -117,7 +122,12 @@ export class ChaptersRadioGroup extends Component<
117122
}
118123

119124
protected _onCuesChange(track: TextTrack) {
120-
this._cues.set([...track.cues]);
125+
const { clipStartTime, clipEndTime } = this._media.$state,
126+
startTime = clipStartTime(),
127+
endTime = clipEndTime() || Infinity;
128+
this._cues.set(
129+
[...track.cues].filter((cue) => cue.startTime <= endTime && cue.endTime >= startTime),
130+
);
121131
}
122132

123133
private _watchValue() {
@@ -134,16 +144,21 @@ export class ChaptersRadioGroup extends Component<
134144
return;
135145
}
136146

137-
const { currentTime } = this._media.$state,
138-
time = currentTime(),
139-
activeCueIndex = track.cues.findIndex((cue) => isCueActive(cue, time));
147+
const { realCurrentTime, clipStartTime, clipEndTime } = this._media.$state,
148+
startTime = clipStartTime(),
149+
endTime = clipEndTime() || Infinity,
150+
time = realCurrentTime(),
151+
activeCueIndex = this._cues().findIndex((cue) => isCueActive(cue, time));
140152

141153
this._index.set(activeCueIndex);
142154

143155
if (activeCueIndex >= 0) {
144-
const cue = track.cues[activeCueIndex],
156+
const cue = this._cues()[activeCueIndex],
145157
radio = this.el!.querySelector(`[aria-checked='true']`),
146-
playedPercent = ((time - cue.startTime) / (cue.endTime - cue.startTime)) * 100;
158+
cueStartTime = Math.max(startTime, cue.startTime),
159+
duration = Math.min(endTime, cue.endTime) - cueStartTime,
160+
playedPercent = (Math.max(0, time - cueStartTime) / duration) * 100;
161+
147162
radio && setStyle(radio as HTMLElement, '--progress', round(playedPercent, 3) + '%');
148163
}
149164
}
@@ -160,11 +175,12 @@ export class ChaptersRadioGroup extends Component<
160175
if (this.disabled || !trigger) return;
161176

162177
const index = +value,
163-
cues = this._track()?.cues;
178+
cues = this._cues(),
179+
{ clipStartTime } = this._media.$state;
164180

165181
if (isNumber(index) && cues?.[index]) {
166182
this._index.set(index);
167-
this._media.remote.seek(cues[index].startTime, trigger);
183+
this._media.remote.seek(cues[index].startTime - clipStartTime(), trigger);
168184
this.dispatch('change', { detail: cues[index], trigger });
169185
}
170186
}

packages/vidstack/src/components/ui/sliders/slider-thumbnail.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export class SliderThumbnail extends Thumbnail {
2222
}
2323

2424
protected override _getTime() {
25-
const { duration } = this._media.$state;
26-
return this._slider.pointerRate() * duration();
25+
const { duration, clipStartTime } = this._media.$state;
26+
return clipStartTime() + this._slider.pointerRate() * duration();
2727
}
2828
}

0 commit comments

Comments
 (0)