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,215 @@
|
|
|
1
|
+
package com.customcamera
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.net.Uri
|
|
5
|
+
import java.io.File
|
|
6
|
+
import java.io.InputStream
|
|
7
|
+
import java.util.Locale
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Single source of truth for "is this a GIF, and does it actually animate".
|
|
11
|
+
*
|
|
12
|
+
* The codebase already carries several independent extension/MIME tables (ThumbnailLoader's
|
|
13
|
+
* alpha-capable set, MediaCacheManager.MediaFormat, MediaPublisher.getMimeType,
|
|
14
|
+
* MediaPreviewEditor.mediaExtension). Those each answer a different question and are left
|
|
15
|
+
* alone; this object exists so the GIF-specific decisions — badge, animated playback,
|
|
16
|
+
* flatten-confirmation — all read the same answer instead of adding a fifth drifting list.
|
|
17
|
+
*
|
|
18
|
+
* Detection uses BOTH signals required of it: the declared MIME type first, then the file
|
|
19
|
+
* extension. The precedence mirrors [ThumbnailLoader.mayHaveAlphaChannel] so the two never
|
|
20
|
+
* disagree about the same file.
|
|
21
|
+
*/
|
|
22
|
+
object GifSupport {
|
|
23
|
+
private const val MIME_GIF = "image/gif"
|
|
24
|
+
private const val EXTENSION_GIF = "gif"
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* How far into the file [isAnimated] is willing to read looking for a second frame. An
|
|
28
|
+
* animated GIF's second Image Descriptor almost always lands within the first frame's
|
|
29
|
+
* compressed data, so a bounded read answers the question without loading a large file into
|
|
30
|
+
* memory on the main path.
|
|
31
|
+
*/
|
|
32
|
+
private const val ANIMATION_SCAN_LIMIT_BYTES = 256 * 1024
|
|
33
|
+
|
|
34
|
+
/** `GIF87a` / `GIF89a` — the two legal GIF signatures, as raw header bytes. */
|
|
35
|
+
private val SIGNATURE_87A = "GIF87a".toByteArray(Charsets.US_ASCII)
|
|
36
|
+
private val SIGNATURE_89A = "GIF89a".toByteArray(Charsets.US_ASCII)
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* True when [asset] is a GIF, judged by its declared MIME type first and its file name /
|
|
40
|
+
* path extension second.
|
|
41
|
+
*
|
|
42
|
+
* A declared, recognised `image/` type is authoritative — an asset that says it is
|
|
43
|
+
* `image/jpeg` is not reclassified as a GIF just because something in its path ends in
|
|
44
|
+
* `.gif`. Only an absent or non-image MIME type falls through to the extension, which is
|
|
45
|
+
* what rescues assets that arrived from a provider reporting no type at all.
|
|
46
|
+
*/
|
|
47
|
+
fun isGif(asset: CameraAsset): Boolean {
|
|
48
|
+
asset.mimeType?.lowercase(Locale.US)?.let { mime ->
|
|
49
|
+
if (mime == MIME_GIF) return true
|
|
50
|
+
if (mime.startsWith("image/")) return false
|
|
51
|
+
}
|
|
52
|
+
val name = asset.fileName ?: asset.cachePath ?: asset.uri.path ?: return false
|
|
53
|
+
return extensionOf(name) == EXTENSION_GIF
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* URI-only overload for call sites that hold no [CameraAsset] — resolves the MIME type
|
|
58
|
+
* through the ContentResolver (which reads the provider's own record) before falling back
|
|
59
|
+
* to the path extension.
|
|
60
|
+
*/
|
|
61
|
+
fun isGif(context: Context, uri: Uri?): Boolean {
|
|
62
|
+
if (uri == null) return false
|
|
63
|
+
val declared = runCatching { context.contentResolver.getType(uri) }
|
|
64
|
+
.getOrNull()
|
|
65
|
+
?.lowercase(Locale.US)
|
|
66
|
+
if (declared == MIME_GIF) return true
|
|
67
|
+
if (declared != null && declared.startsWith("image/")) return false
|
|
68
|
+
return extensionOf(uri.path ?: return false) == EXTENSION_GIF
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** True when [data] starts with a GIF signature, regardless of what it claims to be. */
|
|
72
|
+
fun hasGifSignature(data: ByteArray): Boolean =
|
|
73
|
+
startsWith(data, SIGNATURE_87A) || startsWith(data, SIGNATURE_89A)
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* True when [file] is a GIF holding more than one frame.
|
|
77
|
+
*
|
|
78
|
+
* A single-frame `.gif` is, for every purpose here, an ordinary still image: it can be
|
|
79
|
+
* edited and exported without losing anything, so it must NOT trigger the
|
|
80
|
+
* convert-to-image confirmation. Only a genuinely animated GIF has animation to lose.
|
|
81
|
+
*
|
|
82
|
+
* Failure to parse returns true — the safe direction. Treating an unreadable GIF as
|
|
83
|
+
* animated means the user is asked before it is flattened; the opposite default would
|
|
84
|
+
* destroy animation silently, which is exactly what this is here to prevent.
|
|
85
|
+
*/
|
|
86
|
+
fun isAnimated(file: File): Boolean =
|
|
87
|
+
runCatching {
|
|
88
|
+
if (!file.exists() || file.length() < SIGNATURE_87A.size) return false
|
|
89
|
+
file.inputStream().use { readIsAnimated(it) }
|
|
90
|
+
}.getOrDefault(true)
|
|
91
|
+
|
|
92
|
+
/** [isAnimated] for content:// sources, reading through the resolver. */
|
|
93
|
+
fun isAnimated(context: Context, uri: Uri): Boolean =
|
|
94
|
+
runCatching {
|
|
95
|
+
context.contentResolver.openInputStream(uri)?.use { readIsAnimated(it) } ?: true
|
|
96
|
+
}.getOrDefault(true)
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Walks the GIF block structure looking for a SECOND Image Descriptor (0x2C).
|
|
100
|
+
*
|
|
101
|
+
* A GIF is a header + Logical Screen Descriptor + optional Global Color Table, followed by
|
|
102
|
+
* a stream of blocks. Each frame is introduced by an Image Descriptor; extension blocks
|
|
103
|
+
* (0x21) and the trailer (0x3B) sit between them. Counting descriptors is therefore an
|
|
104
|
+
* exact frame count — far more reliable than looking for a Netscape looping extension,
|
|
105
|
+
* which plenty of animated GIFs omit.
|
|
106
|
+
*
|
|
107
|
+
* Bails out (returning false) once [ANIMATION_SCAN_LIMIT_BYTES] is consumed: a file whose
|
|
108
|
+
* first frame alone exceeds that is large enough that walking the rest would cost more than
|
|
109
|
+
* the answer is worth on a scroll or send path.
|
|
110
|
+
*/
|
|
111
|
+
private fun readIsAnimated(stream: InputStream): Boolean {
|
|
112
|
+
val header = ByteArray(6)
|
|
113
|
+
if (stream.read(header) != header.size) return false
|
|
114
|
+
if (!hasGifSignature(header)) return false
|
|
115
|
+
|
|
116
|
+
// Logical Screen Descriptor: width(2) height(2) packed(1) bgIndex(1) aspect(1).
|
|
117
|
+
val screen = ByteArray(7)
|
|
118
|
+
if (stream.read(screen) != screen.size) return false
|
|
119
|
+
val packed = screen[4].toInt() and 0xFF
|
|
120
|
+
if (packed and 0x80 != 0) {
|
|
121
|
+
// Global Color Table present: 3 bytes per entry, 2^(N+1) entries.
|
|
122
|
+
val entries = 1 shl ((packed and 0x07) + 1)
|
|
123
|
+
if (!skipFully(stream, entries * 3)) return false
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
var consumed = header.size + screen.size
|
|
127
|
+
var imageDescriptors = 0
|
|
128
|
+
while (consumed < ANIMATION_SCAN_LIMIT_BYTES) {
|
|
129
|
+
val block = stream.read()
|
|
130
|
+
if (block < 0) return false
|
|
131
|
+
consumed++
|
|
132
|
+
when (block) {
|
|
133
|
+
0x2C -> {
|
|
134
|
+
// Image Descriptor: left(2) top(2) width(2) height(2) packed(1).
|
|
135
|
+
imageDescriptors++
|
|
136
|
+
if (imageDescriptors > 1) return true
|
|
137
|
+
val descriptor = ByteArray(9)
|
|
138
|
+
if (stream.read(descriptor) != descriptor.size) return false
|
|
139
|
+
consumed += descriptor.size
|
|
140
|
+
val localPacked = descriptor[8].toInt() and 0xFF
|
|
141
|
+
if (localPacked and 0x80 != 0) {
|
|
142
|
+
val entries = 1 shl ((localPacked and 0x07) + 1)
|
|
143
|
+
if (!skipFully(stream, entries * 3)) return false
|
|
144
|
+
consumed += entries * 3
|
|
145
|
+
}
|
|
146
|
+
// LZW minimum code size, then the frame's data sub-blocks.
|
|
147
|
+
if (stream.read() < 0) return false
|
|
148
|
+
consumed++
|
|
149
|
+
consumed += skipSubBlocks(stream) ?: return false
|
|
150
|
+
}
|
|
151
|
+
0x21 -> {
|
|
152
|
+
// Extension block: one label byte, then data sub-blocks.
|
|
153
|
+
if (stream.read() < 0) return false
|
|
154
|
+
consumed++
|
|
155
|
+
consumed += skipSubBlocks(stream) ?: return false
|
|
156
|
+
}
|
|
157
|
+
// Trailer — end of file reached with at most one frame.
|
|
158
|
+
0x3B -> return false
|
|
159
|
+
// Anything else means the stream is not a GIF we can walk; stop rather than guess.
|
|
160
|
+
else -> return false
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return false
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Skips a chain of GIF data sub-blocks (each a length byte followed by that many bytes,
|
|
168
|
+
* terminated by a zero-length block). Returns the number of bytes consumed, or null if the
|
|
169
|
+
* stream ended mid-chain.
|
|
170
|
+
*/
|
|
171
|
+
private fun skipSubBlocks(stream: InputStream): Int? {
|
|
172
|
+
var consumed = 0
|
|
173
|
+
while (true) {
|
|
174
|
+
val size = stream.read()
|
|
175
|
+
if (size < 0) return null
|
|
176
|
+
consumed++
|
|
177
|
+
if (size == 0) return consumed
|
|
178
|
+
if (!skipFully(stream, size)) return null
|
|
179
|
+
consumed += size
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* [InputStream.skip] is permitted to skip fewer bytes than asked without being at the end
|
|
185
|
+
* of the stream, so a single call is not enough to keep the block walk aligned.
|
|
186
|
+
*/
|
|
187
|
+
private fun skipFully(stream: InputStream, count: Int): Boolean {
|
|
188
|
+
var remaining = count.toLong()
|
|
189
|
+
while (remaining > 0) {
|
|
190
|
+
val skipped = stream.skip(remaining)
|
|
191
|
+
if (skipped <= 0) {
|
|
192
|
+
// skip() made no progress — fall back to a read to distinguish "end of stream" from
|
|
193
|
+
// "this stream just doesn't implement skip".
|
|
194
|
+
if (stream.read() < 0) return false
|
|
195
|
+
remaining--
|
|
196
|
+
} else {
|
|
197
|
+
remaining -= skipped
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return true
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private fun extensionOf(path: String): String =
|
|
204
|
+
path.substringAfterLast('/', path)
|
|
205
|
+
.substringAfterLast('.', "")
|
|
206
|
+
.lowercase(Locale.US)
|
|
207
|
+
|
|
208
|
+
private fun startsWith(data: ByteArray, signature: ByteArray): Boolean {
|
|
209
|
+
if (data.size < signature.size) return false
|
|
210
|
+
for (i in signature.indices) {
|
|
211
|
+
if (data[i] != signature[i]) return false
|
|
212
|
+
}
|
|
213
|
+
return true
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
package com.customcamera
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.media.AudioAttributes
|
|
5
|
+
import android.media.AudioFocusRequest
|
|
6
|
+
import android.media.AudioManager
|
|
7
|
+
import android.media.MediaPlayer
|
|
8
|
+
import android.net.Uri
|
|
9
|
+
import android.os.Build
|
|
10
|
+
import android.os.Handler
|
|
11
|
+
import android.os.Looper
|
|
12
|
+
import com.facebook.react.bridge.Arguments
|
|
13
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
14
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
15
|
+
|
|
16
|
+
object GlobalAudioPlayer {
|
|
17
|
+
var mediaPlayer: MediaPlayer? = null
|
|
18
|
+
var currentUri: Uri? = null
|
|
19
|
+
var currentTitle: String = ""
|
|
20
|
+
var prepared = false
|
|
21
|
+
var playbackActive = false
|
|
22
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
23
|
+
var reactContext: ReactApplicationContext? = null
|
|
24
|
+
|
|
25
|
+
val listeners = mutableSetOf<Listener>()
|
|
26
|
+
|
|
27
|
+
// ------------------------------------------------------------------
|
|
28
|
+
// Audio focus — the platform mechanism for call/interruption handling
|
|
29
|
+
// ------------------------------------------------------------------
|
|
30
|
+
// An incoming or outgoing phone call (telephony ring, in-call audio, and VoIP apps
|
|
31
|
+
// alike) takes TRANSIENT audio focus and returns it when the call ends. Participating
|
|
32
|
+
// in audio focus is therefore the root-cause fix for "audio keeps playing during a
|
|
33
|
+
// call": on transient loss we pause and remember the intent to resume; on regain we
|
|
34
|
+
// resume from the exact paused position (MediaPlayer.pause() preserves it). The same
|
|
35
|
+
// listener also handles every other interruption class correctly: permanent loss
|
|
36
|
+
// (another media app starts) pauses without auto-resume, and duck requests
|
|
37
|
+
// (navigation prompts, notifications) just lower the volume.
|
|
38
|
+
|
|
39
|
+
private const val DUCK_VOLUME = 0.2f
|
|
40
|
+
|
|
41
|
+
private var audioManager: AudioManager? = null
|
|
42
|
+
private var focusRequest: AudioFocusRequest? = null // API 26+
|
|
43
|
+
private var hasAudioFocus = false
|
|
44
|
+
// True only when WE paused because of a transient focus loss (call). A user-initiated
|
|
45
|
+
// pause clears it, so pausing manually during a call never auto-resumes afterwards.
|
|
46
|
+
private var resumeOnFocusGain = false
|
|
47
|
+
|
|
48
|
+
private val playbackAttributes: AudioAttributes = AudioAttributes.Builder()
|
|
49
|
+
.setUsage(AudioAttributes.USAGE_MEDIA)
|
|
50
|
+
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
|
51
|
+
.build()
|
|
52
|
+
|
|
53
|
+
private val focusChangeListener = AudioManager.OnAudioFocusChangeListener { change ->
|
|
54
|
+
// Serialize with all other player mutations on the main thread.
|
|
55
|
+
mainHandler.post {
|
|
56
|
+
when (change) {
|
|
57
|
+
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
|
|
58
|
+
// Call ringing / in progress (or similar short interruption): pause,
|
|
59
|
+
// keep our focus request registered, and resume when focus returns.
|
|
60
|
+
if (playbackActive) {
|
|
61
|
+
resumeOnFocusGain = true
|
|
62
|
+
pauseInternal(fromFocusLoss = true)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
|
|
66
|
+
// Short prompt where we may keep playing quietly. Only delivered on
|
|
67
|
+
// pre-O devices — on O+ the system ducks automatically without a
|
|
68
|
+
// callback because the focus request doesn't opt out of auto-duck.
|
|
69
|
+
try { mediaPlayer?.setVolume(DUCK_VOLUME, DUCK_VOLUME) } catch (_: Exception) {}
|
|
70
|
+
}
|
|
71
|
+
AudioManager.AUDIOFOCUS_LOSS -> {
|
|
72
|
+
// Permanent loss (another app started media playback): pause and give
|
|
73
|
+
// up focus. Standard platform behavior is to NOT auto-resume here.
|
|
74
|
+
resumeOnFocusGain = false
|
|
75
|
+
if (playbackActive) {
|
|
76
|
+
pauseInternal(fromFocusLoss = false)
|
|
77
|
+
} else {
|
|
78
|
+
abandonAudioFocus()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
AudioManager.AUDIOFOCUS_GAIN -> {
|
|
82
|
+
// Interruption over: restore full volume and, if the interruption is
|
|
83
|
+
// what paused us (call ended), resume from the preserved position.
|
|
84
|
+
try { mediaPlayer?.setVolume(1f, 1f) } catch (_: Exception) {}
|
|
85
|
+
if (resumeOnFocusGain) {
|
|
86
|
+
resumeOnFocusGain = false
|
|
87
|
+
resume()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Lazily bind the AudioManager from any available context. */
|
|
95
|
+
private fun ensureAudioManager(context: Context?): AudioManager? {
|
|
96
|
+
audioManager?.let { return it }
|
|
97
|
+
val appContext = context?.applicationContext ?: reactContext?.applicationContext
|
|
98
|
+
audioManager = appContext?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
|
|
99
|
+
return audioManager
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Request audio focus before starting playback. Returns true when playback may start.
|
|
104
|
+
* Degrades gracefully: if no AudioManager can be resolved, playback proceeds exactly
|
|
105
|
+
* as it did before this feature existed.
|
|
106
|
+
*/
|
|
107
|
+
private fun requestAudioFocus(): Boolean {
|
|
108
|
+
if (hasAudioFocus) return true
|
|
109
|
+
val am = ensureAudioManager(null) ?: return true
|
|
110
|
+
val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
111
|
+
val request = focusRequest ?: AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
|
|
112
|
+
.setAudioAttributes(playbackAttributes)
|
|
113
|
+
.setOnAudioFocusChangeListener(focusChangeListener, mainHandler)
|
|
114
|
+
.build()
|
|
115
|
+
.also { focusRequest = it }
|
|
116
|
+
am.requestAudioFocus(request)
|
|
117
|
+
} else {
|
|
118
|
+
@Suppress("DEPRECATION")
|
|
119
|
+
am.requestAudioFocus(
|
|
120
|
+
focusChangeListener,
|
|
121
|
+
AudioManager.STREAM_MUSIC,
|
|
122
|
+
AudioManager.AUDIOFOCUS_GAIN
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
hasAudioFocus = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
|
|
126
|
+
return hasAudioFocus
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private fun abandonAudioFocus() {
|
|
130
|
+
resumeOnFocusGain = false
|
|
131
|
+
if (!hasAudioFocus) return
|
|
132
|
+
hasAudioFocus = false
|
|
133
|
+
val am = audioManager ?: return
|
|
134
|
+
try {
|
|
135
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
136
|
+
focusRequest?.let { am.abandonAudioFocusRequest(it) }
|
|
137
|
+
} else {
|
|
138
|
+
@Suppress("DEPRECATION")
|
|
139
|
+
am.abandonAudioFocus(focusChangeListener)
|
|
140
|
+
}
|
|
141
|
+
} catch (_: Exception) {}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
interface Listener {
|
|
145
|
+
fun onProgressChanged(position: Int, duration: Int)
|
|
146
|
+
fun onStateChanged(isPlaying: Boolean, prepared: Boolean)
|
|
147
|
+
fun onSourceChanged(uri: Uri?)
|
|
148
|
+
fun onCompletion()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private val progressUpdater = object : Runnable {
|
|
152
|
+
override fun run() {
|
|
153
|
+
val mp = mediaPlayer
|
|
154
|
+
if (mp != null && prepared) {
|
|
155
|
+
val duration = try { mp.duration } catch (e: Exception) { 0 }
|
|
156
|
+
val pos = try { mp.currentPosition } catch (e: Exception) { 0 }
|
|
157
|
+
if (duration > 0) {
|
|
158
|
+
listeners.forEach { it.onProgressChanged(pos, duration) }
|
|
159
|
+
|
|
160
|
+
reactContext?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
161
|
+
?.emit("onAudioProgress", Arguments.createMap().apply {
|
|
162
|
+
putInt("position", pos)
|
|
163
|
+
putInt("duration", duration)
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (playbackActive) mainHandler.postDelayed(this, 250)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fun play(context: Context, uri: Uri?, title: String) {
|
|
172
|
+
if (uri == null) return
|
|
173
|
+
// Bind the AudioManager while a context is guaranteed available, so focus
|
|
174
|
+
// requests/abandons never depend on the React context being set.
|
|
175
|
+
ensureAudioManager(context)
|
|
176
|
+
if (uri == currentUri) {
|
|
177
|
+
resume()
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
release()
|
|
181
|
+
currentUri = uri
|
|
182
|
+
currentTitle = title
|
|
183
|
+
prepared = false
|
|
184
|
+
listeners.forEach { it.onSourceChanged(uri) }
|
|
185
|
+
|
|
186
|
+
mediaPlayer = MediaPlayer().apply {
|
|
187
|
+
setAudioAttributes(playbackAttributes)
|
|
188
|
+
setOnPreparedListener { mp ->
|
|
189
|
+
prepared = true
|
|
190
|
+
listeners.forEach { it.onStateChanged(playbackActive, prepared) }
|
|
191
|
+
|
|
192
|
+
// Emitting the new audio state
|
|
193
|
+
reactContext?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
194
|
+
?.emit("onAudioStateChange", Arguments.createMap().apply {
|
|
195
|
+
putBoolean("isPlaying", playbackActive)
|
|
196
|
+
putString("uri", uri.toString())
|
|
197
|
+
putString("title", title)
|
|
198
|
+
putInt("duration", mp.duration)
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
setOnCompletionListener { mp ->
|
|
202
|
+
playbackActive = false
|
|
203
|
+
// Track finished naturally — nothing left to interrupt, so release focus
|
|
204
|
+
// (and any pending call-resume intent) back to the system.
|
|
205
|
+
abandonAudioFocus()
|
|
206
|
+
listeners.forEach { it.onCompletion() }
|
|
207
|
+
mainHandler.removeCallbacks(progressUpdater)
|
|
208
|
+
try { mp.seekTo(0) } catch (e: Exception) {}
|
|
209
|
+
|
|
210
|
+
reactContext?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
211
|
+
?.emit("onAudioStateChange", Arguments.createMap().apply {
|
|
212
|
+
putBoolean("isPlaying", false)
|
|
213
|
+
putBoolean("didComplete", true)
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
setOnErrorListener { _, _, _ -> true }
|
|
217
|
+
try {
|
|
218
|
+
setDataSource(context, uri)
|
|
219
|
+
prepareAsync()
|
|
220
|
+
} catch (e: Exception) {
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
fun resume() {
|
|
226
|
+
val mp = mediaPlayer ?: return
|
|
227
|
+
// Every start goes through here (resume() is the only mp.start() call site), so
|
|
228
|
+
// this is the single choke point for acquiring audio focus. If the system denies
|
|
229
|
+
// focus (e.g. a call is literally in progress), do not start — the player stays
|
|
230
|
+
// paused at its current position and the UI keeps showing the play button.
|
|
231
|
+
if (!requestAudioFocus()) return
|
|
232
|
+
try {
|
|
233
|
+
mp.setVolume(1f, 1f) // clear any duck applied during an interruption
|
|
234
|
+
mp.start()
|
|
235
|
+
playbackActive = true
|
|
236
|
+
listeners.forEach { it.onStateChanged(playbackActive, prepared) }
|
|
237
|
+
mainHandler.post(progressUpdater)
|
|
238
|
+
|
|
239
|
+
reactContext?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
240
|
+
?.emit("onAudioStateChange", Arguments.createMap().apply {
|
|
241
|
+
putBoolean("isPlaying", true)
|
|
242
|
+
currentUri?.let { putString("uri", it.toString()) }
|
|
243
|
+
})
|
|
244
|
+
} catch (e: Exception) {}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
fun pause() {
|
|
248
|
+
// Explicit (user/JS-initiated) pause: cancel any pending call-interruption
|
|
249
|
+
// auto-resume — if the user pauses during a call, the end of the call must not
|
|
250
|
+
// restart playback behind their back.
|
|
251
|
+
resumeOnFocusGain = false
|
|
252
|
+
pauseInternal(fromFocusLoss = false)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Shared pause path. [fromFocusLoss] true = a transient interruption (call) paused
|
|
257
|
+
* us, so keep the focus request registered — that's what makes the system deliver
|
|
258
|
+
* AUDIOFOCUS_GAIN when the call ends, driving the auto-resume. False = a deliberate
|
|
259
|
+
* stop; release focus so other apps get clean gain events.
|
|
260
|
+
*/
|
|
261
|
+
private fun pauseInternal(fromFocusLoss: Boolean) {
|
|
262
|
+
val mp = mediaPlayer ?: return
|
|
263
|
+
try {
|
|
264
|
+
if (mp.isPlaying) mp.pause()
|
|
265
|
+
} catch (e: Exception) {}
|
|
266
|
+
playbackActive = false
|
|
267
|
+
if (!fromFocusLoss) abandonAudioFocus()
|
|
268
|
+
listeners.forEach { it.onStateChanged(playbackActive, prepared) }
|
|
269
|
+
mainHandler.removeCallbacks(progressUpdater)
|
|
270
|
+
|
|
271
|
+
reactContext?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
272
|
+
?.emit("onAudioStateChange", Arguments.createMap().apply {
|
|
273
|
+
putBoolean("isPlaying", false)
|
|
274
|
+
currentUri?.let { putString("uri", it.toString()) }
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
fun togglePlayback() {
|
|
279
|
+
if (playbackActive) pause() else resume()
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
fun seekTo(positionMs: Int) {
|
|
283
|
+
val mp = mediaPlayer ?: return
|
|
284
|
+
if (!prepared) return
|
|
285
|
+
try {
|
|
286
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
287
|
+
mp.seekTo(positionMs.toLong(), MediaPlayer.SEEK_CLOSEST)
|
|
288
|
+
} else {
|
|
289
|
+
mp.seekTo(positionMs)
|
|
290
|
+
}
|
|
291
|
+
val duration = mp.duration
|
|
292
|
+
listeners.forEach { it.onProgressChanged(positionMs, duration) }
|
|
293
|
+
|
|
294
|
+
reactContext?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
295
|
+
?.emit("onAudioProgress", Arguments.createMap().apply {
|
|
296
|
+
putInt("position", positionMs)
|
|
297
|
+
putInt("duration", duration)
|
|
298
|
+
})
|
|
299
|
+
} catch (e: Exception) {}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
fun release() {
|
|
303
|
+
mainHandler.removeCallbacks(progressUpdater)
|
|
304
|
+
playbackActive = false
|
|
305
|
+
prepared = false
|
|
306
|
+
abandonAudioFocus()
|
|
307
|
+
mediaPlayer?.let { mp ->
|
|
308
|
+
try { mp.stop() } catch (e: Exception) {}
|
|
309
|
+
try { mp.reset() } catch (e: Exception) {}
|
|
310
|
+
mp.release()
|
|
311
|
+
}
|
|
312
|
+
mediaPlayer = null
|
|
313
|
+
currentUri = null
|
|
314
|
+
currentTitle = ""
|
|
315
|
+
listeners.forEach { it.onSourceChanged(null) }
|
|
316
|
+
|
|
317
|
+
reactContext?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
318
|
+
?.emit("onAudioClose", Arguments.createMap())
|
|
319
|
+
}
|
|
320
|
+
}
|