@vidtreo/recorder 1.3.4 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -69,6 +69,16 @@ export {};
69
69
 
70
70
  export {};
71
71
 
72
+ export {};
73
+
74
+ export {};
75
+
76
+ export {};
77
+
78
+ export {};
79
+
80
+ export {};
81
+
72
82
  export type VidtreoRecorderConfig = {
73
83
  apiKey: string;
74
84
  apiUrl?: string;
@@ -350,6 +360,15 @@ export declare function createBrowserUnsupportedError(options?: BrowserUnsupport
350
360
  export declare function validateBrowserSupport(): void;
351
361
  export {};
352
362
 
363
+ type PermissionName = "camera" | "microphone";
364
+ type PermissionStateChangeHandler = (state: PermissionState) => void;
365
+ export declare function queryPermissionState(permissionName: PermissionName): Promise<PermissionState>;
366
+ export declare function queryAllPermissions(): Promise<PermissionStatus>;
367
+ export declare function isPermissionGranted(state: PermissionState): boolean;
368
+ export declare function isPermissionBlocked(state: PermissionState): boolean;
369
+ export declare function createPermissionChangeListener(permissionName: PermissionName, onStateChange: PermissionStateChangeHandler): () => void;
370
+ export {};
371
+
353
372
  export type AvailableDevices = {
354
373
  audioinput: MediaDeviceInfo[];
355
374
  videoinput: MediaDeviceInfo[];
@@ -359,6 +378,41 @@ export type DeviceCallbacks = {
359
378
  onDeviceSelected: (type: "camera" | "mic", deviceId: string | null) => void;
360
379
  };
361
380
 
381
+ export declare function createPermissionFlowOrchestratorDependencies(): PermissionFlowOrchestratorDependencies;
382
+ export declare class PermissionFlowOrchestrator {
383
+ private readonly dependencies;
384
+ private readonly callbacks;
385
+ private readonly language;
386
+ private currentState;
387
+ private cleanupCameraListener;
388
+ private cleanupMicrophoneListener;
389
+ private isInitialized;
390
+ private hasCompleted;
391
+ constructor(dependencies: PermissionFlowOrchestratorDependencies, options?: PermissionFlowOrchestratorOptions);
392
+ initialize(): Promise<PermissionFlowState>;
393
+ requestCurrentStep(): Promise<PermissionFlowState>;
394
+ retryCurrentStep(): Promise<PermissionFlowState>;
395
+ getState(): PermissionFlowState;
396
+ private requestPermissionAndRefresh;
397
+ destroy(): void;
398
+ private setupPermissionListeners;
399
+ private handlePermissionStateChange;
400
+ private refreshPermissions;
401
+ private createStateFromPermissions;
402
+ private transitionToBlockedFromFailedProbe;
403
+ private createInsecureContextState;
404
+ private updateStateForChecking;
405
+ private emitChange;
406
+ private tryComplete;
407
+ }
408
+
409
+ export declare const DEVICE_PERSISTENCE_KEY_PREFIX = "vidtreo_device_prefs_";
410
+ export declare function createDevicePersistenceKey(apiKey: string): string;
411
+ export declare function saveDevicePreferences(apiKey: string, preferences: DevicePreferences): void;
412
+ export declare function loadDevicePreferences(apiKey: string): DevicePreferences | null;
413
+ export declare function clearDevicePreferences(apiKey: string): void;
414
+ export declare function validateSavedDeviceExists(savedDeviceId: string, availableDevices: MediaDeviceInfo[]): boolean;
415
+
362
416
  import type { CameraStreamManager } from "../stream/stream";
363
417
  export declare class DeviceManager {
364
418
  private readonly streamManager;
@@ -375,6 +429,96 @@ export declare class DeviceManager {
375
429
  getAvailableDevicesList(): AvailableDevices;
376
430
  }
377
431
 
432
+ export declare function createPermissionRecoveryData(errorCode: DeviceIssueCode, browserInfo: {
433
+ name: string;
434
+ version: string;
435
+ }, language?: "en" | "es"): PermissionRecoveryData;
436
+
437
+ export type PermissionState = "granted" | "denied" | "prompt" | "unknown";
438
+ export type PermissionStatus = {
439
+ camera: PermissionState;
440
+ microphone: PermissionState;
441
+ };
442
+ export type DeviceIssueCode = "permission-denied-camera" | "permission-denied-microphone" | "permission-denied-both" | "device-not-found" | "device-in-use" | "device-disconnected" | "silent-microphone" | "track-muted" | "track-ended" | "insecure-context";
443
+ export type DeviceIssue = {
444
+ code: DeviceIssueCode;
445
+ severity: "error" | "warning";
446
+ deviceType: "camera" | "microphone" | "both";
447
+ recoveryData?: PermissionRecoveryData;
448
+ };
449
+ export type PermissionRecoveryData = {
450
+ errorCode: DeviceIssueCode;
451
+ browserName: string;
452
+ browserVersion: string;
453
+ resetInstructions: string;
454
+ resetUrl?: string;
455
+ canRetry: boolean;
456
+ };
457
+ export type DevicePreflightResult = {
458
+ isReady: boolean;
459
+ permissions: PermissionStatus;
460
+ hasCamera: boolean;
461
+ hasMicrophone: boolean;
462
+ issues: DeviceIssue[];
463
+ };
464
+ export type DevicePreferences = {
465
+ cameraDeviceId?: string;
466
+ micDeviceId?: string;
467
+ };
468
+ export type DeviceHealthCallbacks = {
469
+ onDeviceDisconnected?: (deviceType: "camera" | "microphone", deviceId: string, fallbackDeviceId?: string) => void;
470
+ onDeviceFallback?: (requestedDeviceId: string, actualDeviceId: string, deviceType: "camera" | "microphone") => void;
471
+ onSilentMicDetected?: () => void;
472
+ onSilentMicRecovered?: () => void;
473
+ onTrackHealthChange?: (trackType: "audio" | "video", event: "muted" | "unmuted" | "ended") => void;
474
+ onPermissionStateChange?: (permissions: PermissionStatus) => void;
475
+ onPreflightComplete?: (result: DevicePreflightResult) => void;
476
+ };
477
+ export type DeviceAccessConfig = {
478
+ enableDevicePersistence?: boolean;
479
+ enableSilentMicDetection?: boolean;
480
+ enableTrackHealthMonitoring?: boolean;
481
+ silentMicThresholdLevel?: number;
482
+ silentMicThresholdDuration?: number;
483
+ deviceChangeDebounceMs?: number;
484
+ };
485
+ export type PermissionFlowStep = "idle" | "checking" | "awaiting-user" | "blocked" | "ready";
486
+ export type PermissionDenialType = "none" | "soft" | "hard";
487
+ export type PermissionFlowChangeReason = "initialized" | "permission-refresh" | "permission-change" | "current-step-requested" | "retry-requested" | "probe-complete" | "insecure-context" | "destroyed";
488
+ export type PermissionFlowSnapshot = {
489
+ step: PermissionFlowStep;
490
+ permissions: PermissionStatus;
491
+ denialType: PermissionDenialType;
492
+ issueCode?: DeviceIssueCode;
493
+ recoveryData?: PermissionRecoveryData;
494
+ isSecureContext: boolean;
495
+ isComplete: boolean;
496
+ canRetry: boolean;
497
+ shouldProbeUnknown: boolean;
498
+ };
499
+ export type PermissionFlowState = PermissionFlowSnapshot;
500
+ export type PermissionFlowCallbacks = {
501
+ onChange?: (state: PermissionFlowState, reason: PermissionFlowChangeReason) => void;
502
+ onComplete?: (state: PermissionFlowState) => void;
503
+ };
504
+ export type PermissionFlowLanguage = "en" | "es";
505
+ export type PermissionFlowBrowserInfo = {
506
+ name: string;
507
+ version: string;
508
+ };
509
+ export type PermissionFlowOrchestratorDependencies = {
510
+ queryAllPermissions: () => Promise<PermissionStatus>;
511
+ createPermissionChangeListener: (permissionName: "camera" | "microphone", onStateChange: (state: PermissionState) => void) => () => void;
512
+ createPermissionRecoveryData: (errorCode: DeviceIssueCode, browserInfo: PermissionFlowBrowserInfo, language?: PermissionFlowLanguage) => PermissionRecoveryData;
513
+ getBrowserInfo: () => PermissionFlowBrowserInfo;
514
+ getIsSecureContext: () => boolean;
515
+ requestPermissionProbe: () => Promise<void>;
516
+ };
517
+ export type PermissionFlowOrchestratorOptions = {
518
+ callbacks?: PermissionFlowCallbacks;
519
+ language?: PermissionFlowLanguage;
520
+ };
521
+
378
522
  export type NativeCameraFile = {
379
523
  file: File;
380
524
  previewUrl: string;
package/dist/index.js CHANGED
@@ -704,6 +704,488 @@ class DeviceManager {
704
704
  return this.availableDevices;
705
705
  }
706
706
  }
707
+ // src/core/device/permission-checker.ts
708
+ var noOperation = () => {
709
+ return;
710
+ };
711
+ function isPermissionsApiAvailable() {
712
+ if (typeof navigator === "undefined") {
713
+ return false;
714
+ }
715
+ if (!navigator.permissions) {
716
+ return false;
717
+ }
718
+ return typeof navigator.permissions.query === "function";
719
+ }
720
+ function queryPermissionState(permissionName) {
721
+ if (!isPermissionsApiAvailable()) {
722
+ return Promise.resolve("unknown");
723
+ }
724
+ return navigator.permissions.query({ name: permissionName }).then((status) => status.state).catch(() => "unknown");
725
+ }
726
+ async function queryAllPermissions() {
727
+ const [camera, microphone] = await Promise.all([
728
+ queryPermissionState("camera"),
729
+ queryPermissionState("microphone")
730
+ ]);
731
+ return { camera, microphone };
732
+ }
733
+ function createPermissionChangeListener(permissionName, onStateChange) {
734
+ if (!isPermissionsApiAvailable()) {
735
+ return noOperation;
736
+ }
737
+ let permissionStatus = null;
738
+ let isDestroyed = false;
739
+ const handleChange = () => {
740
+ if (!permissionStatus) {
741
+ return;
742
+ }
743
+ onStateChange(permissionStatus.state);
744
+ };
745
+ navigator.permissions.query({ name: permissionName }).then((status) => {
746
+ if (isDestroyed) {
747
+ return;
748
+ }
749
+ permissionStatus = status;
750
+ permissionStatus.addEventListener("change", handleChange);
751
+ }).catch(noOperation);
752
+ return () => {
753
+ isDestroyed = true;
754
+ if (permissionStatus) {
755
+ permissionStatus.removeEventListener("change", handleChange);
756
+ permissionStatus = null;
757
+ }
758
+ };
759
+ }
760
+
761
+ // src/core/device/permission-recovery.ts
762
+ var CHROME_INSTRUCTIONS = {
763
+ en: "Click the lock/tune icon in the address bar → Site settings → Camera/Microphone → Allow",
764
+ es: "Haz clic en el icono de candado/ajustes en la barra de direcciones → Configuración del sitio → Cámara/Micrófono → Permitir",
765
+ resetUrl: "chrome://settings/content/camera"
766
+ };
767
+ var SAFARI_INSTRUCTIONS = {
768
+ en: "Go to Safari → Settings → Websites → Camera/Microphone → Allow for this site",
769
+ es: "Ve a Safari → Ajustes → Sitios web → Cámara/Micrófono → Permitir para este sitio"
770
+ };
771
+ var EDGE_INSTRUCTIONS = {
772
+ en: "Click the lock icon in the address bar → Permissions → Camera/Microphone → Allow",
773
+ es: "Haz clic en el icono de candado en la barra de direcciones → Permisos → Cámara/Micrófono → Permitir",
774
+ resetUrl: "edge://settings/content/camera"
775
+ };
776
+ var OPERA_INSTRUCTIONS = {
777
+ en: "Click the lock icon → Site settings → Camera/Microphone → Allow",
778
+ es: "Haz clic en el icono de candado → Configuración del sitio → Cámara/Micrófono → Permitir",
779
+ resetUrl: "opera://settings/content/camera"
780
+ };
781
+ var UNKNOWN_BROWSER_INSTRUCTIONS = {
782
+ en: "Please check your browser settings to allow camera and microphone access for this site",
783
+ es: "Por favor, revisa la configuración de tu navegador para permitir el acceso a la cámara y el micrófono en este sitio"
784
+ };
785
+ var INSECURE_CONTEXT_INSTRUCTIONS = {
786
+ en: "This site must be accessed over HTTPS (secure connection) to use camera and microphone",
787
+ es: "Este sitio debe ser accedido a través de HTTPS (conexión segura) para usar cámara y micrófono"
788
+ };
789
+ var BROWSER_INSTRUCTIONS_MAP = {
790
+ chrome: CHROME_INSTRUCTIONS,
791
+ safari: SAFARI_INSTRUCTIONS,
792
+ edge: EDGE_INSTRUCTIONS,
793
+ opera: OPERA_INSTRUCTIONS,
794
+ unknown: UNKNOWN_BROWSER_INSTRUCTIONS
795
+ };
796
+ var RETRYABLE_ERROR_CODES = new Set([
797
+ "permission-denied-camera",
798
+ "permission-denied-microphone",
799
+ "permission-denied-both",
800
+ "device-not-found",
801
+ "device-in-use"
802
+ ]);
803
+ function resolveBrowserKey(browserName) {
804
+ const normalizedName = browserName.toLowerCase();
805
+ if (normalizedName.includes("edge")) {
806
+ return "edge";
807
+ }
808
+ if (normalizedName.includes("opera") || normalizedName.includes("opr")) {
809
+ return "opera";
810
+ }
811
+ if (normalizedName.includes("chrome")) {
812
+ return "chrome";
813
+ }
814
+ if (normalizedName.includes("safari")) {
815
+ return "safari";
816
+ }
817
+ return "unknown";
818
+ }
819
+ function createPermissionRecoveryData(errorCode, browserInfo, language = "en") {
820
+ if (errorCode === "insecure-context") {
821
+ return {
822
+ errorCode,
823
+ browserName: browserInfo.name,
824
+ browserVersion: browserInfo.version,
825
+ resetInstructions: INSECURE_CONTEXT_INSTRUCTIONS[language],
826
+ resetUrl: undefined,
827
+ canRetry: false
828
+ };
829
+ }
830
+ const browserKey = resolveBrowserKey(browserInfo.name);
831
+ const instructions = BROWSER_INSTRUCTIONS_MAP[browserKey];
832
+ const canRetry = RETRYABLE_ERROR_CODES.has(errorCode);
833
+ return {
834
+ errorCode,
835
+ browserName: browserInfo.name,
836
+ browserVersion: browserInfo.version,
837
+ resetInstructions: instructions[language],
838
+ resetUrl: instructions.resetUrl,
839
+ canRetry
840
+ };
841
+ }
842
+
843
+ // src/core/device/permission-flow-orchestrator.ts
844
+ var GRANTED_PERMISSION_COUNT = 2;
845
+ var DEFAULT_PERMISSION_STATUS = {
846
+ camera: "unknown",
847
+ microphone: "unknown"
848
+ };
849
+ function noOperation2() {
850
+ return;
851
+ }
852
+ function getIsSecureContext() {
853
+ const globalWindow = globalThis.window;
854
+ if (!globalWindow) {
855
+ return false;
856
+ }
857
+ return globalWindow.isSecureContext;
858
+ }
859
+ function stopStreamTracks(stream) {
860
+ const tracks = stream.getTracks();
861
+ for (const track of tracks) {
862
+ track.stop();
863
+ }
864
+ }
865
+ async function requestPermissionProbe() {
866
+ const mediaDevices = globalThis.navigator?.mediaDevices;
867
+ if (!mediaDevices) {
868
+ return;
869
+ }
870
+ const canGetUserMedia = typeof mediaDevices.getUserMedia === "function";
871
+ if (!canGetUserMedia) {
872
+ return;
873
+ }
874
+ const stream = await mediaDevices.getUserMedia({ audio: true, video: true });
875
+ stopStreamTracks(stream);
876
+ await mediaDevices.getUserMedia({
877
+ audio: true,
878
+ video: { width: { ideal: 1920 }, height: { ideal: 1080 } }
879
+ }).then((stream2) => {
880
+ stopStreamTracks(stream2);
881
+ return;
882
+ }).catch(noOperation2);
883
+ }
884
+ function countGrantedPermissions(permissions) {
885
+ let grantedCount = 0;
886
+ if (permissions.camera === "granted") {
887
+ grantedCount += 1;
888
+ }
889
+ if (permissions.microphone === "granted") {
890
+ grantedCount += 1;
891
+ }
892
+ return grantedCount;
893
+ }
894
+ function hasUnknownPermissionState(permissions) {
895
+ if (permissions.camera === "unknown") {
896
+ return true;
897
+ }
898
+ if (permissions.microphone === "unknown") {
899
+ return true;
900
+ }
901
+ return false;
902
+ }
903
+ function hasPromptPermissionState(permissions) {
904
+ if (permissions.camera === "prompt") {
905
+ return true;
906
+ }
907
+ if (permissions.microphone === "prompt") {
908
+ return true;
909
+ }
910
+ return false;
911
+ }
912
+ function hasHardPermissionDenial(permissions) {
913
+ if (permissions.camera === "denied") {
914
+ return true;
915
+ }
916
+ if (permissions.microphone === "denied") {
917
+ return true;
918
+ }
919
+ return false;
920
+ }
921
+ function getPermissionDeniedIssueCode(permissions) {
922
+ const hasCameraDenied = permissions.camera === "denied";
923
+ const hasMicrophoneDenied = permissions.microphone === "denied";
924
+ if (hasCameraDenied && hasMicrophoneDenied) {
925
+ return "permission-denied-both";
926
+ }
927
+ if (hasCameraDenied) {
928
+ return "permission-denied-camera";
929
+ }
930
+ return "permission-denied-microphone";
931
+ }
932
+ function createPermissionFlowOrchestratorDependencies() {
933
+ return {
934
+ queryAllPermissions,
935
+ createPermissionChangeListener,
936
+ createPermissionRecoveryData,
937
+ getBrowserInfo: () => {
938
+ const browserInfo = getBrowserInfo();
939
+ return {
940
+ name: browserInfo.name,
941
+ version: browserInfo.version
942
+ };
943
+ },
944
+ getIsSecureContext,
945
+ requestPermissionProbe
946
+ };
947
+ }
948
+
949
+ class PermissionFlowOrchestrator {
950
+ dependencies;
951
+ callbacks;
952
+ language;
953
+ currentState;
954
+ cleanupCameraListener = noOperation2;
955
+ cleanupMicrophoneListener = noOperation2;
956
+ isInitialized = false;
957
+ hasCompleted = false;
958
+ constructor(dependencies, options = {}) {
959
+ this.dependencies = dependencies;
960
+ let callbacks = {};
961
+ if (options.callbacks) {
962
+ callbacks = options.callbacks;
963
+ }
964
+ this.callbacks = callbacks;
965
+ let language = "en";
966
+ if (options.language === "es") {
967
+ language = "es";
968
+ }
969
+ this.language = language;
970
+ this.currentState = {
971
+ step: "idle",
972
+ permissions: DEFAULT_PERMISSION_STATUS,
973
+ denialType: "none",
974
+ isSecureContext: true,
975
+ isComplete: false,
976
+ canRetry: true,
977
+ shouldProbeUnknown: true
978
+ };
979
+ }
980
+ initialize() {
981
+ if (this.isInitialized) {
982
+ return Promise.resolve(this.currentState);
983
+ }
984
+ this.isInitialized = true;
985
+ this.hasCompleted = false;
986
+ this.setupPermissionListeners();
987
+ this.updateStateForChecking();
988
+ this.emitChange("initialized");
989
+ const isSecureContext = this.dependencies.getIsSecureContext();
990
+ if (!isSecureContext) {
991
+ this.currentState = this.createInsecureContextState();
992
+ this.emitChange("insecure-context");
993
+ this.tryComplete();
994
+ return Promise.resolve(this.currentState);
995
+ }
996
+ return this.refreshPermissions("permission-refresh");
997
+ }
998
+ requestCurrentStep() {
999
+ if (!this.isInitialized) {
1000
+ return this.initialize();
1001
+ }
1002
+ if (!this.currentState.canRetry) {
1003
+ return Promise.resolve(this.currentState);
1004
+ }
1005
+ this.updateStateForChecking();
1006
+ this.emitChange("current-step-requested");
1007
+ return this.requestPermissionAndRefresh();
1008
+ }
1009
+ retryCurrentStep() {
1010
+ if (!this.isInitialized) {
1011
+ return this.initialize();
1012
+ }
1013
+ if (!this.currentState.canRetry) {
1014
+ return Promise.resolve(this.currentState);
1015
+ }
1016
+ this.updateStateForChecking();
1017
+ this.emitChange("retry-requested");
1018
+ return this.requestPermissionAndRefresh();
1019
+ }
1020
+ getState() {
1021
+ return this.currentState;
1022
+ }
1023
+ requestPermissionAndRefresh() {
1024
+ return this.dependencies.requestPermissionProbe().then(() => {
1025
+ const grantedPermissions = {
1026
+ camera: "granted",
1027
+ microphone: "granted"
1028
+ };
1029
+ this.currentState = this.createStateFromPermissions(grantedPermissions);
1030
+ this.emitChange("probe-complete");
1031
+ this.tryComplete();
1032
+ return this.currentState;
1033
+ }).catch(() => this.refreshPermissions("permission-refresh").then((state) => {
1034
+ if (state.step !== "awaiting-user") {
1035
+ return state;
1036
+ }
1037
+ return this.transitionToBlockedFromFailedProbe();
1038
+ }));
1039
+ }
1040
+ destroy() {
1041
+ this.cleanupCameraListener();
1042
+ this.cleanupCameraListener = noOperation2;
1043
+ this.cleanupMicrophoneListener();
1044
+ this.cleanupMicrophoneListener = noOperation2;
1045
+ this.isInitialized = false;
1046
+ this.hasCompleted = false;
1047
+ this.emitChange("destroyed");
1048
+ }
1049
+ setupPermissionListeners() {
1050
+ this.cleanupCameraListener = this.dependencies.createPermissionChangeListener("camera", (state) => {
1051
+ this.handlePermissionStateChange("camera", state);
1052
+ });
1053
+ this.cleanupMicrophoneListener = this.dependencies.createPermissionChangeListener("microphone", (state) => {
1054
+ this.handlePermissionStateChange("microphone", state);
1055
+ });
1056
+ }
1057
+ handlePermissionStateChange(permissionName, state) {
1058
+ if (!this.isInitialized) {
1059
+ return;
1060
+ }
1061
+ const nextPermissions = {
1062
+ ...this.currentState.permissions,
1063
+ [permissionName]: state
1064
+ };
1065
+ const previousStep = this.currentState.step;
1066
+ this.currentState = this.createStateFromPermissions(nextPermissions);
1067
+ const isFlowReactivating = previousStep === "ready" && this.currentState.step !== "ready";
1068
+ if (isFlowReactivating) {
1069
+ this.hasCompleted = false;
1070
+ }
1071
+ this.emitChange("permission-change");
1072
+ this.tryComplete();
1073
+ }
1074
+ async refreshPermissions(reason) {
1075
+ const permissions = await this.dependencies.queryAllPermissions();
1076
+ this.currentState = this.createStateFromPermissions(permissions);
1077
+ this.emitChange(reason);
1078
+ this.tryComplete();
1079
+ return this.currentState;
1080
+ }
1081
+ createStateFromPermissions(permissions) {
1082
+ const isSecureContext = this.dependencies.getIsSecureContext();
1083
+ if (!isSecureContext) {
1084
+ return this.createInsecureContextState();
1085
+ }
1086
+ const grantedCount = countGrantedPermissions(permissions);
1087
+ if (grantedCount === GRANTED_PERMISSION_COUNT) {
1088
+ return {
1089
+ step: "ready",
1090
+ permissions,
1091
+ denialType: "none",
1092
+ isSecureContext: true,
1093
+ isComplete: true,
1094
+ canRetry: false,
1095
+ shouldProbeUnknown: false
1096
+ };
1097
+ }
1098
+ if (hasHardPermissionDenial(permissions)) {
1099
+ const issueCode = getPermissionDeniedIssueCode(permissions);
1100
+ const recoveryData = this.dependencies.createPermissionRecoveryData(issueCode, this.dependencies.getBrowserInfo(), this.language);
1101
+ return {
1102
+ step: "blocked",
1103
+ permissions,
1104
+ denialType: "hard",
1105
+ issueCode,
1106
+ recoveryData,
1107
+ isSecureContext: true,
1108
+ isComplete: true,
1109
+ canRetry: recoveryData.canRetry,
1110
+ shouldProbeUnknown: false
1111
+ };
1112
+ }
1113
+ const shouldProbeUnknown = hasUnknownPermissionState(permissions);
1114
+ const hasPromptState = hasPromptPermissionState(permissions);
1115
+ let canRetry = false;
1116
+ if (shouldProbeUnknown) {
1117
+ canRetry = true;
1118
+ }
1119
+ if (hasPromptState) {
1120
+ canRetry = true;
1121
+ }
1122
+ return {
1123
+ step: "awaiting-user",
1124
+ permissions,
1125
+ denialType: "soft",
1126
+ isSecureContext: true,
1127
+ isComplete: false,
1128
+ canRetry,
1129
+ shouldProbeUnknown
1130
+ };
1131
+ }
1132
+ transitionToBlockedFromFailedProbe() {
1133
+ const issueCode = "permission-denied-both";
1134
+ const recoveryData = this.dependencies.createPermissionRecoveryData(issueCode, this.dependencies.getBrowserInfo(), this.language);
1135
+ this.currentState = {
1136
+ step: "blocked",
1137
+ permissions: this.currentState.permissions,
1138
+ denialType: "hard",
1139
+ issueCode,
1140
+ recoveryData,
1141
+ isSecureContext: true,
1142
+ isComplete: true,
1143
+ canRetry: recoveryData.canRetry,
1144
+ shouldProbeUnknown: false
1145
+ };
1146
+ this.emitChange("probe-complete");
1147
+ this.tryComplete();
1148
+ return this.currentState;
1149
+ }
1150
+ createInsecureContextState() {
1151
+ const issueCode = "insecure-context";
1152
+ const recoveryData = this.dependencies.createPermissionRecoveryData(issueCode, this.dependencies.getBrowserInfo(), this.language);
1153
+ return {
1154
+ step: "blocked",
1155
+ permissions: this.currentState.permissions,
1156
+ denialType: "hard",
1157
+ issueCode,
1158
+ recoveryData,
1159
+ isSecureContext: false,
1160
+ isComplete: true,
1161
+ canRetry: false,
1162
+ shouldProbeUnknown: false
1163
+ };
1164
+ }
1165
+ updateStateForChecking() {
1166
+ this.currentState = {
1167
+ ...this.currentState,
1168
+ step: "checking"
1169
+ };
1170
+ }
1171
+ emitChange(reason) {
1172
+ if (this.callbacks.onChange) {
1173
+ this.callbacks.onChange(this.currentState, reason);
1174
+ }
1175
+ }
1176
+ tryComplete() {
1177
+ if (this.hasCompleted) {
1178
+ return;
1179
+ }
1180
+ if (!this.currentState.isComplete) {
1181
+ return;
1182
+ }
1183
+ this.hasCompleted = true;
1184
+ if (this.callbacks.onComplete) {
1185
+ this.callbacks.onComplete(this.currentState);
1186
+ }
1187
+ }
1188
+ }
707
1189
  // src/core/utils/video-utils.ts
708
1190
  import { BlobSource, Input, MP4 } from "mediabunny";
709
1191
  async function extractVideoDuration(file) {
@@ -2136,7 +2618,7 @@ function stopLiveTracks(tracks) {
2136
2618
  }
2137
2619
  }
2138
2620
  }
2139
- function stopStreamTracks(stream) {
2621
+ function stopStreamTracks2(stream) {
2140
2622
  stopLiveTracks(stream.getTracks());
2141
2623
  }
2142
2624
  function stopStreamVideoTracks(stream) {
@@ -2153,7 +2635,7 @@ function areTracksLive(videoTrack, audioTrack) {
2153
2635
  }
2154
2636
  function validateTrack(track, trackType, stream) {
2155
2637
  if (!isTrackLive(track)) {
2156
- stopStreamTracks(stream);
2638
+ stopStreamTracks2(stream);
2157
2639
  let readyState = "undefined";
2158
2640
  if (track) {
2159
2641
  readyState = track.readyState;
@@ -2311,7 +2793,7 @@ class CameraStreamBuilder {
2311
2793
  }
2312
2794
  const managerStream = this.dependencies.streamManager.getStream();
2313
2795
  if (!isRecording && managerStream && managerStream !== this.dependencies.getOriginalCameraStream()) {
2314
- stopStreamTracks(managerStream);
2796
+ stopStreamTracks2(managerStream);
2315
2797
  this.dependencies.streamManager.setMediaStream(null);
2316
2798
  }
2317
2799
  if (isRecording) {
@@ -2639,7 +3121,7 @@ class SourceSwitchManager {
2639
3121
  callbacks: this.callbacks,
2640
3122
  streamManager: this.streamManager,
2641
3123
  combineScreenShareWithOriginalAudio: (screenVideoTrack) => this.combineScreenShareWithOriginalAudio(screenVideoTrack),
2642
- stopStreamTracks: (stream) => stopStreamTracks(stream),
3124
+ stopStreamTracks: (stream) => stopStreamTracks2(stream),
2643
3125
  stopStreamVideoTracks: (stream) => stopStreamVideoTracks(stream),
2644
3126
  getCurrentSourceType: () => this.currentSourceType,
2645
3127
  setCurrentSourceType: (sourceType) => {
@@ -3909,7 +4391,7 @@ class CameraStreamManager {
3909
4391
  // package.json
3910
4392
  var package_default = {
3911
4393
  name: "@vidtreo/recorder",
3912
- version: "1.3.4",
4394
+ version: "1.4.0",
3913
4395
  type: "module",
3914
4396
  description: "Vidtreo SDK for browser-based video recording and transcoding. Features include camera/screen recording, real-time MP4 transcoding, audio level analysis, mute/pause controls, source switching, device selection, and automatic backend uploads. Similar to Ziggeo and Addpipe, Vidtreo provides enterprise-grade video processing capabilities for web applications.",
3915
4397
  main: "./dist/index.js",
@@ -18258,6 +18740,7 @@ export {
18258
18740
  extractVideoDuration,
18259
18741
  extractLastFrame,
18260
18742
  extractErrorMessage,
18743
+ createPermissionFlowOrchestratorDependencies,
18261
18744
  checkRecorderSupport,
18262
18745
  calculateVideoBitrate,
18263
18746
  calculateTotalBitrateFromMbPerMinute,
@@ -18271,6 +18754,7 @@ export {
18271
18754
  RecordingManager,
18272
18755
  RecorderController,
18273
18756
  QuotaManager,
18757
+ PermissionFlowOrchestrator,
18274
18758
  PRESET_SIZE_LIMIT_MB_PER_MINUTE,
18275
18759
  NativeCameraHandler,
18276
18760
  FORMAT_DEFAULT_CODECS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vidtreo/recorder",
3
- "version": "1.3.4",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "description": "Vidtreo SDK for browser-based video recording and transcoding. Features include camera/screen recording, real-time MP4 transcoding, audio level analysis, mute/pause controls, source switching, device selection, and automatic backend uploads. Similar to Ziggeo and Addpipe, Vidtreo provides enterprise-grade video processing capabilities for web applications.",
6
6
  "main": "./dist/index.js",