@editframe/elements 0.25.1-beta.0 → 0.26.1-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/EFAudio.d.ts +4 -4
- package/dist/elements/EFCaptions.d.ts +12 -12
- package/dist/elements/EFImage.d.ts +4 -4
- package/dist/elements/EFMedia/AssetMediaEngine.js +2 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +13 -0
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/JitMediaEngine.js +2 -1
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +11 -4
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js +16 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +11 -4
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +2 -2
- package/dist/elements/EFSurface.d.ts +4 -4
- package/dist/elements/EFTemporal.js +16 -2
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFThumbnailStrip.d.ts +4 -4
- package/dist/elements/EFTimegroup.d.ts +22 -0
- package/dist/elements/EFTimegroup.js +39 -0
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +4 -4
- package/dist/elements/EFWaveform.d.ts +4 -4
- package/dist/elements/updateAnimations.js +3 -1
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/gui/EFConfiguration.d.ts +4 -4
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFPreview.d.ts +4 -4
- package/dist/gui/EFResizableBox.d.ts +4 -4
- package/dist/gui/EFScrubber.d.ts +4 -4
- package/dist/gui/EFTimeDisplay.d.ts +4 -4
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFWorkbench.d.ts +6 -6
- package/dist/style.css +10 -0
- package/dist/transcoding/types/index.d.ts +1 -0
- package/package.json +2 -2
- package/src/elements/EFMedia/AssetMediaEngine.ts +1 -0
- package/src/elements/EFMedia/BaseMediaEngine.ts +20 -0
- package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +68 -0
- package/src/elements/EFMedia/JitMediaEngine.ts +1 -0
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +12 -0
- package/src/elements/EFMedia/shared/BufferUtils.ts +42 -0
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +12 -0
- package/src/elements/EFTemporal.ts +20 -4
- package/src/elements/EFTimegroup.browsertest.ts +198 -0
- package/src/elements/EFTimegroup.ts +61 -0
- package/src/elements/updateAnimations.browsertest.ts +801 -0
- package/src/elements/updateAnimations.ts +12 -1
- package/src/transcoding/types/index.ts +1 -0
- package/types.json +1 -1
|
@@ -73,7 +73,9 @@ const coordinateAnimationsForSingleElement = (element) => {
|
|
|
73
73
|
}
|
|
74
74
|
const adjustedTime = currentTime - delay;
|
|
75
75
|
const currentIteration = Math.floor(adjustedTime / duration);
|
|
76
|
-
|
|
76
|
+
let currentIterationTime = adjustedTime % duration;
|
|
77
|
+
const direction = timing.direction || "normal";
|
|
78
|
+
if (direction === "reverse" || direction === "alternate" && currentIteration % 2 === 1 || direction === "alternate-reverse" && currentIteration % 2 === 0) currentIterationTime = duration - currentIterationTime;
|
|
77
79
|
const maxSafeCurrentTime = delay + duration * iterations - ANIMATION_PRECISION_OFFSET;
|
|
78
80
|
if (currentIteration >= iterations) animation.currentTime = maxSafeCurrentTime;
|
|
79
81
|
else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updateAnimations.js","names":[],"sources":["../../src/elements/updateAnimations.ts"],"sourcesContent":["import {\n deepGetTemporalElements,\n type TemporalMixinInterface,\n} from \"./EFTemporal.ts\";\n\n// All animatable elements are temporal elements with HTMLElement interface\nexport type AnimatableElement = TemporalMixinInterface & HTMLElement;\n\n// Constants\nconst ANIMATION_PRECISION_OFFSET = 0.1; // Use 0.1ms to safely avoid completion threshold\nconst DEFAULT_ANIMATION_ITERATIONS = 1;\nconst PROGRESS_PROPERTY = \"--ef-progress\";\nconst DURATION_PROPERTY = \"--ef-duration\";\nconst TRANSITION_DURATION_PROPERTY = \"--ef-transition-duration\";\nconst TRANSITION_OUT_START_PROPERTY = \"--ef-transition-out-start\";\n\n/**\n * Represents the temporal state of an element relative to the timeline\n */\ninterface TemporalState {\n progress: number;\n isVisible: boolean;\n timelineTimeMs: number;\n}\n\n/**\n * Evaluates what the element's state should be based on the timeline\n */\nexport const evaluateTemporalState = (\n element: AnimatableElement,\n): TemporalState => {\n // Get timeline time from root timegroup, or use element's own time if it IS a timegroup\n const timelineTimeMs = (element.rootTimegroup ?? element).currentTimeMs;\n\n const progress =\n element.durationMs <= 0\n ? 1\n : Math.max(0, Math.min(1, element.currentTimeMs / element.durationMs));\n\n // Root elements and elements aligned with composition end should remain visible at exact end time\n // Other elements use exclusive end for clean transitions\n const isRootElement = !(element as any).parentTimegroup;\n const isLastElementInComposition =\n element.endTimeMs === element.rootTimegroup?.endTimeMs;\n const useInclusiveEnd = isRootElement || isLastElementInComposition;\n\n const isVisible =\n element.startTimeMs <= timelineTimeMs &&\n (useInclusiveEnd\n ? element.endTimeMs >= timelineTimeMs\n : element.endTimeMs > timelineTimeMs);\n\n return { progress, isVisible, timelineTimeMs };\n};\n\n/**\n * Evaluates element visibility specifically for animation coordination\n * Uses inclusive end boundaries to prevent animation jumps at exact boundaries\n */\nexport const evaluateTemporalStateForAnimation = (\n element: AnimatableElement,\n): TemporalState => {\n // Get timeline time from root timegroup, or use element's own time if it IS a timegroup\n const timelineTimeMs = (element.rootTimegroup ?? element).currentTimeMs;\n\n const progress =\n element.durationMs <= 0\n ? 1\n : Math.max(0, Math.min(1, element.currentTimeMs / element.durationMs));\n\n // For animation coordination, use inclusive end for ALL elements to prevent visual jumps\n const isVisible =\n element.startTimeMs <= timelineTimeMs &&\n element.endTimeMs >= timelineTimeMs;\n\n return { progress, isVisible, timelineTimeMs };\n};\n\n/**\n * Updates the visual state (CSS + display) to match temporal state\n */\nconst updateVisualState = (\n element: AnimatableElement,\n state: TemporalState,\n): void => {\n // Always set progress (needed for many use cases)\n element.style.setProperty(PROGRESS_PROPERTY, `${state.progress * 100}%`);\n\n // Handle visibility\n if (!state.isVisible) {\n if (element.style.display !== \"none\") {\n element.style.display = \"none\";\n }\n return;\n }\n\n if (element.style.display === \"none\") {\n element.style.display = \"\";\n }\n\n // Set other CSS properties for visible elements only\n element.style.setProperty(DURATION_PROPERTY, `${element.durationMs}ms`);\n element.style.setProperty(\n TRANSITION_DURATION_PROPERTY,\n `${element.parentTimegroup?.overlapMs ?? 0}ms`,\n );\n element.style.setProperty(\n TRANSITION_OUT_START_PROPERTY,\n `${element.durationMs - (element.parentTimegroup?.overlapMs ?? 0)}ms`,\n );\n};\n\n/**\n * Coordinates animations for a single element and its subtree, using the element as the time source\n */\nconst coordinateAnimationsForSingleElement = (\n element: AnimatableElement,\n): void => {\n const animations = element.getAnimations({ subtree: true });\n\n for (const animation of animations) {\n if (animation.playState === \"running\") {\n animation.pause();\n }\n\n const effect = animation.effect;\n if (!(effect && effect instanceof KeyframeEffect)) {\n continue;\n }\n\n const target = effect.target;\n if (!target) {\n continue;\n }\n\n // For animations in this element's subtree, always use this element as the time source\n // This handles both animations directly on the temporal element and on its non-temporal children\n const timing = effect.getTiming();\n const duration = Number(timing.duration) || 0;\n const delay = Number(timing.delay) || 0;\n const iterations =\n Number(timing.iterations) || DEFAULT_ANIMATION_ITERATIONS;\n\n if (duration <= 0) {\n animation.currentTime = 0;\n continue;\n }\n\n // Use the element itself as the time source (it's guaranteed to be temporal)\n const currentTime = element.ownCurrentTimeMs ?? 0;\n\n if (currentTime < delay) {\n animation.currentTime = 0;\n continue;\n }\n\n const adjustedTime = currentTime - delay;\n const currentIteration = Math.floor(adjustedTime / duration);\n
|
|
1
|
+
{"version":3,"file":"updateAnimations.js","names":[],"sources":["../../src/elements/updateAnimations.ts"],"sourcesContent":["import {\n deepGetTemporalElements,\n type TemporalMixinInterface,\n} from \"./EFTemporal.ts\";\n\n// All animatable elements are temporal elements with HTMLElement interface\nexport type AnimatableElement = TemporalMixinInterface & HTMLElement;\n\n// Constants\nconst ANIMATION_PRECISION_OFFSET = 0.1; // Use 0.1ms to safely avoid completion threshold\nconst DEFAULT_ANIMATION_ITERATIONS = 1;\nconst PROGRESS_PROPERTY = \"--ef-progress\";\nconst DURATION_PROPERTY = \"--ef-duration\";\nconst TRANSITION_DURATION_PROPERTY = \"--ef-transition-duration\";\nconst TRANSITION_OUT_START_PROPERTY = \"--ef-transition-out-start\";\n\n/**\n * Represents the temporal state of an element relative to the timeline\n */\ninterface TemporalState {\n progress: number;\n isVisible: boolean;\n timelineTimeMs: number;\n}\n\n/**\n * Evaluates what the element's state should be based on the timeline\n */\nexport const evaluateTemporalState = (\n element: AnimatableElement,\n): TemporalState => {\n // Get timeline time from root timegroup, or use element's own time if it IS a timegroup\n const timelineTimeMs = (element.rootTimegroup ?? element).currentTimeMs;\n\n const progress =\n element.durationMs <= 0\n ? 1\n : Math.max(0, Math.min(1, element.currentTimeMs / element.durationMs));\n\n // Root elements and elements aligned with composition end should remain visible at exact end time\n // Other elements use exclusive end for clean transitions\n const isRootElement = !(element as any).parentTimegroup;\n const isLastElementInComposition =\n element.endTimeMs === element.rootTimegroup?.endTimeMs;\n const useInclusiveEnd = isRootElement || isLastElementInComposition;\n\n const isVisible =\n element.startTimeMs <= timelineTimeMs &&\n (useInclusiveEnd\n ? element.endTimeMs >= timelineTimeMs\n : element.endTimeMs > timelineTimeMs);\n\n return { progress, isVisible, timelineTimeMs };\n};\n\n/**\n * Evaluates element visibility specifically for animation coordination\n * Uses inclusive end boundaries to prevent animation jumps at exact boundaries\n */\nexport const evaluateTemporalStateForAnimation = (\n element: AnimatableElement,\n): TemporalState => {\n // Get timeline time from root timegroup, or use element's own time if it IS a timegroup\n const timelineTimeMs = (element.rootTimegroup ?? element).currentTimeMs;\n\n const progress =\n element.durationMs <= 0\n ? 1\n : Math.max(0, Math.min(1, element.currentTimeMs / element.durationMs));\n\n // For animation coordination, use inclusive end for ALL elements to prevent visual jumps\n const isVisible =\n element.startTimeMs <= timelineTimeMs &&\n element.endTimeMs >= timelineTimeMs;\n\n return { progress, isVisible, timelineTimeMs };\n};\n\n/**\n * Updates the visual state (CSS + display) to match temporal state\n */\nconst updateVisualState = (\n element: AnimatableElement,\n state: TemporalState,\n): void => {\n // Always set progress (needed for many use cases)\n element.style.setProperty(PROGRESS_PROPERTY, `${state.progress * 100}%`);\n\n // Handle visibility\n if (!state.isVisible) {\n if (element.style.display !== \"none\") {\n element.style.display = \"none\";\n }\n return;\n }\n\n if (element.style.display === \"none\") {\n element.style.display = \"\";\n }\n\n // Set other CSS properties for visible elements only\n element.style.setProperty(DURATION_PROPERTY, `${element.durationMs}ms`);\n element.style.setProperty(\n TRANSITION_DURATION_PROPERTY,\n `${element.parentTimegroup?.overlapMs ?? 0}ms`,\n );\n element.style.setProperty(\n TRANSITION_OUT_START_PROPERTY,\n `${element.durationMs - (element.parentTimegroup?.overlapMs ?? 0)}ms`,\n );\n};\n\n/**\n * Coordinates animations for a single element and its subtree, using the element as the time source\n */\nconst coordinateAnimationsForSingleElement = (\n element: AnimatableElement,\n): void => {\n const animations = element.getAnimations({ subtree: true });\n\n for (const animation of animations) {\n if (animation.playState === \"running\") {\n animation.pause();\n }\n\n const effect = animation.effect;\n if (!(effect && effect instanceof KeyframeEffect)) {\n continue;\n }\n\n const target = effect.target;\n if (!target) {\n continue;\n }\n\n // For animations in this element's subtree, always use this element as the time source\n // This handles both animations directly on the temporal element and on its non-temporal children\n const timing = effect.getTiming();\n const duration = Number(timing.duration) || 0;\n const delay = Number(timing.delay) || 0;\n const iterations =\n Number(timing.iterations) || DEFAULT_ANIMATION_ITERATIONS;\n\n if (duration <= 0) {\n animation.currentTime = 0;\n continue;\n }\n\n // Use the element itself as the time source (it's guaranteed to be temporal)\n const currentTime = element.ownCurrentTimeMs ?? 0;\n\n if (currentTime < delay) {\n animation.currentTime = 0;\n continue;\n }\n\n const adjustedTime = currentTime - delay;\n const currentIteration = Math.floor(adjustedTime / duration);\n let currentIterationTime = adjustedTime % duration;\n\n // Handle animation-direction\n const direction = timing.direction || \"normal\";\n const shouldReverse =\n direction === \"reverse\" ||\n (direction === \"alternate\" && currentIteration % 2 === 1) ||\n (direction === \"alternate-reverse\" && currentIteration % 2 === 0);\n\n if (shouldReverse) {\n currentIterationTime = duration - currentIterationTime;\n }\n\n // Calculate the total animation timeline length (delay + duration * iterations)\n const totalAnimationLength = delay + duration * iterations;\n\n // CRITICAL: Always keep currentTime below totalAnimationLength to prevent completion\n const maxSafeCurrentTime =\n totalAnimationLength - ANIMATION_PRECISION_OFFSET;\n\n if (currentIteration >= iterations) {\n // Animation would be complete - clamp to just before completion\n animation.currentTime = maxSafeCurrentTime;\n } else {\n // Animation in progress - clamp to safe value within current iteration\n const proposedCurrentTime =\n Math.min(currentIterationTime, duration - ANIMATION_PRECISION_OFFSET) +\n delay;\n animation.currentTime = Math.min(proposedCurrentTime, maxSafeCurrentTime);\n }\n }\n};\n\n/**\n * Main function: synchronizes DOM element with timeline\n */\nexport const updateAnimations = (element: AnimatableElement): void => {\n const temporalState = evaluateTemporalState(element);\n deepGetTemporalElements(element).forEach((temporalElement) => {\n const temporalState = evaluateTemporalState(temporalElement);\n updateVisualState(temporalElement, temporalState);\n });\n updateVisualState(element, temporalState);\n\n // Coordinate animations - use animation-specific visibility to prevent jumps at exact boundaries\n const animationState = evaluateTemporalStateForAnimation(element);\n if (animationState.isVisible) {\n coordinateAnimationsForSingleElement(element);\n }\n\n // Coordinate animations for child elements using animation-specific visibility\n deepGetTemporalElements(element).forEach((temporalElement) => {\n const childAnimationState =\n evaluateTemporalStateForAnimation(temporalElement);\n if (childAnimationState.isVisible) {\n coordinateAnimationsForSingleElement(temporalElement);\n }\n });\n};\n"],"mappings":"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"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as lit9 from "lit";
|
|
2
2
|
import { LitElement } from "lit";
|
|
3
|
-
import * as
|
|
3
|
+
import * as lit_html9 from "lit-html";
|
|
4
4
|
|
|
5
5
|
//#region src/gui/EFConfiguration.d.ts
|
|
6
6
|
declare class EFConfiguration extends LitElement {
|
|
7
|
-
static styles:
|
|
7
|
+
static styles: lit9.CSSResult[];
|
|
8
8
|
efConfiguration: this;
|
|
9
9
|
apiHost?: string;
|
|
10
10
|
signingURL: string;
|
|
11
11
|
mediaEngine?: "cloud" | "local";
|
|
12
|
-
render():
|
|
12
|
+
render(): lit_html9.TemplateResult<1>;
|
|
13
13
|
}
|
|
14
14
|
declare global {
|
|
15
15
|
interface HTMLElementTagNameMap {
|
package/dist/gui/EFControls.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TemporalMixinInterface } from "../elements/EFTemporal.js";
|
|
2
2
|
import { ControllableInterface } from "./Controllable.js";
|
|
3
3
|
import { FocusContext } from "./focusContext.js";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit21 from "lit";
|
|
5
5
|
import { LitElement, PropertyValueMap } from "lit";
|
|
6
6
|
|
|
7
7
|
//#region src/gui/EFControls.d.ts
|
|
@@ -26,7 +26,7 @@ import { LitElement, PropertyValueMap } from "lit";
|
|
|
26
26
|
*/
|
|
27
27
|
declare class EFControls extends LitElement {
|
|
28
28
|
#private;
|
|
29
|
-
static styles:
|
|
29
|
+
static styles: lit21.CSSResult;
|
|
30
30
|
createRenderRoot(): this;
|
|
31
31
|
/**
|
|
32
32
|
* The ID of the ef-preview element to control
|
package/dist/gui/EFDial.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as lit20 from "lit";
|
|
2
2
|
import { LitElement } from "lit";
|
|
3
|
-
import * as
|
|
3
|
+
import * as lit_html18 from "lit-html";
|
|
4
4
|
|
|
5
5
|
//#region src/gui/EFDial.d.ts
|
|
6
6
|
interface DialChangeDetail {
|
|
@@ -13,12 +13,12 @@ declare class EFDial extends LitElement {
|
|
|
13
13
|
private isDragging;
|
|
14
14
|
private dragStartAngle;
|
|
15
15
|
private dragStartValue;
|
|
16
|
-
static styles:
|
|
16
|
+
static styles: lit20.CSSResult;
|
|
17
17
|
private getAngleFromPoint;
|
|
18
18
|
private handlePointerDown;
|
|
19
19
|
private handlePointerMove;
|
|
20
20
|
private handlePointerUp;
|
|
21
|
-
render():
|
|
21
|
+
render(): lit_html18.TemplateResult<1>;
|
|
22
22
|
}
|
|
23
23
|
//#endregion
|
|
24
24
|
export { DialChangeDetail, EFDial };
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as lit22 from "lit";
|
|
2
2
|
import { LitElement } from "lit";
|
|
3
|
-
import * as
|
|
3
|
+
import * as lit_html19 from "lit-html";
|
|
4
4
|
import * as lit_html_directives_ref_js3 from "lit-html/directives/ref.js";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFFocusOverlay.d.ts
|
|
7
7
|
declare class EFFocusOverlay extends LitElement {
|
|
8
|
-
static styles:
|
|
8
|
+
static styles: lit22.CSSResult;
|
|
9
9
|
focusedElement?: HTMLElement | null;
|
|
10
10
|
overlay: lit_html_directives_ref_js3.Ref<HTMLDivElement>;
|
|
11
11
|
private animationFrame?;
|
|
12
12
|
drawOverlay: () => void;
|
|
13
|
-
render():
|
|
13
|
+
render(): lit_html19.TemplateResult<1>;
|
|
14
14
|
connectedCallback(): void;
|
|
15
15
|
disconnectedCallback(): void;
|
|
16
16
|
protected updated(): void;
|
package/dist/gui/EFPause.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit16 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html14 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFPause.d.ts
|
|
7
7
|
declare const EFPause_base: (new (...args: any[]) => {
|
|
@@ -10,13 +10,13 @@ declare const EFPause_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFPause extends EFPause_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit16.CSSResult[];
|
|
14
14
|
playing: boolean;
|
|
15
15
|
get efContext(): ControllableInterface | null;
|
|
16
16
|
connectedCallback(): void;
|
|
17
17
|
disconnectedCallback(): void;
|
|
18
18
|
updated(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
19
|
-
render():
|
|
19
|
+
render(): lit_html14.TemplateResult<1>;
|
|
20
20
|
handleClick: () => void;
|
|
21
21
|
}
|
|
22
22
|
declare global {
|
package/dist/gui/EFPlay.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit15 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html13 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFPlay.d.ts
|
|
7
7
|
declare const EFPlay_base: (new (...args: any[]) => {
|
|
@@ -10,13 +10,13 @@ declare const EFPlay_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFPlay extends EFPlay_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit15.CSSResult[];
|
|
14
14
|
playing: boolean;
|
|
15
15
|
get efContext(): ControllableInterface | null;
|
|
16
16
|
connectedCallback(): void;
|
|
17
17
|
disconnectedCallback(): void;
|
|
18
18
|
updated(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
19
|
-
render():
|
|
19
|
+
render(): lit_html13.TemplateResult<1>;
|
|
20
20
|
handleClick: () => void;
|
|
21
21
|
}
|
|
22
22
|
declare global {
|
package/dist/gui/EFPreview.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { ContextMixinInterface } from "./ContextMixin.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit11 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html11 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFPreview.d.ts
|
|
7
7
|
declare const EFPreview_base: (new (...args: any[]) => ContextMixinInterface) & typeof LitElement;
|
|
8
8
|
declare class EFPreview extends EFPreview_base {
|
|
9
|
-
static styles:
|
|
9
|
+
static styles: lit11.CSSResult[];
|
|
10
10
|
focusedElement?: HTMLElement;
|
|
11
11
|
/**
|
|
12
12
|
* Find the closest temporal element (timegroup, video, audio, etc.)
|
|
13
13
|
*/
|
|
14
14
|
private findClosestTemporal;
|
|
15
15
|
constructor();
|
|
16
|
-
render():
|
|
16
|
+
render(): lit_html11.TemplateResult<1>;
|
|
17
17
|
}
|
|
18
18
|
declare global {
|
|
19
19
|
interface HTMLElementTagNameMap {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as lit23 from "lit";
|
|
2
2
|
import { LitElement } from "lit";
|
|
3
|
-
import * as
|
|
3
|
+
import * as lit_html20 from "lit-html";
|
|
4
4
|
|
|
5
5
|
//#region src/gui/EFResizableBox.d.ts
|
|
6
6
|
interface BoxBounds {
|
|
@@ -18,7 +18,7 @@ declare class EFResizableBox extends LitElement {
|
|
|
18
18
|
private dragMode;
|
|
19
19
|
private interaction;
|
|
20
20
|
private modifiers;
|
|
21
|
-
static styles:
|
|
21
|
+
static styles: lit23.CSSResult;
|
|
22
22
|
private resizeObserver?;
|
|
23
23
|
connectedCallback(): void;
|
|
24
24
|
private handlePointerDown;
|
|
@@ -33,7 +33,7 @@ declare class EFResizableBox extends LitElement {
|
|
|
33
33
|
private simpleConstrainBounds;
|
|
34
34
|
private constrainResizeDeltas;
|
|
35
35
|
private dispatchBoundsChange;
|
|
36
|
-
render():
|
|
36
|
+
render(): lit_html20.TemplateResult<1>;
|
|
37
37
|
private renderHandles;
|
|
38
38
|
}
|
|
39
39
|
//#endregion
|
package/dist/gui/EFScrubber.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit18 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html16 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFScrubber.d.ts
|
|
7
7
|
declare const EFScrubber_base: (new (...args: any[]) => {
|
|
@@ -10,7 +10,7 @@ declare const EFScrubber_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFScrubber extends EFScrubber_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit18.CSSResult[];
|
|
14
14
|
playing: boolean;
|
|
15
15
|
currentTimeMs: number;
|
|
16
16
|
durationMs: number;
|
|
@@ -22,7 +22,7 @@ declare class EFScrubber extends EFScrubber_base {
|
|
|
22
22
|
private boundHandlePointerDown;
|
|
23
23
|
private boundHandlePointerMove;
|
|
24
24
|
private boundHandlePointerUp;
|
|
25
|
-
render():
|
|
25
|
+
render(): lit_html16.TemplateResult<1>;
|
|
26
26
|
connectedCallback(): void;
|
|
27
27
|
disconnectedCallback(): void;
|
|
28
28
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit19 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html17 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFTimeDisplay.d.ts
|
|
7
7
|
declare const EFTimeDisplay_base: (new (...args: any[]) => {
|
|
@@ -10,11 +10,11 @@ declare const EFTimeDisplay_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFTimeDisplay extends EFTimeDisplay_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit19.CSSResult;
|
|
14
14
|
currentTimeMs: number;
|
|
15
15
|
durationMs: number;
|
|
16
16
|
private formatTime;
|
|
17
|
-
render():
|
|
17
|
+
render(): lit_html17.TemplateResult<1>;
|
|
18
18
|
}
|
|
19
19
|
declare global {
|
|
20
20
|
interface HTMLElementTagNameMap {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit17 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html15 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFToggleLoop.d.ts
|
|
7
7
|
declare const EFToggleLoop_base: (new (...args: any[]) => {
|
|
@@ -10,9 +10,9 @@ declare const EFToggleLoop_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFToggleLoop extends EFToggleLoop_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit17.CSSResult[];
|
|
14
14
|
get context(): ControllableInterface | null;
|
|
15
|
-
render():
|
|
15
|
+
render(): lit_html15.TemplateResult<1>;
|
|
16
16
|
}
|
|
17
17
|
declare global {
|
|
18
18
|
interface HTMLElementTagNameMap {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ControllableInterface } from "./Controllable.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit14 from "lit";
|
|
3
3
|
import { LitElement } from "lit";
|
|
4
|
-
import * as
|
|
4
|
+
import * as lit_html12 from "lit-html";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/EFTogglePlay.d.ts
|
|
7
7
|
declare const EFTogglePlay_base: (new (...args: any[]) => {
|
|
@@ -10,12 +10,12 @@ declare const EFTogglePlay_base: (new (...args: any[]) => {
|
|
|
10
10
|
effectiveContext: ControllableInterface | null;
|
|
11
11
|
}) & typeof LitElement;
|
|
12
12
|
declare class EFTogglePlay extends EFTogglePlay_base {
|
|
13
|
-
static styles:
|
|
13
|
+
static styles: lit14.CSSResult[];
|
|
14
14
|
playing: boolean;
|
|
15
15
|
get efContext(): ControllableInterface | null;
|
|
16
16
|
connectedCallback(): void;
|
|
17
17
|
disconnectedCallback(): void;
|
|
18
|
-
render():
|
|
18
|
+
render(): lit_html12.TemplateResult<1>;
|
|
19
19
|
togglePlay: () => void;
|
|
20
20
|
}
|
|
21
21
|
declare global {
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { ContextMixinInterface } from "./ContextMixin.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as lit10 from "lit";
|
|
3
3
|
import { LitElement, PropertyValueMap } from "lit";
|
|
4
|
-
import * as
|
|
5
|
-
import * as
|
|
4
|
+
import * as lit_html10 from "lit-html";
|
|
5
|
+
import * as lit_html_directives_ref_js2 from "lit-html/directives/ref.js";
|
|
6
6
|
|
|
7
7
|
//#region src/gui/EFWorkbench.d.ts
|
|
8
8
|
declare const EFWorkbench_base: (new (...args: any[]) => ContextMixinInterface) & typeof LitElement;
|
|
9
9
|
declare class EFWorkbench extends EFWorkbench_base {
|
|
10
|
-
static styles:
|
|
10
|
+
static styles: lit10.CSSResult[];
|
|
11
11
|
rendering: boolean;
|
|
12
|
-
focusOverlay:
|
|
12
|
+
focusOverlay: lit_html_directives_ref_js2.Ref<HTMLDivElement>;
|
|
13
13
|
handleStageWheel(event: WheelEvent): void;
|
|
14
14
|
connectedCallback(): void;
|
|
15
15
|
disconnectedCallback(): void;
|
|
16
16
|
update(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
|
|
17
17
|
drawOverlays: () => void;
|
|
18
|
-
render():
|
|
18
|
+
render(): lit_html10.TemplateResult<1>;
|
|
19
19
|
}
|
|
20
20
|
declare global {
|
|
21
21
|
interface HTMLElementTagNameMap {
|
package/dist/style.css
CHANGED
|
@@ -827,6 +827,10 @@ video {
|
|
|
827
827
|
--tw-blur: blur(8px);
|
|
828
828
|
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
|
829
829
|
}
|
|
830
|
+
.invert {
|
|
831
|
+
--tw-invert: invert(100%);
|
|
832
|
+
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
|
833
|
+
}
|
|
830
834
|
.filter {
|
|
831
835
|
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
|
832
836
|
}
|
|
@@ -838,6 +842,12 @@ video {
|
|
|
838
842
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
839
843
|
transition-duration: 150ms;
|
|
840
844
|
}
|
|
845
|
+
.ease-in {
|
|
846
|
+
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
|
|
847
|
+
}
|
|
848
|
+
.ease-in-out {
|
|
849
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
850
|
+
}
|
|
841
851
|
.ease-out {
|
|
842
852
|
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
|
843
853
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.1-beta.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"license": "UNLICENSED",
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@bramus/style-observer": "^1.3.0",
|
|
16
|
-
"@editframe/assets": "0.
|
|
16
|
+
"@editframe/assets": "0.26.1-beta.0",
|
|
17
17
|
"@lit/context": "^1.1.6",
|
|
18
18
|
"@lit/task": "^1.0.3",
|
|
19
19
|
"@opentelemetry/api": "^1.9.0",
|
|
@@ -346,6 +346,7 @@ export class AssetMediaEngine extends BaseMediaEngine implements MediaEngine {
|
|
|
346
346
|
audioBufferDurationMs: 2000,
|
|
347
347
|
maxVideoBufferFetches: 1,
|
|
348
348
|
maxAudioBufferFetches: 1,
|
|
349
|
+
bufferThresholdMs: 30000, // Timeline-aware buffering threshold
|
|
349
350
|
};
|
|
350
351
|
}
|
|
351
352
|
|
|
@@ -482,4 +482,24 @@ export abstract class BaseMediaEngine {
|
|
|
482
482
|
segmentId: number,
|
|
483
483
|
rendition: VideoRendition,
|
|
484
484
|
): number[];
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Get buffer configuration for this media engine
|
|
488
|
+
* Can be overridden by subclasses to provide custom buffer settings
|
|
489
|
+
*/
|
|
490
|
+
getBufferConfig(): {
|
|
491
|
+
videoBufferDurationMs: number;
|
|
492
|
+
audioBufferDurationMs: number;
|
|
493
|
+
maxVideoBufferFetches: number;
|
|
494
|
+
maxAudioBufferFetches: number;
|
|
495
|
+
bufferThresholdMs: number;
|
|
496
|
+
} {
|
|
497
|
+
return {
|
|
498
|
+
videoBufferDurationMs: 10000, // 10 seconds
|
|
499
|
+
audioBufferDurationMs: 10000, // 10 seconds
|
|
500
|
+
maxVideoBufferFetches: 3,
|
|
501
|
+
maxAudioBufferFetches: 3,
|
|
502
|
+
bufferThresholdMs: 30000, // 30 seconds - timeline-aware buffering threshold
|
|
503
|
+
};
|
|
504
|
+
}
|
|
485
505
|
}
|
|
@@ -155,4 +155,72 @@ describe("JitMediaEngine", () => {
|
|
|
155
155
|
"http://localhost:63315/api/v1/transcode/{rendition}/{segmentId}.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
156
156
|
});
|
|
157
157
|
});
|
|
158
|
+
|
|
159
|
+
test("calculatePlayheadDistance utility function", async ({ expect }) => {
|
|
160
|
+
const { calculatePlayheadDistance } = await import(
|
|
161
|
+
"./shared/BufferUtils.js"
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Element is currently active (playhead within element bounds)
|
|
165
|
+
expect(
|
|
166
|
+
calculatePlayheadDistance(
|
|
167
|
+
{ startTimeMs: 0, endTimeMs: 2000 },
|
|
168
|
+
1000, // playhead at 1s
|
|
169
|
+
),
|
|
170
|
+
).toBe(0);
|
|
171
|
+
|
|
172
|
+
// Element hasn't started yet (playhead before element)
|
|
173
|
+
expect(
|
|
174
|
+
calculatePlayheadDistance(
|
|
175
|
+
{ startTimeMs: 2000, endTimeMs: 4000 },
|
|
176
|
+
0, // playhead at 0s
|
|
177
|
+
),
|
|
178
|
+
).toBe(2000);
|
|
179
|
+
|
|
180
|
+
// Element already finished (playhead after element)
|
|
181
|
+
expect(
|
|
182
|
+
calculatePlayheadDistance(
|
|
183
|
+
{ startTimeMs: 0, endTimeMs: 2000 },
|
|
184
|
+
5000, // playhead at 5s
|
|
185
|
+
),
|
|
186
|
+
).toBe(3000);
|
|
187
|
+
|
|
188
|
+
// Playhead at element start boundary
|
|
189
|
+
expect(
|
|
190
|
+
calculatePlayheadDistance(
|
|
191
|
+
{ startTimeMs: 2000, endTimeMs: 4000 },
|
|
192
|
+
2000, // playhead exactly at start
|
|
193
|
+
),
|
|
194
|
+
).toBe(0);
|
|
195
|
+
|
|
196
|
+
// Playhead at element end boundary
|
|
197
|
+
expect(
|
|
198
|
+
calculatePlayheadDistance(
|
|
199
|
+
{ startTimeMs: 2000, endTimeMs: 4000 },
|
|
200
|
+
4000, // playhead exactly at end
|
|
201
|
+
),
|
|
202
|
+
).toBe(0);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("buffer config includes timeline threshold", async ({ expect }) => {
|
|
206
|
+
const configuration = document.createElement("ef-configuration");
|
|
207
|
+
const apiHost = `${window.location.protocol}//${window.location.host}`;
|
|
208
|
+
configuration.setAttribute("api-host", apiHost);
|
|
209
|
+
configuration.apiHost = apiHost;
|
|
210
|
+
configuration.signingURL = "";
|
|
211
|
+
|
|
212
|
+
const video = document.createElement("ef-video");
|
|
213
|
+
video.src = "http://web:3000/head-moov-480p.mp4";
|
|
214
|
+
configuration.appendChild(video);
|
|
215
|
+
document.body.appendChild(configuration);
|
|
216
|
+
|
|
217
|
+
// Wait for media engine to initialize
|
|
218
|
+
const mediaEngine = await video.mediaEngineTask.taskComplete;
|
|
219
|
+
|
|
220
|
+
// Check that buffer config includes the threshold
|
|
221
|
+
const bufferConfig = mediaEngine.getBufferConfig();
|
|
222
|
+
expect(bufferConfig.bufferThresholdMs).toBe(30000);
|
|
223
|
+
|
|
224
|
+
configuration.remove();
|
|
225
|
+
});
|
|
158
226
|
});
|
|
@@ -206,6 +206,7 @@ export class JitMediaEngine extends BaseMediaEngine implements MediaEngine {
|
|
|
206
206
|
audioBufferDurationMs: 8000,
|
|
207
207
|
maxVideoBufferFetches: 3,
|
|
208
208
|
maxAudioBufferFetches: 3,
|
|
209
|
+
bufferThresholdMs: 30000, // Timeline-aware buffering threshold
|
|
209
210
|
};
|
|
210
211
|
}
|
|
211
212
|
|
|
@@ -62,8 +62,19 @@ export const makeAudioBufferTask = (host: EFMedia): AudioBufferTask => {
|
|
|
62
62
|
bufferDurationMs,
|
|
63
63
|
maxParallelFetches,
|
|
64
64
|
enableBuffering: host.enableAudioBuffering,
|
|
65
|
+
bufferThresholdMs: engineConfig.bufferThresholdMs,
|
|
65
66
|
};
|
|
66
67
|
|
|
68
|
+
// Timeline context for priority-based buffering
|
|
69
|
+
const timelineContext =
|
|
70
|
+
host.rootTimegroup?.currentTimeMs !== undefined
|
|
71
|
+
? {
|
|
72
|
+
elementStartMs: host.startTimeMs,
|
|
73
|
+
elementEndMs: host.endTimeMs,
|
|
74
|
+
playheadMs: host.rootTimegroup.currentTimeMs,
|
|
75
|
+
}
|
|
76
|
+
: undefined;
|
|
77
|
+
|
|
67
78
|
return manageMediaBuffer<AudioRendition>(
|
|
68
79
|
seekTimeMs,
|
|
69
80
|
currentConfig,
|
|
@@ -99,6 +110,7 @@ export const makeAudioBufferTask = (host: EFMedia): AudioBufferTask => {
|
|
|
99
110
|
},
|
|
100
111
|
logError: console.error,
|
|
101
112
|
},
|
|
113
|
+
timelineContext,
|
|
102
114
|
);
|
|
103
115
|
},
|
|
104
116
|
});
|
|
@@ -21,6 +21,7 @@ export interface MediaBufferConfig {
|
|
|
21
21
|
maxParallelFetches: number;
|
|
22
22
|
enableBuffering: boolean;
|
|
23
23
|
enableContinuousBuffering?: boolean;
|
|
24
|
+
bufferThresholdMs?: number; // Timeline-aware buffering threshold (default: 30000ms)
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -189,6 +190,26 @@ export const getUnrequestedSegments = (
|
|
|
189
190
|
return segmentIds.filter((id) => !bufferState.requestedSegments.has(id));
|
|
190
191
|
};
|
|
191
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Calculate distance from element to playhead position
|
|
195
|
+
* Returns 0 if element is currently active, otherwise returns distance in milliseconds
|
|
196
|
+
*/
|
|
197
|
+
export const calculatePlayheadDistance = (
|
|
198
|
+
element: { startTimeMs: number; endTimeMs: number },
|
|
199
|
+
playheadMs: number,
|
|
200
|
+
): number => {
|
|
201
|
+
// Element hasn't started yet
|
|
202
|
+
if (playheadMs < element.startTimeMs) {
|
|
203
|
+
return element.startTimeMs - playheadMs;
|
|
204
|
+
}
|
|
205
|
+
// Element already finished
|
|
206
|
+
if (playheadMs > element.endTimeMs) {
|
|
207
|
+
return playheadMs - element.endTimeMs;
|
|
208
|
+
}
|
|
209
|
+
// Element is currently active
|
|
210
|
+
return 0;
|
|
211
|
+
};
|
|
212
|
+
|
|
192
213
|
/**
|
|
193
214
|
* Core media buffering orchestration logic - prefetch only, no data storage
|
|
194
215
|
* Integrates with BaseMediaEngine's existing caching and request deduplication
|
|
@@ -202,11 +223,32 @@ export const manageMediaBuffer = async <
|
|
|
202
223
|
durationMs: number,
|
|
203
224
|
signal: AbortSignal,
|
|
204
225
|
deps: MediaBufferDependencies<T>,
|
|
226
|
+
timelineContext?: {
|
|
227
|
+
elementStartMs: number;
|
|
228
|
+
elementEndMs: number;
|
|
229
|
+
playheadMs: number;
|
|
230
|
+
},
|
|
205
231
|
): Promise<MediaBufferState> => {
|
|
206
232
|
if (!config.enableBuffering) {
|
|
207
233
|
return currentState;
|
|
208
234
|
}
|
|
209
235
|
|
|
236
|
+
// Timeline-aware buffering: skip if element is too far from playhead
|
|
237
|
+
if (timelineContext && config.bufferThresholdMs !== undefined) {
|
|
238
|
+
const distance = calculatePlayheadDistance(
|
|
239
|
+
{
|
|
240
|
+
startTimeMs: timelineContext.elementStartMs,
|
|
241
|
+
endTimeMs: timelineContext.elementEndMs,
|
|
242
|
+
},
|
|
243
|
+
timelineContext.playheadMs,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (distance > config.bufferThresholdMs) {
|
|
247
|
+
// Element is too far from playhead, skip buffering
|
|
248
|
+
return currentState;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
210
252
|
const rendition = await deps.getRendition();
|
|
211
253
|
if (!rendition) {
|
|
212
254
|
// Cannot buffer without a rendition
|