@ruc-lib/screen-recorder 3.1.0 → 3.1.1

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.
@@ -1,636 +0,0 @@
1
- import * as i0 from '@angular/core';
2
- import { Injectable, ViewChild, Input, Component } from '@angular/core';
3
- import * as i2 from '@angular/platform-browser';
4
- import { BehaviorSubject, Subject, timer, Subscription } from 'rxjs';
5
- import { tap, takeWhile } from 'rxjs/operators';
6
- import * as i4 from '@angular/common';
7
- import { CommonModule } from '@angular/common';
8
-
9
- var ScreenRecorderPosition;
10
- (function (ScreenRecorderPosition) {
11
- ScreenRecorderPosition["Top"] = "top";
12
- ScreenRecorderPosition["Bottom"] = "bottom";
13
- ScreenRecorderPosition["Left"] = "left";
14
- ScreenRecorderPosition["Right"] = "right";
15
- })(ScreenRecorderPosition || (ScreenRecorderPosition = {}));
16
- var DisplayFormat;
17
- (function (DisplayFormat) {
18
- DisplayFormat["Icon"] = "icon";
19
- DisplayFormat["Text"] = "text";
20
- })(DisplayFormat || (DisplayFormat = {}));
21
- var RecordingState;
22
- (function (RecordingState) {
23
- RecordingState["Idle"] = "idle";
24
- RecordingState["Recording"] = "recording";
25
- RecordingState["Paused"] = "paused";
26
- RecordingState["Stopped"] = "stopped";
27
- })(RecordingState || (RecordingState = {}));
28
-
29
- class ScreenRecorderConstants {
30
- constructor() {
31
- this.DEFAULT_ICONS = {
32
- record: 'fiber_manual_record', // Material Icon name
33
- stop: 'stop',
34
- pause: 'pause',
35
- resume: 'play_arrow',
36
- play: 'play_circle_filled',
37
- download: 'download',
38
- audioOn: 'volume_up',
39
- audioOff: 'volume_off',
40
- };
41
- this.DEFAULT_LABELS = {
42
- recordButton: 'Start Recording',
43
- stopButton: 'Stop Recording',
44
- pauseButton: 'Pause Recording',
45
- resumeButton: 'Resume Recording',
46
- audioToggleButton: 'Toggle Audio',
47
- downloadButton: 'Download Recording',
48
- playbackVideo: 'Screen Recording Playback',
49
- };
50
- this.INACTIVE = 'inactive';
51
- this.AUDIO_ON = 'Audio ON';
52
- this.AUDIO_OFF = 'Audio OFF';
53
- this.RECORDING_IN_PROGRESS_ERROR = 'Recording is already in progress.';
54
- this.FAILED_TO_GET_DISPLAY_MEDIA_STREAM_ERROR = 'Failed to get display media stream';
55
- this.NO_SUPPORT_MIME = 'No supported MIME type found for MediaRecorder.';
56
- this.NO_RECORDING_DOWNLOAD = 'No recording available to download.';
57
- this.MEDIA_ERROR = 'MediaRecorder error';
58
- this.PERMISSION_DENIED = "Error starting screen recording: NotAllowedError - Permission denied";
59
- this.NO_SUPPORT = 'Screen recording is not supported in this browser.';
60
- }
61
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ScreenRecorderConstants, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
62
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ScreenRecorderConstants, providedIn: 'root' }); }
63
- }
64
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ScreenRecorderConstants, decorators: [{
65
- type: Injectable,
66
- args: [{
67
- providedIn: 'root',
68
- }]
69
- }], ctorParameters: () => [] });
70
-
71
- /* eslint-disable @typescript-eslint/no-inferrable-types */
72
- //import { ScreenRecorderConstants } from '../screen-recorder/screen-recorder-constant';
73
- class ScreenRecorderService {
74
- constructor(screenRecordingConstant) {
75
- this.screenRecordingConstant = screenRecordingConstant;
76
- this.stream = null;
77
- this.mediaRecorder = null;
78
- this.recordedBlobs = [];
79
- this.currentRecordedTime = 0;
80
- this.recordingStateSubject = new BehaviorSubject(RecordingState.Idle);
81
- this.recordingState$ = this.recordingStateSubject.asObservable();
82
- this.recordedTimeSubject = new BehaviorSubject(0);
83
- this.recordedTime$ = this.recordedTimeSubject.asObservable();
84
- this.recordedUrlSubject = new BehaviorSubject(null);
85
- this.recordedUrl$ = this.recordedUrlSubject.asObservable();
86
- this.recordingTimestampSubject = new BehaviorSubject('');
87
- this.recordingTimestamp$ = this.recordingTimestampSubject.asObservable();
88
- this.errorSubject = new Subject();
89
- this.error$ = this.errorSubject.asObservable();
90
- }
91
- /**
92
- * Starts the screen recording with optional audio
93
- *
94
- * @param options - Configuration options for recording
95
- * @param options.audio - Whether to include audio in the recording
96
- *
97
- * @throws Error if recording is already in progress or if media access is denied
98
- * @returns Promise<void>
99
- */
100
- async startRecording(options) {
101
- if (this.recordingStateSubject.value === RecordingState.Recording || this.recordingStateSubject.value === RecordingState.Paused) {
102
- this.handleError(this.screenRecordingConstant.RECORDING_IN_PROGRESS_ERROR);
103
- return;
104
- }
105
- this.cleanupPreviousRecording();
106
- try {
107
- // The browser's native picker handles "specific area" selection (window, tab, screen).
108
- const displayStream = await navigator.mediaDevices.getDisplayMedia({
109
- video: true
110
- });
111
- // If audio is enabled, get the audio stream
112
- let audioStream = null;
113
- if (options.audio) {
114
- audioStream = await navigator.mediaDevices.getUserMedia({
115
- audio: {
116
- echoCancellation: true,
117
- noiseSuppression: true,
118
- sampleRate: 44100
119
- }
120
- });
121
- }
122
- // Combine the streams if audio is enabled
123
- if (audioStream) {
124
- this.stream = new MediaStream([...displayStream.getTracks(), ...audioStream.getTracks()]);
125
- }
126
- else {
127
- this.stream = displayStream;
128
- }
129
- // Listen for when the user stops sharing via browser UI
130
- this.stream.getVideoTracks()[0].onended = () => {
131
- if (this.recordingStateSubject.value === RecordingState.Recording || this.recordingStateSubject.value === RecordingState.Paused) {
132
- this.stopRecordingInternal();
133
- }
134
- };
135
- // Handle errors
136
- if (!this.stream) {
137
- this.handleError(this.screenRecordingConstant.FAILED_TO_GET_DISPLAY_MEDIA_STREAM_ERROR);
138
- return;
139
- }
140
- // Set recording timestamp
141
- const now = new Date();
142
- const timestamp = now.toLocaleString('en-IN', {
143
- year: 'numeric',
144
- month: '2-digit',
145
- day: '2-digit',
146
- hour: '2-digit',
147
- minute: '2-digit',
148
- hour12: true
149
- });
150
- this.recordingTimestampSubject.next(timestamp);
151
- this.recordedBlobs = [];
152
- const mimeType = this.getSupportedMimeType();
153
- if (!mimeType) {
154
- this.handleError(this.screenRecordingConstant.NO_SUPPORT_MIME);
155
- this.stream?.getTracks().forEach(track => track.stop());
156
- this.stream = null;
157
- return;
158
- }
159
- // Create a canvas for timestamp overlay
160
- const canvas = document.createElement('canvas');
161
- const videoTrack = this.stream?.getVideoTracks()[0];
162
- if (videoTrack) {
163
- const video = document.createElement('video');
164
- video.srcObject = this.stream;
165
- // Wait for video to load metadata
166
- video.onloadedmetadata = () => {
167
- canvas.width = video.videoWidth;
168
- canvas.height = video.videoHeight;
169
- const ctx = canvas.getContext('2d');
170
- if (ctx) {
171
- // Draw video frame
172
- ctx.drawImage(video, 0, 0);
173
- // Draw timestamp
174
- ctx.font = '16px Arial';
175
- ctx.fillStyle = 'white';
176
- ctx.textAlign = 'right';
177
- ctx.textBaseline = 'top';
178
- ctx.fillText(`Recorded on: ${this.recordingTimestampSubject.value}`, canvas.width - 10, 10);
179
- }
180
- };
181
- }
182
- // Create a new stream with the canvas
183
- const canvasStream = canvas.captureStream();
184
- const combinedStream = new MediaStream([...this.stream?.getTracks() ?? [], canvasStream.getVideoTracks()[0]]);
185
- this.mediaRecorder = new MediaRecorder(combinedStream, { mimeType });
186
- this.mediaRecorder.ondataavailable = (event) => {
187
- if (event.data && event.data.size > 0) {
188
- this.recordedBlobs.push(event.data);
189
- }
190
- };
191
- this.mediaRecorder.onstop = () => {
192
- const superBuffer = new Blob(this.recordedBlobs, { type: mimeType });
193
- const url = window.URL.createObjectURL(superBuffer);
194
- this.recordedUrlSubject.next(url);
195
- this.recordingStateSubject.next(RecordingState.Stopped);
196
- this.stopTimer();
197
- };
198
- this.mediaRecorder.onerror = (event) => {
199
- const errorEvent = event; // More specific type if available
200
- let message = this.screenRecordingConstant.MEDIA_ERROR;
201
- if (errorEvent.error && errorEvent.error.name) {
202
- message += `: ${errorEvent.error.name}`;
203
- if (errorEvent.error.message)
204
- message += ` - ${errorEvent.error.message}`;
205
- }
206
- this.handleError(message);
207
- this.recordingStateSubject.next(RecordingState.Idle);
208
- this.stopTimer();
209
- };
210
- this.mediaRecorder.start(); // Start recording
211
- this.recordingStateSubject.next(RecordingState.Recording);
212
- this.startTimer();
213
- }
214
- catch (err) {
215
- this.handleError(`Error starting screen recording: ${err.name} - ${err.message}`);
216
- this.recordingStateSubject.next(RecordingState.Idle);
217
- }
218
- }
219
- /**
220
- * Gets the first supported MIME type for MediaRecorder
221
- *
222
- * @returns string|null - The supported MIME type or null if none found
223
- */
224
- getSupportedMimeType() {
225
- const mimeTypes = [
226
- 'video/webm;codecs=vp9,opus',
227
- 'video/webm;codecs=vp8,opus',
228
- 'video/webm;codecs=h264,opus',
229
- 'video/mp4;codecs=h264,aac', // MP4 might have broader compatibility but less browser support for recording
230
- 'video/webm',
231
- ];
232
- for (const mimeType of mimeTypes) {
233
- if (MediaRecorder.isTypeSupported(mimeType)) {
234
- return mimeType;
235
- }
236
- }
237
- return null;
238
- }
239
- /**
240
- * Internal method to stop the recording process
241
- *
242
- * @private
243
- * @returns void
244
- */
245
- stopRecordingInternal() {
246
- if (this.mediaRecorder && (this.recordingStateSubject.value === RecordingState.Recording || this.recordingStateSubject.value === RecordingState.Paused)) {
247
- this.mediaRecorder.stop();
248
- }
249
- this.stream?.getTracks().forEach(track => track.stop());
250
- this.stream = null;
251
- // State will be updated by onstop handler
252
- }
253
- /**
254
- *
255
- * @returns Promise<void>
256
- * @throws Error if recording is not in progress
257
- */
258
- stopRecording() {
259
- this.stopRecordingInternal();
260
- }
261
- /**
262
- * Pauses the current recording
263
- *
264
- * @returns Promise<void>
265
- * @throws Error if recording is not in progress
266
- */
267
- pauseRecording() {
268
- if (this.mediaRecorder && this.recordingStateSubject.value === RecordingState.Recording) {
269
- this.mediaRecorder.pause();
270
- this.recordingStateSubject.next(RecordingState.Paused);
271
- this.stopTimer(); // Pauses the timer display
272
- }
273
- }
274
- /**
275
- * Resumes a paused recording
276
- *
277
- * @returns Promise<void>
278
- * @throws Error if recording is not paused
279
- */
280
- resumeRecording() {
281
- if (this.mediaRecorder && this.recordingStateSubject.value === RecordingState.Paused) {
282
- this.mediaRecorder.resume();
283
- this.recordingStateSubject.next(RecordingState.Recording);
284
- this.startTimer(this.currentRecordedTime); // Resumes timer display
285
- }
286
- }
287
- /**
288
- *
289
- * @param fileName
290
- */
291
- downloadRecording(fileName = 'recording.webm') {
292
- const url = this.recordedUrlSubject.value;
293
- const timestamp = this.recordingTimestampSubject.value;
294
- if (url && timestamp) {
295
- // Format the timestamp for filename (remove spaces and special characters)
296
- const formattedTimestamp = timestamp.replace(/[^a-zA-Z0-9]/g, '_');
297
- // Create filename with timestamp
298
- const fileExtension = fileName.split('.').pop() || 'webm';
299
- const newFileName = `screen_recording_${formattedTimestamp}.${fileExtension}`;
300
- const a = document.createElement('a');
301
- a.style.display = 'none';
302
- a.href = url;
303
- a.download = newFileName;
304
- document.body.appendChild(a);
305
- a.click();
306
- document.body.removeChild(a);
307
- // No need to revoke URL here if user might want to play it again.
308
- // Revoke on cleanup or new recording.
309
- }
310
- else {
311
- this.handleError(this.screenRecordingConstant.NO_RECORDING_DOWNLOAD);
312
- }
313
- }
314
- /**
315
- * Starts the recording timer
316
- *
317
- * @param startTime - Optional start time for the timer
318
- * @private
319
- * @returns void
320
- */
321
- startTimer(startTime = 0) {
322
- this.stopTimer(); // Ensure no multiple timers
323
- this.currentRecordedTime = startTime;
324
- this.recordedTimeSubject.next(this.currentRecordedTime);
325
- this.timerSubscription = timer(0, 1000)
326
- .pipe(tap(() => {
327
- if (this.recordingStateSubject.value === RecordingState.Recording) {
328
- this.currentRecordedTime++;
329
- this.recordedTimeSubject.next(this.currentRecordedTime);
330
- }
331
- }), takeWhile(() => this.recordingStateSubject.value === RecordingState.Recording))
332
- .subscribe();
333
- }
334
- /**
335
- * Stops the recording timer
336
- *
337
- * @private
338
- * @returns void
339
- */
340
- stopTimer() {
341
- if (this.timerSubscription) {
342
- this.timerSubscription.unsubscribe();
343
- this.timerSubscription = null;
344
- }
345
- }
346
- /**
347
- * Cleans up resources from previous recording
348
- *
349
- * @private
350
- * @returns void
351
- */
352
- cleanupPreviousRecording() {
353
- if (this.recordedUrlSubject.value) {
354
- window.URL.revokeObjectURL(this.recordedUrlSubject.value);
355
- this.recordedUrlSubject.next(null);
356
- }
357
- this.recordedBlobs = [];
358
- this.currentRecordedTime = 0;
359
- this.recordedTimeSubject.next(0);
360
- if (this.stream) {
361
- this.stream.getTracks().forEach(track => track.stop());
362
- this.stream = null;
363
- }
364
- if (this.mediaRecorder && this.mediaRecorder.state !== this.screenRecordingConstant.INACTIVE) {
365
- this.mediaRecorder.stop();
366
- }
367
- this.mediaRecorder = null;
368
- }
369
- /**
370
- * Resets the recording state to idle
371
- *
372
- * @returns void
373
- */
374
- resetToIdle() {
375
- this.cleanupPreviousRecording();
376
- this.recordingStateSubject.next(RecordingState.Idle);
377
- this.errorSubject.next(''); // Clear any previous errors
378
- }
379
- /**
380
- * Handles and broadcasts recording errors
381
- *
382
- * @private
383
- * @param error - The error message to handle
384
- * @returns void
385
- */
386
- handleError(error) {
387
- this.errorSubject.next(error);
388
- }
389
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ScreenRecorderService, deps: [{ token: ScreenRecorderConstants }], target: i0.ɵɵFactoryTarget.Injectable }); }
390
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ScreenRecorderService, providedIn: 'root' }); }
391
- }
392
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ScreenRecorderService, decorators: [{
393
- type: Injectable,
394
- args: [{
395
- providedIn: 'root', // Or provide in your module if preferred
396
- }]
397
- }], ctorParameters: () => [{ type: ScreenRecorderConstants }] });
398
-
399
- /* eslint-disable @typescript-eslint/no-inferrable-types */
400
- class RuclibScreenRecorderComponent {
401
- set rucInputData(value) {
402
- this._options = {
403
- position: ScreenRecorderPosition.Bottom,
404
- displayFormat: DisplayFormat.Icon,
405
- showAudioToggle: true,
406
- defaultAudioState: true,
407
- downloadFileName: 'screen-recording.webm',
408
- icons: { ...this.screenRecordingConstant.DEFAULT_ICONS },
409
- showTimer: true,
410
- showPauseResume: true,
411
- showPlaybackControls: true,
412
- showDownloadButton: true,
413
- useUTCForTimer: false,
414
- allowSpecificAreaSelection: true, // Default to allowing browser's native picker
415
- accessibilityLabels: { ...this.screenRecordingConstant.DEFAULT_LABELS },
416
- ...value,
417
- };
418
- // Deep merge icons and labels
419
- if (value.icons) {
420
- this._options.icons = { ...this.screenRecordingConstant.DEFAULT_ICONS, ...value.icons };
421
- }
422
- if (value.accessibilityLabels) {
423
- this._options.accessibilityLabels = { ...this.screenRecordingConstant.DEFAULT_LABELS, ...value.accessibilityLabels };
424
- }
425
- this.isAudioEnabled = this._options.defaultAudioState ?? true;
426
- }
427
- get rucInputData() {
428
- return this._options;
429
- }
430
- /**
431
- * Checks if recording is currently in progress
432
- *
433
- * @returns boolean - True if recording is in progress, false otherwise
434
- */
435
- isRecordingInProgress() {
436
- return this.recordingState === RecordingState.Recording;
437
- }
438
- /**
439
- * Gets the appropriate icon for recording based on audio state and settings
440
- * @returns string - The icon name to display
441
- */
442
- getRecordingIcon() {
443
- if (this.isAudioEnabled && this.rucInputData.showAudioToggle) {
444
- return this.getIcon('recordWithAudio');
445
- }
446
- return this.getIcon('record');
447
- }
448
- constructor(screenRecorderService, sanitizer, cdr, screenRecordingConstant) {
449
- this.screenRecorderService = screenRecorderService;
450
- this.sanitizer = sanitizer;
451
- this.cdr = cdr;
452
- this.screenRecordingConstant = screenRecordingConstant;
453
- this.recordingState = RecordingState.Idle;
454
- this.recordedTime = 0;
455
- this.formattedTime = '00:00:00';
456
- this.safeRecordedUrl = null;
457
- this.recordingTimestamp = '';
458
- this.errorMessage = null;
459
- this.isBrowserSupported = true;
460
- this.isAudioEnabled = true;
461
- // Expose enums to template
462
- this.DisplayFormat = DisplayFormat;
463
- this.RecordingState = RecordingState;
464
- this.ScreenRecorderPosition = ScreenRecorderPosition;
465
- this.subscriptions = new Subscription();
466
- // Initialize with default options. The setter will override if an input is provided.
467
- if (!this._options) {
468
- this.rucInputData = {}; // Trigger setter with empty object to apply defaults
469
- }
470
- }
471
- ngOnInit() {
472
- this.subscriptions.add(this.screenRecorderService.recordingState$.subscribe((state) => {
473
- this.recordingState = state;
474
- if (state === RecordingState.Idle || state === RecordingState.Stopped) {
475
- // Ensure audio toggle is reset to default if needed, or based on user action
476
- }
477
- this.cdr.detectChanges();
478
- }));
479
- this.subscriptions.add(this.screenRecorderService.recordingTimestamp$.subscribe((timestamp) => {
480
- this.recordingTimestamp = timestamp;
481
- this.cdr.detectChanges();
482
- }));
483
- this.subscriptions.add(this.screenRecorderService.recordedTime$.subscribe((time) => {
484
- this.recordedTime = time;
485
- this.formattedTime = this.formatTimeDisplay(time);
486
- this.cdr.detectChanges();
487
- }));
488
- this.subscriptions.add(this.screenRecorderService.recordedUrl$.subscribe((url) => {
489
- this.safeRecordedUrl = url ? this.sanitizer.bypassSecurityTrustUrl(url) : null;
490
- this.cdr.detectChanges();
491
- }));
492
- this.subscriptions.add(this.screenRecorderService.error$.subscribe((error) => {
493
- if (error != this.screenRecordingConstant.PERMISSION_DENIED) {
494
- this.errorMessage = error;
495
- }
496
- this.cdr.detectChanges();
497
- }));
498
- }
499
- ngOnDestroy() {
500
- if (this.subscriptions) {
501
- this.subscriptions.unsubscribe();
502
- }
503
- }
504
- /**
505
- * Toggles the recording state between start and stop
506
- *
507
- * @returns Promise<void>
508
- */
509
- async toggleRecord() {
510
- this.errorMessage = null; // Clear previous errors
511
- if (this.recordingState === RecordingState.Idle || this.recordingState === RecordingState.Stopped) {
512
- // Set recording timestamp when starting recording
513
- const now = new Date();
514
- this.recordingTimestamp = now.toLocaleString('en-IN', {
515
- year: 'numeric',
516
- month: '2-digit',
517
- day: '2-digit',
518
- hour: '2-digit',
519
- minute: '2-digit',
520
- hour12: true
521
- });
522
- await this.screenRecorderService.startRecording({ audio: this.isAudioEnabled });
523
- }
524
- else {
525
- this.screenRecorderService.stopRecording();
526
- }
527
- }
528
- /**
529
- * Toggles between pause and resume states during recording
530
- *
531
- * @returns void
532
- */
533
- togglePauseResume() {
534
- this.errorMessage = null;
535
- if (this.recordingState === RecordingState.Recording) {
536
- this.screenRecorderService.pauseRecording();
537
- }
538
- else if (this.recordingState === RecordingState.Paused) {
539
- this.screenRecorderService.resumeRecording();
540
- }
541
- }
542
- /**
543
- * Checks if recording can be resumed
544
- *
545
- * @returns boolean - True if recording can be resumed, false otherwise
546
- */
547
- canResume() {
548
- return this.recordingState === RecordingState.Paused;
549
- }
550
- /**
551
- * Downloads the recorded video file
552
- *
553
- * @returns void
554
- */
555
- onDownload() {
556
- this.errorMessage = null;
557
- this.screenRecorderService.downloadRecording(this.rucInputData.downloadFileName);
558
- }
559
- /**
560
- * Checks if recording can be downloaded
561
- *
562
- * @returns boolean - True if recording can be downloaded, false otherwise
563
- */
564
- canDownload() {
565
- return this.recordingState === RecordingState.Stopped;
566
- }
567
- /**
568
- * Toggles audio recording on/off
569
- *
570
- * @returns void
571
- */
572
- toggleAudio() {
573
- this.isAudioEnabled = !this.isAudioEnabled;
574
- this.cdr.detectChanges();
575
- }
576
- /**
577
- *
578
- * @param iconName
579
- * @returns
580
- */
581
- getIcon(iconName) {
582
- return this.rucInputData.icons?.[iconName] || this.screenRecordingConstant.DEFAULT_ICONS[iconName];
583
- }
584
- /**
585
- *
586
- * @param labelName
587
- * @returns
588
- */
589
- getLabel(labelName) {
590
- return this.rucInputData.accessibilityLabels?.[labelName] || this.screenRecordingConstant.DEFAULT_LABELS[labelName];
591
- }
592
- /**
593
- *
594
- * @param time - The time in seconds to format
595
- * @returns string - Formatted time string (HH:MM:SS)
596
- */
597
- formatTimeDisplay(time) {
598
- const hours = Math.floor(time / 3600);
599
- const minutes = Math.floor((time % 3600) / 60);
600
- const seconds = time % 60;
601
- const pad = (num) => num.toString().padStart(2, '0');
602
- if (this.rucInputData && this.rucInputData.useUTCForTimer) {
603
- // This is a simple duration format, not a specific UTC time.
604
- // For actual UTC time of day, you'd use new Date().toUTCString() or similar.
605
- // Assuming timer should show elapsed time formatted as HH:MM:SS.
606
- return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
607
- }
608
- return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
609
- }
610
- // Method to reset the component and service to initial state
611
- reset() {
612
- this.screenRecorderService.resetToIdle();
613
- this.isAudioEnabled = this.rucInputData.defaultAudioState ?? true;
614
- this.cdr.detectChanges();
615
- }
616
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RuclibScreenRecorderComponent, deps: [{ token: ScreenRecorderService }, { token: i2.DomSanitizer }, { token: i0.ChangeDetectorRef }, { token: ScreenRecorderConstants }], target: i0.ɵɵFactoryTarget.Component }); }
617
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: RuclibScreenRecorderComponent, isStandalone: true, selector: "uxp-ruclib-screen-recorder", inputs: { customTheme: "customTheme", rucInputData: "rucInputData" }, viewQueries: [{ propertyName: "videoPlayer", first: true, predicate: ["videoPlayer"], descendants: true }], ngImport: i0, template: "@if (isBrowserSupported) {\r\n <div class=\"screen-recorder-controls\" [ngClass]=\"customTheme + ' position-' + (rucInputData.position || ScreenRecorderPosition.Bottom)\"\r\n >\r\n <!-- Error Message -->\r\n @if (errorMessage) {\r\n <div class=\"recorder-error\">\r\n {{ errorMessage }}\r\n </div>\r\n }\r\n <!-- Record/Stop Button -->\r\n @if (recordingState === RecordingState.Idle || recordingState === RecordingState.Stopped) {\r\n <button\r\n (click)=\"toggleRecord()\"\r\n [attr.aria-label]=\"getLabel('recordButton')\" [attr.aria-pressed]=\"false\" class=\"recorder-button record-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">{{ getRecordingIcon() || getIcon('record') }}</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n {{ getLabel('recordButton')\r\n }}\r\n }\r\n </button>\r\n }\r\n @if (recordingState === RecordingState.Recording || recordingState === RecordingState.Paused) {\r\n <button\r\n (click)=\"toggleRecord()\" [attr.aria-label]=\"getLabel('stopButton')\" [attr.aria-pressed]=\"true\"\r\n class=\"recorder-button stop-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">{{ getIcon('stop') }}</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n {{ getLabel('stopButton') }}\r\n }\r\n </button>\r\n }\r\n <!-- Pause/Resume Button -->\r\n @if (rucInputData.showPauseResume && recordingState === RecordingState.Recording) {\r\n <button\r\n (click)=\"togglePauseResume()\" [attr.aria-label]=\"getLabel('pauseButton')\" [attr.aria-pressed]=\"false\"\r\n class=\"recorder-button pause-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">{{ getIcon('pause') }}</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n {{ getLabel('pauseButton') }}\r\n }\r\n </button>\r\n }\r\n @if (rucInputData.showPauseResume && recordingState === RecordingState.Paused) {\r\n <button\r\n (click)=\"togglePauseResume()\" [attr.aria-label]=\"getLabel('resumeButton')\" [attr.aria-pressed]=\"true\"\r\n class=\"recorder-button resume-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">{{ getIcon('resume') }}</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n {{ getLabel('resumeButton')\r\n }}\r\n }\r\n </button>\r\n }\r\n <!-- Audio Toggle Button -->\r\n @if (rucInputData.showAudioToggle && (recordingState === RecordingState.Idle || recordingState === RecordingState.Stopped)) {\r\n <button\r\n (click)=\"toggleAudio()\" [attr.aria-label]=\"getLabel('audioToggleButton')\" [attr.aria-pressed]=\"isAudioEnabled\"\r\n class=\"recorder-button audio-toggle-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">{{ isAudioEnabled ? getIcon('audioOn') : getIcon('audioOff') }}</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n {{ isAudioEnabled ? screenRecordingConstant.AUDIO_ON : screenRecordingConstant.AUDIO_OFF }}\r\n }\r\n </button>\r\n }\r\n <!-- Timer -->\r\n @if (rucInputData.showTimer && (recordingState === RecordingState.Recording || recordingState === RecordingState.Paused)) {\r\n <div\r\n class=\"recorder-timer\" aria-live=\"polite\" aria-atomic=\"true\">\r\n {{ formattedTime }}\r\n </div>\r\n }\r\n <!-- Download Button -->\r\n @if (rucInputData.showDownloadButton && recordingState === RecordingState.Stopped && safeRecordedUrl) {\r\n <button\r\n (click)=\"onDownload()\" [attr.aria-label]=\"getLabel('downloadButton')\" class=\"recorder-button download-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">{{ getIcon('download') }}</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n {{ getLabel('downloadButton')\r\n }}\r\n }\r\n </button>\r\n }\r\n <!-- Playback Area -->\r\n @if (rucInputData.showPlaybackControls && recordingState === RecordingState.Stopped && safeRecordedUrl) {\r\n <div\r\n class=\"playback-area\">\r\n <video #videoPlayer [src]=\"safeRecordedUrl\" controls [attr.aria-label]=\"getLabel('playbackVideo')\"></video>\r\n <div class=\"video-timer-overlay\">\r\n <span>Recorded on: {{ recordingTimestamp }}</span>\r\n </div>\r\n </div>\r\n }\r\n <!-- Reset Button (optional, good for testing/clearing state) -->\r\n @if (recordingState === RecordingState.Stopped && safeRecordedUrl) {\r\n <button (click)=\"reset()\"\r\n aria-label=\"New Recording\" class=\"recorder-button reset-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">refresh</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n New\r\n }\r\n </button>\r\n }\r\n </div>\r\n}\r\n\r\n@if (!isBrowserSupported) {\r\n <div class=\"recorder-notsupported\" [ngClass]=\"customTheme\">\r\n {{screenRecordingConstant.NO_SUPPORT}}\r\n </div>\r\n}\r\n", styles: [":host{display:block;font-family:sans-serif}.screen-recorder-controls{display:flex;gap:8px;align-items:center;padding:8px;border-radius:4px;position:relative;z-index:1000}.screen-recorder-controls.position-top-left{margin-top:10px;margin-left:10px;flex-direction:row}.screen-recorder-controls.position-top-right{margin-top:10px;margin-right:10px;flex-direction:row}.screen-recorder-controls.position-bottom-left{margin-bottom:10px;margin-left:10px;flex-direction:row}.screen-recorder-controls.position-bottom-right{margin-bottom:10px;margin-right:10px;flex-direction:row}.screen-recorder-controls.position-left,.screen-recorder-controls.position-right{float:right}.recorder-button{padding:8px 12px;border:1px solid #ccc;border-radius:4px;background-color:#fff;cursor:pointer;display:inline-flex;align-items:center;gap:4px}.recorder-button:hover{background-color:#e9e9e9}.recorder-button:active{background-color:#d0d0d0}.recorder-button.record-button{color:red}.material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:20px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:\"liga\"}.recorder-timer{padding:0 8px;font-variant-numeric:tabular-nums}.playback-area{width:100%;max-width:100%;margin-top:1rem;position:relative}.playback-area video{width:100%;height:auto;max-width:100%}.playback-area .video-timer-overlay{position:absolute;top:10px;right:10px;background:#000000b3;color:#fff;padding:5px 10px;border-radius:4px;font-size:14px;z-index:10}.recorder-error{color:red;padding:8px;border:1px solid red;background-color:#ffe0e0;border-radius:4px;margin-bottom:8px}.recorder-notsupported{padding:10px;background-color:#fff3cd;border:1px solid #ffeeba;color:#856404;border-radius:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
618
- }
619
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RuclibScreenRecorderComponent, decorators: [{
620
- type: Component,
621
- args: [{ selector: 'uxp-ruclib-screen-recorder', imports: [CommonModule], template: "@if (isBrowserSupported) {\r\n <div class=\"screen-recorder-controls\" [ngClass]=\"customTheme + ' position-' + (rucInputData.position || ScreenRecorderPosition.Bottom)\"\r\n >\r\n <!-- Error Message -->\r\n @if (errorMessage) {\r\n <div class=\"recorder-error\">\r\n {{ errorMessage }}\r\n </div>\r\n }\r\n <!-- Record/Stop Button -->\r\n @if (recordingState === RecordingState.Idle || recordingState === RecordingState.Stopped) {\r\n <button\r\n (click)=\"toggleRecord()\"\r\n [attr.aria-label]=\"getLabel('recordButton')\" [attr.aria-pressed]=\"false\" class=\"recorder-button record-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">{{ getRecordingIcon() || getIcon('record') }}</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n {{ getLabel('recordButton')\r\n }}\r\n }\r\n </button>\r\n }\r\n @if (recordingState === RecordingState.Recording || recordingState === RecordingState.Paused) {\r\n <button\r\n (click)=\"toggleRecord()\" [attr.aria-label]=\"getLabel('stopButton')\" [attr.aria-pressed]=\"true\"\r\n class=\"recorder-button stop-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">{{ getIcon('stop') }}</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n {{ getLabel('stopButton') }}\r\n }\r\n </button>\r\n }\r\n <!-- Pause/Resume Button -->\r\n @if (rucInputData.showPauseResume && recordingState === RecordingState.Recording) {\r\n <button\r\n (click)=\"togglePauseResume()\" [attr.aria-label]=\"getLabel('pauseButton')\" [attr.aria-pressed]=\"false\"\r\n class=\"recorder-button pause-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">{{ getIcon('pause') }}</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n {{ getLabel('pauseButton') }}\r\n }\r\n </button>\r\n }\r\n @if (rucInputData.showPauseResume && recordingState === RecordingState.Paused) {\r\n <button\r\n (click)=\"togglePauseResume()\" [attr.aria-label]=\"getLabel('resumeButton')\" [attr.aria-pressed]=\"true\"\r\n class=\"recorder-button resume-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">{{ getIcon('resume') }}</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n {{ getLabel('resumeButton')\r\n }}\r\n }\r\n </button>\r\n }\r\n <!-- Audio Toggle Button -->\r\n @if (rucInputData.showAudioToggle && (recordingState === RecordingState.Idle || recordingState === RecordingState.Stopped)) {\r\n <button\r\n (click)=\"toggleAudio()\" [attr.aria-label]=\"getLabel('audioToggleButton')\" [attr.aria-pressed]=\"isAudioEnabled\"\r\n class=\"recorder-button audio-toggle-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">{{ isAudioEnabled ? getIcon('audioOn') : getIcon('audioOff') }}</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n {{ isAudioEnabled ? screenRecordingConstant.AUDIO_ON : screenRecordingConstant.AUDIO_OFF }}\r\n }\r\n </button>\r\n }\r\n <!-- Timer -->\r\n @if (rucInputData.showTimer && (recordingState === RecordingState.Recording || recordingState === RecordingState.Paused)) {\r\n <div\r\n class=\"recorder-timer\" aria-live=\"polite\" aria-atomic=\"true\">\r\n {{ formattedTime }}\r\n </div>\r\n }\r\n <!-- Download Button -->\r\n @if (rucInputData.showDownloadButton && recordingState === RecordingState.Stopped && safeRecordedUrl) {\r\n <button\r\n (click)=\"onDownload()\" [attr.aria-label]=\"getLabel('downloadButton')\" class=\"recorder-button download-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">{{ getIcon('download') }}</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n {{ getLabel('downloadButton')\r\n }}\r\n }\r\n </button>\r\n }\r\n <!-- Playback Area -->\r\n @if (rucInputData.showPlaybackControls && recordingState === RecordingState.Stopped && safeRecordedUrl) {\r\n <div\r\n class=\"playback-area\">\r\n <video #videoPlayer [src]=\"safeRecordedUrl\" controls [attr.aria-label]=\"getLabel('playbackVideo')\"></video>\r\n <div class=\"video-timer-overlay\">\r\n <span>Recorded on: {{ recordingTimestamp }}</span>\r\n </div>\r\n </div>\r\n }\r\n <!-- Reset Button (optional, good for testing/clearing state) -->\r\n @if (recordingState === RecordingState.Stopped && safeRecordedUrl) {\r\n <button (click)=\"reset()\"\r\n aria-label=\"New Recording\" class=\"recorder-button reset-button\">\r\n @if (rucInputData.displayFormat === DisplayFormat.Icon) {\r\n <span class=\"material-icons\">refresh</span>\r\n }\r\n @if (rucInputData.displayFormat === DisplayFormat.Text) {\r\n New\r\n }\r\n </button>\r\n }\r\n </div>\r\n}\r\n\r\n@if (!isBrowserSupported) {\r\n <div class=\"recorder-notsupported\" [ngClass]=\"customTheme\">\r\n {{screenRecordingConstant.NO_SUPPORT}}\r\n </div>\r\n}\r\n", styles: [":host{display:block;font-family:sans-serif}.screen-recorder-controls{display:flex;gap:8px;align-items:center;padding:8px;border-radius:4px;position:relative;z-index:1000}.screen-recorder-controls.position-top-left{margin-top:10px;margin-left:10px;flex-direction:row}.screen-recorder-controls.position-top-right{margin-top:10px;margin-right:10px;flex-direction:row}.screen-recorder-controls.position-bottom-left{margin-bottom:10px;margin-left:10px;flex-direction:row}.screen-recorder-controls.position-bottom-right{margin-bottom:10px;margin-right:10px;flex-direction:row}.screen-recorder-controls.position-left,.screen-recorder-controls.position-right{float:right}.recorder-button{padding:8px 12px;border:1px solid #ccc;border-radius:4px;background-color:#fff;cursor:pointer;display:inline-flex;align-items:center;gap:4px}.recorder-button:hover{background-color:#e9e9e9}.recorder-button:active{background-color:#d0d0d0}.recorder-button.record-button{color:red}.material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:20px;display:inline-block;line-height:1;text-transform:none;letter-spacing:normal;word-wrap:normal;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;font-feature-settings:\"liga\"}.recorder-timer{padding:0 8px;font-variant-numeric:tabular-nums}.playback-area{width:100%;max-width:100%;margin-top:1rem;position:relative}.playback-area video{width:100%;height:auto;max-width:100%}.playback-area .video-timer-overlay{position:absolute;top:10px;right:10px;background:#000000b3;color:#fff;padding:5px 10px;border-radius:4px;font-size:14px;z-index:10}.recorder-error{color:red;padding:8px;border:1px solid red;background-color:#ffe0e0;border-radius:4px;margin-bottom:8px}.recorder-notsupported{padding:10px;background-color:#fff3cd;border:1px solid #ffeeba;color:#856404;border-radius:4px}\n"] }]
622
- }], ctorParameters: () => [{ type: ScreenRecorderService }, { type: i2.DomSanitizer }, { type: i0.ChangeDetectorRef }, { type: ScreenRecorderConstants }], propDecorators: { customTheme: [{
623
- type: Input
624
- }], rucInputData: [{
625
- type: Input
626
- }], videoPlayer: [{
627
- type: ViewChild,
628
- args: ['videoPlayer']
629
- }] } });
630
-
631
- /**
632
- * Generated bundle index. Do not edit.
633
- */
634
-
635
- export { DisplayFormat, RecordingState, RuclibScreenRecorderComponent, ScreenRecorderPosition, ScreenRecorderService };
636
- //# sourceMappingURL=ruc-lib-screen-recorder.mjs.map