Skip to content

Commit 16e91af

Browse files
committed
🐛 bug: preview and thumbnail on Android, solved file://
1 parent dc0e38e commit 16e91af

File tree

15 files changed

+139
-29
lines changed

15 files changed

+139
-29
lines changed

android/src/main/java/com/margelo/nitro/multipleimagepicker/CameraEngine.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class CameraEngine(
2828
camera.setRecordVideoMaxSecond(config.videoMaximumDuration?.toInt() ?: 60)
2929
camera.setCameraAroundState(config.cameraDevice == CameraDevice.FRONT)
3030
camera.setOutputPathDir(getSandboxCameraOutputPath())
31-
31+
3232
config.color?.let {
3333
val primaryColor = ColorPropConverter.getColor(it, appContext)
3434
camera.setCaptureLoadingColor(primaryColor)

android/src/main/java/com/margelo/nitro/multipleimagepicker/MultipleImagePickerImp.kt

+20-4
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
9292
.setImageEngine(imageEngine)
9393
.setSelectedData(dataList)
9494
.setSelectorUIStyle(style)
95-
9695
.apply {
9796
if (isCrop) {
9897
setCropOption(config.crop)
@@ -129,6 +128,7 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
129128
setCameraInterceptListener(CameraEngine(appContext, cameraConfig))
130129
}
131130
}
131+
.setVideoThumbnailListener(VideoThumbnailEngine(getVideoThumbnailDir()))
132132
.setImageSpanCount(config.numberOfColumn?.toInt() ?: 3)
133133
.setMaxSelectNum(maxSelect)
134134
.isDirectReturnSingle(true)
@@ -307,7 +307,7 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
307307
.setLanguage(getLanguage(config.language))
308308
.setSelectorUIStyle(previewStyle)
309309
.isPreviewFullScreenMode(true)
310-
.isAutoVideoPlay(true)
310+
.isAutoVideoPlay(config.videoAutoPlay == true)
311311
.setVideoPlayerEngine(ExoPlayerEngine())
312312
.isVideoPauseResumePlay(true)
313313
.setCustomLoadingListener(getCustomLoadingListener())
@@ -334,6 +334,7 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
334334
.setCameraInterceptListener(CameraEngine(appContext, config))
335335
.isQuickCapture(true)
336336
.isOriginalControl(true)
337+
.setVideoThumbnailListener(VideoThumbnailEngine(getVideoThumbnailDir()))
337338
.apply {
338339
if (config.crop != null) {
339340
setCropEngine(CropEngine(cropOption))
@@ -372,6 +373,15 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
372373
}
373374
}
374375

376+
private fun getVideoThumbnailDir(): String {
377+
val externalFilesDir: File? = appContext.getExternalFilesDir("")
378+
val customFile = File(externalFilesDir?.absolutePath, "Thumbnail")
379+
if (!customFile.exists()) {
380+
customFile.mkdirs()
381+
}
382+
return customFile.absolutePath + File.separator
383+
}
384+
375385

376386
private fun getLanguage(language: Language): Int {
377387
return when (language) {
@@ -571,16 +581,22 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
571581
if (item.mimeType.startsWith("video/")) ResultType.VIDEO else ResultType.IMAGE
572582

573583
var path = item.path
574-
575584
var width: Double = item.width.toDouble()
576585
var height: Double = item.height.toDouble()
577586

587+
val thumbnail = item.videoThumbnailPath?.let {
588+
if (!it.startsWith("file://")) "file://$it" else it
589+
}
590+
578591
if (item.isCut) {
579592
path = "file://${item.cutPath}"
580593
width = item.cropImageWidth.toDouble()
581594
height = item.cropImageHeight.toDouble()
582595
}
583596

597+
if (!path.startsWith("file://") && !path.startsWith("content://") && type == ResultType.IMAGE)
598+
path = "file://$path"
599+
584600
val media = Result(
585601
localIdentifier = item.id.toString(),
586602
width,
@@ -595,7 +611,7 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) :
595611
path,
596612
type,
597613
fileName = item.fileName,
598-
thumbnail = item.videoThumbnailPath,
614+
thumbnail = thumbnail,
599615
duration = item.duration.toDouble()
600616
)
601617

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.margelo.nitro.multipleimagepicker
2+
3+
import android.content.Context
4+
import android.graphics.Bitmap
5+
import android.graphics.drawable.Drawable
6+
import com.bumptech.glide.Glide
7+
import com.bumptech.glide.request.target.CustomTarget
8+
import com.bumptech.glide.request.transition.Transition
9+
import com.luck.picture.lib.interfaces.OnKeyValueResultCallbackListener
10+
import com.luck.picture.lib.interfaces.OnVideoThumbnailEventListener
11+
import com.luck.picture.lib.utils.PictureFileUtils
12+
import java.io.ByteArrayOutputStream
13+
import java.io.File
14+
import java.io.FileOutputStream
15+
import java.io.IOException
16+
17+
18+
class VideoThumbnailEngine(private val targetPath: String) : OnVideoThumbnailEventListener {
19+
override fun onVideoThumbnail(
20+
context: Context, videoPath: String, call: OnKeyValueResultCallbackListener
21+
) {
22+
Glide.with(context).asBitmap().sizeMultiplier(0.6f).load(videoPath)
23+
.into(object : CustomTarget<Bitmap?>() {
24+
override fun onResourceReady(
25+
resource: Bitmap, transition: Transition<in Bitmap?>?
26+
) {
27+
val stream = ByteArrayOutputStream()
28+
resource.compress(Bitmap.CompressFormat.JPEG, 60, stream)
29+
var fos: FileOutputStream? = null
30+
var result: String? = null
31+
try {
32+
val targetFile =
33+
File(targetPath, "thumbnails_" + System.currentTimeMillis() + ".jpg")
34+
fos = FileOutputStream(targetFile)
35+
fos.write(stream.toByteArray())
36+
fos.flush()
37+
result = targetFile.absolutePath
38+
} catch (e: IOException) {
39+
e.printStackTrace()
40+
} finally {
41+
PictureFileUtils.close(fos)
42+
PictureFileUtils.close(stream)
43+
}
44+
call.onCallback(videoPath, result)
45+
}
46+
47+
override fun onLoadCleared(placeholder: Drawable?) {
48+
call.onCallback(videoPath, "")
49+
}
50+
})
51+
}
52+
}

example/src/index.tsx

+3-9
Original file line numberDiff line numberDiff line change
@@ -93,20 +93,16 @@ export default function App() {
9393
}
9494
}
9595

96+
console.log('images: ', images)
97+
9698
const onCamera = async () => {
9799
try {
98-
const response = await openCamera({
99-
mediaType: 'all',
100-
videoMaximumDuration: 5,
101-
color: 'black',
102-
})
100+
const response = await openCamera()
103101

104102
setImages((prev) => {
105103
return [response as Result, ...prev]
106104
})
107105

108-
console.log('camera response: ', response)
109-
110106
layoutEffect()
111107
} catch (e) {
112108
console.log('e: ', e)
@@ -115,7 +111,6 @@ export default function App() {
115111

116112
const onCrop = async () => {
117113
try {
118-
console.log('images: ', images)
119114
const response = await openCropper(images[0].path, {
120115
ratio: [
121116
{ title: 'Instagram', width: 1, height: 1 },
@@ -188,7 +183,6 @@ export default function App() {
188183
width={WIDTH - 6}
189184
sourceKey={'path'}
190185
videoKey={'type'}
191-
prefixPath={Platform.OS === 'ios' ? 'file://' : ''}
192186
conditionCheckVideo={'video'}
193187
videoURLKey={'thumbnail'}
194188
showDelete

ios/HybridMultipleImagePicker+Camera.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ extension HybridMultipleImagePicker {
8787

8888
let thumbnail = getVideoThumbnail(from: url.absoluteString, in: 1)
8989

90-
var result = CameraResult(path: url.absoluteString,
90+
var result = CameraResult(path: "file://\(url.absoluteString)",
9191
type: ResultType.video,
9292
width: nil,
9393
height: nil,

ios/HybridMultipleImagePicker+Preview.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ extension HybridMultipleImagePicker {
1515
var assets: [PhotoAsset] = []
1616

1717
previewConfig.tintColor = .white
18-
previewConfig.videoPlayType = .auto
18+
previewConfig.videoPlayType = config.videoAutoPlay == true ? .auto : .normal
1919
previewConfig.livePhotoPlayType = .auto
2020

2121
previewConfig.languageType = setLocale(language: config.language)

ios/HybridMultipleImagePicker+Result.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ extension HybridMultipleImagePicker {
3232
parentFolderName: nil,
3333
creationDate: creationDate > 0 ? Double(creationDate) : nil,
3434
crop: false,
35-
path: url.absoluteString,
35+
path: "file://\(url.absoluteString)",
3636
type: type,
3737
duration: asset.videoDuration,
3838
thumbnail: thumbnail,

ios/PHAsset+Thumbnail.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import Photos
1010
extension PHAsset {
1111
func getVideoAssetThumbnail(from moviePath: String, in seconds: Double) -> String? {
1212
if mediaType == .video {
13-
return getVideoThumbnail(from: moviePath, in: seconds)
13+
if let path = getVideoThumbnail(from: moviePath, in: seconds) {
14+
return "file://\(path)"
15+
}
1416
}
1517

1618
return nil

nitrogen/generated/android/c++/JNitroPreviewConfig.hpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "JLanguage.hpp"
1414
#include "Language.hpp"
15+
#include <optional>
1516

1617
namespace margelo::nitro::multipleimagepicker {
1718

@@ -33,8 +34,11 @@ namespace margelo::nitro::multipleimagepicker {
3334
static const auto clazz = javaClassStatic();
3435
static const auto fieldLanguage = clazz->getField<JLanguage>("language");
3536
jni::local_ref<JLanguage> language = this->getFieldValue(fieldLanguage);
37+
static const auto fieldVideoAutoPlay = clazz->getField<jni::JBoolean>("videoAutoPlay");
38+
jni::local_ref<jni::JBoolean> videoAutoPlay = this->getFieldValue(fieldVideoAutoPlay);
3639
return NitroPreviewConfig(
37-
language->toCpp()
40+
language->toCpp(),
41+
videoAutoPlay != nullptr ? std::make_optional(static_cast<bool>(videoAutoPlay->value())) : std::nullopt
3842
);
3943
}
4044

@@ -45,7 +49,8 @@ namespace margelo::nitro::multipleimagepicker {
4549
[[maybe_unused]]
4650
static jni::local_ref<JNitroPreviewConfig::javaobject> fromCpp(const NitroPreviewConfig& value) {
4751
return newInstance(
48-
JLanguage::fromCpp(value.language)
52+
JLanguage::fromCpp(value.language),
53+
value.videoAutoPlay.has_value() ? jni::JBoolean::valueOf(value.videoAutoPlay.value()) : nullptr
4954
);
5055
}
5156
};

nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/NitroPreviewConfig.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ import com.margelo.nitro.core.*
1717
@DoNotStrip
1818
@Keep
1919
data class NitroPreviewConfig(
20-
val language: Language
20+
val language: Language,
21+
val videoAutoPlay: Boolean?
2122
)

nitrogen/generated/ios/swift/NitroPreviewConfig.swift

+25-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,14 @@ public extension NitroPreviewConfig {
1818
/**
1919
* Create a new instance of `NitroPreviewConfig`.
2020
*/
21-
init(language: Language) {
22-
self.init(language)
21+
init(language: Language, videoAutoPlay: Bool?) {
22+
self.init(language, { () -> bridge.std__optional_bool_ in
23+
if let __unwrappedValue = videoAutoPlay {
24+
return bridge.create_std__optional_bool_(__unwrappedValue)
25+
} else {
26+
return .init()
27+
}
28+
}())
2329
}
2430

2531
var language: Language {
@@ -32,4 +38,21 @@ public extension NitroPreviewConfig {
3238
self.__language = newValue
3339
}
3440
}
41+
42+
var videoAutoPlay: Bool? {
43+
@inline(__always)
44+
get {
45+
return self.__videoAutoPlay.value
46+
}
47+
@inline(__always)
48+
set {
49+
self.__videoAutoPlay = { () -> bridge.std__optional_bool_ in
50+
if let __unwrappedValue = newValue {
51+
return bridge.create_std__optional_bool_(__unwrappedValue)
52+
} else {
53+
return .init()
54+
}
55+
}()
56+
}
57+
}
3558
}

nitrogen/generated/shared/c++/NitroPreviewConfig.hpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
namespace margelo::nitro::multipleimagepicker { enum class Language; }
2323

2424
#include "Language.hpp"
25+
#include <optional>
2526

2627
namespace margelo::nitro::multipleimagepicker {
2728

@@ -31,9 +32,10 @@ namespace margelo::nitro::multipleimagepicker {
3132
struct NitroPreviewConfig {
3233
public:
3334
Language language SWIFT_PRIVATE;
35+
std::optional<bool> videoAutoPlay SWIFT_PRIVATE;
3436

3537
public:
36-
explicit NitroPreviewConfig(Language language): language(language) {}
38+
explicit NitroPreviewConfig(Language language, std::optional<bool> videoAutoPlay): language(language), videoAutoPlay(videoAutoPlay) {}
3739
};
3840

3941
} // namespace margelo::nitro::multipleimagepicker
@@ -48,12 +50,14 @@ namespace margelo::nitro {
4850
static inline NitroPreviewConfig fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
4951
jsi::Object obj = arg.asObject(runtime);
5052
return NitroPreviewConfig(
51-
JSIConverter<Language>::fromJSI(runtime, obj.getProperty(runtime, "language"))
53+
JSIConverter<Language>::fromJSI(runtime, obj.getProperty(runtime, "language")),
54+
JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, "videoAutoPlay"))
5255
);
5356
}
5457
static inline jsi::Value toJSI(jsi::Runtime& runtime, const NitroPreviewConfig& arg) {
5558
jsi::Object obj(runtime);
5659
obj.setProperty(runtime, "language", JSIConverter<Language>::toJSI(runtime, arg.language));
60+
obj.setProperty(runtime, "videoAutoPlay", JSIConverter<std::optional<bool>>::toJSI(runtime, arg.videoAutoPlay));
5761
return obj;
5862
}
5963
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
@@ -62,6 +66,7 @@ namespace margelo::nitro {
6266
}
6367
jsi::Object obj = value.getObject(runtime);
6468
if (!JSIConverter<Language>::canConvert(runtime, obj.getProperty(runtime, "language"))) return false;
69+
if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, "videoAutoPlay"))) return false;
6570
return true;
6671
}
6772
};

src/index.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
CameraResult,
2323
Language,
2424
} from './types'
25-
import { CropError } from './types/error'
25+
import { CropError, CameraError } from './types/error'
2626

2727
const Picker = NitroModules.createHybridObject<MultipleImagePicker>(
2828
'MultipleImagePicker'
@@ -95,10 +95,11 @@ export async function openCropper(
9595
export function openPreview(
9696
media: MediaPreview[] | Result[],
9797
index: number = 0,
98-
conf: PreviewConfig
98+
conf?: PreviewConfig
9999
): void {
100100
const config: PreviewConfig = {
101-
language: conf.language ?? 'system',
101+
language: conf?.language ?? 'system',
102+
videoAutoPlay: true,
102103
...conf,
103104
}
104105

@@ -145,7 +146,7 @@ export async function openCamera(config?: CameraConfig): Promise<CameraResult> {
145146
(result: CameraResult) => {
146147
resolved(result)
147148
},
148-
(error: number) => {
149+
(error: CameraError) => {
149150
rejected(error)
150151
}
151152
)

src/types/error.ts

+4
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ export enum MultipleImagePickerError {
55
export enum CropError {
66
INVALID_IMAGE = 0,
77
}
8+
9+
export enum CameraError {
10+
INVALID_OUTPUT_FILE = 1,
11+
}

src/types/preview.ts

+7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import { ResultType } from './result'
44
// PREVIEW
55
export type NitroPreviewConfig = {
66
language: Language
7+
8+
/**
9+
* Auto play video when open preview.
10+
*
11+
* @platform iOS, Android
12+
*/
13+
videoAutoPlay?: boolean
714
}
815

916
export interface PreviewConfig

0 commit comments

Comments
 (0)