@elizaos/capacitor-screencapture 1.0.0 → 2.0.3-beta.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/LICENSE +21 -0
- package/README.md +120 -0
- package/android/build.gradle +17 -3
- package/dist/esm/web.d.ts.map +1 -1
- package/dist/esm/web.js +54 -9
- package/dist/esm/web.test.d.ts +2 -0
- package/dist/esm/web.test.d.ts.map +1 -0
- package/dist/esm/web.test.js +313 -0
- package/dist/plugin.cjs.js +54 -9
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +54 -9
- package/dist/plugin.js.map +1 -1
- package/package.json +16 -15
- package/electrobun/src/index.ts +0 -102
- package/electrobun/tsconfig.json +0 -18
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shaw Walters and elizaOS Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @elizaos/capacitor-screencapture
|
|
2
|
+
|
|
3
|
+
Cross-platform screen capture Capacitor plugin for elizaOS. Captures screenshots and records the screen on browser (Web API), iOS (ReplayKit + AVFoundation), and Android (MediaProjection).
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- **Screenshot:** Captures a single frame of the screen as a base64-encoded PNG, JPEG, or WebP image.
|
|
8
|
+
- **Screen recording:** Records the screen to a video file (WebM on browser, MP4 on native), with optional audio from the system, microphone, or both.
|
|
9
|
+
- **Pause / resume:** Pause and resume an active recording without creating a new file (Android requires API 24+).
|
|
10
|
+
- **Permission checks:** Query and request screen-capture and microphone permissions in a unified cross-platform API.
|
|
11
|
+
- **Live events:** Subscribe to `recordingState` and `error` events emitted during recording.
|
|
12
|
+
|
|
13
|
+
## Platform support
|
|
14
|
+
|
|
15
|
+
| Feature | Browser | iOS | Android |
|
|
16
|
+
|---------|---------|-----|---------|
|
|
17
|
+
| Screenshot | Yes (getDisplayMedia) | Yes (UIKit) | Yes (MediaProjection) |
|
|
18
|
+
| Screen recording | Yes (MediaRecorder) | Yes (ReplayKit) | Yes (MediaRecorder) |
|
|
19
|
+
| Pause/resume | Yes | Yes | API 24+ only |
|
|
20
|
+
| System audio | Browser-dependent | Yes (RPSampleBufferType.audioApp) | No (microphone only) |
|
|
21
|
+
| Microphone audio | Yes | Yes | Yes |
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @elizaos/capacitor-screencapture
|
|
27
|
+
npx cap sync
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
For iOS, add to your app's `Info.plist`:
|
|
31
|
+
```xml
|
|
32
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
33
|
+
<string>Microphone is used to capture audio during screen recording.</string>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For Android 14+ (API 34), declare in `AndroidManifest.xml`:
|
|
37
|
+
```xml
|
|
38
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
|
|
39
|
+
```
|
|
40
|
+
This is in addition to the `FOREGROUND_SERVICE` permission that this plugin's own `AndroidManifest.xml` already declares.
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { ScreenCapture } from '@elizaos/capacitor-screencapture';
|
|
46
|
+
|
|
47
|
+
// Check support
|
|
48
|
+
const { supported, features } = await ScreenCapture.isSupported();
|
|
49
|
+
|
|
50
|
+
// Take a screenshot
|
|
51
|
+
const shot = await ScreenCapture.captureScreenshot({ format: 'png', quality: 100 });
|
|
52
|
+
// shot.base64 contains the image data
|
|
53
|
+
|
|
54
|
+
// Record the screen
|
|
55
|
+
await ScreenCapture.startRecording({
|
|
56
|
+
quality: 'high', // 'low' | 'medium' | 'high' | 'highest'
|
|
57
|
+
fps: 30,
|
|
58
|
+
captureSystemAudio: true,
|
|
59
|
+
captureMicrophone: false,
|
|
60
|
+
maxDuration: 300, // seconds; undefined = unlimited
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Listen for state updates
|
|
64
|
+
const handle = await ScreenCapture.addListener('recordingState', (state) => {
|
|
65
|
+
console.log(`Recording: ${state.isRecording}, duration: ${state.duration}s`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Stop and get the result
|
|
69
|
+
const result = await ScreenCapture.stopRecording();
|
|
70
|
+
// result.path — blob: URL (browser) or filesystem path (native)
|
|
71
|
+
// result.mimeType — video/webm (browser) or video/mp4 (native)
|
|
72
|
+
|
|
73
|
+
await handle.remove();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Permissions
|
|
77
|
+
|
|
78
|
+
Screen capture permission works differently per platform:
|
|
79
|
+
|
|
80
|
+
- **Browser:** `getDisplayMedia` always shows an OS-level picker dialog. There is no way to pre-grant or query this permission. `checkPermissions()` returns `"prompt"` when the API is available.
|
|
81
|
+
- **iOS:** Screenshots use UIKit only — no permission required. `startRecording` uses ReplayKit, which shows a system broadcast picker on first use.
|
|
82
|
+
- **Android:** `captureScreenshot` and `startRecording` both trigger a `MediaProjection` consent dialog. Microphone permission (`RECORD_AUDIO`) is requested at runtime when `captureMicrophone: true`.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Check permissions
|
|
86
|
+
const status = await ScreenCapture.checkPermissions();
|
|
87
|
+
// status.screenCapture: 'granted' | 'denied' | 'prompt' | 'not_supported'
|
|
88
|
+
// status.microphone: 'granted' | 'denied' | 'prompt'
|
|
89
|
+
|
|
90
|
+
// Request microphone permission before recording with audio
|
|
91
|
+
await ScreenCapture.requestPermissions();
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Output formats
|
|
95
|
+
|
|
96
|
+
| Platform | Screenshot | Recording |
|
|
97
|
+
|----------|-----------|-----------|
|
|
98
|
+
| Browser | PNG / JPEG / WebP | video/webm (VP9 preferred), blob: URL |
|
|
99
|
+
| iOS | PNG / JPEG / WebP (WebP requires iOS 14+) | video/mp4 (H.264 + AAC), file:// path |
|
|
100
|
+
| Android | PNG / JPEG / WebP (WebP lossy requires API 30+) | video/mp4 (H.264), file path |
|
|
101
|
+
|
|
102
|
+
## Recording options
|
|
103
|
+
|
|
104
|
+
| Option | Type | Default | Description |
|
|
105
|
+
|--------|------|---------|-------------|
|
|
106
|
+
| `quality` | `'low' \| 'medium' \| 'high' \| 'highest'` | `'high'` | Quality preset (sets bitrate on iOS; sets fps + bitrate on Android) |
|
|
107
|
+
| `fps` | `number` | 30 | Frames per second (1–60; overrides quality preset) |
|
|
108
|
+
| `bitrate` | `number` | Estimated | Video bitrate in bits/s (overrides quality preset) |
|
|
109
|
+
| `maxDuration` | `number` | unlimited | Stop automatically after N seconds |
|
|
110
|
+
| `maxFileSize` | `number` | unlimited | Stop automatically after N bytes |
|
|
111
|
+
| `captureSystemAudio` | `boolean` | `true` | Include app/system audio (browser + iOS; Android records microphone only) |
|
|
112
|
+
| `captureMicrophone` | `boolean` | `false` | Include microphone audio in recording |
|
|
113
|
+
|
|
114
|
+
## Building from source
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
bun run --cwd plugins/plugin-native-screencapture build
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This runs `tsc` then `rollup` and outputs `dist/esm/`, `dist/plugin.js` (IIFE), and `dist/plugin.cjs.js`.
|
package/android/build.gradle
CHANGED
|
@@ -7,6 +7,16 @@ ext {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
apply plugin: 'com.android.library'
|
|
10
|
+
// Explicitly apply the Kotlin Android plugin. The kotlin-gradle-plugin is on
|
|
11
|
+
// the root buildscript classpath, but without applying it here AGP 8.13 falls
|
|
12
|
+
// back to its "built-in Kotlin" compile path (build/intermediates/
|
|
13
|
+
// built_in_kotlinc), which compiles the .kt sources but does NOT bundle the
|
|
14
|
+
// resulting .class files into the *release* library jar. The app's
|
|
15
|
+
// :app:assembleRelease then links a library AAR with zero plugin classes, so
|
|
16
|
+
// the Capacitor plugin (and any manifest-declared component) is absent from
|
|
17
|
+
// the release dex. Applying the standard Kotlin plugin wires Kotlin
|
|
18
|
+
// compilation into both the debug and release jar-bundling tasks.
|
|
19
|
+
apply plugin: 'org.jetbrains.kotlin.android'
|
|
10
20
|
android {
|
|
11
21
|
namespace = "ai.eliza.plugins.screencapture"
|
|
12
22
|
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
|
|
@@ -25,8 +35,12 @@ android {
|
|
|
25
35
|
}
|
|
26
36
|
|
|
27
37
|
compileOptions {
|
|
28
|
-
sourceCompatibility JavaVersion.
|
|
29
|
-
targetCompatibility JavaVersion.
|
|
38
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
39
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
kotlinOptions {
|
|
43
|
+
jvmTarget = "21"
|
|
30
44
|
}
|
|
31
45
|
|
|
32
46
|
}
|
|
@@ -34,7 +48,7 @@ android {
|
|
|
34
48
|
repositories {
|
|
35
49
|
google()
|
|
36
50
|
maven {
|
|
37
|
-
url = uri(rootProject.ext.mavenCentralMirrorUrl)
|
|
51
|
+
url = uri(rootProject.ext.has('mavenCentralMirrorUrl') ? rootProject.ext.mavenCentralMirrorUrl : 'https://repo.maven.apache.org/maven2')
|
|
38
52
|
}
|
|
39
53
|
mavenCentral()
|
|
40
54
|
}
|
package/dist/esm/web.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,KAAK,EACV,uBAAuB,EACvB,6BAA6B,EAC7B,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAEvB,KAAK,sBAAsB,GAAG,oBAAoB,GAAG,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,KAAK,EACV,uBAAuB,EACvB,6BAA6B,EAC7B,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAEvB,KAAK,sBAAsB,GAAG,oBAAoB,GAAG,uBAAuB,CAAC;AAiD7E,qBAAa,gBAAiB,SAAQ,SAAS;IAC7C,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,sBAAsB,CAA+C;IAC7E,OAAO,CAAC,eAAe,CAGf;IAEF,WAAW,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IASlE,iBAAiB,CACrB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,gBAAgB,CAAC;IAgEtB,cAAc,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+G/D,aAAa,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAuE/C,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B/B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAchC,iBAAiB,IAAI,OAAO,CAAC,oBAAoB,CAAC;IAgBxD;;;;;;;;;;OAUG;IACG,gBAAgB,IAAI,OAAO,CAAC,6BAA6B,CAAC;IAiBhE;;;;;;;;;;OAUG;IACG,kBAAkB,IAAI,OAAO,CAAC,6BAA6B,CAAC;IAkB5D,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,GACpD,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAWrC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzC,SAAS,CAAC,eAAe,CACvB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,sBAAsB,GAC3B,IAAI;CAOR"}
|
package/dist/esm/web.js
CHANGED
|
@@ -5,8 +5,28 @@ const VIDEO_MIME_TYPES = [
|
|
|
5
5
|
"video/webm",
|
|
6
6
|
"video/mp4",
|
|
7
7
|
];
|
|
8
|
-
const getSupportedMimeType = () =>
|
|
9
|
-
|
|
8
|
+
const getSupportedMimeType = () => typeof MediaRecorder === "undefined"
|
|
9
|
+
? null
|
|
10
|
+
: (VIDEO_MIME_TYPES.find((m) => MediaRecorder.isTypeSupported(m)) ?? null);
|
|
11
|
+
const hasDisplayMedia = () => !!navigator.mediaDevices
|
|
12
|
+
?.getDisplayMedia;
|
|
13
|
+
function assertPositiveFiniteNumber(value, label) {
|
|
14
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
15
|
+
throw new Error(`${label} must be a positive finite number`);
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function assertQuality(value) {
|
|
20
|
+
if (value === undefined)
|
|
21
|
+
return 1;
|
|
22
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
23
|
+
throw new Error("quality must be a finite number between 0 and 100");
|
|
24
|
+
}
|
|
25
|
+
if (value < 0 || value > 100) {
|
|
26
|
+
throw new Error("quality must be between 0 and 100");
|
|
27
|
+
}
|
|
28
|
+
return value / 100;
|
|
29
|
+
}
|
|
10
30
|
const getDisplayMedia = (opts) => navigator.mediaDevices.getDisplayMedia(opts);
|
|
11
31
|
export class ScreenCaptureWeb extends WebPlugin {
|
|
12
32
|
constructor() {
|
|
@@ -35,8 +55,16 @@ export class ScreenCaptureWeb extends WebPlugin {
|
|
|
35
55
|
}
|
|
36
56
|
async captureScreenshot(options) {
|
|
37
57
|
const format = options?.format || "png";
|
|
38
|
-
const quality = (options?.quality
|
|
39
|
-
const scale = options?.scale
|
|
58
|
+
const quality = assertQuality(options?.quality);
|
|
59
|
+
const scale = options?.scale === undefined
|
|
60
|
+
? 1
|
|
61
|
+
: assertPositiveFiniteNumber(options.scale, "scale");
|
|
62
|
+
// PERMISSIONS_MIGRATION: getDisplayMedia() triggers the OS screen
|
|
63
|
+
// recording / picker dialog implicitly. New flow probes via
|
|
64
|
+
// `screenRecordingProber` in
|
|
65
|
+
// `packages/agent/src/services/permissions/probers/screen-recording.ts`
|
|
66
|
+
// before opening the stream. Will be retired by the chat-surface
|
|
67
|
+
// migration agent.
|
|
40
68
|
const stream = await getDisplayMedia({
|
|
41
69
|
video: { displaySurface: "monitor" },
|
|
42
70
|
audio: false,
|
|
@@ -45,11 +73,16 @@ export class ScreenCaptureWeb extends WebPlugin {
|
|
|
45
73
|
const settings = track.getSettings();
|
|
46
74
|
const width = (settings.width || 1920) * scale;
|
|
47
75
|
const height = (settings.height || 1080) * scale;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
76
|
+
let bitmap = null;
|
|
77
|
+
try {
|
|
78
|
+
const imageCapture = new ImageCapture(track);
|
|
79
|
+
bitmap = await imageCapture.grabFrame();
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
stream.getTracks().forEach((t) => {
|
|
83
|
+
t.stop();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
53
86
|
const canvas = document.createElement("canvas");
|
|
54
87
|
canvas.width = width;
|
|
55
88
|
canvas.height = height;
|
|
@@ -77,6 +110,18 @@ export class ScreenCaptureWeb extends WebPlugin {
|
|
|
77
110
|
async startRecording(options) {
|
|
78
111
|
if (this.isRecording)
|
|
79
112
|
throw new Error("Recording already in progress");
|
|
113
|
+
if (options?.fps !== undefined) {
|
|
114
|
+
assertPositiveFiniteNumber(options.fps, "fps");
|
|
115
|
+
}
|
|
116
|
+
if (options?.bitrate !== undefined) {
|
|
117
|
+
assertPositiveFiniteNumber(options.bitrate, "bitrate");
|
|
118
|
+
}
|
|
119
|
+
if (options?.maxDuration !== undefined) {
|
|
120
|
+
assertPositiveFiniteNumber(options.maxDuration, "maxDuration");
|
|
121
|
+
}
|
|
122
|
+
if (options?.maxFileSize !== undefined) {
|
|
123
|
+
assertPositiveFiniteNumber(options.maxFileSize, "maxFileSize");
|
|
124
|
+
}
|
|
80
125
|
const videoConstraints = {
|
|
81
126
|
displaySurface: "monitor",
|
|
82
127
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.test.d.ts","sourceRoot":"","sources":["../../src/web.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { ScreenCaptureWeb } from "./web";
|
|
3
|
+
class FakeTrack {
|
|
4
|
+
constructor(kind, settings = {}) {
|
|
5
|
+
this.kind = kind;
|
|
6
|
+
this.settings = settings;
|
|
7
|
+
this.stopped = false;
|
|
8
|
+
this.listeners = new Map();
|
|
9
|
+
}
|
|
10
|
+
getSettings() {
|
|
11
|
+
return this.settings;
|
|
12
|
+
}
|
|
13
|
+
stop() {
|
|
14
|
+
this.stopped = true;
|
|
15
|
+
}
|
|
16
|
+
addEventListener(eventName, callback) {
|
|
17
|
+
const listeners = this.listeners.get(eventName) ?? [];
|
|
18
|
+
listeners.push(callback);
|
|
19
|
+
this.listeners.set(eventName, listeners);
|
|
20
|
+
}
|
|
21
|
+
dispatchEnded() {
|
|
22
|
+
this.listeners.get("ended")?.forEach((callback) => {
|
|
23
|
+
callback(new Event("ended"));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
class FakeStream {
|
|
28
|
+
constructor(tracks) {
|
|
29
|
+
this.tracks = tracks;
|
|
30
|
+
}
|
|
31
|
+
getTracks() {
|
|
32
|
+
return this.tracks;
|
|
33
|
+
}
|
|
34
|
+
getVideoTracks() {
|
|
35
|
+
return this.tracks.filter((track) => track.kind === "video");
|
|
36
|
+
}
|
|
37
|
+
getAudioTracks() {
|
|
38
|
+
return this.tracks.filter((track) => track.kind === "audio");
|
|
39
|
+
}
|
|
40
|
+
addTrack(track) {
|
|
41
|
+
this.tracks.push(track);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
class FakeMediaRecorder {
|
|
45
|
+
static isTypeSupported(mimeType) {
|
|
46
|
+
return FakeMediaRecorder.supported && mimeType === "video/webm";
|
|
47
|
+
}
|
|
48
|
+
constructor(stream, options) {
|
|
49
|
+
this.stream = stream;
|
|
50
|
+
this.options = options;
|
|
51
|
+
this.ondataavailable = null;
|
|
52
|
+
this.onerror = null;
|
|
53
|
+
this.onstop = null;
|
|
54
|
+
this.start = vi.fn((_timeslice) => { });
|
|
55
|
+
this.pause = vi.fn();
|
|
56
|
+
this.resume = vi.fn();
|
|
57
|
+
this.stop = vi.fn(() => {
|
|
58
|
+
queueMicrotask(() => {
|
|
59
|
+
this.onstop?.(new Event("stop"));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
this.mimeType = options.mimeType ?? "";
|
|
63
|
+
FakeMediaRecorder.instances.push(this);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
FakeMediaRecorder.supported = true;
|
|
67
|
+
FakeMediaRecorder.instances = [];
|
|
68
|
+
function setNavigator(value) {
|
|
69
|
+
Object.defineProperty(globalThis, "navigator", {
|
|
70
|
+
configurable: true,
|
|
71
|
+
value,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function installDocument() {
|
|
75
|
+
const context = { drawImage: vi.fn() };
|
|
76
|
+
const canvas = {
|
|
77
|
+
width: 0,
|
|
78
|
+
height: 0,
|
|
79
|
+
getContext: vi.fn(() => context),
|
|
80
|
+
toDataURL: vi.fn(() => "data:image/webp;base64,c2NyZWVu"),
|
|
81
|
+
};
|
|
82
|
+
const createElement = vi.fn((tagName) => {
|
|
83
|
+
if (tagName === "canvas")
|
|
84
|
+
return canvas;
|
|
85
|
+
if (tagName === "video") {
|
|
86
|
+
return {
|
|
87
|
+
videoWidth: 1280,
|
|
88
|
+
videoHeight: 720,
|
|
89
|
+
set src(_value) { },
|
|
90
|
+
set onloadedmetadata(callback) {
|
|
91
|
+
queueMicrotask(callback);
|
|
92
|
+
},
|
|
93
|
+
set onerror(_callback) { },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`unexpected element: ${tagName}`);
|
|
97
|
+
});
|
|
98
|
+
vi.stubGlobal("document", { createElement });
|
|
99
|
+
vi.stubGlobal("URL", {
|
|
100
|
+
createObjectURL: vi.fn(() => "blob:screen-recording"),
|
|
101
|
+
});
|
|
102
|
+
return { canvas, context, createElement };
|
|
103
|
+
}
|
|
104
|
+
describe("ScreenCaptureWeb", () => {
|
|
105
|
+
afterEach(() => {
|
|
106
|
+
vi.useRealTimers();
|
|
107
|
+
vi.restoreAllMocks();
|
|
108
|
+
vi.unstubAllGlobals();
|
|
109
|
+
FakeMediaRecorder.supported = true;
|
|
110
|
+
FakeMediaRecorder.instances = [];
|
|
111
|
+
});
|
|
112
|
+
it("reports unsupported cleanly when optional browser capture APIs are absent", async () => {
|
|
113
|
+
setNavigator({});
|
|
114
|
+
vi.stubGlobal("MediaRecorder", undefined);
|
|
115
|
+
vi.stubGlobal("AudioContext", undefined);
|
|
116
|
+
const plugin = new ScreenCaptureWeb();
|
|
117
|
+
await expect(plugin.isSupported()).resolves.toEqual({
|
|
118
|
+
supported: false,
|
|
119
|
+
features: [],
|
|
120
|
+
});
|
|
121
|
+
await expect(plugin.checkPermissions()).resolves.toEqual({
|
|
122
|
+
screenCapture: "not_supported",
|
|
123
|
+
microphone: "prompt",
|
|
124
|
+
});
|
|
125
|
+
await expect(plugin.requestPermissions()).resolves.toEqual({
|
|
126
|
+
screenCapture: "not_supported",
|
|
127
|
+
microphone: "denied",
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
it("maps screenshot options into display capture and canvas encoding", async () => {
|
|
131
|
+
const track = new FakeTrack("video", { width: 320, height: 200 });
|
|
132
|
+
const stream = new FakeStream([track]);
|
|
133
|
+
const getDisplayMedia = vi.fn(async () => stream);
|
|
134
|
+
const close = vi.fn();
|
|
135
|
+
const grabFrame = vi.fn(async () => ({ close }));
|
|
136
|
+
const imageCapture = vi.fn(function ImageCapture() {
|
|
137
|
+
Object.assign(this, { grabFrame });
|
|
138
|
+
});
|
|
139
|
+
const { canvas, context } = installDocument();
|
|
140
|
+
setNavigator({
|
|
141
|
+
mediaDevices: { getDisplayMedia },
|
|
142
|
+
});
|
|
143
|
+
vi.stubGlobal("ImageCapture", imageCapture);
|
|
144
|
+
await expect(new ScreenCaptureWeb().captureScreenshot({
|
|
145
|
+
format: "webp",
|
|
146
|
+
quality: 25,
|
|
147
|
+
scale: 2,
|
|
148
|
+
})).resolves.toEqual({
|
|
149
|
+
base64: "c2NyZWVu",
|
|
150
|
+
format: "webp",
|
|
151
|
+
width: 640,
|
|
152
|
+
height: 400,
|
|
153
|
+
timestamp: expect.any(Number),
|
|
154
|
+
});
|
|
155
|
+
expect(getDisplayMedia).toHaveBeenCalledWith({
|
|
156
|
+
video: { displaySurface: "monitor" },
|
|
157
|
+
audio: false,
|
|
158
|
+
});
|
|
159
|
+
expect(canvas.width).toBe(640);
|
|
160
|
+
expect(canvas.height).toBe(400);
|
|
161
|
+
expect(context.drawImage).toHaveBeenCalledWith(expect.anything(), 0, 0, 640, 400);
|
|
162
|
+
expect(canvas.toDataURL).toHaveBeenCalledWith("image/webp", 0.25);
|
|
163
|
+
expect(track.stopped).toBe(true);
|
|
164
|
+
expect(close).toHaveBeenCalled();
|
|
165
|
+
});
|
|
166
|
+
it.each([
|
|
167
|
+
{ quality: -1 },
|
|
168
|
+
{ quality: 101 },
|
|
169
|
+
{ quality: Number.POSITIVE_INFINITY },
|
|
170
|
+
{ scale: 0 },
|
|
171
|
+
{ scale: Number.NaN },
|
|
172
|
+
])("rejects malformed screenshot options %# before requesting capture", async (options) => {
|
|
173
|
+
const getDisplayMedia = vi.fn();
|
|
174
|
+
setNavigator({
|
|
175
|
+
mediaDevices: { getDisplayMedia },
|
|
176
|
+
});
|
|
177
|
+
await expect(new ScreenCaptureWeb().captureScreenshot(options)).rejects.toThrow(/quality|scale/);
|
|
178
|
+
expect(getDisplayMedia).not.toHaveBeenCalled();
|
|
179
|
+
});
|
|
180
|
+
it("stops acquired display tracks when screenshot frame capture fails", async () => {
|
|
181
|
+
const track = new FakeTrack("video", { width: 320, height: 200 });
|
|
182
|
+
const stream = new FakeStream([track]);
|
|
183
|
+
const getDisplayMedia = vi.fn(async () => stream);
|
|
184
|
+
const grabFrame = vi.fn(async () => {
|
|
185
|
+
throw new Error("frame unavailable");
|
|
186
|
+
});
|
|
187
|
+
const imageCapture = vi.fn(function ImageCapture() {
|
|
188
|
+
Object.assign(this, { grabFrame });
|
|
189
|
+
});
|
|
190
|
+
installDocument();
|
|
191
|
+
setNavigator({
|
|
192
|
+
mediaDevices: { getDisplayMedia },
|
|
193
|
+
});
|
|
194
|
+
vi.stubGlobal("ImageCapture", imageCapture);
|
|
195
|
+
await expect(new ScreenCaptureWeb().captureScreenshot()).rejects.toThrow("frame unavailable");
|
|
196
|
+
expect(track.stopped).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
it("stops acquired display tracks when recording cannot be encoded", async () => {
|
|
199
|
+
const videoTrack = new FakeTrack("video");
|
|
200
|
+
const audioTrack = new FakeTrack("audio");
|
|
201
|
+
const stream = new FakeStream([videoTrack, audioTrack]);
|
|
202
|
+
const getDisplayMedia = vi.fn(async () => stream);
|
|
203
|
+
setNavigator({
|
|
204
|
+
mediaDevices: { getDisplayMedia },
|
|
205
|
+
});
|
|
206
|
+
vi.stubGlobal("MediaRecorder", undefined);
|
|
207
|
+
const plugin = new ScreenCaptureWeb();
|
|
208
|
+
await expect(plugin.startRecording()).rejects.toThrow("No supported video mime type found");
|
|
209
|
+
expect(videoTrack.stopped).toBe(true);
|
|
210
|
+
expect(audioTrack.stopped).toBe(true);
|
|
211
|
+
await expect(plugin.getRecordingState()).resolves.toEqual({
|
|
212
|
+
isRecording: false,
|
|
213
|
+
duration: 0,
|
|
214
|
+
fileSize: 0,
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
it.each([
|
|
218
|
+
{ fps: 0 },
|
|
219
|
+
{ fps: Number.NaN },
|
|
220
|
+
{ bitrate: -1 },
|
|
221
|
+
{ maxDuration: Number.POSITIVE_INFINITY },
|
|
222
|
+
{ maxFileSize: 0 },
|
|
223
|
+
])("rejects malformed recording options %# before requesting capture", async (options) => {
|
|
224
|
+
const getDisplayMedia = vi.fn();
|
|
225
|
+
setNavigator({
|
|
226
|
+
mediaDevices: { getDisplayMedia },
|
|
227
|
+
});
|
|
228
|
+
vi.stubGlobal("MediaRecorder", FakeMediaRecorder);
|
|
229
|
+
await expect(new ScreenCaptureWeb().startRecording(options)).rejects.toThrow(/fps|bitrate|maxDuration|maxFileSize/);
|
|
230
|
+
expect(getDisplayMedia).not.toHaveBeenCalled();
|
|
231
|
+
});
|
|
232
|
+
it("runs recording pause, resume, stop, and listener removal without leaking tracks", async () => {
|
|
233
|
+
vi.useFakeTimers();
|
|
234
|
+
vi.setSystemTime(1000);
|
|
235
|
+
installDocument();
|
|
236
|
+
vi.stubGlobal("MediaRecorder", FakeMediaRecorder);
|
|
237
|
+
const videoTrack = new FakeTrack("video");
|
|
238
|
+
const micTrack = new FakeTrack("audio");
|
|
239
|
+
const displayStream = new FakeStream([videoTrack]);
|
|
240
|
+
const micStream = new FakeStream([micTrack]);
|
|
241
|
+
const getDisplayMedia = vi.fn(async () => displayStream);
|
|
242
|
+
const getUserMedia = vi.fn(async () => micStream);
|
|
243
|
+
setNavigator({
|
|
244
|
+
mediaDevices: {
|
|
245
|
+
getDisplayMedia,
|
|
246
|
+
getUserMedia,
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
const plugin = new ScreenCaptureWeb();
|
|
250
|
+
const states = [];
|
|
251
|
+
const removedListener = vi.fn();
|
|
252
|
+
plugin.addListener("recordingState", (event) => {
|
|
253
|
+
states.push(event);
|
|
254
|
+
});
|
|
255
|
+
const handle = await plugin.addListener("recordingState", removedListener);
|
|
256
|
+
await handle.remove();
|
|
257
|
+
await plugin.startRecording({
|
|
258
|
+
captureMicrophone: true,
|
|
259
|
+
fps: 30,
|
|
260
|
+
bitrate: 250000,
|
|
261
|
+
});
|
|
262
|
+
const recorder = FakeMediaRecorder.instances[0];
|
|
263
|
+
expect(getDisplayMedia).toHaveBeenCalledWith({
|
|
264
|
+
video: { displaySurface: "monitor", frameRate: { ideal: 30 } },
|
|
265
|
+
audio: true,
|
|
266
|
+
});
|
|
267
|
+
expect(getUserMedia).toHaveBeenCalledWith({ audio: true });
|
|
268
|
+
expect(recorder.options).toEqual({
|
|
269
|
+
mimeType: "video/webm",
|
|
270
|
+
videoBitsPerSecond: 250000,
|
|
271
|
+
});
|
|
272
|
+
expect(recorder.start).toHaveBeenCalledWith(1000);
|
|
273
|
+
expect(states[0]).toEqual({
|
|
274
|
+
isRecording: true,
|
|
275
|
+
duration: 0,
|
|
276
|
+
fileSize: 0,
|
|
277
|
+
});
|
|
278
|
+
expect(removedListener).not.toHaveBeenCalled();
|
|
279
|
+
recorder.ondataavailable?.({
|
|
280
|
+
data: new Blob(["chunk"]),
|
|
281
|
+
});
|
|
282
|
+
vi.setSystemTime(2500);
|
|
283
|
+
await plugin.pauseRecording();
|
|
284
|
+
await plugin.pauseRecording();
|
|
285
|
+
expect(recorder.pause).toHaveBeenCalledTimes(1);
|
|
286
|
+
vi.setSystemTime(3000);
|
|
287
|
+
await plugin.resumeRecording();
|
|
288
|
+
await plugin.resumeRecording();
|
|
289
|
+
expect(recorder.resume).toHaveBeenCalledTimes(1);
|
|
290
|
+
const stopPromise = plugin.stopRecording();
|
|
291
|
+
expect(recorder.stop).toHaveBeenCalledTimes(1);
|
|
292
|
+
await expect(stopPromise).resolves.toEqual({
|
|
293
|
+
path: "blob:screen-recording",
|
|
294
|
+
duration: 1.5,
|
|
295
|
+
width: 1280,
|
|
296
|
+
height: 720,
|
|
297
|
+
fileSize: 5,
|
|
298
|
+
mimeType: "video/webm",
|
|
299
|
+
});
|
|
300
|
+
expect(videoTrack.stopped).toBe(true);
|
|
301
|
+
expect(micTrack.stopped).toBe(true);
|
|
302
|
+
expect(states[states.length - 1]).toEqual({
|
|
303
|
+
isRecording: false,
|
|
304
|
+
duration: 1.5,
|
|
305
|
+
fileSize: 5,
|
|
306
|
+
});
|
|
307
|
+
await expect(plugin.getRecordingState()).resolves.toEqual({
|
|
308
|
+
isRecording: false,
|
|
309
|
+
duration: 0,
|
|
310
|
+
fileSize: 5,
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
});
|
package/dist/plugin.cjs.js
CHANGED
|
@@ -13,8 +13,28 @@ const VIDEO_MIME_TYPES = [
|
|
|
13
13
|
"video/webm",
|
|
14
14
|
"video/mp4",
|
|
15
15
|
];
|
|
16
|
-
const getSupportedMimeType = () =>
|
|
17
|
-
|
|
16
|
+
const getSupportedMimeType = () => typeof MediaRecorder === "undefined"
|
|
17
|
+
? null
|
|
18
|
+
: (VIDEO_MIME_TYPES.find((m) => MediaRecorder.isTypeSupported(m)) ?? null);
|
|
19
|
+
const hasDisplayMedia = () => !!navigator.mediaDevices
|
|
20
|
+
?.getDisplayMedia;
|
|
21
|
+
function assertPositiveFiniteNumber(value, label) {
|
|
22
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
23
|
+
throw new Error(`${label} must be a positive finite number`);
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
function assertQuality(value) {
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
return 1;
|
|
30
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
31
|
+
throw new Error("quality must be a finite number between 0 and 100");
|
|
32
|
+
}
|
|
33
|
+
if (value < 0 || value > 100) {
|
|
34
|
+
throw new Error("quality must be between 0 and 100");
|
|
35
|
+
}
|
|
36
|
+
return value / 100;
|
|
37
|
+
}
|
|
18
38
|
const getDisplayMedia = (opts) => navigator.mediaDevices.getDisplayMedia(opts);
|
|
19
39
|
class ScreenCaptureWeb extends core.WebPlugin {
|
|
20
40
|
constructor() {
|
|
@@ -43,8 +63,16 @@ class ScreenCaptureWeb extends core.WebPlugin {
|
|
|
43
63
|
}
|
|
44
64
|
async captureScreenshot(options) {
|
|
45
65
|
const format = options?.format || "png";
|
|
46
|
-
const quality = (options?.quality
|
|
47
|
-
const scale = options?.scale
|
|
66
|
+
const quality = assertQuality(options?.quality);
|
|
67
|
+
const scale = options?.scale === undefined
|
|
68
|
+
? 1
|
|
69
|
+
: assertPositiveFiniteNumber(options.scale, "scale");
|
|
70
|
+
// PERMISSIONS_MIGRATION: getDisplayMedia() triggers the OS screen
|
|
71
|
+
// recording / picker dialog implicitly. New flow probes via
|
|
72
|
+
// `screenRecordingProber` in
|
|
73
|
+
// `packages/agent/src/services/permissions/probers/screen-recording.ts`
|
|
74
|
+
// before opening the stream. Will be retired by the chat-surface
|
|
75
|
+
// migration agent.
|
|
48
76
|
const stream = await getDisplayMedia({
|
|
49
77
|
video: { displaySurface: "monitor" },
|
|
50
78
|
audio: false,
|
|
@@ -53,11 +81,16 @@ class ScreenCaptureWeb extends core.WebPlugin {
|
|
|
53
81
|
const settings = track.getSettings();
|
|
54
82
|
const width = (settings.width || 1920) * scale;
|
|
55
83
|
const height = (settings.height || 1080) * scale;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
84
|
+
let bitmap = null;
|
|
85
|
+
try {
|
|
86
|
+
const imageCapture = new ImageCapture(track);
|
|
87
|
+
bitmap = await imageCapture.grabFrame();
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
stream.getTracks().forEach((t) => {
|
|
91
|
+
t.stop();
|
|
92
|
+
});
|
|
93
|
+
}
|
|
61
94
|
const canvas = document.createElement("canvas");
|
|
62
95
|
canvas.width = width;
|
|
63
96
|
canvas.height = height;
|
|
@@ -85,6 +118,18 @@ class ScreenCaptureWeb extends core.WebPlugin {
|
|
|
85
118
|
async startRecording(options) {
|
|
86
119
|
if (this.isRecording)
|
|
87
120
|
throw new Error("Recording already in progress");
|
|
121
|
+
if (options?.fps !== undefined) {
|
|
122
|
+
assertPositiveFiniteNumber(options.fps, "fps");
|
|
123
|
+
}
|
|
124
|
+
if (options?.bitrate !== undefined) {
|
|
125
|
+
assertPositiveFiniteNumber(options.bitrate, "bitrate");
|
|
126
|
+
}
|
|
127
|
+
if (options?.maxDuration !== undefined) {
|
|
128
|
+
assertPositiveFiniteNumber(options.maxDuration, "maxDuration");
|
|
129
|
+
}
|
|
130
|
+
if (options?.maxFileSize !== undefined) {
|
|
131
|
+
assertPositiveFiniteNumber(options.maxFileSize, "maxFileSize");
|
|
132
|
+
}
|
|
88
133
|
const videoConstraints = {
|
|
89
134
|
displaySurface: "monitor",
|
|
90
135
|
};
|
package/dist/plugin.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from \"@capacitor/core\";\nexport * from \"./definitions\";\nconst loadWeb = () => import(\"./web\").then((m) => new m.ScreenCaptureWeb());\nexport const ScreenCapture = registerPlugin(\"ScreenCapture\", {\n web: loadWeb,\n});\n","import { WebPlugin } from \"@capacitor/core\";\nconst VIDEO_MIME_TYPES = [\n \"video/webm;codecs=vp9,opus\",\n \"video/webm;codecs=vp8,opus\",\n \"video/webm\",\n \"video/mp4\",\n];\nconst getSupportedMimeType = () => VIDEO_MIME_TYPES.find((m) => MediaRecorder.isTypeSupported(m)) ?? null;\nconst hasDisplayMedia = () => !!navigator.mediaDevices.getDisplayMedia;\nconst getDisplayMedia = (opts) => navigator.mediaDevices.getDisplayMedia(opts);\nexport class ScreenCaptureWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.mediaStream = null;\n this.mediaRecorder = null;\n this.recordedChunks = [];\n this.isRecording = false;\n this.isPaused = false;\n this.recordingStartTime = 0;\n this.pausedDuration = 0;\n this.pauseStartTime = 0;\n this.recordingStateInterval = null;\n this.pluginListeners = [];\n }\n async isSupported() {\n const supported = hasDisplayMedia();\n const features = [];\n if (supported)\n features.push(\"screenshot\", \"recording\");\n if (typeof MediaRecorder !== \"undefined\")\n features.push(\"video_encoding\");\n if (typeof AudioContext !== \"undefined\")\n features.push(\"system_audio\");\n return { supported, features };\n }\n async captureScreenshot(options) {\n const format = options?.format || \"png\";\n const quality = (options?.quality || 100) / 100;\n const scale = options?.scale || 1;\n const stream = await getDisplayMedia({\n video: { displaySurface: \"monitor\" },\n audio: false,\n });\n const track = stream.getVideoTracks()[0];\n const settings = track.getSettings();\n const width = (settings.width || 1920) * scale;\n const height = (settings.height || 1080) * scale;\n const imageCapture = new ImageCapture(track);\n const bitmap = await imageCapture.grabFrame();\n stream.getTracks().forEach((t) => {\n t.stop();\n });\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n throw new Error(\"Failed to get canvas context\");\n }\n ctx.drawImage(bitmap, 0, 0, width, height);\n bitmap.close();\n const mimeType = format === \"png\"\n ? \"image/png\"\n : format === \"webp\"\n ? \"image/webp\"\n : \"image/jpeg\";\n const dataUrl = canvas.toDataURL(mimeType, quality);\n const base64 = dataUrl.split(\",\")[1];\n return {\n base64,\n format,\n width,\n height,\n timestamp: Date.now(),\n };\n }\n async startRecording(options) {\n if (this.isRecording)\n throw new Error(\"Recording already in progress\");\n const videoConstraints = {\n displaySurface: \"monitor\",\n };\n if (options?.fps)\n videoConstraints.frameRate = { ideal: options.fps };\n this.mediaStream = await getDisplayMedia({\n video: videoConstraints,\n audio: options?.captureSystemAudio !== false,\n });\n if (options?.captureMicrophone) {\n const micStream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n });\n micStream.getAudioTracks().forEach((t) => {\n this.mediaStream?.addTrack(t);\n });\n }\n const mimeType = getSupportedMimeType();\n if (!mimeType) {\n this.mediaStream.getTracks().forEach((t) => {\n t.stop();\n });\n throw new Error(\"No supported video mime type found\");\n }\n const recorderOptions = { mimeType };\n if (options?.bitrate)\n recorderOptions.videoBitsPerSecond = options.bitrate;\n this.recordedChunks = [];\n this.mediaRecorder = new MediaRecorder(this.mediaStream, recorderOptions);\n this.mediaRecorder.ondataavailable = (event) => {\n if (event.data.size > 0) {\n this.recordedChunks.push(event.data);\n }\n };\n this.mediaRecorder.onerror = (event) => {\n this.notifyListeners(\"error\", {\n code: \"RECORDING_ERROR\",\n message: `Recording error: ${event.message || \"Unknown error\"}`,\n });\n };\n this.mediaStream.getVideoTracks()[0].addEventListener(\"ended\", () => {\n if (this.isRecording) {\n this.stopRecording().catch((err) => {\n console.error(\"[ScreenCapture] Auto-stop on track end failed:\", err);\n });\n }\n });\n this.recordingStartTime = Date.now();\n this.pausedDuration = 0;\n this.isRecording = true;\n this.isPaused = false;\n this.mediaRecorder.start(1000);\n this.notifyListeners(\"recordingState\", {\n isRecording: true,\n duration: 0,\n fileSize: 0,\n });\n let autoStopping = false;\n this.recordingStateInterval = setInterval(() => {\n if (!this.isRecording || this.isPaused || autoStopping)\n return;\n const duration = (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000;\n const fileSize = this.recordedChunks.reduce((acc, chunk) => acc + chunk.size, 0);\n this.notifyListeners(\"recordingState\", {\n isRecording: true,\n duration,\n fileSize,\n });\n const overLimit = (options?.maxDuration && duration >= options.maxDuration) ||\n (options?.maxFileSize && fileSize >= options.maxFileSize);\n if (overLimit) {\n autoStopping = true;\n this.stopRecording().catch((err) => {\n console.error(\"[ScreenCapture] Auto-stop recording failed:\", err);\n });\n }\n }, 500);\n }\n async stopRecording() {\n if (!this.isRecording || !this.mediaRecorder) {\n throw new Error(\"Not recording\");\n }\n return new Promise((resolve, reject) => {\n if (!this.mediaRecorder) {\n reject(new Error(\"MediaRecorder not initialized\"));\n return;\n }\n const duration = (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000;\n this.mediaRecorder.onstop = () => {\n if (this.recordingStateInterval) {\n clearInterval(this.recordingStateInterval);\n this.recordingStateInterval = null;\n }\n this.isRecording = false;\n this.isPaused = false;\n if (this.mediaStream) {\n this.mediaStream.getTracks().forEach((track) => {\n track.stop();\n });\n this.mediaStream = null;\n }\n const blob = new Blob(this.recordedChunks, {\n type: this.mediaRecorder?.mimeType || \"video/webm\",\n });\n const url = URL.createObjectURL(blob);\n const video = document.createElement(\"video\");\n video.src = url;\n video.onloadedmetadata = () => {\n resolve({\n path: url,\n duration,\n width: video.videoWidth,\n height: video.videoHeight,\n fileSize: blob.size,\n mimeType: this.mediaRecorder?.mimeType || \"video/webm\",\n });\n };\n video.onerror = () => {\n resolve({\n path: url,\n duration,\n width: 0,\n height: 0,\n fileSize: blob.size,\n mimeType: this.mediaRecorder?.mimeType || \"video/webm\",\n });\n };\n this.notifyListeners(\"recordingState\", {\n isRecording: false,\n duration,\n fileSize: blob.size,\n });\n };\n this.mediaRecorder.stop();\n });\n }\n async pauseRecording() {\n if (!this.isRecording || !this.mediaRecorder) {\n throw new Error(\"Not recording\");\n }\n if (this.isPaused) {\n return;\n }\n this.mediaRecorder.pause();\n this.isPaused = true;\n this.pauseStartTime = Date.now();\n const duration = (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000;\n const fileSize = this.recordedChunks.reduce((acc, chunk) => acc + chunk.size, 0);\n this.notifyListeners(\"recordingState\", {\n isRecording: true,\n duration,\n fileSize,\n });\n }\n async resumeRecording() {\n if (!this.isRecording || !this.mediaRecorder) {\n throw new Error(\"Not recording\");\n }\n if (!this.isPaused) {\n return;\n }\n this.pausedDuration += Date.now() - this.pauseStartTime;\n this.mediaRecorder.resume();\n this.isPaused = false;\n }\n async getRecordingState() {\n const duration = this.isRecording\n ? (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000\n : 0;\n const fileSize = this.recordedChunks.reduce((acc, chunk) => acc + chunk.size, 0);\n return {\n isRecording: this.isRecording,\n duration,\n fileSize,\n };\n }\n /**\n * Check screen capture permissions.\n *\n * LIMITATION: The Screen Capture API (getDisplayMedia) does not support permission queries.\n * Unlike camera/microphone, there's no way to check if permission was previously granted.\n * Each call to getDisplayMedia always prompts the user.\n *\n * `screenCapture` will be:\n * - \"not_supported\": getDisplayMedia API not available\n * - \"prompt\": API available, but actual permission state is unknown (always requires prompt)\n */\n async checkPermissions() {\n let microphone = \"prompt\";\n try {\n const result = await navigator.permissions.query({\n name: \"microphone\",\n });\n microphone = result.state;\n }\n catch {\n // Permissions API may not support microphone query in this browser\n }\n // Screen capture permission cannot be queried - getDisplayMedia always prompts\n const screenCaptureStatus = hasDisplayMedia() ? \"prompt\" : \"not_supported\";\n return { screenCapture: screenCaptureStatus, microphone };\n }\n /**\n * Request screen capture permissions.\n *\n * LIMITATION: Screen capture (getDisplayMedia) cannot be pre-requested.\n * The user is prompted only when an actual capture is initiated.\n * This method only requests microphone permission for audio capture during recording.\n *\n * `screenCapture` will be:\n * - \"not_supported\": getDisplayMedia API not available\n * - \"prompt\": API available (permission prompt happens during actual capture)\n */\n async requestPermissions() {\n let microphone = \"denied\";\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n stream.getTracks().forEach((t) => {\n t.stop();\n });\n microphone = \"granted\";\n }\n catch {\n microphone = \"denied\";\n }\n // Cannot pre-request screen capture permission - it requires user gesture + actual capture\n const screenCaptureStatus = hasDisplayMedia() ? \"prompt\" : \"not_supported\";\n return { screenCapture: screenCaptureStatus, microphone };\n }\n async addListener(eventName, listenerFunc) {\n const entry = { eventName, callback: listenerFunc };\n this.pluginListeners.push(entry);\n return {\n remove: async () => {\n const i = this.pluginListeners.indexOf(entry);\n if (i >= 0)\n this.pluginListeners.splice(i, 1);\n },\n };\n }\n async removeAllListeners() {\n this.pluginListeners = [];\n }\n notifyListeners(eventName, data) {\n this.pluginListeners\n .filter((l) => l.eventName === eventName)\n .forEach((l) => {\n l.callback(data);\n });\n }\n}\n"],"names":["registerPlugin","WebPlugin"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from \"@capacitor/core\";\nexport * from \"./definitions\";\nconst loadWeb = () => import(\"./web\").then((m) => new m.ScreenCaptureWeb());\nexport const ScreenCapture = registerPlugin(\"ScreenCapture\", {\n web: loadWeb,\n});\n","import { WebPlugin } from \"@capacitor/core\";\nconst VIDEO_MIME_TYPES = [\n \"video/webm;codecs=vp9,opus\",\n \"video/webm;codecs=vp8,opus\",\n \"video/webm\",\n \"video/mp4\",\n];\nconst getSupportedMimeType = () => typeof MediaRecorder === \"undefined\"\n ? null\n : (VIDEO_MIME_TYPES.find((m) => MediaRecorder.isTypeSupported(m)) ?? null);\nconst hasDisplayMedia = () => !!navigator.mediaDevices\n ?.getDisplayMedia;\nfunction assertPositiveFiniteNumber(value, label) {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0) {\n throw new Error(`${label} must be a positive finite number`);\n }\n return value;\n}\nfunction assertQuality(value) {\n if (value === undefined)\n return 1;\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n throw new Error(\"quality must be a finite number between 0 and 100\");\n }\n if (value < 0 || value > 100) {\n throw new Error(\"quality must be between 0 and 100\");\n }\n return value / 100;\n}\nconst getDisplayMedia = (opts) => navigator.mediaDevices.getDisplayMedia(opts);\nexport class ScreenCaptureWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.mediaStream = null;\n this.mediaRecorder = null;\n this.recordedChunks = [];\n this.isRecording = false;\n this.isPaused = false;\n this.recordingStartTime = 0;\n this.pausedDuration = 0;\n this.pauseStartTime = 0;\n this.recordingStateInterval = null;\n this.pluginListeners = [];\n }\n async isSupported() {\n const supported = hasDisplayMedia();\n const features = [];\n if (supported)\n features.push(\"screenshot\", \"recording\");\n if (typeof MediaRecorder !== \"undefined\")\n features.push(\"video_encoding\");\n if (typeof AudioContext !== \"undefined\")\n features.push(\"system_audio\");\n return { supported, features };\n }\n async captureScreenshot(options) {\n const format = options?.format || \"png\";\n const quality = assertQuality(options?.quality);\n const scale = options?.scale === undefined\n ? 1\n : assertPositiveFiniteNumber(options.scale, \"scale\");\n // PERMISSIONS_MIGRATION: getDisplayMedia() triggers the OS screen\n // recording / picker dialog implicitly. New flow probes via\n // `screenRecordingProber` in\n // `packages/agent/src/services/permissions/probers/screen-recording.ts`\n // before opening the stream. Will be retired by the chat-surface\n // migration agent.\n const stream = await getDisplayMedia({\n video: { displaySurface: \"monitor\" },\n audio: false,\n });\n const track = stream.getVideoTracks()[0];\n const settings = track.getSettings();\n const width = (settings.width || 1920) * scale;\n const height = (settings.height || 1080) * scale;\n let bitmap = null;\n try {\n const imageCapture = new ImageCapture(track);\n bitmap = await imageCapture.grabFrame();\n }\n finally {\n stream.getTracks().forEach((t) => {\n t.stop();\n });\n }\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n throw new Error(\"Failed to get canvas context\");\n }\n ctx.drawImage(bitmap, 0, 0, width, height);\n bitmap.close();\n const mimeType = format === \"png\"\n ? \"image/png\"\n : format === \"webp\"\n ? \"image/webp\"\n : \"image/jpeg\";\n const dataUrl = canvas.toDataURL(mimeType, quality);\n const base64 = dataUrl.split(\",\")[1];\n return {\n base64,\n format,\n width,\n height,\n timestamp: Date.now(),\n };\n }\n async startRecording(options) {\n if (this.isRecording)\n throw new Error(\"Recording already in progress\");\n if (options?.fps !== undefined) {\n assertPositiveFiniteNumber(options.fps, \"fps\");\n }\n if (options?.bitrate !== undefined) {\n assertPositiveFiniteNumber(options.bitrate, \"bitrate\");\n }\n if (options?.maxDuration !== undefined) {\n assertPositiveFiniteNumber(options.maxDuration, \"maxDuration\");\n }\n if (options?.maxFileSize !== undefined) {\n assertPositiveFiniteNumber(options.maxFileSize, \"maxFileSize\");\n }\n const videoConstraints = {\n displaySurface: \"monitor\",\n };\n if (options?.fps)\n videoConstraints.frameRate = { ideal: options.fps };\n this.mediaStream = await getDisplayMedia({\n video: videoConstraints,\n audio: options?.captureSystemAudio !== false,\n });\n if (options?.captureMicrophone) {\n const micStream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n });\n micStream.getAudioTracks().forEach((t) => {\n this.mediaStream?.addTrack(t);\n });\n }\n const mimeType = getSupportedMimeType();\n if (!mimeType) {\n this.mediaStream.getTracks().forEach((t) => {\n t.stop();\n });\n throw new Error(\"No supported video mime type found\");\n }\n const recorderOptions = { mimeType };\n if (options?.bitrate)\n recorderOptions.videoBitsPerSecond = options.bitrate;\n this.recordedChunks = [];\n this.mediaRecorder = new MediaRecorder(this.mediaStream, recorderOptions);\n this.mediaRecorder.ondataavailable = (event) => {\n if (event.data.size > 0) {\n this.recordedChunks.push(event.data);\n }\n };\n this.mediaRecorder.onerror = (event) => {\n this.notifyListeners(\"error\", {\n code: \"RECORDING_ERROR\",\n message: `Recording error: ${event.message || \"Unknown error\"}`,\n });\n };\n this.mediaStream.getVideoTracks()[0].addEventListener(\"ended\", () => {\n if (this.isRecording) {\n this.stopRecording().catch((err) => {\n console.error(\"[ScreenCapture] Auto-stop on track end failed:\", err);\n });\n }\n });\n this.recordingStartTime = Date.now();\n this.pausedDuration = 0;\n this.isRecording = true;\n this.isPaused = false;\n this.mediaRecorder.start(1000);\n this.notifyListeners(\"recordingState\", {\n isRecording: true,\n duration: 0,\n fileSize: 0,\n });\n let autoStopping = false;\n this.recordingStateInterval = setInterval(() => {\n if (!this.isRecording || this.isPaused || autoStopping)\n return;\n const duration = (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000;\n const fileSize = this.recordedChunks.reduce((acc, chunk) => acc + chunk.size, 0);\n this.notifyListeners(\"recordingState\", {\n isRecording: true,\n duration,\n fileSize,\n });\n const overLimit = (options?.maxDuration && duration >= options.maxDuration) ||\n (options?.maxFileSize && fileSize >= options.maxFileSize);\n if (overLimit) {\n autoStopping = true;\n this.stopRecording().catch((err) => {\n console.error(\"[ScreenCapture] Auto-stop recording failed:\", err);\n });\n }\n }, 500);\n }\n async stopRecording() {\n if (!this.isRecording || !this.mediaRecorder) {\n throw new Error(\"Not recording\");\n }\n return new Promise((resolve, reject) => {\n if (!this.mediaRecorder) {\n reject(new Error(\"MediaRecorder not initialized\"));\n return;\n }\n const duration = (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000;\n this.mediaRecorder.onstop = () => {\n if (this.recordingStateInterval) {\n clearInterval(this.recordingStateInterval);\n this.recordingStateInterval = null;\n }\n this.isRecording = false;\n this.isPaused = false;\n if (this.mediaStream) {\n this.mediaStream.getTracks().forEach((track) => {\n track.stop();\n });\n this.mediaStream = null;\n }\n const blob = new Blob(this.recordedChunks, {\n type: this.mediaRecorder?.mimeType || \"video/webm\",\n });\n const url = URL.createObjectURL(blob);\n const video = document.createElement(\"video\");\n video.src = url;\n video.onloadedmetadata = () => {\n resolve({\n path: url,\n duration,\n width: video.videoWidth,\n height: video.videoHeight,\n fileSize: blob.size,\n mimeType: this.mediaRecorder?.mimeType || \"video/webm\",\n });\n };\n video.onerror = () => {\n resolve({\n path: url,\n duration,\n width: 0,\n height: 0,\n fileSize: blob.size,\n mimeType: this.mediaRecorder?.mimeType || \"video/webm\",\n });\n };\n this.notifyListeners(\"recordingState\", {\n isRecording: false,\n duration,\n fileSize: blob.size,\n });\n };\n this.mediaRecorder.stop();\n });\n }\n async pauseRecording() {\n if (!this.isRecording || !this.mediaRecorder) {\n throw new Error(\"Not recording\");\n }\n if (this.isPaused) {\n return;\n }\n this.mediaRecorder.pause();\n this.isPaused = true;\n this.pauseStartTime = Date.now();\n const duration = (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000;\n const fileSize = this.recordedChunks.reduce((acc, chunk) => acc + chunk.size, 0);\n this.notifyListeners(\"recordingState\", {\n isRecording: true,\n duration,\n fileSize,\n });\n }\n async resumeRecording() {\n if (!this.isRecording || !this.mediaRecorder) {\n throw new Error(\"Not recording\");\n }\n if (!this.isPaused) {\n return;\n }\n this.pausedDuration += Date.now() - this.pauseStartTime;\n this.mediaRecorder.resume();\n this.isPaused = false;\n }\n async getRecordingState() {\n const duration = this.isRecording\n ? (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000\n : 0;\n const fileSize = this.recordedChunks.reduce((acc, chunk) => acc + chunk.size, 0);\n return {\n isRecording: this.isRecording,\n duration,\n fileSize,\n };\n }\n /**\n * Check screen capture permissions.\n *\n * LIMITATION: The Screen Capture API (getDisplayMedia) does not support permission queries.\n * Unlike camera/microphone, there's no way to check if permission was previously granted.\n * Each call to getDisplayMedia always prompts the user.\n *\n * `screenCapture` will be:\n * - \"not_supported\": getDisplayMedia API not available\n * - \"prompt\": API available, but actual permission state is unknown (always requires prompt)\n */\n async checkPermissions() {\n let microphone = \"prompt\";\n try {\n const result = await navigator.permissions.query({\n name: \"microphone\",\n });\n microphone = result.state;\n }\n catch {\n // Permissions API may not support microphone query in this browser\n }\n // Screen capture permission cannot be queried - getDisplayMedia always prompts\n const screenCaptureStatus = hasDisplayMedia() ? \"prompt\" : \"not_supported\";\n return { screenCapture: screenCaptureStatus, microphone };\n }\n /**\n * Request screen capture permissions.\n *\n * LIMITATION: Screen capture (getDisplayMedia) cannot be pre-requested.\n * The user is prompted only when an actual capture is initiated.\n * This method only requests microphone permission for audio capture during recording.\n *\n * `screenCapture` will be:\n * - \"not_supported\": getDisplayMedia API not available\n * - \"prompt\": API available (permission prompt happens during actual capture)\n */\n async requestPermissions() {\n let microphone = \"denied\";\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n stream.getTracks().forEach((t) => {\n t.stop();\n });\n microphone = \"granted\";\n }\n catch {\n microphone = \"denied\";\n }\n // Cannot pre-request screen capture permission - it requires user gesture + actual capture\n const screenCaptureStatus = hasDisplayMedia() ? \"prompt\" : \"not_supported\";\n return { screenCapture: screenCaptureStatus, microphone };\n }\n async addListener(eventName, listenerFunc) {\n const entry = { eventName, callback: listenerFunc };\n this.pluginListeners.push(entry);\n return {\n remove: async () => {\n const i = this.pluginListeners.indexOf(entry);\n if (i >= 0)\n this.pluginListeners.splice(i, 1);\n },\n };\n }\n async removeAllListeners() {\n this.pluginListeners = [];\n }\n notifyListeners(eventName, data) {\n this.pluginListeners\n .filter((l) => l.eventName === eventName)\n .forEach((l) => {\n l.callback(data);\n });\n }\n}\n"],"names":["registerPlugin","WebPlugin"],"mappings":"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"}
|
package/dist/plugin.js
CHANGED
|
@@ -12,8 +12,28 @@ var capacitorScreenCapture = (function (exports, core) {
|
|
|
12
12
|
"video/webm",
|
|
13
13
|
"video/mp4",
|
|
14
14
|
];
|
|
15
|
-
const getSupportedMimeType = () =>
|
|
16
|
-
|
|
15
|
+
const getSupportedMimeType = () => typeof MediaRecorder === "undefined"
|
|
16
|
+
? null
|
|
17
|
+
: (VIDEO_MIME_TYPES.find((m) => MediaRecorder.isTypeSupported(m)) ?? null);
|
|
18
|
+
const hasDisplayMedia = () => !!navigator.mediaDevices
|
|
19
|
+
?.getDisplayMedia;
|
|
20
|
+
function assertPositiveFiniteNumber(value, label) {
|
|
21
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
22
|
+
throw new Error(`${label} must be a positive finite number`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function assertQuality(value) {
|
|
27
|
+
if (value === undefined)
|
|
28
|
+
return 1;
|
|
29
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
30
|
+
throw new Error("quality must be a finite number between 0 and 100");
|
|
31
|
+
}
|
|
32
|
+
if (value < 0 || value > 100) {
|
|
33
|
+
throw new Error("quality must be between 0 and 100");
|
|
34
|
+
}
|
|
35
|
+
return value / 100;
|
|
36
|
+
}
|
|
17
37
|
const getDisplayMedia = (opts) => navigator.mediaDevices.getDisplayMedia(opts);
|
|
18
38
|
class ScreenCaptureWeb extends core.WebPlugin {
|
|
19
39
|
constructor() {
|
|
@@ -42,8 +62,16 @@ var capacitorScreenCapture = (function (exports, core) {
|
|
|
42
62
|
}
|
|
43
63
|
async captureScreenshot(options) {
|
|
44
64
|
const format = options?.format || "png";
|
|
45
|
-
const quality = (options?.quality
|
|
46
|
-
const scale = options?.scale
|
|
65
|
+
const quality = assertQuality(options?.quality);
|
|
66
|
+
const scale = options?.scale === undefined
|
|
67
|
+
? 1
|
|
68
|
+
: assertPositiveFiniteNumber(options.scale, "scale");
|
|
69
|
+
// PERMISSIONS_MIGRATION: getDisplayMedia() triggers the OS screen
|
|
70
|
+
// recording / picker dialog implicitly. New flow probes via
|
|
71
|
+
// `screenRecordingProber` in
|
|
72
|
+
// `packages/agent/src/services/permissions/probers/screen-recording.ts`
|
|
73
|
+
// before opening the stream. Will be retired by the chat-surface
|
|
74
|
+
// migration agent.
|
|
47
75
|
const stream = await getDisplayMedia({
|
|
48
76
|
video: { displaySurface: "monitor" },
|
|
49
77
|
audio: false,
|
|
@@ -52,11 +80,16 @@ var capacitorScreenCapture = (function (exports, core) {
|
|
|
52
80
|
const settings = track.getSettings();
|
|
53
81
|
const width = (settings.width || 1920) * scale;
|
|
54
82
|
const height = (settings.height || 1080) * scale;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
83
|
+
let bitmap = null;
|
|
84
|
+
try {
|
|
85
|
+
const imageCapture = new ImageCapture(track);
|
|
86
|
+
bitmap = await imageCapture.grabFrame();
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
stream.getTracks().forEach((t) => {
|
|
90
|
+
t.stop();
|
|
91
|
+
});
|
|
92
|
+
}
|
|
60
93
|
const canvas = document.createElement("canvas");
|
|
61
94
|
canvas.width = width;
|
|
62
95
|
canvas.height = height;
|
|
@@ -84,6 +117,18 @@ var capacitorScreenCapture = (function (exports, core) {
|
|
|
84
117
|
async startRecording(options) {
|
|
85
118
|
if (this.isRecording)
|
|
86
119
|
throw new Error("Recording already in progress");
|
|
120
|
+
if (options?.fps !== undefined) {
|
|
121
|
+
assertPositiveFiniteNumber(options.fps, "fps");
|
|
122
|
+
}
|
|
123
|
+
if (options?.bitrate !== undefined) {
|
|
124
|
+
assertPositiveFiniteNumber(options.bitrate, "bitrate");
|
|
125
|
+
}
|
|
126
|
+
if (options?.maxDuration !== undefined) {
|
|
127
|
+
assertPositiveFiniteNumber(options.maxDuration, "maxDuration");
|
|
128
|
+
}
|
|
129
|
+
if (options?.maxFileSize !== undefined) {
|
|
130
|
+
assertPositiveFiniteNumber(options.maxFileSize, "maxFileSize");
|
|
131
|
+
}
|
|
87
132
|
const videoConstraints = {
|
|
88
133
|
displaySurface: "monitor",
|
|
89
134
|
};
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from \"@capacitor/core\";\nexport * from \"./definitions\";\nconst loadWeb = () => import(\"./web\").then((m) => new m.ScreenCaptureWeb());\nexport const ScreenCapture = registerPlugin(\"ScreenCapture\", {\n web: loadWeb,\n});\n","import { WebPlugin } from \"@capacitor/core\";\nconst VIDEO_MIME_TYPES = [\n \"video/webm;codecs=vp9,opus\",\n \"video/webm;codecs=vp8,opus\",\n \"video/webm\",\n \"video/mp4\",\n];\nconst getSupportedMimeType = () => VIDEO_MIME_TYPES.find((m) => MediaRecorder.isTypeSupported(m)) ?? null;\nconst hasDisplayMedia = () => !!navigator.mediaDevices.getDisplayMedia;\nconst getDisplayMedia = (opts) => navigator.mediaDevices.getDisplayMedia(opts);\nexport class ScreenCaptureWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.mediaStream = null;\n this.mediaRecorder = null;\n this.recordedChunks = [];\n this.isRecording = false;\n this.isPaused = false;\n this.recordingStartTime = 0;\n this.pausedDuration = 0;\n this.pauseStartTime = 0;\n this.recordingStateInterval = null;\n this.pluginListeners = [];\n }\n async isSupported() {\n const supported = hasDisplayMedia();\n const features = [];\n if (supported)\n features.push(\"screenshot\", \"recording\");\n if (typeof MediaRecorder !== \"undefined\")\n features.push(\"video_encoding\");\n if (typeof AudioContext !== \"undefined\")\n features.push(\"system_audio\");\n return { supported, features };\n }\n async captureScreenshot(options) {\n const format = options?.format || \"png\";\n const quality = (options?.quality || 100) / 100;\n const scale = options?.scale || 1;\n const stream = await getDisplayMedia({\n video: { displaySurface: \"monitor\" },\n audio: false,\n });\n const track = stream.getVideoTracks()[0];\n const settings = track.getSettings();\n const width = (settings.width || 1920) * scale;\n const height = (settings.height || 1080) * scale;\n const imageCapture = new ImageCapture(track);\n const bitmap = await imageCapture.grabFrame();\n stream.getTracks().forEach((t) => {\n t.stop();\n });\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n throw new Error(\"Failed to get canvas context\");\n }\n ctx.drawImage(bitmap, 0, 0, width, height);\n bitmap.close();\n const mimeType = format === \"png\"\n ? \"image/png\"\n : format === \"webp\"\n ? \"image/webp\"\n : \"image/jpeg\";\n const dataUrl = canvas.toDataURL(mimeType, quality);\n const base64 = dataUrl.split(\",\")[1];\n return {\n base64,\n format,\n width,\n height,\n timestamp: Date.now(),\n };\n }\n async startRecording(options) {\n if (this.isRecording)\n throw new Error(\"Recording already in progress\");\n const videoConstraints = {\n displaySurface: \"monitor\",\n };\n if (options?.fps)\n videoConstraints.frameRate = { ideal: options.fps };\n this.mediaStream = await getDisplayMedia({\n video: videoConstraints,\n audio: options?.captureSystemAudio !== false,\n });\n if (options?.captureMicrophone) {\n const micStream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n });\n micStream.getAudioTracks().forEach((t) => {\n this.mediaStream?.addTrack(t);\n });\n }\n const mimeType = getSupportedMimeType();\n if (!mimeType) {\n this.mediaStream.getTracks().forEach((t) => {\n t.stop();\n });\n throw new Error(\"No supported video mime type found\");\n }\n const recorderOptions = { mimeType };\n if (options?.bitrate)\n recorderOptions.videoBitsPerSecond = options.bitrate;\n this.recordedChunks = [];\n this.mediaRecorder = new MediaRecorder(this.mediaStream, recorderOptions);\n this.mediaRecorder.ondataavailable = (event) => {\n if (event.data.size > 0) {\n this.recordedChunks.push(event.data);\n }\n };\n this.mediaRecorder.onerror = (event) => {\n this.notifyListeners(\"error\", {\n code: \"RECORDING_ERROR\",\n message: `Recording error: ${event.message || \"Unknown error\"}`,\n });\n };\n this.mediaStream.getVideoTracks()[0].addEventListener(\"ended\", () => {\n if (this.isRecording) {\n this.stopRecording().catch((err) => {\n console.error(\"[ScreenCapture] Auto-stop on track end failed:\", err);\n });\n }\n });\n this.recordingStartTime = Date.now();\n this.pausedDuration = 0;\n this.isRecording = true;\n this.isPaused = false;\n this.mediaRecorder.start(1000);\n this.notifyListeners(\"recordingState\", {\n isRecording: true,\n duration: 0,\n fileSize: 0,\n });\n let autoStopping = false;\n this.recordingStateInterval = setInterval(() => {\n if (!this.isRecording || this.isPaused || autoStopping)\n return;\n const duration = (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000;\n const fileSize = this.recordedChunks.reduce((acc, chunk) => acc + chunk.size, 0);\n this.notifyListeners(\"recordingState\", {\n isRecording: true,\n duration,\n fileSize,\n });\n const overLimit = (options?.maxDuration && duration >= options.maxDuration) ||\n (options?.maxFileSize && fileSize >= options.maxFileSize);\n if (overLimit) {\n autoStopping = true;\n this.stopRecording().catch((err) => {\n console.error(\"[ScreenCapture] Auto-stop recording failed:\", err);\n });\n }\n }, 500);\n }\n async stopRecording() {\n if (!this.isRecording || !this.mediaRecorder) {\n throw new Error(\"Not recording\");\n }\n return new Promise((resolve, reject) => {\n if (!this.mediaRecorder) {\n reject(new Error(\"MediaRecorder not initialized\"));\n return;\n }\n const duration = (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000;\n this.mediaRecorder.onstop = () => {\n if (this.recordingStateInterval) {\n clearInterval(this.recordingStateInterval);\n this.recordingStateInterval = null;\n }\n this.isRecording = false;\n this.isPaused = false;\n if (this.mediaStream) {\n this.mediaStream.getTracks().forEach((track) => {\n track.stop();\n });\n this.mediaStream = null;\n }\n const blob = new Blob(this.recordedChunks, {\n type: this.mediaRecorder?.mimeType || \"video/webm\",\n });\n const url = URL.createObjectURL(blob);\n const video = document.createElement(\"video\");\n video.src = url;\n video.onloadedmetadata = () => {\n resolve({\n path: url,\n duration,\n width: video.videoWidth,\n height: video.videoHeight,\n fileSize: blob.size,\n mimeType: this.mediaRecorder?.mimeType || \"video/webm\",\n });\n };\n video.onerror = () => {\n resolve({\n path: url,\n duration,\n width: 0,\n height: 0,\n fileSize: blob.size,\n mimeType: this.mediaRecorder?.mimeType || \"video/webm\",\n });\n };\n this.notifyListeners(\"recordingState\", {\n isRecording: false,\n duration,\n fileSize: blob.size,\n });\n };\n this.mediaRecorder.stop();\n });\n }\n async pauseRecording() {\n if (!this.isRecording || !this.mediaRecorder) {\n throw new Error(\"Not recording\");\n }\n if (this.isPaused) {\n return;\n }\n this.mediaRecorder.pause();\n this.isPaused = true;\n this.pauseStartTime = Date.now();\n const duration = (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000;\n const fileSize = this.recordedChunks.reduce((acc, chunk) => acc + chunk.size, 0);\n this.notifyListeners(\"recordingState\", {\n isRecording: true,\n duration,\n fileSize,\n });\n }\n async resumeRecording() {\n if (!this.isRecording || !this.mediaRecorder) {\n throw new Error(\"Not recording\");\n }\n if (!this.isPaused) {\n return;\n }\n this.pausedDuration += Date.now() - this.pauseStartTime;\n this.mediaRecorder.resume();\n this.isPaused = false;\n }\n async getRecordingState() {\n const duration = this.isRecording\n ? (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000\n : 0;\n const fileSize = this.recordedChunks.reduce((acc, chunk) => acc + chunk.size, 0);\n return {\n isRecording: this.isRecording,\n duration,\n fileSize,\n };\n }\n /**\n * Check screen capture permissions.\n *\n * LIMITATION: The Screen Capture API (getDisplayMedia) does not support permission queries.\n * Unlike camera/microphone, there's no way to check if permission was previously granted.\n * Each call to getDisplayMedia always prompts the user.\n *\n * `screenCapture` will be:\n * - \"not_supported\": getDisplayMedia API not available\n * - \"prompt\": API available, but actual permission state is unknown (always requires prompt)\n */\n async checkPermissions() {\n let microphone = \"prompt\";\n try {\n const result = await navigator.permissions.query({\n name: \"microphone\",\n });\n microphone = result.state;\n }\n catch {\n // Permissions API may not support microphone query in this browser\n }\n // Screen capture permission cannot be queried - getDisplayMedia always prompts\n const screenCaptureStatus = hasDisplayMedia() ? \"prompt\" : \"not_supported\";\n return { screenCapture: screenCaptureStatus, microphone };\n }\n /**\n * Request screen capture permissions.\n *\n * LIMITATION: Screen capture (getDisplayMedia) cannot be pre-requested.\n * The user is prompted only when an actual capture is initiated.\n * This method only requests microphone permission for audio capture during recording.\n *\n * `screenCapture` will be:\n * - \"not_supported\": getDisplayMedia API not available\n * - \"prompt\": API available (permission prompt happens during actual capture)\n */\n async requestPermissions() {\n let microphone = \"denied\";\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n stream.getTracks().forEach((t) => {\n t.stop();\n });\n microphone = \"granted\";\n }\n catch {\n microphone = \"denied\";\n }\n // Cannot pre-request screen capture permission - it requires user gesture + actual capture\n const screenCaptureStatus = hasDisplayMedia() ? \"prompt\" : \"not_supported\";\n return { screenCapture: screenCaptureStatus, microphone };\n }\n async addListener(eventName, listenerFunc) {\n const entry = { eventName, callback: listenerFunc };\n this.pluginListeners.push(entry);\n return {\n remove: async () => {\n const i = this.pluginListeners.indexOf(entry);\n if (i >= 0)\n this.pluginListeners.splice(i, 1);\n },\n };\n }\n async removeAllListeners() {\n this.pluginListeners = [];\n }\n notifyListeners(eventName, data) {\n this.pluginListeners\n .filter((l) => l.eventName === eventName)\n .forEach((l) => {\n l.callback(data);\n });\n }\n}\n"],"names":["registerPlugin","WebPlugin"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from \"@capacitor/core\";\nexport * from \"./definitions\";\nconst loadWeb = () => import(\"./web\").then((m) => new m.ScreenCaptureWeb());\nexport const ScreenCapture = registerPlugin(\"ScreenCapture\", {\n web: loadWeb,\n});\n","import { WebPlugin } from \"@capacitor/core\";\nconst VIDEO_MIME_TYPES = [\n \"video/webm;codecs=vp9,opus\",\n \"video/webm;codecs=vp8,opus\",\n \"video/webm\",\n \"video/mp4\",\n];\nconst getSupportedMimeType = () => typeof MediaRecorder === \"undefined\"\n ? null\n : (VIDEO_MIME_TYPES.find((m) => MediaRecorder.isTypeSupported(m)) ?? null);\nconst hasDisplayMedia = () => !!navigator.mediaDevices\n ?.getDisplayMedia;\nfunction assertPositiveFiniteNumber(value, label) {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0) {\n throw new Error(`${label} must be a positive finite number`);\n }\n return value;\n}\nfunction assertQuality(value) {\n if (value === undefined)\n return 1;\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n throw new Error(\"quality must be a finite number between 0 and 100\");\n }\n if (value < 0 || value > 100) {\n throw new Error(\"quality must be between 0 and 100\");\n }\n return value / 100;\n}\nconst getDisplayMedia = (opts) => navigator.mediaDevices.getDisplayMedia(opts);\nexport class ScreenCaptureWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.mediaStream = null;\n this.mediaRecorder = null;\n this.recordedChunks = [];\n this.isRecording = false;\n this.isPaused = false;\n this.recordingStartTime = 0;\n this.pausedDuration = 0;\n this.pauseStartTime = 0;\n this.recordingStateInterval = null;\n this.pluginListeners = [];\n }\n async isSupported() {\n const supported = hasDisplayMedia();\n const features = [];\n if (supported)\n features.push(\"screenshot\", \"recording\");\n if (typeof MediaRecorder !== \"undefined\")\n features.push(\"video_encoding\");\n if (typeof AudioContext !== \"undefined\")\n features.push(\"system_audio\");\n return { supported, features };\n }\n async captureScreenshot(options) {\n const format = options?.format || \"png\";\n const quality = assertQuality(options?.quality);\n const scale = options?.scale === undefined\n ? 1\n : assertPositiveFiniteNumber(options.scale, \"scale\");\n // PERMISSIONS_MIGRATION: getDisplayMedia() triggers the OS screen\n // recording / picker dialog implicitly. New flow probes via\n // `screenRecordingProber` in\n // `packages/agent/src/services/permissions/probers/screen-recording.ts`\n // before opening the stream. Will be retired by the chat-surface\n // migration agent.\n const stream = await getDisplayMedia({\n video: { displaySurface: \"monitor\" },\n audio: false,\n });\n const track = stream.getVideoTracks()[0];\n const settings = track.getSettings();\n const width = (settings.width || 1920) * scale;\n const height = (settings.height || 1080) * scale;\n let bitmap = null;\n try {\n const imageCapture = new ImageCapture(track);\n bitmap = await imageCapture.grabFrame();\n }\n finally {\n stream.getTracks().forEach((t) => {\n t.stop();\n });\n }\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n throw new Error(\"Failed to get canvas context\");\n }\n ctx.drawImage(bitmap, 0, 0, width, height);\n bitmap.close();\n const mimeType = format === \"png\"\n ? \"image/png\"\n : format === \"webp\"\n ? \"image/webp\"\n : \"image/jpeg\";\n const dataUrl = canvas.toDataURL(mimeType, quality);\n const base64 = dataUrl.split(\",\")[1];\n return {\n base64,\n format,\n width,\n height,\n timestamp: Date.now(),\n };\n }\n async startRecording(options) {\n if (this.isRecording)\n throw new Error(\"Recording already in progress\");\n if (options?.fps !== undefined) {\n assertPositiveFiniteNumber(options.fps, \"fps\");\n }\n if (options?.bitrate !== undefined) {\n assertPositiveFiniteNumber(options.bitrate, \"bitrate\");\n }\n if (options?.maxDuration !== undefined) {\n assertPositiveFiniteNumber(options.maxDuration, \"maxDuration\");\n }\n if (options?.maxFileSize !== undefined) {\n assertPositiveFiniteNumber(options.maxFileSize, \"maxFileSize\");\n }\n const videoConstraints = {\n displaySurface: \"monitor\",\n };\n if (options?.fps)\n videoConstraints.frameRate = { ideal: options.fps };\n this.mediaStream = await getDisplayMedia({\n video: videoConstraints,\n audio: options?.captureSystemAudio !== false,\n });\n if (options?.captureMicrophone) {\n const micStream = await navigator.mediaDevices.getUserMedia({\n audio: true,\n });\n micStream.getAudioTracks().forEach((t) => {\n this.mediaStream?.addTrack(t);\n });\n }\n const mimeType = getSupportedMimeType();\n if (!mimeType) {\n this.mediaStream.getTracks().forEach((t) => {\n t.stop();\n });\n throw new Error(\"No supported video mime type found\");\n }\n const recorderOptions = { mimeType };\n if (options?.bitrate)\n recorderOptions.videoBitsPerSecond = options.bitrate;\n this.recordedChunks = [];\n this.mediaRecorder = new MediaRecorder(this.mediaStream, recorderOptions);\n this.mediaRecorder.ondataavailable = (event) => {\n if (event.data.size > 0) {\n this.recordedChunks.push(event.data);\n }\n };\n this.mediaRecorder.onerror = (event) => {\n this.notifyListeners(\"error\", {\n code: \"RECORDING_ERROR\",\n message: `Recording error: ${event.message || \"Unknown error\"}`,\n });\n };\n this.mediaStream.getVideoTracks()[0].addEventListener(\"ended\", () => {\n if (this.isRecording) {\n this.stopRecording().catch((err) => {\n console.error(\"[ScreenCapture] Auto-stop on track end failed:\", err);\n });\n }\n });\n this.recordingStartTime = Date.now();\n this.pausedDuration = 0;\n this.isRecording = true;\n this.isPaused = false;\n this.mediaRecorder.start(1000);\n this.notifyListeners(\"recordingState\", {\n isRecording: true,\n duration: 0,\n fileSize: 0,\n });\n let autoStopping = false;\n this.recordingStateInterval = setInterval(() => {\n if (!this.isRecording || this.isPaused || autoStopping)\n return;\n const duration = (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000;\n const fileSize = this.recordedChunks.reduce((acc, chunk) => acc + chunk.size, 0);\n this.notifyListeners(\"recordingState\", {\n isRecording: true,\n duration,\n fileSize,\n });\n const overLimit = (options?.maxDuration && duration >= options.maxDuration) ||\n (options?.maxFileSize && fileSize >= options.maxFileSize);\n if (overLimit) {\n autoStopping = true;\n this.stopRecording().catch((err) => {\n console.error(\"[ScreenCapture] Auto-stop recording failed:\", err);\n });\n }\n }, 500);\n }\n async stopRecording() {\n if (!this.isRecording || !this.mediaRecorder) {\n throw new Error(\"Not recording\");\n }\n return new Promise((resolve, reject) => {\n if (!this.mediaRecorder) {\n reject(new Error(\"MediaRecorder not initialized\"));\n return;\n }\n const duration = (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000;\n this.mediaRecorder.onstop = () => {\n if (this.recordingStateInterval) {\n clearInterval(this.recordingStateInterval);\n this.recordingStateInterval = null;\n }\n this.isRecording = false;\n this.isPaused = false;\n if (this.mediaStream) {\n this.mediaStream.getTracks().forEach((track) => {\n track.stop();\n });\n this.mediaStream = null;\n }\n const blob = new Blob(this.recordedChunks, {\n type: this.mediaRecorder?.mimeType || \"video/webm\",\n });\n const url = URL.createObjectURL(blob);\n const video = document.createElement(\"video\");\n video.src = url;\n video.onloadedmetadata = () => {\n resolve({\n path: url,\n duration,\n width: video.videoWidth,\n height: video.videoHeight,\n fileSize: blob.size,\n mimeType: this.mediaRecorder?.mimeType || \"video/webm\",\n });\n };\n video.onerror = () => {\n resolve({\n path: url,\n duration,\n width: 0,\n height: 0,\n fileSize: blob.size,\n mimeType: this.mediaRecorder?.mimeType || \"video/webm\",\n });\n };\n this.notifyListeners(\"recordingState\", {\n isRecording: false,\n duration,\n fileSize: blob.size,\n });\n };\n this.mediaRecorder.stop();\n });\n }\n async pauseRecording() {\n if (!this.isRecording || !this.mediaRecorder) {\n throw new Error(\"Not recording\");\n }\n if (this.isPaused) {\n return;\n }\n this.mediaRecorder.pause();\n this.isPaused = true;\n this.pauseStartTime = Date.now();\n const duration = (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000;\n const fileSize = this.recordedChunks.reduce((acc, chunk) => acc + chunk.size, 0);\n this.notifyListeners(\"recordingState\", {\n isRecording: true,\n duration,\n fileSize,\n });\n }\n async resumeRecording() {\n if (!this.isRecording || !this.mediaRecorder) {\n throw new Error(\"Not recording\");\n }\n if (!this.isPaused) {\n return;\n }\n this.pausedDuration += Date.now() - this.pauseStartTime;\n this.mediaRecorder.resume();\n this.isPaused = false;\n }\n async getRecordingState() {\n const duration = this.isRecording\n ? (Date.now() - this.recordingStartTime - this.pausedDuration) / 1000\n : 0;\n const fileSize = this.recordedChunks.reduce((acc, chunk) => acc + chunk.size, 0);\n return {\n isRecording: this.isRecording,\n duration,\n fileSize,\n };\n }\n /**\n * Check screen capture permissions.\n *\n * LIMITATION: The Screen Capture API (getDisplayMedia) does not support permission queries.\n * Unlike camera/microphone, there's no way to check if permission was previously granted.\n * Each call to getDisplayMedia always prompts the user.\n *\n * `screenCapture` will be:\n * - \"not_supported\": getDisplayMedia API not available\n * - \"prompt\": API available, but actual permission state is unknown (always requires prompt)\n */\n async checkPermissions() {\n let microphone = \"prompt\";\n try {\n const result = await navigator.permissions.query({\n name: \"microphone\",\n });\n microphone = result.state;\n }\n catch {\n // Permissions API may not support microphone query in this browser\n }\n // Screen capture permission cannot be queried - getDisplayMedia always prompts\n const screenCaptureStatus = hasDisplayMedia() ? \"prompt\" : \"not_supported\";\n return { screenCapture: screenCaptureStatus, microphone };\n }\n /**\n * Request screen capture permissions.\n *\n * LIMITATION: Screen capture (getDisplayMedia) cannot be pre-requested.\n * The user is prompted only when an actual capture is initiated.\n * This method only requests microphone permission for audio capture during recording.\n *\n * `screenCapture` will be:\n * - \"not_supported\": getDisplayMedia API not available\n * - \"prompt\": API available (permission prompt happens during actual capture)\n */\n async requestPermissions() {\n let microphone = \"denied\";\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n stream.getTracks().forEach((t) => {\n t.stop();\n });\n microphone = \"granted\";\n }\n catch {\n microphone = \"denied\";\n }\n // Cannot pre-request screen capture permission - it requires user gesture + actual capture\n const screenCaptureStatus = hasDisplayMedia() ? \"prompt\" : \"not_supported\";\n return { screenCapture: screenCaptureStatus, microphone };\n }\n async addListener(eventName, listenerFunc) {\n const entry = { eventName, callback: listenerFunc };\n this.pluginListeners.push(entry);\n return {\n remove: async () => {\n const i = this.pluginListeners.indexOf(entry);\n if (i >= 0)\n this.pluginListeners.splice(i, 1);\n },\n };\n }\n async removeAllListeners() {\n this.pluginListeners = [];\n }\n notifyListeners(eventName, data) {\n this.pluginListeners\n .filter((l) => l.eventName === eventName)\n .forEach((l) => {\n l.callback(data);\n });\n }\n}\n"],"names":["registerPlugin","WebPlugin"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elizaos/capacitor-screencapture",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.3-beta.2",
|
|
4
4
|
"description": "Captures screenshots and records the screen across web, mobile, and desktop.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"screen-capture",
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
16
|
"types": "./dist/esm/index.d.ts",
|
|
17
|
+
"bun": "./src/index.ts",
|
|
18
|
+
"development": "./src/index.ts",
|
|
17
19
|
"import": "./dist/esm/index.js",
|
|
18
20
|
"require": "./dist/plugin.cjs.js"
|
|
19
21
|
},
|
|
@@ -26,32 +28,30 @@
|
|
|
26
28
|
"dist/",
|
|
27
29
|
"ios/Sources/",
|
|
28
30
|
"ios/Plugin.xcodeproj/",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
+
"*.podspec",
|
|
32
|
+
"dist"
|
|
31
33
|
],
|
|
32
34
|
"author": "elizaOS",
|
|
33
35
|
"license": "MIT",
|
|
34
|
-
"dependencies": {
|
|
35
|
-
"@elizaos/app-core": "2.0.0-alpha.537"
|
|
36
|
-
},
|
|
36
|
+
"dependencies": {},
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
39
|
"url": "https://github.com/elizaOS/eliza"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
|
-
"build": "
|
|
43
|
-
"clean": "
|
|
44
|
-
"
|
|
45
|
-
"
|
|
42
|
+
"build": "node ../../packages/scripts/with-package-build-lock.mjs plugins/plugin-native-screencapture -- bun run build:unlocked",
|
|
43
|
+
"clean": "node ../../packages/scripts/rm-path-recursive.mjs dist",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"prepublishOnly": "bun run build",
|
|
46
|
+
"watch": "tsc --watch",
|
|
47
|
+
"build:unlocked": "bun run clean && tsc && bunx rollup -c rollup.config.mjs"
|
|
46
48
|
},
|
|
47
49
|
"devDependencies": {
|
|
48
|
-
"@capacitor/android": "^8.0.0",
|
|
49
50
|
"@capacitor/core": "^8.3.1",
|
|
50
|
-
"@capacitor/ios": "^8.0.0",
|
|
51
51
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
52
|
-
"rimraf": "^6.0.0",
|
|
53
52
|
"rollup": "^4.60.2",
|
|
54
|
-
"typescript": "^6.0.
|
|
53
|
+
"typescript": "^6.0.3",
|
|
54
|
+
"vitest": "^4.0.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"@capacitor/core": "^8.3.1"
|
|
@@ -80,5 +80,6 @@
|
|
|
80
80
|
"ios": true,
|
|
81
81
|
"android": true
|
|
82
82
|
}
|
|
83
|
-
}
|
|
83
|
+
},
|
|
84
|
+
"gitHead": "82fe0f44215954c2417328203f5bd6510985c1fc"
|
|
84
85
|
}
|
package/electrobun/src/index.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ScreenCapture Plugin for Electrobun
|
|
3
|
-
*
|
|
4
|
-
* Uses the web implementation with a native desktop screenshot
|
|
5
|
-
* fast-path through the shared desktop bridge when available.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { invokeDesktopBridgeRequest } from "@elizaos/app-core";
|
|
9
|
-
import type {
|
|
10
|
-
ScreenCapturePlugin,
|
|
11
|
-
ScreenshotOptions,
|
|
12
|
-
ScreenshotResult,
|
|
13
|
-
} from "../../src/definitions";
|
|
14
|
-
import { ScreenCaptureWeb } from "../../src/web";
|
|
15
|
-
|
|
16
|
-
interface NativeScreenshotResponse {
|
|
17
|
-
available: boolean;
|
|
18
|
-
data?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class ScreenCaptureElectrobun
|
|
22
|
-
extends ScreenCaptureWeb
|
|
23
|
-
implements ScreenCapturePlugin
|
|
24
|
-
{
|
|
25
|
-
async captureScreenshot(
|
|
26
|
-
options?: ScreenshotOptions,
|
|
27
|
-
): Promise<ScreenshotResult> {
|
|
28
|
-
try {
|
|
29
|
-
const screenshot =
|
|
30
|
-
await invokeDesktopBridgeRequest<NativeScreenshotResponse>({
|
|
31
|
-
rpcMethod: "screencaptureTakeScreenshot",
|
|
32
|
-
ipcChannel: "screencapture:takeScreenshot",
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
if (screenshot?.available && screenshot.data) {
|
|
36
|
-
return await this.toScreenshotResult(screenshot.data, options);
|
|
37
|
-
}
|
|
38
|
-
} catch (error) {
|
|
39
|
-
console.warn(
|
|
40
|
-
"[ScreenCapture] native screenshot RPC failed, falling back to getDisplayMedia:",
|
|
41
|
-
error,
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return super.captureScreenshot(options);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private async toScreenshotResult(
|
|
49
|
-
dataUrl: string,
|
|
50
|
-
options?: ScreenshotOptions,
|
|
51
|
-
): Promise<ScreenshotResult> {
|
|
52
|
-
const image = await this.loadImage(dataUrl);
|
|
53
|
-
const width = image.naturalWidth || image.width;
|
|
54
|
-
const height = image.naturalHeight || image.height;
|
|
55
|
-
const format = options?.format ?? "png";
|
|
56
|
-
|
|
57
|
-
if (format === "png") {
|
|
58
|
-
return {
|
|
59
|
-
base64: dataUrl.split(",")[1] ?? "",
|
|
60
|
-
format,
|
|
61
|
-
width,
|
|
62
|
-
height,
|
|
63
|
-
timestamp: Date.now(),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const canvas = document.createElement("canvas");
|
|
68
|
-
canvas.width = width;
|
|
69
|
-
canvas.height = height;
|
|
70
|
-
const ctx = canvas.getContext("2d");
|
|
71
|
-
if (!ctx) {
|
|
72
|
-
throw new Error("Failed to get canvas context");
|
|
73
|
-
}
|
|
74
|
-
ctx.drawImage(image, 0, 0, width, height);
|
|
75
|
-
|
|
76
|
-
const quality = (options?.quality ?? 100) / 100;
|
|
77
|
-
const mimeType = format === "webp" ? "image/webp" : "image/jpeg";
|
|
78
|
-
const convertedUrl = canvas.toDataURL(mimeType, quality);
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
base64: convertedUrl.split(",")[1] ?? "",
|
|
82
|
-
format,
|
|
83
|
-
width,
|
|
84
|
-
height,
|
|
85
|
-
timestamp: Date.now(),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private async loadImage(dataUrl: string): Promise<HTMLImageElement> {
|
|
90
|
-
const image = new Image();
|
|
91
|
-
await new Promise<void>((resolve, reject) => {
|
|
92
|
-
image.onload = () => resolve();
|
|
93
|
-
image.onerror = () =>
|
|
94
|
-
reject(new Error("Failed to load screenshot image"));
|
|
95
|
-
image.src = dataUrl;
|
|
96
|
-
});
|
|
97
|
-
return image;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Export the plugin instance
|
|
102
|
-
export const ScreenCapture = new ScreenCaptureElectrobun();
|
package/electrobun/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"lib": ["ES2020", "DOM"],
|
|
7
|
-
"declaration": true,
|
|
8
|
-
"strict": true,
|
|
9
|
-
"noUnusedLocals": true,
|
|
10
|
-
"noUnusedParameters": true,
|
|
11
|
-
"esModuleInterop": true,
|
|
12
|
-
"skipLibCheck": true,
|
|
13
|
-
"outDir": "./dist",
|
|
14
|
-
"rootDir": "./src"
|
|
15
|
-
},
|
|
16
|
-
"include": ["src/**/*.ts"],
|
|
17
|
-
"exclude": ["node_modules", "dist"]
|
|
18
|
-
}
|