From d686df865832f0ac29cc7bae7cd5b5c06e1ed431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E5=B2=A1=E3=80=80=E4=BE=91=E5=87=9B?= <129148471+LassicYM@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:27:18 +0900 Subject: [PATCH] [RN, fluter] fixed bug about stopLivePreview. --- .../ThetaClientFlutterPlugin.kt | 9 ++++- .../SwiftThetaClientFlutterPlugin.swift | 14 ++++++- .../theta_client_flutter_method_channel.dart | 10 +++-- .../repository/ThetaRepositoryTest.kt | 6 +-- .../ThetaClientSdkModule.kt | 38 ++++++++++++++++--- react-native/ios/ThetaClientReactNative.swift | 32 +++++++++++++--- .../src/theta-repository/theta-repository.ts | 5 ++- .../live-preview-screen.tsx | 4 +- 8 files changed, 97 insertions(+), 21 deletions(-) diff --git a/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ThetaClientFlutterPlugin.kt b/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ThetaClientFlutterPlugin.kt index 2a14f7c4a4..eb44ddd50b 100644 --- a/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ThetaClientFlutterPlugin.kt +++ b/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ThetaClientFlutterPlugin.kt @@ -60,6 +60,7 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { const val messageNotInit: String = "Not initialized." const val messageNoResult: String = "Result is Null." const val messageNoArgument: String = "No Argument." + const val messageLivePreviewRunning: String = "Live preview is running." const val eventNameNotify = "theta_client_flutter/theta_notify" const val notifyIdLivePreview = 10001 @@ -617,6 +618,11 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { } suspend fun getLivePreview(result: Result) { + if (previewing) { + result.error(errorCode, messageLivePreviewRunning, null) + return + } + val theta = thetaRepository if (theta == null) { result.error(errorCode, messageNotInit, null) @@ -633,13 +639,14 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { } result.success(null) } catch (e: Exception) { + previewing = false result.error(e.javaClass.simpleName, e.message, null) } } fun stopLivePreview(result: Result) { previewing = false - result.success(null) + result.success(true) } fun getPhotoCaptureBuilder(result: Result) { diff --git a/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift b/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift index 96455db5f2..0a310a300a 100644 --- a/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift +++ b/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift @@ -44,6 +44,7 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre static let messageNotInit: String = "Not initialized." static let messageNoResult: String = "Result is Null." static let messageNoArgument: String = "No Argument." + static let messageLivePreviewRunning = "Live preview is running." static var endPoint: String = "http://192.168.1.1" var eventSink: FlutterEventSink? = nil var previewing = false @@ -114,7 +115,7 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre case "getLivePreview": getLivePreview(result: result) case "stopLivePreview": - previewing = false + stopLivePreview(result: result) case "listFiles": listFiles(call: call, result: result) case "deleteFiles": @@ -383,6 +384,12 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre } func getLivePreview(result: @escaping FlutterResult) { + if previewing { + let flutterError = FlutterError(code: SwiftThetaClientFlutterPlugin.errorCode, message: SwiftThetaClientFlutterPlugin.messageLivePreviewRunning, details: nil) + result(flutterError) + return + } + if thetaRepository == nil { let flutterError = FlutterError(code: SwiftThetaClientFlutterPlugin.errorCode, message: SwiftThetaClientFlutterPlugin.messageNotInit, details: nil) result(flutterError) @@ -418,6 +425,11 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre } } } + + func stopLivePreview(result: @escaping FlutterResult) { + previewing = false + result(true) + } func listFiles(call: FlutterMethodCall, result: @escaping FlutterResult) { guard let thetaRepository = thetaRepository else { diff --git a/flutter/lib/theta_client_flutter_method_channel.dart b/flutter/lib/theta_client_flutter_method_channel.dart index 0f3a19413c..357f7223bb 100644 --- a/flutter/lib/theta_client_flutter_method_channel.dart +++ b/flutter/lib/theta_client_flutter_method_channel.dart @@ -182,14 +182,18 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { final image = params?['image'] as Uint8List?; if (image != null && !frameHandler(image)) { removeNotify(notifyIdLivePreview); - methodChannel.invokeMethod('stopLivePreview'); + methodChannel.invokeMethod('stopLivePreview'); } }); await methodChannel.invokeMethod('getLivePreview'); completer.complete(null); } catch (e) { - removeNotify(notifyIdLivePreview); - completer.completeError(e); + if (e is PlatformException && e.message == "Live preview is running.") { + completer.completeError(e); + } else { + removeNotify(notifyIdLivePreview); + completer.completeError(e); + } } return completer.future; } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ThetaRepositoryTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ThetaRepositoryTest.kt index 29c521f9d9..5533cb6112 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ThetaRepositoryTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ThetaRepositoryTest.kt @@ -284,9 +284,9 @@ class ThetaRepositoryTest { // execute val timeout = ThetaRepository.Timeout( - connectTimeout = 1L, - requestTimeout = 2L, - socketTimeout = 3L + connectTimeout = 10L, + requestTimeout = 20L, + socketTimeout = 30L ) ThetaRepository.newInstance(endpoint, timeout = timeout) assertNotNull(ThetaRepository.restoreConfig, "restoreConfig") diff --git a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/ThetaClientSdkModule.kt b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/ThetaClientSdkModule.kt index d4dcf991ba..29684a92be 100644 --- a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/ThetaClientSdkModule.kt +++ b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/ThetaClientSdkModule.kt @@ -23,6 +23,7 @@ class ThetaClientReactNativeModule( } var previewing: Boolean = false + var stopLivePreviewPromise: Promise? = null var photoCaptureBuilder: PhotoCapture.Builder? = null var photoCapture: PhotoCapture? = null var timeShiftCaptureBuilder: TimeShiftCapture.Builder? = null @@ -53,6 +54,7 @@ class ThetaClientReactNativeModule( var listenerCount: Int = 0 val messageNotInit: String = "Not initialized." + val messageLivePreviewRunning: String = "Live preview is running." /** * add event listener for [eventName] @@ -97,6 +99,7 @@ class ThetaClientReactNativeModule( try { theta = null previewing = false + stopLivePreviewPromise = null photoCaptureBuilder = null photoCapture = null timeShiftCaptureBuilder = null @@ -427,6 +430,11 @@ class ThetaClientReactNativeModule( */ @ReactMethod fun getLivePreview(promise: Promise) { + if (previewing) { + promise.reject(Exception(messageLivePreviewRunning)) + return + } + val theta = theta if (theta == null) { promise.reject(Exception(messageNotInit)) @@ -435,7 +443,7 @@ class ThetaClientReactNativeModule( fun ByteArray.toBase64(): String = String(Base64.getEncoder().encode(this)) suspend fun callFrameHandler(packet: Pair): Boolean { if (listenerCount == 0) { - return previewing + return stopLivePreviewPromise == null } val param = Arguments.createMap() param.putString( @@ -446,12 +454,19 @@ class ThetaClientReactNativeModule( reactApplicationContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(EVENT_NAME, param) - return previewing + return stopLivePreviewPromise == null } + previewing = true + execAndResetStopLivePreviewResolve(false) + launch { try { theta.getLivePreview(::callFrameHandler) + + execAndResetStopLivePreviewResolve(true) + previewing = false + promise.resolve(true) } catch (t: Throwable) { promise.reject(t) @@ -461,13 +476,26 @@ class ThetaClientReactNativeModule( /** * stopLivePreview - stop live previewing + * @param promise promise to set result */ @ReactMethod - fun stopLivePreview() { + fun stopLivePreview(promise: Promise) { if (theta == null) { - throw Exception(messageNotInit) + promise.reject(Exception(messageNotInit)) + return } - previewing = false + + if (!previewing || stopLivePreviewPromise != null) { + promise.resolve(false) + return + } + + stopLivePreviewPromise = promise + } + + private fun execAndResetStopLivePreviewResolve(flag: Boolean) { + stopLivePreviewPromise?.resolve(flag) + stopLivePreviewPromise = null } /** diff --git a/react-native/ios/ThetaClientReactNative.swift b/react-native/ios/ThetaClientReactNative.swift index 3fd135041b..92c85736dc 100644 --- a/react-native/ios/ThetaClientReactNative.swift +++ b/react-native/ios/ThetaClientReactNative.swift @@ -2,6 +2,7 @@ import THETAClient let ERROR_CODE_ERROR = "error" let MESSAGE_NOT_INIT = "Not initialized." +let MESSAGE_LIVE_PREVIEW_RUNNING = "Live Preview is running." let MESSAGE_NO_RESULT = "No result." let MESSAGE_NO_ARGUMENT = "No Argument." let MESSAGE_NO_PHOTO_CAPTURE = "No photoCapture." @@ -35,6 +36,7 @@ let MESSAGE_NO_EVENT_WEBSOCKET = "no eventWebSocket." class ThetaClientReactNative: RCTEventEmitter { var thetaRepository: ThetaRepository? var previewing = false + var stopLivePreviewResolve: RCTPromiseResolveBlock? var photoCaptureBuilder: PhotoCapture.Builder? var photoCapture: PhotoCapture? var timeShiftCaptureBuilder: TimeShiftCapture.Builder? @@ -143,6 +145,7 @@ class ThetaClientReactNative: RCTEventEmitter { continuousCaptureBuilder = nil continuousCapture = nil previewing = false + stopLivePreviewResolve = nil eventWebSocket?.stop(completionHandler: { _ in }) eventWebSocket = nil @@ -451,6 +454,11 @@ class ThetaClientReactNative: RCTEventEmitter { resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock ) { + if previewing { + reject(ERROR_CODE_ERROR, MESSAGE_LIVE_PREVIEW_RUNNING, nil) + return + } + class FrameHandler: KotlinSuspendFunction1 { let thetaClientReactNative: ThetaClientReactNative static let FrameInterval = CFTimeInterval(1.0 / 10.0) @@ -477,7 +485,7 @@ class ThetaClientReactNative: RCTEventEmitter { } } } - return thetaClientReactNative.previewing + return thetaClientReactNative.stopLivePreviewResolve == nil } } @@ -486,11 +494,17 @@ class ThetaClientReactNative: RCTEventEmitter { return } let frameHandler = FrameHandler(self) + previewing = true + execAndResetStopLivePreviewResolve(false) + thetaRepository.getLivePreview(frameHandler: frameHandler) { error in if let error { reject(ERROR_CODE_ERROR, error.localizedDescription, error) } else { + self.execAndResetStopLivePreviewResolve(true) + self.previewing = false + resolve(true) } } @@ -498,11 +512,19 @@ class ThetaClientReactNative: RCTEventEmitter { @objc(stopLivePreview:withRejecter:) func stopLivePreview( - resolve: RCTPromiseResolveBlock, - reject _: RCTPromiseRejectBlock + resolve: @escaping RCTPromiseResolveBlock, + reject _: @escaping RCTPromiseRejectBlock ) { - previewing = false - resolve(nil) + if !previewing || stopLivePreviewResolve != nil { + resolve(false) + return + } + stopLivePreviewResolve = resolve + } + + private func execAndResetStopLivePreviewResolve(_ flag: Bool) { + stopLivePreviewResolve?(flag) + stopLivePreviewResolve = nil } @objc(getPhotoCaptureBuilder:withRejecter:) diff --git a/react-native/src/theta-repository/theta-repository.ts b/react-native/src/theta-repository/theta-repository.ts index 676e6fe5cf..ac39b6d51f 100644 --- a/react-native/src/theta-repository/theta-repository.ts +++ b/react-native/src/theta-repository/theta-repository.ts @@ -204,9 +204,10 @@ export function getLivePreview(): Promise { * Stop live preview. * * @function StopLivePreview + * @return promise of boolean result */ -export function stopLivePreview() { - ThetaClientReactNative.stopLivePreview(); +export function stopLivePreview(): Promise { + return ThetaClientReactNative.stopLivePreview(); } /** diff --git a/react-native/verification-tool/src/screen/live-preview-screen/live-preview-screen.tsx b/react-native/verification-tool/src/screen/live-preview-screen/live-preview-screen.tsx index b030ec94ed..9aee467914 100644 --- a/react-native/verification-tool/src/screen/live-preview-screen/live-preview-screen.tsx +++ b/react-native/verification-tool/src/screen/live-preview-screen/live-preview-screen.tsx @@ -63,7 +63,9 @@ const LivePreviewScreen: React.FC< const onStop = () => { isInitialized().then((isInit) => { if (isInit) { - stopLivePreview(); + stopLivePreview().then((isStopped) => { + console.log(`isStop = ${isStopped}`); + }); } else { Alert.alert('stopLivePreview', 'error: Not initialized.', [ { text: 'OK' },