@clockworkdog/cogs-client 3.1.3 → 3.2.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.
@@ -0,0 +1,6 @@
1
+ export declare const COGS_SERVER_PORT = 12095;
2
+ /**
3
+ * Get the URL of an asset hosted by the COGS server.
4
+ */
5
+ export declare function assetUrl(file: string): string;
6
+ export declare function preloadUrl(url: string): Promise<string>;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.COGS_SERVER_PORT = void 0;
4
+ exports.assetUrl = assetUrl;
5
+ exports.preloadUrl = preloadUrl;
6
+ exports.COGS_SERVER_PORT = 12095;
7
+ /**
8
+ * Get the URL of an asset hosted by the COGS server.
9
+ */
10
+ function assetUrl(file) {
11
+ const location = typeof window !== 'undefined' ? window.location : undefined;
12
+ const path = `/assets/${encodeURIComponent(file)}`;
13
+ return `${location === null || location === void 0 ? void 0 : location.protocol}//${location === null || location === void 0 ? void 0 : location.hostname}:${exports.COGS_SERVER_PORT}${path}`;
14
+ }
15
+ async function preloadUrl(url) {
16
+ const response = await fetch(url);
17
+ // We used arrayBuffer()` instead of `blob()` because the latter seems to fail on Pis when preloading some files
18
+ return URL.createObjectURL(new Blob([await response.arrayBuffer()]));
19
+ }
@@ -0,0 +1,5 @@
1
+ export type MediaStatus = 'playing' | 'paused' | 'stopped';
2
+ export default interface AllMediaClipStatesMessage {
3
+ mediaType: 'audio' | 'video';
4
+ files: [string, MediaStatus][];
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,39 @@
1
+ export type ActiveAudioClipState = {
2
+ type: 'paused';
3
+ } | {
4
+ type: 'pause_requested';
5
+ fade: number | undefined;
6
+ } | {
7
+ type: 'pausing';
8
+ } | {
9
+ type: 'playing';
10
+ } | {
11
+ type: 'play_requested';
12
+ } | {
13
+ type: 'stopping';
14
+ } | {
15
+ type: 'stop_requested';
16
+ fade: number | undefined;
17
+ };
18
+ export interface AudioClip {
19
+ config: {
20
+ preload: boolean;
21
+ ephemeral: boolean;
22
+ };
23
+ activeClips: {
24
+ [soundId: number]: ActiveClip;
25
+ };
26
+ }
27
+ export interface ActiveClip {
28
+ state: ActiveAudioClipState;
29
+ loop: boolean;
30
+ volume: number;
31
+ playId: string;
32
+ }
33
+ export interface AudioState {
34
+ isPlaying: boolean;
35
+ globalVolume: number;
36
+ clips: {
37
+ [path: string]: AudioClip;
38
+ };
39
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ export type MediaStatus = 'playing' | 'paused' | 'stopped';
2
+ export default interface MediaClipStateMessage {
3
+ playId: string;
4
+ mediaType: 'audio' | 'video';
5
+ file: string;
6
+ status: MediaStatus;
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,26 @@
1
+ import { MediaObjectFit } from '..';
2
+ export declare enum ActiveVideoClipState {
3
+ Paused = "paused",
4
+ Playing = "playing"
5
+ }
6
+ export interface VideoClip {
7
+ config: {
8
+ preload: 'auto' | 'metadata' | 'none';
9
+ ephemeral: boolean;
10
+ fit: MediaObjectFit;
11
+ };
12
+ }
13
+ export interface ActiveClip {
14
+ path: string;
15
+ state: ActiveVideoClipState;
16
+ loop: boolean;
17
+ volume: number;
18
+ }
19
+ export interface VideoState {
20
+ isPlaying: boolean;
21
+ globalVolume: number;
22
+ clips: {
23
+ [path: string]: VideoClip;
24
+ };
25
+ activeClip?: ActiveClip;
26
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ActiveVideoClipState = void 0;
4
+ var ActiveVideoClipState;
5
+ (function (ActiveVideoClipState) {
6
+ ActiveVideoClipState["Paused"] = "paused";
7
+ ActiveVideoClipState["Playing"] = "playing";
8
+ })(ActiveVideoClipState || (exports.ActiveVideoClipState = ActiveVideoClipState = {}));
@@ -11,5 +11,6 @@ export declare class NetworkHostPattern {
11
11
  constructor(pattern: string);
12
12
  /** Checks whether `host` and `port` are matched by this pattern. */
13
13
  validate(host: string, port: number): boolean;
14
- private matchesHost;
14
+ /** Checks whether `host` is matched by the hostname part of this pattern, ignoring the port. */
15
+ validateHostname(host: string): boolean;
15
16
  }
@@ -23,9 +23,10 @@ export class NetworkHostPattern {
23
23
  }
24
24
  /** Checks whether `host` and `port` are matched by this pattern. */
25
25
  validate(host, port) {
26
- return this.matchesHost(host) && (this.port === '*' || Number(this.port) === port);
26
+ return this.validateHostname(host) && (this.port === '*' || Number(this.port) === port);
27
27
  }
28
- matchesHost(host) {
28
+ /** Checks whether `host` is matched by the hostname part of this pattern, ignoring the port. */
29
+ validateHostname(host) {
29
30
  // Hostnames are case-insensitive; IP literals are unaffected by lowercasing.
30
31
  const patternHost = this.host.toLowerCase();
31
32
  host = host.toLowerCase();
@@ -130,7 +130,7 @@ function createManifestSchema(objectSchemaFactory) {
130
130
  access: z
131
131
  .array(objectSchemaFactory({
132
132
  hosts: z.array(z.string().refine(validateNetworkHostPattern, { error: 'Invalid network host pattern' })).min(1),
133
- caCertificate: z.string().optional(),
133
+ caCertificates: z.array(z.string().min(1)).optional(),
134
134
  }))
135
135
  .optional(),
136
136
  })
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Patch `Date.now()` and `new Date()` given the current time is @param now
3
+ */
4
+ export declare function setDate(now: number): void;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ // Adapted from https://stackoverflow.com/a/58325977/244640
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.setDate = setDate;
5
+ const OriginalDateConstructor = globalThis.Date;
6
+ /**
7
+ * Patch `Date.now()` and `new Date()` given the current time is @param now
8
+ */
9
+ function setDate(now) {
10
+ const nowDelta = now - OriginalDateConstructor.now();
11
+ function Date(...args) {
12
+ if (args.length === 0) {
13
+ return new OriginalDateConstructor(Date.now()); // Date.now() is implemented below
14
+ }
15
+ // Specific date constructor
16
+ return new OriginalDateConstructor(...args);
17
+ }
18
+ // copy all properties from the original date, this includes the prototype
19
+ const propertyDescriptors = Object.getOwnPropertyDescriptors(OriginalDateConstructor);
20
+ Object.defineProperties(Date, propertyDescriptors);
21
+ // override Date.now to return the adjusted time
22
+ Date.now = function () {
23
+ return OriginalDateConstructor.now() + nowDelta;
24
+ };
25
+ globalThis.Date = Date;
26
+ }
@@ -0,0 +1,28 @@
1
+ export interface TimeSyncRequestData {
2
+ timesync: {
3
+ id: number;
4
+ };
5
+ }
6
+ export interface TimeSyncResponseData {
7
+ timesync: {
8
+ id: number;
9
+ now: number;
10
+ };
11
+ }
12
+ export interface TimeSyncClient {
13
+ receive(data: TimeSyncResponseData): void;
14
+ destroy(): void;
15
+ }
16
+ export interface TimeSyncServer {
17
+ receive(data: TimeSyncRequestData): void;
18
+ }
19
+ export declare function createTimeSyncClient({ interval, send, onChange, syncSampleSize, syncRequestTimeout, }: {
20
+ interval: number;
21
+ send: (data: TimeSyncRequestData) => void | Promise<void>;
22
+ onChange?: (now: number) => void;
23
+ syncSampleSize?: number;
24
+ syncRequestTimeout?: number;
25
+ }): TimeSyncClient;
26
+ export declare function createTimeSyncServer({ send }: {
27
+ send: (data: TimeSyncResponseData) => void | Promise<void>;
28
+ }): TimeSyncServer;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTimeSyncClient = createTimeSyncClient;
4
+ exports.createTimeSyncServer = createTimeSyncServer;
5
+ const setDate_1 = require("./setDate");
6
+ let id = 0;
7
+ function getId() {
8
+ return ++id;
9
+ }
10
+ const SYNC_SAMPLE_SIZE = 5;
11
+ const SYNC_REQUEST_TIMEOUT = 10000;
12
+ function createTimeSyncClient({ interval, send, onChange = setDate_1.setDate, syncSampleSize = SYNC_SAMPLE_SIZE, syncRequestTimeout = SYNC_REQUEST_TIMEOUT, }) {
13
+ const requests = {};
14
+ async function synchronize() {
15
+ const promises = [];
16
+ // Send a series of requests
17
+ for (let i = 0; i < syncSampleSize; i++) {
18
+ const promise = new Promise((resolve) => {
19
+ const id = getId();
20
+ const sentAt = performance.now();
21
+ send({ timesync: { id } });
22
+ const complete = (receivedAt, serverNow) => resolve({ sentAt, receivedAt, serverNow, clientNow: Date.now() });
23
+ requests[id] = { complete };
24
+ setTimeout(() => resolve(null), syncRequestTimeout);
25
+ });
26
+ promises.push(promise);
27
+ await promise;
28
+ }
29
+ // Perform calculation with results
30
+ const results = await Promise.all(promises);
31
+ const deltas = results
32
+ .filter((result) => result !== null)
33
+ .map((result) => {
34
+ const { sentAt, receivedAt, serverNow, clientNow } = result;
35
+ const halfLatency = (receivedAt - sentAt) / 2;
36
+ return clientNow - serverNow + halfLatency;
37
+ });
38
+ const averageDelta = deltas.reduce((d1, d2) => d1 + d2, 0) / deltas.length;
39
+ if (!isNaN(averageDelta)) {
40
+ onChange(Date.now() + averageDelta);
41
+ }
42
+ }
43
+ const receive = (data) => {
44
+ const receivedAt = performance.now();
45
+ const req = requests[data.timesync.id];
46
+ if (!req)
47
+ return;
48
+ req.complete(receivedAt, data.timesync.now);
49
+ };
50
+ synchronize();
51
+ const loop = setInterval(synchronize, interval);
52
+ const destroy = () => {
53
+ clearInterval(loop);
54
+ };
55
+ return {
56
+ receive,
57
+ destroy,
58
+ };
59
+ }
60
+ function createTimeSyncServer({ send }) {
61
+ return {
62
+ receive(data) {
63
+ const { id } = data.timesync;
64
+ send({ timesync: { id, now: Date.now() } });
65
+ },
66
+ };
67
+ }
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": "3.1.3",
6
+ "version": "3.2.0",
7
7
  "keywords": [],
8
8
  "license": "MIT",
9
9
  "repository": {
@@ -37,7 +37,7 @@
37
37
  "cy:generate": "cypress run --e2e"
38
38
  },
39
39
  "dependencies": {
40
- "@clockworkdog/timesync": "^3.1.3",
40
+ "@clockworkdog/timesync": "^3.2.0",
41
41
  "ip-address": "^10.2.0",
42
42
  "reconnecting-websocket": "^4.4.0",
43
43
  "semver": "^7.8.5",