@flighthq/video 0.1.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,4 @@
1
+ export * from './videoFormat';
2
+ export * from './videoResource';
3
+ export * from './videoResourceFrom';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './videoFormat';
2
+ export * from './videoResource';
3
+ export * from './videoResourceFrom';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function inferVideoType(url: string): string | null;
2
+ //# sourceMappingURL=videoFormat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoFormat.d.ts","sourceRoot":"","sources":["../src/videoFormat.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgBzD"}
@@ -0,0 +1,18 @@
1
+ export function inferVideoType(url) {
2
+ const ext = url.split('?')[0].split('.').pop()?.toLowerCase();
3
+ switch (ext) {
4
+ case 'mp4':
5
+ case 'm4v':
6
+ return 'video/mp4';
7
+ case 'webm':
8
+ return 'video/webm';
9
+ case 'ogv':
10
+ case 'ogg':
11
+ return 'video/ogg';
12
+ case 'mov':
13
+ return 'video/quicktime';
14
+ default:
15
+ return null;
16
+ }
17
+ }
18
+ //# sourceMappingURL=videoFormat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoFormat.js","sourceRoot":"","sources":["../src/videoFormat.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,CAAC;IAC9D,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,KAAK,CAAC;QACX,KAAK,KAAK;YACR,OAAO,WAAW,CAAC;QACrB,KAAK,MAAM;YACT,OAAO,YAAY,CAAC;QACtB,KAAK,KAAK,CAAC;QACX,KAAK,KAAK;YACR,OAAO,WAAW,CAAC;QACrB,KAAK,KAAK;YACR,OAAO,iBAAiB,CAAC;QAC3B;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { VideoResource } from '@flighthq/types';
2
+ export declare function createVideoResource(element?: HTMLVideoElement): VideoResource;
3
+ //# sourceMappingURL=videoResource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoResource.d.ts","sourceRoot":"","sources":["../src/videoResource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,aAAa,CAE7E"}
@@ -0,0 +1,4 @@
1
+ export function createVideoResource(element) {
2
+ return { element: element ?? null };
3
+ }
4
+ //# sourceMappingURL=videoResource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoResource.js","sourceRoot":"","sources":["../src/videoResource.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,mBAAmB,CAAC,OAA0B;IAC5D,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,CAAC;AACtC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { VideoResource, VideoResourceUrl } from '@flighthq/types';
2
+ export declare function loadVideoResourceFromUrl(url: string, signal?: AbortSignal): Promise<VideoResource>;
3
+ export declare function loadVideoResourceFromUrls(sources: VideoResourceUrl[], signal?: AbortSignal): Promise<VideoResource>;
4
+ //# sourceMappingURL=videoResourceFrom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoResourceFrom.d.ts","sourceRoot":"","sources":["../src/videoResourceFrom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAKvE,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAkClG;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAKnH"}
@@ -0,0 +1,42 @@
1
+ import { inferVideoType } from './videoFormat';
2
+ import { createVideoResource } from './videoResource';
3
+ export function loadVideoResourceFromUrl(url, signal) {
4
+ if (signal?.aborted)
5
+ return Promise.reject(signal.reason);
6
+ return new Promise((resolve, reject) => {
7
+ const element = document.createElement('video');
8
+ element.preload = 'auto';
9
+ const onCanPlay = () => {
10
+ cleanup();
11
+ resolve(createVideoResource(element));
12
+ };
13
+ const onError = () => {
14
+ cleanup();
15
+ reject(new Error(`Failed to load video: ${url}`));
16
+ };
17
+ const onAbort = () => {
18
+ cleanup();
19
+ element.src = '';
20
+ reject(signal.reason);
21
+ };
22
+ const cleanup = () => {
23
+ element.removeEventListener('canplay', onCanPlay);
24
+ element.removeEventListener('error', onError);
25
+ if (signal !== undefined)
26
+ signal.removeEventListener('abort', onAbort);
27
+ };
28
+ element.addEventListener('canplay', onCanPlay, { once: true });
29
+ element.addEventListener('error', onError, { once: true });
30
+ if (signal !== undefined)
31
+ signal.addEventListener('abort', onAbort, { once: true });
32
+ element.src = url;
33
+ });
34
+ }
35
+ export function loadVideoResourceFromUrls(sources, signal) {
36
+ const probe = document.createElement('video');
37
+ const selected = sources.find(({ url, type }) => probe.canPlayType(type ?? inferVideoType(url) ?? '') !== '');
38
+ if (selected === undefined)
39
+ return Promise.resolve(createVideoResource());
40
+ return loadVideoResourceFromUrl(selected.url, signal);
41
+ }
42
+ //# sourceMappingURL=videoResourceFrom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoResourceFrom.js","sourceRoot":"","sources":["../src/videoResourceFrom.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,UAAU,wBAAwB,CAAC,GAAW,EAAE,MAAoB;IACxE,IAAI,MAAM,EAAE,OAAO;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAChD,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC;QAEzB,MAAM,SAAS,GAAG,GAAS,EAAE;YAC3B,OAAO,EAAE,CAAC;YACV,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;QACxC,CAAC,CAAC;QAEF,MAAM,OAAO,GAAG,GAAS,EAAE;YACzB,OAAO,EAAE,CAAC;YACV,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,GAAG,EAAE,CAAC,CAAC,CAAC;QACpD,CAAC,CAAC;QAEF,MAAM,OAAO,GAAG,GAAS,EAAE;YACzB,OAAO,EAAE,CAAC;YACV,OAAO,CAAC,GAAG,GAAG,EAAE,CAAC;YACjB,MAAM,CAAC,MAAO,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC,CAAC;QAEF,MAAM,OAAO,GAAG,GAAS,EAAE;YACzB,OAAO,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YAClD,OAAO,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,IAAI,MAAM,KAAK,SAAS;gBAAE,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACzE,CAAC,CAAC;QAEF,OAAO,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,OAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAEpF,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC;IACpB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAA2B,EAAE,MAAoB;IACzF,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;IAC9G,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;IAC1E,OAAO,wBAAwB,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AACxD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@flighthq/video",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "src/**/*.test.ts",
16
+ "!dist/**/*.test.js",
17
+ "!dist/**/*.test.d.ts",
18
+ "!dist/**/*.test.js.map",
19
+ "!dist/**/*.test.d.ts.map"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -b",
23
+ "clean": "tsc -b --clean",
24
+ "test": "vitest run --config vitest.config.ts",
25
+ "test:watch": "vitest --watch --config vitest.config.ts",
26
+ "prepack": "npm run clean && npm run clean:dist && npm run build",
27
+ "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
28
+ },
29
+ "dependencies": {
30
+ "@flighthq/types": "0.1.0"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "^5.3.0"
34
+ },
35
+ "description": "Video resources and URL constructors",
36
+ "sideEffects": false
37
+ }
@@ -0,0 +1,39 @@
1
+ import { inferVideoType } from './videoFormat';
2
+
3
+ describe('inferVideoType', () => {
4
+ it('returns "video/mp4" for .mp4 files', () => {
5
+ expect(inferVideoType('clip.mp4')).toBe('video/mp4');
6
+ });
7
+
8
+ it('returns "video/mp4" for .m4v files', () => {
9
+ expect(inferVideoType('clip.m4v')).toBe('video/mp4');
10
+ });
11
+
12
+ it('returns "video/webm" for .webm files', () => {
13
+ expect(inferVideoType('clip.webm')).toBe('video/webm');
14
+ });
15
+
16
+ it('returns "video/ogg" for .ogv files', () => {
17
+ expect(inferVideoType('clip.ogv')).toBe('video/ogg');
18
+ });
19
+
20
+ it('returns "video/ogg" for .ogg files', () => {
21
+ expect(inferVideoType('clip.ogg')).toBe('video/ogg');
22
+ });
23
+
24
+ it('returns "video/quicktime" for .mov files', () => {
25
+ expect(inferVideoType('clip.mov')).toBe('video/quicktime');
26
+ });
27
+
28
+ it('returns null for unrecognized extensions', () => {
29
+ expect(inferVideoType('clip.avi')).toBeNull();
30
+ });
31
+
32
+ it('strips query parameters before matching', () => {
33
+ expect(inferVideoType('clip.mp4?t=0')).toBe('video/mp4');
34
+ });
35
+
36
+ it('returns null for a URL with no extension', () => {
37
+ expect(inferVideoType('video')).toBeNull();
38
+ });
39
+ });
@@ -0,0 +1,14 @@
1
+ import { createVideoResource } from './videoResource';
2
+
3
+ describe('createVideoResource', () => {
4
+ it('returns a resource with null element when called with no arguments', () => {
5
+ const resource = createVideoResource();
6
+ expect(resource.element).toBeNull();
7
+ });
8
+
9
+ it('stores the provided video element', () => {
10
+ const element = document.createElement('video');
11
+ const resource = createVideoResource(element);
12
+ expect(resource.element).toBe(element);
13
+ });
14
+ });
@@ -0,0 +1,35 @@
1
+ import { loadVideoResourceFromUrl, loadVideoResourceFromUrls } from './videoResourceFrom';
2
+
3
+ describe('loadVideoResourceFromUrl', () => {
4
+ it('returns a Promise', () => {
5
+ const result = loadVideoResourceFromUrl('test.mp4');
6
+ result.catch(() => {});
7
+ expect(result).toBeInstanceOf(Promise);
8
+ });
9
+
10
+ it('rejects immediately when the signal is already aborted', async () => {
11
+ const controller = new AbortController();
12
+ const reason = new Error('pre-aborted');
13
+ controller.abort(reason);
14
+ await expect(loadVideoResourceFromUrl('test.mp4', controller.signal)).rejects.toThrow('pre-aborted');
15
+ });
16
+
17
+ it('rejects when the signal is aborted after the call', async () => {
18
+ const controller = new AbortController();
19
+ const promise = loadVideoResourceFromUrl('test.mp4', controller.signal);
20
+ controller.abort(new Error('cancelled'));
21
+ await expect(promise).rejects.toThrow('cancelled');
22
+ });
23
+ });
24
+
25
+ describe('loadVideoResourceFromUrls', () => {
26
+ it('resolves immediately with a null-element resource when sources is empty', async () => {
27
+ const resource = await loadVideoResourceFromUrls([]);
28
+ expect(resource.element).toBeNull();
29
+ });
30
+
31
+ it('resolves to a null-element resource when no source passes canPlayType in jsdom', async () => {
32
+ const resource = await loadVideoResourceFromUrls([{ url: 'test.mp4' }]);
33
+ expect(resource.element).toBeNull();
34
+ });
35
+ });