UNPKG

@onamfc/video-transcoder

Version:

Backend-agnostic video recording and transcoding module with AWS integration

3 lines (2 loc) 23.6 kB
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).VideoRecorder={})}(this,function(e){"use strict";class t{constructor(e){Object.defineProperty(this,"mediaRecorder",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"stream",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"chunks",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"startTime",{enumerable:!0,configurable:!0,writable:!0,value:0}),Object.defineProperty(this,"config",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"isRecording",{enumerable:!0,configurable:!0,writable:!0,value:!1}),Object.defineProperty(this,"isPaused",{enumerable:!0,configurable:!0,writable:!0,value:!1}),Object.defineProperty(this,"previewElement",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"isInitialized",{enumerable:!0,configurable:!0,writable:!0,value:!1}),this.config=e,this.previewElement=e.previewElement||null}updateConfig(e){const t=this.isInitialized,i=this.previewElement;this.config={...this.config,...e},t&&this.hasVideoQualityChanged(e)?this.reinitializeWithNewQuality(i):this.config={...this.config,...e}}hasVideoQualityChanged(e){return void 0!==e.videoQuality&&e.videoQuality!==this.config.videoQuality}async reinitializeWithNewQuality(e){try{this.stream&&this.stream.getTracks().forEach(e=>e.stop());const t=this.getMediaConstraints();this.stream=await navigator.mediaDevices.getUserMedia(t),e&&this.config.showPreview&&(this.previewElement=e,this.previewElement.srcObject=this.stream)}catch(e){throw e}}async initialize(){try{const e=this.getMediaConstraints();this.stream=await navigator.mediaDevices.getUserMedia(e),this.isInitialized=!0,this.config.showPreview&&this.setupPreview()}catch(e){throw new Error(`Failed to access camera: ${e.message}`)}}setPreviewElement(e){this.previewElement=e,this.stream&&this.config.showPreview&&this.setupPreview()}getMediaConstraints(){const e={low:{width:640,height:480,frameRate:15},medium:{width:1280,height:720,frameRate:30},high:{width:1920,height:1080,frameRate:30},auto:{width:1280,height:720,frameRate:30}}[this.config.videoQuality||"medium"];return{video:{width:{ideal:e.width,max:e.width},height:{ideal:e.height,max:e.height},frameRate:{ideal:e.frameRate,max:e.frameRate}},audio:!1!==this.config.audioEnabled}}setupPreview(){let e=this.previewElement;e||(e=document.getElementById("video-transcoder-preview")),e||(e=document.createElement("video"),e.id="video-transcoder-preview",this.applyPreviewStyles(e),document.body.appendChild(e)),e.autoplay=!0,e.muted=!0,e.playsInline=!0,this.stream&&(e.srcObject=this.stream),this.previewElement=e}applyPreviewStyles(e){this.config.customStyles?Object.assign(e.style,this.config.customStyles):(e.style.width="100%",e.style.maxWidth="400px",e.style.borderRadius="8px",e.style.boxShadow="0 4px 6px rgba(0, 0, 0, 0.1)")}async startRecording(){if(!this.stream)throw new Error("Camera not initialized. Call initialize() first.");if(this.isRecording)throw new Error("Recording already in progress");this.chunks=[],this.startTime=Date.now();const e={mimeType:this.getSupportedMimeType()};this.mediaRecorder=new MediaRecorder(this.stream,e),this.mediaRecorder.ondataavailable=e=>{e.data.size>0&&this.chunks.push(e.data)},this.mediaRecorder.start(1e3),this.isRecording=!0,this.isPaused=!1,this.config.maxDuration&&setTimeout(()=>{this.isRecording&&!this.isPaused&&this.stopRecording()},1e3*this.config.maxDuration)}pauseRecording(){this.mediaRecorder&&this.isRecording&&!this.isPaused&&(this.mediaRecorder.pause(),this.isPaused=!0)}resumeRecording(){this.mediaRecorder&&this.isRecording&&this.isPaused&&(this.mediaRecorder.resume(),this.isPaused=!1)}async stopRecording(){return new Promise((e,t)=>{this.mediaRecorder&&this.isRecording?(this.mediaRecorder.onstop=()=>{const t=(Date.now()-this.startTime)/1e3,i=new Blob(this.chunks,{type:this.getSupportedMimeType()});this.isRecording=!1,this.isPaused=!1,e({blob:i,duration:t,size:i.size,mimeType:i.type})},this.mediaRecorder.stop()):t(new Error("No recording in progress"))})}getSupportedMimeType(){const e=["video/webm;codecs=vp9,opus","video/webm;codecs=vp8,opus","video/webm","video/mp4"];for(const t of e)if(MediaRecorder.isTypeSupported(t))return t;return"video/webm"}cleanup(){this.stream&&(this.stream.getTracks().forEach(e=>e.stop()),this.stream=null),this.mediaRecorder&&(this.mediaRecorder=null),this.previewElement&&"video-transcoder-preview"===this.previewElement.id&&this.previewElement.remove(),this.previewElement=null,this.chunks=[],this.isRecording=!1,this.isPaused=!1,this.isInitialized=!1}getRecordingState(){return{isRecording:this.isRecording,isPaused:this.isPaused,duration:this.isRecording?(Date.now()-this.startTime)/1e3:0}}}class i{constructor(e){Object.defineProperty(this,"config",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"activeUploads",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"progressCallbacks",{enumerable:!0,configurable:!0,writable:!0,value:new Set}),this.config=e}async uploadRecording(e,t={}){const i=this.generateTrackingId(),r=new AbortController;this.activeUploads.set(i,r);try{const a=await this.getUploadToken(e,t),o=this.config.chunkSize??5242880;return e.size>o?await this.multipartUpload(e,a,i,r.signal):await this.singleUpload(e,a,i,r.signal)}catch(e){throw this.activeUploads.delete(i),e}}async getUploadToken(e,t){const i=await fetch(`${this.config.apiEndpoint}/upload-token`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.authHeaders??{}},body:JSON.stringify({filename:`recording-${Date.now()}.webm`,size:e.size,mimeType:e.type,metadata:t})});if(!i.ok)throw new Error(`Failed to get upload token: ${i.status} ${i.statusText}`);return await i.json()}async singleUpload(e,t,i,r){const a=new XMLHttpRequest;return this.emitProgress({trackingId:i,type:"upload",progress:0,bytesUploaded:0,totalBytes:e.size}),new Promise((o,s)=>{a.upload.onprogress=e=>{if(e.lengthComputable){const t=e.loaded/e.total*100;this.emitProgress({trackingId:i,type:"upload",progress:t,bytesUploaded:e.loaded,totalBytes:e.total})}},a.onload=()=>{a.status>=200&&a.status<300?(this.activeUploads.delete(i),this.emitProgress({trackingId:i,type:"upload",progress:100,bytesUploaded:e.size,totalBytes:e.size}),o({...t,trackingId:i})):s(new Error(`Upload failed: ${a.status} ${a.statusText}`))},a.onerror=()=>{s(new Error("Upload failed due to network error"))},r.addEventListener("abort",()=>{a.abort(),s(new Error("Upload cancelled"))},{once:!0});try{a.open("PUT",t.uploadUrl),a.setRequestHeader("Content-Type",e.type),a.send(e)}catch(e){s(e)}})}async multipartUpload(e,t,i,r){if(!t.multipart)throw new Error("Multipart upload info not provided");const{chunks:a}=t.multipart,o=this.config.chunkSize??5242880,s=this.config.parallelUploads??3;let n=0;const d=[];this.emitProgress({trackingId:i,type:"upload",progress:0,bytesUploaded:0,totalBytes:e.size});for(let t=0;t<a.length;t+=s){const c=a.slice(t,t+s).map(async t=>{const a=t.chunkIndex*o,s=Math.min(a+o,e.size),d=e.slice(a,s),c=await fetch(t.uploadUrl,{method:"PUT",body:d,signal:r});if(!c.ok)throw new Error(`Chunk upload failed: ${c.status} ${c.statusText}`);const l=c.headers.get("ETag");if(!l)throw new Error("Missing ETag in chunk upload response");return n+=d.size,this.emitProgress({trackingId:i,type:"upload",progress:n/e.size*100,bytesUploaded:n,totalBytes:e.size}),{partNumber:t.chunkIndex+1,etag:l.replace(/"/g,"")}}),l=await Promise.all(c);d.push(...l)}return await this.completeMultipartUpload(t.multipart,d),this.emitProgress({trackingId:i,type:"upload",progress:100,bytesUploaded:e.size,totalBytes:e.size}),this.activeUploads.delete(i),{...t,trackingId:i}}async completeMultipartUpload(e,t){const i=await fetch(`${this.config.apiEndpoint}/complete-upload`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.authHeaders??{}},body:JSON.stringify({uploadId:e.uploadId,key:e.key,parts:t.sort((e,t)=>e.partNumber-t.partNumber)})});if(!i.ok)throw new Error(`Failed to complete upload: ${i.status} ${i.statusText}`)}async retryUpload(e){throw new Error(`${e} - Retry upload not yet implemented`)}async cancelUpload(e){const t=this.activeUploads.get(e);t&&(t.abort(),this.activeUploads.delete(e))}onProgress(e){this.progressCallbacks.add(e)}offProgress(e){this.progressCallbacks.delete(e)}emitProgress(e){this.progressCallbacks.forEach(t=>{try{t(e)}catch(e){}})}generateTrackingId(){return`rec_${Date.now()}_${Math.random().toString(36).slice(2,11)}`}cleanup(){this.activeUploads.forEach(e=>e.abort()),this.activeUploads.clear(),this.progressCallbacks.clear()}}class r{constructor(e){Object.defineProperty(this,"config",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"pollingIntervals",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"completionCallbacks",{enumerable:!0,configurable:!0,writable:!0,value:new Set}),Object.defineProperty(this,"errorCallbacks",{enumerable:!0,configurable:!0,writable:!0,value:new Set}),this.config=e}async getUploadStatus(e){const t=await fetch(`${this.config.apiEndpoint}/status/${e}`,{headers:{...this.config.authHeaders??{}}});if(!t.ok)throw new Error(`Failed to get status: ${t.status} ${t.statusText}`);return await t.json()}async listRecordings(e,t=1){const i=new URLSearchParams({page:String(t),...e?{userId:e}:{}}),r=await fetch(`${this.config.apiEndpoint}/recordings?${i}`,{headers:{...this.config.authHeaders??{}}});if(!r.ok)throw new Error(`Failed to list recordings: ${r.status} ${r.statusText}`);return await r.json()}startPolling(e,t){const i=t??this.config.pollingIntervalMs??2e3;if(this.pollingIntervals.has(e))return;const r=async()=>{try{const t=await this.getUploadStatus(e);"completed"===t.status?(this.stopPolling(e),this.emitCompletion({trackingId:e,status:"completed",hlsUrl:t.urls?.hls,mp4Url:t.urls?.mp4,webmUrl:t.urls?.webm,thumbnails:t.thumbnails})):"failed"===t.status&&(this.stopPolling(e),this.emitError({trackingId:e,type:"processing",message:t.error||"Processing failed",retryable:!1}))}catch(t){this.emitError({trackingId:e,type:"network",message:"Failed to check status",retryable:!0})}},a=window.setInterval(r,i);this.pollingIntervals.set(e,a),r()}stopPolling(e){const t=this.pollingIntervals.get(e);void 0!==t&&(window.clearInterval(t),this.pollingIntervals.delete(e))}onComplete(e){this.completionCallbacks.add(e)}onError(e){this.errorCallbacks.add(e)}emitCompletion(e){this.completionCallbacks.forEach(t=>{try{t(e)}catch(e){}})}emitError(e){this.errorCallbacks.forEach(t=>{try{t(e)}catch(e){}})}cleanup(){this.pollingIntervals.forEach(e=>window.clearInterval(e)),this.pollingIntervals.clear(),this.completionCallbacks.clear(),this.errorCallbacks.clear()}}class a{constructor(e){Object.defineProperty(this,"recorder",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"uploader",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"tracker",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"config",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.config={maxDuration:300,videoQuality:"medium",audioEnabled:!0,chunkSize:5242880,maxRetries:3,parallelUploads:3,pollingIntervalMs:2e3,outputFormats:["hls","mp4"],thumbnailCount:3,showPreview:!0,...e},this.recorder=new t(this.config),this.uploader=new i(this.config),this.tracker=new r(this.config)}configure(e){this.config={...this.config,...e},this.recorder.updateConfig(this.config),this.uploader=new i(this.config),this.tracker=new r(this.config)}async initialize(){await this.recorder.initialize()}setPreviewElement(e){this.recorder.setPreviewElement(e)}async startRecording(){await this.recorder.startRecording()}async stopRecording(){return this.recorder.stopRecording()}pauseRecording(){this.recorder.pauseRecording()}resumeRecording(){this.recorder.resumeRecording()}async uploadRecording(e,t){const i=await this.uploader.uploadRecording(e,t);return this.tracker.startPolling(i.trackingId),i}async retryUpload(e){return this.uploader.retryUpload(e)}async cancelUpload(e){await this.uploader.cancelUpload(e),this.tracker.stopPolling(e)}onProgress(e){this.uploader.onProgress(e)}onComplete(e){this.tracker.onComplete(e)}onError(e){this.tracker.onError(e)}async getUploadStatus(e){return this.tracker.getUploadStatus(e)}async listRecordings(e){return this.tracker.listRecordings(e)}getRecordingState(){return this.recorder.getRecordingState()}async recordAndUpload(e){await this.startRecording();const t=await this.waitForRecordingComplete();return{recording:t,upload:await this.uploadRecording(t.blob,e)}}async waitForRecordingComplete(){return new Promise((e,t)=>{const i=()=>{const r=this.getRecordingState();r.isRecording?this.config.maxDuration&&r.duration>=this.config.maxDuration?this.stopRecording().then(e).catch(t):setTimeout(i,1e3):this.stopRecording().then(e).catch(t)};setTimeout(i,1e3)})}cleanup(){this.recorder.cleanup(),this.uploader.cleanup(),this.tracker.cleanup()}static async checkBrowserSupport(){const e="undefined"!=typeof window,t="undefined"!=typeof navigator,i={mediaRecorder:e&&"MediaRecorder"in window,getUserMedia:t&&!(!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia),webrtc:e&&(!!window.RTCPeerConnection||!!window.webkitRTCPeerConnection)},r=i.mediaRecorder&&i.getUserMedia,a=[];return i.mediaRecorder||a.push("MediaRecorder API not supported. Consider using a WebRTC-based fallback."),i.getUserMedia||a.push("getUserMedia not supported. Camera access unavailable."),a.length?{supported:r,features:i,recommendations:a}:{supported:r,features:i}}static getRecommendedSettings(e="desktop"){return"mobile"===e?{videoQuality:"medium",maxDuration:180,chunkSize:2097152,parallelUploads:2}:{videoQuality:"high",maxDuration:600,chunkSize:10485760,parallelUploads:4}}}const o=e=>{if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KB","MB","GB","TB"],i=Math.min(Math.floor(Math.log(e)/Math.log(1024)),t.length-1);return`${(e/Math.pow(1024,i)).toFixed(2)} ${t[i]}`};class s extends Error{constructor(e,t,i={}){super(e),Object.defineProperty(this,"type",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"code",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"retryable",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"trackingId",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.name="VideoRecorderError",this.type=t,this.code=i.code,this.retryable=i.retryable??!1,this.trackingId=i.trackingId,i.cause&&(this.cause=i.cause)}toErrorEvent(){return{trackingId:this.trackingId,type:this.type,message:this.message,code:this.code,retryable:this.retryable}}}class n{static handleRecordingError(e,t){return"NotAllowedError"===e.name?new s("Camera access denied. Please allow camera permissions and try again.","recording",{code:"CAMERA_ACCESS_DENIED",retryable:!0,trackingId:t}):"NotFoundError"===e.name?new s("No camera device found. Please connect a camera and try again.","recording",{code:"CAMERA_NOT_FOUND",retryable:!1,trackingId:t}):"NotReadableError"===e.name?new s("Camera is already in use by another application.","recording",{code:"CAMERA_IN_USE",retryable:!0,trackingId:t}):"OverconstrainedError"===e.name?new s("Camera does not support the requested video quality. Try a lower quality setting.","recording",{code:"UNSUPPORTED_CONSTRAINTS",retryable:!0,trackingId:t}):new s(`Recording failed: ${e.message}`,"recording",{retryable:!0,trackingId:t,cause:e})}static handleUploadError(e,t){return"AbortError"===e.name?new s("Upload was cancelled.","upload",{code:"UPLOAD_CANCELLED",retryable:!1,trackingId:t}):e.message.includes("NetworkError")||e.message.includes("fetch")?new s("Network error during upload. Please check your connection and try again.","network",{code:"NETWORK_ERROR",retryable:!0,trackingId:t}):e.message.includes("413")||/too large/i.test(e.message)?new s("File is too large for upload. Try recording a shorter video or reducing quality.","upload",{code:"FILE_TOO_LARGE",retryable:!1,trackingId:t}):e.message.includes("403")||/forbidden/i.test(e.message)?new s("Upload not authorized. Please check your authentication.","upload",{code:"UPLOAD_FORBIDDEN",retryable:!1,trackingId:t}):new s(`Upload failed: ${e.message}`,"upload",{retryable:!0,trackingId:t,cause:e})}static handleProcessingError(e,t){return/timeout/i.test(e.message)?new s("Video processing timed out. The file may be too large or complex.","processing",{code:"PROCESSING_TIMEOUT",retryable:!0,trackingId:t}):/unsupported format/i.test(e.message)?new s("Video format not supported for processing.","processing",{code:"UNSUPPORTED_FORMAT",retryable:!1,trackingId:t}):new s(`Processing failed: ${e.message}`,"processing",{retryable:!0,trackingId:t,cause:e})}static shouldRetry(e){if(!e.retryable)return!1;const t=`${e.type}:${e.code??"unknown"}`,i=this.errorCounts.get(t)??0;return i>=this.maxRetries?(this.errorCounts.delete(t),!1):(this.errorCounts.set(t,i+1),!0)}static getRetryDelay(e){const t=1e3*Math.pow(2,e);return Math.min(t,3e4)}static clearRetryCount(e,t){const i=`${e}:${t??"unknown"}`;this.errorCounts.delete(i)}static logError(e,t){const i="undefined"!=typeof window,r="undefined"!=typeof navigator,a=("undefined"!=typeof process&&void 0!==process.env&&process.env.NODE_ENV,{timestamp:(new Date).toISOString(),error:{message:e.message,type:e.type,code:e.code,retryable:e.retryable,trackingId:e.trackingId},context:t,userAgent:r?navigator.userAgent:"unknown",url:i?window.location.href:"unknown"});"undefined"!=typeof window&&window.errorTracker&&window.errorTracker.captureException(e,a)}}Object.defineProperty(n,"errorCounts",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(n,"maxRetries",{enumerable:!0,configurable:!0,writable:!0,value:3});e.BrowserSupportChecker=class{static async checkSupport(){const e="undefined"!=typeof window,t="undefined"!=typeof navigator,i={mediaRecorder:e&&"MediaRecorder"in window,getUserMedia:t&&!(!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia),webrtc:e&&!(!window.RTCPeerConnection&&!window.webkitRTCPeerConnection),indexedDB:e&&"indexedDB"in window,serviceWorker:t&&"serviceWorker"in navigator},r=i.mediaRecorder&&i.getUserMedia,a=[];i.mediaRecorder||a.push("MediaRecorder API not supported. Consider using a WebRTC-based fallback."),i.getUserMedia||a.push("getUserMedia not supported. Camera access unavailable."),i.webrtc||a.push("WebRTC not supported. Real-time features may be limited."),i.indexedDB||a.push("IndexedDB not supported. Offline capabilities unavailable.");const o=this.detectDeviceType(),s=this.getBrowserInfo();return a.length?{supported:r,features:i,recommendations:a,deviceType:o,browserInfo:s}:{supported:r,features:i,deviceType:o,browserInfo:s}}static detectDeviceType(){if("undefined"==typeof navigator)return"desktop";const e=navigator.userAgent.toLowerCase(),t=/\biPad\b/.test(navigator.userAgent)||/\bMacintosh\b/.test(navigator.userAgent)&&"ontouchend"in("undefined"!=typeof window?window:{}),i=/ipad|android(?!.*mobile)/i.test(e)||t,r=/android|webos|iphone|ipod|blackberry|iemobile|opera mini/i.test(e)&&!i;return i?"tablet":r?"mobile":"desktop"}static getBrowserInfo(){if("undefined"==typeof navigator)return{name:"Unknown",version:"Unknown",engine:"Unknown"};const e=navigator.userAgent;let t="Unknown",i="Unknown",r="Unknown";return e.includes("Edg/")?(t="Edge",i=e.match(/Edg\/(\d+)/)?.[1]??"Unknown",r="Blink"):!e.includes("Chrome")||e.includes("Edg")||e.includes("OPR")?e.includes("Firefox")?(t="Firefox",i=e.match(/Firefox\/(\d+)/)?.[1]??"Unknown",r="Gecko"):e.includes("Safari")&&!e.includes("Chrome")?(t="Safari",i=e.match(/Version\/(\d+)/)?.[1]??"Unknown",r="WebKit"):(e.includes("OPR/")||e.includes("Opera"))&&(t="Opera",i=e.match(/OPR\/(\d+)/)?.[1]??e.match(/Opera\/(\d+)/)?.[1]??"Unknown",r="Blink"):(t="Chrome",i=e.match(/Chrome\/(\d+)/)?.[1]??"Unknown",r="Blink"),{name:t,version:i,engine:r}}static getRecommendedSettings(e="desktop"){const t={audioEnabled:!0,showPreview:!0,maxRetries:3};switch(e){case"mobile":return{...t,videoQuality:"medium",maxDuration:180,chunkSize:2097152,parallelUploads:2};case"tablet":return{...t,videoQuality:"medium",maxDuration:300,chunkSize:5242880,parallelUploads:3};default:return{...t,videoQuality:"high",maxDuration:600,chunkSize:10485760,parallelUploads:4}}}static async testCameraAccess(){if("undefined"==typeof navigator||!navigator.mediaDevices?.enumerateDevices)return{hasCamera:!1,hasMicrophone:!1,devices:[]};try{const e=await navigator.mediaDevices.enumerateDevices(),t=e.filter(e=>"videoinput"===e.kind),i=e.filter(e=>"audioinput"===e.kind);return{hasCamera:t.length>0,hasMicrophone:i.length>0,devices:e}}catch{return{hasCamera:!1,hasMicrophone:!1,devices:[]}}}static async testRecordingCapability(){const e="undefined"!=typeof window,t="undefined"!=typeof navigator,i=e&&"MediaRecorder"in window&&void 0!==window.MediaRecorder?["video/webm;codecs=vp9,opus","video/webm;codecs=vp8,opus","video/webm;codecs=h264,opus","video/webm","video/mp4;codecs=h264,aac","video/mp4"].filter(e=>window.MediaRecorder.isTypeSupported?.(e)):[];let r;if(t&&navigator.mediaDevices?.getUserMedia)try{const e=await navigator.mediaDevices.getUserMedia({video:{width:1920,height:1080}}),t=e.getVideoTracks()[0],i=t?.getSettings?.()??{};r={width:i.width??1920,height:i.height??1080},e.getTracks().forEach(e=>e.stop())}catch{r={width:1280,height:720}}return{canRecord:i.length>0,supportedMimeTypes:i,maxResolution:r}}},e.ErrorHandler=n,e.VideoRecorder=a,e.VideoRecorderError=s,e.createErrorHandler=e=>({recording:t=>n.handleRecordingError(t,e),upload:t=>n.handleUploadError(t,e),processing:t=>n.handleProcessingError(t,e)}),e.default=a,e.formatBitrate=e=>{if(!Number.isFinite(e)||e<=0)return"0 kbps";const t=e/1e3;if(t<1e3)return`${Math.round(t)} kbps`;return`${(t/1e3).toFixed(1)} Mbps`},e.formatDuration=e=>{(!Number.isFinite(e)||e<0)&&(e=0);const t=Math.floor(e/3600),i=Math.floor(e%3600/60),r=Math.floor(e%60);return t>0?`${t}:${i.toString().padStart(2,"0")}:${r.toString().padStart(2,"0")}`:i>0?`${i}:${r.toString().padStart(2,"0")}`:`${r}s`},e.formatETA=(e,t)=>{if(!Number.isFinite(t)||t<=0)return"Calculating...";if(!Number.isFinite(e)||e<=0)return"0s remaining";const i=e/t;if(i<60)return`${Math.round(i)}s remaining`;if(i<3600){return`${Math.round(i/60)}m remaining`}return`${Math.floor(i/3600)}h ${Math.round(i%3600/60)}m remaining`},e.formatFileSize=o,e.formatProgress=(e,t)=>{if(!Number.isFinite(e)||!Number.isFinite(t)||t<=0)return"0%";const i=Math.max(0,Math.min(100,e/t*100));return`${Math.round(i)}%`},e.formatQualityLabel=function(e){return{low:"Low (640×480)",medium:"Medium (1280×720)",high:"High (1920×1080)",auto:"Auto (Adaptive)"}[e]??e},e.formatTimestamp=e=>{const t="string"==typeof e?new Date(e):e;return t instanceof Date&&!isNaN(t.getTime())?new Intl.DateTimeFormat("en-US",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"}).format(t):String(e)},e.formatUploadSpeed=e=>!Number.isFinite(e)||e<=0?"0 B/s":`${o(e)}/s`,e.truncateText=(e,t)=>t<=3||e.length<=t?e:`${e.substring(0,t-3)}...`,Object.defineProperty(e,"__esModule",{value:!0})}); //# sourceMappingURL=video-transcoder.min.js.map