arcane-os 0.3.1 → 0.3.2
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/CHANGELOG.md +14 -0
- package/README.md +86 -117
- package/bin/arcane-test.mjs +170 -46
- package/browser-runtime/ai/browser-speech-artifacts.mjs +855 -895
- package/browser-runtime/ai/browser-speech-providers.mjs +80 -204
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
- package/browser-runtime/ai/browser-wasm.mjs +24 -35
- package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
- package/browser-runtime/ai/model-controller.mjs +584 -181
- package/browser-runtime/ai/speech-worker-client.mjs +8 -148
- package/browser-runtime/ai/speech-worker-runtime.mjs +642 -374
- package/browser-runtime/dom-event-instrumentation.mjs +55 -147
- package/browser-runtime/event-manager.mjs +239 -624
- package/package.json +5 -6
- package/runtime/arcane/components/app-bar.html +3 -15
- package/runtime/arcane/components/assistant-panel.html +10 -10
- package/runtime/arcane/components/calculator.html +1 -1
- package/runtime/arcane/components/chat.html +1359 -135
- package/runtime/arcane/components/conversation-view.html +2 -2
- package/runtime/arcane/components/document-inspector.html +11 -17
- package/runtime/arcane/components/file-manager.html +13 -56
- package/runtime/arcane/components/markdown-document.html +82 -281
- package/runtime/arcane/components/markdown-editor.html +7 -10
- package/runtime/arcane/components/media-embed.html +6 -6
- package/runtime/arcane/components/screen-capture.html +4 -4
- package/runtime/arcane/components/source-explanation.html +2 -2
- package/runtime/arcane/components/speech.html +112 -68
- package/runtime/arcane/components/terminal-workspace.html +4 -4
- package/runtime/arcane/components/theme-editor.html +1 -1
- package/runtime/arcane/components/unified-inbox.html +2 -2
- package/runtime/arcane/components/voice-transcription.html +31 -21
- package/runtime/arcane/entities/Calculation.js +2 -3
- package/runtime/arcane/entities/Chat.js +228 -43
- package/runtime/arcane/entities/Preference.js +3 -5
- package/runtime/arcane/entities/Weather.js +5 -5
- package/runtime/arcane/modules/AI.js +1042 -427
- package/runtime/arcane/modules/AIProviderRuntime.js +618 -359
- package/runtime/arcane/modules/AIResponseLength.js +9 -19
- package/runtime/arcane/modules/AIRuntimeState.js +109 -72
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
- package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
- package/runtime/arcane/modules/CalculatorEngine.js +9 -9
- package/runtime/arcane/modules/CommunicationAppController.js +3 -7
- package/runtime/arcane/modules/ComponentContracts.js +30 -32
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
- package/runtime/arcane/modules/ConversationActionItems.js +26 -59
- package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
- package/runtime/arcane/modules/ConversationTimebox.js +27 -15
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
- package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
- package/runtime/arcane/modules/HTMLImport.js +54 -1
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
- package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
- package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
- package/runtime/arcane/modules/MD.js +1 -45
- package/runtime/arcane/modules/Mail.js +51 -103
- package/runtime/arcane/modules/MailOutbox.mjs +95 -193
- package/runtime/arcane/modules/MailTransport.mjs +36 -57
- package/runtime/arcane/modules/ModelDefinition.js +22 -106
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
- package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
- package/runtime/arcane/modules/PreferenceStore.js +102 -30
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
- package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
- package/runtime/arcane/modules/ScreenCapture.js +175 -128
- package/runtime/arcane/modules/SpeechPlayback.js +46 -149
- package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
- package/runtime/arcane/modules/ToolCallRouter.js +25 -12
- package/runtime/arcane/modules/YouTubeMedia.js +6 -5
- package/schemas/arcane-app-bundle.schema.json +13 -78
- package/schemas/arcane-app.schema.json +9 -25
- package/schemas/arcane-lock.schema.json +18 -151
- package/schemas/arcane-package.schema.json +2 -16
- package/schemas/native-build-plan.schema.json +119 -122
- package/src/app-descriptor.mjs +75 -132
- package/src/application-tests.mjs +200 -0
- package/src/cli/main.mjs +27 -46
- package/src/constants.mjs +3 -4
- package/src/dev-server.mjs +30 -324
- package/src/doctor.mjs +92 -154
- package/src/dom-event-instrumentation.mjs +55 -147
- package/src/errors.mjs +2 -3
- package/src/event-manager.mjs +239 -624
- package/src/event-queue.mjs +3 -3
- package/src/import-map.mjs +273 -1028
- package/src/index.mjs +14 -16
- package/src/installed-sdk-runtime.mjs +27 -67
- package/src/integrated-provider-loader.mjs +53 -382
- package/src/mail-api.mjs +0 -2
- package/src/mail-server.mjs +224 -580
- package/src/mail.mjs +4 -10
- package/src/native-plan.mjs +163 -598
- package/src/native-provider-loader.mjs +104 -1063
- package/src/packager/core.mjs +485 -3229
- package/src/process.mjs +5 -10
- package/src/release-bundle.mjs +292 -2405
- package/src/runtime.mjs +76 -396
- package/src/scaffold.mjs +30 -80
- package/src/sdk-browser-runtime.mjs +70 -626
- package/src/source-server.mjs +588 -0
- package/src/targets/index.mjs +78 -188
- package/src/templates/workspace-template.mjs +19 -135
- package/src/testing-loader.mjs +164 -0
- package/src/testing.mjs +1 -1
- package/src/toolchain.mjs +131 -544
- package/src/update-check.mjs +26 -64
- package/src/workspace-operation-lock.mjs +139 -430
- package/src/workspace-runtime.mjs +109 -1558
- package/src/workspace.mjs +40 -302
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
- package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
- package/browser-runtime/ai/internal/sha256.mjs +0 -166
- package/docs/architecture.md +0 -344
- package/docs/compatibility.md +0 -36
- package/docs/event-manager.md +0 -294
- package/docs/platform-targets.md +0 -108
- package/docs/publishing.md +0 -201
- package/docs/reference/README.md +0 -185
- package/docs/reference/ai/browser-speech-package-authority.json +0 -835
- package/docs/reference/ai/browser-speech.md +0 -1295
- package/docs/reference/ai/browser-wasm.md +0 -530
- package/docs/reference/arcane-ollama.md +0 -288
- package/docs/reference/availability-and-normalization.md +0 -183
- package/docs/reference/behavioral-testing.md +0 -133
- package/docs/reference/cli.md +0 -779
- package/docs/reference/core/README.md +0 -62
- package/docs/reference/core/arcane-ai-contracts.md +0 -907
- package/docs/reference/core/arcane-api.md +0 -601
- package/docs/reference/core/arcane-entities.md +0 -65
- package/docs/reference/core/arcane-events.md +0 -134
- package/docs/reference/core/ollama-module.md +0 -181
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
- package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
- package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
- package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
- package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
- package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
- package/docs/reference/event-manager.md +0 -1511
- package/docs/reference/inventory/package-api.json +0 -3284
- package/docs/reference/inventory/runtime-components.json +0 -1011
- package/docs/reference/inventory/runtime-entities.json +0 -26
- package/docs/reference/inventory/runtime-modules.json +0 -1431
- package/docs/reference/mail.md +0 -316
- package/docs/reference/protocols.md +0 -719
- package/docs/reference/runtime-components.md +0 -1366
- package/docs/reference/runtime-entities.md +0 -303
- package/docs/reference/runtime-modules.md +0 -2965
- package/docs/reference/sdk-api.md +0 -6698
- package/docs/roadmap.md +0 -79
- package/docs/work-amplification.md +0 -129
- package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
|
@@ -1,142 +1,142 @@
|
|
|
1
1
|
import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
2
2
|
import GifEncoder from './GifEncoder.js';
|
|
3
3
|
|
|
4
|
-
export const SCREEN_CAPTURE_EVENT_TYPES=
|
|
4
|
+
export const SCREEN_CAPTURE_EVENT_TYPES={
|
|
5
5
|
displaySelectionRequested:'capture-requesting',
|
|
6
6
|
captureStarted:'capture-start',
|
|
7
7
|
captureCompleted:'capture-result',
|
|
8
8
|
captureFailed:'capture-error',
|
|
9
9
|
captureStopped:'capture-stop'
|
|
10
|
-
}
|
|
10
|
+
};
|
|
11
11
|
|
|
12
|
-
export const SCREEN_CAPTURE_STATUSES=
|
|
12
|
+
export const SCREEN_CAPTURE_STATUSES={
|
|
13
13
|
selectingDisplay:'selecting-display',
|
|
14
14
|
recording:'recording',
|
|
15
15
|
captureReady:'capture-ready',
|
|
16
16
|
captureRejected:'capture-rejected',
|
|
17
17
|
captureStopped:'capture-stopped'
|
|
18
|
-
}
|
|
18
|
+
};
|
|
19
19
|
|
|
20
|
-
export const SCREEN_CAPTURE_IMAGE_TYPE_FALLBACK=
|
|
20
|
+
export const SCREEN_CAPTURE_IMAGE_TYPE_FALLBACK={
|
|
21
21
|
when:'blob-type-unreported',
|
|
22
22
|
mimeType:'image/png',
|
|
23
23
|
extension:'png'
|
|
24
|
-
}
|
|
24
|
+
};
|
|
25
25
|
|
|
26
|
-
export const SCREEN_CAPTURE_ERRORS=
|
|
27
|
-
active:
|
|
26
|
+
export const SCREEN_CAPTURE_ERRORS={
|
|
27
|
+
active:{
|
|
28
28
|
code:'ARCANE_SCREEN_CAPTURE_OPERATION_ACTIVE',
|
|
29
29
|
reason:'screen-capture-operation-active'
|
|
30
|
-
}
|
|
31
|
-
disposed:
|
|
30
|
+
},
|
|
31
|
+
disposed:{
|
|
32
32
|
code:'ARCANE_SCREEN_CAPTURE_DISPOSED',
|
|
33
33
|
reason:'screen-capture-disposed'
|
|
34
|
-
}
|
|
35
|
-
optionsRecordRejected:
|
|
34
|
+
},
|
|
35
|
+
optionsRecordRejected:{
|
|
36
36
|
code:'ARCANE_SCREEN_CAPTURE_OPTIONS_RECORD_REJECTED',
|
|
37
37
|
reason:'screen-capture-options-record-rejected'
|
|
38
|
-
}
|
|
39
|
-
abortSignalRejected:
|
|
38
|
+
},
|
|
39
|
+
abortSignalRejected:{
|
|
40
40
|
code:'ARCANE_SCREEN_CAPTURE_ABORT_SIGNAL_REJECTED',
|
|
41
41
|
reason:'screen-capture-abort-signal-rejected'
|
|
42
|
-
}
|
|
43
|
-
operationIdRejected:
|
|
42
|
+
},
|
|
43
|
+
operationIdRejected:{
|
|
44
44
|
code:'ARCANE_SCREEN_CAPTURE_OPERATION_ID_REJECTED',
|
|
45
45
|
reason:'screen-capture-operation-id-rejected'
|
|
46
|
-
}
|
|
47
|
-
cleanupRejected:
|
|
46
|
+
},
|
|
47
|
+
cleanupRejected:{
|
|
48
48
|
code:'ARCANE_SCREEN_CAPTURE_CLEANUP_REJECTED',
|
|
49
49
|
reason:'screen-capture-cleanup-rejected'
|
|
50
|
-
}
|
|
51
|
-
displayUnavailable:
|
|
50
|
+
},
|
|
51
|
+
displayUnavailable:{
|
|
52
52
|
code:'ARCANE_SCREEN_CAPTURE_DISPLAY_UNAVAILABLE',
|
|
53
53
|
reason:'screen-capture-display-unavailable'
|
|
54
|
-
}
|
|
55
|
-
displayMetadataRejected:
|
|
54
|
+
},
|
|
55
|
+
displayMetadataRejected:{
|
|
56
56
|
code:'ARCANE_SCREEN_CAPTURE_DISPLAY_METADATA_REJECTED',
|
|
57
57
|
reason:'screen-capture-display-metadata-rejected'
|
|
58
|
-
}
|
|
59
|
-
displayPlaybackRejected:
|
|
58
|
+
},
|
|
59
|
+
displayPlaybackRejected:{
|
|
60
60
|
code:'ARCANE_SCREEN_CAPTURE_DISPLAY_PLAYBACK_REJECTED',
|
|
61
61
|
reason:'screen-capture-display-playback-rejected'
|
|
62
|
-
}
|
|
63
|
-
displaySelectionCancelled:
|
|
62
|
+
},
|
|
63
|
+
displaySelectionCancelled:{
|
|
64
64
|
code:'ARCANE_SCREEN_CAPTURE_DISPLAY_SELECTION_CANCELLED',
|
|
65
65
|
reason:'screen-capture-display-selection-cancelled'
|
|
66
|
-
}
|
|
67
|
-
displaySelectionRejected:
|
|
66
|
+
},
|
|
67
|
+
displaySelectionRejected:{
|
|
68
68
|
code:'ARCANE_SCREEN_CAPTURE_DISPLAY_SELECTION_REJECTED',
|
|
69
69
|
reason:'screen-capture-display-selection-rejected'
|
|
70
|
-
}
|
|
71
|
-
displayTrackEndedBeforeRecording:
|
|
70
|
+
},
|
|
71
|
+
displayTrackEndedBeforeRecording:{
|
|
72
72
|
code:'ARCANE_SCREEN_CAPTURE_DISPLAY_TRACK_ENDED_BEFORE_RECORDING',
|
|
73
73
|
reason:'screen-capture-display-track-ended-before-recording'
|
|
74
|
-
}
|
|
75
|
-
eventTypeUndeclared:
|
|
74
|
+
},
|
|
75
|
+
eventTypeUndeclared:{
|
|
76
76
|
code:'ARCANE_SCREEN_CAPTURE_EVENT_TYPE_UNDECLARED',
|
|
77
77
|
reason:'screen-capture-event-type-undeclared'
|
|
78
|
-
}
|
|
79
|
-
gifEncodingRejected:
|
|
78
|
+
},
|
|
79
|
+
gifEncodingRejected:{
|
|
80
80
|
code:'ARCANE_SCREEN_CAPTURE_GIF_ENCODING_REJECTED',
|
|
81
81
|
reason:'screen-capture-gif-encoding-rejected'
|
|
82
|
-
}
|
|
83
|
-
gifFrameReadRejected:
|
|
82
|
+
},
|
|
83
|
+
gifFrameReadRejected:{
|
|
84
84
|
code:'ARCANE_SCREEN_CAPTURE_GIF_FRAME_READ_REJECTED',
|
|
85
85
|
reason:'screen-capture-gif-frame-read-rejected'
|
|
86
|
-
}
|
|
87
|
-
imageCanvasUnavailable:
|
|
86
|
+
},
|
|
87
|
+
imageCanvasUnavailable:{
|
|
88
88
|
code:'ARCANE_SCREEN_CAPTURE_IMAGE_CANVAS_UNAVAILABLE',
|
|
89
89
|
reason:'screen-capture-image-canvas-unavailable'
|
|
90
|
-
}
|
|
91
|
-
imageDrawRejected:
|
|
90
|
+
},
|
|
91
|
+
imageDrawRejected:{
|
|
92
92
|
code:'ARCANE_SCREEN_CAPTURE_IMAGE_DRAW_REJECTED',
|
|
93
93
|
reason:'screen-capture-image-draw-rejected'
|
|
94
|
-
}
|
|
95
|
-
imageEncodingRejected:
|
|
94
|
+
},
|
|
95
|
+
imageEncodingRejected:{
|
|
96
96
|
code:'ARCANE_SCREEN_CAPTURE_IMAGE_ENCODING_REJECTED',
|
|
97
97
|
reason:'screen-capture-image-encoding-rejected'
|
|
98
|
-
}
|
|
99
|
-
recorderConstructionRejected:
|
|
98
|
+
},
|
|
99
|
+
recorderConstructionRejected:{
|
|
100
100
|
code:'ARCANE_SCREEN_CAPTURE_RECORDER_CONSTRUCTION_REJECTED',
|
|
101
101
|
reason:'screen-capture-recorder-construction-rejected'
|
|
102
|
-
}
|
|
103
|
-
recorderErrorReceived:
|
|
102
|
+
},
|
|
103
|
+
recorderErrorReceived:{
|
|
104
104
|
code:'ARCANE_SCREEN_CAPTURE_RECORDER_ERROR_RECEIVED',
|
|
105
105
|
reason:'screen-capture-recorder-error-received'
|
|
106
|
-
}
|
|
107
|
-
recorderStartRejected:
|
|
106
|
+
},
|
|
107
|
+
recorderStartRejected:{
|
|
108
108
|
code:'ARCANE_SCREEN_CAPTURE_RECORDER_START_REJECTED',
|
|
109
109
|
reason:'screen-capture-recorder-start-rejected'
|
|
110
|
-
}
|
|
111
|
-
recorderStopRejected:
|
|
110
|
+
},
|
|
111
|
+
recorderStopRejected:{
|
|
112
112
|
code:'ARCANE_SCREEN_CAPTURE_RECORDER_STOP_REJECTED',
|
|
113
113
|
reason:'screen-capture-recorder-stop-rejected'
|
|
114
|
-
}
|
|
115
|
-
recorderUnavailable:
|
|
114
|
+
},
|
|
115
|
+
recorderUnavailable:{
|
|
116
116
|
code:'ARCANE_SCREEN_CAPTURE_RECORDER_UNAVAILABLE',
|
|
117
117
|
reason:'screen-capture-recorder-unavailable'
|
|
118
|
-
}
|
|
119
|
-
operationAborted:
|
|
118
|
+
},
|
|
119
|
+
operationAborted:{
|
|
120
120
|
code:'ARCANE_SCREEN_CAPTURE_OPERATION_ABORTED',
|
|
121
121
|
reason:'screen-capture-operation-aborted'
|
|
122
|
-
}
|
|
123
|
-
operationStopped:
|
|
122
|
+
},
|
|
123
|
+
operationStopped:{
|
|
124
124
|
code:'ARCANE_SCREEN_CAPTURE_OPERATION_STOPPED',
|
|
125
125
|
reason:'screen-capture-operation-stopped'
|
|
126
|
-
}
|
|
127
|
-
reset:
|
|
126
|
+
},
|
|
127
|
+
reset:{
|
|
128
128
|
code:'ARCANE_SCREEN_CAPTURE_OPERATION_RESET',
|
|
129
129
|
reason:'screen-capture-reset'
|
|
130
|
-
}
|
|
131
|
-
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
132
|
|
|
133
|
-
export const SCREEN_CAPTURE_ERROR_CODES=Object.
|
|
133
|
+
export const SCREEN_CAPTURE_ERROR_CODES=Object.fromEntries(
|
|
134
134
|
Object.entries(SCREEN_CAPTURE_ERRORS).map(function mapScreenCaptureCode(entry){
|
|
135
135
|
return [entry[0],entry[1].code];
|
|
136
136
|
})
|
|
137
|
-
)
|
|
137
|
+
);
|
|
138
138
|
|
|
139
|
-
export const SCREEN_CAPTURE_REASONS=
|
|
139
|
+
export const SCREEN_CAPTURE_REASONS={
|
|
140
140
|
displaySelectionRequested:'screen-capture-display-selection-requested',
|
|
141
141
|
gifCompleted:'screen-capture-gif-completed',
|
|
142
142
|
gifRecordingStarted:'screen-capture-gif-recording-started',
|
|
@@ -148,15 +148,15 @@ export const SCREEN_CAPTURE_REASONS=Object.freeze({
|
|
|
148
148
|
...Object.fromEntries(Object.entries(SCREEN_CAPTURE_ERRORS).map(
|
|
149
149
|
function mapScreenCaptureReason(entry){return [entry[0],entry[1].reason];}
|
|
150
150
|
))
|
|
151
|
-
}
|
|
151
|
+
};
|
|
152
152
|
|
|
153
|
-
const EVENT_NAMES=
|
|
153
|
+
const EVENT_NAMES={
|
|
154
154
|
requesting:SCREEN_CAPTURE_EVENT_TYPES.displaySelectionRequested,
|
|
155
155
|
start:SCREEN_CAPTURE_EVENT_TYPES.captureStarted,
|
|
156
156
|
result:SCREEN_CAPTURE_EVENT_TYPES.captureCompleted,
|
|
157
157
|
error:SCREEN_CAPTURE_EVENT_TYPES.captureFailed,
|
|
158
158
|
stop:SCREEN_CAPTURE_EVENT_TYPES.captureStopped
|
|
159
|
-
}
|
|
159
|
+
};
|
|
160
160
|
|
|
161
161
|
function signalLike(value){
|
|
162
162
|
return value===undefined
|
|
@@ -212,11 +212,10 @@ function admittedOperationId(value){
|
|
|
212
212
|
if(value===undefined||value===null)return null;
|
|
213
213
|
if(typeof value!=='string'
|
|
214
214
|
||value.trim()!==value
|
|
215
|
-
|
|
216
|
-
||value.length>256){
|
|
215
|
+
||!value){
|
|
217
216
|
throw captureError(
|
|
218
217
|
SCREEN_CAPTURE_ERRORS.operationIdRejected,
|
|
219
|
-
'Screen capture operationId must
|
|
218
|
+
'Screen capture operationId must be a non-empty string without edge whitespace.',
|
|
220
219
|
undefined,
|
|
221
220
|
TypeError
|
|
222
221
|
);
|
|
@@ -224,10 +223,6 @@ function admittedOperationId(value){
|
|
|
224
223
|
return value;
|
|
225
224
|
}
|
|
226
225
|
|
|
227
|
-
function normalizedWidth(value,fallback){
|
|
228
|
-
return Math.max(1,Number(value)||fallback);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
226
|
function imageResultType(blob){
|
|
232
227
|
const reported=typeof blob?.type==='string'?blob.type.trim().toLowerCase():'';
|
|
233
228
|
if(!reported)return SCREEN_CAPTURE_IMAGE_TYPE_FALLBACK;
|
|
@@ -240,7 +235,7 @@ function imageResultType(blob){
|
|
|
240
235
|
:/^[a-z0-9][a-z0-9.-]*$/u.test(canonicalSubtype)
|
|
241
236
|
?canonicalSubtype
|
|
242
237
|
:'bin';
|
|
243
|
-
return
|
|
238
|
+
return {mimeType:reported,extension};
|
|
244
239
|
}
|
|
245
240
|
|
|
246
241
|
function supportedRecorderType(Recorder){
|
|
@@ -260,6 +255,10 @@ function reportDetachedError(error){
|
|
|
260
255
|
else globalThis.console?.error?.(error);
|
|
261
256
|
}
|
|
262
257
|
|
|
258
|
+
function monotonicNow(){
|
|
259
|
+
return globalThis.performance?.now?.()??Date.now();
|
|
260
|
+
}
|
|
261
|
+
|
|
263
262
|
export default class ScreenCapture extends EventTarget{
|
|
264
263
|
#acquisitions=new Set();
|
|
265
264
|
#disposed=false;
|
|
@@ -289,7 +288,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
289
288
|
this.gifDelay=0;
|
|
290
289
|
this.#events=createArcaneEventSource(this,{
|
|
291
290
|
source:'screen-capture',
|
|
292
|
-
eventTypes:Object.
|
|
291
|
+
eventTypes:Object.values(SCREEN_CAPTURE_EVENT_TYPES)
|
|
293
292
|
});
|
|
294
293
|
}
|
|
295
294
|
|
|
@@ -360,7 +359,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
360
359
|
let pending;
|
|
361
360
|
try{
|
|
362
361
|
pending=Promise.resolve(this.mediaDevices.getDisplayMedia({
|
|
363
|
-
video:
|
|
362
|
+
video:true,
|
|
364
363
|
audio:Boolean(options.audio)
|
|
365
364
|
}));
|
|
366
365
|
}catch(cause){
|
|
@@ -424,30 +423,23 @@ export default class ScreenCapture extends EventTarget{
|
|
|
424
423
|
}
|
|
425
424
|
|
|
426
425
|
#publicDetail(detail,{code=null,reason,status}){
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
...(typeof detail?.mode==='string'?{mode:detail.mode}:{}),
|
|
430
|
-
...(typeof detail?.mimeType==='string'?{mimeType:detail.mimeType}:{}),
|
|
431
|
-
...(typeof detail?.extension==='string'?{extension:detail.extension}:{}),
|
|
432
|
-
...(Number.isFinite(detail?.duration)?{duration:Math.max(0,detail.duration)}:{}),
|
|
433
|
-
...(Number.isSafeInteger(detail?.width)?{width:detail.width}:{}),
|
|
434
|
-
...(Number.isSafeInteger(detail?.height)?{height:detail.height}:{}),
|
|
435
|
-
...(Number.isSafeInteger(blob?.size)?{byteCount:blob.size}:{}),
|
|
426
|
+
return {
|
|
427
|
+
...detail,
|
|
436
428
|
...(code?{code}:{}),
|
|
437
429
|
reason,
|
|
438
430
|
status
|
|
439
|
-
}
|
|
431
|
+
};
|
|
440
432
|
}
|
|
441
433
|
|
|
442
434
|
#publish(operation,type,detail,{code=null,reason,status,cancelable=false}={}){
|
|
443
435
|
if(this.#events.disposed)return null;
|
|
444
|
-
const compatibilityDetail=
|
|
436
|
+
const compatibilityDetail={
|
|
445
437
|
...detail,
|
|
446
438
|
mode:typeof detail?.mode==='string'?detail.mode:operation.mode,
|
|
447
439
|
operationId:operation.id,
|
|
448
440
|
reason,
|
|
449
441
|
...(code?{code}:{})
|
|
450
|
-
}
|
|
442
|
+
};
|
|
451
443
|
return this.#events.dispatch(
|
|
452
444
|
EVENT_NAMES[type],
|
|
453
445
|
compatibilityDetail,
|
|
@@ -465,6 +457,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
465
457
|
const controller=new AbortController();
|
|
466
458
|
const operation={
|
|
467
459
|
abortPublishesError:true,
|
|
460
|
+
cancelFrameSampling:null,
|
|
468
461
|
cleanup:[],
|
|
469
462
|
completed:false,
|
|
470
463
|
controller,
|
|
@@ -493,7 +486,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
493
486
|
const publication=this.#publish(
|
|
494
487
|
operation,
|
|
495
488
|
'requesting',
|
|
496
|
-
|
|
489
|
+
{mode},
|
|
497
490
|
{
|
|
498
491
|
reason:SCREEN_CAPTURE_REASONS.displaySelectionRequested,
|
|
499
492
|
status:SCREEN_CAPTURE_STATUSES.selectingDisplay,
|
|
@@ -577,6 +570,15 @@ export default class ScreenCapture extends EventTarget{
|
|
|
577
570
|
for(const remove of operation.recorderCleanup.splice(0))remove();
|
|
578
571
|
}
|
|
579
572
|
|
|
573
|
+
#cancelFrameSampling(operation){
|
|
574
|
+
let error=null;
|
|
575
|
+
try{operation.cancelFrameSampling?.();}catch(cause){error=cause;}
|
|
576
|
+
operation.cancelFrameSampling=null;
|
|
577
|
+
operation.timer=0;
|
|
578
|
+
this.timer=0;
|
|
579
|
+
return error;
|
|
580
|
+
}
|
|
581
|
+
|
|
580
582
|
#settleRecorder(operation,outcome){
|
|
581
583
|
if(!operation.resolveRecorderOutcome)return false;
|
|
582
584
|
const resolve=operation.resolveRecorderOutcome;
|
|
@@ -589,10 +591,8 @@ export default class ScreenCapture extends EventTarget{
|
|
|
589
591
|
#cleanupResources(operation,{settleRecorderError=null}={}){
|
|
590
592
|
let firstError=null;
|
|
591
593
|
function remember(error){firstError??=error;}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
operation.timer=0;
|
|
595
|
-
this.timer=0;
|
|
594
|
+
const frameSamplingError=this.#cancelFrameSampling(operation);
|
|
595
|
+
if(frameSamplingError)remember(frameSamplingError);
|
|
596
596
|
if(settleRecorderError)this.#settleRecorder(operation,{error:settleRecorderError});
|
|
597
597
|
const recorder=operation.recorder??this.recorder;
|
|
598
598
|
if(recorder&&recorder.state!=='inactive'){
|
|
@@ -633,7 +633,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
633
633
|
this.#publish(
|
|
634
634
|
operation,
|
|
635
635
|
'error',
|
|
636
|
-
|
|
636
|
+
{error,mode:operation.mode},
|
|
637
637
|
{
|
|
638
638
|
code:error.code,
|
|
639
639
|
reason:error.reason,
|
|
@@ -649,7 +649,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
649
649
|
this.#publish(
|
|
650
650
|
operation,
|
|
651
651
|
'stop',
|
|
652
|
-
|
|
652
|
+
{mode:operation.mode},
|
|
653
653
|
{code,reason,status:SCREEN_CAPTURE_STATUSES.captureStopped}
|
|
654
654
|
);
|
|
655
655
|
return true;
|
|
@@ -761,7 +761,6 @@ export default class ScreenCapture extends EventTarget{
|
|
|
761
761
|
stage='display-preparation';
|
|
762
762
|
const prepared=await this.prepare(
|
|
763
763
|
stream,
|
|
764
|
-
normalizedWidth(options.maxWidth,1920),
|
|
765
764
|
{signal:operation.controller.signal}
|
|
766
765
|
);
|
|
767
766
|
this.#setPreparedSurface(operation,prepared);
|
|
@@ -833,7 +832,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
833
832
|
this.stopPromise=operation.stopPromise;
|
|
834
833
|
const owner=this;
|
|
835
834
|
function receiveRecordedData(event){
|
|
836
|
-
if(event.data
|
|
835
|
+
if(event.data)chunks.push(event.data);
|
|
837
836
|
}
|
|
838
837
|
function receiveRecorderStop(){
|
|
839
838
|
let outcome;
|
|
@@ -844,7 +843,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
844
843
|
blob:new Blob(chunks,{type}),
|
|
845
844
|
mimeType:type,
|
|
846
845
|
extension:type.includes('mp4')?'mp4':'webm',
|
|
847
|
-
duration:
|
|
846
|
+
duration:Date.now()-operation.startedAt
|
|
848
847
|
}
|
|
849
848
|
};
|
|
850
849
|
}catch(cause){
|
|
@@ -956,7 +955,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
956
955
|
this.#publish(
|
|
957
956
|
operation,
|
|
958
957
|
'start',
|
|
959
|
-
|
|
958
|
+
{mode:'video'},
|
|
960
959
|
{
|
|
961
960
|
reason:SCREEN_CAPTURE_REASONS.videoRecordingStarted,
|
|
962
961
|
status:SCREEN_CAPTURE_STATUSES.recording
|
|
@@ -970,7 +969,7 @@ export default class ScreenCapture extends EventTarget{
|
|
|
970
969
|
}
|
|
971
970
|
}
|
|
972
971
|
|
|
973
|
-
#sampleGifFrame(operation,context){
|
|
972
|
+
#sampleGifFrame(operation,context,capturedAt=monotonicNow()){
|
|
974
973
|
context.drawImage(
|
|
975
974
|
operation.video,
|
|
976
975
|
0,
|
|
@@ -984,9 +983,61 @@ export default class ScreenCapture extends EventTarget{
|
|
|
984
983
|
operation.canvas.width,
|
|
985
984
|
operation.canvas.height
|
|
986
985
|
));
|
|
986
|
+
operation.frameTimes.push(capturedAt);
|
|
987
987
|
this.frames=operation.frames;
|
|
988
988
|
}
|
|
989
989
|
|
|
990
|
+
#startGifFrameSampling(operation,context,frameDelay){
|
|
991
|
+
const requestedDelay=Number(frameDelay);
|
|
992
|
+
const explicitDelay=Number.isFinite(requestedDelay)&&requestedDelay>0
|
|
993
|
+
?requestedDelay
|
|
994
|
+
:null;
|
|
995
|
+
let active=true;
|
|
996
|
+
let frameHandle=0;
|
|
997
|
+
let requestFrame;
|
|
998
|
+
let cancelFrame;
|
|
999
|
+
if(explicitDelay!==null){
|
|
1000
|
+
requestFrame=callback=>setTimeout(()=>callback(monotonicNow()),explicitDelay);
|
|
1001
|
+
cancelFrame=handle=>clearTimeout(handle);
|
|
1002
|
+
}else if(typeof operation.video?.requestVideoFrameCallback==='function'){
|
|
1003
|
+
requestFrame=callback=>operation.video.requestVideoFrameCallback(callback);
|
|
1004
|
+
cancelFrame=handle=>operation.video.cancelVideoFrameCallback?.(handle);
|
|
1005
|
+
}else if(typeof globalThis.requestAnimationFrame==='function'){
|
|
1006
|
+
requestFrame=callback=>globalThis.requestAnimationFrame(callback);
|
|
1007
|
+
cancelFrame=handle=>globalThis.cancelAnimationFrame?.(handle);
|
|
1008
|
+
}else{
|
|
1009
|
+
throw captureError(
|
|
1010
|
+
SCREEN_CAPTURE_ERRORS.gifFrameReadRejected,
|
|
1011
|
+
'The browser does not expose a display-frame callback for GIF capture.'
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
const owner=this;
|
|
1015
|
+
function schedule(){
|
|
1016
|
+
if(!active||!owner.#isCurrent(operation)||operation.phase!=='recording')return;
|
|
1017
|
+
frameHandle=requestFrame(sampleDisplayFrame);
|
|
1018
|
+
operation.timer=frameHandle;
|
|
1019
|
+
owner.timer=frameHandle;
|
|
1020
|
+
}
|
|
1021
|
+
function sampleDisplayFrame(now){
|
|
1022
|
+
if(!active||!owner.#isCurrent(operation)||operation.phase!=='recording')return;
|
|
1023
|
+
try{owner.#sampleGifFrame(operation,context,now);}
|
|
1024
|
+
catch(cause){
|
|
1025
|
+
owner.#failOperation(
|
|
1026
|
+
operation,
|
|
1027
|
+
owner.#operationError(cause,operation,'gif-frame-read')
|
|
1028
|
+
);
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
schedule();
|
|
1032
|
+
}
|
|
1033
|
+
operation.cancelFrameSampling=function cancelGifFrameSampling(){
|
|
1034
|
+
if(!active)return;
|
|
1035
|
+
active=false;
|
|
1036
|
+
cancelFrame(frameHandle);
|
|
1037
|
+
};
|
|
1038
|
+
schedule();
|
|
1039
|
+
}
|
|
1040
|
+
|
|
990
1041
|
async startGif(optionsValue={}){
|
|
991
1042
|
const options=optionsRecord(optionsValue,'GIF capture options');
|
|
992
1043
|
const operation=this.#beginOperation('gif',{
|
|
@@ -1000,7 +1051,6 @@ export default class ScreenCapture extends EventTarget{
|
|
|
1000
1051
|
stage='display-preparation';
|
|
1001
1052
|
const prepared=await this.prepare(
|
|
1002
1053
|
stream,
|
|
1003
|
-
normalizedWidth(options.maxWidth,640),
|
|
1004
1054
|
{signal:operation.controller.signal}
|
|
1005
1055
|
);
|
|
1006
1056
|
this.#setPreparedSurface(operation,prepared);
|
|
@@ -1012,7 +1062,11 @@ export default class ScreenCapture extends EventTarget{
|
|
|
1012
1062
|
);
|
|
1013
1063
|
}
|
|
1014
1064
|
operation.frames=[];
|
|
1015
|
-
operation.
|
|
1065
|
+
operation.frameTimes=[];
|
|
1066
|
+
const requestedDelay=Number(options.frameDelay);
|
|
1067
|
+
operation.gifDelay=Number.isFinite(requestedDelay)&&requestedDelay>0
|
|
1068
|
+
?requestedDelay
|
|
1069
|
+
:0;
|
|
1016
1070
|
operation.startedAt=Date.now();
|
|
1017
1071
|
this.frames=operation.frames;
|
|
1018
1072
|
this.gifDelay=operation.gifDelay;
|
|
@@ -1020,23 +1074,11 @@ export default class ScreenCapture extends EventTarget{
|
|
|
1020
1074
|
stage='gif-frame-read';
|
|
1021
1075
|
this.#sampleGifFrame(operation,context);
|
|
1022
1076
|
operation.phase='recording';
|
|
1023
|
-
|
|
1024
|
-
function sampleScreenCaptureGifFrame(){
|
|
1025
|
-
if(!owner.#isCurrent(operation)||operation.phase!=='recording')return;
|
|
1026
|
-
try{owner.#sampleGifFrame(operation,context);}
|
|
1027
|
-
catch(cause){
|
|
1028
|
-
owner.#failOperation(
|
|
1029
|
-
operation,
|
|
1030
|
-
owner.#operationError(cause,operation,'gif-frame-read')
|
|
1031
|
-
);
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
operation.timer=setInterval(sampleScreenCaptureGifFrame,operation.gifDelay);
|
|
1035
|
-
this.timer=operation.timer;
|
|
1077
|
+
this.#startGifFrameSampling(operation,context,options.frameDelay);
|
|
1036
1078
|
this.#publish(
|
|
1037
1079
|
operation,
|
|
1038
1080
|
'start',
|
|
1039
|
-
|
|
1081
|
+
{mode:'gif'},
|
|
1040
1082
|
{
|
|
1041
1083
|
reason:SCREEN_CAPTURE_REASONS.gifRecordingStarted,
|
|
1042
1084
|
status:SCREEN_CAPTURE_STATUSES.recording
|
|
@@ -1095,18 +1137,23 @@ export default class ScreenCapture extends EventTarget{
|
|
|
1095
1137
|
);
|
|
1096
1138
|
}
|
|
1097
1139
|
}else{
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1140
|
+
const frameSamplingError=this.#cancelFrameSampling(operation);
|
|
1141
|
+
if(frameSamplingError)throw frameSamplingError;
|
|
1142
|
+
const stoppedAt=monotonicNow();
|
|
1101
1143
|
const encoder=new GifEncoder(operation.canvas.width,operation.canvas.height);
|
|
1102
|
-
for(
|
|
1103
|
-
|
|
1144
|
+
for(let index=0;index<operation.frames.length;index++){
|
|
1145
|
+
const capturedAt=operation.frameTimes[index];
|
|
1146
|
+
const nextCapturedAt=operation.frameTimes[index+1]??stoppedAt;
|
|
1147
|
+
encoder.addFrame(
|
|
1148
|
+
operation.frames[index],
|
|
1149
|
+
{delay:Math.max(0,nextCapturedAt-capturedAt)}
|
|
1150
|
+
);
|
|
1104
1151
|
}
|
|
1105
1152
|
result={
|
|
1106
1153
|
blob:encoder.encode(),
|
|
1107
1154
|
mimeType:'image/gif',
|
|
1108
1155
|
extension:'gif',
|
|
1109
|
-
duration:
|
|
1156
|
+
duration:Date.now()-operation.startedAt,
|
|
1110
1157
|
width:operation.canvas.width,
|
|
1111
1158
|
height:operation.canvas.height
|
|
1112
1159
|
};
|
|
@@ -1150,9 +1197,11 @@ export default class ScreenCapture extends EventTarget{
|
|
|
1150
1197
|
return operation.stopCallPromise;
|
|
1151
1198
|
}
|
|
1152
1199
|
|
|
1153
|
-
async prepare(stream,
|
|
1200
|
+
async prepare(stream,optionsValue={},legacyOptionsValue={}){
|
|
1154
1201
|
this.#assertOpen();
|
|
1155
|
-
const options=
|
|
1202
|
+
const options=typeof optionsValue==='number'
|
|
1203
|
+
?optionsRecord(legacyOptionsValue,'Screen capture display options')
|
|
1204
|
+
:optionsRecord(optionsValue,'Screen capture display options');
|
|
1156
1205
|
if(options.signal?.aborted)throw abortError(options.signal.reason);
|
|
1157
1206
|
let video;
|
|
1158
1207
|
try{
|
|
@@ -1219,10 +1268,9 @@ export default class ScreenCapture extends EventTarget{
|
|
|
1219
1268
|
'The selected display reported invalid frame dimensions.'
|
|
1220
1269
|
);
|
|
1221
1270
|
}
|
|
1222
|
-
const scale=Math.min(1,normalizedWidth(maxWidth,1920)/width);
|
|
1223
1271
|
const canvas=this.document.createElement('canvas');
|
|
1224
|
-
canvas.width=
|
|
1225
|
-
canvas.height=
|
|
1272
|
+
canvas.width=width;
|
|
1273
|
+
canvas.height=height;
|
|
1226
1274
|
return {video,canvas};
|
|
1227
1275
|
}catch(error){
|
|
1228
1276
|
let cleanupFailure=null;
|
|
@@ -1378,7 +1426,6 @@ export default class ScreenCapture extends EventTarget{
|
|
|
1378
1426
|
cleanupFailure=operation.terminalError?.cleanupCause??null;
|
|
1379
1427
|
}else{
|
|
1380
1428
|
try{this.stopTracks(this.stream);}catch(error){cleanupFailure=error;}
|
|
1381
|
-
clearInterval(this.timer);
|
|
1382
1429
|
}
|
|
1383
1430
|
for(const acquisition of this.#acquisitions){
|
|
1384
1431
|
if(!acquisition.controller.signal.aborted){
|