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,455 @@
|
|
|
1
|
+
package com.customcamera
|
|
2
|
+
|
|
3
|
+
import android.content.ContentResolver
|
|
4
|
+
import android.graphics.Bitmap
|
|
5
|
+
import android.graphics.BitmapFactory
|
|
6
|
+
import android.graphics.Color
|
|
7
|
+
import android.graphics.ImageDecoder
|
|
8
|
+
import android.graphics.drawable.ColorDrawable
|
|
9
|
+
import android.media.MediaMetadataRetriever
|
|
10
|
+
import android.os.Build
|
|
11
|
+
import android.util.LruCache
|
|
12
|
+
import android.util.Size
|
|
13
|
+
import android.widget.ImageView
|
|
14
|
+
import java.io.File
|
|
15
|
+
import kotlinx.coroutines.CoroutineScope
|
|
16
|
+
import kotlinx.coroutines.Dispatchers
|
|
17
|
+
import kotlinx.coroutines.SupervisorJob
|
|
18
|
+
import kotlinx.coroutines.cancel
|
|
19
|
+
import kotlinx.coroutines.launch
|
|
20
|
+
import kotlinx.coroutines.withContext
|
|
21
|
+
|
|
22
|
+
class ThumbnailLoader(
|
|
23
|
+
private val contentResolver: ContentResolver
|
|
24
|
+
) {
|
|
25
|
+
var lowMemoryMode: Boolean = false
|
|
26
|
+
|
|
27
|
+
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
28
|
+
private val cache = object : LruCache<String, Bitmap>(cacheSizeKb()) {
|
|
29
|
+
override fun sizeOf(key: String, value: Bitmap): Int = value.byteCount / 1024
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fun load(asset: CameraAsset, imageView: ImageView, sizePx: Int) {
|
|
33
|
+
val actualSizePx = if (lowMemoryMode) sizePx / 2 else sizePx
|
|
34
|
+
val key = "${asset.coverUri ?: asset.uri}@$actualSizePx"
|
|
35
|
+
imageView.tag = key
|
|
36
|
+
val placeholderColor = CameraTheme.get(imageView.context)
|
|
37
|
+
.componentColor("mediaGrid", "thumbnailPlaceholder", 0xFF111111.toInt())
|
|
38
|
+
imageView.setImageDrawable(ColorDrawable(placeholderColor))
|
|
39
|
+
|
|
40
|
+
cache.get(key)?.let {
|
|
41
|
+
imageView.setImageBitmap(it)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
scope.launch {
|
|
46
|
+
val bitmap = loadBitmap(asset, actualSizePx)
|
|
47
|
+
if (bitmap != null) {
|
|
48
|
+
cache.put(key, bitmap)
|
|
49
|
+
withContext(Dispatchers.Main) {
|
|
50
|
+
if (imageView.tag == key) {
|
|
51
|
+
imageView.setImageBitmap(bitmap)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Callback variant of [load] for hosts that own their view's tag.
|
|
60
|
+
*
|
|
61
|
+
* [load] stores its cache key in `imageView.tag`, which is exactly where ReactNative's
|
|
62
|
+
* BaseViewManager writes `testID` — a RN-hosted thumbnail must therefore not go through it.
|
|
63
|
+
* Same cache, same decode path (cover frame → video frame → resolver thumbnail → decoder);
|
|
64
|
+
* [onReady] is invoked on the main thread and only when a bitmap was produced, so a format
|
|
65
|
+
* this device cannot decode simply leaves the caller's placeholder in place.
|
|
66
|
+
*/
|
|
67
|
+
fun requestBitmap(asset: CameraAsset, sizePx: Int, onReady: (Bitmap) -> Unit) {
|
|
68
|
+
val actualSizePx = if (lowMemoryMode) sizePx / 2 else sizePx
|
|
69
|
+
val key = "${asset.coverUri ?: asset.uri}@$actualSizePx"
|
|
70
|
+
|
|
71
|
+
cache.get(key)?.let {
|
|
72
|
+
onReady(it)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
scope.launch {
|
|
77
|
+
val bitmap = loadBitmap(asset, actualSizePx) ?: return@launch
|
|
78
|
+
cache.put(key, bitmap)
|
|
79
|
+
withContext(Dispatchers.Main) { onReady(bitmap) }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fun preload(asset: CameraAsset, sizePx: Int) {
|
|
84
|
+
if (lowMemoryMode) return
|
|
85
|
+
val key = "${asset.coverUri ?: asset.uri}@$sizePx"
|
|
86
|
+
if (cache.get(key) != null) return
|
|
87
|
+
|
|
88
|
+
scope.launch {
|
|
89
|
+
val bitmap = loadBitmap(asset, sizePx) ?: return@launch
|
|
90
|
+
cache.put(key, bitmap)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fun clearCache() {
|
|
95
|
+
cache.evictAll()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Drop every cached bitmap decoded from [source] — one media file, at any decode size.
|
|
100
|
+
*
|
|
101
|
+
* Cache keys are `"<coverUri ?: uri>@<sizePx>"`, so one file can hold several entries (the grid
|
|
102
|
+
* tile, the strip, a full-width poster) and each must go. Called when media is DELETED: without
|
|
103
|
+
* it the LruCache keeps serving a decoded copy of the file, so a securely erased photo carries
|
|
104
|
+
* on rendering in the list — and that bitmap is itself a recoverable, if downscaled, copy of the
|
|
105
|
+
* media the user asked to have destroyed.
|
|
106
|
+
*
|
|
107
|
+
* `snapshot()` is copied before iterating because [LruCache.remove] mutates the map.
|
|
108
|
+
*/
|
|
109
|
+
fun evictSource(source: String) {
|
|
110
|
+
if (source.isEmpty()) return
|
|
111
|
+
val prefix = "$source@"
|
|
112
|
+
cache.snapshot().keys
|
|
113
|
+
.filter { it.startsWith(prefix) }
|
|
114
|
+
.forEach { cache.remove(it) }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fun putOverride(key: String, bitmap: Bitmap) {
|
|
118
|
+
cache.put(key, bitmap)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fun clear() {
|
|
122
|
+
cache.evictAll()
|
|
123
|
+
scope.cancel()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private fun loadBitmap(asset: CameraAsset, sizePx: Int): Bitmap? {
|
|
127
|
+
// A user-selected cover overrides frame extraction — decode that JPEG directly.
|
|
128
|
+
asset.coverUri?.let { cover ->
|
|
129
|
+
loadCoverBitmap(cover, sizePx)?.let { return it }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (asset.type == "video") {
|
|
133
|
+
loadVideoFrame(asset, sizePx)?.let { return it }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Try ContentResolver thumbnail API for content:// URIs on Q+
|
|
137
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && asset.uri.scheme == "content") {
|
|
138
|
+
val thumb = runCatching {
|
|
139
|
+
contentResolver.loadThumbnail(asset.uri, Size(sizePx, sizePx), null)
|
|
140
|
+
}.getOrNull()
|
|
141
|
+
if (thumb != null && !thumb.isMostlyBlack()) return thumb
|
|
142
|
+
thumb?.recycle()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return if (asset.type == "video") {
|
|
146
|
+
loadVideoFrame(asset, sizePx)
|
|
147
|
+
} else {
|
|
148
|
+
loadImageBitmap(asset, sizePx)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Decode a user-selected cover frame (content:// or file:// URI string) at [sizePx]. */
|
|
153
|
+
private fun loadCoverBitmap(coverUri: String, sizePx: Int): Bitmap? =
|
|
154
|
+
runCatching {
|
|
155
|
+
val uri = android.net.Uri.parse(coverUri)
|
|
156
|
+
val filePath = if (uri.scheme == "file") uri.path else null
|
|
157
|
+
if (filePath != null && File(filePath).exists()) {
|
|
158
|
+
val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
|
159
|
+
BitmapFactory.decodeFile(filePath, bounds)
|
|
160
|
+
val options = BitmapFactory.Options().apply {
|
|
161
|
+
inSampleSize = bounds.calculateSampleSize(sizePx)
|
|
162
|
+
// Halves thumbnail memory on small-heap devices — see decodeConfig().
|
|
163
|
+
inPreferredConfig = decodeConfig()
|
|
164
|
+
}
|
|
165
|
+
return@runCatching BitmapFactory.decodeFile(filePath, options)
|
|
166
|
+
}
|
|
167
|
+
val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
|
168
|
+
contentResolver.openInputStream(uri)?.use { stream ->
|
|
169
|
+
BitmapFactory.decodeStream(stream, null, bounds)
|
|
170
|
+
}
|
|
171
|
+
contentResolver.openInputStream(uri)?.use { stream ->
|
|
172
|
+
val options = BitmapFactory.Options().apply {
|
|
173
|
+
inSampleSize = bounds.calculateSampleSize(sizePx)
|
|
174
|
+
// Halves thumbnail memory on small-heap devices — see decodeConfig().
|
|
175
|
+
inPreferredConfig = decodeConfig()
|
|
176
|
+
}
|
|
177
|
+
BitmapFactory.decodeStream(stream, null, options)
|
|
178
|
+
}
|
|
179
|
+
}.getOrNull()
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Decode a still thumbnail for an image asset.
|
|
183
|
+
*
|
|
184
|
+
* BitmapFactory decodes the FIRST frame of an animated GIF, which is exactly what a grid or
|
|
185
|
+
* strip tile wants: one static, cheap frame, no decoder kept alive, no per-tile animation
|
|
186
|
+
* competing with the scroll. The same call therefore covers GIF, PNG, WebP and JPEG — and, on
|
|
187
|
+
* devices whose platform decoders support them, HEIF (API 28+) and AVIF (API 31+). The only
|
|
188
|
+
* format-specific decision is the bitmap config (see [decodeConfig]).
|
|
189
|
+
*
|
|
190
|
+
* Where BitmapFactory returns nothing, [decodeViaImageDecoder] retries through ImageDecoder,
|
|
191
|
+
* which handles some HEIF/AVIF variants BitmapFactory declines. A device too old to decode the
|
|
192
|
+
* format at all yields null, and the caller shows the placeholder rather than a black tile.
|
|
193
|
+
*/
|
|
194
|
+
private fun loadImageBitmap(asset: CameraAsset, sizePx: Int): Bitmap? {
|
|
195
|
+
// Formats the platform cannot decode on this API level (HEIF < 28, AVIF < 31) would waste a
|
|
196
|
+
// full decode attempt per tile on every bind. The asset is still listed and still sendable —
|
|
197
|
+
// only its pixels are unavailable here.
|
|
198
|
+
if (!ImageFormatSupport.isDecodable(asset)) return null
|
|
199
|
+
|
|
200
|
+
return runCatching {
|
|
201
|
+
val config = decodeConfig(mayHaveAlpha = asset.mayHaveAlphaChannel())
|
|
202
|
+
// For file:// URIs or assets with cachePath, read directly from the file
|
|
203
|
+
val filePath = asset.cachePath ?: asset.uri.path
|
|
204
|
+
if (filePath != null && File(filePath).exists()) {
|
|
205
|
+
val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
|
206
|
+
BitmapFactory.decodeFile(filePath, bounds)
|
|
207
|
+
val options = BitmapFactory.Options().apply {
|
|
208
|
+
inSampleSize = bounds.calculateSampleSize(sizePx)
|
|
209
|
+
// Halves thumbnail memory on small-heap devices — see decodeConfig().
|
|
210
|
+
inPreferredConfig = config
|
|
211
|
+
}
|
|
212
|
+
return@runCatching BitmapFactory.decodeFile(filePath, options)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Fallback to ContentResolver for content:// URIs
|
|
216
|
+
val bounds = BitmapFactory.Options().apply {
|
|
217
|
+
inJustDecodeBounds = true
|
|
218
|
+
}
|
|
219
|
+
contentResolver.openInputStream(asset.uri)?.use { stream ->
|
|
220
|
+
BitmapFactory.decodeStream(stream, null, bounds)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
contentResolver.openInputStream(asset.uri)?.use { stream ->
|
|
224
|
+
val options = BitmapFactory.Options().apply {
|
|
225
|
+
inSampleSize = bounds.calculateSampleSize(sizePx)
|
|
226
|
+
// Halves thumbnail memory on small-heap devices — see decodeConfig().
|
|
227
|
+
inPreferredConfig = config
|
|
228
|
+
}
|
|
229
|
+
BitmapFactory.decodeStream(stream, null, options)
|
|
230
|
+
}
|
|
231
|
+
}.getOrNull() ?: decodeViaImageDecoder(asset, sizePx)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Last-resort decode through ImageDecoder (API 28+), which reads some HEIF/AVIF files that
|
|
236
|
+
* BitmapFactory refuses. Downsamples during decode so a full-resolution frame is never
|
|
237
|
+
* materialised just to be scaled down for a tile.
|
|
238
|
+
*/
|
|
239
|
+
private fun decodeViaImageDecoder(asset: CameraAsset, sizePx: Int): Bitmap? {
|
|
240
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return null
|
|
241
|
+
return runCatching {
|
|
242
|
+
val filePath = asset.cachePath ?: asset.uri.path
|
|
243
|
+
val source = if (filePath != null && File(filePath).exists()) {
|
|
244
|
+
ImageDecoder.createSource(File(filePath))
|
|
245
|
+
} else {
|
|
246
|
+
ImageDecoder.createSource(contentResolver, asset.uri)
|
|
247
|
+
}
|
|
248
|
+
ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
|
|
249
|
+
val maxDimension = maxOf(info.size.width, info.size.height)
|
|
250
|
+
if (maxDimension > sizePx && sizePx > 0) {
|
|
251
|
+
val scale = sizePx.toFloat() / maxDimension.toFloat()
|
|
252
|
+
decoder.setTargetSize(
|
|
253
|
+
(info.size.width * scale).toInt().coerceAtLeast(1),
|
|
254
|
+
(info.size.height * scale).toInt().coerceAtLeast(1)
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
// ImageDecoder yields a HARDWARE bitmap by default, which cannot be read back by
|
|
258
|
+
// isMostlyBlack()'s getPixel() and cannot be cached safely alongside software bitmaps.
|
|
259
|
+
decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
|
|
260
|
+
}
|
|
261
|
+
}.getOrNull()
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private fun loadVideoFrame(asset: CameraAsset, sizePx: Int): Bitmap? =
|
|
265
|
+
runCatching {
|
|
266
|
+
val retriever = MediaMetadataRetriever()
|
|
267
|
+
try {
|
|
268
|
+
val filePath = asset.cachePath ?: asset.uri.path
|
|
269
|
+
if (filePath != null && File(filePath).exists()) {
|
|
270
|
+
retriever.setDataSource(filePath)
|
|
271
|
+
} else {
|
|
272
|
+
contentResolver.openAssetFileDescriptor(asset.uri, "r")?.use { descriptor ->
|
|
273
|
+
retriever.setDataSource(
|
|
274
|
+
descriptor.fileDescriptor,
|
|
275
|
+
descriptor.startOffset,
|
|
276
|
+
descriptor.length
|
|
277
|
+
)
|
|
278
|
+
} ?: return@runCatching null
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
val durationMs =
|
|
282
|
+
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() ?: 0L
|
|
283
|
+
val candidateTimesUs = buildList {
|
|
284
|
+
add(1_000L)
|
|
285
|
+
add(100_000L)
|
|
286
|
+
add(500_000L)
|
|
287
|
+
add(1_000_000L)
|
|
288
|
+
if (durationMs > 0) {
|
|
289
|
+
add((durationMs * 500).coerceAtLeast(1_000L))
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
for (timeUs in candidateTimesUs) {
|
|
294
|
+
val frame = extractFrame(retriever, timeUs, MediaMetadataRetriever.OPTION_CLOSEST_SYNC, sizePx)
|
|
295
|
+
?: extractFrame(retriever, timeUs, MediaMetadataRetriever.OPTION_CLOSEST, sizePx)
|
|
296
|
+
if (frame != null) {
|
|
297
|
+
if (!frame.isMostlyBlack()) {
|
|
298
|
+
return@runCatching downscaleBitmap(frame, sizePx)
|
|
299
|
+
}
|
|
300
|
+
frame.recycle()
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
extractFrame(retriever, 0, MediaMetadataRetriever.OPTION_CLOSEST_SYNC, sizePx)?.let { fallback ->
|
|
305
|
+
downscaleBitmap(fallback, sizePx)
|
|
306
|
+
}
|
|
307
|
+
} finally {
|
|
308
|
+
retriever.release()
|
|
309
|
+
}
|
|
310
|
+
}.getOrNull()
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Pull one frame, scaled DURING extraction wherever the platform allows it.
|
|
314
|
+
*
|
|
315
|
+
* `getFrameAtTime` always hands back a frame at the clip's coded resolution — 8 MB for 1080p,
|
|
316
|
+
* ~33 MB for 4K — which is then thrown away by [downscaleBitmap] to produce a 68 or 120 dp tile.
|
|
317
|
+
* The loop above tries several timestamps, so a single video thumbnail could allocate and
|
|
318
|
+
* discard that full-resolution buffer five times over, and several tiles decoding at once (the
|
|
319
|
+
* grid, the strip, and a just-recorded clip's own preloads) is a realistic way to run a
|
|
320
|
+
* small-heap device out of memory on nothing but thumbnails.
|
|
321
|
+
*
|
|
322
|
+
* `getScaledFrameAtTime` (API 27+) asks the decoder for the size we actually want, so the big
|
|
323
|
+
* intermediate never exists. Below API 27 the old behaviour is unavoidable and is kept.
|
|
324
|
+
*
|
|
325
|
+
* OutOfMemoryError is caught rather than allowed to propagate: it is an Error, so the
|
|
326
|
+
* `runCatching` in the caller would NOT contain it, and a thumbnail that cannot be decoded must
|
|
327
|
+
* degrade to a placeholder instead of taking the process down.
|
|
328
|
+
*/
|
|
329
|
+
private fun extractFrame(
|
|
330
|
+
retriever: MediaMetadataRetriever,
|
|
331
|
+
timeUs: Long,
|
|
332
|
+
option: Int,
|
|
333
|
+
sizePx: Int
|
|
334
|
+
): Bitmap? = try {
|
|
335
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 && sizePx > 0) {
|
|
336
|
+
val videoW = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull()
|
|
337
|
+
val videoH = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull()
|
|
338
|
+
if (videoW != null && videoH != null && videoW > 0 && videoH > 0) {
|
|
339
|
+
val scale = sizePx.toFloat() / maxOf(videoW, videoH).toFloat()
|
|
340
|
+
val targetW = (videoW * scale).toInt().coerceAtLeast(1)
|
|
341
|
+
val targetH = (videoH * scale).toInt().coerceAtLeast(1)
|
|
342
|
+
retriever.getScaledFrameAtTime(timeUs, option, targetW, targetH)
|
|
343
|
+
} else {
|
|
344
|
+
retriever.getScaledFrameAtTime(timeUs, option, sizePx, sizePx)
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
retriever.getFrameAtTime(timeUs, option)
|
|
348
|
+
}
|
|
349
|
+
} catch (e: Exception) {
|
|
350
|
+
null
|
|
351
|
+
} catch (e: OutOfMemoryError) {
|
|
352
|
+
null
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
companion object {
|
|
356
|
+
/**
|
|
357
|
+
* Size the in-memory thumbnail cache as a fraction of the app's actual heap budget
|
|
358
|
+
* instead of a fixed 24 MB. A fixed cap is simultaneously too big for a low-RAM device
|
|
359
|
+
* (it competes with decode buffers and invites OOM/eviction thrash) and needlessly small
|
|
360
|
+
* on a high-end one. Using 1/8 of the max heap follows the standard Android bitmap-cache
|
|
361
|
+
* guidance and adapts automatically: a 512 MB-heap device gets ~64 MB, while a low-end
|
|
362
|
+
* 64 MB-heap device gets ~8 MB. Runtime.maxMemory() needs no Context, so the constructor
|
|
363
|
+
* signature is unchanged. Clamped to a sane floor/ceiling for predictability.
|
|
364
|
+
*/
|
|
365
|
+
private fun cacheSizeKb(): Int {
|
|
366
|
+
val maxHeapKb = (Runtime.getRuntime().maxMemory() / 1024L)
|
|
367
|
+
val eighthKb = (maxHeapKb / 8L)
|
|
368
|
+
return eighthKb.coerceIn(6 * 1024L, 48 * 1024L).toInt()
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* On small-heap (≤128 MB) devices decode grid/list thumbnails as RGB_565: 2 bytes per
|
|
373
|
+
* pixel instead of ARGB_8888's 4, halving both decode allocation and cache footprint —
|
|
374
|
+
* so the same LruCache holds twice the thumbnails with zero extra memory. Thumbnails
|
|
375
|
+
* are small photographic previews; the 565 banding is not visible at these sizes.
|
|
376
|
+
* Full-size preview/editor decodes elsewhere keep ARGB_8888 fidelity.
|
|
377
|
+
*/
|
|
378
|
+
val PREFER_565: Boolean =
|
|
379
|
+
Runtime.getRuntime().maxMemory() <= 128L * 1024L * 1024L
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* [mayHaveAlpha] opts a format out of the RGB_565 saving. RGB_565 has no alpha channel, so a
|
|
383
|
+
* transparent GIF/PNG/WebP/HEIF/AVIF decoded into it loses its transparency to solid BLACK —
|
|
384
|
+
* the tile renders as a black square on exactly the low-RAM devices the optimisation targets.
|
|
385
|
+
* Formats that can carry transparency are worth the extra 2 bytes/pixel; opaque JPEG (every
|
|
386
|
+
* camera capture and every cover frame) still takes the cheap path.
|
|
387
|
+
*/
|
|
388
|
+
fun decodeConfig(mayHaveAlpha: Boolean = false): Bitmap.Config =
|
|
389
|
+
if (PREFER_565 && !mayHaveAlpha) Bitmap.Config.RGB_565 else Bitmap.Config.ARGB_8888
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Whether this asset's container can hold transparency. Delegates to [ImageFormatSupport],
|
|
393
|
+
* which reads the MIME type first and the file name / URI extension second, so an asset that
|
|
394
|
+
* arrived without a MIME type is still classified correctly — and so adding a format there
|
|
395
|
+
* automatically fixes its alpha handling here.
|
|
396
|
+
*/
|
|
397
|
+
internal fun CameraAsset.mayHaveAlphaChannel(): Boolean =
|
|
398
|
+
ImageFormatSupport.mayHaveAlpha(this)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private fun downscaleBitmap(source: Bitmap, targetSize: Int): Bitmap {
|
|
403
|
+
val maxDimension = maxOf(source.width, source.height)
|
|
404
|
+
if (maxDimension <= targetSize) return source
|
|
405
|
+
|
|
406
|
+
val scale = targetSize.toFloat() / maxDimension.toFloat()
|
|
407
|
+
val width = (source.width * scale).toInt().coerceAtLeast(1)
|
|
408
|
+
val height = (source.height * scale).toInt().coerceAtLeast(1)
|
|
409
|
+
val scaled = Bitmap.createScaledBitmap(source, width, height, true)
|
|
410
|
+
if (scaled !== source) {
|
|
411
|
+
source.recycle()
|
|
412
|
+
}
|
|
413
|
+
return scaled
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Heuristic for "the frame extractor gave us nothing usable" — a thumbnail that is black at
|
|
418
|
+
* almost every sample point. Transparent pixels are deliberately NOT counted: a transparent
|
|
419
|
+
* GIF/PNG reads as (0,0,0,0) at every sample, which would condemn a perfectly good thumbnail
|
|
420
|
+
* as a failed decode and send it round the fallback path for the same result.
|
|
421
|
+
*/
|
|
422
|
+
private fun Bitmap.isMostlyBlack(): Boolean {
|
|
423
|
+
if (width <= 0 || height <= 0) return true
|
|
424
|
+
|
|
425
|
+
val samplePoints = listOf(
|
|
426
|
+
width / 2 to height / 2,
|
|
427
|
+
width / 4 to height / 4,
|
|
428
|
+
(width * 3) / 4 to height / 4,
|
|
429
|
+
width / 4 to (height * 3) / 4,
|
|
430
|
+
(width * 3) / 4 to (height * 3) / 4
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
var darkSamples = 0
|
|
434
|
+
for ((x, y) in samplePoints) {
|
|
435
|
+
val pixel = getPixel(x.coerceIn(0, width - 1), y.coerceIn(0, height - 1))
|
|
436
|
+
if (Color.alpha(pixel) < 16) continue
|
|
437
|
+
if (Color.red(pixel) < 20 && Color.green(pixel) < 20 && Color.blue(pixel) < 20) {
|
|
438
|
+
darkSamples++
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return darkSamples >= samplePoints.size - 1
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private fun BitmapFactory.Options.calculateSampleSize(targetSize: Int): Int {
|
|
446
|
+
var sampleSize = 1
|
|
447
|
+
val halfHeight = outHeight / 2
|
|
448
|
+
val halfWidth = outWidth / 2
|
|
449
|
+
|
|
450
|
+
while (halfHeight / sampleSize >= targetSize && halfWidth / sampleSize >= targetSize) {
|
|
451
|
+
sampleSize *= 2
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return sampleSize.coerceAtLeast(1)
|
|
455
|
+
}
|