@design.estate/wcctools 5.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.
- package/.smartconfig.json +65 -0
- package/changelog.md +414 -0
- package/dist_bundle/bundle.js +92419 -0
- package/dist_bundle/bundle.js.map +1 -0
- package/dist_ts_web/00_commitinfo_data.d.ts +8 -0
- package/dist_ts_web/00_commitinfo_data.js +9 -0
- package/dist_ts_web/elements/wcc-contextmenu.d.ts +41 -0
- package/dist_ts_web/elements/wcc-contextmenu.js +329 -0
- package/dist_ts_web/elements/wcc-dashboard.d.ts +119 -0
- package/dist_ts_web/elements/wcc-dashboard.js +700 -0
- package/dist_ts_web/elements/wcc-frame.d.ts +22 -0
- package/dist_ts_web/elements/wcc-frame.js +256 -0
- package/dist_ts_web/elements/wcc-properties.d.ts +43 -0
- package/dist_ts_web/elements/wcc-properties.js +1147 -0
- package/dist_ts_web/elements/wcc-record-button.d.ts +12 -0
- package/dist_ts_web/elements/wcc-record-button.js +165 -0
- package/dist_ts_web/elements/wcc-recording-panel.d.ts +44 -0
- package/dist_ts_web/elements/wcc-recording-panel.js +1103 -0
- package/dist_ts_web/elements/wcc-sidebar.d.ts +91 -0
- package/dist_ts_web/elements/wcc-sidebar.js +1497 -0
- package/dist_ts_web/elements/wcctools.helpers.d.ts +46 -0
- package/dist_ts_web/elements/wcctools.helpers.js +106 -0
- package/dist_ts_web/index.d.ts +18 -0
- package/dist_ts_web/index.js +30 -0
- package/dist_ts_web/pages/index.d.ts +1 -0
- package/dist_ts_web/pages/index.js +2 -0
- package/dist_ts_web/services/recorder.service.d.ts +55 -0
- package/dist_ts_web/services/recorder.service.js +353 -0
- package/dist_ts_web/wcctools.interfaces.d.ts +29 -0
- package/dist_ts_web/wcctools.interfaces.js +2 -0
- package/dist_ts_web/wcctools.plugins.d.ts +2 -0
- package/dist_ts_web/wcctools.plugins.js +3 -0
- package/license.md +19 -0
- package/package.json +64 -0
- package/readme.md +568 -0
- package/ts_web/00_commitinfo_data.ts +8 -0
- package/ts_web/elements/wcc-contextmenu.ts +291 -0
- package/ts_web/elements/wcc-dashboard.ts +672 -0
- package/ts_web/elements/wcc-frame.ts +189 -0
- package/ts_web/elements/wcc-properties.ts +1089 -0
- package/ts_web/elements/wcc-record-button.ts +108 -0
- package/ts_web/elements/wcc-recording-panel.ts +1010 -0
- package/ts_web/elements/wcc-sidebar.ts +1470 -0
- package/ts_web/elements/wcctools.helpers.ts +153 -0
- package/ts_web/index.ts +38 -0
- package/ts_web/pages/index.ts +1 -0
- package/ts_web/readme.md +177 -0
- package/ts_web/services/recorder.service.ts +451 -0
- package/ts_web/tspublish.json +3 -0
- package/ts_web/types/dom-mediacapture-stub/index.d.ts +12 -0
- package/ts_web/types/dom-mediacapture-stub/package.json +6 -0
- package/ts_web/types/dom-webcodecs-stub/index.d.ts +2 -0
- package/ts_web/types/dom-webcodecs-stub/package.json +6 -0
- package/ts_web/wcctools.interfaces.ts +31 -0
- package/ts_web/wcctools.plugins.ts +5 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { TemplateResult } from 'lit';
|
|
2
|
+
export type TTemplateFactory = () => TemplateResult | Promise<TemplateResult>;
|
|
3
|
+
/** Find the nearest matching instance through light DOM and open shadow roots. */
|
|
4
|
+
export declare const findElementInstance: <T extends HTMLElement>(containerArg: Element | ShadowRoot, elementClassArg: new (...args: any[]) => T) => T | null;
|
|
5
|
+
export type TDemoDefinition = TTemplateFactory | TTemplateFactory[];
|
|
6
|
+
/**
|
|
7
|
+
* How a catalog export is treated in an elements section:
|
|
8
|
+
* - `demo`: a custom element class with a usable static `demo`; listed and navigable
|
|
9
|
+
* - `no-demo`: a registered custom element without a usable demo; listed under "Without demo"
|
|
10
|
+
* - `ignored`: anything else (helpers, constants, types at runtime, abstract base classes)
|
|
11
|
+
*/
|
|
12
|
+
export type TCatalogEntryKind = 'demo' | 'no-demo' | 'ignored';
|
|
13
|
+
/** The static surface wcctools reads from a catalog element class. */
|
|
14
|
+
export interface ICatalogElementConstructor extends CustomElementConstructor {
|
|
15
|
+
demo?: unknown;
|
|
16
|
+
demoGroups?: unknown;
|
|
17
|
+
}
|
|
18
|
+
/** A demo is one template factory or a non-empty array of template factories. */
|
|
19
|
+
export declare const isDemoDefinition: (demoArg: unknown) => demoArg is TDemoDefinition;
|
|
20
|
+
/** True for classes that extend HTMLElement (registered or not). */
|
|
21
|
+
export declare const isElementConstructor: (itemArg: unknown) => itemArg is ICatalogElementConstructor;
|
|
22
|
+
/** Classify one export of an elements section. */
|
|
23
|
+
export declare const classifyCatalogEntry: (itemArg: unknown) => TCatalogEntryKind;
|
|
24
|
+
/** The tag name an element class is registered under, if any. */
|
|
25
|
+
export declare const getElementTagName: (itemArg: unknown) => string | null;
|
|
26
|
+
/** The demo groups an element declares, normalized to an array. */
|
|
27
|
+
export declare const getElementDemoGroups: (itemArg: unknown) => string[];
|
|
28
|
+
export type TPropertyType = 'String' | 'Number' | 'Boolean' | 'Object' | 'Enum' | 'Array';
|
|
29
|
+
export interface IReactivePropertyDeclaration {
|
|
30
|
+
state?: boolean;
|
|
31
|
+
type?: unknown;
|
|
32
|
+
}
|
|
33
|
+
export declare const determinePropertyType: (propertyArg: IReactivePropertyDeclaration) => TPropertyType | null;
|
|
34
|
+
export declare const resolveTemplateFactory: (factoryArg: TTemplateFactory) => Promise<TemplateResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Get the number of demos for an element
|
|
37
|
+
*/
|
|
38
|
+
export declare const getDemoCount: (demo: TDemoDefinition) => number;
|
|
39
|
+
/**
|
|
40
|
+
* Get a specific demo by index (0-based internally, displayed as 1-based)
|
|
41
|
+
*/
|
|
42
|
+
export declare const getDemoAtIndex: (demo: TDemoDefinition, index: number) => TTemplateFactory | null;
|
|
43
|
+
/**
|
|
44
|
+
* Check if an element has multiple demos
|
|
45
|
+
*/
|
|
46
|
+
export declare const hasMultipleDemos: (demo: TDemoDefinition) => boolean;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/** Find the nearest matching instance through light DOM and open shadow roots. */
|
|
2
|
+
export const findElementInstance = (containerArg, elementClassArg) => {
|
|
3
|
+
const pending = Array.from(containerArg.children);
|
|
4
|
+
for (let index = 0; index < pending.length; index++) {
|
|
5
|
+
const element = pending[index];
|
|
6
|
+
if (element instanceof elementClassArg)
|
|
7
|
+
return element;
|
|
8
|
+
pending.push(...element.children);
|
|
9
|
+
if (element.shadowRoot)
|
|
10
|
+
pending.push(...element.shadowRoot.children);
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
};
|
|
14
|
+
/** A demo is one template factory or a non-empty array of template factories. */
|
|
15
|
+
export const isDemoDefinition = (demoArg) => {
|
|
16
|
+
if (typeof demoArg === 'function') {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return Array.isArray(demoArg)
|
|
20
|
+
&& demoArg.length > 0
|
|
21
|
+
&& demoArg.every((factoryArg) => typeof factoryArg === 'function');
|
|
22
|
+
};
|
|
23
|
+
/** True for classes that extend HTMLElement (registered or not). */
|
|
24
|
+
export const isElementConstructor = (itemArg) => {
|
|
25
|
+
return typeof HTMLElement !== 'undefined'
|
|
26
|
+
&& typeof itemArg === 'function'
|
|
27
|
+
&& itemArg.prototype instanceof HTMLElement;
|
|
28
|
+
};
|
|
29
|
+
/** Classify one export of an elements section. */
|
|
30
|
+
export const classifyCatalogEntry = (itemArg) => {
|
|
31
|
+
if (!isElementConstructor(itemArg)) {
|
|
32
|
+
return 'ignored';
|
|
33
|
+
}
|
|
34
|
+
if (isDemoDefinition(itemArg.demo)) {
|
|
35
|
+
return 'demo';
|
|
36
|
+
}
|
|
37
|
+
return customElements.getName(itemArg) ? 'no-demo' : 'ignored';
|
|
38
|
+
};
|
|
39
|
+
/** The tag name an element class is registered under, if any. */
|
|
40
|
+
export const getElementTagName = (itemArg) => {
|
|
41
|
+
return isElementConstructor(itemArg) ? customElements.getName(itemArg) : null;
|
|
42
|
+
};
|
|
43
|
+
/** The demo groups an element declares, normalized to an array. */
|
|
44
|
+
export const getElementDemoGroups = (itemArg) => {
|
|
45
|
+
if (!isElementConstructor(itemArg)) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
const groups = itemArg.demoGroups;
|
|
49
|
+
const list = Array.isArray(groups) ? groups : [groups];
|
|
50
|
+
return list.filter((groupArg) => typeof groupArg === 'string' && groupArg.length > 0);
|
|
51
|
+
};
|
|
52
|
+
const isEnumerationType = (typeArg) => {
|
|
53
|
+
if (!typeArg || typeof typeArg !== 'object' || Array.isArray(typeArg)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const values = Object.values(typeArg);
|
|
57
|
+
return values.length > 0 && values.every((valueArg) => typeof valueArg === 'string' || typeof valueArg === 'number');
|
|
58
|
+
};
|
|
59
|
+
export const determinePropertyType = (propertyArg) => {
|
|
60
|
+
if (!propertyArg || typeof propertyArg !== 'object' || propertyArg.state) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
switch (propertyArg.type) {
|
|
64
|
+
case String:
|
|
65
|
+
return 'String';
|
|
66
|
+
case Number:
|
|
67
|
+
return 'Number';
|
|
68
|
+
case Boolean:
|
|
69
|
+
return 'Boolean';
|
|
70
|
+
case Object:
|
|
71
|
+
return 'Object';
|
|
72
|
+
case Array:
|
|
73
|
+
return 'Array';
|
|
74
|
+
default:
|
|
75
|
+
return isEnumerationType(propertyArg.type) ? 'Enum' : null;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
export const resolveTemplateFactory = async (factoryArg) => {
|
|
79
|
+
return await Promise.resolve(factoryArg());
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Get the number of demos for an element
|
|
83
|
+
*/
|
|
84
|
+
export const getDemoCount = (demo) => {
|
|
85
|
+
if (Array.isArray(demo)) {
|
|
86
|
+
return demo.length;
|
|
87
|
+
}
|
|
88
|
+
return 1;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Get a specific demo by index (0-based internally, displayed as 1-based)
|
|
92
|
+
*/
|
|
93
|
+
export const getDemoAtIndex = (demo, index) => {
|
|
94
|
+
if (Array.isArray(demo)) {
|
|
95
|
+
return demo[index] ?? null;
|
|
96
|
+
}
|
|
97
|
+
// Single demo - only index 0 is valid
|
|
98
|
+
return index === 0 ? demo : null;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Check if an element has multiple demos
|
|
102
|
+
*/
|
|
103
|
+
export const hasMultipleDemos = (demo) => {
|
|
104
|
+
return Array.isArray(demo) && demo.length > 1;
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2NjdG9vbHMuaGVscGVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzX3dlYi9lbGVtZW50cy93Y2N0b29scy5oZWxwZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUlBLGtGQUFrRjtBQUNsRixNQUFNLENBQUMsTUFBTSxtQkFBbUIsR0FBRyxDQUNqQyxZQUFrQyxFQUNsQyxlQUEwQyxFQUNoQyxFQUFFO0lBQ1osTUFBTSxPQUFPLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDbEQsS0FBSyxJQUFJLEtBQUssR0FBRyxDQUFDLEVBQUUsS0FBSyxHQUFHLE9BQU8sQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FBQztRQUNwRCxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDL0IsSUFBSSxPQUFPLFlBQVksZUFBZTtZQUFFLE9BQU8sT0FBTyxDQUFDO1FBQ3ZELE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDbEMsSUFBSSxPQUFPLENBQUMsVUFBVTtZQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3ZFLENBQUM7SUFDRCxPQUFPLElBQUksQ0FBQztBQUNkLENBQUMsQ0FBQztBQW1CRixpRkFBaUY7QUFDakYsTUFBTSxDQUFDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxPQUFnQixFQUE4QixFQUFFO0lBQy9FLElBQUksT0FBTyxPQUFPLEtBQUssVUFBVSxFQUFFLENBQUM7UUFDbEMsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBQ0QsT0FBTyxLQUFLLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQztXQUN4QixPQUFPLENBQUMsTUFBTSxHQUFHLENBQUM7V0FDbEIsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLFVBQVUs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
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IWccConfig } from './wcctools.interfaces.js';
|
|
2
|
+
export { RecorderService, type IRecorderEvents, type IRecordingOptions, type TOutputFormat } from './services/recorder.service.js';
|
|
3
|
+
export { WccRecordButton } from './elements/wcc-record-button.js';
|
|
4
|
+
export { WccRecordingPanel } from './elements/wcc-recording-panel.js';
|
|
5
|
+
export type { IWccConfig, IWccSection } from './wcctools.interfaces.js';
|
|
6
|
+
/**
|
|
7
|
+
* Set up the WCC Tools dashboard:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* setupWccTools({
|
|
10
|
+
* sections: [
|
|
11
|
+
* { name: 'Elements', type: 'elements', items: elements },
|
|
12
|
+
* { name: 'Pages', type: 'pages', items: pages },
|
|
13
|
+
* ]
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
declare const setupWccTools: (config: IWccConfig) => void;
|
|
18
|
+
export { setupWccTools };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { WccDashboard } from './elements/wcc-dashboard.js';
|
|
2
|
+
// Export recording components and service
|
|
3
|
+
export { RecorderService } from './services/recorder.service.js';
|
|
4
|
+
export { WccRecordButton } from './elements/wcc-record-button.js';
|
|
5
|
+
export { WccRecordingPanel } from './elements/wcc-recording-panel.js';
|
|
6
|
+
/**
|
|
7
|
+
* Set up the WCC Tools dashboard:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* setupWccTools({
|
|
10
|
+
* sections: [
|
|
11
|
+
* { name: 'Elements', type: 'elements', items: elements },
|
|
12
|
+
* { name: 'Pages', type: 'pages', items: pages },
|
|
13
|
+
* ]
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
const setupWccTools = (config) => {
|
|
18
|
+
let hasRun = false;
|
|
19
|
+
const runWccToolsSetup = async () => {
|
|
20
|
+
if (document.readyState === 'complete' && !hasRun) {
|
|
21
|
+
hasRun = true;
|
|
22
|
+
const wccTools = new WccDashboard(config);
|
|
23
|
+
document.querySelector('body').append(wccTools);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
document.addEventListener('readystatechange', runWccToolsSetup);
|
|
27
|
+
runWccToolsSetup();
|
|
28
|
+
};
|
|
29
|
+
export { setupWccTools };
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c193ZWIvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBRzNELDBDQUEwQztBQUMxQyxPQUFPLEVBQUUsZUFBZSxFQUFvRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ25JLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNsRSxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUt0RTs7Ozs7Ozs7OztHQVVHO0FBQ0gsTUFBTSxhQUFhLEdBQUcsQ0FBQyxNQUFrQixFQUFFLEVBQUU7SUFDM0MsSUFBSSxNQUFNLEdBQUcsS0FBSyxDQUFDO0lBQ25CLE1BQU0sZ0JBQWdCLEdBQUcsS0FBSyxJQUFJLEVBQUU7UUFDbEMsSUFBSSxRQUFRLENBQUMsVUFBVSxLQUFLLFVBQVUsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ2xELE1BQU0sR0FBRyxJQUFJLENBQUM7WUFDZCxNQUFNLFFBQVEsR0FBRyxJQUFJLFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUMxQyxRQUFRLENBQUMsYUFBYSxDQUFDLE1BQU0sQ0FBQyxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUNsRCxDQUFDO0lBQ0gsQ0FBQyxDQUFDO0lBQ0YsUUFBUSxDQUFDLGdCQUFnQixDQUFDLGtCQUFrQixFQUFFLGdCQUFnQixDQUFDLENBQUM7SUFDaEUsZ0JBQWdCLEVBQUUsQ0FBQztBQUNyQixDQUFDLENBQUM7QUFFRixPQUFPLEVBQ0wsYUFBYSxFQUNkLENBQUMifQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const page1: any;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export const page1 = null;
|
|
2
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c193ZWIvcGFnZXMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsTUFBTSxDQUFDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyJ9
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RecorderService - Handles all MediaRecorder, audio monitoring, and video export logic.
|
|
3
|
+
* Recording always uses MediaRecorder → WebM (the reliable browser path).
|
|
4
|
+
* MP4 output is produced by converting WebM → MP4 via mediabunny at export time.
|
|
5
|
+
*/
|
|
6
|
+
export type TOutputFormat = 'webm' | 'mp4';
|
|
7
|
+
export interface IRecorderEvents {
|
|
8
|
+
onDurationUpdate?: (duration: number) => void;
|
|
9
|
+
onRecordingComplete?: (blob: Blob) => void;
|
|
10
|
+
onAudioLevelUpdate?: (level: number) => void;
|
|
11
|
+
onError?: (error: Error) => void;
|
|
12
|
+
onStreamEnded?: () => void;
|
|
13
|
+
}
|
|
14
|
+
export interface IRecordingOptions {
|
|
15
|
+
mode: 'viewport' | 'screen';
|
|
16
|
+
audioDeviceId?: string;
|
|
17
|
+
viewportElement?: HTMLElement;
|
|
18
|
+
outputFormat?: TOutputFormat;
|
|
19
|
+
}
|
|
20
|
+
export declare class RecorderService {
|
|
21
|
+
private mediaRecorder;
|
|
22
|
+
private recordedChunks;
|
|
23
|
+
private durationInterval;
|
|
24
|
+
private _duration;
|
|
25
|
+
private _recordedBlob;
|
|
26
|
+
private _isRecording;
|
|
27
|
+
private _outputFormat;
|
|
28
|
+
private audioContext;
|
|
29
|
+
private audioAnalyser;
|
|
30
|
+
private audioMonitoringInterval;
|
|
31
|
+
private monitoringStream;
|
|
32
|
+
private currentStream;
|
|
33
|
+
private events;
|
|
34
|
+
constructor(events?: IRecorderEvents);
|
|
35
|
+
get isRecording(): boolean;
|
|
36
|
+
get duration(): number;
|
|
37
|
+
get recordedBlob(): Blob | null;
|
|
38
|
+
get outputFormat(): TOutputFormat;
|
|
39
|
+
setEvents(events: IRecorderEvents): void;
|
|
40
|
+
loadMicrophones(requestPermission?: boolean): Promise<MediaDeviceInfo[]>;
|
|
41
|
+
startAudioMonitoring(deviceId: string): Promise<void>;
|
|
42
|
+
stopAudioMonitoring(): void;
|
|
43
|
+
startRecording(options: IRecordingOptions): Promise<void>;
|
|
44
|
+
stopRecording(): void;
|
|
45
|
+
private handleRecordingComplete;
|
|
46
|
+
/**
|
|
47
|
+
* Converts a WebM blob to MP4 using mediabunny's Conversion API.
|
|
48
|
+
* Uses WebCodecs for hardware-accelerated H.264 encoding.
|
|
49
|
+
*/
|
|
50
|
+
convertToMp4(webmBlob: Blob): Promise<Blob>;
|
|
51
|
+
exportTrimmedVideo(videoElement: HTMLVideoElement, trimStart: number, trimEnd: number): Promise<Blob>;
|
|
52
|
+
private recordTrimmedStream;
|
|
53
|
+
reset(): void;
|
|
54
|
+
dispose(): void;
|
|
55
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RecorderService - Handles all MediaRecorder, audio monitoring, and video export logic.
|
|
3
|
+
* Recording always uses MediaRecorder → WebM (the reliable browser path).
|
|
4
|
+
* MP4 output is produced by converting WebM → MP4 via mediabunny at export time.
|
|
5
|
+
*/
|
|
6
|
+
export class RecorderService {
|
|
7
|
+
// Recording state
|
|
8
|
+
mediaRecorder = null;
|
|
9
|
+
recordedChunks = [];
|
|
10
|
+
durationInterval = null;
|
|
11
|
+
_duration = 0;
|
|
12
|
+
_recordedBlob = null;
|
|
13
|
+
_isRecording = false;
|
|
14
|
+
_outputFormat = 'webm';
|
|
15
|
+
// Audio monitoring state
|
|
16
|
+
audioContext = null;
|
|
17
|
+
audioAnalyser = null;
|
|
18
|
+
audioMonitoringInterval = null;
|
|
19
|
+
monitoringStream = null;
|
|
20
|
+
// Current recording stream
|
|
21
|
+
currentStream = null;
|
|
22
|
+
// Event callbacks
|
|
23
|
+
events = {};
|
|
24
|
+
constructor(events) {
|
|
25
|
+
if (events) {
|
|
26
|
+
this.events = events;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Public getters
|
|
30
|
+
get isRecording() {
|
|
31
|
+
return this._isRecording;
|
|
32
|
+
}
|
|
33
|
+
get duration() {
|
|
34
|
+
return this._duration;
|
|
35
|
+
}
|
|
36
|
+
get recordedBlob() {
|
|
37
|
+
return this._recordedBlob;
|
|
38
|
+
}
|
|
39
|
+
get outputFormat() {
|
|
40
|
+
return this._outputFormat;
|
|
41
|
+
}
|
|
42
|
+
// Update event callbacks
|
|
43
|
+
setEvents(events) {
|
|
44
|
+
this.events = { ...this.events, ...events };
|
|
45
|
+
}
|
|
46
|
+
// ==================== Microphone Management ====================
|
|
47
|
+
async loadMicrophones(requestPermission = false) {
|
|
48
|
+
try {
|
|
49
|
+
if (requestPermission) {
|
|
50
|
+
// Request permission by getting a temporary stream
|
|
51
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
52
|
+
stream.getTracks().forEach(track => track.stop());
|
|
53
|
+
}
|
|
54
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
55
|
+
return devices.filter(d => d.kind === 'audioinput');
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error('Error loading microphones:', error);
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async startAudioMonitoring(deviceId) {
|
|
63
|
+
this.stopAudioMonitoring();
|
|
64
|
+
if (!deviceId)
|
|
65
|
+
return;
|
|
66
|
+
try {
|
|
67
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
68
|
+
audio: { deviceId: { exact: deviceId } }
|
|
69
|
+
});
|
|
70
|
+
this.monitoringStream = stream;
|
|
71
|
+
this.audioContext = new AudioContext();
|
|
72
|
+
const source = this.audioContext.createMediaStreamSource(stream);
|
|
73
|
+
this.audioAnalyser = this.audioContext.createAnalyser();
|
|
74
|
+
this.audioAnalyser.fftSize = 256;
|
|
75
|
+
source.connect(this.audioAnalyser);
|
|
76
|
+
const dataArray = new Uint8Array(this.audioAnalyser.frequencyBinCount);
|
|
77
|
+
this.audioMonitoringInterval = window.setInterval(() => {
|
|
78
|
+
if (this.audioAnalyser) {
|
|
79
|
+
this.audioAnalyser.getByteFrequencyData(dataArray);
|
|
80
|
+
const average = dataArray.reduce((a, b) => a + b) / dataArray.length;
|
|
81
|
+
const level = Math.min(100, (average / 128) * 100);
|
|
82
|
+
this.events.onAudioLevelUpdate?.(level);
|
|
83
|
+
}
|
|
84
|
+
}, 50);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
console.error('Error starting audio monitoring:', error);
|
|
88
|
+
this.events.onAudioLevelUpdate?.(0);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
stopAudioMonitoring() {
|
|
92
|
+
if (this.audioMonitoringInterval) {
|
|
93
|
+
clearInterval(this.audioMonitoringInterval);
|
|
94
|
+
this.audioMonitoringInterval = null;
|
|
95
|
+
}
|
|
96
|
+
if (this.audioContext) {
|
|
97
|
+
this.audioContext.close();
|
|
98
|
+
this.audioContext = null;
|
|
99
|
+
}
|
|
100
|
+
if (this.monitoringStream) {
|
|
101
|
+
this.monitoringStream.getTracks().forEach(track => track.stop());
|
|
102
|
+
this.monitoringStream = null;
|
|
103
|
+
}
|
|
104
|
+
this.audioAnalyser = null;
|
|
105
|
+
}
|
|
106
|
+
// ==================== Recording Control ====================
|
|
107
|
+
async startRecording(options) {
|
|
108
|
+
try {
|
|
109
|
+
this._outputFormat = options.outputFormat || 'webm';
|
|
110
|
+
// Stop audio monitoring before recording
|
|
111
|
+
this.stopAudioMonitoring();
|
|
112
|
+
// Get video stream based on mode
|
|
113
|
+
const displayMediaOptions = {
|
|
114
|
+
video: {
|
|
115
|
+
displaySurface: options.mode === 'viewport' ? 'browser' : 'monitor',
|
|
116
|
+
frameRate: { ideal: 60 },
|
|
117
|
+
},
|
|
118
|
+
audio: false
|
|
119
|
+
};
|
|
120
|
+
// Add preferCurrentTab hint for viewport mode
|
|
121
|
+
if (options.mode === 'viewport') {
|
|
122
|
+
displayMediaOptions.preferCurrentTab = true;
|
|
123
|
+
}
|
|
124
|
+
const videoStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
|
|
125
|
+
// If viewport mode, try to crop to viewport element using Element Capture API
|
|
126
|
+
if (options.mode === 'viewport' && options.viewportElement) {
|
|
127
|
+
try {
|
|
128
|
+
if ('CropTarget' in window) {
|
|
129
|
+
const cropTarget = await window.CropTarget.fromElement(options.viewportElement);
|
|
130
|
+
const [videoTrack] = videoStream.getVideoTracks();
|
|
131
|
+
await videoTrack.cropTo(cropTarget);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
console.warn('Element Capture not supported, recording full tab:', e);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Combine video with audio if enabled
|
|
139
|
+
let combinedStream = videoStream;
|
|
140
|
+
if (options.audioDeviceId) {
|
|
141
|
+
try {
|
|
142
|
+
const audioStream = await navigator.mediaDevices.getUserMedia({
|
|
143
|
+
audio: { deviceId: { exact: options.audioDeviceId } }
|
|
144
|
+
});
|
|
145
|
+
combinedStream = new MediaStream([
|
|
146
|
+
...videoStream.getVideoTracks(),
|
|
147
|
+
...audioStream.getAudioTracks()
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
catch (audioError) {
|
|
151
|
+
console.warn('Could not add audio:', audioError);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Store stream for cleanup
|
|
155
|
+
this.currentStream = combinedStream;
|
|
156
|
+
// Handle stream ending (user clicks "Stop sharing")
|
|
157
|
+
videoStream.getVideoTracks()[0].onended = () => {
|
|
158
|
+
if (this._isRecording) {
|
|
159
|
+
this.stopRecording();
|
|
160
|
+
this.events.onStreamEnded?.();
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
// Always record as WebM — conversion to MP4 happens at export time
|
|
164
|
+
const mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9')
|
|
165
|
+
? 'video/webm;codecs=vp9'
|
|
166
|
+
: 'video/webm';
|
|
167
|
+
this.mediaRecorder = new MediaRecorder(combinedStream, {
|
|
168
|
+
mimeType,
|
|
169
|
+
videoBitsPerSecond: 8_000_000, // 8 Mbps for smooth, high-quality capture
|
|
170
|
+
});
|
|
171
|
+
this.recordedChunks = [];
|
|
172
|
+
this.mediaRecorder.ondataavailable = (e) => {
|
|
173
|
+
if (e.data.size > 0) {
|
|
174
|
+
this.recordedChunks.push(e.data);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
this.mediaRecorder.onstop = () => this.handleRecordingComplete();
|
|
178
|
+
this.mediaRecorder.start(1000); // Capture in 1-second chunks
|
|
179
|
+
// Start duration timer
|
|
180
|
+
this._duration = 0;
|
|
181
|
+
this.durationInterval = window.setInterval(() => {
|
|
182
|
+
this._duration++;
|
|
183
|
+
this.events.onDurationUpdate?.(this._duration);
|
|
184
|
+
}, 1000);
|
|
185
|
+
this._isRecording = true;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
console.error('Error starting recording:', error);
|
|
189
|
+
this._isRecording = false;
|
|
190
|
+
this.events.onError?.(error);
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
stopRecording() {
|
|
195
|
+
if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
|
|
196
|
+
this.mediaRecorder.stop();
|
|
197
|
+
}
|
|
198
|
+
if (this.durationInterval) {
|
|
199
|
+
clearInterval(this.durationInterval);
|
|
200
|
+
this.durationInterval = null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async handleRecordingComplete() {
|
|
204
|
+
const blob = new Blob(this.recordedChunks, { type: 'video/webm' });
|
|
205
|
+
this._recordedBlob = blob;
|
|
206
|
+
// Stop all tracks
|
|
207
|
+
if (this.currentStream) {
|
|
208
|
+
this.currentStream.getTracks().forEach(track => track.stop());
|
|
209
|
+
this.currentStream = null;
|
|
210
|
+
}
|
|
211
|
+
this._isRecording = false;
|
|
212
|
+
this.events.onRecordingComplete?.(this._recordedBlob);
|
|
213
|
+
}
|
|
214
|
+
// ==================== Conversion & Export ====================
|
|
215
|
+
/**
|
|
216
|
+
* Converts a WebM blob to MP4 using mediabunny's Conversion API.
|
|
217
|
+
* Uses WebCodecs for hardware-accelerated H.264 encoding.
|
|
218
|
+
*/
|
|
219
|
+
async convertToMp4(webmBlob) {
|
|
220
|
+
const { Input, Output, Conversion, BlobSource, BufferTarget, Mp4OutputFormat, WEBM, QUALITY_HIGH, } = await import('mediabunny');
|
|
221
|
+
const input = new Input({
|
|
222
|
+
source: new BlobSource(webmBlob),
|
|
223
|
+
formats: [WEBM],
|
|
224
|
+
});
|
|
225
|
+
const target = new BufferTarget();
|
|
226
|
+
const output = new Output({
|
|
227
|
+
format: new Mp4OutputFormat({ fastStart: 'in-memory' }),
|
|
228
|
+
target,
|
|
229
|
+
});
|
|
230
|
+
const conversion = await Conversion.init({
|
|
231
|
+
input,
|
|
232
|
+
output,
|
|
233
|
+
// Force transcoding from VP9 → H.264 and Opus → AAC
|
|
234
|
+
video: {
|
|
235
|
+
codec: 'avc',
|
|
236
|
+
bitrate: QUALITY_HIGH,
|
|
237
|
+
fit: 'contain',
|
|
238
|
+
},
|
|
239
|
+
audio: {
|
|
240
|
+
codec: 'aac',
|
|
241
|
+
bitrate: QUALITY_HIGH,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
await conversion.execute();
|
|
245
|
+
const buffer = target.buffer;
|
|
246
|
+
if (!buffer || buffer.byteLength === 0) {
|
|
247
|
+
throw new Error('MP4 conversion produced empty output');
|
|
248
|
+
}
|
|
249
|
+
return new Blob([buffer], { type: 'video/mp4' });
|
|
250
|
+
}
|
|
251
|
+
async exportTrimmedVideo(videoElement, trimStart, trimEnd) {
|
|
252
|
+
return new Promise((resolve, reject) => {
|
|
253
|
+
// Create a canvas for capturing frames
|
|
254
|
+
const canvas = document.createElement('canvas');
|
|
255
|
+
canvas.width = videoElement.videoWidth || 1280;
|
|
256
|
+
canvas.height = videoElement.videoHeight || 720;
|
|
257
|
+
const ctx = canvas.getContext('2d');
|
|
258
|
+
if (!ctx) {
|
|
259
|
+
reject(new Error('Could not get canvas context'));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
// Create canvas stream for video
|
|
263
|
+
const canvasStream = canvas.captureStream(30);
|
|
264
|
+
// Try to capture audio from video element
|
|
265
|
+
let combinedStream;
|
|
266
|
+
try {
|
|
267
|
+
// Create audio context to capture video's audio
|
|
268
|
+
const audioCtx = new AudioContext();
|
|
269
|
+
const source = audioCtx.createMediaElementSource(videoElement);
|
|
270
|
+
const destination = audioCtx.createMediaStreamDestination();
|
|
271
|
+
source.connect(destination);
|
|
272
|
+
source.connect(audioCtx.destination); // Also play through speakers
|
|
273
|
+
// Combine video (from canvas) and audio (from video element)
|
|
274
|
+
combinedStream = new MediaStream([
|
|
275
|
+
...canvasStream.getVideoTracks(),
|
|
276
|
+
...destination.stream.getAudioTracks()
|
|
277
|
+
]);
|
|
278
|
+
// Store audioCtx for cleanup
|
|
279
|
+
const cleanup = () => {
|
|
280
|
+
audioCtx.close();
|
|
281
|
+
};
|
|
282
|
+
this.recordTrimmedStream(videoElement, canvas, ctx, combinedStream, trimStart, trimEnd, cleanup, resolve, reject);
|
|
283
|
+
}
|
|
284
|
+
catch (audioError) {
|
|
285
|
+
console.warn('Could not capture audio, recording video only:', audioError);
|
|
286
|
+
combinedStream = canvasStream;
|
|
287
|
+
this.recordTrimmedStream(videoElement, canvas, ctx, combinedStream, trimStart, trimEnd, () => { }, resolve, reject);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
recordTrimmedStream(video, canvas, ctx, stream, trimStart, trimEnd, cleanup, resolve, reject) {
|
|
292
|
+
const mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9')
|
|
293
|
+
? 'video/webm;codecs=vp9'
|
|
294
|
+
: 'video/webm';
|
|
295
|
+
const recorder = new MediaRecorder(stream, { mimeType });
|
|
296
|
+
const chunks = [];
|
|
297
|
+
recorder.ondataavailable = (e) => {
|
|
298
|
+
if (e.data.size > 0) {
|
|
299
|
+
chunks.push(e.data);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
recorder.onstop = () => {
|
|
303
|
+
cleanup();
|
|
304
|
+
resolve(new Blob(chunks, { type: 'video/webm' }));
|
|
305
|
+
};
|
|
306
|
+
recorder.onerror = (e) => {
|
|
307
|
+
cleanup();
|
|
308
|
+
reject(new Error('Recording error: ' + e));
|
|
309
|
+
};
|
|
310
|
+
// Seek to trim start
|
|
311
|
+
video.currentTime = trimStart;
|
|
312
|
+
video.onseeked = () => {
|
|
313
|
+
// Start recording
|
|
314
|
+
recorder.start(100);
|
|
315
|
+
// Start playing
|
|
316
|
+
video.play();
|
|
317
|
+
// Draw frames to canvas
|
|
318
|
+
const drawFrame = () => {
|
|
319
|
+
if (video.currentTime >= trimEnd || video.paused || video.ended) {
|
|
320
|
+
video.pause();
|
|
321
|
+
video.onseeked = null;
|
|
322
|
+
// Give a small delay before stopping to ensure last frame is captured
|
|
323
|
+
setTimeout(() => {
|
|
324
|
+
if (recorder.state === 'recording') {
|
|
325
|
+
recorder.stop();
|
|
326
|
+
}
|
|
327
|
+
}, 100);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
331
|
+
requestAnimationFrame(drawFrame);
|
|
332
|
+
};
|
|
333
|
+
drawFrame();
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
// ==================== Cleanup ====================
|
|
337
|
+
reset() {
|
|
338
|
+
this._recordedBlob = null;
|
|
339
|
+
this.recordedChunks = [];
|
|
340
|
+
this._duration = 0;
|
|
341
|
+
this._isRecording = false;
|
|
342
|
+
}
|
|
343
|
+
dispose() {
|
|
344
|
+
this.stopRecording();
|
|
345
|
+
this.stopAudioMonitoring();
|
|
346
|
+
this.reset();
|
|
347
|
+
if (this.currentStream) {
|
|
348
|
+
this.currentStream.getTracks().forEach(track => track.stop());
|
|
349
|
+
this.currentStream = null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVjb3JkZXIuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzX3dlYi9zZXJ2aWNlcy9yZWNvcmRlci5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7O0dBSUc7QUFtQkgsTUFBTSxPQUFPLGVBQWU7SUFDMUIsa0JBQWtCO0lBQ1YsYUFBYSxHQUF5QixJQUFJLENBQUM7SUFDM0MsY0FBYyxHQUFXLEVBQUUsQ0FBQztJQUM1QixnQkFBZ0IsR0FBa0IsSUFBSSxDQUFDO0lBQ3ZDLFNBQVMsR0FBVyxDQUFDLENBQUM7SUFDdEIsYUFBYSxHQUFnQixJQUFJLENBQUM7SUFDbEMsWUFBWSxHQUFZLEtBQUssQ0FBQztJQUM5QixhQUFhLEdBQWtCLE1BQU0sQ0FBQztJQUU5Qyx5QkFBeUI7SUFDakIsWUFBWSxHQUF3QixJQUFJLENBQUM7SUFDekMsYUFBYSxHQUF3QixJQUFJLENBQUM7SUFDMUMsdUJBQXVCLEdBQWtCLElBQUksQ0FBQztJQUM5QyxnQkFBZ0IsR0FBdUIsSUFBSSxDQUFDO0lBRXBELDJCQUEyQjtJQUNuQixhQUFhLEdBQXVCLElBQUksQ0FBQztJQUVqRCxrQkFBa0I7SUFDVixNQUFNLEdBQW9CLEVBQUUsQ0FBQztJQUVyQyxZQUFZLE1BQXdCO1FBQ2xDLElBQUksTUFBTSxFQUFFLENBQUM7WUFDWCxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUN2QixDQUFDO0lBQ0gsQ0FBQztJQUVELGlCQUFpQjtJQUNqQixJQUFJLFdBQVc7UUFDYixPQUFPLElBQUksQ0FBQyxZQUFZLENBQUM7SUFDM0IsQ0FBQztJQUVELElBQUksUUFBUTtRQUNWLE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQztJQUN4QixDQUFDO0lBRUQsSUFBSSxZQUFZO1FBQ2QsT0FBTyxJQUFJLENBQUMsYUFBYSxDQUFDO0lBQzVCLENBQUM7SUFFRCxJQUFJLFlBQVk7UUFDZCxPQUFPLElBQUksQ0FBQyxhQUFhLENBQUM7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
|