@clockworkdog/cogs-client 2.11.2 → 3.0.0-alpha.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.
@@ -0,0 +1,199 @@
1
+ import { z } from 'zod';
2
+ export type TemporalProperties = z.infer<typeof TemporalProperties>;
3
+ declare const TemporalProperties: z.ZodObject<{
4
+ t: z.ZodNumber;
5
+ rate: z.ZodNumber;
6
+ }, z.core.$strip>;
7
+ export type VisualProperties = z.infer<typeof VisualProperties>;
8
+ declare const VisualProperties: z.ZodObject<{
9
+ opacity: z.ZodNumber;
10
+ }, z.core.$strip>;
11
+ export type AudialProperties = z.infer<typeof AudialProperties>;
12
+ declare const AudialProperties: z.ZodObject<{
13
+ volume: z.ZodNumber;
14
+ }, z.core.$strip>;
15
+ export type ImageMetadata = z.infer<typeof ImageMetadata>;
16
+ declare const ImageMetadata: z.ZodObject<{
17
+ type: z.ZodLiteral<"image">;
18
+ file: z.ZodString;
19
+ fit: z.ZodUnion<readonly [z.ZodLiteral<"contain">, z.ZodLiteral<"cover">, z.ZodLiteral<"none">]>;
20
+ }, z.core.$strip>;
21
+ export type AudioMetadata = z.infer<typeof AudioMetadata>;
22
+ declare const AudioMetadata: z.ZodObject<{
23
+ type: z.ZodLiteral<"audio">;
24
+ file: z.ZodString;
25
+ audioOutput: z.ZodString;
26
+ }, z.core.$strip>;
27
+ export type VideoMetadata = z.infer<typeof VideoMetadata>;
28
+ declare const VideoMetadata: z.ZodObject<{
29
+ type: z.ZodLiteral<"video">;
30
+ file: z.ZodString;
31
+ audioOutput: z.ZodString;
32
+ fit: z.ZodUnion<readonly [z.ZodLiteral<"contain">, z.ZodLiteral<"cover">, z.ZodLiteral<"none">]>;
33
+ }, z.core.$strip>;
34
+ export type NullKeyframe = z.infer<typeof NullKeyframe>;
35
+ declare const NullKeyframe: z.ZodTuple<[z.ZodNumber, z.ZodNull], null>;
36
+ /**
37
+ * Keyframes are indexed by a timestamp given in ms
38
+ */
39
+ export type InitialImageKeyframe = z.infer<typeof InitialImageKeyframe>;
40
+ declare const InitialImageKeyframe: z.ZodTuple<[z.ZodNumber, z.ZodObject<{
41
+ set: z.ZodOptional<z.ZodObject<{
42
+ opacity: z.ZodOptional<z.ZodNumber>;
43
+ }, z.core.$strip>>;
44
+ }, z.core.$strip>], null>;
45
+ /**
46
+ * Keyframes are indexed by a timestamp given in ms
47
+ */
48
+ export type ImageKeyframe = z.infer<typeof ImageKeyframe>;
49
+ declare const ImageKeyframe: z.ZodTuple<[z.ZodNumber, z.ZodObject<{
50
+ set: z.ZodOptional<z.ZodObject<{
51
+ opacity: z.ZodOptional<z.ZodNumber>;
52
+ }, z.core.$strip>>;
53
+ lerp: z.ZodOptional<z.ZodObject<{
54
+ opacity: z.ZodOptional<z.ZodNumber>;
55
+ }, z.core.$strip>>;
56
+ }, z.core.$strip>], null>;
57
+ /**
58
+ * Keyframes are indexed by a timestamp given in ms
59
+ */
60
+ export type InitialAudioKeyframe = z.infer<typeof InitialAudioKeyframe>;
61
+ declare const InitialAudioKeyframe: z.ZodTuple<[z.ZodNumber, z.ZodObject<{
62
+ set: z.ZodOptional<z.ZodObject<{
63
+ volume: z.ZodOptional<z.ZodNumber>;
64
+ t: z.ZodOptional<z.ZodNumber>;
65
+ rate: z.ZodOptional<z.ZodNumber>;
66
+ }, z.core.$strip>>;
67
+ }, z.core.$strip>], null>;
68
+ /**
69
+ * Keyframes are indexed by a timestamp given in ms
70
+ */
71
+ export type AudioKeyframe = z.infer<typeof AudioKeyframe>;
72
+ declare const AudioKeyframe: z.ZodTuple<[z.ZodNumber, z.ZodObject<{
73
+ set: z.ZodOptional<z.ZodObject<{
74
+ volume: z.ZodOptional<z.ZodNumber>;
75
+ t: z.ZodOptional<z.ZodNumber>;
76
+ rate: z.ZodOptional<z.ZodNumber>;
77
+ }, z.core.$strip>>;
78
+ lerp: z.ZodOptional<z.ZodObject<{
79
+ volume: z.ZodOptional<z.ZodNumber>;
80
+ }, z.core.$strip>>;
81
+ }, z.core.$strip>], null>;
82
+ /**
83
+ * Keyframes are indexed by a timestamp given in ms
84
+ */
85
+ export type InitialVideoKeyframe = z.infer<typeof InitialVideoKeyframe>;
86
+ declare const InitialVideoKeyframe: z.ZodTuple<[z.ZodNumber, z.ZodObject<{
87
+ set: z.ZodOptional<z.ZodObject<{
88
+ opacity: z.ZodOptional<z.ZodNumber>;
89
+ volume: z.ZodOptional<z.ZodNumber>;
90
+ t: z.ZodOptional<z.ZodNumber>;
91
+ rate: z.ZodOptional<z.ZodNumber>;
92
+ }, z.core.$strip>>;
93
+ }, z.core.$strip>], null>;
94
+ /**
95
+ * Keyframes are indexed by a timestamp given in ms
96
+ */
97
+ export type VideoKeyframe = z.infer<typeof VideoKeyframe>;
98
+ declare const VideoKeyframe: z.ZodTuple<[z.ZodNumber, z.ZodObject<{
99
+ set: z.ZodOptional<z.ZodObject<{
100
+ opacity: z.ZodOptional<z.ZodNumber>;
101
+ volume: z.ZodOptional<z.ZodNumber>;
102
+ t: z.ZodOptional<z.ZodNumber>;
103
+ rate: z.ZodOptional<z.ZodNumber>;
104
+ }, z.core.$strip>>;
105
+ lerp: z.ZodOptional<z.ZodObject<{
106
+ opacity: z.ZodOptional<z.ZodNumber>;
107
+ volume: z.ZodOptional<z.ZodNumber>;
108
+ }, z.core.$strip>>;
109
+ }, z.core.$strip>], null>;
110
+ export declare const MediaSurfaceStateSchema: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
111
+ keyframes: z.ZodTuple<[z.ZodTuple<[z.ZodNumber, z.ZodObject<{
112
+ set: z.ZodOptional<z.ZodObject<{
113
+ opacity: z.ZodOptional<z.ZodNumber>;
114
+ }, z.core.$strip>>;
115
+ lerp: z.ZodOptional<z.ZodObject<{
116
+ opacity: z.ZodOptional<z.ZodNumber>;
117
+ }, z.core.$strip>>;
118
+ }, z.core.$strip>], null>], z.ZodUnion<readonly [z.ZodTuple<[z.ZodNumber, z.ZodObject<{
119
+ set: z.ZodOptional<z.ZodObject<{
120
+ opacity: z.ZodOptional<z.ZodNumber>;
121
+ }, z.core.$strip>>;
122
+ }, z.core.$strip>], null>, z.ZodTuple<[z.ZodNumber, z.ZodNull], null>]>>;
123
+ type: z.ZodLiteral<"image">;
124
+ file: z.ZodString;
125
+ fit: z.ZodUnion<readonly [z.ZodLiteral<"contain">, z.ZodLiteral<"cover">, z.ZodLiteral<"none">]>;
126
+ }, z.core.$strip>, z.ZodObject<{
127
+ keyframes: z.ZodTuple<[z.ZodTuple<[z.ZodNumber, z.ZodObject<{
128
+ set: z.ZodOptional<z.ZodObject<{
129
+ volume: z.ZodOptional<z.ZodNumber>;
130
+ t: z.ZodOptional<z.ZodNumber>;
131
+ rate: z.ZodOptional<z.ZodNumber>;
132
+ }, z.core.$strip>>;
133
+ lerp: z.ZodOptional<z.ZodObject<{
134
+ volume: z.ZodOptional<z.ZodNumber>;
135
+ }, z.core.$strip>>;
136
+ }, z.core.$strip>], null>], z.ZodUnion<readonly [z.ZodTuple<[z.ZodNumber, z.ZodObject<{
137
+ set: z.ZodOptional<z.ZodObject<{
138
+ volume: z.ZodOptional<z.ZodNumber>;
139
+ t: z.ZodOptional<z.ZodNumber>;
140
+ rate: z.ZodOptional<z.ZodNumber>;
141
+ }, z.core.$strip>>;
142
+ }, z.core.$strip>], null>, z.ZodTuple<[z.ZodNumber, z.ZodNull], null>]>>;
143
+ type: z.ZodLiteral<"audio">;
144
+ file: z.ZodString;
145
+ audioOutput: z.ZodString;
146
+ }, z.core.$strip>, z.ZodObject<{
147
+ keyframes: z.ZodTuple<[z.ZodTuple<[z.ZodNumber, z.ZodObject<{
148
+ set: z.ZodOptional<z.ZodObject<{
149
+ opacity: z.ZodOptional<z.ZodNumber>;
150
+ volume: z.ZodOptional<z.ZodNumber>;
151
+ t: z.ZodOptional<z.ZodNumber>;
152
+ rate: z.ZodOptional<z.ZodNumber>;
153
+ }, z.core.$strip>>;
154
+ lerp: z.ZodOptional<z.ZodObject<{
155
+ opacity: z.ZodOptional<z.ZodNumber>;
156
+ volume: z.ZodOptional<z.ZodNumber>;
157
+ }, z.core.$strip>>;
158
+ }, z.core.$strip>], null>], z.ZodUnion<readonly [z.ZodTuple<[z.ZodNumber, z.ZodObject<{
159
+ set: z.ZodOptional<z.ZodObject<{
160
+ opacity: z.ZodOptional<z.ZodNumber>;
161
+ volume: z.ZodOptional<z.ZodNumber>;
162
+ t: z.ZodOptional<z.ZodNumber>;
163
+ rate: z.ZodOptional<z.ZodNumber>;
164
+ }, z.core.$strip>>;
165
+ }, z.core.$strip>], null>, z.ZodTuple<[z.ZodNumber, z.ZodNull], null>]>>;
166
+ type: z.ZodLiteral<"video">;
167
+ file: z.ZodString;
168
+ audioOutput: z.ZodString;
169
+ fit: z.ZodUnion<readonly [z.ZodLiteral<"contain">, z.ZodLiteral<"cover">, z.ZodLiteral<"none">]>;
170
+ }, z.core.$strip>]>>;
171
+ export type ImageOptions = VisualProperties;
172
+ export type AudioOptions = TemporalProperties & AudialProperties;
173
+ export type VideoOptions = TemporalProperties & VisualProperties & AudialProperties;
174
+ export type ImageState = {
175
+ type: 'image';
176
+ file: string;
177
+ fit: 'cover' | 'contain' | 'none';
178
+ keyframes: [InitialImageKeyframe, ...Array<ImageKeyframe | NullKeyframe>];
179
+ };
180
+ export type AudioState = {
181
+ type: 'audio';
182
+ file: string;
183
+ audioOutput: string;
184
+ keyframes: [InitialAudioKeyframe, ...Array<AudioKeyframe | NullKeyframe>];
185
+ };
186
+ export type VideoState = {
187
+ type: 'video';
188
+ file: string;
189
+ fit: 'cover' | 'contain' | 'none';
190
+ audioOutput: string;
191
+ keyframes: [InitialVideoKeyframe, ...Array<VideoKeyframe | NullKeyframe>];
192
+ };
193
+ export type MediaClipState = ImageState | AudioState | VideoState;
194
+ export type MediaSurfaceState = Record<string, MediaClipState>;
195
+ export type UnionsEqual<A, B> = Exclude<A, B> extends never ? (Exclude<B, A> extends never ? true : false) : false;
196
+ export declare const defaultImageOptions: ImageOptions;
197
+ export declare const defaultAudioOptions: AudioOptions;
198
+ export declare const defaultVideoOptions: VideoOptions;
199
+ export {};
@@ -0,0 +1,153 @@
1
+ import { z } from 'zod';
2
+ const TemporalProperties = z.object({
3
+ t: z.number().gte(0),
4
+ rate: z.number().gte(0),
5
+ });
6
+ const VisualProperties = z.object({
7
+ opacity: z.number().gte(0).lte(1),
8
+ });
9
+ const AudialProperties = z.object({
10
+ volume: z.number().gte(0).lte(1),
11
+ });
12
+ const ImageMetadata = z.object({
13
+ type: z.literal('image'),
14
+ file: z.string(),
15
+ fit: z.union([z.literal('contain'), z.literal('cover'), z.literal('none')]),
16
+ });
17
+ const AudioMetadata = z.object({
18
+ type: z.literal('audio'),
19
+ file: z.string(),
20
+ audioOutput: z.string(),
21
+ });
22
+ const VideoMetadata = z.object({
23
+ type: z.literal('video'),
24
+ file: z.string(),
25
+ audioOutput: z.string(),
26
+ fit: z.union([z.literal('contain'), z.literal('cover'), z.literal('none')]),
27
+ });
28
+ const NullKeyframe = z.tuple([z.number(), z.null()]);
29
+ const InitialImageKeyframe = z.tuple([
30
+ z.number(),
31
+ z
32
+ .object({
33
+ set: z
34
+ .object({
35
+ ...VisualProperties.shape,
36
+ })
37
+ .partial(),
38
+ })
39
+ .partial(),
40
+ ]);
41
+ const ImageKeyframe = z.tuple([
42
+ z.number(),
43
+ z
44
+ .object({
45
+ set: z
46
+ .object({
47
+ ...VisualProperties.shape,
48
+ })
49
+ .partial(),
50
+ lerp: z
51
+ .object({
52
+ ...VisualProperties.shape,
53
+ })
54
+ .partial(),
55
+ })
56
+ .partial(),
57
+ ]);
58
+ const InitialAudioKeyframe = z.tuple([
59
+ z.number(),
60
+ z
61
+ .object({
62
+ set: z
63
+ .object({
64
+ ...TemporalProperties.shape,
65
+ ...AudialProperties.shape,
66
+ })
67
+ .partial(),
68
+ })
69
+ .partial(),
70
+ ]);
71
+ const AudioKeyframe = z.tuple([
72
+ z.number(),
73
+ z
74
+ .object({
75
+ set: z
76
+ .object({
77
+ ...TemporalProperties.shape,
78
+ ...AudialProperties.shape,
79
+ })
80
+ .partial(),
81
+ lerp: z
82
+ .object({
83
+ ...AudialProperties.shape,
84
+ })
85
+ .partial(),
86
+ })
87
+ .partial(),
88
+ ]);
89
+ const InitialVideoKeyframe = z.tuple([
90
+ z.number(),
91
+ z
92
+ .object({
93
+ set: z
94
+ .object({
95
+ ...TemporalProperties.shape,
96
+ ...AudialProperties.shape,
97
+ ...VisualProperties.shape,
98
+ })
99
+ .partial(),
100
+ })
101
+ .partial(),
102
+ ]);
103
+ const VideoKeyframe = z.tuple([
104
+ z.number(),
105
+ z
106
+ .object({
107
+ set: z
108
+ .object({
109
+ ...TemporalProperties.shape,
110
+ ...AudialProperties.shape,
111
+ ...VisualProperties.shape,
112
+ })
113
+ .partial(),
114
+ lerp: z
115
+ .object({
116
+ ...AudialProperties.shape,
117
+ ...VisualProperties.shape,
118
+ })
119
+ .partial(),
120
+ })
121
+ .partial(),
122
+ ]);
123
+ const ImageClip = z.object({
124
+ ...ImageMetadata.shape,
125
+ keyframes: z.tuple([ImageKeyframe], z.union([InitialImageKeyframe, NullKeyframe])),
126
+ });
127
+ const AudioClip = z.object({
128
+ ...AudioMetadata.shape,
129
+ keyframes: z.tuple([AudioKeyframe], z.union([InitialAudioKeyframe, NullKeyframe])),
130
+ });
131
+ const VideoClip = z.object({
132
+ ...VideoMetadata.shape,
133
+ keyframes: z.tuple([VideoKeyframe], z.union([InitialVideoKeyframe, NullKeyframe])),
134
+ });
135
+ export const MediaSurfaceStateSchema = z.record(z.string(), z.union([ImageClip, AudioClip, VideoClip]));
136
+ true;
137
+ true;
138
+ true;
139
+ true;
140
+ export const defaultImageOptions = {
141
+ opacity: 1,
142
+ };
143
+ export const defaultAudioOptions = {
144
+ t: 0,
145
+ rate: 1,
146
+ volume: 1,
147
+ };
148
+ export const defaultVideoOptions = {
149
+ t: 0,
150
+ rate: 1,
151
+ volume: 1,
152
+ opacity: 1,
153
+ };
@@ -0,0 +1,28 @@
1
+ import { MediaClipState, TemporalProperties } from '../types/MediaSchema';
2
+ export declare function getStateAtTime<State extends MediaClipState>(state: State, time: number): State['keyframes'][0][1]['set'] | undefined;
3
+ /**
4
+ * Goes through all keyframes to lerp between many different properties
5
+ * Note: This has no specific logic regarding types of properties
6
+ * Do not use this for setting time / rate.
7
+ * They behave differently in the schema. ( @see getTemporalPropertiesAtTime )
8
+ *
9
+ * @param keyframes keyframes from a given MediaClipState
10
+ * @param time the time (on or between keyframes) to calculate
11
+ * @returns a grouped object of all properties
12
+ */
13
+ export declare function getPropertiesAtTime<P extends Record<string, unknown>>(keyframes: [number, {
14
+ set?: Partial<P>;
15
+ lerp?: Partial<P>;
16
+ }][], time: number): P;
17
+ /**
18
+ * Keep track of time whilst going through the keyframes at respective rate.
19
+ * Temporal properties cannot be interpolated in the media schema.
20
+ *
21
+ * @param keyframes temporal keyframes from an AudioClipState or VideoClipState
22
+ * @param time the time (on or between keyframes) to calculate
23
+ * @returns the temporal properties of the media at the given time
24
+ */
25
+ export declare function getTemporalPropertiesAtTime<P extends TemporalProperties>(keyframes: [number, {
26
+ set?: Partial<P>;
27
+ lerp?: Partial<P>;
28
+ }][], time: number): TemporalProperties | undefined;
@@ -0,0 +1,141 @@
1
+ import { defaultAudioOptions, defaultImageOptions, defaultVideoOptions } from '../types/MediaSchema';
2
+ export function getStateAtTime(state, time) {
3
+ switch (state.type) {
4
+ case 'image': {
5
+ const firstTimestamp = state.keyframes[0][0];
6
+ if (firstTimestamp > time) {
7
+ return undefined;
8
+ }
9
+ const nonNullKeyframes = state.keyframes.filter((k) => k[1] !== null);
10
+ const properties = getPropertiesAtTime(nonNullKeyframes, time);
11
+ return { ...defaultImageOptions, ...properties };
12
+ }
13
+ case 'audio': {
14
+ const nonNullKeyframes = state.keyframes.filter((k) => k[1] !== null);
15
+ const temporalProperties = getTemporalPropertiesAtTime(nonNullKeyframes, time);
16
+ if (!temporalProperties) {
17
+ return undefined;
18
+ }
19
+ const properties = getPropertiesAtTime(nonNullKeyframes, time);
20
+ return { ...defaultAudioOptions, ...properties, ...temporalProperties };
21
+ }
22
+ case 'video': {
23
+ const nonNullKeyframes = state.keyframes.filter((k) => k[1] !== null);
24
+ const temporalProperties = getTemporalPropertiesAtTime(nonNullKeyframes, time);
25
+ if (!temporalProperties) {
26
+ return undefined;
27
+ }
28
+ const properties = getPropertiesAtTime(nonNullKeyframes, time);
29
+ return { ...defaultVideoOptions, ...properties, ...temporalProperties };
30
+ }
31
+ }
32
+ }
33
+ /**
34
+ * Goes through all keyframes to lerp between many different properties
35
+ * Note: This has no specific logic regarding types of properties
36
+ * Do not use this for setting time / rate.
37
+ * They behave differently in the schema. ( @see getTemporalPropertiesAtTime )
38
+ *
39
+ * @param keyframes keyframes from a given MediaClipState
40
+ * @param time the time (on or between keyframes) to calculate
41
+ * @returns a grouped object of all properties
42
+ */
43
+ export function getPropertiesAtTime(keyframes, time) {
44
+ const propertyKeyframes = {};
45
+ for (const [timestamp, properties] of keyframes) {
46
+ if (timestamp <= time) {
47
+ // If lerp and set are both present, we assume we lerp up until the timestamp,
48
+ // then set to a new value
49
+ Object.entries(properties.lerp ?? {}).forEach(([property, value]) => {
50
+ propertyKeyframes[property] ?? (propertyKeyframes[property] = {});
51
+ propertyKeyframes[property].before = [timestamp, value];
52
+ });
53
+ Object.entries(properties.set ?? {}).forEach(([property, value]) => {
54
+ propertyKeyframes[property] ?? (propertyKeyframes[property] = {});
55
+ propertyKeyframes[property].before = [timestamp, value];
56
+ });
57
+ }
58
+ else {
59
+ // We're trying to find the closest timestamp afterwards for lerping
60
+ // So only set if not yet set
61
+ Object.entries(properties.lerp ?? {}).forEach(([property, value]) => {
62
+ propertyKeyframes[property] ?? (propertyKeyframes[property] = {});
63
+ if (propertyKeyframes[property].after === undefined) {
64
+ propertyKeyframes[property].after = [timestamp, value];
65
+ }
66
+ });
67
+ }
68
+ }
69
+ const properties = {};
70
+ Object.entries(propertyKeyframes).forEach(([property, { before, after }]) => {
71
+ // There is no lerping, and it has been set before
72
+ // It's a constant!
73
+ if (after === undefined && before) {
74
+ properties[property] = before[1];
75
+ return;
76
+ }
77
+ // Multiple timestamps on the same, we return after
78
+ if (before && after && before[0] === after[0]) {
79
+ properties[property] = after[1];
80
+ return;
81
+ }
82
+ // Property has been set, and there's a lerp timestamp afterwards
83
+ // We've got numbers, so lets try to linearly inerpolate
84
+ if (before && typeof before[1] === 'number' && after && typeof after[1] === 'number') {
85
+ properties[property] = (before[1] + ((time - before[0]) * (after[1] - before[1])) / (after[0] - before[0]));
86
+ return;
87
+ }
88
+ });
89
+ return properties;
90
+ }
91
+ /**
92
+ * Keep track of time whilst going through the keyframes at respective rate.
93
+ * Temporal properties cannot be interpolated in the media schema.
94
+ *
95
+ * @param keyframes temporal keyframes from an AudioClipState or VideoClipState
96
+ * @param time the time (on or between keyframes) to calculate
97
+ * @returns the temporal properties of the media at the given time
98
+ */
99
+ export function getTemporalPropertiesAtTime(keyframes, time) {
100
+ // Not defined if the media starts in the future
101
+ const firstKeyframe = keyframes[0];
102
+ if (!firstKeyframe || firstKeyframe[0] > time) {
103
+ return undefined;
104
+ }
105
+ let timeAtLastKeyframe = 0;
106
+ let { t: mediaTimeAtLastKeyframe, rate: mediaRateAtLastKeyframe } = defaultAudioOptions;
107
+ for (const [timestamp, properties] of keyframes) {
108
+ // Only calculate up to the keyframe on or before
109
+ if (timestamp > time)
110
+ break;
111
+ const { set } = properties;
112
+ if (!set)
113
+ continue;
114
+ const { t, rate } = set;
115
+ // time is set - no calculations needed
116
+ if (t !== undefined) {
117
+ timeAtLastKeyframe = timestamp;
118
+ mediaTimeAtLastKeyframe = t;
119
+ if (rate !== undefined) {
120
+ mediaRateAtLastKeyframe = rate;
121
+ }
122
+ continue;
123
+ }
124
+ // rate is set on it's own, calculate how much time has passed
125
+ if (rate !== undefined) {
126
+ const duration = timestamp - timeAtLastKeyframe;
127
+ const mediaDuration = duration * mediaRateAtLastKeyframe;
128
+ timeAtLastKeyframe = timestamp;
129
+ mediaTimeAtLastKeyframe += mediaDuration;
130
+ mediaRateAtLastKeyframe = rate;
131
+ }
132
+ }
133
+ // Calculate time after last keyframe
134
+ const finalDuration = time - timeAtLastKeyframe;
135
+ const finalMediaDuration = finalDuration * mediaRateAtLastKeyframe;
136
+ const finalMediaTime = mediaTimeAtLastKeyframe + finalMediaDuration;
137
+ return {
138
+ rate: mediaRateAtLastKeyframe,
139
+ t: finalMediaTime,
140
+ };
141
+ }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Connect to COGS to build a custom Media Master",
4
4
  "author": "Clockwork Dog <info@clockwork.dog>",
5
5
  "homepage": "https://github.com/clockwork-dog/cogs-sdk/tree/main/packages/javascript",
6
- "version": "2.11.2",
6
+ "version": "3.0.0-alpha.1",
7
7
  "keywords": [],
8
8
  "license": "MIT",
9
9
  "repository": {
@@ -30,12 +30,14 @@
30
30
  "build:browser": "vite build",
31
31
  "watch-build": "tsc -w",
32
32
  "build-docs": "typedoc --out ../../docs/javascript --name @clockworkdog/cogs-client src/index.ts",
33
- "release": "yarn npm publish --access public"
33
+ "release": "yarn npm publish --access public",
34
+ "prerelease": "yarn npm publish --access public --tag=next"
34
35
  },
35
36
  "dependencies": {
36
- "@clockworkdog/timesync": "^2.11.2",
37
+ "@clockworkdog/timesync": "^3.0.0-alpha.1",
37
38
  "howler": "clockwork-dog/howler.js#fix-looping-clips",
38
- "reconnecting-websocket": "^4.4.0"
39
+ "reconnecting-websocket": "^4.4.0",
40
+ "zod": "^4.1.13"
39
41
  },
40
42
  "devDependencies": {
41
43
  "@eslint/js": "^9.17.0",
@@ -52,5 +54,6 @@
52
54
  "typescript-eslint": "^8.18.1",
53
55
  "vite": "^7.1.12",
54
56
  "vitest": "^4.0.6"
55
- }
57
+ },
58
+ "stableVersion": "0.0.0"
56
59
  }