@editframe/elements 0.26.2-beta.0 → 0.26.4-beta.0
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/elements/EFTimegroup.js +7 -2
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/package.json +2 -2
- package/scripts/build-css.js +3 -3
- package/tsdown.config.ts +1 -1
- package/types.json +1 -1
- package/src/elements/ContextProxiesController.ts +0 -124
- package/src/elements/CrossUpdateController.ts +0 -22
- package/src/elements/EFAudio.browsertest.ts +0 -706
- package/src/elements/EFAudio.ts +0 -56
- package/src/elements/EFCaptions.browsertest.ts +0 -1960
- package/src/elements/EFCaptions.ts +0 -823
- package/src/elements/EFImage.browsertest.ts +0 -120
- package/src/elements/EFImage.ts +0 -113
- package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
- package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
- package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
- package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
- package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
- package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
- package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
- package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
- package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
- package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
- package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
- package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
- package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
- package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
- package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
- package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
- package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
- package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
- package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
- package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
- package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
- package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
- package/src/elements/EFMedia.browsertest.ts +0 -872
- package/src/elements/EFMedia.ts +0 -341
- package/src/elements/EFSourceMixin.ts +0 -60
- package/src/elements/EFSurface.browsertest.ts +0 -151
- package/src/elements/EFSurface.ts +0 -142
- package/src/elements/EFTemporal.browsertest.ts +0 -215
- package/src/elements/EFTemporal.ts +0 -800
- package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
- package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
- package/src/elements/EFThumbnailStrip.ts +0 -906
- package/src/elements/EFTimegroup.browsertest.ts +0 -870
- package/src/elements/EFTimegroup.ts +0 -878
- package/src/elements/EFVideo.browsertest.ts +0 -1482
- package/src/elements/EFVideo.ts +0 -564
- package/src/elements/EFWaveform.ts +0 -547
- package/src/elements/FetchContext.browsertest.ts +0 -401
- package/src/elements/FetchMixin.ts +0 -38
- package/src/elements/SampleBuffer.ts +0 -94
- package/src/elements/TargetController.browsertest.ts +0 -230
- package/src/elements/TargetController.ts +0 -224
- package/src/elements/TimegroupController.ts +0 -26
- package/src/elements/durationConverter.ts +0 -35
- package/src/elements/parseTimeToMs.ts +0 -9
- package/src/elements/printTaskStatus.ts +0 -16
- package/src/elements/renderTemporalAudio.ts +0 -108
- package/src/elements/updateAnimations.browsertest.ts +0 -1884
- package/src/elements/updateAnimations.ts +0 -217
- package/src/elements/util.ts +0 -24
- package/src/gui/ContextMixin.browsertest.ts +0 -860
- package/src/gui/ContextMixin.ts +0 -562
- package/src/gui/Controllable.browsertest.ts +0 -258
- package/src/gui/Controllable.ts +0 -41
- package/src/gui/EFConfiguration.ts +0 -40
- package/src/gui/EFControls.browsertest.ts +0 -389
- package/src/gui/EFControls.ts +0 -195
- package/src/gui/EFDial.browsertest.ts +0 -84
- package/src/gui/EFDial.ts +0 -172
- package/src/gui/EFFilmstrip.browsertest.ts +0 -712
- package/src/gui/EFFilmstrip.ts +0 -1349
- package/src/gui/EFFitScale.ts +0 -152
- package/src/gui/EFFocusOverlay.ts +0 -79
- package/src/gui/EFPause.browsertest.ts +0 -202
- package/src/gui/EFPause.ts +0 -73
- package/src/gui/EFPlay.browsertest.ts +0 -202
- package/src/gui/EFPlay.ts +0 -73
- package/src/gui/EFPreview.ts +0 -74
- package/src/gui/EFResizableBox.browsertest.ts +0 -79
- package/src/gui/EFResizableBox.ts +0 -898
- package/src/gui/EFScrubber.ts +0 -151
- package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
- package/src/gui/EFTimeDisplay.ts +0 -55
- package/src/gui/EFToggleLoop.ts +0 -35
- package/src/gui/EFTogglePlay.ts +0 -70
- package/src/gui/EFWorkbench.ts +0 -115
- package/src/gui/PlaybackController.ts +0 -527
- package/src/gui/TWMixin.css +0 -6
- package/src/gui/TWMixin.ts +0 -61
- package/src/gui/TargetOrContextMixin.ts +0 -185
- package/src/gui/currentTimeContext.ts +0 -5
- package/src/gui/durationContext.ts +0 -3
- package/src/gui/efContext.ts +0 -6
- package/src/gui/fetchContext.ts +0 -5
- package/src/gui/focusContext.ts +0 -7
- package/src/gui/focusedElementContext.ts +0 -5
- package/src/gui/playingContext.ts +0 -5
- package/src/otel/BridgeSpanExporter.ts +0 -150
- package/src/otel/setupBrowserTracing.ts +0 -73
- package/src/otel/tracingHelpers.ts +0 -251
- package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
- package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
- package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
- package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
- package/src/transcoding/types/index.ts +0 -312
- package/src/transcoding/utils/MediaUtils.ts +0 -63
- package/src/transcoding/utils/UrlGenerator.ts +0 -68
- package/src/transcoding/utils/constants.ts +0 -36
- package/src/utils/LRUCache.test.ts +0 -274
- package/src/utils/LRUCache.ts +0 -696
|
@@ -1,823 +0,0 @@
|
|
|
1
|
-
import { Task, TaskStatus } from "@lit/task";
|
|
2
|
-
import { css, html, LitElement, type PropertyValueMap } from "lit";
|
|
3
|
-
import { customElement, property } from "lit/decorators.js";
|
|
4
|
-
import type { GetISOBMFFFileTranscriptionResult } from "../../../api/src/index.js";
|
|
5
|
-
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
6
|
-
import { CrossUpdateController } from "./CrossUpdateController.js";
|
|
7
|
-
import { EFAudio } from "./EFAudio.js";
|
|
8
|
-
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
9
|
-
import { EFTemporal, flushStartTimeMsCache } from "./EFTemporal.js";
|
|
10
|
-
import { flushSequenceDurationCache } from "./EFTimegroup.js";
|
|
11
|
-
import { EFVideo } from "./EFVideo.js";
|
|
12
|
-
import { FetchMixin } from "./FetchMixin.js";
|
|
13
|
-
|
|
14
|
-
export interface WordSegment {
|
|
15
|
-
text: string;
|
|
16
|
-
start: number;
|
|
17
|
-
end: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface Segment {
|
|
21
|
-
start: number;
|
|
22
|
-
end: number;
|
|
23
|
-
text: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface Caption {
|
|
27
|
-
segments: Segment[];
|
|
28
|
-
word_segments: WordSegment[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const stopWords = new Set(["", ".", "!", "?", ","]);
|
|
32
|
-
|
|
33
|
-
@customElement("ef-captions-active-word")
|
|
34
|
-
export class EFCaptionsActiveWord extends EFTemporal(LitElement) {
|
|
35
|
-
static styles = [
|
|
36
|
-
css`
|
|
37
|
-
:host {
|
|
38
|
-
display: inline-block;
|
|
39
|
-
white-space: normal;
|
|
40
|
-
line-height: 1;
|
|
41
|
-
}
|
|
42
|
-
:host([hidden]) {
|
|
43
|
-
opacity: 0;
|
|
44
|
-
pointer-events: none;
|
|
45
|
-
}
|
|
46
|
-
`,
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
render() {
|
|
50
|
-
if (stopWords.has(this.wordText)) {
|
|
51
|
-
this.hidden = true;
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
this.hidden = false;
|
|
55
|
-
|
|
56
|
-
// Set deterministic --ef-word-seed value based on word index
|
|
57
|
-
const seed = (this.wordIndex * 9007) % 233; // Prime numbers for better distribution
|
|
58
|
-
const seedValue = seed / 233; // Normalize to 0-1 range
|
|
59
|
-
this.style.setProperty("--ef-word-seed", seedValue.toString());
|
|
60
|
-
|
|
61
|
-
return html`${this.wordText}`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
@property({ type: Number, attribute: false })
|
|
65
|
-
wordStartMs = 0;
|
|
66
|
-
|
|
67
|
-
@property({ type: Number, attribute: false })
|
|
68
|
-
wordEndMs = 0;
|
|
69
|
-
|
|
70
|
-
@property({ type: String, attribute: false })
|
|
71
|
-
wordText = "";
|
|
72
|
-
|
|
73
|
-
@property({ type: Number, attribute: false })
|
|
74
|
-
wordIndex = 0;
|
|
75
|
-
|
|
76
|
-
@property({ type: Boolean, reflect: true })
|
|
77
|
-
hidden = false;
|
|
78
|
-
|
|
79
|
-
get startTimeMs() {
|
|
80
|
-
// Get parent captions element's absolute start time, then add our local offset
|
|
81
|
-
const parentCaptions = this.closest("ef-captions") as EFCaptions;
|
|
82
|
-
const parentStartTime = parentCaptions?.startTimeMs || 0;
|
|
83
|
-
return parentStartTime + (this.wordStartMs || 0);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
get endTimeMs() {
|
|
87
|
-
const parentCaptions = this.closest("ef-captions") as EFCaptions;
|
|
88
|
-
const parentStartTime = parentCaptions?.startTimeMs || 0;
|
|
89
|
-
return parentStartTime + (this.wordEndMs || 0);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
get durationMs(): number {
|
|
93
|
-
return this.wordEndMs - this.wordStartMs;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
@customElement("ef-captions-segment")
|
|
98
|
-
export class EFCaptionsSegment extends EFTemporal(LitElement) {
|
|
99
|
-
static styles = [
|
|
100
|
-
css`
|
|
101
|
-
:host {
|
|
102
|
-
display: inline-block;
|
|
103
|
-
white-space: normal;
|
|
104
|
-
line-height: 1;
|
|
105
|
-
}
|
|
106
|
-
`,
|
|
107
|
-
];
|
|
108
|
-
|
|
109
|
-
render() {
|
|
110
|
-
if (stopWords.has(this.segmentText)) {
|
|
111
|
-
this.hidden = true;
|
|
112
|
-
return undefined;
|
|
113
|
-
}
|
|
114
|
-
this.hidden = false;
|
|
115
|
-
return html`${this.segmentText}`;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
@property({ type: Number, attribute: false })
|
|
119
|
-
segmentStartMs = 0;
|
|
120
|
-
|
|
121
|
-
@property({ type: Number, attribute: false })
|
|
122
|
-
segmentEndMs = 0;
|
|
123
|
-
|
|
124
|
-
@property({ type: String, attribute: false })
|
|
125
|
-
segmentText = "";
|
|
126
|
-
|
|
127
|
-
@property({ type: Boolean, reflect: true })
|
|
128
|
-
hidden = false;
|
|
129
|
-
|
|
130
|
-
get startTimeMs() {
|
|
131
|
-
// Get parent captions element's absolute start time, then add our local offset
|
|
132
|
-
const parentCaptions = this.closest("ef-captions") as EFCaptions;
|
|
133
|
-
const parentStartTime = parentCaptions?.startTimeMs || 0;
|
|
134
|
-
return parentStartTime + (this.segmentStartMs || 0);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
get endTimeMs() {
|
|
138
|
-
const parentCaptions = this.closest("ef-captions") as EFCaptions;
|
|
139
|
-
const parentStartTime = parentCaptions?.startTimeMs || 0;
|
|
140
|
-
return parentStartTime + (this.segmentEndMs || 0);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
get durationMs(): number {
|
|
144
|
-
return this.segmentEndMs - this.segmentStartMs;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
@customElement("ef-captions-before-active-word")
|
|
149
|
-
export class EFCaptionsBeforeActiveWord extends EFCaptionsSegment {
|
|
150
|
-
static styles = [
|
|
151
|
-
css`
|
|
152
|
-
:host {
|
|
153
|
-
display: inline-block;
|
|
154
|
-
white-space: pre;
|
|
155
|
-
line-height: 1;
|
|
156
|
-
}
|
|
157
|
-
:host([hidden]) {
|
|
158
|
-
opacity: 0;
|
|
159
|
-
pointer-events: none;
|
|
160
|
-
}
|
|
161
|
-
`,
|
|
162
|
-
];
|
|
163
|
-
|
|
164
|
-
render() {
|
|
165
|
-
if (stopWords.has(this.segmentText)) {
|
|
166
|
-
this.hidden = true;
|
|
167
|
-
return undefined;
|
|
168
|
-
}
|
|
169
|
-
this.hidden = false;
|
|
170
|
-
|
|
171
|
-
// Check if there's an active word by looking for sibling active word element
|
|
172
|
-
const activeWord = this.closest("ef-captions")?.querySelector(
|
|
173
|
-
"ef-captions-active-word",
|
|
174
|
-
);
|
|
175
|
-
const hasActiveWord = activeWord?.wordText && !activeWord.hidden;
|
|
176
|
-
|
|
177
|
-
return html`${this.segmentText}${hasActiveWord ? " " : ""}`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
@property({ type: Boolean, reflect: true })
|
|
181
|
-
hidden = false;
|
|
182
|
-
|
|
183
|
-
@property({ type: String, attribute: false })
|
|
184
|
-
segmentText = "";
|
|
185
|
-
|
|
186
|
-
@property({ type: Number, attribute: false })
|
|
187
|
-
segmentStartMs = 0;
|
|
188
|
-
|
|
189
|
-
@property({ type: Number, attribute: false })
|
|
190
|
-
segmentEndMs = 0;
|
|
191
|
-
|
|
192
|
-
get startTimeMs() {
|
|
193
|
-
// Get parent captions element's absolute start time, then add our local offset
|
|
194
|
-
const parentCaptions = this.closest("ef-captions") as EFCaptions;
|
|
195
|
-
const parentStartTime = parentCaptions?.startTimeMs || 0;
|
|
196
|
-
return parentStartTime + (this.segmentStartMs || 0);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
get endTimeMs() {
|
|
200
|
-
const parentCaptions = this.closest("ef-captions") as EFCaptions;
|
|
201
|
-
const parentStartTime = parentCaptions?.startTimeMs || 0;
|
|
202
|
-
return parentStartTime + (this.segmentEndMs || 0);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
get durationMs(): number {
|
|
206
|
-
return this.segmentEndMs - this.segmentStartMs;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
@customElement("ef-captions-after-active-word")
|
|
211
|
-
export class EFCaptionsAfterActiveWord extends EFCaptionsSegment {
|
|
212
|
-
static styles = [
|
|
213
|
-
css`
|
|
214
|
-
:host {
|
|
215
|
-
display: inline-block;
|
|
216
|
-
white-space: pre;
|
|
217
|
-
line-height: 1;
|
|
218
|
-
}
|
|
219
|
-
:host([hidden]) {
|
|
220
|
-
opacity: 0;
|
|
221
|
-
pointer-events: none;
|
|
222
|
-
}
|
|
223
|
-
`,
|
|
224
|
-
];
|
|
225
|
-
|
|
226
|
-
render() {
|
|
227
|
-
if (stopWords.has(this.segmentText)) {
|
|
228
|
-
this.hidden = true;
|
|
229
|
-
return undefined;
|
|
230
|
-
}
|
|
231
|
-
this.hidden = false;
|
|
232
|
-
|
|
233
|
-
// Check if there's an active word by looking for sibling active word element
|
|
234
|
-
const activeWord = this.closest("ef-captions")?.querySelector(
|
|
235
|
-
"ef-captions-active-word",
|
|
236
|
-
);
|
|
237
|
-
const hasActiveWord = activeWord?.wordText && !activeWord.hidden;
|
|
238
|
-
|
|
239
|
-
return html`${hasActiveWord ? " " : ""}${this.segmentText}`;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
@property({ type: Boolean, reflect: true })
|
|
243
|
-
hidden = false;
|
|
244
|
-
|
|
245
|
-
@property({ type: String, attribute: false })
|
|
246
|
-
segmentText = "";
|
|
247
|
-
|
|
248
|
-
@property({ type: Number, attribute: false })
|
|
249
|
-
segmentStartMs = 0;
|
|
250
|
-
|
|
251
|
-
@property({ type: Number, attribute: false })
|
|
252
|
-
segmentEndMs = 0;
|
|
253
|
-
|
|
254
|
-
get startTimeMs() {
|
|
255
|
-
// Get parent captions element's absolute start time, then add our local offset
|
|
256
|
-
const parentCaptions = this.closest("ef-captions") as EFCaptions;
|
|
257
|
-
const parentStartTime = parentCaptions?.startTimeMs || 0;
|
|
258
|
-
return parentStartTime + (this.segmentStartMs || 0);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
get endTimeMs() {
|
|
262
|
-
const parentCaptions = this.closest("ef-captions") as EFCaptions;
|
|
263
|
-
const parentStartTime = parentCaptions?.startTimeMs || 0;
|
|
264
|
-
return parentStartTime + (this.segmentEndMs || 0);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
get durationMs(): number {
|
|
268
|
-
return this.segmentEndMs - this.segmentStartMs;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
@customElement("ef-captions")
|
|
273
|
-
export class EFCaptions extends EFSourceMixin(
|
|
274
|
-
EFTemporal(FetchMixin(LitElement)),
|
|
275
|
-
{ assetType: "caption_files" },
|
|
276
|
-
) {
|
|
277
|
-
static styles = [
|
|
278
|
-
css`
|
|
279
|
-
:host {
|
|
280
|
-
display: inline-flex;
|
|
281
|
-
white-space: normal;
|
|
282
|
-
line-height: 1;
|
|
283
|
-
gap: 0;
|
|
284
|
-
}
|
|
285
|
-
::slotted(*) {
|
|
286
|
-
display: inline-block;
|
|
287
|
-
margin: 0;
|
|
288
|
-
padding: 0;
|
|
289
|
-
}
|
|
290
|
-
`,
|
|
291
|
-
];
|
|
292
|
-
|
|
293
|
-
@property({ type: String, attribute: "target", reflect: true })
|
|
294
|
-
targetSelector = "";
|
|
295
|
-
|
|
296
|
-
set target(value: string) {
|
|
297
|
-
this.targetSelector = value;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
@property({ attribute: "word-style" })
|
|
301
|
-
wordStyle = "";
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* URL or path to a JSON file containing custom captions data.
|
|
305
|
-
* The JSON should conform to the Caption interface with 'segments' and 'word_segments' arrays.
|
|
306
|
-
*/
|
|
307
|
-
@property({ type: String, attribute: "captions-src", reflect: true })
|
|
308
|
-
captionsSrc = "";
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Direct captions data object. Takes priority over captions-src and captions-script.
|
|
312
|
-
* Should conform to the Caption interface with 'segments' and 'word_segments' arrays.
|
|
313
|
-
*/
|
|
314
|
-
@property({ type: Object, attribute: false })
|
|
315
|
-
captionsData: Caption | null = null;
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* ID of a <script> element containing JSON captions data.
|
|
319
|
-
* The script's textContent should be valid JSON conforming to the Caption interface.
|
|
320
|
-
*/
|
|
321
|
-
@property({ type: String, attribute: "captions-script", reflect: true })
|
|
322
|
-
captionsScript = "";
|
|
323
|
-
|
|
324
|
-
activeWordContainers = this.getElementsByTagName("ef-captions-active-word");
|
|
325
|
-
segmentContainers = this.getElementsByTagName("ef-captions-segment");
|
|
326
|
-
beforeActiveWordContainers = this.getElementsByTagName(
|
|
327
|
-
"ef-captions-before-active-word",
|
|
328
|
-
);
|
|
329
|
-
afterActiveWordContainers = this.getElementsByTagName(
|
|
330
|
-
"ef-captions-after-active-word",
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
render() {
|
|
334
|
-
return html`<slot></slot>`;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
transcriptionsPath() {
|
|
338
|
-
if (!this.targetElement) {
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
if (this.targetElement.assetId) {
|
|
342
|
-
return `${this.apiHost}/api/v1/isobmff_files/${this.targetElement.assetId}/transcription`;
|
|
343
|
-
}
|
|
344
|
-
return null;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
captionsPath() {
|
|
348
|
-
if (!this.targetElement) {
|
|
349
|
-
return null;
|
|
350
|
-
}
|
|
351
|
-
if (this.targetElement.assetId) {
|
|
352
|
-
return `${this.apiHost}/api/v1/caption_files/${this.targetElement.assetId}`;
|
|
353
|
-
}
|
|
354
|
-
const targetSrc = this.targetElement.src;
|
|
355
|
-
return `/@ef-captions/${targetSrc}`;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
protected md5SumLoader = new Task(this, {
|
|
359
|
-
autoRun: false,
|
|
360
|
-
args: () => [this.target, this.fetch] as const,
|
|
361
|
-
task: async ([_target, fetch], { signal }) => {
|
|
362
|
-
if (!this.targetElement) {
|
|
363
|
-
return null;
|
|
364
|
-
}
|
|
365
|
-
const md5Path = `/@ef-asset/${this.targetElement.src ?? ""}`;
|
|
366
|
-
const response = await fetch(md5Path, { method: "HEAD", signal });
|
|
367
|
-
return response.headers.get("etag") ?? undefined;
|
|
368
|
-
},
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
private transcriptionDataTask = new Task(this, {
|
|
372
|
-
autoRun: EF_INTERACTIVE,
|
|
373
|
-
args: () =>
|
|
374
|
-
[
|
|
375
|
-
this.transcriptionsPath(),
|
|
376
|
-
this.fetch,
|
|
377
|
-
this.hasCustomCaptionsData,
|
|
378
|
-
] as const,
|
|
379
|
-
task: async ([transcriptionsPath, fetch, hasCustomData], { signal }) => {
|
|
380
|
-
// Skip transcription if we have custom captions data
|
|
381
|
-
if (hasCustomData || !transcriptionsPath) {
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
const response = await fetch(transcriptionsPath, { signal });
|
|
385
|
-
return response.json() as any as GetISOBMFFFileTranscriptionResult;
|
|
386
|
-
},
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
private transcriptionFragmentPath(
|
|
390
|
-
transcriptionId: string,
|
|
391
|
-
fragmentIndex: number,
|
|
392
|
-
) {
|
|
393
|
-
return `${this.apiHost}/api/v1/transcriptions/${transcriptionId}/fragments/${fragmentIndex}`;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
private fragmentIndexTask = new Task(this, {
|
|
397
|
-
autoRun: EF_INTERACTIVE,
|
|
398
|
-
args: () =>
|
|
399
|
-
[this.transcriptionDataTask.value, this.ownCurrentTimeMs] as const,
|
|
400
|
-
task: async ([transcription, ownCurrentTimeMs]) => {
|
|
401
|
-
if (!transcription) {
|
|
402
|
-
return null;
|
|
403
|
-
}
|
|
404
|
-
const fragmentIndex = Math.floor(
|
|
405
|
-
ownCurrentTimeMs / transcription.work_slice_ms,
|
|
406
|
-
);
|
|
407
|
-
return fragmentIndex;
|
|
408
|
-
},
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
private customCaptionsDataTask = new Task(this, {
|
|
412
|
-
autoRun: EF_INTERACTIVE,
|
|
413
|
-
args: () =>
|
|
414
|
-
[
|
|
415
|
-
this.captionsSrc,
|
|
416
|
-
this.captionsData,
|
|
417
|
-
this.captionsScript,
|
|
418
|
-
this.fetch,
|
|
419
|
-
] as const,
|
|
420
|
-
task: async (
|
|
421
|
-
[captionsSrc, captionsData, captionsScript, fetch],
|
|
422
|
-
{ signal },
|
|
423
|
-
) => {
|
|
424
|
-
// Priority: direct data > script reference > URL source
|
|
425
|
-
if (captionsData) {
|
|
426
|
-
return captionsData;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (captionsScript) {
|
|
430
|
-
const scriptElement = document.getElementById(captionsScript);
|
|
431
|
-
if (scriptElement?.textContent) {
|
|
432
|
-
try {
|
|
433
|
-
return JSON.parse(scriptElement.textContent) as Caption;
|
|
434
|
-
} catch (error) {
|
|
435
|
-
console.error(
|
|
436
|
-
`Failed to parse captions from script #${captionsScript}:`,
|
|
437
|
-
error,
|
|
438
|
-
);
|
|
439
|
-
return null;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if (captionsSrc) {
|
|
445
|
-
try {
|
|
446
|
-
const response = await fetch(captionsSrc, { signal });
|
|
447
|
-
return (await response.json()) as Caption;
|
|
448
|
-
} catch (error) {
|
|
449
|
-
console.error(`Failed to load captions from ${captionsSrc}:`, error);
|
|
450
|
-
return null;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
return null;
|
|
455
|
-
},
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
private transcriptionFragmentDataTask = new Task(this, {
|
|
459
|
-
autoRun: EF_INTERACTIVE,
|
|
460
|
-
args: () =>
|
|
461
|
-
[
|
|
462
|
-
this.transcriptionDataTask.value,
|
|
463
|
-
this.fragmentIndexTask.value,
|
|
464
|
-
this.fetch,
|
|
465
|
-
] as const,
|
|
466
|
-
task: async ([transcription, fragmentIndex, fetch], { signal }) => {
|
|
467
|
-
if (
|
|
468
|
-
transcription === null ||
|
|
469
|
-
transcription === undefined ||
|
|
470
|
-
fragmentIndex === null ||
|
|
471
|
-
fragmentIndex === undefined
|
|
472
|
-
) {
|
|
473
|
-
return null;
|
|
474
|
-
}
|
|
475
|
-
const fragmentPath = this.transcriptionFragmentPath(
|
|
476
|
-
transcription.id,
|
|
477
|
-
fragmentIndex,
|
|
478
|
-
);
|
|
479
|
-
const response = await fetch(fragmentPath, { signal });
|
|
480
|
-
return response.json() as any as Caption;
|
|
481
|
-
},
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
unifiedCaptionsDataTask = new Task(this, {
|
|
485
|
-
autoRun: EF_INTERACTIVE,
|
|
486
|
-
args: () =>
|
|
487
|
-
[
|
|
488
|
-
this.customCaptionsDataTask.value,
|
|
489
|
-
this.transcriptionFragmentDataTask.value,
|
|
490
|
-
] as const,
|
|
491
|
-
task: async ([_customData, _transcriptionData]) => {
|
|
492
|
-
if (this.customCaptionsDataTask.status === TaskStatus.PENDING) {
|
|
493
|
-
await this.customCaptionsDataTask.taskComplete;
|
|
494
|
-
}
|
|
495
|
-
if (this.transcriptionFragmentDataTask.status === TaskStatus.PENDING) {
|
|
496
|
-
await this.transcriptionFragmentDataTask.taskComplete;
|
|
497
|
-
}
|
|
498
|
-
return (
|
|
499
|
-
this.customCaptionsDataTask.value ||
|
|
500
|
-
this.transcriptionFragmentDataTask.value
|
|
501
|
-
);
|
|
502
|
-
},
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
frameTask = new Task(this, {
|
|
506
|
-
autoRun: EF_INTERACTIVE,
|
|
507
|
-
args: () => [this.unifiedCaptionsDataTask.status, this.ownCurrentTimeMs],
|
|
508
|
-
task: async () => {
|
|
509
|
-
await this.unifiedCaptionsDataTask.taskComplete;
|
|
510
|
-
// Trigger updateTextContainers when data is ready or time changes
|
|
511
|
-
this.updateTextContainers();
|
|
512
|
-
},
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
connectedCallback() {
|
|
516
|
-
super.connectedCallback();
|
|
517
|
-
|
|
518
|
-
// Try to get target element safely
|
|
519
|
-
const target = this.targetSelector
|
|
520
|
-
? document.getElementById(this.targetSelector)
|
|
521
|
-
: null;
|
|
522
|
-
if (target && (target instanceof EFAudio || target instanceof EFVideo)) {
|
|
523
|
-
new CrossUpdateController(target, this);
|
|
524
|
-
}
|
|
525
|
-
// For standalone captions with custom data, ensure proper timeline sync
|
|
526
|
-
else if (this.hasCustomCaptionsData && this.rootTimegroup) {
|
|
527
|
-
new CrossUpdateController(this.rootTimegroup, this);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// Prevent display:none from being set on caption elements
|
|
531
|
-
// This maintains constant width in the parent flex container
|
|
532
|
-
const observer = new MutationObserver(() => {
|
|
533
|
-
if (this.style.display === "none") {
|
|
534
|
-
// Remove the display:none and use opacity instead
|
|
535
|
-
this.style.removeProperty("display");
|
|
536
|
-
this.style.opacity = "0";
|
|
537
|
-
this.style.pointerEvents = "none";
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
observer.observe(this, { attributes: true, attributeFilter: ["style"] });
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
protected updated(
|
|
544
|
-
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
|
|
545
|
-
): void {
|
|
546
|
-
this.updateTextContainers();
|
|
547
|
-
|
|
548
|
-
// Force duration recalculation when custom captions data changes
|
|
549
|
-
if (
|
|
550
|
-
changedProperties.has("captionsData") ||
|
|
551
|
-
changedProperties.has("captionsSrc") ||
|
|
552
|
-
changedProperties.has("captionsScript")
|
|
553
|
-
) {
|
|
554
|
-
this.requestUpdate("intrinsicDurationMs");
|
|
555
|
-
|
|
556
|
-
// Flush sequence duration cache and notify parent timegroups that child duration has changed
|
|
557
|
-
flushSequenceDurationCache();
|
|
558
|
-
flushStartTimeMsCache();
|
|
559
|
-
|
|
560
|
-
// Notify parent timegroup to recalculate its duration
|
|
561
|
-
if (this.parentTimegroup) {
|
|
562
|
-
this.parentTimegroup.requestUpdate("durationMs");
|
|
563
|
-
this.parentTimegroup.requestUpdate("currentTime");
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Update captions when timeline position changes
|
|
568
|
-
if (changedProperties.has("ownCurrentTimeMs")) {
|
|
569
|
-
this.updateTextContainers();
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
updateTextContainers() {
|
|
574
|
-
const captionsData = this.unifiedCaptionsDataTask.value as Caption;
|
|
575
|
-
if (!captionsData) {
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Use ownCurrentTimeMs which is synchronized with the timegroup
|
|
580
|
-
const currentTimeMs = this.ownCurrentTimeMs;
|
|
581
|
-
const currentTimeSec = currentTimeMs / 1000;
|
|
582
|
-
|
|
583
|
-
// Find the current word from word_segments
|
|
584
|
-
// Use exclusive end boundary to prevent overlap at exact boundaries
|
|
585
|
-
const currentWord = captionsData.word_segments.find(
|
|
586
|
-
(word) => currentTimeSec >= word.start && currentTimeSec < word.end,
|
|
587
|
-
);
|
|
588
|
-
|
|
589
|
-
// Find the current segment
|
|
590
|
-
// Use exclusive end boundary to prevent overlap at exact boundaries
|
|
591
|
-
const currentSegment = captionsData.segments.find(
|
|
592
|
-
(segment) =>
|
|
593
|
-
currentTimeSec >= segment.start && currentTimeSec < segment.end,
|
|
594
|
-
);
|
|
595
|
-
|
|
596
|
-
for (const wordContainer of this.activeWordContainers) {
|
|
597
|
-
if (currentWord) {
|
|
598
|
-
wordContainer.wordText = currentWord.text;
|
|
599
|
-
wordContainer.wordStartMs = currentWord.start * 1000;
|
|
600
|
-
wordContainer.wordEndMs = currentWord.end * 1000;
|
|
601
|
-
// Set word index for deterministic animation variation
|
|
602
|
-
const wordIndex = captionsData.word_segments.findIndex(
|
|
603
|
-
(w) =>
|
|
604
|
-
w.start === currentWord.start &&
|
|
605
|
-
w.end === currentWord.end &&
|
|
606
|
-
w.text === currentWord.text,
|
|
607
|
-
);
|
|
608
|
-
wordContainer.wordIndex = wordIndex >= 0 ? wordIndex : 0;
|
|
609
|
-
// Force re-render to update hidden property
|
|
610
|
-
wordContainer.requestUpdate();
|
|
611
|
-
} else {
|
|
612
|
-
// No active word - maintain layout with invisible placeholder
|
|
613
|
-
wordContainer.wordText = ""; // Empty when no active word
|
|
614
|
-
wordContainer.wordStartMs = 0;
|
|
615
|
-
wordContainer.wordEndMs = 0;
|
|
616
|
-
wordContainer.requestUpdate();
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
for (const segmentContainer of this.segmentContainers) {
|
|
621
|
-
if (currentSegment) {
|
|
622
|
-
segmentContainer.segmentText = currentSegment.text;
|
|
623
|
-
segmentContainer.segmentStartMs = currentSegment.start * 1000;
|
|
624
|
-
segmentContainer.segmentEndMs = currentSegment.end * 1000;
|
|
625
|
-
} else {
|
|
626
|
-
// No active segment - clear the container
|
|
627
|
-
segmentContainer.segmentText = "";
|
|
628
|
-
segmentContainer.segmentStartMs = 0;
|
|
629
|
-
segmentContainer.segmentEndMs = 0;
|
|
630
|
-
}
|
|
631
|
-
segmentContainer.requestUpdate();
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// Process context for both word and segment cases
|
|
635
|
-
if (currentWord && currentSegment) {
|
|
636
|
-
// Find all word segments that fall within the current segment's time range
|
|
637
|
-
const segmentWords = captionsData.word_segments.filter(
|
|
638
|
-
(word) =>
|
|
639
|
-
word.start >= currentSegment.start && word.end <= currentSegment.end,
|
|
640
|
-
);
|
|
641
|
-
|
|
642
|
-
// Find the index of the current word within the segment's word segments
|
|
643
|
-
const currentWordIndex = segmentWords.findIndex(
|
|
644
|
-
(word) =>
|
|
645
|
-
word.start === currentWord.start && word.end === currentWord.end,
|
|
646
|
-
);
|
|
647
|
-
|
|
648
|
-
if (currentWordIndex !== -1) {
|
|
649
|
-
// Get words before the current word
|
|
650
|
-
const beforeWords = segmentWords
|
|
651
|
-
.slice(0, currentWordIndex)
|
|
652
|
-
.map((w) => w.text.trim())
|
|
653
|
-
.join(" ");
|
|
654
|
-
|
|
655
|
-
// Get words after the current word
|
|
656
|
-
const afterWords = segmentWords
|
|
657
|
-
.slice(currentWordIndex + 1)
|
|
658
|
-
.map((w) => w.text.trim())
|
|
659
|
-
.join(" ");
|
|
660
|
-
|
|
661
|
-
// Update before containers - should be visible at the same time as active word
|
|
662
|
-
for (const container of this.beforeActiveWordContainers) {
|
|
663
|
-
container.segmentText = beforeWords;
|
|
664
|
-
container.segmentStartMs = currentWord.start * 1000;
|
|
665
|
-
container.segmentEndMs = currentWord.end * 1000;
|
|
666
|
-
container.requestUpdate();
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// Update after containers - should be visible at the same time as active word
|
|
670
|
-
for (const container of this.afterActiveWordContainers) {
|
|
671
|
-
container.segmentText = afterWords;
|
|
672
|
-
container.segmentStartMs = currentWord.start * 1000;
|
|
673
|
-
container.segmentEndMs = currentWord.end * 1000;
|
|
674
|
-
container.requestUpdate();
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
} else if (currentSegment) {
|
|
678
|
-
// No active word but we have an active segment
|
|
679
|
-
const segmentWords = captionsData.word_segments.filter(
|
|
680
|
-
(word) =>
|
|
681
|
-
word.start >= currentSegment.start && word.end <= currentSegment.end,
|
|
682
|
-
);
|
|
683
|
-
|
|
684
|
-
// Check if we're before the first word or after the last word
|
|
685
|
-
const firstWord = segmentWords[0];
|
|
686
|
-
const isBeforeFirstWord = firstWord && currentTimeSec < firstWord.start;
|
|
687
|
-
|
|
688
|
-
if (isBeforeFirstWord) {
|
|
689
|
-
// Before first word starts - show all words in "after" container (they're all upcoming)
|
|
690
|
-
const allWords = segmentWords.map((w) => w.text.trim()).join(" ");
|
|
691
|
-
|
|
692
|
-
for (const container of this.beforeActiveWordContainers) {
|
|
693
|
-
container.segmentText = ""; // Nothing before yet
|
|
694
|
-
container.segmentStartMs = currentSegment.start * 1000;
|
|
695
|
-
container.segmentEndMs = currentSegment.end * 1000;
|
|
696
|
-
container.requestUpdate();
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
for (const container of this.afterActiveWordContainers) {
|
|
700
|
-
container.segmentText = allWords; // All words are upcoming
|
|
701
|
-
container.segmentStartMs = currentSegment.start * 1000;
|
|
702
|
-
container.segmentEndMs = currentSegment.end * 1000;
|
|
703
|
-
container.requestUpdate();
|
|
704
|
-
}
|
|
705
|
-
} else {
|
|
706
|
-
// After last word ends - show all completed words in "before" container
|
|
707
|
-
const allCompletedWords = segmentWords
|
|
708
|
-
.map((w) => w.text.trim())
|
|
709
|
-
.join(" ");
|
|
710
|
-
|
|
711
|
-
for (const container of this.beforeActiveWordContainers) {
|
|
712
|
-
container.segmentText = allCompletedWords;
|
|
713
|
-
container.segmentStartMs = currentSegment.start * 1000;
|
|
714
|
-
container.segmentEndMs = currentSegment.end * 1000;
|
|
715
|
-
container.requestUpdate();
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
for (const container of this.afterActiveWordContainers) {
|
|
719
|
-
container.segmentText = "";
|
|
720
|
-
container.segmentStartMs = currentSegment.start * 1000;
|
|
721
|
-
container.segmentEndMs = currentSegment.end * 1000;
|
|
722
|
-
container.requestUpdate();
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
} else {
|
|
726
|
-
// No active segment or word - clear all context containers
|
|
727
|
-
for (const container of this.beforeActiveWordContainers) {
|
|
728
|
-
container.segmentText = "";
|
|
729
|
-
container.segmentStartMs = 0;
|
|
730
|
-
container.segmentEndMs = 0;
|
|
731
|
-
container.requestUpdate();
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
for (const container of this.afterActiveWordContainers) {
|
|
735
|
-
container.segmentText = "";
|
|
736
|
-
container.segmentStartMs = 0;
|
|
737
|
-
container.segmentEndMs = 0;
|
|
738
|
-
container.requestUpdate();
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
get targetElement() {
|
|
744
|
-
const target = document.getElementById(this.targetSelector ?? "");
|
|
745
|
-
if (target instanceof EFAudio || target instanceof EFVideo) {
|
|
746
|
-
return target;
|
|
747
|
-
}
|
|
748
|
-
// When using custom captions data, a target is not required
|
|
749
|
-
if (this.hasCustomCaptionsData) {
|
|
750
|
-
return null;
|
|
751
|
-
}
|
|
752
|
-
return null;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
get hasCustomCaptionsData(): boolean {
|
|
756
|
-
return !!(this.captionsData || this.captionsSrc || this.captionsScript);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
// Follow the exact EFMedia pattern for safe duration integration
|
|
760
|
-
get intrinsicDurationMs(): number | undefined {
|
|
761
|
-
// Direct access to custom captions data - avoiding complex task dependencies
|
|
762
|
-
// Priority: direct data > script reference > external file
|
|
763
|
-
let captionsData: Caption | null = null;
|
|
764
|
-
|
|
765
|
-
if (this.captionsData) {
|
|
766
|
-
captionsData = this.captionsData;
|
|
767
|
-
} else if (this.captionsScript) {
|
|
768
|
-
const scriptElement = document.getElementById(this.captionsScript);
|
|
769
|
-
if (scriptElement?.textContent) {
|
|
770
|
-
try {
|
|
771
|
-
captionsData = JSON.parse(scriptElement.textContent) as Caption;
|
|
772
|
-
} catch {
|
|
773
|
-
// Invalid JSON, fall through to return undefined
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
} else if (this.customCaptionsDataTask.value) {
|
|
777
|
-
captionsData = this.customCaptionsDataTask.value as Caption;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
if (!captionsData) {
|
|
781
|
-
return undefined;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
if (
|
|
785
|
-
captionsData.segments.length === 0 &&
|
|
786
|
-
captionsData.word_segments.length === 0
|
|
787
|
-
) {
|
|
788
|
-
return 0;
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
// Find the maximum end time from both segments and word_segments
|
|
792
|
-
const maxSegmentEnd =
|
|
793
|
-
captionsData.segments.length > 0
|
|
794
|
-
? Math.max(...captionsData.segments.map((s) => s.end))
|
|
795
|
-
: 0;
|
|
796
|
-
const maxWordEnd =
|
|
797
|
-
captionsData.word_segments.length > 0
|
|
798
|
-
? Math.max(...captionsData.word_segments.map((w) => w.end))
|
|
799
|
-
: 0;
|
|
800
|
-
|
|
801
|
-
return Math.max(maxSegmentEnd, maxWordEnd) * 1000; // Convert to milliseconds
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// Follow the exact EFMedia pattern for safe duration integration
|
|
805
|
-
get hasOwnDuration(): boolean {
|
|
806
|
-
// Simple check - if we have any form of custom captions data, we have our own duration
|
|
807
|
-
return !!(
|
|
808
|
-
this.captionsData ||
|
|
809
|
-
this.captionsScript ||
|
|
810
|
-
this.customCaptionsDataTask.value
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
declare global {
|
|
816
|
-
interface HTMLElementTagNameMap {
|
|
817
|
-
"ef-captions": EFCaptions;
|
|
818
|
-
"ef-captions-active-word": EFCaptionsActiveWord;
|
|
819
|
-
"ef-captions-segment": EFCaptionsSegment;
|
|
820
|
-
"ef-captions-before-active-word": EFCaptionsBeforeActiveWord;
|
|
821
|
-
"ef-captions-after-active-word": EFCaptionsAfterActiveWord;
|
|
822
|
-
}
|
|
823
|
-
}
|