react-native-ensys-camera-x 1.0.7
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/CustomCamera.podspec +20 -0
- package/LICENSE +20 -0
- package/README.md +833 -0
- package/android/build.gradle +135 -0
- package/android/consumer-rules.pro +47 -0
- package/android/src/main/AndroidManifest.xml +72 -0
- package/android/src/main/java/android/print/PdfPrint.java +51 -0
- package/android/src/main/java/com/customcamera/AnimatedImageViewManager.kt +199 -0
- package/android/src/main/java/com/customcamera/AudioPlayerView.kt +322 -0
- package/android/src/main/java/com/customcamera/AudioRecorder.kt +506 -0
- package/android/src/main/java/com/customcamera/BarcodeScannerActivity.kt +361 -0
- package/android/src/main/java/com/customcamera/BlurBackgroundView.kt +199 -0
- package/android/src/main/java/com/customcamera/CacheCleanupManager.kt +386 -0
- package/android/src/main/java/com/customcamera/CameraAspectRatioMode.kt +77 -0
- package/android/src/main/java/com/customcamera/CameraAsset.kt +211 -0
- package/android/src/main/java/com/customcamera/CameraTheme.kt +479 -0
- package/android/src/main/java/com/customcamera/CompressionCacheStore.kt +504 -0
- package/android/src/main/java/com/customcamera/CompressionFileNameUtils.kt +7 -0
- package/android/src/main/java/com/customcamera/CompressionProfile.kt +195 -0
- package/android/src/main/java/com/customcamera/CustomCameraActivity.kt +7291 -0
- package/android/src/main/java/com/customcamera/CustomCameraFileProvider.kt +9 -0
- package/android/src/main/java/com/customcamera/CustomCameraModule.kt +2479 -0
- package/android/src/main/java/com/customcamera/CustomCameraPackage.kt +53 -0
- package/android/src/main/java/com/customcamera/DisplayUtils.kt +9 -0
- package/android/src/main/java/com/customcamera/DocumentPreviewView.kt +846 -0
- package/android/src/main/java/com/customcamera/DrawingView.kt +214 -0
- package/android/src/main/java/com/customcamera/EmojiPickerData.kt +228 -0
- package/android/src/main/java/com/customcamera/FilterStripAdapter.kt +158 -0
- package/android/src/main/java/com/customcamera/GifSupport.kt +215 -0
- package/android/src/main/java/com/customcamera/GlobalAudioPlayer.kt +320 -0
- package/android/src/main/java/com/customcamera/ImageExporter.kt +1394 -0
- package/android/src/main/java/com/customcamera/ImageFormatSupport.kt +163 -0
- package/android/src/main/java/com/customcamera/Localization.kt +206 -0
- package/android/src/main/java/com/customcamera/MediaAdapters.kt +758 -0
- package/android/src/main/java/com/customcamera/MediaCacheManager.kt +782 -0
- package/android/src/main/java/com/customcamera/MediaExportFormats.kt +201 -0
- package/android/src/main/java/com/customcamera/MediaFileIo.kt +310 -0
- package/android/src/main/java/com/customcamera/MediaMetadataStore.kt +494 -0
- package/android/src/main/java/com/customcamera/MediaPreviewEditor.kt +14865 -0
- package/android/src/main/java/com/customcamera/MediaPublisher.kt +423 -0
- package/android/src/main/java/com/customcamera/MediaStorageWorkflow.kt +722 -0
- package/android/src/main/java/com/customcamera/MediaStoreRepository.kt +378 -0
- package/android/src/main/java/com/customcamera/MediaThumbnailView.kt +286 -0
- package/android/src/main/java/com/customcamera/MediaUtils.kt +37 -0
- package/android/src/main/java/com/customcamera/NativeVideoViewManager.kt +845 -0
- package/android/src/main/java/com/customcamera/OfficeDocumentConverter.kt +2791 -0
- package/android/src/main/java/com/customcamera/PendingCaptureCrops.kt +88 -0
- package/android/src/main/java/com/customcamera/PowerPointPreview.kt +4077 -0
- package/android/src/main/java/com/customcamera/RecentPickerStore.kt +74 -0
- package/android/src/main/java/com/customcamera/SecureFileEraser.kt +411 -0
- package/android/src/main/java/com/customcamera/SecureKeyModule.kt +88 -0
- package/android/src/main/java/com/customcamera/SecureMediaDeleter.kt +493 -0
- package/android/src/main/java/com/customcamera/StickerPickerData.kt +107 -0
- package/android/src/main/java/com/customcamera/SvgDrawable.kt +175 -0
- package/android/src/main/java/com/customcamera/SvgIconViewManager.kt +77 -0
- package/android/src/main/java/com/customcamera/TextEntryController.kt +553 -0
- package/android/src/main/java/com/customcamera/ThumbnailLoader.kt +455 -0
- package/android/src/main/java/com/customcamera/UploadService.kt +909 -0
- package/android/src/main/java/com/customcamera/VideoEditUtils.kt +210 -0
- package/android/src/main/java/com/customcamera/VideoRotationTranscoder.kt +1992 -0
- package/android/src/main/java/com/customcamera/gif/AnimatedGifEncoder.kt +473 -0
- package/android/src/main/java/com/customcamera/gif/GifDecoder.kt +690 -0
- package/android/src/main/java/com/customcamera/gif/GifFramesDrawable.kt +160 -0
- package/android/src/main/java/com/customcamera/gif/GifHttp.kt +112 -0
- package/android/src/main/java/com/customcamera/gif/GifImageLoader.kt +186 -0
- package/android/src/main/java/com/customcamera/gif/GifModels.kt +74 -0
- package/android/src/main/java/com/customcamera/gif/GifProvider.kt +49 -0
- package/android/src/main/java/com/customcamera/gif/GifProviders.kt +27 -0
- package/android/src/main/java/com/customcamera/gif/GifRepository.kt +124 -0
- package/android/src/main/java/com/customcamera/gif/GifSafety.kt +33 -0
- package/android/src/main/java/com/customcamera/gif/GiphyGifProvider.kt +146 -0
- package/android/src/main/java/com/customcamera/gif/LZWEncoder.kt +258 -0
- package/android/src/main/java/com/customcamera/gif/NeuQuant.kt +435 -0
- package/android/src/main/java/com/customcamera/gif/TenorGifProvider.kt +147 -0
- package/android/src/main/res/drawable/bg_brush_selected.xml +5 -0
- package/android/src/main/res/drawable/bg_color_swatch.xml +5 -0
- package/android/src/main/res/drawable/bg_editor_toolbar.xml +6 -0
- package/android/src/main/res/drawable/bg_icon_circle.xml +5 -0
- package/android/src/main/res/drawable/bg_icon_circle_light.xml +5 -0
- package/android/src/main/res/drawable/bg_mode_tab_active.xml +6 -0
- package/android/src/main/res/drawable/bg_pill.xml +6 -0
- package/android/src/main/res/drawable/bg_recording_dot.xml +6 -0
- package/android/src/main/res/drawable/bg_selection_badge.xml +8 -0
- package/android/src/main/res/drawable/bg_send_button.xml +5 -0
- package/android/src/main/res/drawable/bg_shutter_inner.xml +5 -0
- package/android/src/main/res/drawable/bg_shutter_recording.xml +6 -0
- package/android/src/main/res/drawable/bg_shutter_ring.xml +8 -0
- package/android/src/main/res/drawable/bg_shutter_video_idle.xml +5 -0
- package/android/src/main/res/drawable/bg_thumbnail.xml +9 -0
- package/android/src/main/res/drawable/ic_aspect_ratio.xml +17 -0
- package/android/src/main/res/drawable/ic_audio.xml +10 -0
- package/android/src/main/res/drawable/ic_back.xml +10 -0
- package/android/src/main/res/drawable/ic_brush_medium.xml +12 -0
- package/android/src/main/res/drawable/ic_brush_thick.xml +12 -0
- package/android/src/main/res/drawable/ic_brush_thin.xml +12 -0
- package/android/src/main/res/drawable/ic_check.xml +17 -0
- package/android/src/main/res/drawable/ic_chevron_down.xml +10 -0
- package/android/src/main/res/drawable/ic_chevron_up.xml +13 -0
- package/android/src/main/res/drawable/ic_clear_drawing.xml +10 -0
- package/android/src/main/res/drawable/ic_close.xml +10 -0
- package/android/src/main/res/drawable/ic_cover.xml +9 -0
- package/android/src/main/res/drawable/ic_crop.xml +10 -0
- package/android/src/main/res/drawable/ic_delete.xml +9 -0
- package/android/src/main/res/drawable/ic_document.xml +10 -0
- package/android/src/main/res/drawable/ic_done.xml +10 -0
- package/android/src/main/res/drawable/ic_download.xml +10 -0
- package/android/src/main/res/drawable/ic_emoji.xml +10 -0
- package/android/src/main/res/drawable/ic_filter.xml +10 -0
- package/android/src/main/res/drawable/ic_flash_auto.xml +7 -0
- package/android/src/main/res/drawable/ic_flash_off.xml +23 -0
- package/android/src/main/res/drawable/ic_flash_on.xml +16 -0
- package/android/src/main/res/drawable/ic_flip.xml +15 -0
- package/android/src/main/res/drawable/ic_gallery.xml +10 -0
- package/android/src/main/res/drawable/ic_gif.xml +24 -0
- package/android/src/main/res/drawable/ic_lock.xml +19 -0
- package/android/src/main/res/drawable/ic_lock_open.xml +22 -0
- package/android/src/main/res/drawable/ic_marker.xml +10 -0
- package/android/src/main/res/drawable/ic_more_vert.xml +18 -0
- package/android/src/main/res/drawable/ic_overlay_close.xml +19 -0
- package/android/src/main/res/drawable/ic_overlay_resize.xml +18 -0
- package/android/src/main/res/drawable/ic_pause.xml +10 -0
- package/android/src/main/res/drawable/ic_photo_mode.xml +10 -0
- package/android/src/main/res/drawable/ic_play.xml +10 -0
- package/android/src/main/res/drawable/ic_redo.xml +10 -0
- package/android/src/main/res/drawable/ic_rotate.xml +9 -0
- package/android/src/main/res/drawable/ic_send.xml +10 -0
- package/android/src/main/res/drawable/ic_share.xml +10 -0
- package/android/src/main/res/drawable/ic_stat_upload.xml +10 -0
- package/android/src/main/res/drawable/ic_sticker.xml +24 -0
- package/android/src/main/res/drawable/ic_text.xml +10 -0
- package/android/src/main/res/drawable/ic_text_align_center.xml +11 -0
- package/android/src/main/res/drawable/ic_text_align_left.xml +11 -0
- package/android/src/main/res/drawable/ic_text_align_right.xml +11 -0
- package/android/src/main/res/drawable/ic_text_bg.xml +11 -0
- package/android/src/main/res/drawable/ic_trim.xml +23 -0
- package/android/src/main/res/drawable/ic_undo.xml +10 -0
- package/android/src/main/res/drawable/ic_video_mode.xml +10 -0
- package/android/src/main/res/drawable/ic_video_note.xml +13 -0
- package/android/src/main/res/drawable/ic_volume_off.xml +9 -0
- package/android/src/main/res/drawable/ic_volume_on.xml +9 -0
- package/android/src/main/res/drawable/sticker_heart.xml +9 -0
- package/android/src/main/res/drawable/sticker_smile.xml +9 -0
- package/android/src/main/res/drawable/sticker_star.xml +9 -0
- package/android/src/main/res/drawable/sticker_thumbsup.xml +9 -0
- package/android/src/main/res/values/colors.xml +34 -0
- package/android/src/main/res/values/ids.xml +15 -0
- package/android/src/main/res/values/strings.xml +15 -0
- package/android/src/main/res/values/styles.xml +20 -0
- package/android/src/main/res/xml/file_paths.xml +34 -0
- package/ios/CustomCamera.h +5 -0
- package/ios/CustomCamera.mm +214 -0
- package/lib/module/AudioPlayerContext.js +233 -0
- package/lib/module/AudioPlayerContext.js.map +1 -0
- package/lib/module/CameraPreview.js +304 -0
- package/lib/module/CameraPreview.js.map +1 -0
- package/lib/module/NativeAnimatedImageView.js +41 -0
- package/lib/module/NativeAnimatedImageView.js.map +1 -0
- package/lib/module/NativeAudioPlayerView.js +33 -0
- package/lib/module/NativeAudioPlayerView.js.map +1 -0
- package/lib/module/NativeBlurView.js +35 -0
- package/lib/module/NativeBlurView.js.map +1 -0
- package/lib/module/NativeCustomCamera.js +128 -0
- package/lib/module/NativeCustomCamera.js.map +1 -0
- package/lib/module/NativeCustomCamera.web.js +143 -0
- package/lib/module/NativeCustomCamera.web.js.map +1 -0
- package/lib/module/NativeCustomVideoView.js +5 -0
- package/lib/module/NativeCustomVideoView.js.map +1 -0
- package/lib/module/NativeDocumentPreviewView.js +156 -0
- package/lib/module/NativeDocumentPreviewView.js.map +1 -0
- package/lib/module/NativeMediaThumbnailView.js +110 -0
- package/lib/module/NativeMediaThumbnailView.js.map +1 -0
- package/lib/module/NativeSvgIcon.js +32 -0
- package/lib/module/NativeSvgIcon.js.map +1 -0
- package/lib/module/ResponsivePreviewContainer.js +171 -0
- package/lib/module/ResponsivePreviewContainer.js.map +1 -0
- package/lib/module/accessibility.js +61 -0
- package/lib/module/accessibility.js.map +1 -0
- package/lib/module/config.js +352 -0
- package/lib/module/config.js.map +1 -0
- package/lib/module/cpuOptimization.js +296 -0
- package/lib/module/cpuOptimization.js.map +1 -0
- package/lib/module/db/DatabaseManager.js +191 -0
- package/lib/module/db/DatabaseManager.js.map +1 -0
- package/lib/module/db/MediaMapper.js +284 -0
- package/lib/module/db/MediaMapper.js.map +1 -0
- package/lib/module/db/MediaRepository.js +242 -0
- package/lib/module/db/MediaRepository.js.map +1 -0
- package/lib/module/db/index.js +41 -0
- package/lib/module/db/index.js.map +1 -0
- package/lib/module/db/migrations.js +180 -0
- package/lib/module/db/migrations.js.map +1 -0
- package/lib/module/db/react-native-sqlcipher.d.js +2 -0
- package/lib/module/db/react-native-sqlcipher.d.js.map +1 -0
- package/lib/module/db/secureKey.js +87 -0
- package/lib/module/db/secureKey.js.map +1 -0
- package/lib/module/db/types.js +57 -0
- package/lib/module/db/types.js.map +1 -0
- package/lib/module/db/useMediaDatabase.js +123 -0
- package/lib/module/db/useMediaDatabase.js.map +1 -0
- package/lib/module/defaultIcons.js +2259 -0
- package/lib/module/defaultIcons.js.map +1 -0
- package/lib/module/deviceCapabilities.js +408 -0
- package/lib/module/deviceCapabilities.js.map +1 -0
- package/lib/module/errors.js +267 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/iconConfig.js +796 -0
- package/lib/module/iconConfig.js.map +1 -0
- package/lib/module/icons.js +514 -0
- package/lib/module/icons.js.map +1 -0
- package/lib/module/index.js +251 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/jest-globals.d.js +2 -0
- package/lib/module/jest-globals.d.js.map +1 -0
- package/lib/module/library/MediaLibrary.js +107 -0
- package/lib/module/library/MediaLibrary.js.map +1 -0
- package/lib/module/library/MediaLibraryScreen.js +2597 -0
- package/lib/module/library/MediaLibraryScreen.js.map +1 -0
- package/lib/module/library/components/AudioPreview.js +74 -0
- package/lib/module/library/components/AudioPreview.js.map +1 -0
- package/lib/module/library/components/DocumentPreview.js +331 -0
- package/lib/module/library/components/DocumentPreview.js.map +1 -0
- package/lib/module/library/components/ExpandableFab.js +343 -0
- package/lib/module/library/components/ExpandableFab.js.map +1 -0
- package/lib/module/library/components/FileTiles.js +251 -0
- package/lib/module/library/components/FileTiles.js.map +1 -0
- package/lib/module/library/components/ImagePreview.js +70 -0
- package/lib/module/library/components/ImagePreview.js.map +1 -0
- package/lib/module/library/components/MediaMetadataHeader.js +175 -0
- package/lib/module/library/components/MediaMetadataHeader.js.map +1 -0
- package/lib/module/library/components/RecordAudioScreen.js +816 -0
- package/lib/module/library/components/RecordAudioScreen.js.map +1 -0
- package/lib/module/library/components/VideoPreview.js +453 -0
- package/lib/module/library/components/VideoPreview.js.map +1 -0
- package/lib/module/library/components/ViewerCounter.js +55 -0
- package/lib/module/library/components/ViewerCounter.js.map +1 -0
- package/lib/module/library/compressionEstimate.js +507 -0
- package/lib/module/library/compressionEstimate.js.map +1 -0
- package/lib/module/library/fileMetadata.js +196 -0
- package/lib/module/library/fileMetadata.js.map +1 -0
- package/lib/module/library/index.js +42 -0
- package/lib/module/library/index.js.map +1 -0
- package/lib/module/library/libraryIconRegistry.js +69 -0
- package/lib/module/library/libraryIconRegistry.js.map +1 -0
- package/lib/module/library/libraryIcons.js +858 -0
- package/lib/module/library/libraryIcons.js.map +1 -0
- package/lib/module/library/localizedFormat.js +181 -0
- package/lib/module/library/localizedFormat.js.map +1 -0
- package/lib/module/library/palette.js +225 -0
- package/lib/module/library/palette.js.map +1 -0
- package/lib/module/library/responsive.js +53 -0
- package/lib/module/library/responsive.js.map +1 -0
- package/lib/module/locales.js +416 -0
- package/lib/module/locales.js.map +1 -0
- package/lib/module/localization.js +1064 -0
- package/lib/module/localization.js.map +1 -0
- package/lib/module/logger.js +20 -0
- package/lib/module/logger.js.map +1 -0
- package/lib/module/lowMemoryCache.js +206 -0
- package/lib/module/lowMemoryCache.js.map +1 -0
- package/lib/module/mediaHistory.js +33 -0
- package/lib/module/mediaHistory.js.map +1 -0
- package/lib/module/mediaStorageService.js +402 -0
- package/lib/module/mediaStorageService.js.map +1 -0
- package/lib/module/memory.js +79 -0
- package/lib/module/memory.js.map +1 -0
- package/lib/module/nativeActions.js +307 -0
- package/lib/module/nativeActions.js.map +1 -0
- package/lib/module/nativeComponentSupport.js +61 -0
- package/lib/module/nativeComponentSupport.js.map +1 -0
- package/lib/module/networkPerformance.js +24 -0
- package/lib/module/networkPerformance.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/performance.js +104 -0
- package/lib/module/performance.js.map +1 -0
- package/lib/module/performanceMonitor.js +155 -0
- package/lib/module/performanceMonitor.js.map +1 -0
- package/lib/module/permissions.js +370 -0
- package/lib/module/permissions.js.map +1 -0
- package/lib/module/rendering.js +40 -0
- package/lib/module/rendering.js.map +1 -0
- package/lib/module/security.js +18 -0
- package/lib/module/security.js.map +1 -0
- package/lib/module/state.js +266 -0
- package/lib/module/state.js.map +1 -0
- package/lib/module/storage.js +400 -0
- package/lib/module/storage.js.map +1 -0
- package/lib/module/theme.js +390 -0
- package/lib/module/theme.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils.js +115 -0
- package/lib/module/utils.js.map +1 -0
- package/lib/module/validation.js +288 -0
- package/lib/module/validation.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/AudioPlayerContext.d.ts +27 -0
- package/lib/typescript/src/AudioPlayerContext.d.ts.map +1 -0
- package/lib/typescript/src/CameraPreview.d.ts +13 -0
- package/lib/typescript/src/CameraPreview.d.ts.map +1 -0
- package/lib/typescript/src/NativeAnimatedImageView.d.ts +33 -0
- package/lib/typescript/src/NativeAnimatedImageView.d.ts.map +1 -0
- package/lib/typescript/src/NativeAudioPlayerView.d.ts +12 -0
- package/lib/typescript/src/NativeAudioPlayerView.d.ts.map +1 -0
- package/lib/typescript/src/NativeBlurView.d.ts +12 -0
- package/lib/typescript/src/NativeBlurView.d.ts.map +1 -0
- package/lib/typescript/src/NativeCustomCamera.d.ts +167 -0
- package/lib/typescript/src/NativeCustomCamera.d.ts.map +1 -0
- package/lib/typescript/src/NativeCustomCamera.web.d.ts +47 -0
- package/lib/typescript/src/NativeCustomCamera.web.d.ts.map +1 -0
- package/lib/typescript/src/NativeCustomVideoView.d.ts +30 -0
- package/lib/typescript/src/NativeCustomVideoView.d.ts.map +1 -0
- package/lib/typescript/src/NativeDocumentPreviewView.d.ts +48 -0
- package/lib/typescript/src/NativeDocumentPreviewView.d.ts.map +1 -0
- package/lib/typescript/src/NativeMediaThumbnailView.d.ts +54 -0
- package/lib/typescript/src/NativeMediaThumbnailView.d.ts.map +1 -0
- package/lib/typescript/src/NativeSvgIcon.d.ts +25 -0
- package/lib/typescript/src/NativeSvgIcon.d.ts.map +1 -0
- package/lib/typescript/src/ResponsivePreviewContainer.d.ts +92 -0
- package/lib/typescript/src/ResponsivePreviewContainer.d.ts.map +1 -0
- package/lib/typescript/src/accessibility.d.ts +20 -0
- package/lib/typescript/src/accessibility.d.ts.map +1 -0
- package/lib/typescript/src/config.d.ts +145 -0
- package/lib/typescript/src/config.d.ts.map +1 -0
- package/lib/typescript/src/cpuOptimization.d.ts +63 -0
- package/lib/typescript/src/cpuOptimization.d.ts.map +1 -0
- package/lib/typescript/src/db/DatabaseManager.d.ts +48 -0
- package/lib/typescript/src/db/DatabaseManager.d.ts.map +1 -0
- package/lib/typescript/src/db/MediaMapper.d.ts +77 -0
- package/lib/typescript/src/db/MediaMapper.d.ts.map +1 -0
- package/lib/typescript/src/db/MediaRepository.d.ts +66 -0
- package/lib/typescript/src/db/MediaRepository.d.ts.map +1 -0
- package/lib/typescript/src/db/index.d.ts +23 -0
- package/lib/typescript/src/db/index.d.ts.map +1 -0
- package/lib/typescript/src/db/migrations.d.ts +31 -0
- package/lib/typescript/src/db/migrations.d.ts.map +1 -0
- package/lib/typescript/src/db/secureKey.d.ts +10 -0
- package/lib/typescript/src/db/secureKey.d.ts.map +1 -0
- package/lib/typescript/src/db/types.d.ts +115 -0
- package/lib/typescript/src/db/types.d.ts.map +1 -0
- package/lib/typescript/src/db/useMediaDatabase.d.ts +41 -0
- package/lib/typescript/src/db/useMediaDatabase.d.ts.map +1 -0
- package/lib/typescript/src/defaultIcons.d.ts +139 -0
- package/lib/typescript/src/defaultIcons.d.ts.map +1 -0
- package/lib/typescript/src/deviceCapabilities.d.ts +92 -0
- package/lib/typescript/src/deviceCapabilities.d.ts.map +1 -0
- package/lib/typescript/src/errors.d.ts +95 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/iconConfig.d.ts +176 -0
- package/lib/typescript/src/iconConfig.d.ts.map +1 -0
- package/lib/typescript/src/icons.d.ts +217 -0
- package/lib/typescript/src/icons.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +104 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/library/MediaLibrary.d.ts +57 -0
- package/lib/typescript/src/library/MediaLibrary.d.ts.map +1 -0
- package/lib/typescript/src/library/MediaLibraryScreen.d.ts +51 -0
- package/lib/typescript/src/library/MediaLibraryScreen.d.ts.map +1 -0
- package/lib/typescript/src/library/components/AudioPreview.d.ts +6 -0
- package/lib/typescript/src/library/components/AudioPreview.d.ts.map +1 -0
- package/lib/typescript/src/library/components/DocumentPreview.d.ts +10 -0
- package/lib/typescript/src/library/components/DocumentPreview.d.ts.map +1 -0
- package/lib/typescript/src/library/components/ExpandableFab.d.ts +39 -0
- package/lib/typescript/src/library/components/ExpandableFab.d.ts.map +1 -0
- package/lib/typescript/src/library/components/FileTiles.d.ts +8 -0
- package/lib/typescript/src/library/components/FileTiles.d.ts.map +1 -0
- package/lib/typescript/src/library/components/ImagePreview.d.ts +5 -0
- package/lib/typescript/src/library/components/ImagePreview.d.ts.map +1 -0
- package/lib/typescript/src/library/components/MediaMetadataHeader.d.ts +16 -0
- package/lib/typescript/src/library/components/MediaMetadataHeader.d.ts.map +1 -0
- package/lib/typescript/src/library/components/RecordAudioScreen.d.ts +8 -0
- package/lib/typescript/src/library/components/RecordAudioScreen.d.ts.map +1 -0
- package/lib/typescript/src/library/components/VideoPreview.d.ts +20 -0
- package/lib/typescript/src/library/components/VideoPreview.d.ts.map +1 -0
- package/lib/typescript/src/library/components/ViewerCounter.d.ts +13 -0
- package/lib/typescript/src/library/components/ViewerCounter.d.ts.map +1 -0
- package/lib/typescript/src/library/compressionEstimate.d.ts +313 -0
- package/lib/typescript/src/library/compressionEstimate.d.ts.map +1 -0
- package/lib/typescript/src/library/fileMetadata.d.ts +33 -0
- package/lib/typescript/src/library/fileMetadata.d.ts.map +1 -0
- package/lib/typescript/src/library/index.d.ts +40 -0
- package/lib/typescript/src/library/index.d.ts.map +1 -0
- package/lib/typescript/src/library/libraryIconRegistry.d.ts +35 -0
- package/lib/typescript/src/library/libraryIconRegistry.d.ts.map +1 -0
- package/lib/typescript/src/library/libraryIcons.d.ts +85 -0
- package/lib/typescript/src/library/libraryIcons.d.ts.map +1 -0
- package/lib/typescript/src/library/localizedFormat.d.ts +49 -0
- package/lib/typescript/src/library/localizedFormat.d.ts.map +1 -0
- package/lib/typescript/src/library/palette.d.ts +104 -0
- package/lib/typescript/src/library/palette.d.ts.map +1 -0
- package/lib/typescript/src/library/responsive.d.ts +15 -0
- package/lib/typescript/src/library/responsive.d.ts.map +1 -0
- package/lib/typescript/src/locales.d.ts +103 -0
- package/lib/typescript/src/locales.d.ts.map +1 -0
- package/lib/typescript/src/localization.d.ts +1051 -0
- package/lib/typescript/src/localization.d.ts.map +1 -0
- package/lib/typescript/src/logger.d.ts +4 -0
- package/lib/typescript/src/logger.d.ts.map +1 -0
- package/lib/typescript/src/lowMemoryCache.d.ts +64 -0
- package/lib/typescript/src/lowMemoryCache.d.ts.map +1 -0
- package/lib/typescript/src/mediaHistory.d.ts +3 -0
- package/lib/typescript/src/mediaHistory.d.ts.map +1 -0
- package/lib/typescript/src/mediaStorageService.d.ts +155 -0
- package/lib/typescript/src/mediaStorageService.d.ts.map +1 -0
- package/lib/typescript/src/memory.d.ts +21 -0
- package/lib/typescript/src/memory.d.ts.map +1 -0
- package/lib/typescript/src/nativeActions.d.ts +174 -0
- package/lib/typescript/src/nativeActions.d.ts.map +1 -0
- package/lib/typescript/src/nativeComponentSupport.d.ts +30 -0
- package/lib/typescript/src/nativeComponentSupport.d.ts.map +1 -0
- package/lib/typescript/src/networkPerformance.d.ts +12 -0
- package/lib/typescript/src/networkPerformance.d.ts.map +1 -0
- package/lib/typescript/src/performance.d.ts +32 -0
- package/lib/typescript/src/performance.d.ts.map +1 -0
- package/lib/typescript/src/performanceMonitor.d.ts +45 -0
- package/lib/typescript/src/performanceMonitor.d.ts.map +1 -0
- package/lib/typescript/src/permissions.d.ts +132 -0
- package/lib/typescript/src/permissions.d.ts.map +1 -0
- package/lib/typescript/src/rendering.d.ts +9 -0
- package/lib/typescript/src/rendering.d.ts.map +1 -0
- package/lib/typescript/src/security.d.ts +6 -0
- package/lib/typescript/src/security.d.ts.map +1 -0
- package/lib/typescript/src/state.d.ts +102 -0
- package/lib/typescript/src/state.d.ts.map +1 -0
- package/lib/typescript/src/storage.d.ts +117 -0
- package/lib/typescript/src/storage.d.ts.map +1 -0
- package/lib/typescript/src/theme.d.ts +15 -0
- package/lib/typescript/src/theme.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +802 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/utils.d.ts +50 -0
- package/lib/typescript/src/utils.d.ts.map +1 -0
- package/lib/typescript/src/validation.d.ts +52 -0
- package/lib/typescript/src/validation.d.ts.map +1 -0
- package/package.json +204 -0
- package/react-native.config.js +17 -0
- package/src/AudioPlayerContext.tsx +220 -0
- package/src/CameraPreview.tsx +352 -0
- package/src/NativeAnimatedImageView.tsx +71 -0
- package/src/NativeAudioPlayerView.tsx +39 -0
- package/src/NativeBlurView.tsx +40 -0
- package/src/NativeCustomCamera.ts +305 -0
- package/src/NativeCustomCamera.web.ts +192 -0
- package/src/NativeCustomVideoView.ts +30 -0
- package/src/NativeDocumentPreviewView.tsx +196 -0
- package/src/NativeMediaThumbnailView.tsx +167 -0
- package/src/NativeSvgIcon.tsx +46 -0
- package/src/ResponsivePreviewContainer.tsx +227 -0
- package/src/accessibility.ts +80 -0
- package/src/config.ts +406 -0
- package/src/cpuOptimization.ts +365 -0
- package/src/db/DatabaseManager.ts +212 -0
- package/src/db/MediaMapper.ts +377 -0
- package/src/db/MediaRepository.ts +313 -0
- package/src/db/index.ts +73 -0
- package/src/db/migrations.ts +222 -0
- package/src/db/react-native-sqlcipher.d.ts +76 -0
- package/src/db/secureKey.ts +100 -0
- package/src/db/types.ts +123 -0
- package/src/db/useMediaDatabase.ts +191 -0
- package/src/defaultIcons.tsx +2390 -0
- package/src/deviceCapabilities.ts +438 -0
- package/src/errors.ts +350 -0
- package/src/iconConfig.tsx +962 -0
- package/src/icons.tsx +788 -0
- package/src/index.tsx +763 -0
- package/src/jest-globals.d.ts +9 -0
- package/src/library/MediaLibrary.tsx +141 -0
- package/src/library/MediaLibraryScreen.tsx +3003 -0
- package/src/library/components/AudioPreview.tsx +73 -0
- package/src/library/components/DocumentPreview.tsx +361 -0
- package/src/library/components/ExpandableFab.tsx +342 -0
- package/src/library/components/FileTiles.tsx +252 -0
- package/src/library/components/ImagePreview.tsx +72 -0
- package/src/library/components/MediaMetadataHeader.tsx +208 -0
- package/src/library/components/RecordAudioScreen.tsx +845 -0
- package/src/library/components/VideoPreview.tsx +478 -0
- package/src/library/components/ViewerCounter.tsx +71 -0
- package/src/library/compressionEstimate.ts +662 -0
- package/src/library/fileMetadata.ts +212 -0
- package/src/library/index.ts +66 -0
- package/src/library/libraryIconRegistry.tsx +86 -0
- package/src/library/libraryIcons.tsx +928 -0
- package/src/library/localizedFormat.ts +207 -0
- package/src/library/palette.ts +281 -0
- package/src/library/responsive.ts +49 -0
- package/src/locales.ts +381 -0
- package/src/localization.ts +1947 -0
- package/src/logger.ts +20 -0
- package/src/lowMemoryCache.ts +218 -0
- package/src/mediaHistory.ts +42 -0
- package/src/mediaStorageService.ts +546 -0
- package/src/memory.ts +102 -0
- package/src/nativeActions.ts +422 -0
- package/src/nativeComponentSupport.ts +60 -0
- package/src/networkPerformance.ts +30 -0
- package/src/performance.ts +137 -0
- package/src/performanceMonitor.ts +219 -0
- package/src/permissions.ts +403 -0
- package/src/rendering.ts +47 -0
- package/src/security.ts +19 -0
- package/src/state.ts +333 -0
- package/src/storage.ts +453 -0
- package/src/theme.tsx +467 -0
- package/src/types.ts +946 -0
- package/src/utils.ts +177 -0
- package/src/validation.ts +372 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
package com.customcamera
|
|
2
|
+
|
|
3
|
+
import android.media.MediaCodecList
|
|
4
|
+
import android.media.MediaMuxer
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import java.util.Locale
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The containers the preview editor's "Export By" menu can PRODUCE, and whether this particular
|
|
10
|
+
* device can actually produce them.
|
|
11
|
+
*
|
|
12
|
+
* This is the encode-side counterpart of [ImageFormatSupport], which answers the decode-side
|
|
13
|
+
* question ("what is this file, and can we render it?"). The two are deliberately separate: a
|
|
14
|
+
* device can happily *decode* AVIF from API 31 while having no AVIF *encoder* at all, and a
|
|
15
|
+
* format that cannot be written must never appear in the menu. Everything that decides what the
|
|
16
|
+
* dropdown shows reads [encodableImageFormats] / [encodableVideoContainers], so an option is
|
|
17
|
+
* offered only when the export behind it can genuinely be performed.
|
|
18
|
+
*
|
|
19
|
+
* Capability is resolved from the platform codec list rather than from an API-level table alone,
|
|
20
|
+
* because HEIC/AVIF image encoders are hardware features that vary by device at the same API
|
|
21
|
+
* level. Results are memoised: [MediaCodecList] enumeration is not free and the menu can be
|
|
22
|
+
* opened repeatedly.
|
|
23
|
+
*/
|
|
24
|
+
object MediaExportFormats {
|
|
25
|
+
|
|
26
|
+
// MIME spellings of the platform's dedicated STILL-image encoders. Written as literals rather
|
|
27
|
+
// than referencing MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC / MIMETYPE_IMAGE_AVIF so this file
|
|
28
|
+
// compiles and runs identically regardless of compileSdk (they are API 28 / API 34 constants).
|
|
29
|
+
const val MIME_IMAGE_HEIC = "image/vnd.android.heic"
|
|
30
|
+
const val MIME_IMAGE_AVIF = "image/avif"
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Still containers offered by "Export By", in menu order.
|
|
34
|
+
*
|
|
35
|
+
* The entries are [ImageFormatSupport.Format] values so the extension, MIME type and display
|
|
36
|
+
* name keep coming from the single container table — an exported `.webp` is described as
|
|
37
|
+
* `image/webp` by the cache, the publisher and the thumbnail loader without any of them
|
|
38
|
+
* learning about exporting.
|
|
39
|
+
*
|
|
40
|
+
* GIF and BMP are deliberately absent: Android ships no encoder for either, so they could
|
|
41
|
+
* never be produced. HEIC is absent because it is the same container as HEIF and offering
|
|
42
|
+
* both would present the user with one choice twice.
|
|
43
|
+
*/
|
|
44
|
+
private val IMAGE_FORMAT_ORDER: List<ImageFormatSupport.Format> = listOf(
|
|
45
|
+
ImageFormatSupport.Format.JPEG,
|
|
46
|
+
ImageFormatSupport.Format.PNG,
|
|
47
|
+
ImageFormatSupport.Format.WEBP,
|
|
48
|
+
ImageFormatSupport.Format.HEIF,
|
|
49
|
+
ImageFormatSupport.Format.AVIF,
|
|
50
|
+
ImageFormatSupport.Format.PDF
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A video container this library can write, together with everything the transcode needs:
|
|
55
|
+
* the video codec to encode into, the [MediaMuxer] output format that writes the container,
|
|
56
|
+
* and the audio codecs that container is able to carry.
|
|
57
|
+
*
|
|
58
|
+
* [supportsOrientationHint] is why a portrait clip survives the conversion. MP4 and 3GP store
|
|
59
|
+
* orientation as container metadata, so the frames are re-encoded in their coded orientation
|
|
60
|
+
* and the source's rotation is re-stamped on the copy (exactly what compression does — see
|
|
61
|
+
* [VideoRotationTranscoder.compress]). WebM has no such field, so for it the rotation is baked
|
|
62
|
+
* into the pixels instead; either way the exported clip DISPLAYS at the original's dimensions,
|
|
63
|
+
* aspect ratio and orientation.
|
|
64
|
+
*
|
|
65
|
+
* [audioMimeTypes] is the set the container's muxer accepts. A source track outside it cannot
|
|
66
|
+
* be carried through — WebM takes only Vorbis/Opus, so an AAC track is dropped rather than
|
|
67
|
+
* making the whole export fail. Callers are told so they can say it out loud.
|
|
68
|
+
*/
|
|
69
|
+
enum class VideoContainer(
|
|
70
|
+
val extension: String,
|
|
71
|
+
val mimeType: String,
|
|
72
|
+
val videoMimeType: String,
|
|
73
|
+
val muxerOutputFormat: Int,
|
|
74
|
+
val minSdk: Int,
|
|
75
|
+
val supportsOrientationHint: Boolean,
|
|
76
|
+
val audioMimeTypes: Set<String>,
|
|
77
|
+
val alternateExtensions: Set<String> = emptySet()
|
|
78
|
+
) {
|
|
79
|
+
MP4(
|
|
80
|
+
extension = "mp4",
|
|
81
|
+
mimeType = "video/mp4",
|
|
82
|
+
videoMimeType = "video/avc",
|
|
83
|
+
muxerOutputFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
|
|
84
|
+
minSdk = Build.VERSION_CODES.BASE,
|
|
85
|
+
supportsOrientationHint = true,
|
|
86
|
+
audioMimeTypes = setOf("audio/mp4a-latm", "audio/3gpp", "audio/amr-wb"),
|
|
87
|
+
alternateExtensions = setOf("m4v")
|
|
88
|
+
),
|
|
89
|
+
WEBM(
|
|
90
|
+
extension = "webm",
|
|
91
|
+
mimeType = "video/webm",
|
|
92
|
+
videoMimeType = "video/x-vnd.on2.vp8",
|
|
93
|
+
muxerOutputFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM,
|
|
94
|
+
minSdk = Build.VERSION_CODES.BASE,
|
|
95
|
+
// The WebM writer has no rotation field, so orientation is baked into the pixels instead.
|
|
96
|
+
supportsOrientationHint = false,
|
|
97
|
+
audioMimeTypes = setOf("audio/vorbis", "audio/opus")
|
|
98
|
+
),
|
|
99
|
+
THREE_GPP(
|
|
100
|
+
extension = "3gp",
|
|
101
|
+
mimeType = "video/3gpp",
|
|
102
|
+
videoMimeType = "video/avc",
|
|
103
|
+
// MUXER_OUTPUT_3GPP is API 26; the enum is filtered by minSdk before it is ever used.
|
|
104
|
+
muxerOutputFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP,
|
|
105
|
+
minSdk = Build.VERSION_CODES.O,
|
|
106
|
+
supportsOrientationHint = true,
|
|
107
|
+
audioMimeTypes = setOf("audio/mp4a-latm", "audio/3gpp", "audio/amr-wb"),
|
|
108
|
+
alternateExtensions = setOf("3gpp")
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
/** Every file extension that names this container, without the leading dot. */
|
|
112
|
+
val extensions: Set<String> get() = alternateExtensions + extension
|
|
113
|
+
|
|
114
|
+
/** The extension with its leading dot, for building file names. */
|
|
115
|
+
val dottedExtension: String get() = ".$extension"
|
|
116
|
+
|
|
117
|
+
/** Whether [audioMimeType] can be muxed into this container as-is (no audio re-encode). */
|
|
118
|
+
fun carriesAudio(audioMimeType: String?): Boolean =
|
|
119
|
+
audioMimeType != null && audioMimeTypes.contains(audioMimeType.lowercase(Locale.US))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Encoder availability is a device property, so it is answered once and remembered.
|
|
123
|
+
private val encoderCache = HashMap<String, Boolean>()
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Whether this device advertises an ENCODER for [mimeType]. Enumerates the regular codec list
|
|
127
|
+
* rather than calling `findEncoderForFormat`, which additionally matches resolution/frame-rate
|
|
128
|
+
* constraints — the question here is only "does this codec exist at all", and a format built
|
|
129
|
+
* for the probe would answer a different, narrower one.
|
|
130
|
+
*/
|
|
131
|
+
@Synchronized
|
|
132
|
+
fun hasEncoder(mimeType: String): Boolean = encoderCache.getOrPut(mimeType) {
|
|
133
|
+
runCatching {
|
|
134
|
+
MediaCodecList(MediaCodecList.REGULAR_CODECS).codecInfos.any { info ->
|
|
135
|
+
info.isEncoder && info.supportedTypes.any { it.equals(mimeType, ignoreCase = true) }
|
|
136
|
+
}
|
|
137
|
+
}.getOrDefault(false)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Whether [format] can be WRITTEN on this device.
|
|
142
|
+
*
|
|
143
|
+
* JPEG, PNG and WebP go through `Bitmap.compress`, which every supported API level implements.
|
|
144
|
+
* HEIF and AVIF need a dedicated still-image encoder plus the HEIF muxer (API 28), so they are
|
|
145
|
+
* offered only where both exist — which is what keeps the menu honest instead of listing an
|
|
146
|
+
* option that always fails.
|
|
147
|
+
*/
|
|
148
|
+
fun canEncodeImage(format: ImageFormatSupport.Format): Boolean = when (format) {
|
|
149
|
+
ImageFormatSupport.Format.JPEG,
|
|
150
|
+
ImageFormatSupport.Format.PNG,
|
|
151
|
+
ImageFormatSupport.Format.WEBP,
|
|
152
|
+
ImageFormatSupport.Format.PDF -> true
|
|
153
|
+
ImageFormatSupport.Format.HEIF ->
|
|
154
|
+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && hasEncoder(MIME_IMAGE_HEIC)
|
|
155
|
+
ImageFormatSupport.Format.AVIF ->
|
|
156
|
+
// MediaFormat.MIMETYPE_IMAGE_AVIF exists from API 34; below that no encoder can be
|
|
157
|
+
// addressed even if the hardware has one.
|
|
158
|
+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && hasEncoder(MIME_IMAGE_AVIF)
|
|
159
|
+
else -> false
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The encoder MIME type used to write [format], or null when it is not encodable here. */
|
|
163
|
+
fun stillEncoderMimeType(format: ImageFormatSupport.Format): String? = when (format) {
|
|
164
|
+
ImageFormatSupport.Format.HEIF -> MIME_IMAGE_HEIC
|
|
165
|
+
ImageFormatSupport.Format.AVIF -> MIME_IMAGE_AVIF
|
|
166
|
+
else -> null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Still containers this device can export to, in menu order. */
|
|
170
|
+
fun encodableImageFormats(): List<ImageFormatSupport.Format> =
|
|
171
|
+
IMAGE_FORMAT_ORDER.filter { canEncodeImage(it) }
|
|
172
|
+
|
|
173
|
+
/** Whether [container] can be written on this device (API level + a usable video encoder). */
|
|
174
|
+
fun canEncodeVideo(container: VideoContainer): Boolean =
|
|
175
|
+
Build.VERSION.SDK_INT >= container.minSdk && hasEncoder(container.videoMimeType)
|
|
176
|
+
|
|
177
|
+
/** Video containers this device can export to, in menu order. */
|
|
178
|
+
fun encodableVideoContainers(): List<VideoContainer> =
|
|
179
|
+
VideoContainer.entries.filter { canEncodeVideo(it) }
|
|
180
|
+
|
|
181
|
+
private val VIDEO_BY_EXTENSION: Map<String, VideoContainer> =
|
|
182
|
+
VideoContainer.entries.flatMap { container ->
|
|
183
|
+
container.extensions.map { it to container }
|
|
184
|
+
}.toMap()
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The video container the extension of [name] (a file name, path or URI) denotes, or null.
|
|
188
|
+
*
|
|
189
|
+
* Used by the cache and the publisher to describe an exported clip by its real container
|
|
190
|
+
* instead of assuming `video/mp4` for every video — the same class of "the name no longer
|
|
191
|
+
* describes the bytes" bug [ImageFormatSupport] prevents on the stills side.
|
|
192
|
+
*/
|
|
193
|
+
fun videoContainerFromFileName(name: String?): VideoContainer? {
|
|
194
|
+
if (name.isNullOrEmpty()) return null
|
|
195
|
+
val path = name.substringBefore('?').substringBefore('#')
|
|
196
|
+
val segment = path.substringAfterLast('/', path)
|
|
197
|
+
val extension = segment.substringAfterLast('.', "").lowercase(Locale.US)
|
|
198
|
+
if (extension.isEmpty()) return null
|
|
199
|
+
return VIDEO_BY_EXTENSION[extension]
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
package com.customcamera
|
|
2
|
+
|
|
3
|
+
import android.os.StatFs
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import java.io.File
|
|
6
|
+
import java.io.FileInputStream
|
|
7
|
+
import java.io.FileOutputStream
|
|
8
|
+
import java.io.InputStream
|
|
9
|
+
import java.io.RandomAccessFile
|
|
10
|
+
import java.security.MessageDigest
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* File plumbing shared by every stage that handles media which can be ARBITRARILY LARGE.
|
|
14
|
+
*
|
|
15
|
+
* A still photo is a couple of megabytes, so the historic "copy it with an 8 KiB buffer and
|
|
16
|
+
* SHA-256 the whole thing" approach was invisible. A recording is not: a few minutes of HD is
|
|
17
|
+
* hundreds of megabytes and a long take can pass a gigabyte. At that size the same code turns
|
|
18
|
+
* one capture into several full passes over the file — the recording→preview handoff sits on a
|
|
19
|
+
* frozen viewfinder for tens of seconds, the send path stalls, and each redundant copy needs
|
|
20
|
+
* another file's worth of free space, which is how a long recording ends in a write failure or
|
|
21
|
+
* an out-of-space crash rather than a video.
|
|
22
|
+
*
|
|
23
|
+
* Everything here exists to make the per-byte cost of a large clip as close to zero as the
|
|
24
|
+
* platform allows:
|
|
25
|
+
*
|
|
26
|
+
* - [relocate] renames when it can, so moving a 1 GB clip inside one volume costs no I/O at all
|
|
27
|
+
* instead of 1 GB read + 1 GB written + 1 GB of extra space held.
|
|
28
|
+
* - [copy] / [copyStream] move bytes in 1 MiB units through the kernel (`transferTo`) instead
|
|
29
|
+
* of 8 KiB user-space hops, and NEVER leave a truncated destination behind on failure.
|
|
30
|
+
* - [contentSignature] keeps duplicate detection O(1) in file size past a threshold, so hashing
|
|
31
|
+
* stops being a multi-second, whole-file read on the capture and send paths.
|
|
32
|
+
* - [usableSpaceBytes] lets callers refuse to start work that cannot possibly fit.
|
|
33
|
+
*/
|
|
34
|
+
internal object MediaFileIo {
|
|
35
|
+
private const val TAG = "MediaFileIo"
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 1 MiB. Copying 1 GB with the old 8 KiB buffer is ~131,000 read/write syscall pairs; at 1 MiB
|
|
39
|
+
* it is ~1,000. The buffer itself is short-lived and dwarfed by the bitmaps the app already
|
|
40
|
+
* allocates, so there is no memory trade-off worth making here.
|
|
41
|
+
*/
|
|
42
|
+
const val COPY_BUFFER_BYTES: Int = 1 shl 20
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Files at or below this size are hashed in FULL, exactly as before — every photo, every
|
|
46
|
+
* document, every short clip. Duplicate detection for them is unchanged.
|
|
47
|
+
*
|
|
48
|
+
* Above it, hashing the whole file is the single most expensive thing in the capture pipeline
|
|
49
|
+
* and it is pure overhead: the signature is only ever used to notice that the same bytes are
|
|
50
|
+
* being stored twice, and [contentSignature]'s bounded form answers that question just as well
|
|
51
|
+
* for a video. 16 MiB reads in well under a frame's worth of time on any device this library
|
|
52
|
+
* supports.
|
|
53
|
+
*/
|
|
54
|
+
private const val FULL_HASH_MAX_BYTES: Long = 16L * 1024L * 1024L
|
|
55
|
+
|
|
56
|
+
/** Bytes sampled at each of the three probe points of a large file's signature. */
|
|
57
|
+
private const val SIGNATURE_WINDOW_BYTES: Int = 2 * 1024 * 1024
|
|
58
|
+
|
|
59
|
+
/** Head-room kept free by default when a relocation has to write a second copy. */
|
|
60
|
+
const val DEFAULT_SPACE_MARGIN_BYTES: Long = 32L * 1024L * 1024L
|
|
61
|
+
|
|
62
|
+
/** How a [relocate] / [duplicate] request actually resolved. */
|
|
63
|
+
enum class Outcome {
|
|
64
|
+
/** Directory entry moved. No bytes read or written, no free space consumed. */
|
|
65
|
+
RENAMED,
|
|
66
|
+
|
|
67
|
+
/** Bytes were streamed to the destination (and, for [relocate], the source unlinked). */
|
|
68
|
+
COPIED,
|
|
69
|
+
|
|
70
|
+
/** The destination volume cannot hold another copy. The source is untouched. */
|
|
71
|
+
INSUFFICIENT_SPACE,
|
|
72
|
+
|
|
73
|
+
/** The operation failed. The source is untouched and no partial destination remains. */
|
|
74
|
+
FAILED;
|
|
75
|
+
|
|
76
|
+
val succeeded: Boolean get() = this == RENAMED || this == COPIED
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Move [source] to [dest], preferring a rename.
|
|
81
|
+
*
|
|
82
|
+
* A rename is a directory-entry update: it does not read or write a single byte of content and
|
|
83
|
+
* needs NO free space, so relocating a large recording within one volume becomes instant and
|
|
84
|
+
* works even on a nearly full device. Renames only work within one filesystem, so the
|
|
85
|
+
* cross-volume case (internal cache → external app files dir) falls back to [copy] plus
|
|
86
|
+
* unlinking the source — still a genuine move as far as the caller and the disk are concerned.
|
|
87
|
+
*
|
|
88
|
+
* The free-space check is deliberately applied ONLY on the copy path, and only after the rename
|
|
89
|
+
* has been tried. Checking up front would refuse a 2 GB clip that a rename could have relocated
|
|
90
|
+
* on a device with 1 GB free — exactly the case this class exists to make work.
|
|
91
|
+
*
|
|
92
|
+
* [source] is never touched unless the relocation completed, so a caller cannot mistake a
|
|
93
|
+
* failure for a success and lose the only copy.
|
|
94
|
+
*/
|
|
95
|
+
fun relocate(
|
|
96
|
+
source: File,
|
|
97
|
+
dest: File,
|
|
98
|
+
spaceMarginBytes: Long = DEFAULT_SPACE_MARGIN_BYTES
|
|
99
|
+
): Outcome {
|
|
100
|
+
if (!source.exists()) return Outcome.FAILED
|
|
101
|
+
if (source.absolutePath == dest.absolutePath) return Outcome.RENAMED
|
|
102
|
+
|
|
103
|
+
dest.parentFile?.mkdirs()
|
|
104
|
+
// A stale destination makes renameTo fail on some filesystems, and nothing here appends to
|
|
105
|
+
// one, so clear it first.
|
|
106
|
+
if (dest.exists()) runCatching { dest.delete() }
|
|
107
|
+
|
|
108
|
+
if (runCatching { source.renameTo(dest) }.getOrDefault(false)) return Outcome.RENAMED
|
|
109
|
+
|
|
110
|
+
val required = source.length() + spaceMarginBytes
|
|
111
|
+
val parent = dest.parentFile
|
|
112
|
+
if (parent != null && !hasRoomFor(parent, required)) {
|
|
113
|
+
Log.w(TAG, "No room to copy ${source.name} (${source.length()} bytes) to ${parent.path}")
|
|
114
|
+
return Outcome.INSUFFICIENT_SPACE
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!copy(source, dest)) return Outcome.FAILED
|
|
118
|
+
// The bytes are safely at the destination; failing to unlink the source only wastes cache
|
|
119
|
+
// space (the orphan sweep collects it later), so it is not part of the success contract.
|
|
120
|
+
runCatching { source.delete() }
|
|
121
|
+
return Outcome.COPIED
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Copy [source] to [dest], keeping the source. Unlike [relocate] this ALWAYS writes a second
|
|
126
|
+
* copy, so the free-space check is unconditional.
|
|
127
|
+
*/
|
|
128
|
+
fun duplicate(
|
|
129
|
+
source: File,
|
|
130
|
+
dest: File,
|
|
131
|
+
spaceMarginBytes: Long = DEFAULT_SPACE_MARGIN_BYTES
|
|
132
|
+
): Outcome {
|
|
133
|
+
if (!source.exists()) return Outcome.FAILED
|
|
134
|
+
|
|
135
|
+
dest.parentFile?.mkdirs()
|
|
136
|
+
val parent = dest.parentFile
|
|
137
|
+
if (parent != null && !hasRoomFor(parent, source.length() + spaceMarginBytes)) {
|
|
138
|
+
Log.w(TAG, "No room to copy ${source.name} (${source.length()} bytes) to ${parent.path}")
|
|
139
|
+
return Outcome.INSUFFICIENT_SPACE
|
|
140
|
+
}
|
|
141
|
+
return if (copy(source, dest)) Outcome.COPIED else Outcome.FAILED
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Copy [source] to [dest] via the kernel, returning false on failure.
|
|
146
|
+
*
|
|
147
|
+
* `FileChannel.transferTo` hands the copy to the kernel, which moves the data without bouncing
|
|
148
|
+
* it through a user-space buffer at all. It is allowed to transfer less than requested, so the
|
|
149
|
+
* loop continues until the whole file has moved, and it can legitimately refuse to progress on
|
|
150
|
+
* some filesystems — that case finishes the job with an ordinary buffered read/write loop
|
|
151
|
+
* rather than abandoning a half-written file.
|
|
152
|
+
*
|
|
153
|
+
* On ANY failure the partial destination is deleted. That matters more than the speed: the old
|
|
154
|
+
* code left whatever bytes it had managed to write in place, so a copy interrupted by a full
|
|
155
|
+
* disk produced a truncated video file that every later stage (metadata read, thumbnail decode,
|
|
156
|
+
* playback, upload) treated as real and failed on.
|
|
157
|
+
*/
|
|
158
|
+
fun copy(source: File, dest: File): Boolean {
|
|
159
|
+
dest.parentFile?.mkdirs()
|
|
160
|
+
return try {
|
|
161
|
+
FileInputStream(source).use { input ->
|
|
162
|
+
FileOutputStream(dest).use { output ->
|
|
163
|
+
val inChannel = input.channel
|
|
164
|
+
val outChannel = output.channel
|
|
165
|
+
val size = inChannel.size()
|
|
166
|
+
var position = 0L
|
|
167
|
+
var stalled = false
|
|
168
|
+
while (position < size) {
|
|
169
|
+
val transferred = inChannel.transferTo(position, size - position, outChannel)
|
|
170
|
+
if (transferred <= 0L) {
|
|
171
|
+
stalled = true
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
position += transferred
|
|
175
|
+
}
|
|
176
|
+
if (stalled) {
|
|
177
|
+
// FileInputStream/FileOutputStream share their position with their channel, so
|
|
178
|
+
// resuming the streams from `position` continues exactly where transferTo stopped.
|
|
179
|
+
inChannel.position(position)
|
|
180
|
+
outChannel.position(position)
|
|
181
|
+
val buffer = ByteArray(COPY_BUFFER_BYTES)
|
|
182
|
+
while (true) {
|
|
183
|
+
val read = input.read(buffer)
|
|
184
|
+
if (read < 0) break
|
|
185
|
+
output.write(buffer, 0, read)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
true
|
|
191
|
+
} catch (t: Throwable) {
|
|
192
|
+
Log.e(TAG, "Copy failed: ${source.absolutePath} -> ${dest.absolutePath}", t)
|
|
193
|
+
runCatching { dest.delete() }
|
|
194
|
+
false
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Stream [input] into [dest] with the large buffer, deleting a partial [dest] and rethrowing if
|
|
200
|
+
* the write fails.
|
|
201
|
+
*
|
|
202
|
+
* Rethrowing is deliberate: callers distinguish "the media could not be stored" from "the media
|
|
203
|
+
* was stored" by whether this threw, and a truncated file left on disk would make an
|
|
204
|
+
* out-of-space failure look like a successful capture.
|
|
205
|
+
*/
|
|
206
|
+
fun copyStream(input: InputStream, dest: File): Long {
|
|
207
|
+
dest.parentFile?.mkdirs()
|
|
208
|
+
return try {
|
|
209
|
+
FileOutputStream(dest).use { output -> input.copyTo(output, COPY_BUFFER_BYTES) }
|
|
210
|
+
} catch (t: Throwable) {
|
|
211
|
+
runCatching { dest.delete() }
|
|
212
|
+
throw t
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* A stable content signature used for duplicate detection.
|
|
218
|
+
*
|
|
219
|
+
* Small files (≤ [FULL_HASH_MAX_BYTES]) are hashed in full — identical semantics to the plain
|
|
220
|
+
* SHA-256 this replaces. Larger files are described by their exact length plus three 2 MiB
|
|
221
|
+
* windows (head, middle, tail), which is bounded work regardless of whether the clip is 30 MB
|
|
222
|
+
* or 3 GB.
|
|
223
|
+
*
|
|
224
|
+
* This is a de-duplication key, not a security primitive: two DIFFERENT videos that share an
|
|
225
|
+
* identical length AND identical head, middle and tail windows would collide, which cannot
|
|
226
|
+
* happen for independently encoded clips. What it buys is that recording a long video no longer
|
|
227
|
+
* spends multiple seconds (and a full extra read pass, twice — once when caching and once when
|
|
228
|
+
* publishing) computing a hash that is only ever compared for equality.
|
|
229
|
+
*
|
|
230
|
+
* Returns "" when the file cannot be read, matching the previous behaviour: callers treat an
|
|
231
|
+
* empty signature as "unknown", which simply skips duplicate detection.
|
|
232
|
+
*/
|
|
233
|
+
fun contentSignature(file: File): String {
|
|
234
|
+
return try {
|
|
235
|
+
val length = file.length()
|
|
236
|
+
val digest = MessageDigest.getInstance("SHA-256")
|
|
237
|
+
|
|
238
|
+
if (length <= FULL_HASH_MAX_BYTES) {
|
|
239
|
+
FileInputStream(file).use { input ->
|
|
240
|
+
val buffer = ByteArray(COPY_BUFFER_BYTES)
|
|
241
|
+
while (true) {
|
|
242
|
+
val read = input.read(buffer)
|
|
243
|
+
if (read < 0) break
|
|
244
|
+
digest.update(buffer, 0, read)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
// Fold the length in first so two clips that happen to share a sampled window are still
|
|
249
|
+
// separated by their size, and so the sampled form can never collide with the full-hash
|
|
250
|
+
// form of a different file.
|
|
251
|
+
digest.update(length.toString().toByteArray(Charsets.US_ASCII))
|
|
252
|
+
RandomAccessFile(file, "r").use { raf ->
|
|
253
|
+
val window = ByteArray(SIGNATURE_WINDOW_BYTES)
|
|
254
|
+
val offsets = longArrayOf(
|
|
255
|
+
0L,
|
|
256
|
+
(length / 2) - (SIGNATURE_WINDOW_BYTES / 2),
|
|
257
|
+
length - SIGNATURE_WINDOW_BYTES
|
|
258
|
+
)
|
|
259
|
+
for (offset in offsets) {
|
|
260
|
+
raf.seek(offset.coerceAtLeast(0L))
|
|
261
|
+
raf.readFully(window)
|
|
262
|
+
digest.update(window)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
digest.digest().joinToString("") { "%02x".format(it) }
|
|
268
|
+
} catch (t: Throwable) {
|
|
269
|
+
Log.w(TAG, "Failed to compute content signature for ${file.name}", t)
|
|
270
|
+
""
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Bytes still writable on the volume that holds [dir].
|
|
276
|
+
*
|
|
277
|
+
* Returns [Long.MAX_VALUE] when the platform will not answer, so an unknown figure never blocks
|
|
278
|
+
* a capture the device could actually have completed.
|
|
279
|
+
*/
|
|
280
|
+
fun usableSpaceBytes(dir: File): Long {
|
|
281
|
+
runCatching {
|
|
282
|
+
val stat = StatFs(dir.absolutePath)
|
|
283
|
+
val available = stat.availableBlocksLong * stat.blockSizeLong
|
|
284
|
+
if (available > 0L) return available
|
|
285
|
+
}
|
|
286
|
+
return runCatching { dir.usableSpace }.getOrNull()?.takeIf { it > 0L } ?: Long.MAX_VALUE
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** True when [dir]'s volume has room for [requiredBytes] plus a small working margin. */
|
|
290
|
+
fun hasRoomFor(dir: File, requiredBytes: Long): Boolean =
|
|
291
|
+
usableSpaceBytes(dir) >= requiredBytes
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Format [bytes] for a figure that is REDRAWN CONTINUOUSLY — the live size of a recording in
|
|
295
|
+
* flight, updated once a second.
|
|
296
|
+
*
|
|
297
|
+
* Fixed at megabytes on purpose. An adaptive formatter (B → KB → MB) is the right call for a
|
|
298
|
+
* static label, but a ticking one would change unit under the user in the first seconds of every
|
|
299
|
+
* recording (`820 KB` → `1.0 MB`), and each unit change resizes the centred pill it sits in. One
|
|
300
|
+
* unit, one decimal, all the way up to a gigabyte.
|
|
301
|
+
*/
|
|
302
|
+
fun formatMegabytes(bytes: Long): String {
|
|
303
|
+
val mb = bytes.coerceAtLeast(0L) / (1024.0 * 1024.0)
|
|
304
|
+
return if (mb >= 1024.0) {
|
|
305
|
+
Localization.formatUnit("fileSizeGb", "{0} GB", "%.2f", mb / 1024.0)
|
|
306
|
+
} else {
|
|
307
|
+
Localization.formatUnit("fileSizeMb", "{0} MB", "%.1f", mb)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|