diff --git a/.github/workflows/test-kmp.yaml b/.github/workflows/test-kmp.yaml index 7c677cf1ff..210530d597 100644 --- a/.github/workflows/test-kmp.yaml +++ b/.github/workflows/test-kmp.yaml @@ -20,7 +20,7 @@ jobs: - name: Build and Test with Gradle uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 with: - arguments: testReleaseUnitTest + arguments: testReleaseUnitTest --info - name: Archive code coverage results if: always() uses: actions/upload-artifact@v1 diff --git a/demos/demo-android/app/build.gradle b/demos/demo-android/app/build.gradle index 1407a30387..f232ce693b 100755 --- a/demos/demo-android/app/build.gradle +++ b/demos/demo-android/app/build.gradle @@ -75,7 +75,7 @@ dependencies { implementation 'com.jakewharton.timber:timber:5.0.1' implementation 'io.coil-kt:coil-compose:2.2.2' implementation "io.ktor:ktor-client-cio:2.3.9" - implementation "com.ricoh360.thetaclient:theta-client:1.9.1" + implementation "com.ricoh360.thetaclient:theta-client:1.10.0" testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0' testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" diff --git a/demos/demo-android/app/src/main/java/com/ricoh360/thetaclient/thetaClientDemo/PreviewScreen.kt b/demos/demo-android/app/src/main/java/com/ricoh360/thetaclient/thetaClientDemo/PreviewScreen.kt index aa3721ea73..729bb5e498 100755 --- a/demos/demo-android/app/src/main/java/com/ricoh360/thetaclient/thetaClientDemo/PreviewScreen.kt +++ b/demos/demo-android/app/src/main/java/com/ricoh360/thetaclient/thetaClientDemo/PreviewScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalLifecycleOwner import com.ricoh360.thetaclient.ThetaRepository +import com.ricoh360.thetaclient.capture.CapturingStatusEnum import com.ricoh360.thetaclient.capture.PhotoCapture import com.ricoh360.thetaclient.thetaClientDemo.ui.theme.ThetaSimpleAndroidAppTheme import kotlinx.coroutines.CoroutineScope @@ -51,6 +52,10 @@ fun PreviewScreen(toPhoto: (photoUrl: String) -> Unit, viewModel: ThetaViewModel } } + override fun onCapturing(status: CapturingStatusEnum) { + Timber.i("takePicture onCapturing: ${status.name}") + } + override fun onError(exception: ThetaRepository.ThetaRepositoryException) { Timber.e(exception) } diff --git a/demos/demo-flutter/lib/take_picture_screen.dart b/demos/demo-flutter/lib/take_picture_screen.dart index 5db74978b8..339cbe7a01 100644 --- a/demos/demo-flutter/lib/take_picture_screen.dart +++ b/demos/demo-flutter/lib/take_picture_screen.dart @@ -212,6 +212,8 @@ class _TakePictureScreen extends State shooting = false; }); debugPrint(exception.toString()); + }, onCapturing: (status) { + debugPrint("onCapturing: $status"); }); } } diff --git a/demos/demo-ios/Podfile b/demos/demo-ios/Podfile index 2bf4212214..50402c470d 100644 --- a/demos/demo-ios/Podfile +++ b/demos/demo-ios/Podfile @@ -7,5 +7,5 @@ target 'SdkSample' do use_frameworks! # Pods for SdkSample - pod 'THETAClient', '1.9.1' + pod 'THETAClient', '1.10.0' end diff --git a/demos/demo-ios/SdkSample/ThetaSdk.swift b/demos/demo-ios/SdkSample/ThetaSdk.swift index cf0c171e40..8c0203fc2d 100644 --- a/demos/demo-ios/SdkSample/ThetaSdk.swift +++ b/demos/demo-ios/SdkSample/ThetaSdk.swift @@ -151,7 +151,9 @@ class Theta { callback(fileUrl, nil) } - func onProgress(completion _: Float) {} + func onCapturing(status: CapturingStatusEnum) { + print("takePicture onCapturing: " + status.name) + } func onError(exception: ThetaException) { callback(nil, exception.asError()) diff --git a/demos/demo-react-native/package.json b/demos/demo-react-native/package.json index c199795939..2661b23e53 100644 --- a/demos/demo-react-native/package.json +++ b/demos/demo-react-native/package.json @@ -13,7 +13,7 @@ "dependencies": { "@react-navigation/native": "^6.1.0", "@react-navigation/native-stack": "^6.9.5", - "theta-client-react-native": "1.9.1", + "theta-client-react-native": "1.10.0", "react": "18.2.0", "react-native": "0.71.14", "react-native-safe-area-context": "^4.4.1", diff --git a/demos/demo-react-native/src/ListPhotos.tsx b/demos/demo-react-native/src/ListPhotos.tsx index 515d3bc814..0777e90f3a 100644 --- a/demos/demo-react-native/src/ListPhotos.tsx +++ b/demos/demo-react-native/src/ListPhotos.tsx @@ -16,7 +16,7 @@ import { getThetaInfo, FileTypeEnum, FileInfo, -} from 'theta-client-react-native'; +} from './modules/theta-client'; const listPhotos = async () => { const {fileList} = await listFiles(FileTypeEnum.IMAGE, 0, 1000); diff --git a/demos/demo-react-native/src/MainMenu.tsx b/demos/demo-react-native/src/MainMenu.tsx index 00ea3015a1..e2528b96a1 100644 --- a/demos/demo-react-native/src/MainMenu.tsx +++ b/demos/demo-react-native/src/MainMenu.tsx @@ -9,7 +9,7 @@ import { } from 'react-native'; import {SafeAreaView} from 'react-native-safe-area-context'; import styles from './Styles'; -import {initialize} from 'theta-client-react-native'; +import {initialize} from './modules/theta-client'; const MainMenu = ({navigation}) => { const goTake = () => { diff --git a/demos/demo-react-native/src/TakePhoto.tsx b/demos/demo-react-native/src/TakePhoto.tsx index df02888ae1..3227e04d17 100644 --- a/demos/demo-react-native/src/TakePhoto.tsx +++ b/demos/demo-react-native/src/TakePhoto.tsx @@ -14,7 +14,7 @@ import { getPhotoCaptureBuilder, THETA_EVENT_NAME, isInitialized, -} from 'theta-client-react-native'; +} from './modules/theta-client'; import {useIsFocused} from '@react-navigation/native'; import WebView from 'react-native-webview'; diff --git a/demos/demo-react-native/src/modules/theta-client.ts b/demos/demo-react-native/src/modules/theta-client.ts new file mode 100644 index 0000000000..1769d38e03 --- /dev/null +++ b/demos/demo-react-native/src/modules/theta-client.ts @@ -0,0 +1 @@ +export * from 'theta-client-react-native'; diff --git a/demos/demo-react-native/tsconfig.json b/demos/demo-react-native/tsconfig.json index 0cc9cdd69a..fdc5fcde14 100644 --- a/demos/demo-react-native/tsconfig.json +++ b/demos/demo-react-native/tsconfig.json @@ -2,6 +2,7 @@ { "extends": "@tsconfig/react-native/tsconfig.json", /* Recommended React Native TSConfig base */ "compilerOptions": { + "jsx": "react", /* Visit https://aka.ms/tsconfig.json to read more about this file */ /* Completeness */ diff --git a/docs/tutorial-android.ja.md b/docs/tutorial-android.ja.md index 0114c03c60..79eb8fb658 100644 --- a/docs/tutorial-android.ja.md +++ b/docs/tutorial-android.ja.md @@ -4,7 +4,7 @@ - モジュールの`build.gradle`の`dependencies`に次を追加します。 ``` - implementation "com.ricoh360.thetaclient:theta-client:1.9.1" + implementation "com.ricoh360.thetaclient:theta-client:1.10.0" ``` - 本 SDK を使用したアプリケーションが動作するスマートフォンと THETA を無線 LAN 接続しておきます。 diff --git a/docs/tutorial-android.md b/docs/tutorial-android.md index 4199bf37c5..b619cecf07 100644 --- a/docs/tutorial-android.md +++ b/docs/tutorial-android.md @@ -5,7 +5,7 @@ - Add following descriptions to the `dependencies` of your module's `build.gradle`. ``` - implementation "com.ricoh360.thetaclient:theta-client:1.9.1" + implementation "com.ricoh360.thetaclient:theta-client:1.10.0" ``` - Connect the wireless LAN between THETA and the smartphone that runs on the application using this SDK. diff --git a/flutter/android/build.gradle b/flutter/android/build.gradle index 64968793b0..a29a08dc2d 100644 --- a/flutter/android/build.gradle +++ b/flutter/android/build.gradle @@ -53,5 +53,5 @@ dependencies { implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.9") implementation("com.soywiz.korlibs.krypto:krypto:4.0.10") - implementation("com.ricoh360.thetaclient:theta-client:1.9.1") + implementation("com.ricoh360.thetaclient:theta-client:1.10.0") } diff --git a/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt b/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt index bf5f0abe94..58bb132b2e 100644 --- a/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt +++ b/flutter/android/src/main/kotlin/com/ricoh360/thetaclient/theta_client_flutter/ConvertUtil.kt @@ -11,6 +11,7 @@ const val KEY_NOTIFY_PARAMS = "params" const val KEY_NOTIFY_PARAM_COMPLETION = "completion" const val KEY_NOTIFY_PARAM_IMAGE = "image" const val KEY_NOTIFY_PARAM_MESSAGE = "message" +const val KEY_NOTIFY_PARAM_STATUS = "status" const val KEY_GPS_INFO = "gpsInfo" const val KEY_STATE_EXTERNAL_GPS_INFO = "externalGpsInfo" const val KEY_STATE_INTERNAL_GPS_INFO = "internalGpsInfo" @@ -344,6 +345,11 @@ fun setCaptureBuilderParams(call: MethodCall, builder: Capture.Builder) { } fun setPhotoCaptureBuilderParams(call: MethodCall, builder: PhotoCapture.Builder) { + call.argument("_capture_interval")?.let { + if (it >= 0) { + builder.setCheckStatusCommandInterval(it.toLong()) + } + } call.argument(OptionNameEnum.Filter.name)?.let { enumName -> FilterEnum.values().find { it.name == enumName }?.let { builder.setFilter(it) @@ -382,6 +388,11 @@ fun setTimeShiftCaptureBuilderParams(call: MethodCall, builder: TimeShiftCapture } fun setVideoCaptureBuilderParams(call: MethodCall, builder: VideoCapture.Builder) { + call.argument("_capture_interval")?.let { + if (it >= 0) { + builder.setCheckStatusCommandInterval(it.toLong()) + } + } call.argument(OptionNameEnum.MaxRecordableTime.name)?.let { enumName -> MaxRecordableTimeEnum.values().find { it.name == enumName }?.let { builder.setMaxRecordableTime(it) @@ -395,6 +406,11 @@ fun setVideoCaptureBuilderParams(call: MethodCall, builder: VideoCapture.Builder } fun setLimitlessIntervalCaptureBuilderParams(call: MethodCall, builder: LimitlessIntervalCapture.Builder) { + call.argument("_capture_interval")?.let { + if (it >= 0) { + builder.setCheckStatusCommandInterval(it.toLong()) + } + } call.argument(OptionNameEnum.CaptureInterval.name)?.also { builder.setCaptureInterval(it) } @@ -906,3 +922,9 @@ fun toMessageNotifyParam(message: String): Map { KEY_NOTIFY_PARAM_MESSAGE to message ) } + +fun toCapturingNotifyParam(status: CapturingStatusEnum): Map { + return mapOf( + KEY_NOTIFY_PARAM_STATUS to status.name + ) +} 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 6aec8756ed..8ef714e42f 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 @@ -65,17 +65,26 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { const val notifyIdLivePreview = 10001 const val notifyIdTimeShiftProgress = 10011 const val notifyIdTimeShiftStopError = 10012 - const val notifyIdVideoCaptureStopError = 10003 + const val notifyIdTimeShiftCapturing = 10013 const val notifyIdLimitlessIntervalCaptureStopError = 10004 + const val notifyIdLimitlessIntervalCaptureCapturing = 10005 const val notifyIdShotCountSpecifiedIntervalCaptureProgress = 10021 const val notifyIdShotCountSpecifiedIntervalCaptureStopError = 10022 + const val notifyIdShotCountSpecifiedIntervalCaptureCapturing = 10023 const val notifyIdCompositeIntervalCaptureProgress = 10031; const val notifyIdCompositeIntervalCaptureStopError = 10032; + const val notifyIdCompositeIntervalCaptureCapturing = 10033; const val notifyIdMultiBracketCaptureProgress = 10041; const val notifyIdMultiBracketCaptureStopError = 10042; + const val notifyIdMultiBracketCaptureCapturing = 10043; const val notifyIdBurstCaptureProgress = 10051; const val notifyIdBurstCaptureStopError = 10052; + const val notifyIdBurstCaptureCapturing = 10053 const val notifyIdContinuousCaptureProgress = 10061; + const val notifyIdContinuousCaptureCapturing = 10062; + const val notifyIdPhotoCapturing = 10071 + const val notifyIdVideoCaptureStopError = 10081 + const val notifyIdVideoCaptureCapturing = 10082 } fun sendNotifyEvent(id: Int, params: Map) { @@ -666,6 +675,13 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { result.success(fileUrl) } + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + notifyIdPhotoCapturing, + toCapturingNotifyParam(status) + ) + } + override fun onError(exception: ThetaRepository.ThetaRepositoryException) { result.error(exception.javaClass.simpleName, exception.message, null) } @@ -726,6 +742,13 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { ) } + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + notifyIdTimeShiftCapturing, + toCapturingNotifyParam(status) + ) + } + override fun onCaptureCompleted(fileUrl: String?) { result.success(fileUrl) } @@ -789,6 +812,13 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { toMessageNotifyParam(exception.message ?: exception.toString()) ) } + + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + notifyIdVideoCaptureCapturing, + toCapturingNotifyParam(status) + ) + } }) } @@ -849,6 +879,13 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { toMessageNotifyParam(exception.message ?: exception.toString()) ) } + + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + notifyIdLimitlessIntervalCaptureCapturing, + toCapturingNotifyParam(status) + ) + } }) } @@ -917,6 +954,13 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { ) } + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + notifyIdShotCountSpecifiedIntervalCaptureCapturing, + toCapturingNotifyParam(status) + ) + } + override fun onCaptureCompleted(fileUrls: List?) { result.success(fileUrls) } @@ -990,6 +1034,13 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { ) } + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + notifyIdCompositeIntervalCaptureCapturing, + toCapturingNotifyParam(status) + ) + } + override fun onCaptureCompleted(fileUrls: List?) { result.success(fileUrls) } @@ -1097,6 +1148,13 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { ) } + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + notifyIdBurstCaptureCapturing, + toCapturingNotifyParam(status) + ) + } + override fun onCaptureCompleted(fileUrls: List?) { result.success(fileUrls) } @@ -1168,6 +1226,13 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { ) } + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + notifyIdMultiBracketCaptureCapturing, + toCapturingNotifyParam(status) + ) + } + override fun onCaptureCompleted(fileUrls: List?) { result.success(fileUrls) } @@ -1231,6 +1296,13 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { ) } + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + notifyIdContinuousCaptureCapturing, + toCapturingNotifyParam(status) + ) + } + override fun onCaptureCompleted(fileUrls: List?) { result.success(fileUrls) } diff --git a/flutter/ios/Classes/ConvertUtil.swift b/flutter/ios/Classes/ConvertUtil.swift index 407732d99d..17f678fbe8 100644 --- a/flutter/ios/Classes/ConvertUtil.swift +++ b/flutter/ios/Classes/ConvertUtil.swift @@ -8,6 +8,7 @@ let KEY_NOTIFY_PARAMS = "params" let KEY_NOTIFY_PARAM_COMPLETION = "completion" let KEY_NOTIFY_PARAM_IMAGE = "image" let KEY_NOTIFY_PARAM_MESSAGE = "message" +let KEY_NOTIFY_PARAM_STATUS = "status" let KEY_GPS_INFO = "gpsInfo" let KEY_STATE_EXTERNAL_GPS_INFO = "externalGpsInfo" let KEY_STATE_INTERNAL_GPS_INFO = "internalGpsInfo" @@ -254,6 +255,11 @@ func setCaptureBuilderParams(params: [String: Any], builder: CaptureBuilder= 0 + { + builder.setCheckStatusCommandInterval(timeMillis: Int64(interval)) + } if let value = params[ThetaRepository.OptionNameEnum.filter.name] as? String { if let enumValue = getEnumValue(values: ThetaRepository.FilterEnum.values(), name: value) { builder.setFilter(filter: enumValue) @@ -292,6 +298,11 @@ func setTimeShiftCaptureBuilderParams(params: [String: Any], builder: TimeShiftC } func setVideoCaptureBuilderParams(params: [String: Any], builder: VideoCapture.Builder) { + if let interval = params["_capture_interval"] as? Int, + interval >= 0 + { + builder.setCheckStatusCommandInterval(timeMillis: Int64(interval)) + } if let value = params[ThetaRepository.OptionNameEnum.maxrecordabletime.name] as? String { if let enumValue = getEnumValue(values: ThetaRepository.MaxRecordableTimeEnum.values(), name: value) { builder.setMaxRecordableTime(time: enumValue) @@ -305,6 +316,11 @@ func setVideoCaptureBuilderParams(params: [String: Any], builder: VideoCapture.B } func setLimitlessIntervalCaptureBuilderParams(params: [String: Any], builder: LimitlessIntervalCapture.Builder) { + if let interval = params["_capture_interval"] as? Int, + interval >= 0 + { + builder.setCheckStatusCommandInterval(timeMillis: Int64(interval)) + } if let value = params[ThetaRepository.OptionNameEnum.captureinterval.name] as? Int32 { builder.setCaptureInterval(interval: value) } @@ -1104,3 +1120,9 @@ func toMessageNotifyParam(message: String) -> [String: Any] { KEY_NOTIFY_PARAM_MESSAGE: message, ] } + +func toCapturingNotifyParam(value: CapturingStatusEnum) -> [String: Any] { + return [ + KEY_NOTIFY_PARAM_STATUS: value.name, + ] +} diff --git a/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift b/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift index 2130d26edb..d4cf66a3a4 100644 --- a/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift +++ b/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift @@ -5,18 +5,27 @@ import UIKit let EVENT_NOTIFY = "theta_client_flutter/theta_notify" let NOTIFY_LIVE_PREVIEW = 10001 let NOTIFY_TIME_SHIFT_PROGRESS = 10011 -let NOTIFY_TIME_SHIFT_STOP_ERROR = 10011 -let NOTIFY_VIDEO_CAPTURE_STOP_ERROR = 10003 +let NOTIFY_TIME_SHIFT_STOP_ERROR = 10012 +let NOTIFY_TIME_SHIFT_CAPTURING = 10013 let NOTIFY_LIMITLESS_INTERVAL_CAPTURE_STOP_ERROR = 10004 +let NOTIFY_LIMITLESS_INTERVAL_CAPTURE_CAPTURING = 10005 let NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_CAPTURE_PROGRESS = 10021 let NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_CAPTURE_STOP_ERROR = 10022 +let NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_CAPTURE_CAPTURING = 10023 let NOTIFY_COMPOSITE_INTERVAL_PROGRESS = 10031 let NOTIFY_COMPOSITE_INTERVAL_STOP_ERROR = 10032 +let NOTIFY_COMPOSITE_INTERVAL_CAPTURING = 10033 let NOTIFY_MULTI_BRACKET_INTERVAL_PROGRESS = 10041 let NOTIFY_MULTI_BRACKET_INTERVAL_STOP_ERROR = 10042 +let NOTIFY_MULTI_BRACKET_INTERVAL_CAPTURING = 10043 let NOTIFY_BURST_PROGRESS = 10051 let NOTIFY_BURST_STOP_ERROR = 10052 +let NOTIFY_BURST_CAPTURING = 10053 let NOTIFY_CONTINUOUS_PROGRESS = 10061 +let NOTIFY_CONTINUOUS_CAPTURING = 10062 +let NOTIFY_PHOTO_CAPTURING = 10071 +let NOTIFY_VIDEO_CAPTURE_STOP_ERROR = 10081 +let NOTIFY_VIDEO_CAPTURE_CAPTURING = 10082 public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStreamHandler { public func onListen(withArguments _: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { @@ -75,7 +84,9 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre func sendNotifyEvent(id: Int, params: [String: Any]?) { if let eventSink = eventSink { - eventSink(toNotify(id: id, params: params)) + DispatchQueue.main.async { + eventSink(toNotify(id: id, params: params)) + } } } @@ -547,29 +558,33 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre class Callback: PhotoCaptureTakePictureCallback { let callback: (_ url: String?, _ error: Error?) -> Void - init(_ callback: @escaping (_ url: String?, _ error: Error?) -> Void) { + weak var plugin: SwiftThetaClientFlutterPlugin? + init(_ callback: @escaping (_ url: String?, _ error: Error?) -> Void, plugin: SwiftThetaClientFlutterPlugin) { self.callback = callback + self.plugin = plugin } func onSuccess(fileUrl: String?) { callback(fileUrl, nil) } - func onProgress(completion _: Float) {} + func onCapturing(status: CapturingStatusEnum) { + plugin?.sendNotifyEvent(id: NOTIFY_PHOTO_CAPTURING, params: toCapturingNotifyParam(value: status)) + } func onError(exception: ThetaRepository.ThetaRepositoryException) { callback(nil, exception.asError()) } } photoCapture!.takePicture( - callback: Callback { fileUrl, error in + callback: Callback({ fileUrl, error in if let thetaError = error { let flutterError = FlutterError(code: SwiftThetaClientFlutterPlugin.errorCode, message: thetaError.localizedDescription, details: nil) result(flutterError) } else { result(fileUrl) } - } + }, plugin: self) ) } @@ -629,7 +644,11 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre func onProgress(completion: Float) { plugin?.sendNotifyEvent(id: NOTIFY_TIME_SHIFT_PROGRESS, params: toCaptureProgressNotifyParam(value: completion)) } - + + func onCapturing(status: CapturingStatusEnum) { + plugin?.sendNotifyEvent(id: NOTIFY_TIME_SHIFT_CAPTURING, params: toCapturingNotifyParam(value: status)) + } + func onCaptureCompleted(fileUrl: String?) { callback(fileUrl, nil) } @@ -715,6 +734,10 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre let error = exception.asError() plugin?.sendNotifyEvent(id: NOTIFY_VIDEO_CAPTURE_STOP_ERROR, params: toMessageNotifyParam(message: error.localizedDescription)) } + + func onCapturing(status: CapturingStatusEnum) { + plugin?.sendNotifyEvent(id: NOTIFY_VIDEO_CAPTURE_CAPTURING, params: toCapturingNotifyParam(value: status)) + } } videoCapturing = videoCapture!.startCapture( callback: Callback({ fileUrl, error in @@ -797,6 +820,10 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre let error = exception.asError() plugin?.sendNotifyEvent(id: NOTIFY_LIMITLESS_INTERVAL_CAPTURE_STOP_ERROR, params: toMessageNotifyParam(message: error.localizedDescription)) } + + func onCapturing(status: CapturingStatusEnum) { + plugin?.sendNotifyEvent(id: NOTIFY_LIMITLESS_INTERVAL_CAPTURE_CAPTURING, params: toCapturingNotifyParam(value: status)) + } } limitlessIntervalCapturing = limitlessIntervalCapture.startCapture( callback: Callback({ urls, error in @@ -880,7 +907,11 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre func onProgress(completion: Float) { plugin?.sendNotifyEvent(id: NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_CAPTURE_PROGRESS, params: toCaptureProgressNotifyParam(value: completion)) } - + + func onCapturing(status: CapturingStatusEnum) { + plugin?.sendNotifyEvent(id: NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_CAPTURE_CAPTURING, params: toCapturingNotifyParam(value: status)) + } + func onCaptureCompleted(fileUrls: [String]?) { callback(fileUrls, nil) } @@ -968,7 +999,11 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre func onProgress(completion: Float) { plugin?.sendNotifyEvent(id: NOTIFY_COMPOSITE_INTERVAL_PROGRESS, params: toCaptureProgressNotifyParam(value: completion)) } - + + func onCapturing(status: CapturingStatusEnum) { + plugin?.sendNotifyEvent(id: NOTIFY_COMPOSITE_INTERVAL_CAPTURING, params: toCapturingNotifyParam(value: status)) + } + func onCaptureCompleted(fileUrls: [String]?) { callback(fileUrls, nil) } @@ -1082,6 +1117,10 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre plugin?.sendNotifyEvent(id: NOTIFY_BURST_PROGRESS, params: toCaptureProgressNotifyParam(value: completion)) } + func onCapturing(status: CapturingStatusEnum) { + plugin?.sendNotifyEvent(id: NOTIFY_BURST_CAPTURING, params: toCapturingNotifyParam(value: status)) + } + func onCaptureCompleted(fileUrls: [String]?) { callback(fileUrls, nil) } @@ -1170,6 +1209,10 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre plugin?.sendNotifyEvent(id: NOTIFY_MULTI_BRACKET_INTERVAL_PROGRESS, params: toCaptureProgressNotifyParam(value: completion)) } + func onCapturing(status: CapturingStatusEnum) { + plugin?.sendNotifyEvent(id: NOTIFY_MULTI_BRACKET_INTERVAL_CAPTURING, params: toCapturingNotifyParam(value: status)) + } + func onCaptureCompleted(fileUrls: [String]?) { callback(fileUrls, nil) } @@ -1251,7 +1294,11 @@ public class SwiftThetaClientFlutterPlugin: NSObject, FlutterPlugin, FlutterStre func onProgress(completion: Float) { plugin?.sendNotifyEvent(id: NOTIFY_CONTINUOUS_PROGRESS, params: toCaptureProgressNotifyParam(value: completion)) } - + + func onCapturing(status: CapturingStatusEnum) { + plugin?.sendNotifyEvent(id: NOTIFY_CONTINUOUS_CAPTURING, params: toCapturingNotifyParam(value: status)) + } + func onCaptureCompleted(fileUrls: [String]?) { callback(fileUrls, nil) } diff --git a/flutter/ios/theta_client_flutter.podspec b/flutter/ios/theta_client_flutter.podspec index a615a9ce40..0c88db1af0 100644 --- a/flutter/ios/theta_client_flutter.podspec +++ b/flutter/ios/theta_client_flutter.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'theta_client_flutter' - s.version = '1.9.1' + s.version = '1.10.0' s.summary = 'theta-client plugin project.' s.description = <<-DESC theta-client Flutter plugin project. @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.dependency 'Flutter' s.platform = :ios, '15.0' - s.dependency 'THETAClient', '1.9.1' + s.dependency 'THETAClient', '1.10.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/flutter/lib/capture/capture.dart b/flutter/lib/capture/capture.dart index 77b202e5fd..2278e5e3ff 100644 --- a/flutter/lib/capture/capture.dart +++ b/flutter/lib/capture/capture.dart @@ -52,6 +52,31 @@ class Capture { Capture(this._options); } +/// Capturing status +enum CapturingStatusEnum { + /// Capture in progress + capturing('CAPTURING'), + + /// Self-timer in progress + selfTimerCountdown('SELF_TIMER_COUNTDOWN'), + ; + + final String rawValue; + + const CapturingStatusEnum(this.rawValue); + + @override + String toString() { + return rawValue; + } + + static CapturingStatusEnum? getValue(String rawValue) { + return CapturingStatusEnum.values.cast().firstWhere( + (element) => element?.rawValue == rawValue, + orElse: () => null); + } +} + /// Common PhotoCapture class class PhotoCaptureBase extends Capture { PhotoCaptureBase(super.options); @@ -64,7 +89,13 @@ class PhotoCaptureBase extends Capture { /// Capture of Photo class PhotoCapture extends PhotoCaptureBase { - PhotoCapture(super.options); + final int _interval; + + PhotoCapture(super.options, this._interval); + + int getCheckStatusCommandInterval() { + return _interval; + } /// Get image processing filter. FilterEnum? getFilter() { @@ -78,9 +109,10 @@ class PhotoCapture extends PhotoCaptureBase { /// Take a picture. void takePicture(void Function(String? fileUrl) onSuccess, - void Function(Exception exception) onError) { + void Function(Exception exception) onError, + {void Function(CapturingStatusEnum status)? onCapturing}) { ThetaClientFlutterPlatform.instance - .takePicture() + .takePicture(onCapturing) .then((value) => onSuccess(value!)) .onError((error, stackTrace) => onError(error as Exception)); } @@ -105,9 +137,10 @@ class TimeShiftCapture extends Capture { void Function(String? fileUrl) onCaptureCompleted, void Function(double completion) onProgress, void Function(Exception exception) onCaptureFailed, - {void Function(Exception exception)? onStopFailed}) { + {void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing}) { ThetaClientFlutterPlatform.instance - .startTimeShiftCapture(onProgress, onStopFailed) + .startTimeShiftCapture(onProgress, onStopFailed, onCapturing) .then((value) => onCaptureCompleted(value)) .onError((error, stackTrace) => onCaptureFailed(error as Exception)); return TimeShiftCapturing(); @@ -116,7 +149,13 @@ class TimeShiftCapture extends Capture { /// Capture of Video class VideoCapture extends Capture { - VideoCapture(super.options); + final int _interval; + + VideoCapture(super.options, this._interval); + + int getCheckStatusCommandInterval() { + return _interval; + } /// Get maximum recordable time (in seconds) of the camera. MaxRecordableTimeEnum? getMaxRecordableTime() { @@ -131,9 +170,10 @@ class VideoCapture extends Capture { /// Starts video capture. VideoCapturing startCapture(void Function(String? fileUrl) onCaptureCompleted, void Function(Exception exception) onCaptureFailed, - {void Function(Exception exception)? onStopFailed}) { + {void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing}) { ThetaClientFlutterPlatform.instance - .startVideoCapture(onStopFailed) + .startVideoCapture(onStopFailed, onCapturing) .then((value) => onCaptureCompleted(value)) .onError((error, stackTrace) => onCaptureFailed(error as Exception)); return VideoCapturing(); @@ -142,7 +182,13 @@ class VideoCapture extends Capture { /// Capture of limitless interval class LimitlessIntervalCapture extends Capture { - LimitlessIntervalCapture(super.options); + final int _interval; + + LimitlessIntervalCapture(super.options, this._interval); + + int getCheckStatusCommandInterval() { + return _interval; + } /// Get shooting interval (sec.) for interval shooting. int? getCaptureInterval() => @@ -152,9 +198,10 @@ class LimitlessIntervalCapture extends Capture { LimitlessIntervalCapturing startCapture( void Function(List? fileUrls) onCaptureCompleted, void Function(Exception exception) onCaptureFailed, - {void Function(Exception exception)? onStopFailed}) { + {void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing}) { ThetaClientFlutterPlatform.instance - .startLimitlessIntervalCapture(onStopFailed) + .startLimitlessIntervalCapture(onStopFailed, onCapturing) .then((value) => onCaptureCompleted(value)) .onError((error, stackTrace) => onCaptureFailed(error as Exception)); return LimitlessIntervalCapturing(); @@ -183,9 +230,11 @@ class ShotCountSpecifiedIntervalCapture extends Capture { void Function(List? fileUrls) onSuccess, void Function(double completion) onProgress, void Function(Exception exception) onCaptureFailed, - {void Function(Exception exception)? onStopFailed}) { + {void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing}) { ThetaClientFlutterPlatform.instance - .startShotCountSpecifiedIntervalCapture(onProgress, onStopFailed) + .startShotCountSpecifiedIntervalCapture( + onProgress, onStopFailed, onCapturing) .then((value) => onSuccess(value)) .onError((error, stackTrace) => onCaptureFailed(error as Exception)); return ShotCountSpecifiedIntervalCapturing(); @@ -215,9 +264,10 @@ class CompositeIntervalCapture extends Capture { void Function(List? fileUrls) onSuccess, void Function(double completion) onProgress, void Function(Exception exception) onCaptureFailed, - {void Function(Exception exception)? onStopFailed}) { + {void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing}) { ThetaClientFlutterPlatform.instance - .startCompositeIntervalCapture(onProgress, onStopFailed) + .startCompositeIntervalCapture(onProgress, onStopFailed, onCapturing) .then((value) => onSuccess(value)) .onError((error, stackTrace) => onCaptureFailed(error as Exception)); return CompositeIntervalCapturing(); @@ -246,9 +296,10 @@ class BurstCapture extends Capture { void Function(List? fileUrls) onSuccess, void Function(double completion) onProgress, void Function(Exception exception) onCaptureFailed, - {void Function(Exception exception)? onStopFailed}) { + {void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing}) { ThetaClientFlutterPlatform.instance - .startBurstCapture(onProgress, onStopFailed) + .startBurstCapture(onProgress, onStopFailed, onCapturing) .then((value) => onSuccess(value)) .onError((error, stackTrace) => onCaptureFailed(error as Exception)); return BurstCapturing(); @@ -270,9 +321,10 @@ class MultiBracketCapture extends Capture { void Function(List? fileUrls) onSuccess, void Function(double completion) onProgress, void Function(Exception exception) onCaptureFailed, - {void Function(Exception exception)? onStopFailed}) { + {void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing}) { ThetaClientFlutterPlatform.instance - .startMultiBracketCapture(onProgress, onStopFailed) + .startMultiBracketCapture(onProgress, onStopFailed, onCapturing) .then((value) => onSuccess(value)) .onError((error, stackTrace) => onCaptureFailed(error as Exception)); return MultiBracketCapturing(); @@ -303,12 +355,12 @@ class ContinuousCapture extends Capture { } /// Starts continuous shooting - void startCapture( - void Function(List? fileUrls) onSuccess, + void startCapture(void Function(List? fileUrls) onSuccess, void Function(double completion) onProgress, - void Function(Exception exception) onCaptureFailed) { + void Function(Exception exception) onCaptureFailed, + {void Function(CapturingStatusEnum status)? onCapturing}) { ThetaClientFlutterPlatform.instance - .startContinuousCapture(onProgress) + .startContinuousCapture(onProgress, onCapturing) .then((value) => onSuccess(value)) .onError((error, stackTrace) => onCaptureFailed(error as Exception)); } diff --git a/flutter/lib/capture/capture_builder.dart b/flutter/lib/capture/capture_builder.dart index 3b8f27281e..904d427e48 100644 --- a/flutter/lib/capture/capture_builder.dart +++ b/flutter/lib/capture/capture_builder.dart @@ -93,6 +93,12 @@ class PhotoCaptureBuilderBase extends CaptureBuilder { /// Builder of [PhotoCapture] class PhotoCaptureBuilder extends PhotoCaptureBuilderBase { + int _interval = -1; + + PhotoCaptureBuilder setCheckStatusCommandInterval(int timeMillis) { + _interval = timeMillis; + return this; + } /// Set image processing filter. PhotoCaptureBuilder setFilter(FilterEnum filter) { @@ -111,8 +117,8 @@ class PhotoCaptureBuilder extends PhotoCaptureBuilderBase { var completer = Completer(); try { await ThetaClientFlutterPlatform.instance - .buildPhotoCapture(_options); - completer.complete(PhotoCapture(_options)); + .buildPhotoCapture(_options, _interval); + completer.complete(PhotoCapture(_options, _interval)); } catch (e) { completer.completeError(e); } @@ -174,6 +180,13 @@ class TimeShiftCaptureBuilder extends CaptureBuilder { /// Builder of VideoCapture class VideoCaptureBuilder extends CaptureBuilder { + int _interval = -1; + + VideoCaptureBuilder setCheckStatusCommandInterval(int timeMillis) { + _interval = timeMillis; + return this; + } + /// Set video file format. VideoCaptureBuilder setFileFormat(VideoFileFormatEnum fileFormat) { _options[TagNameEnum.videoFileFormat.rawValue] = fileFormat; @@ -190,8 +203,9 @@ class VideoCaptureBuilder extends CaptureBuilder { Future build() async { var completer = Completer(); try { - await ThetaClientFlutterPlatform.instance.buildVideoCapture(_options); - completer.complete(VideoCapture(_options)); + await ThetaClientFlutterPlatform.instance + .buildVideoCapture(_options, _interval); + completer.complete(VideoCapture(_options, _interval)); } catch (e) { completer.completeError(e); } @@ -202,6 +216,14 @@ class VideoCaptureBuilder extends CaptureBuilder { /// Builder of LimitlessIntervalCapture class LimitlessIntervalCaptureBuilder extends CaptureBuilder { + int _interval = -1; + + LimitlessIntervalCaptureBuilder setCheckStatusCommandInterval( + int timeMillis) { + _interval = timeMillis; + return this; + } + /// Set shooting interval (sec.) for interval shooting. LimitlessIntervalCaptureBuilder setCaptureInterval(int interval) { _options[OptionNameEnum.captureInterval.rawValue] = interval; @@ -213,8 +235,8 @@ class LimitlessIntervalCaptureBuilder var completer = Completer(); try { await ThetaClientFlutterPlatform.instance - .buildLimitlessIntervalCapture(_options); - completer.complete(LimitlessIntervalCapture(_options)); + .buildLimitlessIntervalCapture(_options, _interval); + completer.complete(LimitlessIntervalCapture(_options, _interval)); } catch (e) { completer.completeError(e); } diff --git a/flutter/lib/state/capture_status.dart b/flutter/lib/state/capture_status.dart new file mode 100644 index 0000000000..635b077de7 --- /dev/null +++ b/flutter/lib/state/capture_status.dart @@ -0,0 +1,48 @@ +/// Capture Status +enum CaptureStatusEnum { + /// Undefined value + unknown('UNKNOWN'), + + /// Capture status. Performing continuously shoot + shooting('SHOOTING'), + + /// Capture status. In standby + idle('IDLE'), + + /// Capture status. Self-timer is operating + selfTimerCountdown('SELF_TIMER_COUNTDOWN'), + + /// Capture status. Performing multi bracket shooting + bracketShooting('BRACKET_SHOOTING'), + + /// Capture status. Converting post file... + converting('CONVERTING'), + + /// Capture status. Performing timeShift shooting + timeShiftShooting('TIME_SHIFT_SHOOTING'), + + /// Capture status. Performing continuous shooting + continuousShooting('CONTINUOUS_SHOOTING'), + + /// Capture status. Waiting for retrospective video... + retrospectiveImageRecording('RETROSPECTIVE_IMAGE_RECORDING'), + + /// Capture status. Performing burst shooting + burstShooting('BURST_SHOOTING'), + ; + + final String rawValue; + + const CaptureStatusEnum(this.rawValue); + + @override + String toString() { + return rawValue; + } + + static CaptureStatusEnum? getValue(String rawValue) { + return CaptureStatusEnum.values.cast().firstWhere( + (element) => element?.rawValue == rawValue, + orElse: () => null); + } +} diff --git a/flutter/lib/state/importer.dart b/flutter/lib/state/importer.dart new file mode 100644 index 0000000000..d92f2f32e6 --- /dev/null +++ b/flutter/lib/state/importer.dart @@ -0,0 +1,6 @@ +/// THETA state +library; + +export 'capture_status.dart'; +export 'state_gps_info.dart'; +export 'theta_state.dart'; diff --git a/flutter/lib/state/theta_state.dart b/flutter/lib/state/theta_state.dart index 86923dbdfc..e61b8bd11b 100644 --- a/flutter/lib/state/theta_state.dart +++ b/flutter/lib/state/theta_state.dart @@ -1,5 +1,4 @@ import '../theta_client_flutter.dart'; -import 'state_gps_info.dart'; /// Mutable values representing Theta status. class ThetaState { @@ -100,3 +99,201 @@ class ThetaState { this.boardTemp, this.batteryTemp); } + +/// Battery charging state +enum ChargingStateEnum { + /// Battery charging state. Charging + charging('CHARGING'), + + /// Battery charging state. Charging completed + completed('COMPLETED'), + + /// Battery charging state. Not charging + notCharging('NOT_CHARGING'); + + final String rawValue; + + const ChargingStateEnum(this.rawValue); + + @override + String toString() { + return rawValue; + } + + static ChargingStateEnum? getValue(String rawValue) { + return ChargingStateEnum.values.cast().firstWhere( + (element) => element?.rawValue == rawValue, + orElse: () => null); + } +} + +/// Shooting function. +/// Shooting settings are retained separately for both the Still image shooting mode and Video shooting mode. +/// Setting them at the same time as exposureDelay will result in an error. +/// +/// For +/// - RICOH THETA X +/// - RICOH THETA Z1 +enum ShootingFunctionEnum { + /// Normal shooting function + normal('NORMAL'), + + /// Self-timer shooting function(RICOH THETA X is not supported.) + selfTimer('SELF_TIMER'), + + /// My setting shooting function + mySetting('MY_SETTING'); + + final String rawValue; + + const ShootingFunctionEnum(this.rawValue); + + @override + String toString() { + return rawValue; + } + + static ShootingFunctionEnum? getValue(String rawValue) { + return ShootingFunctionEnum.values.cast().firstWhere( + (element) => element?.rawValue == rawValue, + orElse: () => null); + } +} + +/// Microphone option +enum MicrophoneOptionEnum { + /// Microphone option. auto + auto('AUTO'), + + /// Microphone option. built-in microphone + internal('INTERNAL'), + + /// Microphone option. external microphone + external('EXTERNAL'); + + final String rawValue; + + const MicrophoneOptionEnum(this.rawValue); + + @override + String toString() { + return rawValue; + } + + static MicrophoneOptionEnum? getValue(String rawValue) { + return MicrophoneOptionEnum.values.cast().firstWhere( + (element) => element?.rawValue == rawValue, + orElse: () => null); + } +} + +/// Camera error +enum CameraErrorEnum { + /// Camera error + /// Undefined value + unknown('UNKNOWN'), + + /// Camera error + /// Insufficient memory + noMemory('NO_MEMORY'), + + /// Camera error + /// Maximum file number exceeded + fileNumberOver('FILE_NUMBER_OVER'), + + /// Camera error + /// Camera clock not set + noDateSetting('NO_DATE_SETTING'), + + /// Camera error + /// Includes when the card is removed + readError('READ_ERROR'), + + /// Camera error + /// Unsupported media (SDHC, etc.) + notSupportedMediaType('NOT_SUPPORTED_MEDIA_TYPE'), + + /// Camera error + /// FAT32, etc. + notSupportedFileSystem('NOT_SUPPORTED_FILE_SYSTEM'), + + /// Camera error + /// Error warning while mounting + mediaNotReady('MEDIA_NOT_READY'), + + /// Camera error + /// Battery level warning (firmware update) + notEnoughBattery('NOT_ENOUGH_BATTERY'), + + /// Camera error + /// Firmware file mismatch warning + invalidFile('INVALID_FILE'), + + /// Camera error + /// Plugin start warning (IoT technical standards compliance) + pluginBootError('PLUGIN_BOOT_ERROR'), + + /// Camera error + /// When performing continuous shooting by operating the camera while executing , + /// , or with the WebAPI or MTP. + inProgressError('IN_PROGRESS_ERROR'), + + /// Camera error + /// Battery inserted + WLAN ON + Video mode + 4K 60fps / 5.7K 10fps / 5.7K 15fps / 5.7K 30fps / 8K 10fps + cannotRecording('CANNOT_RECORDING'), + + /// Camera error + /// Battery inserted AND Specified battery level or lower + WLAN ON + Video mode + 4K 30fps + cannotRecordLowbat('CANNOT_RECORD_LOWBAT'), + + /// Camera error + /// Shooting hardware failure + captureHwFailed('CAPTURE_HW_FAILED'), + + /// Camera error + /// Software error + captureSwFailed('CAPTURE_SW_FAILED'), + + /// Camera error + /// Internal memory access error + internalMemAccessFail('INTERNAL_MEM_ACCESS_FAIL'), + + /// Camera error + /// Undefined error + unexpectedError('UNEXPECTED_ERROR'), + + /// Camera error + /// Charging error + batteryChargeFail('BATTERY_CHARGE_FAIL'), + + /// Camera error + /// (Board) temperature warning + highTemperatureWarning('HIGH_TEMPERATURE_WARNING'), + + /// Camera error + /// (Board) temperature error + highTemperature('HIGH_TEMPERATURE'), + + /// Camera error + /// Battery temperature error + batteryHighTemperature('BATTERY_HIGH_TEMPERATURE'), + + /// Camera error + /// Electronic compass error + compassCalibration('COMPASS_CALIBRATION'); + + final String rawValue; + + const CameraErrorEnum(this.rawValue); + + @override + String toString() { + return rawValue; + } + + static CameraErrorEnum? getValue(String rawValue) { + return CameraErrorEnum.values.cast().firstWhere( + (element) => element?.rawValue == rawValue, + orElse: () => null); + } +} diff --git a/flutter/lib/theta_client_flutter.dart b/flutter/lib/theta_client_flutter.dart index c539f84d54..54a6352ea5 100644 --- a/flutter/lib/theta_client_flutter.dart +++ b/flutter/lib/theta_client_flutter.dart @@ -5,13 +5,14 @@ import 'package:theta_client_flutter/digest_auth.dart'; import 'capture/capture_builder.dart'; import 'options/importer.dart'; -import 'state/theta_state.dart'; +import 'state/importer.dart'; import 'theta_client_flutter_platform_interface.dart'; export 'capture/capture.dart'; export 'capture/capture_builder.dart'; export 'capture/capturing.dart'; export 'options/importer.dart'; +export 'state/importer.dart'; /// Handle Theta web APIs. class ThetaClientFlutter { @@ -1086,246 +1087,6 @@ enum BurstOrderEnum { } } -/// Capture Status -enum CaptureStatusEnum { - /// Capture status. Performing continuously shoot - shooting('SHOOTING'), - - /// Capture status. In standby - idle('IDLE'), - - /// Capture status. Self-timer is operating - selfTimerCountdown('SELF_TIMER_COUNTDOWN'), - - /// Capture status. Performing multi bracket shooting - bracketShooting('BRACKET_SHOOTING'), - - /// Capture status. Converting post file... - converting('CONVERTING'), - - /// Capture status. Performing timeShift shooting - timeShiftShooting('TIME_SHIFT_SHOOTING'), - - /// Capture status. Performing continuous shooting - continuousShooting('CONTINUOUS_SHOOTING'), - - /// Capture status. Waiting for retrospective video... - retrospectiveImageRecording('RETROSPECTIVE_IMAGE_RECORDING'); - - final String rawValue; - - const CaptureStatusEnum(this.rawValue); - - @override - String toString() { - return rawValue; - } - - static CaptureStatusEnum? getValue(String rawValue) { - return CaptureStatusEnum.values.cast().firstWhere( - (element) => element?.rawValue == rawValue, - orElse: () => null); - } -} - -/// Battery charging state -enum ChargingStateEnum { - /// Battery charging state. Charging - charging('CHARGING'), - - /// Battery charging state. Charging completed - completed('COMPLETED'), - - /// Battery charging state. Not charging - notCharging('NOT_CHARGING'); - - final String rawValue; - - const ChargingStateEnum(this.rawValue); - - @override - String toString() { - return rawValue; - } - - static ChargingStateEnum? getValue(String rawValue) { - return ChargingStateEnum.values.cast().firstWhere( - (element) => element?.rawValue == rawValue, - orElse: () => null); - } -} - -/// Shooting function. -/// Shooting settings are retained separately for both the Still image shooting mode and Video shooting mode. -/// Setting them at the same time as exposureDelay will result in an error. -/// -/// For -/// - RICOH THETA X -/// - RICOH THETA Z1 -enum ShootingFunctionEnum { - /// Normal shooting function - normal('NORMAL'), - - /// Self-timer shooting function(RICOH THETA X is not supported.) - selfTimer('SELF_TIMER'), - - /// My setting shooting function - mySetting('MY_SETTING'); - - final String rawValue; - - const ShootingFunctionEnum(this.rawValue); - - @override - String toString() { - return rawValue; - } - - static ShootingFunctionEnum? getValue(String rawValue) { - return ShootingFunctionEnum.values.cast().firstWhere( - (element) => element?.rawValue == rawValue, - orElse: () => null); - } -} - -/// Microphone option -enum MicrophoneOptionEnum { - /// Microphone option. auto - auto('AUTO'), - - /// Microphone option. built-in microphone - internal('INTERNAL'), - - /// Microphone option. external microphone - external('EXTERNAL'); - - final String rawValue; - - const MicrophoneOptionEnum(this.rawValue); - - @override - String toString() { - return rawValue; - } - - static MicrophoneOptionEnum? getValue(String rawValue) { - return MicrophoneOptionEnum.values.cast().firstWhere( - (element) => element?.rawValue == rawValue, - orElse: () => null); - } -} - -/// Camera error -enum CameraErrorEnum { - /// Camera error - /// Undefined value - unknown('UNKNOWN'), - - /// Camera error - /// Insufficient memory - noMemory('NO_MEMORY'), - - /// Camera error - /// Maximum file number exceeded - fileNumberOver('FILE_NUMBER_OVER'), - - /// Camera error - /// Camera clock not set - noDateSetting('NO_DATE_SETTING'), - - /// Camera error - /// Includes when the card is removed - readError('READ_ERROR'), - - /// Camera error - /// Unsupported media (SDHC, etc.) - notSupportedMediaType('NOT_SUPPORTED_MEDIA_TYPE'), - - /// Camera error - /// FAT32, etc. - notSupportedFileSystem('NOT_SUPPORTED_FILE_SYSTEM'), - - /// Camera error - /// Error warning while mounting - mediaNotReady('MEDIA_NOT_READY'), - - /// Camera error - /// Battery level warning (firmware update) - notEnoughBattery('NOT_ENOUGH_BATTERY'), - - /// Camera error - /// Firmware file mismatch warning - invalidFile('INVALID_FILE'), - - /// Camera error - /// Plugin start warning (IoT technical standards compliance) - pluginBootError('PLUGIN_BOOT_ERROR'), - - /// Camera error - /// When performing continuous shooting by operating the camera while executing , - /// , or with the WebAPI or MTP. - inProgressError('IN_PROGRESS_ERROR'), - - /// Camera error - /// Battery inserted + WLAN ON + Video mode + 4K 60fps / 5.7K 10fps / 5.7K 15fps / 5.7K 30fps / 8K 10fps - cannotRecording('CANNOT_RECORDING'), - - /// Camera error - /// Battery inserted AND Specified battery level or lower + WLAN ON + Video mode + 4K 30fps - cannotRecordLowbat('CANNOT_RECORD_LOWBAT'), - - /// Camera error - /// Shooting hardware failure - captureHwFailed('CAPTURE_HW_FAILED'), - - /// Camera error - /// Software error - captureSwFailed('CAPTURE_SW_FAILED'), - - /// Camera error - /// Internal memory access error - internalMemAccessFail('INTERNAL_MEM_ACCESS_FAIL'), - - /// Camera error - /// Undefined error - unexpectedError('UNEXPECTED_ERROR'), - - /// Camera error - /// Charging error - batteryChargeFail('BATTERY_CHARGE_FAIL'), - - /// Camera error - /// (Board) temperature warning - highTemperatureWarning('HIGH_TEMPERATURE_WARNING'), - - /// Camera error - /// (Board) temperature error - highTemperature('HIGH_TEMPERATURE'), - - /// Camera error - /// Battery temperature error - batteryHighTemperature('BATTERY_HIGH_TEMPERATURE'), - - /// Camera error - /// Electronic compass error - compassCalibration('COMPASS_CALIBRATION'); - - final String rawValue; - - const CameraErrorEnum(this.rawValue); - - @override - String toString() { - return rawValue; - } - - static CameraErrorEnum? getValue(String rawValue) { - return CameraErrorEnum.values.cast().firstWhere( - (element) => element?.rawValue == rawValue, - orElse: () => null); - } -} - /// Exif metadata of a still image. class Exif { /// EXIF Support version diff --git a/flutter/lib/theta_client_flutter_method_channel.dart b/flutter/lib/theta_client_flutter_method_channel.dart index e288d238fb..ccd0760e08 100644 --- a/flutter/lib/theta_client_flutter_method_channel.dart +++ b/flutter/lib/theta_client_flutter_method_channel.dart @@ -5,23 +5,31 @@ import 'package:flutter/services.dart'; import 'package:theta_client_flutter/theta_client_flutter.dart'; import 'package:theta_client_flutter/utils/convert_utils.dart'; -import 'state/theta_state.dart'; import 'theta_client_flutter_platform_interface.dart'; const notifyIdLivePreview = 10001; const notifyIdTimeShiftProgress = 10011; const notifyIdTimeShiftStopError = 10012; -const notifyIdVideoCaptureStopError = 10003; +const notifyIdTimeShiftCapturing = 10013; const notifyIdLimitlessIntervalCaptureStopError = 10004; +const notifyIdLimitlessIntervalCaptureCapturing = 10005; const notifyIdShotCountSpecifiedIntervalCaptureProgress = 10021; const notifyIdShotCountSpecifiedIntervalCaptureStopError = 10022; +const notifyIdShotCountSpecifiedIntervalCaptureCapturing = 10023; const notifyIdCompositeIntervalCaptureProgress = 10031; const notifyIdCompositeIntervalCaptureStopError = 10032; +const notifyIdCompositeIntervalCaptureCapturing = 10033; const notifyIdMultiBracketCaptureProgress = 10041; const notifyIdMultiBracketCaptureStopError = 10042; +const notifyIdMultiBracketCaptureCapturing = 10043; const notifyIdBurstCaptureProgress = 10051; const notifyIdBurstCaptureStopError = 10052; +const notifyIdBurstCaptureCapturing = 10053; const notifyIdContinuousCaptureProgress = 10061; +const notifyIdContinuousCaptureCapturing = 10062; +const notifyIdPhotoCapturing = 10071; +const notifyIdVideoCaptureStopError = 10081; +const notifyIdVideoCaptureCapturing = 10082; /// An implementation of [ThetaClientFlutterPlatform] that uses method channels. class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { @@ -235,14 +243,38 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } @override - Future buildPhotoCapture(Map options) async { + Future buildPhotoCapture(Map options, int interval) async { + final params = ConvertUtils.convertCaptureParams(options); + params['_capture_interval'] = interval; return methodChannel.invokeMethod( - 'buildPhotoCapture', ConvertUtils.convertCaptureParams(options)); + 'buildPhotoCapture', params); } @override - Future takePicture() async { - return methodChannel.invokeMethod('takePicture'); + Future takePicture( + void Function(CapturingStatusEnum status)? onCapturing) async { + var completer = Completer(); + try { + enableNotifyEventReceiver(); + if (onCapturing != null) { + addNotify(notifyIdPhotoCapturing, (params) { + final strStatus = params?['status'] as String?; + if (strStatus != null) { + final status = CapturingStatusEnum.getValue(strStatus); + if (status != null) { + onCapturing(status); + } + } + }); + } + final fileUrl = await methodChannel.invokeMethod('takePicture'); + removeNotify(notifyIdPhotoCapturing); + completer.complete(fileUrl); + } catch (e) { + removeNotify(notifyIdPhotoCapturing); + completer.completeError(e); + } + return completer.future; } @override @@ -260,7 +292,8 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { @override Future startTimeShiftCapture(void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) async { + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) async { var completer = Completer(); try { enableNotifyEventReceiver(); @@ -280,14 +313,27 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } }); } + if (onCapturing != null) { + addNotify(notifyIdTimeShiftCapturing, (params) { + final strStatus = params?['status'] as String?; + if (strStatus != null) { + final status = CapturingStatusEnum.getValue(strStatus); + if (status != null) { + onCapturing(status); + } + } + }); + } final fileUrl = await methodChannel.invokeMethod('startTimeShiftCapture'); removeNotify(notifyIdTimeShiftProgress); removeNotify(notifyIdTimeShiftStopError); + removeNotify(notifyIdTimeShiftCapturing); completer.complete(fileUrl); } catch (e) { removeNotify(notifyIdTimeShiftProgress); removeNotify(notifyIdTimeShiftStopError); + removeNotify(notifyIdTimeShiftCapturing); completer.completeError(e); } return completer.future; @@ -304,14 +350,17 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } @override - Future buildVideoCapture(Map options) async { - return methodChannel.invokeMethod( - 'buildVideoCapture', ConvertUtils.convertCaptureParams(options)); + Future buildVideoCapture( + Map options, int interval) async { + final params = ConvertUtils.convertCaptureParams(options); + params['_capture_interval'] = interval; + return methodChannel.invokeMethod('buildVideoCapture', params); } @override Future startVideoCapture( - void Function(Exception exception)? onStopFailed) async { + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) async { var completer = Completer(); try { enableNotifyEventReceiver(); @@ -323,12 +372,25 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } }); } + if (onCapturing != null) { + addNotify(notifyIdVideoCaptureCapturing, (params) { + final strStatus = params?['status'] as String?; + if (strStatus != null) { + final status = CapturingStatusEnum.getValue(strStatus); + if (status != null) { + onCapturing(status); + } + } + }); + } final fileUrl = await methodChannel.invokeMethod('startVideoCapture'); removeNotify(notifyIdVideoCaptureStopError); + removeNotify(notifyIdVideoCaptureCapturing); completer.complete(fileUrl); } catch (e) { removeNotify(notifyIdVideoCaptureStopError); + removeNotify(notifyIdVideoCaptureCapturing); completer.completeError(e); } return completer.future; @@ -346,15 +408,18 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } @override - Future buildLimitlessIntervalCapture( - Map options) async { - return methodChannel.invokeMethod('buildLimitlessIntervalCapture', - ConvertUtils.convertCaptureParams(options)); + Future buildLimitlessIntervalCapture(Map options, + int interval) async { + final params = ConvertUtils.convertCaptureParams(options); + params['_capture_interval'] = interval; + return methodChannel.invokeMethod( + 'buildLimitlessIntervalCapture', params); } @override Future?> startLimitlessIntervalCapture( - void Function(Exception exception)? onStopFailed) async { + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) async { var completer = Completer?>(); try { enableNotifyEventReceiver(); @@ -366,9 +431,21 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } }); } + if (onCapturing != null) { + addNotify(notifyIdLimitlessIntervalCaptureCapturing, (params) { + final strStatus = params?['status'] as String?; + if (strStatus != null) { + final status = CapturingStatusEnum.getValue(strStatus); + if (status != null) { + onCapturing(status); + } + } + }); + } final fileUrls = await methodChannel .invokeMethod?>('startLimitlessIntervalCapture'); removeNotify(notifyIdLimitlessIntervalCaptureStopError); + removeNotify(notifyIdLimitlessIntervalCaptureCapturing); if (fileUrls == null) { completer.complete(null); } else { @@ -376,6 +453,7 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } } catch (e) { removeNotify(notifyIdLimitlessIntervalCaptureStopError); + removeNotify(notifyIdLimitlessIntervalCaptureCapturing); completer.completeError(e); } return completer.future; @@ -405,7 +483,8 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { @override Future?> startShotCountSpecifiedIntervalCapture( void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) async { + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) async { var completer = Completer?>(); try { enableNotifyEventReceiver(); @@ -425,10 +504,22 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } }); } + if (onCapturing != null) { + addNotify(notifyIdShotCountSpecifiedIntervalCaptureCapturing, (params) { + final strStatus = params?['status'] as String?; + if (strStatus != null) { + final status = CapturingStatusEnum.getValue(strStatus); + if (status != null) { + onCapturing(status); + } + } + }); + } final fileUrls = await methodChannel.invokeMethod?>( 'startShotCountSpecifiedIntervalCapture'); removeNotify(notifyIdShotCountSpecifiedIntervalCaptureProgress); removeNotify(notifyIdShotCountSpecifiedIntervalCaptureStopError); + removeNotify(notifyIdShotCountSpecifiedIntervalCaptureCapturing); if (fileUrls == null) { completer.complete(null); } else { @@ -437,6 +528,7 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } catch (e) { removeNotify(notifyIdShotCountSpecifiedIntervalCaptureProgress); removeNotify(notifyIdShotCountSpecifiedIntervalCaptureStopError); + removeNotify(notifyIdShotCountSpecifiedIntervalCaptureCapturing); completer.completeError(e); } return completer.future; @@ -466,7 +558,8 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { @override Future?> startCompositeIntervalCapture( void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) async { + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) async { var completer = Completer?>(); try { enableNotifyEventReceiver(); @@ -486,10 +579,22 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } }); } + if (onCapturing != null) { + addNotify(notifyIdCompositeIntervalCaptureCapturing, (params) { + final strStatus = params?['status'] as String?; + if (strStatus != null) { + final status = CapturingStatusEnum.getValue(strStatus); + if (status != null) { + onCapturing(status); + } + } + }); + } final fileUrls = await methodChannel .invokeMethod?>('startCompositeIntervalCapture'); removeNotify(notifyIdCompositeIntervalCaptureProgress); removeNotify(notifyIdCompositeIntervalCaptureStopError); + removeNotify(notifyIdCompositeIntervalCaptureCapturing); if (fileUrls == null) { completer.complete(null); } else { @@ -498,6 +603,7 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } catch (e) { removeNotify(notifyIdCompositeIntervalCaptureProgress); removeNotify(notifyIdCompositeIntervalCaptureStopError); + removeNotify(notifyIdCompositeIntervalCaptureCapturing); completer.completeError(e); } return completer.future; @@ -536,8 +642,10 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } @override - Future?> startBurstCapture(void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) async { + Future?> startBurstCapture( + void Function(double)? onProgress, + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) async { var completer = Completer?>(); try { enableNotifyEventReceiver(); @@ -557,10 +665,22 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } }); } + if (onCapturing != null) { + addNotify(notifyIdBurstCaptureCapturing, (params) { + final strStatus = params?['status'] as String?; + if (strStatus != null) { + final status = CapturingStatusEnum.getValue(strStatus); + if (status != null) { + onCapturing(status); + } + } + }); + } final fileUrls = await methodChannel.invokeMethod?>('startBurstCapture'); removeNotify(notifyIdBurstCaptureProgress); removeNotify(notifyIdBurstCaptureStopError); + removeNotify(notifyIdBurstCaptureCapturing); if (fileUrls == null) { completer.complete(null); } else { @@ -569,6 +689,7 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } catch (e) { removeNotify(notifyIdBurstCaptureProgress); removeNotify(notifyIdBurstCaptureStopError); + removeNotify(notifyIdBurstCaptureCapturing); completer.completeError(e); } return completer.future; @@ -595,7 +716,8 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { @override Future?> startMultiBracketCapture( void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) async { + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) async { var completer = Completer?>(); try { enableNotifyEventReceiver(); @@ -615,10 +737,22 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } }); } + if (onCapturing != null) { + addNotify(notifyIdMultiBracketCaptureCapturing, (params) { + final strStatus = params?['status'] as String?; + if (strStatus != null) { + final status = CapturingStatusEnum.getValue(strStatus); + if (status != null) { + onCapturing(status); + } + } + }); + } final fileUrls = await methodChannel .invokeMethod?>('startMultiBracketCapture'); removeNotify(notifyIdMultiBracketCaptureProgress); removeNotify(notifyIdMultiBracketCaptureStopError); + removeNotify(notifyIdMultiBracketCaptureCapturing); if (fileUrls == null) { completer.complete(null); } else { @@ -627,6 +761,7 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } catch (e) { removeNotify(notifyIdMultiBracketCaptureProgress); removeNotify(notifyIdMultiBracketCaptureStopError); + removeNotify(notifyIdMultiBracketCaptureCapturing); completer.completeError(e); } return completer.future; @@ -652,7 +787,8 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { @override Future?> startContinuousCapture( - void Function(double)? onProgress) async { + void Function(double)? onProgress, + void Function(CapturingStatusEnum status)? onCapturing) async { var completer = Completer?>(); try { enableNotifyEventReceiver(); @@ -664,9 +800,21 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } }); } + if (onCapturing != null) { + addNotify(notifyIdContinuousCaptureCapturing, (params) { + final strStatus = params?['status'] as String?; + if (strStatus != null) { + final status = CapturingStatusEnum.getValue(strStatus); + if (status != null) { + onCapturing(status); + } + } + }); + } final fileUrls = await methodChannel .invokeMethod?>('startContinuousCapture'); removeNotify(notifyIdContinuousCaptureProgress); + removeNotify(notifyIdContinuousCaptureCapturing); if (fileUrls == null) { completer.complete(null); } else { @@ -674,6 +822,7 @@ class MethodChannelThetaClientFlutter extends ThetaClientFlutterPlatform { } } catch (e) { removeNotify(notifyIdContinuousCaptureProgress); + removeNotify(notifyIdContinuousCaptureCapturing); completer.completeError(e); } return completer.future; diff --git a/flutter/lib/theta_client_flutter_platform_interface.dart b/flutter/lib/theta_client_flutter_platform_interface.dart index a652b02eb4..382f7e7d66 100644 --- a/flutter/lib/theta_client_flutter_platform_interface.dart +++ b/flutter/lib/theta_client_flutter_platform_interface.dart @@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:theta_client_flutter/theta_client_flutter.dart'; -import 'state/theta_state.dart'; import 'theta_client_flutter_method_channel.dart'; abstract class ThetaClientFlutterPlatform extends PlatformInterface { @@ -93,11 +92,12 @@ abstract class ThetaClientFlutterPlatform extends PlatformInterface { 'getPhotoCaptureBuilder() has not been implemented.'); } - Future buildPhotoCapture(Map options) { + Future buildPhotoCapture(Map options, int interval) { throw UnimplementedError('buildPhotoCapture() has not been implemented.'); } - Future takePicture() { + Future takePicture( + void Function(CapturingStatusEnum status)? onCapturing) { throw UnimplementedError('takePicture() has not been implemented.'); } @@ -113,7 +113,8 @@ abstract class ThetaClientFlutterPlatform extends PlatformInterface { } Future startTimeShiftCapture(void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) { + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { throw UnimplementedError( 'startTimeShiftCapture() has not been implemented.'); } @@ -128,12 +129,13 @@ abstract class ThetaClientFlutterPlatform extends PlatformInterface { 'getVideoCaptureBuilder() has not been implemented.'); } - Future buildVideoCapture(Map options) { + Future buildVideoCapture(Map options, int interval) { throw UnimplementedError('buildVideoCapture() has not been implemented.'); } Future startVideoCapture( - void Function(Exception exception)? onStopFailed) { + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { throw UnimplementedError('startVideoCapture() has not been implemented.'); } @@ -146,13 +148,15 @@ abstract class ThetaClientFlutterPlatform extends PlatformInterface { 'getLimitlessIntervalCaptureBuilder() has not been implemented.'); } - Future buildLimitlessIntervalCapture(Map options) { + Future buildLimitlessIntervalCapture(Map options, + int interval) { throw UnimplementedError( 'buildLimitlessIntervalCapture() has not been implemented.'); } Future?> startLimitlessIntervalCapture( - void Function(Exception exception)? onStopFailed) { + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { throw UnimplementedError( 'startLimitlessIntervalCapture() has not been implemented.'); } @@ -175,7 +179,8 @@ abstract class ThetaClientFlutterPlatform extends PlatformInterface { Future?> startShotCountSpecifiedIntervalCapture( void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) { + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { throw UnimplementedError( 'startShotCountSpecifiedIntervalCapture() has not been implemented.'); } @@ -198,7 +203,8 @@ abstract class ThetaClientFlutterPlatform extends PlatformInterface { Future?> startCompositeIntervalCapture( void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) { + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { throw UnimplementedError( 'startCompositeIntervalCapture() has not been implemented.'); } @@ -223,8 +229,10 @@ abstract class ThetaClientFlutterPlatform extends PlatformInterface { throw UnimplementedError('buildBurstCapture() has not been implemented.'); } - Future?> startBurstCapture(void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) { + Future?> startBurstCapture( + void Function(double)? onProgress, + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { throw UnimplementedError('startBurstCapture() has not been implemented.'); } @@ -245,7 +253,8 @@ abstract class ThetaClientFlutterPlatform extends PlatformInterface { Future?> startMultiBracketCapture( void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) { + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { throw UnimplementedError( 'startMultiBracketCapture() has not been implemented.'); } @@ -267,7 +276,8 @@ abstract class ThetaClientFlutterPlatform extends PlatformInterface { } Future?> startContinuousCapture( - void Function(double)? onProgress) { + void Function(double)? onProgress, + void Function(CapturingStatusEnum status)? onCapturing) { throw UnimplementedError( 'startContinuousCapture() has not been implemented.'); } diff --git a/flutter/lib/utils/convert_utils.dart b/flutter/lib/utils/convert_utils.dart index 25dd59749c..728255f0f8 100644 --- a/flutter/lib/utils/convert_utils.dart +++ b/flutter/lib/utils/convert_utils.dart @@ -1,9 +1,6 @@ import 'package:theta_client_flutter/digest_auth.dart'; import 'package:theta_client_flutter/theta_client_flutter.dart'; -import '../state/state_gps_info.dart'; -import '../state/theta_state.dart'; - class ConvertUtils { static List? convertAutoBracketOption(List? data) { if (data == null) { diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 6bc79646e3..ce8f66a9ca 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: theta_client_flutter description: THETA Client Flutter plugin project. -version: 1.9.1 +version: 1.10.0 homepage: environment: diff --git a/flutter/test/capture/burst_capture_method_channel_test.dart b/flutter/test/capture/burst_capture_method_channel_test.dart index 5ed193b6c4..d7d83c0191 100644 --- a/flutter/test/capture/burst_capture_method_channel_test.dart +++ b/flutter/test/capture/burst_capture_method_channel_test.dart @@ -79,7 +79,7 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrls; }); - expect(await platform.startBurstCapture(null, null), fileUrls); + expect(await platform.startBurstCapture(null, null, null), fileUrls); }); test('startBurstCapture no file', () async { @@ -88,7 +88,7 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrls; }); - expect(await platform.startBurstCapture(null, null), null); + expect(await platform.startBurstCapture(null, null, null), null); }); test('startBurstCapture exception', () async { @@ -97,7 +97,7 @@ void main() { throw Exception('test error'); }); try { - await platform.startBurstCapture(null, null); + await platform.startBurstCapture(null, null, null); expect(true, false, reason: 'not exception'); } catch (error) { expect(error.toString().contains('test error'), true); @@ -135,7 +135,7 @@ void main() { int progressCount = 0; var resultCapture = platform.startBurstCapture((completion) { progressCount++; - }, null); + }, null, null); var result = await resultCapture.timeout(const Duration(seconds: 5)); expect(result, fileUrls); expect(progressCount, 2); @@ -166,11 +166,43 @@ void main() { var isOnStopFailed = false; var resultCapture = platform.startBurstCapture(null, (exception) { isOnStopFailed = true; - }); + }, null); var result = await resultCapture.timeout(const Duration(seconds: 5)); expect(result, fileUrls); expect(platform.notifyList.containsKey(10052), false, reason: 'remove notify stop error'); expect(isOnStopFailed, true); }); + + test('call onCapturing', () async { + const fileUrls = [ + 'http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0013336.MP4' + ]; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + expect(platform.notifyList.containsKey(10053), true, + reason: 'add notify capturing status'); + + await Future.delayed(const Duration(milliseconds: 1)); + // native event + platform.onNotify({ + 'id': 10053, + 'params': { + 'status': 'SELF_TIMER_COUNTDOWN', + }, + }); + return fileUrls; + }); + + CapturingStatusEnum? lastStatus; + var resultCapture = + platform.startBurstCapture(null, (exception) {}, (status) { + lastStatus = status; + }); + var result = await resultCapture.timeout(const Duration(seconds: 5)); + expect(result, fileUrls); + expect(platform.notifyList.containsKey(10052), false, + reason: 'remove notify capturing status'); + expect(lastStatus, CapturingStatusEnum.selfTimerCountdown); + }); } diff --git a/flutter/test/capture/burst_capture_test.dart b/flutter/test/capture/burst_capture_test.dart index e55167b0e8..901d1fad5f 100644 --- a/flutter/test/capture/burst_capture_test.dart +++ b/flutter/test/capture/burst_capture_test.dart @@ -122,7 +122,7 @@ void main() { const imageUrls = ['http://test.jpeg']; - onCallStartBurstCapture = (onProgress, onStopFailed) { + onCallStartBurstCapture = (onProgress, onStopFailed, onCapturing) { return Future.value(imageUrls); }; @@ -157,7 +157,7 @@ void main() { ThetaClientFlutterPlatform.instance = fakePlatform; var completer = Completer>(); - onCallStartBurstCapture = (onProgress, onStopFailed) { + onCallStartBurstCapture = (onProgress, onStopFailed, onCapturing) { return completer.future; }; onCallStopBurstCapture = () { @@ -195,7 +195,7 @@ void main() { const imageUrls = ['http://test.jpeg']; var completer = Completer>(); - onCallStartBurstCapture = (onProgress, onStopFailed) { + onCallStartBurstCapture = (onProgress, onStopFailed, onCapturing) { return completer.future; }; onCallStopBurstCapture = () { @@ -237,7 +237,7 @@ void main() { void Function(Exception exception)? paramStopFailed; - onCallStartBurstCapture = (onProgress, onStopFailed) { + onCallStartBurstCapture = (onProgress, onStopFailed, onCapturing) { paramStopFailed = onStopFailed; return Completer>().future; }; @@ -282,7 +282,7 @@ void main() { void Function(double completion)? paramOnProgress; - onCallStartBurstCapture = (onProgress, onStopFailed) { + onCallStartBurstCapture = (onProgress, onStopFailed, onCapturing) { paramOnProgress = onProgress; return Completer>().future; }; @@ -307,4 +307,45 @@ void main() { await completer.future.timeout(const Duration(milliseconds: 10)); expect(isOnProgress, true); }); + + test('call onCapturing', () async { + ThetaClientFlutter thetaClientPlugin = ThetaClientFlutter(); + MockThetaClientFlutterPlatform fakePlatform = + MockThetaClientFlutterPlatform(); + ThetaClientFlutterPlatform.instance = fakePlatform; + + var completer = Completer(); + + void Function(CapturingStatusEnum status)? paramOnCapturing; + + onCallStartBurstCapture = (onProgress, onStopFailed, onCapturing) { + paramOnCapturing = onCapturing; + return Completer>().future; + }; + + final builder = thetaClientPlugin.getBurstCaptureBuilder( + burstCaptureNum, + burstBracketStep, + burstCompensation, + burstMaxExposureTime, + burstEnableIsoControl, + burstOrder); + var capture = await builder.build(); + var isOnCapturing = false; + capture.startCapture( + (fileUrl) { + expect(false, isTrue, reason: 'startCapture'); + }, + (completion) {}, + (exception) {}, + onCapturing: (status) { + isOnCapturing = true; + expect(status, CapturingStatusEnum.capturing); + completer.complete(null); + }); + + paramOnCapturing?.call(CapturingStatusEnum.capturing); + await completer.future.timeout(const Duration(milliseconds: 10)); + expect(isOnCapturing, true); + }); } diff --git a/flutter/test/capture/composite_interval_capture_method_channel_test.dart b/flutter/test/capture/composite_interval_capture_method_channel_test.dart index cc9203143e..eca64178de 100644 --- a/flutter/test/capture/composite_interval_capture_method_channel_test.dart +++ b/flutter/test/capture/composite_interval_capture_method_channel_test.dart @@ -79,7 +79,8 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrls; }); - expect(await platform.startCompositeIntervalCapture(null, null), fileUrls); + expect(await platform.startCompositeIntervalCapture(null, null, null), + fileUrls); }); test('startCompositeIntervalCapture no file', () async { @@ -88,7 +89,8 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrls; }); - expect(await platform.startCompositeIntervalCapture(null, null), null); + expect( + await platform.startCompositeIntervalCapture(null, null, null), null); }); test('startCompositeIntervalCapture exception', () async { @@ -97,7 +99,7 @@ void main() { throw Exception('test error'); }); try { - await platform.startCompositeIntervalCapture(null, null); + await platform.startCompositeIntervalCapture(null, null, null); expect(true, false, reason: 'not exception'); } catch (error) { expect(error.toString().contains('test error'), true); @@ -135,7 +137,7 @@ void main() { int progressCount = 0; var resultCapture = platform.startCompositeIntervalCapture((completion) { progressCount++; - }, null); + }, null, null); var result = await resultCapture.timeout(const Duration(seconds: 5)); expect(result, fileUrls); expect(progressCount, 2); @@ -165,13 +167,45 @@ void main() { var isOnStopFailed = false; var resultCapture = - platform.startCompositeIntervalCapture(null, (exception) { + platform.startCompositeIntervalCapture(null, (exception) { isOnStopFailed = true; - }); + }, null); var result = await resultCapture.timeout(const Duration(seconds: 5)); expect(result, fileUrls); expect(platform.notifyList.containsKey(10032), false, reason: 'remove notify stop error'); expect(isOnStopFailed, true); }); + + test('call onCapturing', () async { + const fileUrls = [ + 'http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0013336.MP4' + ]; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + expect(platform.notifyList.containsKey(10033), true, + reason: 'add notify capturing status'); + + await Future.delayed(const Duration(milliseconds: 1)); + // native event + platform.onNotify({ + 'id': 10033, + 'params': { + 'status': 'SELF_TIMER_COUNTDOWN', + }, + }); + return fileUrls; + }); + + CapturingStatusEnum? lastStatus; + var resultCapture = + platform.startCompositeIntervalCapture(null, (exception) {}, (status) { + lastStatus = status; + }); + var result = await resultCapture.timeout(const Duration(seconds: 5)); + expect(result, fileUrls); + expect(platform.notifyList.containsKey(10032), false, + reason: 'remove notify capturing status'); + expect(lastStatus, CapturingStatusEnum.selfTimerCountdown); + }); } diff --git a/flutter/test/capture/composite_interval_capture_test.dart b/flutter/test/capture/composite_interval_capture_test.dart index 196aedbd67..d10b43259d 100644 --- a/flutter/test/capture/composite_interval_capture_test.dart +++ b/flutter/test/capture/composite_interval_capture_test.dart @@ -109,7 +109,8 @@ void main() { const shootingTimeSec = 600; const imageUrls = ['http://test.jpeg']; - onCallStartCompositeIntervalCapture = (onProgress, onStopFailed) { + onCallStartCompositeIntervalCapture = + (onProgress, onStopFailed, onCapturing) { return Future.value(imageUrls); }; @@ -139,7 +140,8 @@ void main() { ThetaClientFlutterPlatform.instance = fakePlatform; var completer = Completer>(); - onCallStartCompositeIntervalCapture = (onProgress, onStopFailed) { + onCallStartCompositeIntervalCapture = + (onProgress, onStopFailed, onCapturing) { return completer.future; }; onCallStopCompositeIntervalCapture = () { @@ -175,7 +177,8 @@ void main() { const imageUrls = ['http://test.jpeg']; var completer = Completer>(); - onCallStartCompositeIntervalCapture = (onProgress, onStopFailed) { + onCallStartCompositeIntervalCapture = + (onProgress, onStopFailed, onCapturing) { return completer.future; }; onCallStopCompositeIntervalCapture = () { @@ -212,7 +215,8 @@ void main() { void Function(Exception exception)? paramStopFailed; - onCallStartCompositeIntervalCapture = (onProgress, onStopFailed) { + onCallStartCompositeIntervalCapture = + (onProgress, onStopFailed, onCapturing) { paramStopFailed = onStopFailed; return Completer>().future; }; @@ -253,7 +257,8 @@ void main() { void Function(double completion)? paramOnProgress; - onCallStartCompositeIntervalCapture = (onProgress, onStopFailed) { + onCallStartCompositeIntervalCapture = + (onProgress, onStopFailed, onCapturing) { paramOnProgress = onProgress; return Completer>().future; }; @@ -274,4 +279,39 @@ void main() { await completer.future.timeout(const Duration(milliseconds: 10)); expect(isOnProgress, true); }); + + test('call onCapturing', () async { + ThetaClientFlutter thetaClientPlugin = ThetaClientFlutter(); + MockThetaClientFlutterPlatform fakePlatform = + MockThetaClientFlutterPlatform(); + ThetaClientFlutterPlatform.instance = fakePlatform; + + var completer = Completer(); + + void Function(CapturingStatusEnum status)? paramOnCapturing; + + onCallStartCompositeIntervalCapture = + (onProgress, onStopFailed, onCapturing) { + paramOnCapturing = onCapturing; + return Completer>().future; + }; + + const shootingTimeSec = 600; + var builder = + thetaClientPlugin.getCompositeIntervalCaptureBuilder(shootingTimeSec); + var capture = await builder.build(); + var isOnCapturing = false; + capture.startCapture((fileUrl) { + expect(false, isTrue, reason: 'startCapture'); + }, (completion) {}, (exception) {}, + onCapturing: (status) { + isOnCapturing = true; + expect(status, CapturingStatusEnum.capturing); + completer.complete(null); + }); + + paramOnCapturing?.call(CapturingStatusEnum.capturing); + await completer.future.timeout(const Duration(milliseconds: 10)); + expect(isOnCapturing, true); + }); } diff --git a/flutter/test/capture/continuous_capture_method_channel_test.dart b/flutter/test/capture/continuous_capture_method_channel_test.dart index 6740a25a28..212f44576b 100644 --- a/flutter/test/capture/continuous_capture_method_channel_test.dart +++ b/flutter/test/capture/continuous_capture_method_channel_test.dart @@ -79,7 +79,7 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrls; }); - expect(await platform.startContinuousCapture(null), fileUrls); + expect(await platform.startContinuousCapture(null, null), fileUrls); }); test('startContinuousCapture no file', () async { @@ -88,7 +88,7 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrls; }); - expect(await platform.startContinuousCapture(null), null); + expect(await platform.startContinuousCapture(null, null), null); }); test('startContinuousCapture exception', () async { @@ -97,7 +97,7 @@ void main() { throw Exception('test error'); }); try { - await platform.startContinuousCapture(null); + await platform.startContinuousCapture(null, null); expect(true, false, reason: 'not exception'); } catch (error) { expect(error.toString().contains('test error'), true); @@ -135,11 +135,42 @@ void main() { int progressCount = 0; var resultCapture = platform.startContinuousCapture((completion) { progressCount++; - }); + }, null); var result = await resultCapture.timeout(const Duration(seconds: 5)); expect(result, fileUrls); expect(progressCount, 2); expect(platform.notifyList.containsKey(10061), false, reason: 'remove notify progress'); }); + + test('call onCapturing', () async { + const fileUrls = [ + 'http://192.168.1.1/files/150100624436344d4201375fda9dc400/100RICOH/R0013336.MP4' + ]; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + expect(platform.notifyList.containsKey(10062), true, + reason: 'add notify capturing status'); + + // native event + platform.onNotify({ + 'id': 10062, + 'params': { + 'status': 'SELF_TIMER_COUNTDOWN', + }, + }); + return fileUrls; + }); + + CapturingStatusEnum? lastStatus; + var resultCapture = + platform.startContinuousCapture((completion) {}, (status) { + lastStatus = status; + }); + var result = await resultCapture.timeout(const Duration(seconds: 5)); + expect(result, fileUrls); + expect(lastStatus, CapturingStatusEnum.selfTimerCountdown); + expect(platform.notifyList.containsKey(10062), false, + reason: 'remove notify capturing status'); + }); } diff --git a/flutter/test/capture/continuous_capture_test.dart b/flutter/test/capture/continuous_capture_test.dart index 43541938f1..f1cbf72ea7 100644 --- a/flutter/test/capture/continuous_capture_test.dart +++ b/flutter/test/capture/continuous_capture_test.dart @@ -102,7 +102,7 @@ void main() { const imageUrls = ['http://test.jpeg']; - onCallStartContinuousCapture = (onProgress) { + onCallStartContinuousCapture = (onProgress, onCapturing) { return Future.value(imageUrls); }; @@ -131,7 +131,7 @@ void main() { ThetaClientFlutterPlatform.instance = fakePlatform; var completer = Completer>(); - onCallStartContinuousCapture = (onProgress) { + onCallStartContinuousCapture = (onProgress, onCapturing) { return completer.future; }; @@ -158,7 +158,7 @@ void main() { void Function(double completion)? paramOnProgress; - onCallStartContinuousCapture = (onProgress) { + onCallStartContinuousCapture = (onProgress, onCapturing) { paramOnProgress = onProgress; return Completer>().future; }; @@ -229,4 +229,36 @@ void main() { expect(error.toString().contains('test error'), true); } }); + + test('call onCapturing', () async { + ThetaClientFlutter thetaClientPlugin = ThetaClientFlutter(); + MockThetaClientFlutterPlatform fakePlatform = + MockThetaClientFlutterPlatform(); + ThetaClientFlutterPlatform.instance = fakePlatform; + + var completer = Completer(); + + void Function(CapturingStatusEnum status)? paramOnCapturing; + + onCallStartContinuousCapture = (onProgress, onCapturing) { + paramOnCapturing = onCapturing; + return Completer>().future; + }; + + final builder = thetaClientPlugin.getContinuousCaptureBuilder(); + var capture = await builder.build(); + var isOnCapturing = false; + capture.startCapture((fileUrl) { + expect(false, isTrue, reason: 'startCapture'); + }, (completion) {}, (exception) {}, + onCapturing: (status) { + isOnCapturing = true; + expect(status, CapturingStatusEnum.capturing); + completer.complete(null); + }); + + paramOnCapturing?.call(CapturingStatusEnum.capturing); + await completer.future.timeout(const Duration(milliseconds: 10)); + expect(isOnCapturing, true); + }); } diff --git a/flutter/test/capture/limitless_interval_capture_method_channel_test.dart b/flutter/test/capture/limitless_interval_capture_method_channel_test.dart index 66cfb55b73..30433815af 100644 --- a/flutter/test/capture/limitless_interval_capture_method_channel_test.dart +++ b/flutter/test/capture/limitless_interval_capture_method_channel_test.dart @@ -61,13 +61,14 @@ void main() { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) async { var arguments = methodCall.arguments as Map; + expect(arguments['_capture_interval'], 1); for (int i = 0; i < data.length; i++) { expect(arguments[data[i][0]], data[i][2], reason: data[i][0]); } return Future.value(); }); - await platform.buildLimitlessIntervalCapture(options); + await platform.buildLimitlessIntervalCapture(options, 1); }); test('startLimitlessIntervalCapture', () async { @@ -78,6 +79,68 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrls; }); - expect(await platform.startLimitlessIntervalCapture(null), fileUrls); + expect(await platform.startLimitlessIntervalCapture(null, null), fileUrls); + }); + + test('call onStopFailed', () async { + const fileUrls = [ + 'http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0013336.MP4' + ]; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + expect(platform.notifyList.containsKey(10004), true, + reason: 'add notify stop error'); + + // native event + platform.onNotify({ + 'id': 10004, + 'params': { + 'message': "stop error", + }, + }); + + return fileUrls; + }); + + var isOnStopFailed = false; + expect( + await platform.startLimitlessIntervalCapture((exception) { + isOnStopFailed = true; + }, null), + fileUrls); + expect(platform.notifyList.containsKey(10004), false, + reason: 'remove notify stop error'); + expect(isOnStopFailed, true); + }); + + test('call onCapturing', () async { + const fileUrls = [ + 'http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0013336.MP4' + ]; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + expect(platform.notifyList.containsKey(10005), true, + reason: 'add notify capturing status'); + + // native event + platform.onNotify({ + 'id': 10005, + 'params': { + 'status': 'SELF_TIMER_COUNTDOWN', + }, + }); + + return fileUrls; + }); + + CapturingStatusEnum? lastStatus; + expect( + await platform.startLimitlessIntervalCapture(null, (status) { + lastStatus = status; + }), + fileUrls); + expect(platform.notifyList.containsKey(10005), false, + reason: 'remove notify capturing status'); + expect(lastStatus, CapturingStatusEnum.selfTimerCountdown); }); } diff --git a/flutter/test/capture/limitless_interval_capture_test.dart b/flutter/test/capture/limitless_interval_capture_test.dart index 328c64fb3a..a9a5d63c94 100644 --- a/flutter/test/capture/limitless_interval_capture_test.dart +++ b/flutter/test/capture/limitless_interval_capture_test.dart @@ -7,7 +7,10 @@ import 'package:theta_client_flutter/theta_client_flutter_platform_interface.dar import '../theta_client_flutter_test.dart'; void main() { - setUp(() {}); + setUp(() { + onCallGetLimitlessIntervalCaptureBuilder = Future.value; + onCallBuildLimitlessIntervalCapture = (options, interval) => Future.value(); + }); tearDown(() {}); @@ -51,7 +54,7 @@ void main() { const isoAutoHighLimit = [IsoAutoHighLimitEnum.iso125, 'IsoAutoHighLimit']; const whiteBalance = [WhiteBalanceEnum.auto, 'WhiteBalance']; - onCallBuildLimitlessIntervalCapture = (options) { + onCallBuildLimitlessIntervalCapture = (options, interval) { expect(options[aperture[1]], aperture[0]); expect(options[colorTemperature[1]], colorTemperature[0]); expect(options[exposureCompensation[1]], exposureCompensation[0]); @@ -100,9 +103,7 @@ void main() { const imageUrls = ['http://test.jpeg']; - onCallGetLimitlessIntervalCaptureBuilder = Future.value; - onCallBuildLimitlessIntervalCapture = Future.value; - onCallStartLimitlessIntervalCapture = (onStopFailed) { + onCallStartLimitlessIntervalCapture = (onStopFailed, onCapturing) { return Future.value(imageUrls); }; @@ -128,10 +129,8 @@ void main() { MockThetaClientFlutterPlatform(); ThetaClientFlutterPlatform.instance = fakePlatform; - onCallGetLimitlessIntervalCaptureBuilder = Future.value; - onCallBuildLimitlessIntervalCapture = Future.value; var completer = Completer>(); - onCallStartLimitlessIntervalCapture = (onStopFailed) { + onCallStartLimitlessIntervalCapture = (onStopFailed, onCapturing) { return completer.future; }; onCallStopLimitlessIntervalCapture = () { @@ -160,10 +159,8 @@ void main() { const imageUrls = ['http://test.jpeg']; - onCallGetLimitlessIntervalCaptureBuilder = Future.value; - onCallBuildLimitlessIntervalCapture = Future.value; var completer = Completer>(); - onCallStartLimitlessIntervalCapture = (onStopFailed) { + onCallStartLimitlessIntervalCapture = (onStopFailed, onCapturing) { return completer.future; }; onCallStopLimitlessIntervalCapture = () { @@ -192,10 +189,8 @@ void main() { MockThetaClientFlutterPlatform(); ThetaClientFlutterPlatform.instance = fakePlatform; - onCallGetLimitlessIntervalCaptureBuilder = Future.value; - onCallBuildLimitlessIntervalCapture = Future.value; var completer = Completer?>(); - onCallStartLimitlessIntervalCapture = (onStopFailed) { + onCallStartLimitlessIntervalCapture = (onStopFailed, onCapturing) { return completer.future; }; onCallStopLimitlessIntervalCapture = () { @@ -216,4 +211,70 @@ void main() { await Future.delayed(const Duration(milliseconds: 10), () {}); expect(fileUrls, null); }); + + test('call onStopFailed', () async { + ThetaClientFlutter thetaClientPlugin = ThetaClientFlutter(); + MockThetaClientFlutterPlatform fakePlatform = + MockThetaClientFlutterPlatform(); + ThetaClientFlutterPlatform.instance = fakePlatform; + + const imageUrls = ['http://test.jpeg']; + + onCallStartLimitlessIntervalCapture = (onStopFailed, onCapturing) { + onStopFailed?.call(Exception("on stop error.")); + return Future.value(imageUrls); + }; + + var builder = thetaClientPlugin.getLimitlessIntervalCaptureBuilder(); + var capture = await builder.build(); + List? fileUrls; + + var isOnStopFailed = false; + capture.startCapture((value) { + expect(value, imageUrls); + fileUrls = value; + }, (exception) { + expect(false, isTrue, reason: 'Error. startCapture'); + }, onStopFailed: (exception) { + expect(exception, isNotNull, reason: 'Error. stopCapture'); + isOnStopFailed = true; + }); + + await Future.delayed(const Duration(milliseconds: 100), () {}); + expect(fileUrls, imageUrls); + expect(isOnStopFailed, true); + }); + + test('call onCapturing', () async { + ThetaClientFlutter thetaClientPlugin = ThetaClientFlutter(); + MockThetaClientFlutterPlatform fakePlatform = + MockThetaClientFlutterPlatform(); + ThetaClientFlutterPlatform.instance = fakePlatform; + + const imageUrls = ['http://test.jpeg']; + + onCallStartLimitlessIntervalCapture = (onStopFailed, onCapturing) { + onCapturing?.call(CapturingStatusEnum.capturing); + return Future.value(imageUrls); + }; + + var builder = thetaClientPlugin.getLimitlessIntervalCaptureBuilder(); + var capture = await builder.build(); + List? fileUrls; + + var isOnCapturing = false; + capture.startCapture((value) { + expect(value, imageUrls); + fileUrls = value; + }, (exception) { + expect(false, isTrue, reason: 'Error. startCapture'); + }, onCapturing: (status) { + isOnCapturing = true; + expect(status, CapturingStatusEnum.capturing); + }); + + await Future.delayed(const Duration(milliseconds: 100), () {}); + expect(fileUrls, imageUrls); + expect(isOnCapturing, true); + }); } diff --git a/flutter/test/capture/multi_bracket_capture_method_channel_test.dart b/flutter/test/capture/multi_bracket_capture_method_channel_test.dart index 425e91c0ff..de7a29771c 100644 --- a/flutter/test/capture/multi_bracket_capture_method_channel_test.dart +++ b/flutter/test/capture/multi_bracket_capture_method_channel_test.dart @@ -83,7 +83,7 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrls; }); - expect(await platform.startMultiBracketCapture(null, null), fileUrls); + expect(await platform.startMultiBracketCapture(null, null, null), fileUrls); }); test('startMultiBracketCapture no file', () async { @@ -92,7 +92,7 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrls; }); - expect(await platform.startMultiBracketCapture(null, null), null); + expect(await platform.startMultiBracketCapture(null, null, null), null); }); test('startMultiBracketCapture exception', () async { @@ -101,7 +101,7 @@ void main() { throw Exception('test error'); }); try { - await platform.startMultiBracketCapture(null, null); + await platform.startMultiBracketCapture(null, null, null); expect(true, false, reason: 'not exception'); } catch (error) { expect(error.toString().contains('test error'), true); @@ -139,7 +139,7 @@ void main() { int progressCount = 0; var resultCapture = platform.startMultiBracketCapture((completion) { progressCount++; - }, null); + }, null, null); var result = await resultCapture.timeout(const Duration(seconds: 5)); expect(result, fileUrls); expect(progressCount, 2); @@ -170,11 +170,42 @@ void main() { var isOnStopFailed = false; var resultCapture = platform.startMultiBracketCapture(null, (exception) { isOnStopFailed = true; - }); + }, null); var result = await resultCapture.timeout(const Duration(seconds: 5)); expect(result, fileUrls); expect(platform.notifyList.containsKey(10042), false, reason: 'remove notify stop error'); expect(isOnStopFailed, true); }); + + test('call onCapturing', () async { + const fileUrls = [ + 'http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0013336.MP4' + ]; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + expect(platform.notifyList.containsKey(10043), true, + reason: 'add notify capturing status'); + + await Future.delayed(const Duration(milliseconds: 1)); + // native event + platform.onNotify({ + 'id': 10043, + 'params': { + 'status': 'SELF_TIMER_COUNTDOWN', + }, + }); + return fileUrls; + }); + + CapturingStatusEnum? lastStatus; + var resultCapture = platform.startMultiBracketCapture(null, null, (status) { + lastStatus = status; + }); + var result = await resultCapture.timeout(const Duration(seconds: 5)); + expect(result, fileUrls); + expect(platform.notifyList.containsKey(10043), false, + reason: 'remove notify stop error'); + expect(lastStatus, CapturingStatusEnum.selfTimerCountdown); + }); } diff --git a/flutter/test/capture/multi_bracket_capture_test.dart b/flutter/test/capture/multi_bracket_capture_test.dart index e07cc4c6b6..4da8161ab9 100644 --- a/flutter/test/capture/multi_bracket_capture_test.dart +++ b/flutter/test/capture/multi_bracket_capture_test.dart @@ -61,7 +61,7 @@ void main() { const imageUrls = ['http://test1.jpeg', 'http://test2.jpeg']; - onCallStartMultiBracketCapture = (onProgress, onStopFailed) { + onCallStartMultiBracketCapture = (onProgress, onStopFailed, onCapturing) { return Future.value(imageUrls); }; @@ -90,7 +90,7 @@ void main() { ThetaClientFlutterPlatform.instance = fakePlatform; var completer = Completer>(); - onCallStartMultiBracketCapture = (onProgress, onStopFailed) { + onCallStartMultiBracketCapture = (onProgress, onStopFailed, onCapturing) { return completer.future; }; onCallStopMultiBracketCapture = () { @@ -122,7 +122,7 @@ void main() { const imageUrls = ['http://test1.jpeg', 'http://test2.jpeg']; var completer = Completer>(); - onCallStartMultiBracketCapture = (onProgress, onStopFailed) { + onCallStartMultiBracketCapture = (onProgress, onStopFailed, onCapturing) { return completer.future; }; onCallStopMultiBracketCapture = () { @@ -158,7 +158,7 @@ void main() { void Function(Exception exception)? paramStopFailed; - onCallStartMultiBracketCapture = (onProgress, onStopFailed) { + onCallStartMultiBracketCapture = (onProgress, onStopFailed, onCapturing) { paramStopFailed = onStopFailed; return Completer>().future; }; @@ -197,7 +197,7 @@ void main() { void Function(double completion)? paramOnProgress; - onCallStartMultiBracketCapture = (onProgress, onStopFailed) { + onCallStartMultiBracketCapture = (onProgress, onStopFailed, onCapturing) { paramOnProgress = onProgress; return Completer>().future; }; @@ -216,4 +216,39 @@ void main() { await completer.future.timeout(const Duration(milliseconds: 10)); expect(isOnProgress, true); }); + + test('call onCapturing', () async { + ThetaClientFlutter thetaClientPlugin = ThetaClientFlutter(); + MockThetaClientFlutterPlatform fakePlatform = + MockThetaClientFlutterPlatform(); + ThetaClientFlutterPlatform.instance = fakePlatform; + + var completer = Completer(); + + void Function(CapturingStatusEnum status)? paramOnCapturing; + + onCallStartMultiBracketCapture = (onProgress, onStopFailed, onCapturing) { + paramOnCapturing = onCapturing; + return Completer>().future; + }; + + var builder = thetaClientPlugin.getMultiBracketCaptureBuilder(); + var capture = await builder.build(); + var isOnCapturing = false; + capture.startCapture( + (fileUrl) { + expect(false, isTrue, reason: 'startCapture'); + }, + (completion) {}, + (exception) {}, + onCapturing: (status) { + isOnCapturing = true; + expect(status, CapturingStatusEnum.capturing); + completer.complete(null); + }); + + paramOnCapturing?.call(CapturingStatusEnum.capturing); + await completer.future.timeout(const Duration(milliseconds: 10)); + expect(isOnCapturing, true); + }); } diff --git a/flutter/test/capture/photo_capture_method_channel_test.dart b/flutter/test/capture/photo_capture_method_channel_test.dart index a93d5aac81..0cc4926e7c 100644 --- a/flutter/test/capture/photo_capture_method_channel_test.dart +++ b/flutter/test/capture/photo_capture_method_channel_test.dart @@ -64,7 +64,7 @@ void main() { return Future.value(); }); - await platform.buildPhotoCapture(options); + await platform.buildPhotoCapture(options, 1); }); test('takePicture', () async { @@ -74,6 +74,38 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrl; }); - expect(await platform.takePicture(), fileUrl); + expect(await platform.takePicture(null), fileUrl); + }); + + test('call onCapturing', () async { + const fileUrl = + 'http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0013336.JPG'; + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + expect(platform.notifyList.containsKey(10071), true, + reason: 'add notify capturing status'); + + await Future.delayed(const Duration(milliseconds: 5)); + // native event + platform.onNotify({ + 'id': 10071, + 'params': { + 'status': 'SELF_TIMER_COUNTDOWN', + }, + }); + await Future.delayed(const Duration(milliseconds: 5)); + + return fileUrl; + }); + + CapturingStatusEnum? lastStatus; + expect( + await platform.takePicture((status) { + expect(status, CapturingStatusEnum.selfTimerCountdown); + lastStatus = status; + }), + fileUrl); + expect(lastStatus, CapturingStatusEnum.selfTimerCountdown); }); } diff --git a/flutter/test/capture/photo_capture_test.dart b/flutter/test/capture/photo_capture_test.dart index 703ea98b12..4ec00877a3 100644 --- a/flutter/test/capture/photo_capture_test.dart +++ b/flutter/test/capture/photo_capture_test.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter_test/flutter_test.dart'; import 'package:theta_client_flutter/theta_client_flutter.dart'; import 'package:theta_client_flutter/theta_client_flutter_platform_interface.dart'; @@ -5,7 +7,9 @@ import 'package:theta_client_flutter/theta_client_flutter_platform_interface.dar import '../theta_client_flutter_test.dart'; void main() { - setUp(() {}); + setUp(() { + onCallBuildPhotoCapture = (options, interval) => Future.value(); + }); tearDown(() {}); @@ -52,7 +56,7 @@ void main() { const preset = [PresetEnum.face, 'Preset']; const whiteBalance = [WhiteBalanceEnum.auto, 'WhiteBalance']; - onCallBuildPhotoCapture = (options) { + onCallBuildPhotoCapture = (options, interval) { expect(options[aperture[1]], aperture[0]); expect(options[colorTemperature[1]], colorTemperature[0]); expect(options[exposureCompensation[1]], exposureCompensation[0]); @@ -84,6 +88,7 @@ void main() { builder.setIsoAutoHighLimit(isoAutoHighLimit[0] as IsoAutoHighLimitEnum); builder.setPreset(preset[0] as PresetEnum); builder.setWhiteBalance(whiteBalance[0] as WhiteBalanceEnum); + builder.setCheckStatusCommandInterval(100); var capture = await builder.build(); expect(capture, isNotNull); @@ -100,6 +105,7 @@ void main() { expect(capture.getIsoAutoHighLimit(), isoAutoHighLimit[0]); expect(capture.getPreset(), preset[0]); expect(capture.getWhiteBalance(), whiteBalance[0]); + expect(capture.getCheckStatusCommandInterval(), 100); }); test('takePicture', () async { @@ -111,8 +117,8 @@ void main() { const imageUrl = 'http://test.jpg'; onCallGetPhotoCaptureBuilder = Future.value; - onCallBuildPhotoCapture = Future.value; - onCallTakePicture = () { + onCallBuildPhotoCapture = (options, interval) => Future.value(); + onCallTakePicture = (onCapturing) { return Future.value(imageUrl); }; @@ -137,8 +143,8 @@ void main() { ThetaClientFlutterPlatform.instance = fakePlatform; onCallGetPhotoCaptureBuilder = Future.value; - onCallBuildPhotoCapture = Future.value; - onCallTakePicture = () { + onCallBuildPhotoCapture = (options, interval) => Future.value(); + onCallTakePicture = (onCapturing) { return Future.error(Exception('Error. takePicture')); }; @@ -157,4 +163,43 @@ void main() { expect(fileUrl, isNull); expect(error, isNotNull); }); + + test('call onCapturing', () async { + ThetaClientFlutter thetaClientPlugin = ThetaClientFlutter(); + MockThetaClientFlutterPlatform fakePlatform = + MockThetaClientFlutterPlatform(); + ThetaClientFlutterPlatform.instance = fakePlatform; + + var completer = Completer(); + void Function(CapturingStatusEnum status)? paramOnCapturing; + + const imageUrl = 'http://test.jpg'; + + onCallGetPhotoCaptureBuilder = Future.value; + onCallBuildPhotoCapture = (options, interval) => Future.value(); + onCallTakePicture = (onCapturing) { + paramOnCapturing = onCapturing; + return Future.value(imageUrl); + }; + + var builder = thetaClientPlugin.getPhotoCaptureBuilder(); + var isOnCapturing = false; + var capture = await builder.build(); + String? fileUrl; + capture.takePicture((value) { + expect(value, imageUrl); + fileUrl = value; + completer.complete(null); + }, (exception) { + expect(false, isTrue, reason: 'Error. takePicture'); + }, onCapturing: (status) { + isOnCapturing = true; + expect(status, CapturingStatusEnum.capturing); + }); + paramOnCapturing?.call(CapturingStatusEnum.capturing); + + await Future.delayed(const Duration(milliseconds: 10), () {}); + expect(fileUrl, imageUrl); + expect(isOnCapturing, isTrue); + }); } diff --git a/flutter/test/capture/shot_count_specified_interval_capture_method_channel_test.dart b/flutter/test/capture/shot_count_specified_interval_capture_method_channel_test.dart index 835dfe6def..e961389a0e 100644 --- a/flutter/test/capture/shot_count_specified_interval_capture_method_channel_test.dart +++ b/flutter/test/capture/shot_count_specified_interval_capture_method_channel_test.dart @@ -73,7 +73,8 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrls; }); - expect(await platform.startShotCountSpecifiedIntervalCapture(null, null), + expect( + await platform.startShotCountSpecifiedIntervalCapture(null, null, null), fileUrls); }); @@ -83,7 +84,8 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrls; }); - expect(await platform.startShotCountSpecifiedIntervalCapture(null, null), + expect( + await platform.startShotCountSpecifiedIntervalCapture(null, null, null), null); }); @@ -93,7 +95,7 @@ void main() { throw Exception('test error'); }); try { - await platform.startShotCountSpecifiedIntervalCapture(null, null); + await platform.startShotCountSpecifiedIntervalCapture(null, null, null); expect(true, false, reason: 'not exception'); } catch (error) { expect(error.toString().contains('test error'), true); @@ -130,9 +132,9 @@ void main() { int progressCount = 0; var resultCapture = - platform.startShotCountSpecifiedIntervalCapture((completion) { + platform.startShotCountSpecifiedIntervalCapture((completion) { progressCount++; - }, null); + }, null, null); var result = await resultCapture.timeout(const Duration(seconds: 5)); expect(result, fileUrls); expect(progressCount, 2); @@ -162,13 +164,46 @@ void main() { var isOnStopFailed = false; var resultCapture = - platform.startShotCountSpecifiedIntervalCapture(null, (exception) { + platform.startShotCountSpecifiedIntervalCapture(null, (exception) { isOnStopFailed = true; - }); + }, null); var result = await resultCapture.timeout(const Duration(seconds: 5)); expect(result, fileUrls); expect(platform.notifyList.containsKey(10022), false, reason: 'remove notify stop error'); expect(isOnStopFailed, true); }); + + test('call onCapturing', () async { + const fileUrls = [ + 'http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0013336.MP4' + ]; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + expect(platform.notifyList.containsKey(10023), true, + reason: 'add notify capturing status'); + + await Future.delayed(const Duration(milliseconds: 1)); + // native event + platform.onNotify({ + 'id': 10023, + 'params': { + 'status': 'SELF_TIMER_COUNTDOWN', + }, + }); + return fileUrls; + }); + + CapturingStatusEnum? lastStatus; + var resultCapture = + platform.startShotCountSpecifiedIntervalCapture( + null, (exception) {}, (status) { + lastStatus = status; + }); + var result = await resultCapture.timeout(const Duration(seconds: 5)); + expect(result, fileUrls); + expect(platform.notifyList.containsKey(10022), false, + reason: 'remove notify capturing status'); + expect(lastStatus, CapturingStatusEnum.selfTimerCountdown); + }); } diff --git a/flutter/test/capture/shot_count_specified_interval_capture_test.dart b/flutter/test/capture/shot_count_specified_interval_capture_test.dart index b9de4ec97c..2f42e5b10d 100644 --- a/flutter/test/capture/shot_count_specified_interval_capture_test.dart +++ b/flutter/test/capture/shot_count_specified_interval_capture_test.dart @@ -106,7 +106,8 @@ void main() { const shotCount = 2; const imageUrls = ['http://test.jpeg']; - onCallStartShotCountSpecifiedIntervalCapture = (onProgress, onStopFailed) { + onCallStartShotCountSpecifiedIntervalCapture = + (onProgress, onStopFailed, onCapturing) { return Future.value(imageUrls); }; @@ -136,7 +137,8 @@ void main() { ThetaClientFlutterPlatform.instance = fakePlatform; var completer = Completer>(); - onCallStartShotCountSpecifiedIntervalCapture = (onProgress, onStopFailed) { + onCallStartShotCountSpecifiedIntervalCapture = + (onProgress, onStopFailed, onCapturing) { return completer.future; }; onCallStopShotCountSpecifiedIntervalCapture = () { @@ -172,7 +174,8 @@ void main() { const imageUrls = ['http://test.jpeg']; var completer = Completer>(); - onCallStartShotCountSpecifiedIntervalCapture = (onProgress, onStopFailed) { + onCallStartShotCountSpecifiedIntervalCapture = + (onProgress, onStopFailed, onCapturing) { return completer.future; }; onCallStopShotCountSpecifiedIntervalCapture = () { @@ -209,7 +212,8 @@ void main() { void Function(Exception exception)? paramStopFailed; - onCallStartShotCountSpecifiedIntervalCapture = (onProgress, onStopFailed) { + onCallStartShotCountSpecifiedIntervalCapture = + (onProgress, onStopFailed, onCapturing) { paramStopFailed = onStopFailed; return Completer>().future; }; @@ -250,7 +254,8 @@ void main() { void Function(double completion)? paramOnProgress; - onCallStartShotCountSpecifiedIntervalCapture = (onProgress, onStopFailed) { + onCallStartShotCountSpecifiedIntervalCapture = + (onProgress, onStopFailed, onCapturing) { paramOnProgress = onProgress; return Completer>().future; }; @@ -271,4 +276,38 @@ void main() { await completer.future.timeout(const Duration(milliseconds: 10)); expect(isOnProgress, true); }); + + test('call onCapturing', () async { + ThetaClientFlutter thetaClientPlugin = ThetaClientFlutter(); + MockThetaClientFlutterPlatform fakePlatform = + MockThetaClientFlutterPlatform(); + ThetaClientFlutterPlatform.instance = fakePlatform; + + var completer = Completer(); + + void Function(CapturingStatusEnum status)? paramOnCapturing; + + onCallStartShotCountSpecifiedIntervalCapture = + (onProgress, onStopFailed, onCapturing) { + paramOnCapturing = onCapturing; + return Completer>().future; + }; + + const shotCount = 2; + var builder = thetaClientPlugin + .getShotCountSpecifiedIntervalCaptureBuilder(shotCount); + var capture = await builder.build(); + var isOnCapturing = false; + capture.startCapture((fileUrl) { + expect(false, isTrue, reason: 'startCapture'); + }, (completion) {}, (exception) {}, + onCapturing: (status) { + isOnCapturing = true; + completer.complete(null); + }); + + paramOnCapturing?.call(CapturingStatusEnum.capturing); + await completer.future.timeout(const Duration(milliseconds: 10)); + expect(isOnCapturing, true); + }); } diff --git a/flutter/test/capture/time_shift_capture_method_channel_test.dart b/flutter/test/capture/time_shift_capture_method_channel_test.dart index 4e9db27ed1..2dc6cacb4d 100644 --- a/flutter/test/capture/time_shift_capture_method_channel_test.dart +++ b/flutter/test/capture/time_shift_capture_method_channel_test.dart @@ -52,7 +52,7 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrl; }); - expect(await platform.startTimeShiftCapture(null, null), fileUrl); + expect(await platform.startTimeShiftCapture(null, null, null), fileUrl); }); test('startTimeShiftCapture no file', () async { @@ -61,7 +61,7 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrl; }); - expect(await platform.startTimeShiftCapture(null, null), null); + expect(await platform.startTimeShiftCapture(null, null, null), null); }); test('startTimeShiftCapture exception', () async { @@ -70,7 +70,7 @@ void main() { throw Exception('test error'); }); try { - await platform.startTimeShiftCapture(null, null); + await platform.startTimeShiftCapture(null, null, null); expect(true, false, reason: 'not exception'); } catch (error) { expect(error.toString().contains('test error'), true); @@ -108,11 +108,43 @@ void main() { int progressCount = 0; var resultCapture = platform.startTimeShiftCapture((completion) { progressCount++; - }, null); + }, null, null); var result = await resultCapture.timeout(const Duration(seconds: 5)); expect(result, fileUrl); expect(progressCount, 2); expect(platform.notifyList.containsKey(10011), false, reason: 'remove notify progress'); }); + + test('call onCapturing', () async { + const fileUrl = + 'http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0013336.MP4'; + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + expect(platform.notifyList.containsKey(10013), true, + reason: 'add notify capturing'); + + // native event + platform.onNotify({ + 'id': 10013, + 'params': { + 'status': 'SELF_TIMER_COUNTDOWN', + }, + }); + await Future.delayed(const Duration(milliseconds: 10)); + + return fileUrl; + }); + + CapturingStatusEnum? lastStatus; + var resultCapture = platform.startTimeShiftCapture(null, null, (status) { + lastStatus = status; + }); + var result = await resultCapture.timeout(const Duration(seconds: 5)); + expect(result, fileUrl); + expect(lastStatus, CapturingStatusEnum.selfTimerCountdown); + expect(platform.notifyList.containsKey(10013), false, + reason: 'remove notify capturing'); + }); } diff --git a/flutter/test/capture/time_shift_capture_test.dart b/flutter/test/capture/time_shift_capture_test.dart index b2a812394a..648d69b18a 100644 --- a/flutter/test/capture/time_shift_capture_test.dart +++ b/flutter/test/capture/time_shift_capture_test.dart @@ -67,7 +67,7 @@ void main() { const imageUrl = 'http://test.JPG'; - onCallStartTimeShiftCapture = (onProgress, onStopFailed) { + onCallStartTimeShiftCapture = (onProgress, onStopFailed, onCapturing) { return Future.value(imageUrl); }; @@ -95,7 +95,7 @@ void main() { ThetaClientFlutterPlatform.instance = fakePlatform; var completer = Completer(); - onCallStartTimeShiftCapture = (onProgress, onStopFailed) { + onCallStartTimeShiftCapture = (onProgress, onStopFailed, onCapturing) { return completer.future; }; onCallStopTimeShiftCapture = () { @@ -127,7 +127,7 @@ void main() { const imageUrl = 'http://test.mp4'; var completer = Completer(); - onCallStartTimeShiftCapture = (onProgress, onStopFailed) { + onCallStartTimeShiftCapture = (onProgress, onStopFailed, onCapturing) { return completer.future; }; onCallStopTimeShiftCapture = () { @@ -163,7 +163,7 @@ void main() { void Function(Exception exception)? paramStopFailed; - onCallStartTimeShiftCapture = (onProgress, onStopFailed) { + onCallStartTimeShiftCapture = (onProgress, onStopFailed, onCapturing) { paramStopFailed = onStopFailed; return Completer().future; }; @@ -191,4 +191,43 @@ void main() { await completer.future.timeout(const Duration(milliseconds: 10)); expect(isOnStopFailed, true); }); + + test('call onCapturing', () async { + ThetaClientFlutter thetaClientPlugin = ThetaClientFlutter(); + MockThetaClientFlutterPlatform fakePlatform = + MockThetaClientFlutterPlatform(); + ThetaClientFlutterPlatform.instance = fakePlatform; + + var completer = Completer(); + + void Function(CapturingStatusEnum status)? paramOnCapturing; + + onCallStartTimeShiftCapture = (onProgress, onStopFailed, onCapturing) { + paramOnCapturing = onCapturing; + return Completer().future; + }; + onCallStopTimeShiftCapture = () { + paramOnCapturing?.call(CapturingStatusEnum.capturing); + return Future.value(); + }; + + var builder = thetaClientPlugin.getTimeShiftCaptureBuilder(); + var capture = await builder.build(); + var isOnCapturing = false; + var capturing = capture.startCapture( + (fileUrl) { + expect(false, isTrue, reason: 'startCapture'); + }, + (completion) {}, + (exception) {}, + onCapturing: (status) { + expect(status, CapturingStatusEnum.capturing); + isOnCapturing = true; + completer.complete(null); + }); + + capturing.stopCapture(); + await completer.future.timeout(const Duration(milliseconds: 10)); + expect(isOnCapturing, true); + }); } diff --git a/flutter/test/capture/video_capture_method_channel_test.dart b/flutter/test/capture/video_capture_method_channel_test.dart index 100cefac26..11dd6eb34e 100644 --- a/flutter/test/capture/video_capture_method_channel_test.dart +++ b/flutter/test/capture/video_capture_method_channel_test.dart @@ -67,7 +67,7 @@ void main() { return Future.value(); }); - await platform.buildVideoCapture(options); + await platform.buildVideoCapture(options, 1); }); test('startVideoCapture', () async { @@ -77,6 +77,66 @@ void main() { .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return fileUrl; }); - expect(await platform.startVideoCapture(null), fileUrl); + expect(await platform.startVideoCapture(null, null), fileUrl); + }); + + test('call onStopFailed', () async { + const fileUrl = + 'http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0013336.MP4'; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + expect(platform.notifyList.containsKey(10081), true, + reason: 'add notify stop error'); + + // native event + platform.onNotify({ + 'id': 10081, + 'params': { + 'message': "stop error", + }, + }); + + return fileUrl; + }); + + var isOnStopFailed = false; + expect( + await platform.startVideoCapture((exception) { + isOnStopFailed = true; + }, null), + fileUrl); + expect(platform.notifyList.containsKey(10081), false, + reason: 'remove notify stop error'); + expect(isOnStopFailed, true); + }); + + test('call onCapturing', () async { + const fileUrl = + 'http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0013336.MP4'; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + expect(platform.notifyList.containsKey(10082), true, + reason: 'add notify capturing status'); + + // native event + platform.onNotify({ + 'id': 10082, + 'params': { + 'status': 'SELF_TIMER_COUNTDOWN', + }, + }); + + return fileUrl; + }); + + CapturingStatusEnum? lastStatus; + expect( + await platform.startVideoCapture(null, (status) { + lastStatus = status; + }), + fileUrl); + expect(platform.notifyList.containsKey(10082), false, + reason: 'remove notify capturing status'); + expect(lastStatus, CapturingStatusEnum.selfTimerCountdown); }); } diff --git a/flutter/test/capture/video_capture_test.dart b/flutter/test/capture/video_capture_test.dart index 119a4fae4a..098a8ed3be 100644 --- a/flutter/test/capture/video_capture_test.dart +++ b/flutter/test/capture/video_capture_test.dart @@ -7,7 +7,10 @@ import 'package:theta_client_flutter/theta_client_flutter_platform_interface.dar import '../theta_client_flutter_test.dart'; void main() { - setUp(() {}); + setUp(() { + onCallGetVideoCaptureBuilder = Future.value; + onCallBuildVideoCapture = (options, interval) => Future.value(); + }); tearDown(() {}); @@ -29,8 +32,6 @@ void main() { MockThetaClientFlutterPlatform(); ThetaClientFlutterPlatform.instance = fakePlatform; - onCallGetVideoCaptureBuilder = Future.value; - const aperture = [ApertureEnum.aperture_2_0, 'Aperture']; const colorTemperature = [2, 'ColorTemperature']; const exposureCompensation = [ @@ -56,7 +57,7 @@ void main() { 'MaxRecordableTime' ]; - onCallBuildVideoCapture = (options) { + onCallBuildVideoCapture = (options, interval) { expect(options[aperture[1]], aperture[0]); expect(options[colorTemperature[1]], colorTemperature[0]); expect(options[exposureCompensation[1]], exposureCompensation[0]); @@ -111,14 +112,14 @@ void main() { const imageUrl = 'http://test.mp4'; - onCallGetVideoCaptureBuilder = Future.value; - onCallBuildVideoCapture = Future.value; - onCallStartVideoCapture = (onStopFailed) { + onCallStartVideoCapture = (onStopFailed, onCapturing) { return Future.value(imageUrl); }; var builder = thetaClientPlugin.getVideoCaptureBuilder(); + builder.setCheckStatusCommandInterval(1); var capture = await builder.build(); + expect(capture.getCheckStatusCommandInterval(), 1); String? fileUrl; capture.startCapture((value) { @@ -139,10 +140,8 @@ void main() { MockThetaClientFlutterPlatform(); ThetaClientFlutterPlatform.instance = fakePlatform; - onCallGetVideoCaptureBuilder = Future.value; - onCallBuildVideoCapture = Future.value; var completer = Completer(); - onCallStartVideoCapture = (onStopFailed) { + onCallStartVideoCapture = (onStopFailed, onCapturing) { return completer.future; }; onCallStopVideoCapture = () { @@ -152,6 +151,7 @@ void main() { var builder = thetaClientPlugin.getVideoCaptureBuilder(); var capture = await builder.build(); + expect(capture.getCheckStatusCommandInterval(), -1); var capturing = capture.startCapture((value) { expect(false, isTrue, reason: 'startCapture'); }, (exception) { @@ -170,10 +170,8 @@ void main() { const imageUrl = 'http://test.mp4'; - onCallGetVideoCaptureBuilder = Future.value; - onCallBuildVideoCapture = Future.value; var completer = Completer(); - onCallStartVideoCapture = (onStopFailed) { + onCallStartVideoCapture = (onStopFailed, onCapturing) { return completer.future; }; onCallStopVideoCapture = () { @@ -195,4 +193,70 @@ void main() { expect(fileUrl, imageUrl); expect(capture.getAperture(), isNull); }); + + test('call onStopFailed', () async { + ThetaClientFlutter thetaClientPlugin = ThetaClientFlutter(); + MockThetaClientFlutterPlatform fakePlatform = + MockThetaClientFlutterPlatform(); + ThetaClientFlutterPlatform.instance = fakePlatform; + + const imageUrl = 'http://test.mp4'; + + onCallStartVideoCapture = (onStopFailed, onCapturing) { + onStopFailed?.call(Exception("on stop error.")); + return Future.value(imageUrl); + }; + + var builder = thetaClientPlugin.getVideoCaptureBuilder(); + var capture = await builder.build(); + String? fileUrl; + + var isOnStopFailed = false; + capture.startCapture((value) { + expect(value, imageUrl); + fileUrl = value; + }, (exception) { + expect(false, isTrue, reason: 'Error. startCapture'); + }, onStopFailed: (exception) { + expect(exception, isNotNull, reason: 'Error. stopCapture'); + isOnStopFailed = true; + }); + + await Future.delayed(const Duration(milliseconds: 100), () {}); + expect(fileUrl, imageUrl); + expect(isOnStopFailed, true); + }); + + test('call onCapturing', () async { + ThetaClientFlutter thetaClientPlugin = ThetaClientFlutter(); + MockThetaClientFlutterPlatform fakePlatform = + MockThetaClientFlutterPlatform(); + ThetaClientFlutterPlatform.instance = fakePlatform; + + const imageUrl = 'http://test.mp4'; + + onCallStartVideoCapture = (onStopFailed, onCapturing) { + onCapturing?.call(CapturingStatusEnum.capturing); + return Future.value(imageUrl); + }; + + var builder = thetaClientPlugin.getVideoCaptureBuilder(); + var capture = await builder.build(); + String? fileUrl; + + var isOnCapturing = false; + capture.startCapture((value) { + expect(value, imageUrl); + fileUrl = value; + }, (exception) { + expect(false, isTrue, reason: 'Error. startCapture'); + }, onCapturing: (status) { + isOnCapturing = true; + expect(status, CapturingStatusEnum.capturing); + }); + + await Future.delayed(const Duration(milliseconds: 100), () {}); + expect(fileUrl, imageUrl); + expect(isOnCapturing, true); + }); } diff --git a/flutter/test/enum_name_test.dart b/flutter/test/enum_name_test.dart index ff86986fa4..f1984bcbae 100644 --- a/flutter/test/enum_name_test.dart +++ b/flutter/test/enum_name_test.dart @@ -255,26 +255,6 @@ void main() { } }); - test('CaptureStatusEnum', () async { - List> data = [ - [CaptureStatusEnum.shooting, 'SHOOTING'], - [CaptureStatusEnum.idle, 'IDLE'], - [CaptureStatusEnum.selfTimerCountdown, 'SELF_TIMER_COUNTDOWN'], - [CaptureStatusEnum.bracketShooting, 'BRACKET_SHOOTING'], - [CaptureStatusEnum.converting, 'CONVERTING'], - [CaptureStatusEnum.timeShiftShooting, 'TIME_SHIFT_SHOOTING'], - [CaptureStatusEnum.continuousShooting, 'CONTINUOUS_SHOOTING'], - [ - CaptureStatusEnum.retrospectiveImageRecording, - 'RETROSPECTIVE_IMAGE_RECORDING' - ], - ]; - expect(data.length, CaptureStatusEnum.values.length, reason: 'enum count'); - for (int i = 0; i < data.length; i++) { - expect(data[i][0].toString(), data[i][1], reason: data[i][1]); - } - }); - test('ChargingStateEnum', () async { List> data = [ [ChargingStateEnum.charging, 'CHARGING'], @@ -928,4 +908,16 @@ void main() { expect(data[i][0].toString(), data[i][1], reason: data[i][1]); } }); + + test('CapturingStatusEnum', () async { + List> data = [ + [CapturingStatusEnum.capturing, 'CAPTURING'], + [CapturingStatusEnum.selfTimerCountdown, 'SELF_TIMER_COUNTDOWN'], + ]; + expect(data.length, CapturingStatusEnum.values.length, + reason: 'enum count'); + for (int i = 0; i < data.length; i++) { + expect(data[i][0].toString(), data[i][1], reason: data[i][1]); + } + }); } diff --git a/flutter/test/state/capture_status_test.dart b/flutter/test/state/capture_status_test.dart new file mode 100644 index 0000000000..dac0b15063 --- /dev/null +++ b/flutter/test/state/capture_status_test.dart @@ -0,0 +1,30 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:theta_client_flutter/theta_client_flutter.dart'; + +void main() { + setUp(() {}); + + tearDown(() {}); + + test('CaptureStatusEnum', () async { + List> data = [ + [CaptureStatusEnum.unknown, 'UNKNOWN'], + [CaptureStatusEnum.shooting, 'SHOOTING'], + [CaptureStatusEnum.idle, 'IDLE'], + [CaptureStatusEnum.selfTimerCountdown, 'SELF_TIMER_COUNTDOWN'], + [CaptureStatusEnum.bracketShooting, 'BRACKET_SHOOTING'], + [CaptureStatusEnum.converting, 'CONVERTING'], + [CaptureStatusEnum.timeShiftShooting, 'TIME_SHIFT_SHOOTING'], + [CaptureStatusEnum.continuousShooting, 'CONTINUOUS_SHOOTING'], + [ + CaptureStatusEnum.retrospectiveImageRecording, + 'RETROSPECTIVE_IMAGE_RECORDING' + ], + [CaptureStatusEnum.burstShooting, 'BURST_SHOOTING'], + ]; + expect(data.length, CaptureStatusEnum.values.length, reason: 'enum count'); + for (int i = 0; i < data.length; i++) { + expect(data[i][0].toString(), data[i][1], reason: data[i][1]); + } + }); +} diff --git a/flutter/test/theta_client_flutter_test.dart b/flutter/test/theta_client_flutter_test.dart index 727761e287..2221606763 100644 --- a/flutter/test/theta_client_flutter_test.dart +++ b/flutter/test/theta_client_flutter_test.dart @@ -2,8 +2,6 @@ import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -import 'package:theta_client_flutter/state/state_gps_info.dart'; -import 'package:theta_client_flutter/state/theta_state.dart'; import 'package:theta_client_flutter/theta_client_flutter.dart'; import 'package:theta_client_flutter/theta_client_flutter_method_channel.dart'; import 'package:theta_client_flutter/theta_client_flutter_platform_interface.dart'; @@ -62,13 +60,14 @@ class MockThetaClientFlutterPlatform } @override - Future buildPhotoCapture(Map options) { - return onCallBuildPhotoCapture(options); + Future buildPhotoCapture(Map options, int interval) { + return onCallBuildPhotoCapture(options, interval); } @override - Future takePicture() { - return onCallTakePicture(); + Future takePicture( + void Function(CapturingStatusEnum status)? onCapturing) { + return onCallTakePicture(onCapturing); } @override @@ -84,8 +83,9 @@ class MockThetaClientFlutterPlatform @override Future startTimeShiftCapture(void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) { - return onCallStartTimeShiftCapture(onProgress, onStopFailed); + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { + return onCallStartTimeShiftCapture(onProgress, onStopFailed, onCapturing); } @override @@ -99,14 +99,15 @@ class MockThetaClientFlutterPlatform } @override - Future buildVideoCapture(Map options) { - return onCallBuildVideoCapture(options); + Future buildVideoCapture(Map options, int interval) { + return onCallBuildVideoCapture(options, interval); } @override Future startVideoCapture( - void Function(Exception exception)? onStopFailed) { - return onCallStartVideoCapture(onStopFailed); + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { + return onCallStartVideoCapture(onStopFailed, onCapturing); } @override @@ -115,8 +116,9 @@ class MockThetaClientFlutterPlatform } @override - Future buildLimitlessIntervalCapture(Map options) { - return onCallBuildLimitlessIntervalCapture(options); + Future buildLimitlessIntervalCapture(Map options, + int interval) { + return onCallBuildLimitlessIntervalCapture(options, interval); } @override @@ -126,8 +128,9 @@ class MockThetaClientFlutterPlatform @override Future?> startLimitlessIntervalCapture( - void Function(Exception exception)? onStopFailed) { - return onCallStartLimitlessIntervalCapture(onStopFailed); + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { + return onCallStartLimitlessIntervalCapture(onStopFailed, onCapturing); } @override @@ -149,9 +152,10 @@ class MockThetaClientFlutterPlatform @override Future?> startShotCountSpecifiedIntervalCapture( void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) { + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { return onCallStartShotCountSpecifiedIntervalCapture( - onProgress, onStopFailed); + onProgress, onStopFailed, onCapturing); } @override @@ -173,8 +177,10 @@ class MockThetaClientFlutterPlatform @override Future?> startCompositeIntervalCapture( void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) { - return onCallStartCompositeIntervalCapture(onProgress, onStopFailed); + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { + return onCallStartCompositeIntervalCapture( + onProgress, onStopFailed, onCapturing); } @override @@ -199,9 +205,11 @@ class MockThetaClientFlutterPlatform } @override - Future?> startBurstCapture(void Function(double p1)? onProgress, - void Function(Exception exception)? onStopFailed) { - return onCallStartBurstCapture(onProgress, onStopFailed); + Future?> startBurstCapture( + void Function(double p1)? onProgress, + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { + return onCallStartBurstCapture(onProgress, onStopFailed, onCapturing); } @override @@ -223,8 +231,10 @@ class MockThetaClientFlutterPlatform @override Future?> startMultiBracketCapture( void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) { - return onCallStartMultiBracketCapture(onProgress, onStopFailed); + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) { + return onCallStartMultiBracketCapture( + onProgress, onStopFailed, onCapturing); } @override @@ -245,8 +255,9 @@ class MockThetaClientFlutterPlatform @override Future?> startContinuousCapture( - void Function(double p1)? onProgress) { - return onCallStartContinuousCapture(onProgress); + void Function(double p1)? onProgress, + void Function(CapturingStatusEnum status)? onCapturing) { + return onCallStartContinuousCapture(onProgress, onCapturing); } @override @@ -419,77 +430,92 @@ Future Function() onGetThetaLicense = Future.value; Future Function() onGetThetaState = Future.value; Future Function() onCallGetLivePreview = Future.value; Future Function() onCallListFiles = Future.value; + Future Function() onCallGetPhotoCaptureBuilder = Future.value; -Future Function(Map options) onCallBuildPhotoCapture = - Future.value; -Future Function() onCallTakePicture = Future.value; +Future Function(Map options, int interval) onCallBuildPhotoCapture = + (options, interval) => Future.value(); +Future Function(void Function(CapturingStatusEnum)? onCapturing) + onCallTakePicture = (onCapturing) => Future.value(); Future Function() onCallGetTimeShiftCaptureBuilder = Future.value; Future Function(Map options, int interval) - onCallBuildTimeShiftCapture = (options, interval) => Future.value(); +onCallBuildTimeShiftCapture = (options, interval) => Future.value(); Future Function(void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) - onCallStartTimeShiftCapture = (onProgress, onStopFailed) => Future.value(); + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) +onCallStartTimeShiftCapture = (onProgress, onStopFailed, onCapturing) => + Future.value(); Future Function() onCallStopTimeShiftCapture = Future.value; Future Function() onCallGetVideoCaptureBuilder = Future.value; -Future Function(Map options) onCallBuildVideoCapture = - Future.value; -Future Function(void Function(Exception exception)? onStopFailed) - onCallStartVideoCapture = (onStopFailed) => Future.value(); +Future Function(Map options, int interval) + onCallBuildVideoCapture = (options, interval) => Future.value(); +Future Function(void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) + onCallStartVideoCapture = (onStopFailed, onCapturing) => Future.value(); Future Function() onCallStopVideoCapture = Future.value; Future Function() onCallGetLimitlessIntervalCaptureBuilder = Future.value; -Future Function(Map options) - onCallBuildLimitlessIntervalCapture = Future.value; -Future?> Function(void Function(Exception exception)? onStopFailed) - onCallStartLimitlessIntervalCapture = (onStopFailed) => Future.value(); +Future Function(Map options, int interval) +onCallBuildLimitlessIntervalCapture = (options, interval) => Future.value(); +Future?> Function(void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) +onCallStartLimitlessIntervalCapture = (onStopFailed, onCapturing) => + Future.value(); Future Function() onCallStopLimitlessIntervalCapture = Future.value; Future Function(int shotCount) - onCallGetShotCountSpecifiedIntervalCaptureBuilder = Future.value; +onCallGetShotCountSpecifiedIntervalCaptureBuilder = Future.value; Future Function(Map options, int interval) - onCallBuildShotCountSpecifiedIntervalCapture = +onCallBuildShotCountSpecifiedIntervalCapture = (options, interval) => Future.value(); Future?> Function(void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) - onCallStartShotCountSpecifiedIntervalCapture = - (onProgress, onStopFailed) => Future.value(); + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) +onCallStartShotCountSpecifiedIntervalCapture = + (onProgress, onStopFailed, onCapturing) => Future.value(); Future Function() onCallStopShotCountSpecifiedIntervalCapture = Future.value; Future Function(int shootingTimeSec) - onCallGetCompositeIntervalCaptureBuilder = Future.value; +onCallGetCompositeIntervalCaptureBuilder = Future.value; Future Function(Map options, int interval) - onCallBuildCompositeIntervalCapture = (options, interval) => Future.value(); +onCallBuildCompositeIntervalCapture = (options, interval) => Future.value(); Future?> Function(void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) - onCallStartCompositeIntervalCapture = - (onProgress, onStopFailed) => Future.value(); + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) +onCallStartCompositeIntervalCapture = + (onProgress, onStopFailed, onCapturing) => Future.value(); Future Function() onCallStopCompositeIntervalCapture = Future.value; Future Function() onCallGetBurstCaptureBuilder = Future.value; Future Function(Map options, int interval) onCallBuildBurstCapture = (options, interval) => Future.value(); -Future?> Function(void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) - onCallStartBurstCapture = (onProgress, onStopFailed) => Future.value(); +Future?> Function( + void Function(double)? onProgress, + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) + onCallStartBurstCapture = + (onProgress, onStopFailed, onCapturing) => Future.value(); Future Function() onCallStopBurstCapture = Future.value; Future Function() onCallGetMultiBracketCaptureBuilder = Future.value; Future Function(Map options, int interval) onCallBuildMultiBracketCapture = (options, interval) => Future.value(); -Future?> Function(void Function(double)? onProgress, - void Function(Exception exception)? onStopFailed) +Future?> Function( + void Function(double)? onProgress, + void Function(Exception exception)? onStopFailed, + void Function(CapturingStatusEnum status)? onCapturing) onCallStartMultiBracketCapture = - (onProgress, onStopFailed) => Future.value(); + (onProgress, onStopFailed, onCapturing) => Future.value(); Future Function() onCallStopMultiBracketCapture = Future.value; Future Function() onCallGetContinuousCaptureBuilder = Future.value; Future Function(Map options, int interval) - onCallBuildContinuousCapture = (options, interval) => Future.value(); -Future?> Function(void Function(double)? onProgress) - onCallStartContinuousCapture = (onProgress) => Future.value(); +onCallBuildContinuousCapture = (options, interval) => Future.value(); +Future?> Function(void Function(double)? onProgress, + void Function(CapturingStatusEnum status)? onCapturing) +onCallStartContinuousCapture = (onProgress, onCapturing) => Future.value(); Future Function(List optionNames) onCallGetOptions = (optionNames) => Future.value(Options()); diff --git a/kotlin-multiplatform/build.gradle.kts b/kotlin-multiplatform/build.gradle.kts index 93f3565c7a..d6514eaa19 100644 --- a/kotlin-multiplatform/build.gradle.kts +++ b/kotlin-multiplatform/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { dokkaPlugin("org.jetbrains.dokka:versioning-plugin:1.9.10") } -val thetaClientVersion = "1.9.1" +val thetaClientVersion = "1.10.0" group = "com.ricoh360.thetaclient" version = thetaClientVersion diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt index a549156bd2..734533a759 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt @@ -6592,6 +6592,11 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? * Capture Status */ enum class CaptureStatusEnum { + /** + * Undefined value + */ + UNKNOWN, + /** * Capture status * Performing continuously shoot @@ -6638,7 +6643,14 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? * Capture status * Waiting for retrospective video... */ - RETROSPECTIVE_IMAGE_RECORDING; + RETROSPECTIVE_IMAGE_RECORDING, + + /** + * Capture status + * Performing burst shooting + */ + BURST_SHOOTING, + ; companion object { /** @@ -6649,6 +6661,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? */ internal fun get(captureStatus: CaptureStatus): CaptureStatusEnum { return when (captureStatus) { + CaptureStatus.UNKNOWN -> UNKNOWN CaptureStatus.SHOOTING -> SHOOTING CaptureStatus.IDLE -> IDLE CaptureStatus.SELF_TIMER_COUNTDOWN -> SELF_TIMER_COUNTDOWN @@ -6657,6 +6670,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? CaptureStatus.TIME_SHIFT_SHOOTING -> TIME_SHIFT_SHOOTING CaptureStatus.CONTINUOUS_SHOOTING -> CONTINUOUS_SHOOTING CaptureStatus.RETROSPECTIVE_IMAGE_RECORDING -> RETROSPECTIVE_IMAGE_RECORDING + CaptureStatus.BURST_SHOOTING -> BURST_SHOOTING } } } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/BurstCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/BurstCapture.kt index 05ea5d8b6e..42a0adf7f5 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/BurstCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/BurstCapture.kt @@ -11,6 +11,7 @@ import com.ricoh360.thetaclient.transferred.BurstMaxExposureTime import com.ricoh360.thetaclient.transferred.BurstOption import com.ricoh360.thetaclient.transferred.BurstOrder import com.ricoh360.thetaclient.transferred.CaptureMode +import com.ricoh360.thetaclient.transferred.CaptureStatus import com.ricoh360.thetaclient.transferred.CommandApiResponse import com.ricoh360.thetaclient.transferred.CommandState import com.ricoh360.thetaclient.transferred.Options @@ -68,6 +69,13 @@ class BurstCapture private constructor( */ fun onProgress(completion: Float) + /** + * Called when change capture status. + * + * @param status Capturing status + */ + fun onCapturing(status: CapturingStatusEnum) {} + /** * Called when stopCapture error occurs. * @@ -91,9 +99,27 @@ class BurstCapture private constructor( } private suspend fun monitorCommandStatus(id: String, callback: StartCaptureCallback) { + val monitor = CaptureStatusMonitor( + endpoint, + onChangeStatus = { newStatus, _ -> + when (newStatus) { + CaptureStatus.SELF_TIMER_COUNTDOWN -> callback.onCapturing( + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + + else -> callback.onCapturing(CapturingStatusEnum.CAPTURING) + } + }, + onError = { error -> + println("CaptureStatusMonitor error: ${error.message}") + }, + checkStatusCommandInterval, + 1 + ) try { var response: CommandApiResponse? = null var state = CommandState.IN_PROGRESS + monitor.start() while (state == CommandState.IN_PROGRESS) { delay(timeMillis = checkStatusCommandInterval) response = ThetaApi.callStatusApi( @@ -103,6 +129,7 @@ class BurstCapture private constructor( callback.onProgress(completion = response.progress?.completion ?: 0f) state = response.state } + monitor.stop() if (response?.state == CommandState.DONE) { val captureResponse = response as StartCaptureResponse @@ -120,11 +147,26 @@ class BurstCapture private constructor( callback.onCaptureCompleted(fileUrls = null) // canceled } } catch (e: JsonConvertException) { - callback.onCaptureFailed(exception = ThetaRepository.ThetaWebApiException(message = e.message ?: e.toString())) + monitor.stop() + callback.onCaptureFailed( + exception = ThetaRepository.ThetaWebApiException( + message = e.message ?: e.toString() + ) + ) } catch (e: ResponseException) { - callback.onCaptureFailed(exception = ThetaRepository.ThetaWebApiException.create(exception = e)) + monitor.stop() + callback.onCaptureFailed( + exception = ThetaRepository.ThetaWebApiException.create( + exception = e + ) + ) } catch (e: Exception) { - callback.onCaptureFailed(exception = ThetaRepository.NotConnectedException(message = e.message ?: e.toString())) + monitor.stop() + callback.onCaptureFailed( + exception = ThetaRepository.NotConnectedException( + message = e.message ?: e.toString() + ) + ) } } @@ -149,7 +191,6 @@ class BurstCapture private constructor( return@launch } - delay(timeMillis = checkStatusCommandInterval) startCaptureResponse.id?.let { monitorCommandStatus(it, callback) } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/Capture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/Capture.kt index e4bea546aa..9862b1d2e0 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/Capture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/Capture.kt @@ -6,6 +6,23 @@ import com.ricoh360.thetaclient.transferred.UnknownResponse import io.ktor.client.call.body import io.ktor.client.statement.HttpResponse +/** + * Capturing status + * + * Identify the self-timer during capture + */ +enum class CapturingStatusEnum { + /** + * Capture in progress + */ + CAPTURING, + + /** + * Self-timer in progress + */ + SELF_TIMER_COUNTDOWN, +} + /* * Capture * @@ -29,8 +46,8 @@ abstract class Capture internal constructor(internal val options: Options) { * * @return Video file format */ - internal fun getVideoFileFormat() = options.fileFormat?.let { it -> - ThetaRepository.FileFormatEnum.get(it).let { + internal fun getVideoFileFormat() = options.fileFormat?.let { fileFormat -> + ThetaRepository.FileFormatEnum.get(fileFormat).let { ThetaRepository.VideoFileFormatEnum.get(it) } } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/CaptureStatusMonitor.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/CaptureStatusMonitor.kt index 722f110db5..9bcebee243 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/CaptureStatusMonitor.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/CaptureStatusMonitor.kt @@ -4,6 +4,7 @@ import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.transferred.CaptureStatus import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -12,11 +13,13 @@ internal class CaptureStatusMonitor( var onChangeStatus: ((newStatus: CaptureStatus, oldStatus: CaptureStatus?) -> Unit), var onError: ((error: Throwable) -> Unit), val checkStateInterval: Long = CHECK_STATE_INTERVAL, + val checkShootingIdleCount: Int = CHECK_SHOOTING_IDLE_COUNT, ) { private var isStartMonitor = false private val scope = CoroutineScope(Dispatchers.Default) var currentStatus: CaptureStatus? = null var lastException: Throwable? = null + var job: Job? = null companion object { private const val CHECK_STATE_INTERVAL = 1000L @@ -25,9 +28,12 @@ internal class CaptureStatusMonitor( } fun start() { + if (isStartMonitor) { + return + } isStartMonitor = true - scope.launch { - var idleCount = CHECK_SHOOTING_IDLE_COUNT + job = scope.launch { + var idleCount = checkShootingIdleCount while (isStartMonitor) { when (val status = getCaptureStatus()) { CaptureStatus.IDLE -> { @@ -43,21 +49,27 @@ internal class CaptureStatusMonitor( } else -> { - idleCount = CHECK_SHOOTING_IDLE_COUNT + idleCount = checkShootingIdleCount updateStatus(status) } } - delay(checkStateInterval) + if (isStartMonitor) { + delay(checkStateInterval) + } } } } fun stop() { isStartMonitor = false + job?.let { + it.cancel() + job = null + } } private fun updateStatus(status: CaptureStatus) { - if (currentStatus == status) { + if (!isStartMonitor || currentStatus == status) { return } val oldStatus = currentStatus diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCapture.kt index 396fe1a0af..9af3014290 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCapture.kt @@ -50,6 +50,13 @@ class CompositeIntervalCapture private constructor( */ fun onProgress(completion: Float) + /** + * Called when change capture status. + * + * @param status Capturing status + */ + fun onCapturing(status: CapturingStatusEnum) {} + /** * Called when stopCapture error occurs. * @@ -73,9 +80,27 @@ class CompositeIntervalCapture private constructor( } private suspend fun monitorCommandStatus(id: String, callback: StartCaptureCallback) { + val monitor = CaptureStatusMonitor( + endpoint, + onChangeStatus = { newStatus, _ -> + when (newStatus) { + CaptureStatus.SELF_TIMER_COUNTDOWN -> callback.onCapturing( + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + + else -> callback.onCapturing(CapturingStatusEnum.CAPTURING) + } + }, + onError = { error -> + println("CaptureStatusMonitor error: ${error.message}") + }, + checkStatusCommandInterval, + 1 + ) try { var response: CommandApiResponse? = null var state = CommandState.IN_PROGRESS + monitor.start() while (state == CommandState.IN_PROGRESS) { delay(timeMillis = checkStatusCommandInterval) response = ThetaApi.callStatusApi( @@ -85,6 +110,7 @@ class CompositeIntervalCapture private constructor( callback.onProgress(completion = response.progress?.completion ?: 0f) state = response.state } + monitor.stop() if (response?.state == CommandState.DONE) { val captureResponse = response as StartCaptureResponse @@ -102,10 +128,13 @@ class CompositeIntervalCapture private constructor( callback.onCaptureCompleted(fileUrls = null) // canceled } } catch (e: JsonConvertException) { + monitor.stop() callback.onCaptureFailed(exception = ThetaRepository.ThetaWebApiException(message = e.message ?: e.toString())) } catch (e: ResponseException) { + monitor.stop() callback.onCaptureFailed(exception = ThetaRepository.ThetaWebApiException.create(exception = e)) } catch (e: Exception) { + monitor.stop() callback.onCaptureFailed(exception = ThetaRepository.NotConnectedException(message = e.message ?: e.toString())) } } @@ -131,7 +160,6 @@ class CompositeIntervalCapture private constructor( return@launch } - delay(timeMillis = checkStatusCommandInterval) startCaptureResponse.id?.let { monitorCommandStatus(it, callback) } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ContinuousCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ContinuousCapture.kt index 5c0da5b48b..673d660574 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ContinuousCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ContinuousCapture.kt @@ -4,6 +4,7 @@ import com.ricoh360.thetaclient.CHECK_COMMAND_STATUS_INTERVAL import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode +import com.ricoh360.thetaclient.transferred.CaptureStatus import com.ricoh360.thetaclient.transferred.CommandApiResponse import com.ricoh360.thetaclient.transferred.CommandState import com.ricoh360.thetaclient.transferred.Options @@ -49,7 +50,7 @@ class ContinuousCapture private constructor( * @return Photo file format */ fun getFileFormat() = options.fileFormat?.let { format -> - ThetaRepository.FileFormatEnum.get(format)?.let { + ThetaRepository.FileFormatEnum.get(format).let { ThetaRepository.PhotoFileFormatEnum.get(it) } } @@ -75,6 +76,13 @@ class ContinuousCapture private constructor( */ fun onProgress(completion: Float) + /** + * Called when change capture status. + * + * @param status Capturing status + */ + fun onCapturing(status: CapturingStatusEnum) {} + /** * Called when error occurs. * @@ -91,9 +99,27 @@ class ContinuousCapture private constructor( } private suspend fun monitorCommandStatus(id: String, callback: StartCaptureCallback) { + val monitor = CaptureStatusMonitor( + endpoint, + onChangeStatus = { newStatus, _ -> + when (newStatus) { + CaptureStatus.SELF_TIMER_COUNTDOWN -> callback.onCapturing( + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + + else -> callback.onCapturing(CapturingStatusEnum.CAPTURING) + } + }, + onError = { error -> + println("CaptureStatusMonitor error: ${error.message}") + }, + checkStatusCommandInterval, + 1 + ) try { var response: CommandApiResponse? = null var state = CommandState.IN_PROGRESS + monitor.start() while (state == CommandState.IN_PROGRESS) { delay(timeMillis = checkStatusCommandInterval) response = ThetaApi.callStatusApi( @@ -103,6 +129,7 @@ class ContinuousCapture private constructor( callback.onProgress(completion = response.progress?.completion ?: 0f) state = response.state } + monitor.stop() if (response?.state == CommandState.DONE) { val captureResponse = response as StartCaptureResponse @@ -120,8 +147,10 @@ class ContinuousCapture private constructor( callback.onCaptureCompleted(fileUrls = null) // canceled } } catch (e: JsonConvertException) { + monitor.stop() callback.onCaptureFailed(exception = ThetaRepository.ThetaWebApiException(message = e.message ?: e.toString())) } catch (e: ResponseException) { + monitor.stop() try { val error: UnknownResponse = e.response.body() if (error.error?.isCanceledShootingCode() == true) { @@ -134,6 +163,7 @@ class ContinuousCapture private constructor( ) } } catch (exception: Exception) { + monitor.stop() callback.onCaptureFailed( exception = ThetaRepository.ThetaWebApiException( message = exception.message ?: exception.toString() @@ -141,6 +171,7 @@ class ContinuousCapture private constructor( ) } } catch (e: Exception) { + monitor.stop() callback.onCaptureFailed(exception = ThetaRepository.NotConnectedException(message = e.message ?: e.toString())) } } @@ -163,7 +194,6 @@ class ContinuousCapture private constructor( return@launch } - delay(timeMillis = checkStatusCommandInterval) startCaptureResponse.id?.let { monitorCommandStatus(it, callback) } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCapture.kt index 21b969b8ac..a89c38931e 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCapture.kt @@ -1,5 +1,6 @@ package com.ricoh360.thetaclient.capture +import com.ricoh360.thetaclient.CHECK_COMMAND_STATUS_INTERVAL import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.* @@ -7,11 +8,8 @@ import io.ktor.client.plugins.* import io.ktor.serialization.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.launch -private const val CHECK_STATE_INTERVAL = 1000L -private const val CHECK_STATE_RETRY = 3 private const val CHECK_SHOOTING_IDLE_COUNT = 2 private const val ERROR_GET_CAPTURE_STATUS = "Capture status cannot be retrieved." @@ -22,11 +20,20 @@ private const val ERROR_GET_CAPTURE_STATUS = "Capture status cannot be retrieved * @property cameraModel Camera model info. * @property options option of limitless interval capture */ -class LimitlessIntervalCapture private constructor(private val endpoint: String, private val cameraModel: ThetaRepository.ThetaModel? = null, options: Options) : +class LimitlessIntervalCapture private constructor( + private val endpoint: String, + private val cameraModel: ThetaRepository.ThetaModel? = null, + options: Options, + private val checkStatusCommandInterval: Long +) : Capture(options) { private val scope = CoroutineScope(Dispatchers.Default) + fun getCheckStatusCommandInterval(): Long { + return checkStatusCommandInterval + } + /** * Get shooting interval (sec.) for interval shooting. */ @@ -59,21 +66,13 @@ class LimitlessIntervalCapture private constructor(private val endpoint: String, * @param fileUrls URLs of the limitless interval capture */ fun onCaptureCompleted(fileUrls: List?) - } - internal suspend fun getCaptureStatus(): CaptureStatus? { - var retry = CHECK_STATE_RETRY - while (retry > 0) { - try { - val stateResponse = ThetaApi.callStateApi(endpoint) - return stateResponse.state._captureStatus - } catch (e: Exception) { - println("getCaptureStatus retry: $retry") - delay(CHECK_STATE_INTERVAL) - } - retry -= 1 - } - return null + /** + * Called when change capture status. + * + * @param status Capturing status + */ + fun onCapturing(status: CapturingStatusEnum) {} } /** @@ -82,28 +81,55 @@ class LimitlessIntervalCapture private constructor(private val endpoint: String, * @param callback Success or failure of the call */ fun startCapture(callback: StartCaptureCallback): LimitlessIntervalCapturing { - var isEndCapture = false + var captureStatusMonitor: CaptureStatusMonitor? = null fun callOnCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { - if (isEndCapture) { + if (captureStatusMonitor == null) { return } - isEndCapture = true + captureStatusMonitor?.stop() + captureStatusMonitor = null callback.onCaptureFailed(exception) } fun callOnCaptureCompleted(fileUrls: List?) { println("call callOnCaptureCompleted: $fileUrls") - if (isEndCapture) { + if (captureStatusMonitor == null) { return } - isEndCapture = true + captureStatusMonitor?.stop() + captureStatusMonitor = null callback.onCaptureCompleted(fileUrls) } + captureStatusMonitor = CaptureStatusMonitor( + endpoint, + onChangeStatus = { newStatus, _ -> + when (newStatus) { + CaptureStatus.SELF_TIMER_COUNTDOWN -> callback.onCapturing( + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + + CaptureStatus.IDLE -> callOnCaptureCompleted(null) + + else -> callback.onCapturing(CapturingStatusEnum.CAPTURING) + } + }, + onError = { error -> + println("CaptureStatusMonitor error: ${error.message}") + callOnCaptureFailed( + ThetaRepository.ThetaWebApiException( + ERROR_GET_CAPTURE_STATUS + ) + ) + }, + checkStatusCommandInterval, + CHECK_SHOOTING_IDLE_COUNT + ) + val captureCallback = object : StartCaptureCallback { override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { - if (!isEndCapture) { + if (captureStatusMonitor != null) { callback.onStopFailed(exception) } } @@ -137,33 +163,8 @@ class LimitlessIntervalCapture private constructor(private val endpoint: String, ) } - var idleCount = CHECK_SHOOTING_IDLE_COUNT - while (!isEndCapture) { - delay(CHECK_STATE_INTERVAL) - when (getCaptureStatus()) { - null -> { - callOnCaptureFailed( - ThetaRepository.ThetaWebApiException( - ERROR_GET_CAPTURE_STATUS - ) - ) - break - } - - CaptureStatus.IDLE -> { - idleCount -= 1 - // In the case of SC2, it becomes idle in the middle, so wait multiple times - if (idleCount <= 0) { - break - } - } - - else -> { - idleCount = CHECK_SHOOTING_IDLE_COUNT - } - } - } - callOnCaptureCompleted(null) + captureStatusMonitor?.start() + } return LimitlessIntervalCapturing( endpoint = endpoint, @@ -177,7 +178,11 @@ class LimitlessIntervalCapture private constructor(private val endpoint: String, * @property endpoint URL of Theta web API endpoint * @property cameraModel Camera model info. */ - class Builder internal constructor(private val endpoint: String, val cameraModel: ThetaRepository.ThetaModel? = null) : Capture.Builder() { + class Builder internal constructor( + private val endpoint: String, + val cameraModel: ThetaRepository.ThetaModel? = null + ) : Capture.Builder() { + private var interval: Long? = null /** * Builds an instance of a LimitlessIntervalCapture that has all the combined parameters of the Options that have been added to the Builder. @@ -196,10 +201,15 @@ class LimitlessIntervalCapture private constructor(private val endpoint: String, options.captureNumber = 0 // Unlimited (_limitless) when (cameraModel) { - ThetaRepository.ThetaModel.THETA_X -> options._shootingMethod = ShootingMethod.INTERVAL + ThetaRepository.ThetaModel.THETA_X -> options._shootingMethod = + ShootingMethod.INTERVAL + else -> {} } - ThetaApi.callSetOptionsCommand(endpoint = endpoint, params = SetOptionsParams(options)).error?.let { + ThetaApi.callSetOptionsCommand( + endpoint = endpoint, + params = SetOptionsParams(options) + ).error?.let { throw ThetaRepository.ThetaWebApiException(message = it.message) } } catch (e: JsonConvertException) { @@ -211,7 +221,17 @@ class LimitlessIntervalCapture private constructor(private val endpoint: String, } catch (e: Exception) { throw ThetaRepository.NotConnectedException(message = e.message ?: e.toString()) } - return LimitlessIntervalCapture(endpoint = endpoint, cameraModel = cameraModel, options = options) + return LimitlessIntervalCapture( + endpoint = endpoint, + cameraModel = cameraModel, + options = options, + checkStatusCommandInterval = interval ?: CHECK_COMMAND_STATUS_INTERVAL + ) + } + + fun setCheckStatusCommandInterval(timeMillis: Long): Builder { + this.interval = timeMillis + return this } /** diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCapture.kt index f62761b877..d2f2c0c853 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCapture.kt @@ -68,6 +68,13 @@ class MultiBracketCapture private constructor( */ fun onProgress(completion: Float) + /** + * Called when change capture status. + * + * @param status Capturing status + */ + fun onCapturing(status: CapturingStatusEnum) {} + /** * Called when stopCapture error occurs. * @@ -98,9 +105,27 @@ class MultiBracketCapture private constructor( * @param callback calls according to the progress. */ private suspend fun monitorCommandStatus(id: String, callback: StartCaptureCallback) { + val monitor = CaptureStatusMonitor( + endpoint, + onChangeStatus = { newStatus, _ -> + when (newStatus) { + CaptureStatus.SELF_TIMER_COUNTDOWN -> callback.onCapturing( + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + + else -> callback.onCapturing(CapturingStatusEnum.CAPTURING) + } + }, + onError = { error -> + println("CaptureStatusMonitor error: ${error.message}") + }, + checkStatusCommandInterval, + 1 + ) try { var response: CommandApiResponse? = null var state = CommandState.IN_PROGRESS + monitor.start() while (state == CommandState.IN_PROGRESS) { delay(timeMillis = checkStatusCommandInterval) response = ThetaApi.callStatusApi( @@ -110,6 +135,7 @@ class MultiBracketCapture private constructor( callback.onProgress(completion = response.progress?.completion ?: 0f) state = response.state } + monitor.stop() if (state == CommandState.DONE) { val captureResponse = response as StartCaptureResponse @@ -133,12 +159,14 @@ class MultiBracketCapture private constructor( callback.onCaptureCompleted(fileUrls = null) // canceled } } catch (e: JsonConvertException) { + monitor.stop() callback.onCaptureFailed( exception = ThetaRepository.ThetaWebApiException( message = e.message ?: e.toString() ) ) } catch (e: ResponseException) { + monitor.stop() try { val error: UnknownResponse = e.response.body() if (error.error?.isCanceledShootingCode() == true) { @@ -158,6 +186,7 @@ class MultiBracketCapture private constructor( ) } } catch (e: Exception) { + monitor.stop() callback.onCaptureFailed( exception = ThetaRepository.NotConnectedException( message = e.message ?: e.toString() @@ -187,11 +216,16 @@ class MultiBracketCapture private constructor( } } + CaptureStatus.SELF_TIMER_COUNTDOWN -> callback.onCapturing( + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + CaptureStatus.BRACKET_SHOOTING -> { isCaptured = true + callback.onCapturing(CapturingStatusEnum.CAPTURING) } - else -> {} + else -> callback.onCapturing(CapturingStatusEnum.CAPTURING) } }, { e -> @@ -202,7 +236,7 @@ class MultiBracketCapture private constructor( ) ) }, - SC2_STATE_CHECK_INTERVAL, + checkStatusCommandInterval, ) captureStatusMonitor = monitor monitor.start() @@ -234,7 +268,6 @@ class MultiBracketCapture private constructor( return@launch } - delay(timeMillis = checkStatusCommandInterval) when (cameraModel) { ThetaRepository.ThetaModel.THETA_SC2, ThetaRepository.ThetaModel.THETA_SC2_B -> { monitorCaptureStatus(callback) @@ -323,7 +356,10 @@ class MultiBracketCapture private constructor( endpoint = endpoint, cameraModel = cameraModel, options = options, - checkStatusCommandInterval = interval ?: CHECK_COMMAND_STATUS_INTERVAL + checkStatusCommandInterval = interval ?: when (cameraModel) { + ThetaRepository.ThetaModel.THETA_SC2, ThetaRepository.ThetaModel.THETA_SC2_B -> SC2_STATE_CHECK_INTERVAL + else -> CHECK_COMMAND_STATUS_INTERVAL + } ) } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/PhotoCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/PhotoCapture.kt index 86a5950dba..595b226efa 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/PhotoCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/PhotoCapture.kt @@ -17,10 +17,15 @@ import kotlinx.coroutines.* class PhotoCapture private constructor( private val endpoint: String, options: Options, + private val checkStatusCommandInterval: Long ) : PhotoCaptureBase(options) { private val scope = CoroutineScope(Dispatchers.Default) + fun getCheckStatusCommandInterval(): Long { + return checkStatusCommandInterval + } + /** * Get image processing filter. * @@ -51,11 +56,11 @@ class PhotoCapture private constructor( fun onSuccess(fileUrl: String?) /** - * Called when state "inProgress". + * Called when change capture status. * - * @param completion Progress rate of command executed + * @param status Capturing status */ - fun onProgress(completion: Float) {} + fun onCapturing(status: CapturingStatusEnum) {} /** * Called when error occurs. @@ -73,21 +78,41 @@ class PhotoCapture private constructor( fun takePicture(callback: TakePictureCallback) { scope.launch { lateinit var takePictureResponse: TakePictureResponse + val monitor = CaptureStatusMonitor( + endpoint, + { newStatus, _ -> + when (newStatus) { + CaptureStatus.SELF_TIMER_COUNTDOWN -> callback.onCapturing( + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + + else -> callback.onCapturing(CapturingStatusEnum.CAPTURING) + } + }, + { error -> + println("CaptureStatusMonitor error: ${error.message}") + }, + checkStatusCommandInterval, + 1 + ) try { takePictureResponse = ThetaApi.callTakePictureCommand(endpoint = endpoint) + monitor.start() val id = takePictureResponse.id while (takePictureResponse.state == CommandState.IN_PROGRESS) { - delay(timeMillis = CHECK_COMMAND_STATUS_INTERVAL) + delay(timeMillis = checkStatusCommandInterval) takePictureResponse = ThetaApi.callStatusApi( endpoint = endpoint, params = StatusApiParams(id = id) ) as TakePictureResponse - callback.onProgress(completion = takePictureResponse.progress?.completion ?: 0f) } + monitor.stop() } catch (e: JsonConvertException) { + monitor.stop() callback.onError(exception = ThetaRepository.ThetaWebApiException(message = e.message ?: e.toString())) return@launch } catch (e: ResponseException) { + monitor.stop() if (isCanceledShootingResponse(e.response)) { callback.onSuccess(fileUrl = null) // canceled } else { @@ -95,6 +120,7 @@ class PhotoCapture private constructor( } return@launch } catch (e: Exception) { + monitor.stop() callback.onError(exception = ThetaRepository.NotConnectedException(message = e.message ?: e.toString())) return@launch } @@ -123,6 +149,8 @@ class PhotoCapture private constructor( private val endpoint: String, private val cameraModel: ThetaRepository.ThetaModel? = null ) : PhotoCaptureBase.Builder() { + private var interval: Long? = null + internal fun isPreset(): Boolean { return options._preset != null && (cameraModel == ThetaRepository.ThetaModel.THETA_SC2 || cameraModel == ThetaRepository.ThetaModel.THETA_SC2_B) } @@ -166,7 +194,16 @@ class PhotoCapture private constructor( } catch (e: Exception) { throw ThetaRepository.NotConnectedException(e.message ?: e.toString()) } - return PhotoCapture(endpoint, options) + return PhotoCapture( + endpoint = endpoint, + options = options, + checkStatusCommandInterval = interval ?: CHECK_COMMAND_STATUS_INTERVAL, + ) + } + + fun setCheckStatusCommandInterval(timeMillis: Long): Builder { + this.interval = timeMillis + return this } /** diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCapture.kt index 6dc8d85740..a6fbdd8203 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCapture.kt @@ -53,6 +53,13 @@ class ShotCountSpecifiedIntervalCapture private constructor( */ fun onProgress(completion: Float) + /** + * Called when change capture status. + * + * @param status Capturing status + */ + fun onCapturing(status: CapturingStatusEnum) {} + /** * Called when stopCapture error occurs. * @@ -76,9 +83,27 @@ class ShotCountSpecifiedIntervalCapture private constructor( } private suspend fun monitorCommandStatus(id: String, callback: StartCaptureCallback) { + val monitor = CaptureStatusMonitor( + endpoint, + onChangeStatus = { newStatus, _ -> + when (newStatus) { + CaptureStatus.SELF_TIMER_COUNTDOWN -> callback.onCapturing( + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + + else -> callback.onCapturing(CapturingStatusEnum.CAPTURING) + } + }, + onError = { error -> + println("CaptureStatusMonitor error: ${error.message}") + }, + checkStatusCommandInterval, + 1 + ) try { var response: CommandApiResponse? = null var state = CommandState.IN_PROGRESS + monitor.start() while (state == CommandState.IN_PROGRESS) { delay(timeMillis = checkStatusCommandInterval) response = ThetaApi.callStatusApi( @@ -88,6 +113,7 @@ class ShotCountSpecifiedIntervalCapture private constructor( callback.onProgress(completion = response.progress?.completion ?: 0f) state = response.state } + monitor.stop() if (response?.state == CommandState.DONE) { val captureResponse = response as StartCaptureResponse @@ -105,10 +131,13 @@ class ShotCountSpecifiedIntervalCapture private constructor( callback.onCaptureCompleted(fileUrls = null) // canceled } } catch (e: JsonConvertException) { + monitor.stop() callback.onCaptureFailed(exception = ThetaRepository.ThetaWebApiException(message = e.message ?: e.toString())) } catch (e: ResponseException) { + monitor.stop() callback.onCaptureFailed(exception = ThetaRepository.ThetaWebApiException.create(exception = e)) } catch (e: Exception) { + monitor.stop() callback.onCaptureFailed(exception = ThetaRepository.NotConnectedException(message = e.message ?: e.toString())) } } @@ -127,11 +156,16 @@ class ShotCountSpecifiedIntervalCapture private constructor( } } + CaptureStatus.SELF_TIMER_COUNTDOWN -> callback.onCapturing( + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + CaptureStatus.SHOOTING -> { isCaptured = true + callback.onCapturing(CapturingStatusEnum.CAPTURING) } - else -> {} + else -> callback.onCapturing(CapturingStatusEnum.CAPTURING) } }, { e -> captureStatusMonitor?.stop() @@ -140,7 +174,8 @@ class ShotCountSpecifiedIntervalCapture private constructor( message = e.message ?: e.toString() ) ) - }) + }, checkStatusCommandInterval + ) captureStatusMonitor = monitor monitor.start() } @@ -169,7 +204,6 @@ class ShotCountSpecifiedIntervalCapture private constructor( return@launch } - delay(timeMillis = checkStatusCommandInterval) when (cameraModel) { ThetaRepository.ThetaModel.THETA_SC2, ThetaRepository.ThetaModel.THETA_SC2_B -> { monitorCaptureStatus(callback) diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCapture.kt index c85c638477..e1bfeac8f6 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCapture.kt @@ -47,6 +47,13 @@ class TimeShiftCapture private constructor( */ fun onProgress(completion: Float) + /** + * Called when change capture status. + * + * @param status Capturing status + */ + fun onCapturing(status: CapturingStatusEnum) {} + /** * Called when stopCapture error occurs. * @@ -77,12 +84,30 @@ class TimeShiftCapture private constructor( */ fun startCapture(callback: StartCaptureCallback): TimeShiftCapturing { scope.launch { + val monitor = CaptureStatusMonitor( + endpoint, + { newStatus, _ -> + when (newStatus) { + CaptureStatus.SELF_TIMER_COUNTDOWN -> callback.onCapturing( + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + + else -> callback.onCapturing(CapturingStatusEnum.CAPTURING) + } + }, + { error -> + println("CaptureStatusMonitor error: ${error.message}") + }, + checkStatusCommandInterval, + 1 + ) lateinit var startCaptureResponse: StartCaptureResponse try { startCaptureResponse = ThetaApi.callStartCaptureCommand( endpoint = endpoint, params = StartCaptureParams(_mode = ShootingMode.TIME_SHIFT_SHOOTING) ) + monitor.start() /* * Note that Theta SC2 for business returns a response different from Theta X like this: @@ -102,6 +127,7 @@ class TimeShiftCapture private constructor( ) callback.onProgress(completion = response.progress?.completion ?: 0f) } + monitor.stop() if (response.state == CommandState.DONE) { val fileUrl: String? = when (response.name) { @@ -131,12 +157,14 @@ class TimeShiftCapture private constructor( } } } catch (e: JsonConvertException) { + monitor.stop() callback.onCaptureFailed( exception = ThetaRepository.ThetaWebApiException( message = e.message ?: e.toString() ) ) } catch (e: ResponseException) { + monitor.stop() if (isCanceledShootingResponse(e.response)) { callback.onCaptureCompleted(fileUrl = null) // canceled } else { @@ -147,6 +175,7 @@ class TimeShiftCapture private constructor( ) } } catch (e: Exception) { + monitor.stop() callback.onCaptureFailed( exception = ThetaRepository.NotConnectedException( message = e.message ?: e.toString() diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/VideoCapture.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/VideoCapture.kt index 328c3dc7c5..35b24274ef 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/VideoCapture.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/capture/VideoCapture.kt @@ -1,5 +1,6 @@ package com.ricoh360.thetaclient.capture +import com.ricoh360.thetaclient.CHECK_COMMAND_STATUS_INTERVAL import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.* @@ -21,11 +22,19 @@ private const val ERROR_GET_CAPTURE_STATUS = "Capture status cannot be retrieved * @property endpoint URL of Theta web API endpoint * @property options option of video capture */ -class VideoCapture private constructor(private val endpoint: String, options: Options) : +class VideoCapture private constructor( + private val endpoint: String, + options: Options, + private val checkStatusCommandInterval: Long +) : Capture(options) { private val scope = CoroutineScope(Dispatchers.Default) + fun getCheckStatusCommandInterval(): Long { + return checkStatusCommandInterval + } + /** * Get maximum recordable time (in seconds) of the camera. * @@ -69,6 +78,13 @@ class VideoCapture private constructor(private val endpoint: String, options: Op * @param fileUrl URL of the video capture */ fun onCaptureCompleted(fileUrl: String?) + + /** + * Called when change capture status. + * + * @param status Capturing status + */ + fun onCapturing(status: CapturingStatusEnum) {} } internal suspend fun getCaptureStatus(): CaptureStatus? { @@ -92,28 +108,55 @@ class VideoCapture private constructor(private val endpoint: String, options: Op * @param callback Success or failure of the call */ fun startCapture(callback: StartCaptureCallback): VideoCapturing { - var isEndCapture = false + var captureStatusMonitor: CaptureStatusMonitor? = null fun callOnCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { - if (isEndCapture) { + if (captureStatusMonitor == null) { return } - isEndCapture = true + captureStatusMonitor?.stop() + captureStatusMonitor = null callback.onCaptureFailed(exception) } fun callOnCaptureCompleted(fileUrl: String?) { println("call callOnCaptureCompleted: $fileUrl") - if (isEndCapture) { + if (captureStatusMonitor == null) { return } - isEndCapture = true + captureStatusMonitor?.stop() + captureStatusMonitor = null callback.onCaptureCompleted(fileUrl) } + captureStatusMonitor = CaptureStatusMonitor( + endpoint, + onChangeStatus = { newStatus, _ -> + when (newStatus) { + CaptureStatus.SELF_TIMER_COUNTDOWN -> callback.onCapturing( + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + + CaptureStatus.IDLE -> callOnCaptureCompleted(null) + + else -> callback.onCapturing(CapturingStatusEnum.CAPTURING) + } + }, + onError = { error -> + println("CaptureStatusMonitor error: ${error.message}") + callOnCaptureFailed( + ThetaRepository.ThetaWebApiException( + ERROR_GET_CAPTURE_STATUS + ) + ) + }, + checkStatusCommandInterval, + CHECK_SHOOTING_IDLE_COUNT + ) + val captureCallback = object : StartCaptureCallback { override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { - if (!isEndCapture) { + if (captureStatusMonitor != null) { callback.onStopFailed(exception) } } @@ -143,33 +186,7 @@ class VideoCapture private constructor(private val endpoint: String, options: Op ) } - var idleCount = CHECK_SHOOTING_IDLE_COUNT - while (!isEndCapture) { - delay(CHECK_STATE_INTERVAL) - when (getCaptureStatus()) { - null -> { - callOnCaptureFailed( - ThetaRepository.ThetaWebApiException( - ERROR_GET_CAPTURE_STATUS - ) - ) - break - } - - CaptureStatus.IDLE -> { - idleCount -= 1 - // In the case of SC2, it becomes idle in the middle, so wait multiple times - if (idleCount <= 0) { - break - } - } - - else -> { - idleCount = CHECK_SHOOTING_IDLE_COUNT - } - } - } - callOnCaptureCompleted(null) + captureStatusMonitor?.start() } return VideoCapturing(endpoint, captureCallback) } @@ -180,6 +197,7 @@ class VideoCapture private constructor(private val endpoint: String, options: Op * @property endpoint URL of Theta web API endpoint */ class Builder internal constructor(private val endpoint: String) : Capture.Builder() { + private var interval: Long? = null /** * Builds an instance of a VideoCapture that has all the combined parameters of the Options that have been added to the Builder. @@ -209,7 +227,11 @@ class VideoCapture private constructor(private val endpoint: String, options: Op } catch (e: Exception) { throw ThetaRepository.NotConnectedException(e.message ?: e.toString()) } - return VideoCapture(endpoint, options) + return VideoCapture( + endpoint = endpoint, + options = options, + checkStatusCommandInterval = interval ?: CHECK_COMMAND_STATUS_INTERVAL + ) } /** @@ -232,6 +254,11 @@ class VideoCapture private constructor(private val endpoint: String, options: Op fun setFileFormat(fileFormat: ThetaRepository.VideoFileFormatEnum): Builder = setVideoFileFormat(fileFormat) + fun setCheckStatusCommandInterval(timeMillis: Long): Builder { + this.interval = timeMillis + return this + } + // TODO: Add set video option property } } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/stateApi.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/stateApi.kt index 7fd40c101f..750dfc2995 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/stateApi.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/stateApi.kt @@ -6,7 +6,6 @@ package com.ricoh360.thetaclient.transferred import io.ktor.http.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonNames /** * /osc/state api request @@ -222,58 +221,81 @@ internal data class CameraState( val _batteryTemp: Int? = null, ) +internal object CaptureStatusSerializer : + SerialNameEnumIgnoreUnknownSerializer(CaptureStatus.entries, CaptureStatus.UNKNOWN) + /** * Capture status */ -@Serializable -internal enum class CaptureStatus { +@Serializable(with = CaptureStatusSerializer::class) +internal enum class CaptureStatus : SerialNameEnum { + /** + * Undefined value + */ + UNKNOWN, + /** * shooting: Performing continuously shoots, */ - @SerialName("shooting") - SHOOTING, + SHOOTING { + override val serialName: String = "shooting" + }, /** * idle: In standby, */ - @SerialName("idle") - IDLE, + IDLE { + override val serialName: String = "idle" + }, /** * self-timer countdown: Self-timer is operating, */ - @SerialName("self-timer countdown") - SELF_TIMER_COUNTDOWN, + SELF_TIMER_COUNTDOWN { + override val serialName: String = "self-timer countdown" + }, /** * bracket shooting: Performing multi bracket shooting, */ - @SerialName("bracket shooting") - BRACKET_SHOOTING, + BRACKET_SHOOTING { + override val serialName: String = "bracket shooting" + }, /** * converting: Converting post file…, */ - @SerialName("converting") - CONVERTING, + CONVERTING { + override val serialName: String = "converting" + }, /** * timeShift shooting: Performing timeShift shooting, */ - @SerialName("timeShift shooting") - TIME_SHIFT_SHOOTING, + TIME_SHIFT_SHOOTING { + override val serialName: String = "timeShift shooting" + }, /** * continuous shooting: Performing continuous shooting, */ - @SerialName("continuous shooting") - CONTINUOUS_SHOOTING, + CONTINUOUS_SHOOTING { + override val serialName: String = "continuous shooting" + }, /** * retrospective image recording: Waiting for retrospective video… */ - @SerialName("retrospective image recording") - RETROSPECTIVE_IMAGE_RECORDING, + RETROSPECTIVE_IMAGE_RECORDING { + override val serialName: String = "retrospective image recording" + }, + + /** + * burst shooting + */ + BURST_SHOOTING { + override val serialName: String = "burst shooting" + }, } /** diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/BurstCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/BurstCaptureTest.kt index 8142eb4378..63ee1a44b0 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/BurstCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/BurstCaptureTest.kt @@ -3,6 +3,7 @@ package com.ricoh360.thetaclient.capture import com.goncalossilva.resources.Resource import com.ricoh360.thetaclient.CheckRequest import com.ricoh360.thetaclient.MockApiClient +import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.BurstBracketStep import com.ricoh360.thetaclient.transferred.BurstCaptureNum @@ -39,8 +40,10 @@ class BurstCaptureTest { ) private val thetaBurstCaptureNum = ThetaRepository.BurstCaptureNumEnum.BURST_CAPTURE_NUM_1 private val thetaBurstBracketStep = ThetaRepository.BurstBracketStepEnum.BRACKET_STEP_0_0 - private val thetaBurstCompensation = ThetaRepository.BurstCompensationEnum.BURST_COMPENSATION_0_0 - private val thetaBurstMaxExposureTime = ThetaRepository.BurstMaxExposureTimeEnum.MAX_EXPOSURE_TIME_1 + private val thetaBurstCompensation = + ThetaRepository.BurstCompensationEnum.BURST_COMPENSATION_0_0 + private val thetaBurstMaxExposureTime = + ThetaRepository.BurstMaxExposureTimeEnum.MAX_EXPOSURE_TIME_1 private val thetaBurstEnableIsoControl = ThetaRepository.BurstEnableIsoControlEnum.OFF private val thetaBurstOrder = ThetaRepository.BurstOrderEnum.BURST_BRACKET_ORDER_0 private val endpoint = "http://192.168.1.1:80/" @@ -69,26 +72,40 @@ class BurstCaptureTest { Resource("src/commonTest/resources/BurstCapture/start_capture_progress.json").readText(), Resource("src/commonTest/resources/BurstCapture/start_capture_done.json").readText(), ) + val stateShootingResponse = + Resource("src/commonTest/resources/BurstCapture/state_burst_shooting.json").readText() var counter = 0 MockApiClient.onRequest = { request -> - val index = counter++ - + val index = counter + if (request.url.encodedPath != "/osc/state") { + counter++ + } // check request - when (index) { + val response = when (index) { 0 -> { CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.IMAGE) + responseArray[index] } 1 -> { CheckRequest.checkSetOptions(request = request, burstOption = burstOption) + responseArray[index] } 2 -> { CheckRequest.checkCommandName(request, "camera.startCapture") + responseArray[index] + } + + else -> { + when (request.url.encodedPath) { + "/osc/state" -> stateShootingResponse + else -> responseArray[index] + } } } - ByteReadChannel(responseArray[index]) + ByteReadChannel(response) } val deferred = CompletableDeferred() @@ -105,6 +122,7 @@ class BurstCaptureTest { ) .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = null capture.startCapture(object : BurstCapture.StartCaptureCallback { @@ -148,23 +166,40 @@ class BurstCaptureTest { // setup var isStop = false val deferredStart = CompletableDeferred() + val jsonOptionDone = "src/commonTest/resources/setOptions/set_options_done.json" + val jsonProgress = "src/commonTest/resources/BurstCapture/start_capture_progress.json" + val jsonStopCapture = "src/commonTest/resources/BurstCapture/stop_capture_done.json" + val jsonCaptureEmpty = "src/commonTest/resources/BurstCapture/start_capture_done_empty.json" + val jsonStateShooting = "src/commonTest/resources/BurstCapture/state_burst_shooting.json" + MockApiClient.onRequest = { request -> - val textBody = request.body as TextContent - val path = if (textBody.text.contains("camera.stopCapture")) { - isStop = true - "src/commonTest/resources/BurstCapture/stop_capture_done.json" - } else if (textBody.text.contains("camera.setOptions")) { - "src/commonTest/resources/setOptions/set_options_done.json" - } else { - if (!deferredStart.isCompleted) { - deferredStart.complete(Unit) + val path = when (request.url.encodedPath) { + "/osc/state" -> jsonStateShooting + "/osc/commands/status" -> { + if (!deferredStart.isCompleted) { + deferredStart.complete(Unit) + } + if (isStop) jsonCaptureEmpty else jsonProgress } - if (isStop) - "src/commonTest/resources/BurstCapture/start_capture_done_empty.json" - else - "src/commonTest/resources/BurstCapture/start_capture_progress.json" - } + else -> { + assertEquals(request.url.encodedPath, "/osc/commands/execute") + val textBody = request.body as TextContent + when { + textBody.text.contains("camera.setOptions") -> jsonOptionDone + textBody.text.contains("camera.startCapture") -> jsonProgress + textBody.text.contains("camera.stopCapture") -> { + isStop = true + jsonStopCapture + } + + else -> { + assertTrue(false) + "" + } + } + } + } ByteReadChannel(Resource(path).readText()) } @@ -183,7 +218,7 @@ class BurstCaptureTest { ) .setCheckStatusCommandInterval(100) .build() - + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = listOf() val capturing = capture.startCapture(object : BurstCapture.StartCaptureCallback { @@ -240,10 +275,18 @@ class BurstCaptureTest { Resource("src/commonTest/resources/BurstCapture/start_capture_progress.json").readText(), Resource("src/commonTest/resources/BurstCapture/start_capture_cancel.json").readText(), ) + val stateShootingResponse = + Resource("src/commonTest/resources/BurstCapture/state_burst_shooting.json").readText() var counter = 0 - MockApiClient.onRequest = { _ -> - val index = counter++ - ByteReadChannel(responseArray[index]) + MockApiClient.onRequest = { request -> + val response = when (request.url.encodedPath) { + "/osc/state" -> stateShootingResponse + else -> { + val index = counter++ + responseArray[index] + } + } + ByteReadChannel(response) } val deferred = CompletableDeferred() @@ -260,6 +303,7 @@ class BurstCaptureTest { ) .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = null capture.startCapture(object : BurstCapture.StartCaptureCallback { @@ -371,7 +415,11 @@ class BurstCaptureTest { .build() // check result - assertEquals(capture.getBurstMode(), ThetaRepository.BurstModeEnum.OFF, "set option _burstMode OFF") + assertEquals( + capture.getBurstMode(), + ThetaRepository.BurstModeEnum.OFF, + "set option _burstMode OFF" + ) } /** @@ -524,6 +572,7 @@ class BurstCaptureTest { thetaBurstEnableIsoControl, thetaBurstOrder ).build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute error response var deferred = CompletableDeferred() @@ -622,6 +671,7 @@ class BurstCaptureTest { thetaBurstOrder ) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute status error and json response var deferred = CompletableDeferred() @@ -924,4 +974,111 @@ class BurstCaptureTest { } } } + + /** + * Capturing status. + */ + @Test + fun capturingStatusTest() = runTest { + // setup + val optionResponse = + Resource("src/commonTest/resources/setOptions/set_options_done.json").readText() + val progressResponse = + Resource("src/commonTest/resources/BurstCapture/start_capture_progress.json").readText() + val stateShootingResponse = + Resource("src/commonTest/resources/BurstCapture/state_burst_shooting.json").readText() + val stateSelfTimerResponse = + Resource("src/commonTest/resources/BurstCapture/state_self_timer.json").readText() + val cancelResponse = + Resource("src/commonTest/resources/BurstCapture/start_capture_cancel.json").readText() + var counter = 0 + var stateCounter = 0 + MockApiClient.onRequest = { request -> + val index = counter++ + val response = when (index) { + 0, 1 -> { + optionResponse + } + + 3 -> progressResponse + else -> { + when (request.url.encodedPath) { + "/osc/state" -> { + stateCounter++ + when { + stateCounter < 2 -> stateSelfTimerResponse + else -> stateShootingResponse + } + } + + else -> { + when { + stateCounter < 3 -> progressResponse + else -> cancelResponse + } + } + } + } + } + ByteReadChannel(response) + } + val deferred = CompletableDeferred() + + // execute + val thetaRepository = ThetaRepository(endpoint) + thetaRepository.cameraModel = ThetaRepository.ThetaModel.THETA_X + val capture = thetaRepository.getBurstCaptureBuilder( + thetaBurstCaptureNum, + thetaBurstBracketStep, + thetaBurstCompensation, + thetaBurstMaxExposureTime, + thetaBurstEnableIsoControl, + thetaBurstOrder + ) + .setCheckStatusCommandInterval(100) + .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 + + var files: List? = null + capture.startCapture(object : BurstCapture.StartCaptureCallback { + override fun onCaptureCompleted(fileUrls: List?) { + files = fileUrls + deferred.complete(Unit) + } + + override fun onProgress(completion: Float) { + assertTrue(completion >= 0f, "onProgress") + } + + override fun onCapturing(status: CapturingStatusEnum) { + when { + stateCounter < 2 -> assertEquals( + status, + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + + else -> assertEquals(status, CapturingStatusEnum.CAPTURING) + } + } + + override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue(false, "error start burst shooting") + deferred.complete(Unit) + } + + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue(false, "onStopFailed") + } + }) + + runBlocking { + withTimeout(30_000) { + deferred.await() + } + } + + // check result + assertTrue(stateCounter > 2, "capTureStatus count") + assertNull(files, "shooting is canceled") + } } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCaptureTest.kt index 2cb4b53cfb..6a0457e18f 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/CompositeIntervalCaptureTest.kt @@ -3,6 +3,7 @@ package com.ricoh360.thetaclient.capture import com.goncalossilva.resources.Resource import com.ricoh360.thetaclient.CheckRequest import com.ricoh360.thetaclient.MockApiClient +import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode import io.ktor.client.network.sockets.ConnectTimeoutException @@ -17,6 +18,7 @@ import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertTrue @@ -47,26 +49,40 @@ class CompositeIntervalCaptureTest { Resource("src/commonTest/resources/CompositeIntervalCapture/start_capture_progress.json").readText(), Resource("src/commonTest/resources/CompositeIntervalCapture/start_capture_done.json").readText(), ) + val stateShootingResponse = + Resource("src/commonTest/resources/CompositeIntervalCapture/state_shooting.json").readText() var counter = 0 MockApiClient.onRequest = { request -> - val index = counter++ - + val index = counter + if (request.url.encodedPath != "/osc/state") { + counter++ + } // check request - when (index) { + val response = when (index) { 0 -> { CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.IMAGE) + responseArray[index] } 1 -> { CheckRequest.checkSetOptions(request = request, compositeShootingTime = 600) + responseArray[index] } 2 -> { CheckRequest.checkCommandName(request, "camera.startCapture") + responseArray[index] + } + + else -> { + when (request.url.encodedPath) { + "/osc/state" -> stateShootingResponse + else -> responseArray[index] + } } } - ByteReadChannel(responseArray[index]) + ByteReadChannel(response) } val deferred = CompletableDeferred() @@ -76,6 +92,7 @@ class CompositeIntervalCaptureTest { val capture = thetaRepository.getCompositeIntervalCaptureBuilder(600) .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = null capture.startCapture(object : CompositeIntervalCapture.StartCaptureCallback { @@ -119,23 +136,47 @@ class CompositeIntervalCaptureTest { // setup var isStop = false val deferredStart = CompletableDeferred() + val jsonOptionDone = "src/commonTest/resources/setOptions/set_options_done.json" + val jsonProgress = + "src/commonTest/resources/CompositeIntervalCapture/start_capture_progress.json" + val jsonStopCapture = + "src/commonTest/resources/CompositeIntervalCapture/stop_capture_done.json" + val jsonCaptureEmpty = + "src/commonTest/resources/CompositeIntervalCapture/start_capture_done_empty.json" + val jsonStateShooting = + "src/commonTest/resources/CompositeIntervalCapture/state_shooting.json" + MockApiClient.onRequest = { request -> - val textBody = request.body as TextContent - val path = if (textBody.text.contains("camera.stopCapture")) { - isStop = true - "src/commonTest/resources/CompositeIntervalCapture/stop_capture_done.json" - } else if (textBody.text.contains("camera.setOptions")) { - "src/commonTest/resources/setOptions/set_options_done.json" - } else { - if (!deferredStart.isCompleted) { - deferredStart.complete(Unit) + val path = when (request.url.encodedPath) { + "/osc/state" -> { + jsonStateShooting + } + + "/osc/commands/status" -> { + if (!deferredStart.isCompleted) { + deferredStart.complete(Unit) + } + if (isStop) jsonCaptureEmpty else jsonProgress } - if (isStop) - "src/commonTest/resources/CompositeIntervalCapture/start_capture_done_empty.json" - else - "src/commonTest/resources/CompositeIntervalCapture/start_capture_progress.json" - } + else -> { + assertEquals(request.url.encodedPath, "/osc/commands/execute") + val textBody = request.body as TextContent + when { + textBody.text.contains("camera.setOptions") -> jsonOptionDone + textBody.text.contains("camera.startCapture") -> jsonProgress + textBody.text.contains("camera.stopCapture") -> { + isStop = true + jsonStopCapture + } + + else -> { + assertTrue(false) + "" + } + } + } + } ByteReadChannel(Resource(path).readText()) } @@ -147,6 +188,7 @@ class CompositeIntervalCaptureTest { val capture = thetaRepository.getCompositeIntervalCaptureBuilder(600) .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = listOf() val capturing = @@ -204,10 +246,19 @@ class CompositeIntervalCaptureTest { Resource("src/commonTest/resources/CompositeIntervalCapture/start_capture_progress.json").readText(), Resource("src/commonTest/resources/CompositeIntervalCapture/start_capture_cancel.json").readText(), ) + val stateShootingResponse = + Resource("src/commonTest/resources/CompositeIntervalCapture/state_shooting.json").readText() var counter = 0 - MockApiClient.onRequest = { _ -> - val index = counter++ - ByteReadChannel(responseArray[index]) + MockApiClient.onRequest = { request -> + val response = when (request.url.encodedPath) { + "/osc/state" -> stateShootingResponse + else -> { + val index = counter++ + responseArray[index] + } + } + + ByteReadChannel(response) } val deferred = CompletableDeferred() @@ -217,6 +268,7 @@ class CompositeIntervalCaptureTest { val capture = thetaRepository.getCompositeIntervalCaptureBuilder(600) .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = listOf() capture.startCapture(object : CompositeIntervalCapture.StartCaptureCallback { @@ -437,7 +489,10 @@ class CompositeIntervalCaptureTest { val thetaRepository = ThetaRepository(endpoint) thetaRepository.cameraModel = ThetaRepository.ThetaModel.THETA_X - val capture = thetaRepository.getCompositeIntervalCaptureBuilder(600).build() + val capture = thetaRepository.getCompositeIntervalCaptureBuilder(600) + .setCheckStatusCommandInterval(100) + .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute error response var deferred = CompletableDeferred() @@ -528,7 +583,9 @@ class CompositeIntervalCaptureTest { val thetaRepository = ThetaRepository(endpoint) val capture = thetaRepository.getCompositeIntervalCaptureBuilder(600) + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute status error and json response var deferred = CompletableDeferred() @@ -831,4 +888,111 @@ class CompositeIntervalCaptureTest { } } } + + /** + * Capturing status. + */ + @Test + fun capturingStatusTest() = runTest { + // setup + var isStop = false + val jsonOptionDone = "src/commonTest/resources/setOptions/set_options_done.json" + val jsonProgress = + "src/commonTest/resources/CompositeIntervalCapture/start_capture_progress.json" + val jsonCaptureDone = + "src/commonTest/resources/CompositeIntervalCapture/start_capture_done.json" + val jsonStateShooting = + "src/commonTest/resources/CompositeIntervalCapture/state_shooting.json" + val jsonStateSelfTimer = + "src/commonTest/resources/CompositeIntervalCapture/state_self_timer.json" + + var stateCounter = 0 + MockApiClient.onRequest = { request -> + val path = when (request.url.encodedPath) { + "/osc/state" -> { + when (stateCounter++) { + 0 -> jsonStateSelfTimer + else -> { + isStop = true + jsonStateShooting + } + } + } + + "/osc/commands/status" -> { + if (isStop) jsonCaptureDone else jsonProgress + } + + else -> { + assertEquals(request.url.encodedPath, "/osc/commands/execute") + val textBody = request.body as TextContent + when { + textBody.text.contains("camera.setOptions") -> jsonOptionDone + textBody.text.contains("camera.startCapture") -> jsonProgress + + else -> { + assertTrue(false) + "" + } + } + } + } + ByteReadChannel(Resource(path).readText()) + } + + val deferred = CompletableDeferred() + + // execute + val thetaRepository = ThetaRepository(endpoint) + thetaRepository.cameraModel = ThetaRepository.ThetaModel.THETA_X + val capture = thetaRepository.getCompositeIntervalCaptureBuilder(600) + .setCheckStatusCommandInterval(100) + .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 + + var files: List? = listOf() + capture.startCapture(object : CompositeIntervalCapture.StartCaptureCallback { + override fun onCaptureCompleted(fileUrls: List?) { + files = fileUrls + deferred.complete(Unit) + } + + override fun onProgress(completion: Float) { + assertEquals(completion, 0f, "onProgress") + } + + override fun onCapturing(status: CapturingStatusEnum) { + when { + stateCounter < 2 -> assertEquals( + status, + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + + else -> assertEquals(status, CapturingStatusEnum.CAPTURING) + } + } + + override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue(false, "error start interval composite shooting") + deferred.complete(Unit) + } + + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue(false, "onStopFailed") + } + }) + + runBlocking { + withTimeout(7000) { + deferred.await() + } + } + + // check result + assertTrue(stateCounter >= 2, "capTureStatus count") + assertFalse( + files?.isEmpty() ?: true, + "done interval composite shooting" + ) + } } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ContinuousCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ContinuousCaptureTest.kt index ebe1b8d63a..1df02cf882 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ContinuousCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ContinuousCaptureTest.kt @@ -3,6 +3,7 @@ package com.ricoh360.thetaclient.capture import com.goncalossilva.resources.Resource import com.ricoh360.thetaclient.CheckRequest import com.ricoh360.thetaclient.MockApiClient +import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.* import io.ktor.client.network.sockets.* @@ -12,7 +13,6 @@ import kotlinx.coroutines.* import kotlinx.coroutines.test.runTest import kotlin.test.* -@OptIn(ExperimentalCoroutinesApi::class) class ContinuousCaptureTest { private val endpoint = "http://192.168.1.1:80/" @@ -40,22 +40,35 @@ class ContinuousCaptureTest { Resource("src/commonTest/resources/ContinuousCapture/start_capture_progress.json").readText(), Resource("src/commonTest/resources/ContinuousCapture/start_capture_done.json").readText(), ) + val stateShootingResponse = + Resource("src/commonTest/resources/ContinuousCapture/state_shooting.json").readText() var counter = 0 MockApiClient.onRequest = { request -> - val index = counter++ - + val index = counter + if (request.url.encodedPath != "/osc/state") { + counter++ + } // check request - when (index) { + val response = when (index) { 0 -> { CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.IMAGE) + responseArray[index] } 2 -> { CheckRequest.checkCommandName(request, "camera.startCapture") + responseArray[index] + } + + else -> { + when (request.url.encodedPath) { + "/osc/state" -> stateShootingResponse + else -> responseArray[index] + } } } - ByteReadChannel(responseArray[index]) + ByteReadChannel(response) } val deferred = CompletableDeferred() @@ -65,6 +78,7 @@ class ContinuousCaptureTest { val capture = thetaRepository.getContinuousCaptureBuilder() .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = null capture.startCapture(object : ContinuousCapture.StartCaptureCallback { @@ -161,7 +175,11 @@ class ContinuousCaptureTest { .build() // check result - assertEquals(capture.getFileFormat(), ThetaRepository.PhotoFileFormatEnum.IMAGE_5_5K, "set option fileformat IMAGE_5_5K") + assertEquals( + capture.getFileFormat(), + ThetaRepository.PhotoFileFormatEnum.IMAGE_5_5K, + "set option file format IMAGE_5_5K" + ) } /** @@ -460,7 +478,10 @@ class ContinuousCaptureTest { } 2 -> { - CheckRequest.checkGetOptions(request = request, optionNames = listOf("continuousNumber")) + CheckRequest.checkGetOptions( + request = request, + optionNames = listOf("continuousNumber") + ) } } @@ -477,4 +498,110 @@ class ContinuousCaptureTest { val continuousNumber = capture.getContinuousNumber() assertEquals(continuousNumber, ThetaRepository.ContinuousNumberEnum.MAX_20) } + + /** + * Capturing status. + */ + @Test + fun capturingStatusTest() = runTest { + // setup + val responseArray = arrayOf( + Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), + Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), + Resource("src/commonTest/resources/ContinuousCapture/start_capture_progress.json").readText(), + Resource("src/commonTest/resources/ContinuousCapture/start_capture_progress.json").readText(), + Resource("src/commonTest/resources/ContinuousCapture/start_capture_progress.json").readText(), + Resource("src/commonTest/resources/ContinuousCapture/start_capture_done.json").readText(), + ) + val stateSelfTimerResponse = + Resource("src/commonTest/resources/ContinuousCapture/state_self_timer.json").readText() + val stateShootingResponse = + Resource("src/commonTest/resources/ContinuousCapture/state_shooting.json").readText() + + var stateCounter = 0 + var counter = 0 + MockApiClient.onRequest = { request -> + val index = counter + if (request.url.encodedPath != "/osc/state") { + counter++ + } + // check request + val response = when (index) { + 0 -> { + CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.IMAGE) + responseArray[index] + } + + 2 -> { + CheckRequest.checkCommandName(request, "camera.startCapture") + responseArray[index] + } + + else -> { + when (request.url.encodedPath) { + "/osc/state" -> { + when (stateCounter++) { + 0 -> stateSelfTimerResponse + else -> stateShootingResponse + } + } + + else -> responseArray[index] + } + } + } + + ByteReadChannel(response) + } + val deferred = CompletableDeferred() + + // execute + val thetaRepository = ThetaRepository(endpoint) + thetaRepository.cameraModel = ThetaRepository.ThetaModel.THETA_X + val capture = thetaRepository.getContinuousCaptureBuilder() + .setCheckStatusCommandInterval(100) + .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 + + var files: List? = null + capture.startCapture(object : ContinuousCapture.StartCaptureCallback { + override fun onCaptureCompleted(fileUrls: List?) { + files = fileUrls + deferred.complete(Unit) + } + + override fun onProgress(completion: Float) { + assertTrue(completion >= 0f, "onProgress") + } + + override fun onCapturing(status: CapturingStatusEnum) { + when { + stateCounter < 2 -> assertEquals( + status, + CapturingStatusEnum.SELF_TIMER_COUNTDOWN + ) + + else -> assertEquals(status, CapturingStatusEnum.CAPTURING) + } + } + + override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue(false, "error start continuous shooting") + deferred.complete(Unit) + } + }) + + runBlocking { + withTimeout(30_000) { + deferred.await() + } + } + + // check result + assertTrue(stateCounter >= 2, "capTureStatus count") + assertTrue( + files?.firstOrNull()?.startsWith("http://") ?: false, + "start capture continuous shooting" + ) + } } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCaptureTest.kt index d41b11770e..c756e88688 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/LimitlessIntervalCaptureTest.kt @@ -3,6 +3,7 @@ package com.ricoh360.thetaclient.capture import com.goncalossilva.resources.Resource import com.ricoh360.thetaclient.CheckRequest import com.ricoh360.thetaclient.MockApiClient +import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode import io.ktor.client.network.sockets.* @@ -12,7 +13,6 @@ import kotlinx.coroutines.* import kotlinx.coroutines.test.runTest import kotlin.test.* -@OptIn(ExperimentalCoroutinesApi::class) class LimitlessIntervalCaptureTest { private val endpoint = "http://192.168.1.1:80/" @@ -38,39 +38,51 @@ class LimitlessIntervalCaptureTest { Resource("src/commonTest/resources/LimitlessIntervalCapture/start_capture_done.json").readText(), Resource("src/commonTest/resources/LimitlessIntervalCapture/stop_capture_done.json").readText() ) + val stateSelfTimer = + Resource("src/commonTest/resources/LimitlessIntervalCapture/state_self_timer.json").readText() + val stateShooting = + Resource("src/commonTest/resources/LimitlessIntervalCapture/state_shooting.json").readText() + val deferredStart = CompletableDeferred() var counter = 0 + var stateCounter = 0 MockApiClient.onRequest = { request -> val index = counter++ - var response = "" // check request - when (index) { + val response = when (index) { 0 -> { CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.IMAGE) - response = responseArray[0] + responseArray[0] } 1 -> { CheckRequest.checkSetOptions(request = request, captureNumber = 0) - response = responseArray[1] + responseArray[1] } 2 -> { CheckRequest.checkCommandName(request, "camera.startCapture") - response = responseArray[2] + responseArray[2] } else -> { - if (!deferredStart.isCompleted) { - deferredStart.complete(Unit) - } if (CheckRequest.getCommandName(request) == "camera.stopCapture") { CheckRequest.checkCommandName(request, "camera.stopCapture") - response = responseArray[3] + responseArray[3] } else if (request.url.encodedPath == "/osc/state") { - response = - Resource("src/commonTest/resources/LimitlessIntervalCapture/state_shooting.json").readText() + when (stateCounter++) { + 0 -> stateSelfTimer + + else -> { + if (!deferredStart.isCompleted) { + deferredStart.complete(Unit) + } + stateShooting + } + } + } else { + "" // Error } } } @@ -82,32 +94,43 @@ class LimitlessIntervalCaptureTest { // execute val thetaRepository = ThetaRepository(endpoint) val capture = thetaRepository.getLimitlessIntervalCaptureBuilder() + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 assertNull(capture.getCaptureInterval(), "set option captureInterval") var files: List? = null - val capturing = capture.startCapture(object : LimitlessIntervalCapture.StartCaptureCallback { - override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue(false, "error stop capture") - deferred.complete(Unit) - } + val capturing = + capture.startCapture(object : LimitlessIntervalCapture.StartCaptureCallback { + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue(false, "error stop capture") + deferred.complete(Unit) + } - override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue(false, "error capture limitless interval") - deferred.complete(Unit) - } + override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue(false, "error capture limitless interval") + deferred.complete(Unit) + } - override fun onCaptureCompleted(fileUrls: List?) { - files = fileUrls - deferred.complete(Unit) - } - }) + override fun onCaptureCompleted(fileUrls: List?) { + files = fileUrls + deferred.complete(Unit) + } + + override fun onCapturing(status: CapturingStatusEnum) { + when (stateCounter) { + 1 -> assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + else -> assertEquals(status, CapturingStatusEnum.CAPTURING) + } + } + }) runBlocking { withTimeout(5000) { deferredStart.await() } + delay(100) } capturing.stopCapture() @@ -116,12 +139,13 @@ class LimitlessIntervalCaptureTest { deferred.await() } } - runBlocking { - delay(2000) - } // check result - assertTrue(files?.firstOrNull()?.startsWith("http://") ?: false, "start capture limitless interval") + assertTrue( + files?.firstOrNull()?.startsWith("http://") ?: false, + "start capture limitless interval" + ) + assertTrue(stateCounter >= 2, "count onCapturing") } /** @@ -175,7 +199,10 @@ class LimitlessIntervalCaptureTest { // execute val thetaRepository = ThetaRepository(endpoint) - val capture = thetaRepository.getLimitlessIntervalCaptureBuilder().build() + val capture = thetaRepository.getLimitlessIntervalCaptureBuilder() + .setCheckStatusCommandInterval(100) + .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 assertNull(capture.getCaptureInterval(), "set option captureInterval") @@ -380,7 +407,10 @@ class LimitlessIntervalCaptureTest { } val thetaRepository = ThetaRepository(endpoint) - val capture = thetaRepository.getLimitlessIntervalCaptureBuilder().build() + val capture = thetaRepository.getLimitlessIntervalCaptureBuilder() + .setCheckStatusCommandInterval(100) + .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute error response var deferred = CompletableDeferred() @@ -419,7 +449,10 @@ class LimitlessIntervalCaptureTest { } override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue(exception.message!!.length >= 0, "capture limitless interval json error response") + assertTrue( + exception.message!!.length >= 0, + "capture limitless interval json error response" + ) deferred.complete(Unit) } @@ -461,7 +494,9 @@ class LimitlessIntervalCaptureTest { val thetaRepository = ThetaRepository(endpoint) val capture = thetaRepository.getLimitlessIntervalCaptureBuilder() .setCaptureInterval(100) + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute status error and json response val deferred = CompletableDeferred() @@ -517,7 +552,9 @@ class LimitlessIntervalCaptureTest { val thetaRepository = ThetaRepository(endpoint) val capture = thetaRepository.getLimitlessIntervalCaptureBuilder() .setCaptureInterval(100) + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute status error and not json response val deferred = CompletableDeferred() @@ -570,7 +607,9 @@ class LimitlessIntervalCaptureTest { val thetaRepository = ThetaRepository(endpoint) val capture = thetaRepository.getLimitlessIntervalCaptureBuilder() .setCaptureInterval(100) + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute timeout exception val deferred = CompletableDeferred() diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCaptureTest.kt index b22099071b..fc5963199d 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/MultiBracketCaptureTest.kt @@ -3,6 +3,7 @@ package com.ricoh360.thetaclient.capture import com.goncalossilva.resources.Resource import com.ricoh360.thetaclient.CheckRequest import com.ricoh360.thetaclient.MockApiClient +import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode import io.ktor.http.HttpStatusCode @@ -55,23 +56,43 @@ class MultiBracketCaptureTest { "/osc/commands/status", "/osc/commands/status", ) - var counter = 0 - MockApiClient.onRequest = { request -> - val index = counter++ - // check request - assertEquals(request.url.encodedPath, requestPathArray[index], "start capture request") - when (index) { - 0 -> { - CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.IMAGE) - } + val stateSelfTimer = + Resource("src/commonTest/resources/MultiBracketCapture/state_self_timer.json").readText() + val stateShooting = + Resource("src/commonTest/resources/MultiBracketCapture/state_shooting.json").readText() - 2 -> { - CheckRequest.checkCommandName(request, "camera.startCapture") + var counter = 0 + var onSelfTimer = false + MockApiClient.onRequest = { request -> + val index = counter + val response = if (request.url.encodedPath != "/osc/state") { + counter++ + + // check request + assertEquals( + request.url.encodedPath, + requestPathArray[index], + "start capture request" + ) + when (index) { + 0 -> { + CheckRequest.checkSetOptions( + request = request, + captureMode = CaptureMode.IMAGE + ) + } + + 2 -> { + CheckRequest.checkCommandName(request, "camera.startCapture") + } } + responseArray[index] + } else { + if (onSelfTimer) stateShooting else stateSelfTimer } - ByteReadChannel(responseArray[index]) + ByteReadChannel(response) } val deferred = CompletableDeferred() @@ -92,7 +113,10 @@ class MultiBracketCaptureTest { iso = ThetaRepository.IsoEnum.ISO_800, whiteBalance = ThetaRepository.WhiteBalanceEnum.DAYLIGHT, ).build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 + var files: List? = null + var onCapturingCounter = 0 multiBracketCapture.startCapture(object : MultiBracketCapture.StartCaptureCallback { override fun onCaptureCompleted(fileUrls: List?) { assertTrue(true, "onCaptureCompleted") @@ -104,6 +128,16 @@ class MultiBracketCaptureTest { assertTrue(completion >= 0f, "onProgress") } + override fun onCapturing(status: CapturingStatusEnum) { + onCapturingCounter++ + if (onSelfTimer) { + assertEquals(status, CapturingStatusEnum.CAPTURING) + } else { + onSelfTimer = true + assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + } + } + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { assertTrue(false, "error to stop multi bracket capture") deferred.complete(Unit) @@ -123,6 +157,8 @@ class MultiBracketCaptureTest { // check result assertTrue(files?.get(1)?.startsWith("http://") ?: false, "start multi bracket capture") + assertTrue(onSelfTimer, "onCapturing self timer") + assertTrue(onCapturingCounter >= 2, "onCapturing count") } /** @@ -147,23 +183,38 @@ class MultiBracketCaptureTest { "/osc/commands/status", "/osc/commands/status", ) + val stateShooting = + Resource("src/commonTest/resources/MultiBracketCapture/state_shooting.json").readText() var counter = 0 MockApiClient.onRequest = { request -> - val index = counter++ - - // check request - assertEquals(request.url.encodedPath, requestPathArray[index], "start capture request") - when (index) { - 0 -> { - CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.IMAGE) - } - - 2 -> { - CheckRequest.checkCommandName(request, "camera.startCapture") + val index = counter + val response = if (request.url.encodedPath != "/osc/state") { + counter++ + + // check request + assertEquals( + request.url.encodedPath, + requestPathArray[index], + "start capture request" + ) + when (index) { + 0 -> { + CheckRequest.checkSetOptions( + request = request, + captureMode = CaptureMode.IMAGE + ) + } + + 2 -> { + CheckRequest.checkCommandName(request, "camera.startCapture") + } } + responseArray[index] + } else { + stateShooting } - ByteReadChannel(responseArray[index]) + ByteReadChannel(response) } val deferred = CompletableDeferred() @@ -189,6 +240,8 @@ class MultiBracketCaptureTest { ) ) ).build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 + var files: List? = null multiBracketCapture.startCapture(object : MultiBracketCapture.StartCaptureCallback { override fun onCaptureCompleted(fileUrls: List?) { @@ -201,6 +254,10 @@ class MultiBracketCaptureTest { assertTrue(completion >= 0f, "onProgress") } + override fun onCapturing(status: CapturingStatusEnum) { + assertEquals(status, CapturingStatusEnum.CAPTURING) + } + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { assertTrue(false, "error to stop multi bracket capture") deferred.complete(Unit) @@ -232,6 +289,7 @@ class MultiBracketCaptureTest { Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), Resource("src/commonTest/resources/MultiBracketCapture/start_capture_progress.json").readText(), + Resource("src/commonTest/resources/MultiBracketCapture/state_self_timer.json").readText(), Resource("src/commonTest/resources/MultiBracketCapture/state_shooting.json").readText(), Resource("src/commonTest/resources/MultiBracketCapture/state_idle.json").readText(), ) @@ -249,7 +307,8 @@ class MultiBracketCaptureTest { } } - val response = if (index >= responseArray.size) responseArray[responseArray.size - 1] else responseArray[index] + val response = + if (index >= responseArray.size) responseArray[responseArray.size - 1] else responseArray[index] ByteReadChannel(response) } val deferred = CompletableDeferred() @@ -269,8 +328,10 @@ class MultiBracketCaptureTest { ) .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = null + var capturingCount = 0 multiBracketCapture.startCapture(object : MultiBracketCapture.StartCaptureCallback { override fun onCaptureCompleted(fileUrls: List?) { assertTrue(true, "onCaptureCompleted") @@ -282,6 +343,15 @@ class MultiBracketCaptureTest { assertTrue(completion >= 0f, "onProgress") } + override fun onCapturing(status: CapturingStatusEnum) { + when (counter) { + 4 -> assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + 5 -> assertEquals(status, CapturingStatusEnum.CAPTURING) + 6 -> assertEquals(status, CapturingStatusEnum.CAPTURING) + } + capturingCount++ + } + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { assertTrue(false, "error to stop multi bracket capture") deferred.complete(Unit) @@ -301,6 +371,7 @@ class MultiBracketCaptureTest { // check result assertTrue(files?.size == 0, "result multi bracket capture") + assertTrue(capturingCount >= 2, "onCapturing count") } /** @@ -312,20 +383,24 @@ class MultiBracketCaptureTest { var isStop = false val deferredStart = CompletableDeferred() MockApiClient.onRequest = { request -> - val textBody = request.body as TextContent - val path = if (textBody.text.contains("camera.stopCapture")) { - isStop = true - "src/commonTest/resources/MultiBracketCapture/stop_capture_done.json" - } else if (textBody.text.contains("camera.setOptions")) { - "src/commonTest/resources/setOptions/set_options_done.json" + val path = if (request.url.encodedPath == "/osc/state") { + "src/commonTest/resources/MultiBracketCapture/state_shooting.json" } else { - if (!deferredStart.isCompleted) { - deferredStart.complete(Unit) + val textBody = request.body as TextContent + if (textBody.text.contains("camera.stopCapture")) { + isStop = true + "src/commonTest/resources/MultiBracketCapture/stop_capture_done.json" + } else if (textBody.text.contains("camera.setOptions")) { + "src/commonTest/resources/setOptions/set_options_done.json" + } else { + if (!deferredStart.isCompleted) { + deferredStart.complete(Unit) + } + if (isStop) + "src/commonTest/resources/MultiBracketCapture/start_capture_done_stopped.json" + else + "src/commonTest/resources/MultiBracketCapture/start_capture_progress.json" } - if (isStop) - "src/commonTest/resources/MultiBracketCapture/start_capture_done_stopped.json" - else - "src/commonTest/resources/MultiBracketCapture/start_capture_progress.json" } ByteReadChannel(Resource(path).readText()) @@ -387,6 +462,7 @@ class MultiBracketCaptureTest { iso = ThetaRepository.IsoEnum.ISO_100, whiteBalance = ThetaRepository.WhiteBalanceEnum.DAYLIGHT, ).build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = null val capturing = @@ -440,10 +516,18 @@ class MultiBracketCaptureTest { Resource("src/commonTest/resources/MultiBracketCapture/start_capture_progress.json").readText(), Resource("src/commonTest/resources/MultiBracketCapture/start_capture_cancel.json").readText(), ) + val stateShooting = + Resource("src/commonTest/resources/MultiBracketCapture/state_shooting.json").readText() var counter = 0 - MockApiClient.onRequest = { _ -> - val index = counter++ - ByteReadChannel(responseArray[index]) + MockApiClient.onRequest = { request -> + val response = when (request.url.encodedPath) { + "/osc/state" -> stateShooting + else -> { + val index = counter++ + responseArray[index] + } + } + ByteReadChannel(response) } val deferred = CompletableDeferred() @@ -459,7 +543,9 @@ class MultiBracketCaptureTest { shutterSpeed = ThetaRepository.ShutterSpeedEnum.SHUTTER_SPEED_1, iso = ThetaRepository.IsoEnum.ISO_100, whiteBalance = ThetaRepository.WhiteBalanceEnum.DAYLIGHT, - ).build() + ).setCheckStatusCommandInterval(100) + .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = listOf() capture.startCapture(object : MultiBracketCapture.StartCaptureCallback { diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/PhotoCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/PhotoCaptureTest.kt index 2c2eef52fc..0493661b1d 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/PhotoCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/PhotoCaptureTest.kt @@ -3,6 +3,7 @@ package com.ricoh360.thetaclient.capture import com.goncalossilva.resources.Resource import com.ricoh360.thetaclient.CheckRequest import com.ricoh360.thetaclient.MockApiClient +import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode import com.ricoh360.thetaclient.transferred.MediaFileFormat @@ -11,13 +12,11 @@ import io.ktor.client.network.sockets.* import io.ktor.http.* import io.ktor.utils.io.* import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withTimeout import kotlin.test.* -@OptIn(ExperimentalCoroutinesApi::class) class PhotoCaptureTest { private val endpoint = "http://192.168.1.1:80/" @@ -42,6 +41,8 @@ class PhotoCaptureTest { Resource("src/commonTest/resources/PhotoCapture/takepicture_progress.json").readText(), Resource("src/commonTest/resources/PhotoCapture/takepicture_done.json").readText() ) + val stateIdleResponse = + Resource("src/commonTest/resources/PhotoCapture/state_idle.json").readText() val requestPathArray = arrayOf( "/osc/commands/execute", "/osc/commands/execute", @@ -49,28 +50,53 @@ class PhotoCaptureTest { ) var counter = 0 MockApiClient.onRequest = { request -> - val index = counter++ - // check request - assertEquals(request.url.encodedPath, requestPathArray[index], "take picture request") - when (index) { + val response = when (val index = counter++) { 0 -> { + assertEquals( + request.url.encodedPath, + requestPathArray[index], + "take picture request" + ) CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.IMAGE) + responseArray[index] } 1 -> { + assertEquals( + request.url.encodedPath, + requestPathArray[index], + "take picture request" + ) CheckRequest.checkCommandName(request, "camera.takePicture") + responseArray[index] + } + + else -> { + when (request.url.encodedPath) { + "/osc/commands/status" -> { + assertEquals( + request.url.encodedPath, + requestPathArray[2], + "take picture request" + ) + responseArray[2] + } + else -> stateIdleResponse + } } } - ByteReadChannel(responseArray[index]) + ByteReadChannel(response) } val deferred = CompletableDeferred() // execute val thetaRepository = ThetaRepository(endpoint) val photoCapture = thetaRepository.getPhotoCaptureBuilder() + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 assertNull(photoCapture.getFilter(), "set option filter") assertNull(photoCapture.getFileFormat(), "set option fileFormat") @@ -82,6 +108,10 @@ class PhotoCaptureTest { deferred.complete(Unit) } + override fun onCapturing(status: CapturingStatusEnum) { + assertEquals(status, CapturingStatusEnum.CAPTURING) + } + override fun onError(exception: ThetaRepository.ThetaRepositoryException) { assertTrue(false, "error take picture") deferred.complete(Unit) @@ -108,17 +138,32 @@ class PhotoCaptureTest { Resource("src/commonTest/resources/PhotoCapture/takepicture_progress.json").readText(), Resource("src/commonTest/resources/PhotoCapture/takepicture_cancel.json").readText() ) + val stateIdleResponse = + Resource("src/commonTest/resources/PhotoCapture/state_idle.json").readText() var counter = 0 - MockApiClient.onRequest = { _ -> - val index = counter++ - ByteReadChannel(responseArray[index]) + MockApiClient.onRequest = { request -> + val response = when (val index = counter++) { + 0, 1 -> { + responseArray[index] + } + + else -> { + when (request.url.encodedPath) { + "/osc/commands/status" -> responseArray[2] + else -> stateIdleResponse + } + } + } + ByteReadChannel(response) } val deferred = CompletableDeferred() // execute val thetaRepository = ThetaRepository(endpoint) val photoCapture = thetaRepository.getPhotoCaptureBuilder() + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var file: String? = "" photoCapture.takePicture(object : PhotoCapture.TakePictureCallback { @@ -153,19 +198,35 @@ class PhotoCaptureTest { Resource("src/commonTest/resources/PhotoCapture/takepicture_progress.json").readText(), Resource("src/commonTest/resources/PhotoCapture/takepicture_cancel.json").readText() ) + val stateIdleResponse = + Resource("src/commonTest/resources/PhotoCapture/state_idle.json").readText() var counter = 0 - MockApiClient.onRequest = { _ -> + MockApiClient.onRequest = { request -> val index = counter++ + val response = when (index) { + 0, 1 -> { + responseArray[index] + } + + else -> { + when (request.url.encodedPath) { + "/osc/commands/status" -> responseArray[2] + else -> stateIdleResponse + } + } + } MockApiClient.status = if (index == 2) HttpStatusCode.Forbidden else HttpStatusCode.OK - ByteReadChannel(responseArray[index]) + ByteReadChannel(response) } val deferred = CompletableDeferred() // execute val thetaRepository = ThetaRepository(endpoint) val photoCapture = thetaRepository.getPhotoCaptureBuilder() + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var file: String? = "" photoCapture.takePicture(object : PhotoCapture.TakePictureCallback { @@ -210,40 +271,74 @@ class PhotoCaptureTest { "/osc/commands/execute", "/osc/commands/status" ) + val stateIdleResponse = + Resource("src/commonTest/resources/PhotoCapture/state_idle.json").readText() var counter = 0 MockApiClient.onRequest = { request -> - val index = counter++ - // check request - assertEquals(request.url.encodedPath, requestPathArray[index], "take picture request") - when (index) { + val response = when (val index = counter++) { 0 -> { + assertEquals( + request.url.encodedPath, + requestPathArray[index], + "take picture request" + ) CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.IMAGE) + responseArray[index] } 1 -> { + assertEquals( + request.url.encodedPath, + requestPathArray[index], + "take picture request" + ) CheckRequest.checkSetOptions( request = request, filter = filter.filter, fileFormat = fileFormat.fileFormat.toMediaFileFormat() ) + responseArray[index] } 2 -> { + assertEquals( + request.url.encodedPath, + requestPathArray[index], + "take picture request" + ) CheckRequest.checkCommandName(request, "camera.takePicture") + responseArray[index] + } + + else -> { + when (request.url.encodedPath) { + "/osc/commands/status" -> { + assertEquals( + request.url.encodedPath, + requestPathArray[3], + "take picture request" + ) + responseArray[3] + } + + else -> stateIdleResponse + } } } - ByteReadChannel(responseArray[index]) + ByteReadChannel(response) } val deferred = CompletableDeferred() // execute val thetaRepository = ThetaRepository(endpoint) val photoCapture = thetaRepository.getPhotoCaptureBuilder() + .setCheckStatusCommandInterval(100) .setFilter(filter) .setFileFormat(fileFormat) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 assertEquals(photoCapture.getFilter(), filter, "set option filter") assertEquals(photoCapture.getFileFormat(), fileFormat, "set option filter") @@ -277,7 +372,7 @@ class PhotoCaptureTest { @Test fun settingFilterTest() = runTest { // setup - val filterList = ThetaRepository.FilterEnum.values() + val filterList = ThetaRepository.FilterEnum.entries val responseArray = arrayOf( Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), @@ -396,7 +491,7 @@ class PhotoCaptureTest { @Test fun settingApertureTest() = runTest { // setup - val valueList = ThetaRepository.ApertureEnum.values() + val valueList = ThetaRepository.ApertureEnum.entries val responseArray = arrayOf( Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), @@ -506,7 +601,7 @@ class PhotoCaptureTest { @Test fun settingExposureCompensationTest() = runTest { // setup - val valueList = ThetaRepository.ExposureCompensationEnum.values() + val valueList = ThetaRepository.ExposureCompensationEnum.entries val responseArray = arrayOf( Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), @@ -542,6 +637,7 @@ class PhotoCaptureTest { val photoCapture = thetaRepository.getPhotoCaptureBuilder() .setExposureCompensation(it) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // check result assertEquals( @@ -561,7 +657,7 @@ class PhotoCaptureTest { @Test fun settingExposureDelayTest() = runTest { // setup - val valueList = ThetaRepository.ExposureDelayEnum.values() + val valueList = ThetaRepository.ExposureDelayEnum.entries val responseArray = arrayOf( Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), @@ -616,7 +712,7 @@ class PhotoCaptureTest { @Test fun settingExposureProgramTest() = runTest { // setup - val valueList = ThetaRepository.ExposureProgramEnum.values() + val valueList = ThetaRepository.ExposureProgramEnum.entries val responseArray = arrayOf( Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), @@ -730,7 +826,7 @@ class PhotoCaptureTest { @Test fun settingGpsTagRecordingTest() = runTest { // setup - val valueList = ThetaRepository.GpsTagRecordingEnum.values() + val valueList = ThetaRepository.GpsTagRecordingEnum.entries val responseArray = arrayOf( Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), @@ -785,7 +881,7 @@ class PhotoCaptureTest { @Test fun settingIsoTest() = runTest { // setup - val valueList = ThetaRepository.IsoEnum.values() + val valueList = ThetaRepository.IsoEnum.entries val responseArray = arrayOf( Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), @@ -840,7 +936,7 @@ class PhotoCaptureTest { @Test fun settingIsoAutoHighLimitTest() = runTest { // setup - val valueList = ThetaRepository.IsoAutoHighLimitEnum.values() + val valueList = ThetaRepository.IsoAutoHighLimitEnum.entries val responseArray = arrayOf( Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), @@ -895,7 +991,7 @@ class PhotoCaptureTest { @Test fun settingWhiteBalanceTest() = runTest { // setup - val valueList = ThetaRepository.WhiteBalanceEnum.values() + val valueList = ThetaRepository.WhiteBalanceEnum.entries val responseArray = arrayOf( Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), @@ -950,7 +1046,7 @@ class PhotoCaptureTest { @Test fun settingPresetTest() = runTest { // setup - val valueList = ThetaRepository.PresetEnum.values() + val valueList = ThetaRepository.PresetEnum.entries val responseArray = arrayOf( Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), @@ -1151,17 +1247,29 @@ class PhotoCaptureTest { Resource("src/commonTest/resources/PhotoCapture/takepicture_error.json").readText(), // takePicture status error "Not json" // json error ) + val stateIdleResponse = + Resource("src/commonTest/resources/PhotoCapture/state_idle.json").readText() var counter = 0 - MockApiClient.onRequest = { _ -> - val index = counter++ - ByteReadChannel(responseArray[index]) + MockApiClient.onRequest = { request -> + val response = when (request.url.encodedPath) { + "/osc/state" -> stateIdleResponse + + else -> { + val index = counter++ + responseArray[index] + } + } + + ByteReadChannel(response) } // execute val thetaRepository = ThetaRepository(endpoint) val photoCapture = thetaRepository.getPhotoCaptureBuilder() + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute takePicture error response var deferred = CompletableDeferred() @@ -1255,7 +1363,9 @@ class PhotoCaptureTest { val thetaRepository = ThetaRepository(endpoint) val photoCapture = thetaRepository.getPhotoCaptureBuilder() + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute status error and json response var deferred = CompletableDeferred() @@ -1317,4 +1427,99 @@ class PhotoCaptureTest { } } } + + /** + * Capturing status. + */ + @Test + fun capturingStatusTest() = runTest { + // setup + val optionResponse = + Resource("src/commonTest/resources/setOptions/set_options_done.json").readText() + val progressResponse = + Resource("src/commonTest/resources/PhotoCapture/takepicture_progress.json").readText() + val doneResponse = + Resource("src/commonTest/resources/PhotoCapture/takepicture_done.json").readText() + val stateIdleResponse = + Resource("src/commonTest/resources/PhotoCapture/state_idle.json").readText() + val stateSelfTimerResponse = + Resource("src/commonTest/resources/PhotoCapture/state_self_timer.json").readText() + var counter = 0 + var statusCount = 0 + MockApiClient.onRequest = { request -> + val response = when (counter++) { + 0 -> { + optionResponse + } + + 1 -> { + progressResponse + } + + else -> { + when (request.url.encodedPath) { + "/osc/state" -> { + statusCount += 1 + when (statusCount) { + 1 -> stateSelfTimerResponse + else -> stateIdleResponse + } + } + + else -> { + if (statusCount > 1) { + doneResponse + } else { + progressResponse + } + } + } + } + } + + ByteReadChannel(response) + } + val deferred = CompletableDeferred() + + // execute + val thetaRepository = ThetaRepository(endpoint) + val photoCapture = thetaRepository.getPhotoCaptureBuilder() + .setCheckStatusCommandInterval(100) + .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 + + assertNull(photoCapture.getFilter(), "set option filter") + assertNull(photoCapture.getFileFormat(), "set option fileFormat") + + var file: String? = null + var onCapturingCount = 0 + photoCapture.takePicture(object : PhotoCapture.TakePictureCallback { + override fun onSuccess(fileUrl: String?) { + file = fileUrl + deferred.complete(Unit) + } + + override fun onCapturing(status: CapturingStatusEnum) { + when (onCapturingCount) { + 0 -> assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + else -> assertEquals(status, CapturingStatusEnum.CAPTURING) + } + onCapturingCount += 1 + } + + override fun onError(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue(false, "error take picture") + deferred.complete(Unit) + } + }) + runBlocking { + withTimeout(10000) { + deferred.await() + } + } + + // check result + assertTrue(file?.startsWith("http://") ?: false, "take picture") + assertEquals(onCapturingCount, 2) + } } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCaptureTest.kt index 9658e60bf6..010da96cc9 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/ShotCountSpecifiedIntervalCaptureTest.kt @@ -3,6 +3,7 @@ package com.ricoh360.thetaclient.capture import com.goncalossilva.resources.Resource import com.ricoh360.thetaclient.CheckRequest import com.ricoh360.thetaclient.MockApiClient +import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode import io.ktor.client.network.sockets.ConnectTimeoutException @@ -47,26 +48,39 @@ class ShotCountSpecifiedIntervalCaptureTest { Resource("src/commonTest/resources/ShotCountSpecifiedIntervalCapture/start_capture_progress.json").readText(), Resource("src/commonTest/resources/ShotCountSpecifiedIntervalCapture/start_capture_done.json").readText(), ) + val stateSelfTimer = + Resource("src/commonTest/resources/MultiBracketCapture/state_self_timer.json").readText() + val stateShooting = + Resource("src/commonTest/resources/MultiBracketCapture/state_shooting.json").readText() var counter = 0 + var onSelfTimer = false MockApiClient.onRequest = { request -> - val index = counter++ - - // check request - when (index) { - 0 -> { - CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.IMAGE) - } + val index = counter + val response = if (request.url.encodedPath != "/osc/state") { + counter++ + // check request + when (index) { + 0 -> { + CheckRequest.checkSetOptions( + request = request, + captureMode = CaptureMode.IMAGE + ) + } - 1 -> { - CheckRequest.checkSetOptions(request = request, captureNumber = 2) - } + 1 -> { + CheckRequest.checkSetOptions(request = request, captureNumber = 2) + } - 2 -> { - CheckRequest.checkCommandName(request, "camera.startCapture") + 2 -> { + CheckRequest.checkCommandName(request, "camera.startCapture") + } } + responseArray[index] + } else { + if (onSelfTimer) stateShooting else stateSelfTimer } - ByteReadChannel(responseArray[index]) + ByteReadChannel(response) } val deferred = CompletableDeferred() @@ -76,8 +90,10 @@ class ShotCountSpecifiedIntervalCaptureTest { val capture = thetaRepository.getShotCountSpecifiedIntervalCaptureBuilder(2) .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = null + var onCapturingCounter = 0 capture.startCapture(object : ShotCountSpecifiedIntervalCapture.StartCaptureCallback { override fun onCaptureCompleted(fileUrls: List?) { files = fileUrls @@ -88,6 +104,17 @@ class ShotCountSpecifiedIntervalCaptureTest { assertTrue(completion >= 0f, "onProgress") } + override fun onCapturing(status: CapturingStatusEnum) { + onCapturingCounter++ + if (onSelfTimer) { + assertEquals(status, CapturingStatusEnum.CAPTURING) + } else { + onSelfTimer = true + assertEquals(onCapturingCounter, 1) + assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + } + } + override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { assertTrue(false, "error start interval shooting with the shot count specified") deferred.complete(Unit) @@ -109,6 +136,8 @@ class ShotCountSpecifiedIntervalCaptureTest { files?.firstOrNull()?.startsWith("http://") ?: false, "start capture interval shooting with the shot count specified" ) + assertTrue(onSelfTimer, "onCapturing self timer") + assertTrue(onCapturingCounter >= 2, "onCapturing count") } /** @@ -121,7 +150,7 @@ class ShotCountSpecifiedIntervalCaptureTest { Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), Resource("src/commonTest/resources/ShotCountSpecifiedIntervalCapture/start_capture_progress.json").readText(), - Resource("src/commonTest/resources/ShotCountSpecifiedIntervalCapture/state_shooting.json").readText(), + Resource("src/commonTest/resources/ShotCountSpecifiedIntervalCapture/state_self_timer.json").readText(), Resource("src/commonTest/resources/ShotCountSpecifiedIntervalCapture/state_shooting.json").readText(), Resource("src/commonTest/resources/ShotCountSpecifiedIntervalCapture/state_idle.json").readText(), ) @@ -139,6 +168,7 @@ class ShotCountSpecifiedIntervalCaptureTest { val capture = thetaRepository.getShotCountSpecifiedIntervalCaptureBuilder(2) .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = null capture.startCapture(object : ShotCountSpecifiedIntervalCapture.StartCaptureCallback { @@ -150,6 +180,12 @@ class ShotCountSpecifiedIntervalCaptureTest { override fun onProgress(completion: Float) { assertTrue(false, "onProgress") } + override fun onCapturing(status: CapturingStatusEnum) { + when (counter) { + 4 -> assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + else -> assertEquals(status, CapturingStatusEnum.CAPTURING) + } + } override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { assertTrue(false, "error start interval shooting with the shot count specified") @@ -202,6 +238,7 @@ class ShotCountSpecifiedIntervalCaptureTest { val capture = thetaRepository.getShotCountSpecifiedIntervalCaptureBuilder(2) .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = null capture.startCapture(object : ShotCountSpecifiedIntervalCapture.StartCaptureCallback { @@ -245,17 +282,21 @@ class ShotCountSpecifiedIntervalCaptureTest { // setup var isStop = false MockApiClient.onRequest = { request -> - val textBody = request.body as TextContent - val path = if (textBody.text.contains("camera.stopCapture")) { - isStop = true - "src/commonTest/resources/ShotCountSpecifiedIntervalCapture/stop_capture_done.json" - } else if (textBody.text.contains("camera.setOptions")) { - "src/commonTest/resources/setOptions/set_options_done.json" + val path = if (request.url.encodedPath == "/osc/state") { + "src/commonTest/resources/MultiBracketCapture/state_shooting.json" } else { - if (isStop) - "src/commonTest/resources/ShotCountSpecifiedIntervalCapture/start_capture_done_empty.json" - else - "src/commonTest/resources/ShotCountSpecifiedIntervalCapture/start_capture_progress.json" + val textBody = request.body as TextContent + if (textBody.text.contains("camera.stopCapture")) { + isStop = true + "src/commonTest/resources/ShotCountSpecifiedIntervalCapture/stop_capture_done.json" + } else if (textBody.text.contains("camera.setOptions")) { + "src/commonTest/resources/setOptions/set_options_done.json" + } else { + if (isStop) + "src/commonTest/resources/ShotCountSpecifiedIntervalCapture/start_capture_done_empty.json" + else + "src/commonTest/resources/ShotCountSpecifiedIntervalCapture/start_capture_progress.json" + } } ByteReadChannel(Resource(path).readText()) @@ -269,6 +310,7 @@ class ShotCountSpecifiedIntervalCaptureTest { val capture = thetaRepository.getShotCountSpecifiedIntervalCaptureBuilder(10) .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = null val deferredStart = CompletableDeferred() @@ -331,10 +373,16 @@ class ShotCountSpecifiedIntervalCaptureTest { Resource("src/commonTest/resources/ShotCountSpecifiedIntervalCapture/start_capture_progress.json").readText(), Resource("src/commonTest/resources/ShotCountSpecifiedIntervalCapture/start_capture_cancel.json").readText(), ) + val stateShooting = + Resource("src/commonTest/resources/MultiBracketCapture/state_shooting.json").readText() var counter = 0 - MockApiClient.onRequest = { _ -> - val index = counter++ - ByteReadChannel(responseArray[index]) + MockApiClient.onRequest = { request -> + val response = if (request.url.encodedPath != "/osc/state") { + responseArray[counter++] + } else { + stateShooting + } + ByteReadChannel(response) } val deferred = CompletableDeferred() @@ -344,6 +392,7 @@ class ShotCountSpecifiedIntervalCaptureTest { val capture = thetaRepository.getShotCountSpecifiedIntervalCaptureBuilder(2) .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = listOf() capture.startCapture(object : ShotCountSpecifiedIntervalCapture.StartCaptureCallback { @@ -403,6 +452,7 @@ class ShotCountSpecifiedIntervalCaptureTest { val capture = thetaRepository.getShotCountSpecifiedIntervalCaptureBuilder(2) .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var files: List? = listOf() capture.startCapture(object : ShotCountSpecifiedIntervalCapture.StartCaptureCallback { @@ -619,7 +669,9 @@ class ShotCountSpecifiedIntervalCaptureTest { val thetaRepository = ThetaRepository(endpoint) thetaRepository.cameraModel = ThetaRepository.ThetaModel.THETA_X - val capture = thetaRepository.getShotCountSpecifiedIntervalCaptureBuilder(2).build() + val capture = thetaRepository.getShotCountSpecifiedIntervalCaptureBuilder(2) + .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute error response var deferred = CompletableDeferred() @@ -711,6 +763,7 @@ class ShotCountSpecifiedIntervalCaptureTest { val thetaRepository = ThetaRepository(endpoint) val capture = thetaRepository.getShotCountSpecifiedIntervalCaptureBuilder(2) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute status error and json response var deferred = CompletableDeferred() diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCaptureTest.kt index 766e34c796..af315e7120 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/TimeShiftCaptureTest.kt @@ -3,12 +3,14 @@ package com.ricoh360.thetaclient.capture import com.goncalossilva.resources.Resource import com.ricoh360.thetaclient.CheckRequest import com.ricoh360.thetaclient.MockApiClient +import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode import com.ricoh360.thetaclient.transferred.FirstShootingEnum import com.ricoh360.thetaclient.transferred.Preset import com.ricoh360.thetaclient.transferred.TimeShift import io.ktor.client.network.sockets.ConnectTimeoutException +import io.ktor.client.request.HttpRequestData import io.ktor.http.HttpStatusCode import io.ktor.http.content.TextContent import io.ktor.utils.io.ByteReadChannel @@ -26,14 +28,27 @@ import kotlin.test.assertTrue class TimeShiftCaptureTest { private val endpoint = "http://192.168.1.1:80/" + private var onCommandRequest: ((HttpRequestData) -> ByteReadChannel)? = null + @BeforeTest fun setup() { MockApiClient.status = HttpStatusCode.OK + MockApiClient.onRequest = { request -> + if (request.url.encodedPath == "/osc/state") { + MockApiClient.status = HttpStatusCode.OK + ByteReadChannel(Resource("src/commonTest/resources/TimeShiftCapture/state_shooting.json").readText()) + } else { + onCommandRequest?.let { it(request) } + ?: throw Exception("Not implement onCommandRequest") + } + } } @AfterTest fun teardown() { MockApiClient.status = HttpStatusCode.OK + MockApiClient.onRequest = null + onCommandRequest = null } /** @@ -57,23 +72,41 @@ class TimeShiftCaptureTest { "/osc/commands/status", "/osc/commands/execute", ) + val stateSelfTimer = + Resource("src/commonTest/resources/TimeShiftCapture/state_self_timer.json").readText() + val stateShooting = + Resource("src/commonTest/resources/TimeShiftCapture/state_shooting.json").readText() var counter = 0 + var onSelfTimer = false MockApiClient.onRequest = { request -> - val index = counter++ - - // check request - assertEquals(request.url.encodedPath, requestPathArray[index], "start capture request") - when (index) { - 0 -> { - CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.IMAGE) - } - - 1 -> { - CheckRequest.checkCommandName(request, "camera.startCapture") + val index = counter + val response = if (request.url.encodedPath != "/osc/state") { + counter++ + + // check request + assertEquals( + request.url.encodedPath, + requestPathArray[index], + "start capture request" + ) + when (index) { + 0 -> { + CheckRequest.checkSetOptions( + request = request, + captureMode = CaptureMode.IMAGE + ) + } + + 1 -> { + CheckRequest.checkCommandName(request, "camera.startCapture") + } } + responseArray[index] + } else { + if (onSelfTimer) stateShooting else stateSelfTimer } - ByteReadChannel(responseArray[index]) + ByteReadChannel(response) } val deferred = CompletableDeferred() @@ -83,8 +116,10 @@ class TimeShiftCaptureTest { val timeShiftCapture = thetaRepository.getTimeShiftCaptureBuilder() .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var file: String? = null + var onCapturingCounter = 0 timeShiftCapture.startCapture(object : TimeShiftCapture.StartCaptureCallback { override fun onCaptureCompleted(fileUrl: String?) { file = fileUrl @@ -95,6 +130,17 @@ class TimeShiftCaptureTest { assertTrue(completion >= 0f, "onProgress") } + override fun onCapturing(status: CapturingStatusEnum) { + onCapturingCounter++ + if (onSelfTimer) { + assertEquals(status, CapturingStatusEnum.CAPTURING) + } else { + onSelfTimer = true + assertEquals(onCapturingCounter, 1) + assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + } + } + override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { assertTrue(false, "error start time-shift") deferred.complete(Unit) @@ -114,6 +160,8 @@ class TimeShiftCaptureTest { // check result assertTrue(file?.startsWith("http://") ?: false, "start time-shift") + assertTrue(onSelfTimer, "onCapturing self timer") + assertTrue(onCapturingCounter >= 2, "onCapturing count") } /** @@ -139,14 +187,18 @@ class TimeShiftCaptureTest { "/osc/commands/execute", ) var counter = 0 - MockApiClient.onRequest = { request -> + onCommandRequest = { request -> val index = counter++ // check request assertEquals(request.url.encodedPath, requestPathArray[index], "start capture request") when (index) { 0 -> { - CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.PRESET, preset = Preset.ROOM) + CheckRequest.checkSetOptions( + request = request, + captureMode = CaptureMode.PRESET, + preset = Preset.ROOM + ) } 1 -> { @@ -164,6 +216,7 @@ class TimeShiftCaptureTest { val timeShiftCapture = thetaRepository.getTimeShiftCaptureBuilder() .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var file: String? = null timeShiftCapture.startCapture(object : TimeShiftCapture.StartCaptureCallback { @@ -220,14 +273,18 @@ class TimeShiftCaptureTest { "/osc/commands/execute", ) var counter = 0 - MockApiClient.onRequest = { request -> + onCommandRequest = { request -> val index = counter++ // check request assertEquals(request.url.encodedPath, requestPathArray[index], "start capture request") when (index) { 0 -> { - CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.PRESET, preset = Preset.ROOM) + CheckRequest.checkSetOptions( + request = request, + captureMode = CaptureMode.PRESET, + preset = Preset.ROOM + ) } 1 -> { @@ -245,6 +302,7 @@ class TimeShiftCaptureTest { val timeShiftCapture = thetaRepository.getTimeShiftCaptureBuilder() .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var file: String? = null timeShiftCapture.startCapture(object : TimeShiftCapture.StartCaptureCallback { @@ -285,7 +343,7 @@ class TimeShiftCaptureTest { fun cancelCaptureTest() = runTest { // setup var isStop = false - MockApiClient.onRequest = { request -> + onCommandRequest = { request -> val textBody = request.body as TextContent val path = if (textBody.text.contains("camera.stopCapture")) { isStop = true @@ -311,31 +369,33 @@ class TimeShiftCaptureTest { val timeShiftCapture = thetaRepository.getTimeShiftCaptureBuilder() .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var file: String? = "" - val capturing = timeShiftCapture.startCapture(object : TimeShiftCapture.StartCaptureCallback { - override fun onCaptureCompleted(fileUrl: String?) { - file = fileUrl - deferred.complete(Unit) - } + val capturing = + timeShiftCapture.startCapture(object : TimeShiftCapture.StartCaptureCallback { + override fun onCaptureCompleted(fileUrl: String?) { + file = fileUrl + deferred.complete(Unit) + } - override fun onProgress(completion: Float) { - assertEquals(completion, 0f, "onProgress") - if (!deferredStart.isCompleted) { - deferredStart.complete(Unit) + override fun onProgress(completion: Float) { + assertEquals(completion, 0f, "onProgress") + if (!deferredStart.isCompleted) { + deferredStart.complete(Unit) + } } - } - override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue(false, "error start time-shift") - deferred.complete(Unit) - } + override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue(false, "error start time-shift") + deferred.complete(Unit) + } - override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue(false, "error start time-shift") - deferred.complete(Unit) - } - }) + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue(false, "error start time-shift") + deferred.complete(Unit) + } + }) runBlocking { withTimeout(5000) { @@ -368,7 +428,7 @@ class TimeShiftCaptureTest { Resource("src/commonTest/resources/TimeShiftCapture/start_capture_cancel.json").readText(), ) var counter = 0 - MockApiClient.onRequest = { _ -> + onCommandRequest = { _ -> val index = counter++ ByteReadChannel(responseArray[index]) } @@ -380,6 +440,7 @@ class TimeShiftCaptureTest { val timeShiftCapture = thetaRepository.getTimeShiftCaptureBuilder() .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var file: String? = "" timeShiftCapture.startCapture(object : TimeShiftCapture.StartCaptureCallback { @@ -425,7 +486,7 @@ class TimeShiftCaptureTest { Resource("src/commonTest/resources/TimeShiftCapture/start_capture_cancel.json").readText(), ) var counter = 0 - MockApiClient.onRequest = { _ -> + onCommandRequest = { _ -> val index = counter++ MockApiClient.status = if (index == 2) HttpStatusCode.Forbidden else HttpStatusCode.OK ByteReadChannel(responseArray[index]) @@ -438,6 +499,7 @@ class TimeShiftCaptureTest { val timeShiftCapture = thetaRepository.getTimeShiftCaptureBuilder() .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var file: String? = "" timeShiftCapture.startCapture(object : TimeShiftCapture.StartCaptureCallback { @@ -490,7 +552,11 @@ class TimeShiftCaptureTest { .build() // check result - assertEquals(capture.getCheckStatusCommandInterval(), timeMillis, "set CheckStatusCommandInterval $timeMillis") + assertEquals( + capture.getCheckStatusCommandInterval(), + timeMillis, + "set CheckStatusCommandInterval $timeMillis" + ) } /** @@ -505,7 +571,10 @@ class TimeShiftCaptureTest { // check request when (index) { 1 -> { - CheckRequest.checkSetOptions(request, timeShift = TimeShift(firstShooting = FirstShootingEnum.REAR)) + CheckRequest.checkSetOptions( + request, + timeShift = TimeShift(firstShooting = FirstShootingEnum.REAR) + ) } } index += 1 @@ -521,7 +590,11 @@ class TimeShiftCaptureTest { .build() // check result - assertEquals(capture.getTimeShiftSetting()?.isFrontFirst ?: true, isFrontFirst, "set setIsFrontFirst $isFrontFirst") + assertEquals( + capture.getTimeShiftSetting()?.isFrontFirst ?: true, + isFrontFirst, + "set setIsFrontFirst $isFrontFirst" + ) } /** @@ -529,7 +602,8 @@ class TimeShiftCaptureTest { */ @Test fun settingFirstIntervalTest() = runTest { - val interval: ThetaRepository.TimeShiftIntervalEnum = ThetaRepository.TimeShiftIntervalEnum.INTERVAL_3 + val interval: ThetaRepository.TimeShiftIntervalEnum = + ThetaRepository.TimeShiftIntervalEnum.INTERVAL_3 var index = 0 MockApiClient.onRequest = { request -> @@ -552,7 +626,11 @@ class TimeShiftCaptureTest { .build() // check result - assertEquals(capture.getTimeShiftSetting()?.firstInterval, interval, "set setFirstInterval $interval") + assertEquals( + capture.getTimeShiftSetting()?.firstInterval, + interval, + "set setFirstInterval $interval" + ) } /** @@ -560,7 +638,8 @@ class TimeShiftCaptureTest { */ @Test fun settingSecondIntervalTest() = runTest { - val interval: ThetaRepository.TimeShiftIntervalEnum = ThetaRepository.TimeShiftIntervalEnum.INTERVAL_5 + val interval: ThetaRepository.TimeShiftIntervalEnum = + ThetaRepository.TimeShiftIntervalEnum.INTERVAL_5 var index = 0 MockApiClient.onRequest = { request -> @@ -583,7 +662,11 @@ class TimeShiftCaptureTest { .build() // check result - assertEquals(capture.getTimeShiftSetting()?.secondInterval, interval, "set setSecondInterval $interval") + assertEquals( + capture.getTimeShiftSetting()?.secondInterval, + interval, + "set setSecondInterval $interval" + ) } /** @@ -661,7 +744,10 @@ class TimeShiftCaptureTest { try { thetaRepository.getTimeShiftCaptureBuilder().build() } catch (e: ThetaRepository.ThetaWebApiException) { - assertTrue((e.message?.indexOf("UnitTest", 0, true) ?: -1) >= 0, "status error and json response") + assertTrue( + (e.message?.indexOf("UnitTest", 0, true) ?: -1) >= 0, + "status error and json response" + ) exceptionStatusJson = true } assertTrue(exceptionStatusJson, "status error and json response") @@ -696,7 +782,9 @@ class TimeShiftCaptureTest { val thetaRepository = ThetaRepository(endpoint) thetaRepository.cameraModel = ThetaRepository.ThetaModel.THETA_X - val capture = thetaRepository.getTimeShiftCaptureBuilder().build() + val capture = thetaRepository.getTimeShiftCaptureBuilder() + .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute error response var deferred = CompletableDeferred() @@ -710,7 +798,10 @@ class TimeShiftCaptureTest { } override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue((exception.message?.indexOf("UnitTest", 0, true) ?: -1) >= 0, "capture time-shift error response") + assertTrue( + (exception.message?.indexOf("UnitTest", 0, true) ?: -1) >= 0, + "capture time-shift error response" + ) deferred.complete(Unit) } @@ -738,7 +829,10 @@ class TimeShiftCaptureTest { } override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue((exception.message?.length ?: -1) >= 0, "capture time-shift json error response") + assertTrue( + (exception.message?.length ?: -1) >= 0, + "capture time-shift json error response" + ) deferred.complete(Unit) } @@ -782,6 +876,7 @@ class TimeShiftCaptureTest { val thetaRepository = ThetaRepository(endpoint) thetaRepository.cameraModel = ThetaRepository.ThetaModel.THETA_X val capture = thetaRepository.getTimeShiftCaptureBuilder().build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute status error and json response var deferred = CompletableDeferred() @@ -795,7 +890,10 @@ class TimeShiftCaptureTest { } override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue((exception.message?.indexOf("UnitTest", 0, true) ?: -1) >= 0, "status error and json response") + assertTrue( + (exception.message?.indexOf("UnitTest", 0, true) ?: -1) >= 0, + "status error and json response" + ) deferred.complete(Unit) } @@ -851,7 +949,10 @@ class TimeShiftCaptureTest { } override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue((exception.message?.indexOf("time", 0, true) ?: -1) >= 0, "timeout exception") + assertTrue( + (exception.message?.indexOf("time", 0, true) ?: -1) >= 0, + "timeout exception" + ) deferred.complete(Unit) } @@ -882,7 +983,7 @@ class TimeShiftCaptureTest { "Not json" // json error ) var counter = 0 - MockApiClient.onRequest = { _ -> + onCommandRequest = { _ -> val index = counter++ ByteReadChannel(responseArray[index]) } @@ -895,30 +996,38 @@ class TimeShiftCaptureTest { val timeShiftCapture = thetaRepository.getTimeShiftCaptureBuilder() .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 var isCaptureFailed = false var isStopFailed = false - val capturing = timeShiftCapture.startCapture(object : TimeShiftCapture.StartCaptureCallback { - override fun onCaptureCompleted(fileUrl: String?) { - assertTrue(false, "capture time-shift") - deferred.complete(Unit) - } + val capturing = + timeShiftCapture.startCapture(object : TimeShiftCapture.StartCaptureCallback { + override fun onCaptureCompleted(fileUrl: String?) { + assertTrue(false, "capture time-shift") + deferred.complete(Unit) + } - override fun onProgress(completion: Float) { - deferredStart.complete(Unit) - } + override fun onProgress(completion: Float) { + deferredStart.complete(Unit) + } - override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { - isCaptureFailed = true - assertTrue((exception.message?.indexOf("Unknown", 0, true) ?: -1) >= 0, "stop capture error response") - deferred.complete(Unit) - } + override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { + isCaptureFailed = true + assertTrue( + (exception.message?.indexOf("Unknown", 0, true) ?: -1) >= 0, + "stop capture error response" + ) + deferred.complete(Unit) + } - override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { - isStopFailed = true - assertTrue((exception.message?.indexOf("UnitTest", 0, true) ?: -1) >= 0, "stop capture error response") - } - }) + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { + isStopFailed = true + assertTrue( + (exception.message?.indexOf("UnitTest", 0, true) ?: -1) >= 0, + "stop capture error response" + ) + } + }) runBlocking { withTimeout(5000) { @@ -961,25 +1070,29 @@ class TimeShiftCaptureTest { var deferred = CompletableDeferred() - var capturing = TimeShiftCapturing(endpoint, object : TimeShiftCapture.StartCaptureCallback { - override fun onCaptureCompleted(fileUrl: String?) { - assertTrue(false, "capture time-shift") - deferred.complete(Unit) - } + var capturing = + TimeShiftCapturing(endpoint, object : TimeShiftCapture.StartCaptureCallback { + override fun onCaptureCompleted(fileUrl: String?) { + assertTrue(false, "capture time-shift") + deferred.complete(Unit) + } - override fun onProgress(completion: Float) { - } + override fun onProgress(completion: Float) { + } - override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue(false, "capture time-shift") - deferred.complete(Unit) - } + override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue(false, "capture time-shift") + deferred.complete(Unit) + } - override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue((exception.message?.indexOf("UnitTest", 0, true) ?: -1) >= 0, "status error and json response") - deferred.complete(Unit) - } - }) + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue( + (exception.message?.indexOf("UnitTest", 0, true) ?: -1) >= 0, + "status error and json response" + ) + deferred.complete(Unit) + } + }) capturing.cancelCapture() @@ -1034,7 +1147,10 @@ class TimeShiftCaptureTest { } override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue((exception.message?.indexOf("time", 0, true) ?: -1) >= 0, "timeout exception") + assertTrue( + (exception.message?.indexOf("time", 0, true) ?: -1) >= 0, + "timeout exception" + ) deferred.complete(Unit) } }) diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/VideoCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/VideoCaptureTest.kt index 480d868fa7..896c06f5cb 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/VideoCaptureTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/VideoCaptureTest.kt @@ -3,6 +3,7 @@ package com.ricoh360.thetaclient.capture import com.goncalossilva.resources.Resource import com.ricoh360.thetaclient.CheckRequest import com.ricoh360.thetaclient.MockApiClient +import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode import com.ricoh360.thetaclient.transferred.MediaFileFormat @@ -46,34 +47,47 @@ class VideoCaptureTest { Resource("src/commonTest/resources/VideoCapture/start_capture_done.json").readText(), Resource("src/commonTest/resources/VideoCapture/stop_capture_done.json").readText() ) + + val stateSelfTimer = + Resource("src/commonTest/resources/VideoCapture/state_self_timer.json").readText() + val stateShooting = + Resource("src/commonTest/resources/VideoCapture/state_shooting.json").readText() + val deferredStart = CompletableDeferred() var counter = 0 + var stateCounter = 0 MockApiClient.onRequest = { request -> val index = counter++ - var response = "" // check request - when (index) { + val response = when (index) { 0 -> { CheckRequest.checkSetOptions(request = request, captureMode = CaptureMode.VIDEO) - response = responseArray[0] + responseArray[0] } 1 -> { CheckRequest.checkCommandName(request, "camera.startCapture") - response = responseArray[1] + responseArray[1] } else -> { if (CheckRequest.getCommandName(request) == "camera.stopCapture") { CheckRequest.checkCommandName(request, "camera.stopCapture") - response = responseArray[2] + responseArray[2] } else if (request.url.encodedPath == "/osc/state") { - if (!deferredStart.isCompleted) { - deferredStart.complete(Unit) + when (stateCounter++) { + 0 -> stateSelfTimer + + else -> { + if (!deferredStart.isCompleted) { + deferredStart.complete(Unit) + } + stateShooting + } } - response = - Resource("src/commonTest/resources/VideoCapture/state_shooting.json").readText() + } else { + "" // Error } } } @@ -85,7 +99,9 @@ class VideoCaptureTest { // execute val thetaRepository = ThetaRepository(endpoint) val videoCapture = thetaRepository.getVideoCaptureBuilder() + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 assertNull(videoCapture.getMaxRecordableTime(), "set option maxRecordableTime") assertNull(videoCapture.getFileFormat(), "set option fileFormat") @@ -106,6 +122,13 @@ class VideoCaptureTest { file = fileUrl deferred.complete(Unit) } + + override fun onCapturing(status: CapturingStatusEnum) { + when (stateCounter) { + 1 -> assertEquals(status, CapturingStatusEnum.SELF_TIMER_COUNTDOWN) + else -> assertEquals(status, CapturingStatusEnum.CAPTURING) + } + } }) runBlocking { withTimeout(10000) { @@ -124,6 +147,7 @@ class VideoCaptureTest { // check result assertTrue(file?.startsWith("http://") ?: false, "start capture video") + assertTrue(stateCounter >= 2, "count onCapturing") } /** @@ -172,7 +196,9 @@ class VideoCaptureTest { // execute val thetaRepository = ThetaRepository(endpoint) val videoCapture = thetaRepository.getVideoCaptureBuilder() + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 assertNull(videoCapture.getMaxRecordableTime(), "set option maxRecordableTime") assertNull(videoCapture.getFileFormat(), "set option fileFormat") @@ -557,7 +583,9 @@ class VideoCaptureTest { val thetaRepository = ThetaRepository(endpoint) val videoCapture = thetaRepository.getVideoCaptureBuilder() + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute error response var deferred = CompletableDeferred() @@ -635,7 +663,9 @@ class VideoCaptureTest { val thetaRepository = ThetaRepository(endpoint) val videoCapture = thetaRepository.getVideoCaptureBuilder() + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute status error and json response val deferred = CompletableDeferred() @@ -688,7 +718,9 @@ class VideoCaptureTest { val thetaRepository = ThetaRepository(endpoint) val videoCapture = thetaRepository.getVideoCaptureBuilder() + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 val deferred = CompletableDeferred() // execute status error and not json response @@ -738,7 +770,9 @@ class VideoCaptureTest { val thetaRepository = ThetaRepository(endpoint) val videoCapture = thetaRepository.getVideoCaptureBuilder() + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 val deferred = CompletableDeferred() diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaStateTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaStateTest.kt index 8366240d91..b41202ba59 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaStateTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetThetaStateTest.kt @@ -520,6 +520,11 @@ class GetThetaStateTest { } // check CaptureStatusEnum + assertEquals( + ThetaRepository.CaptureStatusEnum.get(CaptureStatus.UNKNOWN), + ThetaRepository.CaptureStatusEnum.UNKNOWN, + "CaptureStatusEnum" + ) assertEquals( ThetaRepository.CaptureStatusEnum.get(CaptureStatus.SHOOTING), ThetaRepository.CaptureStatusEnum.SHOOTING, @@ -560,6 +565,11 @@ class GetThetaStateTest { ThetaRepository.CaptureStatusEnum.RETROSPECTIVE_IMAGE_RECORDING, "CaptureStatusEnum" ) + assertEquals( + ThetaRepository.CaptureStatusEnum.get(CaptureStatus.BURST_SHOOTING), + ThetaRepository.CaptureStatusEnum.BURST_SHOOTING, + "CaptureStatusEnum" + ) // check ChargingStateEnum assertEquals( diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/transferred/StateApiTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/transferred/StateApiTest.kt new file mode 100644 index 0000000000..bb39e34f34 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/transferred/StateApiTest.kt @@ -0,0 +1,63 @@ +package com.ricoh360.thetaclient.transferred + +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +@OptIn(ExperimentalSerializationApi::class) +class StateApiTest { + @BeforeTest + fun setup() { + } + + @AfterTest + fun teardown() { + } + + val js = Json { + encodeDefaults = true // Encode properties with default value. + explicitNulls = false // Don't encode properties with null value. + ignoreUnknownKeys = true // Ignore unknown keys on decode. + } + + @Test + fun serializeCaptureStatus() = runTest { + val data = listOf( + Pair(CaptureStatus.UNKNOWN, "unknown value"), + Pair(CaptureStatus.SHOOTING, "shooting"), + Pair(CaptureStatus.IDLE, "idle"), + Pair(CaptureStatus.SELF_TIMER_COUNTDOWN, "self-timer countdown"), + Pair(CaptureStatus.BRACKET_SHOOTING, "bracket shooting"), + Pair(CaptureStatus.CONVERTING, "converting"), + Pair(CaptureStatus.TIME_SHIFT_SHOOTING, "timeShift shooting"), + Pair(CaptureStatus.CONTINUOUS_SHOOTING, "continuous shooting"), + Pair(CaptureStatus.RETROSPECTIVE_IMAGE_RECORDING, "retrospective image recording"), + Pair(CaptureStatus.BURST_SHOOTING, "burst shooting"), + ) + + @Serializable + data class Dummy( + val value: CaptureStatus, + ) + + data.forEach { + val jsonString = """ + { + "value": "${it.second}" + } + """.trimIndent() + val dummy = js.decodeFromString(jsonString) + assertEquals(dummy.value, it.first, "CaptureStatus: ${it.first.name}") + + val encoded = js.encodeToString(dummy) + val dist = js.decodeFromString(encoded) + assertEquals(dist.value, it.first, "CaptureStatus: ${it.first.name}") + } + } +} \ No newline at end of file diff --git a/kotlin-multiplatform/src/commonTest/resources/BurstCapture/state_burst_shooting.json b/kotlin-multiplatform/src/commonTest/resources/BurstCapture/state_burst_shooting.json new file mode 100644 index 0000000000..4298b34b15 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/BurstCapture/state_burst_shooting.json @@ -0,0 +1,22 @@ +{ + "fingerprint": "FIG_0008", + "state": { + "_apiVersion": 2, + "batteryLevel": 0.81, + "_batteryState": "disconnect", + "_cameraError": [ + "COMPASS_CALIBRATION" + ], + "_captureStatus": "burst shooting", + "_capturedPictures": 0, + "_compositeShootingElapsedTime": 0, + "_function": "selfTimer", + "_latestFileUrl": "http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0013331.JPG", + "_mySettingChanged": false, + "_pluginRunning": false, + "_pluginWebServer": true, + "_recordableTime": 0, + "_recordedTime": 0, + "storageUri": "http://192.168.1.1/files/150100524436344d4201375fda9dc400/" + } +} diff --git a/kotlin-multiplatform/src/commonTest/resources/BurstCapture/state_self_timer.json b/kotlin-multiplatform/src/commonTest/resources/BurstCapture/state_self_timer.json index e34db1a80d..1bdc92b1d7 100644 --- a/kotlin-multiplatform/src/commonTest/resources/BurstCapture/state_self_timer.json +++ b/kotlin-multiplatform/src/commonTest/resources/BurstCapture/state_self_timer.json @@ -1 +1 @@ -{"fingerprint":"FIG_0003","state":{"batteryLevel":0.8,"storageUri":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e","_apiVersion":2,"_batteryState":"charging","_cameraError":[],"_captureStatus":"idle","_capturedPictures":0,"_latestFileUrl":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e/100RICOH/R0012313.JPG","_recordableTime":0,"_recordedTime":0,"_function":"selfTimer"}} +{"fingerprint":"FIG_0003","state":{"batteryLevel":0.8,"storageUri":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e","_apiVersion":2,"_batteryState":"charging","_cameraError":[],"_captureStatus":"self-timer countdown","_capturedPictures":0,"_latestFileUrl":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e/100RICOH/R0012313.JPG","_recordableTime":0,"_recordedTime":0,"_function":"selfTimer"}} diff --git a/kotlin-multiplatform/src/commonTest/resources/CompositeIntervalCapture/state_self_timer.json b/kotlin-multiplatform/src/commonTest/resources/CompositeIntervalCapture/state_self_timer.json index e34db1a80d..1bdc92b1d7 100644 --- a/kotlin-multiplatform/src/commonTest/resources/CompositeIntervalCapture/state_self_timer.json +++ b/kotlin-multiplatform/src/commonTest/resources/CompositeIntervalCapture/state_self_timer.json @@ -1 +1 @@ -{"fingerprint":"FIG_0003","state":{"batteryLevel":0.8,"storageUri":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e","_apiVersion":2,"_batteryState":"charging","_cameraError":[],"_captureStatus":"idle","_capturedPictures":0,"_latestFileUrl":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e/100RICOH/R0012313.JPG","_recordableTime":0,"_recordedTime":0,"_function":"selfTimer"}} +{"fingerprint":"FIG_0003","state":{"batteryLevel":0.8,"storageUri":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e","_apiVersion":2,"_batteryState":"charging","_cameraError":[],"_captureStatus":"self-timer countdown","_capturedPictures":0,"_latestFileUrl":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e/100RICOH/R0012313.JPG","_recordableTime":0,"_recordedTime":0,"_function":"selfTimer"}} diff --git a/kotlin-multiplatform/src/commonTest/resources/ContinuousCapture/state_self_timer.json b/kotlin-multiplatform/src/commonTest/resources/ContinuousCapture/state_self_timer.json new file mode 100644 index 0000000000..1bdc92b1d7 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/ContinuousCapture/state_self_timer.json @@ -0,0 +1 @@ +{"fingerprint":"FIG_0003","state":{"batteryLevel":0.8,"storageUri":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e","_apiVersion":2,"_batteryState":"charging","_cameraError":[],"_captureStatus":"self-timer countdown","_capturedPictures":0,"_latestFileUrl":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e/100RICOH/R0012313.JPG","_recordableTime":0,"_recordedTime":0,"_function":"selfTimer"}} diff --git a/kotlin-multiplatform/src/commonTest/resources/ContinuousCapture/state_shooting.json b/kotlin-multiplatform/src/commonTest/resources/ContinuousCapture/state_shooting.json new file mode 100644 index 0000000000..b6aecc5163 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/ContinuousCapture/state_shooting.json @@ -0,0 +1,22 @@ +{ + "fingerprint": "FIG_0008", + "state": { + "_apiVersion": 2, + "batteryLevel": 0.81, + "_batteryState": "disconnect", + "_cameraError": [ + "COMPASS_CALIBRATION" + ], + "_captureStatus": "shooting", + "_capturedPictures": 0, + "_compositeShootingElapsedTime": 0, + "_function": "selfTimer", + "_latestFileUrl": "http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0013331.JPG", + "_mySettingChanged": false, + "_pluginRunning": false, + "_pluginWebServer": true, + "_recordableTime": 0, + "_recordedTime": 0, + "storageUri": "http://192.168.1.1/files/150100524436344d4201375fda9dc400/" + } +} diff --git a/kotlin-multiplatform/src/commonTest/resources/LimitlessIntervalCapture/state_self_timer.json b/kotlin-multiplatform/src/commonTest/resources/LimitlessIntervalCapture/state_self_timer.json new file mode 100644 index 0000000000..1bdc92b1d7 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/LimitlessIntervalCapture/state_self_timer.json @@ -0,0 +1 @@ +{"fingerprint":"FIG_0003","state":{"batteryLevel":0.8,"storageUri":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e","_apiVersion":2,"_batteryState":"charging","_cameraError":[],"_captureStatus":"self-timer countdown","_capturedPictures":0,"_latestFileUrl":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e/100RICOH/R0012313.JPG","_recordableTime":0,"_recordedTime":0,"_function":"selfTimer"}} diff --git a/kotlin-multiplatform/src/commonTest/resources/MultiBracketCapture/state_self_timer.json b/kotlin-multiplatform/src/commonTest/resources/MultiBracketCapture/state_self_timer.json new file mode 100644 index 0000000000..1bdc92b1d7 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/MultiBracketCapture/state_self_timer.json @@ -0,0 +1 @@ +{"fingerprint":"FIG_0003","state":{"batteryLevel":0.8,"storageUri":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e","_apiVersion":2,"_batteryState":"charging","_cameraError":[],"_captureStatus":"self-timer countdown","_capturedPictures":0,"_latestFileUrl":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e/100RICOH/R0012313.JPG","_recordableTime":0,"_recordedTime":0,"_function":"selfTimer"}} diff --git a/kotlin-multiplatform/src/commonTest/resources/PhotoCapture/state_idle.json b/kotlin-multiplatform/src/commonTest/resources/PhotoCapture/state_idle.json new file mode 100644 index 0000000000..e9c43eff77 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/PhotoCapture/state_idle.json @@ -0,0 +1,22 @@ +{ + "fingerprint": "FIG_0008", + "state": { + "_apiVersion": 2, + "batteryLevel": 0.81, + "_batteryState": "disconnect", + "_cameraError": [ + "COMPASS_CALIBRATION" + ], + "_captureStatus": "idle", + "_capturedPictures": 0, + "_compositeShootingElapsedTime": 0, + "_function": "selfTimer", + "_latestFileUrl": "http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0013331.JPG", + "_mySettingChanged": false, + "_pluginRunning": false, + "_pluginWebServer": true, + "_recordableTime": 0, + "_recordedTime": 0, + "storageUri": "http://192.168.1.1/files/150100524436344d4201375fda9dc400/" + } +} diff --git a/kotlin-multiplatform/src/commonTest/resources/PhotoCapture/state_self_timer.json b/kotlin-multiplatform/src/commonTest/resources/PhotoCapture/state_self_timer.json new file mode 100644 index 0000000000..1bdc92b1d7 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/PhotoCapture/state_self_timer.json @@ -0,0 +1 @@ +{"fingerprint":"FIG_0003","state":{"batteryLevel":0.8,"storageUri":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e","_apiVersion":2,"_batteryState":"charging","_cameraError":[],"_captureStatus":"self-timer countdown","_capturedPictures":0,"_latestFileUrl":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e/100RICOH/R0012313.JPG","_recordableTime":0,"_recordedTime":0,"_function":"selfTimer"}} diff --git a/kotlin-multiplatform/src/commonTest/resources/ShotCountSpecifiedIntervalCapture/state_self_timer.json b/kotlin-multiplatform/src/commonTest/resources/ShotCountSpecifiedIntervalCapture/state_self_timer.json index e34db1a80d..1bdc92b1d7 100644 --- a/kotlin-multiplatform/src/commonTest/resources/ShotCountSpecifiedIntervalCapture/state_self_timer.json +++ b/kotlin-multiplatform/src/commonTest/resources/ShotCountSpecifiedIntervalCapture/state_self_timer.json @@ -1 +1 @@ -{"fingerprint":"FIG_0003","state":{"batteryLevel":0.8,"storageUri":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e","_apiVersion":2,"_batteryState":"charging","_cameraError":[],"_captureStatus":"idle","_capturedPictures":0,"_latestFileUrl":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e/100RICOH/R0012313.JPG","_recordableTime":0,"_recordedTime":0,"_function":"selfTimer"}} +{"fingerprint":"FIG_0003","state":{"batteryLevel":0.8,"storageUri":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e","_apiVersion":2,"_batteryState":"charging","_cameraError":[],"_captureStatus":"self-timer countdown","_capturedPictures":0,"_latestFileUrl":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e/100RICOH/R0012313.JPG","_recordableTime":0,"_recordedTime":0,"_function":"selfTimer"}} diff --git a/kotlin-multiplatform/src/commonTest/resources/TimeShiftCapture/state_self_timer.json b/kotlin-multiplatform/src/commonTest/resources/TimeShiftCapture/state_self_timer.json new file mode 100644 index 0000000000..1bdc92b1d7 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/TimeShiftCapture/state_self_timer.json @@ -0,0 +1 @@ +{"fingerprint":"FIG_0003","state":{"batteryLevel":0.8,"storageUri":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e","_apiVersion":2,"_batteryState":"charging","_cameraError":[],"_captureStatus":"self-timer countdown","_capturedPictures":0,"_latestFileUrl":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e/100RICOH/R0012313.JPG","_recordableTime":0,"_recordedTime":0,"_function":"selfTimer"}} diff --git a/kotlin-multiplatform/src/commonTest/resources/TimeShiftCapture/state_shooting.json b/kotlin-multiplatform/src/commonTest/resources/TimeShiftCapture/state_shooting.json new file mode 100644 index 0000000000..de6491203c --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/TimeShiftCapture/state_shooting.json @@ -0,0 +1 @@ +{"fingerprint":"FIG_0003","state":{"batteryLevel":0.8,"storageUri":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e","_apiVersion":2,"_batteryState":"charging","_cameraError":[],"_captureStatus":"timeShift shooting","_capturedPictures":0,"_latestFileUrl":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e/100RICOH/R0012313.JPG","_recordableTime":0,"_recordedTime":0,"_function":"selfTimer"}} diff --git a/kotlin-multiplatform/src/commonTest/resources/VideoCapture/state_self_timer.json b/kotlin-multiplatform/src/commonTest/resources/VideoCapture/state_self_timer.json new file mode 100644 index 0000000000..1bdc92b1d7 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/VideoCapture/state_self_timer.json @@ -0,0 +1 @@ +{"fingerprint":"FIG_0003","state":{"batteryLevel":0.8,"storageUri":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e","_apiVersion":2,"_batteryState":"charging","_cameraError":[],"_captureStatus":"self-timer countdown","_capturedPictures":0,"_latestFileUrl":"http://192.168.1.1/files/thetasc26c21a247daf35838792bad9e/100RICOH/R0012313.JPG","_recordableTime":0,"_recordedTime":0,"_function":"selfTimer"}} diff --git a/react-native/android/build.gradle b/react-native/android/build.gradle index 889ef179ad..88207bae54 100644 --- a/react-native/android/build.gradle +++ b/react-native/android/build.gradle @@ -135,7 +135,7 @@ dependencies { implementation "com.facebook.react:react-native:+" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" - implementation "com.ricoh360.thetaclient:theta-client:1.9.1" + implementation "com.ricoh360.thetaclient:theta-client:1.10.0" // From node_modules } diff --git a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt index 9206a4e32c..0d2c74febc 100644 --- a/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt +++ b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt @@ -16,6 +16,7 @@ const val KEY_NOTIFY_PARAMS = "params" const val KEY_NOTIFY_PARAM_COMPLETION = "completion" const val KEY_NOTIFY_PARAM_EVENT = "event" const val KEY_NOTIFY_PARAM_MESSAGE = "message" +const val KEY_NOTIFY_PARAM_STATUS = "status" const val KEY_GPS_INFO = "gpsInfo" const val KEY_STATE_EXTERNAL_GPS_INFO = "externalGpsInfo" const val KEY_STATE_INTERNAL_GPS_INFO = "internalGpsInfo" @@ -114,6 +115,12 @@ fun toMessageNotifyParam(value: String): WritableMap { return result } +fun toCapturingNotifyParam(status: CapturingStatusEnum): WritableMap { + val result = Arguments.createMap() + result.putString(KEY_NOTIFY_PARAM_STATUS, status.name) + return result +} + fun toGpsInfo(map: ReadableMap): GpsInfo { return GpsInfo( latitude = map.getDouble("latitude").toFloat(), @@ -157,6 +164,13 @@ fun setCaptureBuilderParams(optionMap: ReadableMap, builder: Capture.Builder } fun setPhotoCaptureBuilderParams(optionMap: ReadableMap, builder: PhotoCapture.Builder) { + val interval = if (optionMap.hasKey("_capture_interval")) optionMap.getInt("_capture_interval") else null + interval?.let { + if (it >= 0) { + builder.setCheckStatusCommandInterval(it.toLong()) + } + } + optionMap.getString("filter")?.let { builder.setFilter(FilterEnum.valueOf(it)) } @@ -190,6 +204,13 @@ fun setTimeShiftCaptureBuilderParams(optionMap: ReadableMap, builder: TimeShiftC } fun setVideoCaptureBuilderParams(optionMap: ReadableMap, builder: VideoCapture.Builder) { + val interval = + if (optionMap.hasKey("_capture_interval")) optionMap.getInt("_capture_interval") else null + interval?.let { + if (it >= 0) { + builder.setCheckStatusCommandInterval(it.toLong()) + } + } optionMap.getString("maxRecordableTime")?.let { builder.setMaxRecordableTime(MaxRecordableTimeEnum.valueOf(it)) } @@ -199,6 +220,12 @@ fun setVideoCaptureBuilderParams(optionMap: ReadableMap, builder: VideoCapture.B } fun setLimitlessIntervalCaptureBuilderParams(optionMap: ReadableMap, builder: LimitlessIntervalCapture.Builder) { + val interval = if (optionMap.hasKey("_capture_interval")) optionMap.getInt("_capture_interval") else null + interval?.let { + if (it >= 0) { + builder.setCheckStatusCommandInterval(it.toLong()) + } + } if (optionMap.hasKey("captureInterval")) { builder.setCaptureInterval(optionMap.getInt("captureInterval")) } 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 351b165a8c..fa608a4275 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 @@ -537,6 +537,13 @@ class ThetaClientReactNativeModule( photoCapture = null } + override fun onCapturing(status: CapturingStatusEnum) { + super.onCapturing(status) + sendNotifyEvent( + toNotify(NOTIFY_PHOTO_CAPTURING, toCapturingNotifyParam(status = status)) + ) + } + override fun onError(exception: ThetaRepository.ThetaRepositoryException) { promise.reject(exception) photoCapture = null @@ -618,6 +625,13 @@ class ThetaClientReactNativeModule( ) } + override fun onCapturing(status: CapturingStatusEnum) { + super.onCapturing(status) + sendNotifyEvent( + toNotify(NOTIFY_TIMESHIFT_CAPTURING, toCapturingNotifyParam(status = status)) + ) + } + override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { promise.reject(exception) timeShiftCapture = null @@ -728,6 +742,13 @@ class ThetaClientReactNativeModule( ) ) } + + override fun onCapturing(status: CapturingStatusEnum) { + super.onCapturing(status) + sendNotifyEvent( + toNotify(NOTIFY_VIDEO_CAPTURE_CAPTURING, toCapturingNotifyParam(status = status)) + ) + } } videoCapturing = videoCapture?.startCapture(StartCaptureCallback()) } @@ -830,6 +851,16 @@ class ThetaClientReactNativeModule( ) ) } + + override fun onCapturing(status: CapturingStatusEnum) { + super.onCapturing(status) + sendNotifyEvent( + toNotify( + NOTIFY_LIMITLESS_INTERVAL_CAPTURE_CAPTURING, + toCapturingNotifyParam(status = status) + ) + ) + } } limitlessIntervalCapturing = limitlessIntervalCapture?.startCapture(StartCaptureCallback()) } @@ -927,6 +958,15 @@ class ThetaClientReactNativeModule( ) } + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + toNotify( + NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_CAPTURING, + toCapturingNotifyParam(status = status) + ) + ) + } + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { sendNotifyEvent( toNotify( @@ -1041,6 +1081,12 @@ class ThetaClientReactNativeModule( ) } + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + toNotify(NOTIFY_COMPOSITE_INTERVAL_CAPTURING, toCapturingNotifyParam(status = status)) + ) + } + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { sendNotifyEvent( toNotify( @@ -1176,6 +1222,12 @@ class ThetaClientReactNativeModule( ) } + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + toNotify(NOTIFY_BURST_CAPTURING, toCapturingNotifyParam(status = status)) + ) + } + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { sendNotifyEvent( toNotify( @@ -1288,6 +1340,12 @@ class ThetaClientReactNativeModule( ) } + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + toNotify(NOTIFY_MULTI_BRACKET_CAPTURING, toCapturingNotifyParam(status = status)) + ) + } + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { sendNotifyEvent( toNotify( @@ -1402,6 +1460,12 @@ class ThetaClientReactNativeModule( ) } + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + toNotify(NOTIFY_CONTINUOUS_CAPTURING, toCapturingNotifyParam(status = status)) + ) + } + override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { promise.reject(exception) continuousCapture = null @@ -2084,19 +2148,28 @@ class ThetaClientReactNativeModule( const val NAME = "ThetaClientReactNative" const val EVENT_NAME = "ThetaFrameEvent" const val EVENT_NOTIFY = "ThetaNotify" + const val NOTIFY_PHOTO_CAPTURING = "PHOTO-CAPTURING" const val NOTIFY_TIMESHIFT_PROGRESS = "TIME-SHIFT-PROGRESS" const val NOTIFY_TIMESHIFT_STOP_ERROR = "TIME-SHIFT-STOP-ERROR" + const val NOTIFY_TIMESHIFT_CAPTURING = "TIME-SHIFT-CAPTURING" const val NOTIFY_VIDEO_CAPTURE_STOP_ERROR = "VIDEO-CAPTURE-STOP-ERROR" + const val NOTIFY_VIDEO_CAPTURE_CAPTURING = "VIDEO-CAPTURE-CAPTURING" const val NOTIFY_LIMITLESS_INTERVAL_CAPTURE_STOP_ERROR = "LIMITLESS-INTERVAL-CAPTURE-STOP-ERROR" + const val NOTIFY_LIMITLESS_INTERVAL_CAPTURE_CAPTURING = "LIMITLESS-INTERVAL-CAPTURE-CAPTURING" const val NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_PROGRESS = "SHOT-COUNT-SPECIFIED-INTERVAL-PROGRESS" const val NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_STOP_ERROR = "SHOT-COUNT-SPECIFIED-INTERVAL-STOP-ERROR" + const val NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_CAPTURING = "SHOT-COUNT-SPECIFIED-INTERVAL-CAPTURING" const val NOTIFY_COMPOSITE_INTERVAL_PROGRESS = "COMPOSITE-INTERVAL-PROGRESS" const val NOTIFY_COMPOSITE_INTERVAL_STOP_ERROR = "COMPOSITE-INTERVAL-STOP-ERROR" + const val NOTIFY_COMPOSITE_INTERVAL_CAPTURING = "COMPOSITE-INTERVAL-CAPTURING" const val NOTIFY_BURST_PROGRESS = "BURST-PROGRESS" const val NOTIFY_BURST_STOP_ERROR = "BURST-STOP-ERROR" + const val NOTIFY_BURST_CAPTURING = "BURST-CAPTURING" const val NOTIFY_MULTI_BRACKET_PROGRESS = "MULTI-BRACKET-PROGRESS" const val NOTIFY_MULTI_BRACKET_STOP_ERROR = "MULTI-BRACKET-STOP-ERROR" + const val NOTIFY_MULTI_BRACKET_CAPTURING = "MULTI-BRACKET-CAPTURING" const val NOTIFY_CONTINUOUS_PROGRESS = "CONTINUOUS-PROGRESS" + const val NOTIFY_CONTINUOUS_CAPTURING = "CONTINUOUS-CAPTURING" const val NOTIFY_EVENT_WEBSOCKET_EVENT = "EVENT-WEBSOCKET-EVENT" const val NOTIFY_EVENT_WEBSOCKET_CLOSE = "EVENT-WEBSOCKET-CLOSE" } diff --git a/react-native/ios/ConvertUtil.swift b/react-native/ios/ConvertUtil.swift index 3bb6e09fae..86b4e532ee 100644 --- a/react-native/ios/ConvertUtil.swift +++ b/react-native/ios/ConvertUtil.swift @@ -8,6 +8,7 @@ let KEY_NOTIFY_PARAM_COMPLETION = "completion" let KEY_NOTIFY_PARAM_EVENT = "event" let KEY_NOTIFY_PARAM_IMAGE = "image" let KEY_NOTIFY_PARAM_MESSAGE = "message" +let KEY_NOTIFY_PARAM_STATUS = "status" let KEY_DATETIME = "dateTime" let KEY_LANGUAGE = "language" let KEY_OFF_DELAY = "offDelay" @@ -457,6 +458,12 @@ func toMessageNotifyParam(value: String) -> [String: Any] { ] } +func toCapturingNotifyParam(value: CapturingStatusEnum) -> [String: Any] { + return [ + KEY_NOTIFY_PARAM_STATUS: value.name, + ] +} + // MARK: - Capture builder func setCaptureBuilderParams(params: [String: Any], builder: CaptureBuilder) { @@ -524,6 +531,11 @@ func setCaptureBuilderParams(params: [String: Any], builder: CaptureBuilder= 0 + { + builder.setCheckStatusCommandInterval(timeMillis: Int64(interval)) + } if let value = params[KEY_FILTER] as? String { if let enumValue = getEnumValue(values: ThetaRepository.FilterEnum.values(), name: value) { builder.setFilter(filter: enumValue) @@ -564,6 +576,11 @@ func setTimeShiftCaptureBuilderParams(params: [String: Any], builder: TimeShiftC } func setVideoCaptureBuilderParams(params: [String: Any], builder: VideoCapture.Builder) { + if let interval = params[KEY_TIMESHIFT_CAPTURE_INTERVAL] as? Int, + interval >= 0 + { + builder.setCheckStatusCommandInterval(timeMillis: Int64(interval)) + } if let value = params[KEY_MAX_RECORDABLE_TIME] as? String { if let enumValue = getEnumValue( values: ThetaRepository.MaxRecordableTimeEnum.values(), name: value @@ -581,6 +598,11 @@ func setVideoCaptureBuilderParams(params: [String: Any], builder: VideoCapture.B } func setLimitlessIntervalCaptureBuilderParams(params: [String: Any], builder: LimitlessIntervalCapture.Builder) { + if let interval = params[KEY_TIMESHIFT_CAPTURE_INTERVAL] as? Int, + interval >= 0 + { + builder.setCheckStatusCommandInterval(timeMillis: Int64(interval)) + } if let value = params[KEY_CAPTURE_INTERVAL] as? Int32 { builder.setCaptureInterval(interval: value) } diff --git a/react-native/ios/ThetaClientReactNative.swift b/react-native/ios/ThetaClientReactNative.swift index d5e4d036a3..8c09c6769a 100644 --- a/react-native/ios/ThetaClientReactNative.swift +++ b/react-native/ios/ThetaClientReactNative.swift @@ -65,22 +65,31 @@ class ThetaClientReactNative: RCTEventEmitter { static let EVENT_FRAME = "ThetaFrameEvent" static let EVENT_NOTIFY = "ThetaNotify" + static let NOTIFY_PHOTO_CAPTURING = "PHOTO-CAPTURING" static let NOTIFY_TIMESHIFT_PROGRESS = "TIME-SHIFT-PROGRESS" static let NOTIFY_TIMESHIFT_STOP_ERROR = "TIME-SHIFT-STOP-ERROR" + static let NOTIFY_TIMESHIFT_CAPTURING = "TIME-SHIFT-CAPTURING" static let NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_PROGRESS = "SHOT-COUNT-SPECIFIED-INTERVAL-PROGRESS" static let NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_STOP_ERROR = "SHOT-COUNT-SPECIFIED-INTERVAL-STOP-ERROR" + static let NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_CAPTURING = "SHOT-COUNT-SPECIFIED-INTERVAL-CAPTURING" static let NOTIFY_VIDEO_CAPTURE_STOP_ERROR = "VIDEO-CAPTURE-STOP-ERROR" + static let NOTIFY_VIDEO_CAPTURE_CAPTURING = "VIDEO-CAPTURE-CAPTURING" static let NOTIFY_LIMITLESS_INTERVAL_CAPTURE_STOP_ERROR = "LIMITLESS-INTERVAL-CAPTURE-STOP-ERROR" + static let NOTIFY_LIMITLESS_INTERVAL_CAPTURE_CAPTURING = "LIMITLESS-INTERVAL-CAPTURE-CAPTURING" static let NOTIFY_COMPOSITE_INTERVAL_PROGRESS = "COMPOSITE-INTERVAL-PROGRESS" static let NOTIFY_COMPOSITE_INTERVAL_STOP_ERROR = "COMPOSITE-INTERVAL-STOP-ERROR" + static let NOTIFY_COMPOSITE_INTERVAL_CAPTURING = "COMPOSITE-INTERVAL-CAPTURING" static let NOTIFY_BURST_PROGRESS = "BURST-PROGRESS" static let NOTIFY_BURST_STOP_ERROR = "BURST-STOP-ERROR" + static let NOTIFY_BURST_CAPTURING = "BURST-CAPTURING" static let NOTIFY_MULTI_BRACKET_PROGRESS = "MULTI-BRACKET-PROGRESS" static let NOTIFY_MULTI_BRACKET_STOP_ERROR = "MULTI-BRACKET-STOP-ERROR" + static let NOTIFY_MULTI_BRACKET_CAPTURING = "MULTI-BRACKET-CAPTURING" static let NOTIFY_CONTINUOUS_PROGRESS = "CONTINUOUS-PROGRESS" + static let NOTIFY_CONTINUOUS_CAPTURING = "CONTINUOUS-CAPTURING" static let NOTIFY_EVENT_WEBSOCKET_EVENT = "EVENT-WEBSOCKET-EVENT" static let NOTIFY_EVENT_WEBSOCKET_CLOSE = "EVENT-WEBSOCKET-CLOSE" - + @objc override func supportedEvents() -> [String]! { return [ThetaClientReactNative.EVENT_FRAME, ThetaClientReactNative.EVENT_NOTIFY] @@ -556,15 +565,28 @@ class ThetaClientReactNative: RCTEventEmitter { class Callback: PhotoCaptureTakePictureCallback { let callback: (_ url: String?, _ error: Error?) -> Void - init(_ callback: @escaping (_ url: String?, _ error: Error?) -> Void) { + weak var client: ThetaClientReactNative? + init( + _ callback: @escaping (_ url: String?, _ error: Error?) -> Void, + client: ThetaClientReactNative + ) { self.callback = callback + self.client = client } func onSuccess(fileUrl: String?) { callback(fileUrl, nil) } - func onProgress(completion _: Float) {} + func onCapturing(status: CapturingStatusEnum) { + client?.sendEvent( + withName: ThetaClientReactNative.EVENT_NOTIFY, + body: toNotify( + name: ThetaClientReactNative.NOTIFY_PHOTO_CAPTURING, + params: toCapturingNotifyParam(value: status) + ) + ) + } func onError(exception: ThetaRepository.ThetaRepositoryException) { callback(nil, exception.asError()) @@ -572,14 +594,15 @@ class ThetaClientReactNative: RCTEventEmitter { } photoCapture.takePicture( - callback: Callback { url, error in + callback: Callback({ url, error in if let error { reject(ERROR_CODE_ERROR, error.localizedDescription, error) } else { self.photoCapture = nil resolve(url) } - }) + }, client: self) + ) } @objc(getTimeShiftCaptureBuilder:withRejecter:) @@ -676,7 +699,17 @@ class ThetaClientReactNative: RCTEventEmitter { ) ) } - + + func onCapturing(status: CapturingStatusEnum) { + client?.sendEvent( + withName: ThetaClientReactNative.EVENT_NOTIFY, + body: toNotify( + name: ThetaClientReactNative.NOTIFY_TIMESHIFT_CAPTURING, + params: toCapturingNotifyParam(value: status) + ) + ) + } + func onCaptureCompleted(fileUrl: String?) { callback(fileUrl, nil) } @@ -798,6 +831,16 @@ class ThetaClientReactNative: RCTEventEmitter { ) ) } + + func onCapturing(status: CapturingStatusEnum) { + client?.sendEvent( + withName: ThetaClientReactNative.EVENT_NOTIFY, + body: toNotify( + name: ThetaClientReactNative.NOTIFY_VIDEO_CAPTURE_CAPTURING, + params: toCapturingNotifyParam(value: status) + ) + ) + } } videoCapturing = videoCapture.startCapture( @@ -918,6 +961,16 @@ class ThetaClientReactNative: RCTEventEmitter { ) ) } + + func onCapturing(status: CapturingStatusEnum) { + client?.sendEvent( + withName: ThetaClientReactNative.EVENT_NOTIFY, + body: toNotify( + name: ThetaClientReactNative.NOTIFY_LIMITLESS_INTERVAL_CAPTURE_CAPTURING, + params: toCapturingNotifyParam(value: status) + ) + ) + } } limitlessIntervalCapturing = limitlessIntervalCapture.startCapture( @@ -1034,7 +1087,17 @@ class ThetaClientReactNative: RCTEventEmitter { ) ) } - + + func onCapturing(status: CapturingStatusEnum) { + client?.sendEvent( + withName: ThetaClientReactNative.EVENT_NOTIFY, + body: toNotify( + name: ThetaClientReactNative.NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_CAPTURING, + params: toCapturingNotifyParam(value: status) + ) + ) + } + func onCaptureCompleted(fileUrls: [String]?) { callback(fileUrls, nil) } @@ -1165,6 +1228,16 @@ class ThetaClientReactNative: RCTEventEmitter { ) } + func onCapturing(status: CapturingStatusEnum) { + client?.sendEvent( + withName: ThetaClientReactNative.EVENT_NOTIFY, + body: toNotify( + name: ThetaClientReactNative.NOTIFY_COMPOSITE_INTERVAL_CAPTURING, + params: toCapturingNotifyParam(value: status) + ) + ) + } + func onCaptureCompleted(fileUrls: [String]?) { callback(fileUrls, nil) } @@ -1319,6 +1392,16 @@ class ThetaClientReactNative: RCTEventEmitter { ) } + func onCapturing(status: CapturingStatusEnum) { + client?.sendEvent( + withName: ThetaClientReactNative.EVENT_NOTIFY, + body: toNotify( + name: ThetaClientReactNative.NOTIFY_BURST_CAPTURING, + params: toCapturingNotifyParam(value: status) + ) + ) + } + func onCaptureCompleted(fileUrls: [String]?) { callback(fileUrls, nil) } @@ -1449,7 +1532,17 @@ class ThetaClientReactNative: RCTEventEmitter { ) ) } - + + func onCapturing(status: CapturingStatusEnum) { + client?.sendEvent( + withName: ThetaClientReactNative.EVENT_NOTIFY, + body: toNotify( + name: ThetaClientReactNative.NOTIFY_MULTI_BRACKET_CAPTURING, + params: toCapturingNotifyParam(value: status) + ) + ) + } + func onCaptureCompleted(fileUrls: [String]?) { callback(fileUrls, nil) } @@ -1580,7 +1673,17 @@ class ThetaClientReactNative: RCTEventEmitter { ) ) } - + + func onCapturing(status: CapturingStatusEnum) { + client?.sendEvent( + withName: ThetaClientReactNative.EVENT_NOTIFY, + body: toNotify( + name: ThetaClientReactNative.NOTIFY_CONTINUOUS_CAPTURING, + params: toCapturingNotifyParam(value: status) + ) + ) + } + func onCaptureCompleted(fileUrls: [String]?) { callback(fileUrls, nil) } diff --git a/react-native/package.json b/react-native/package.json index 57381f315f..5f131060dd 100644 --- a/react-native/package.json +++ b/react-native/package.json @@ -1,6 +1,6 @@ { "name": "theta-client-react-native", - "version": "1.9.1", + "version": "1.10.0", "description": "This library provides a way to control RICOH THETA using.", "main": "lib/commonjs/index", "module": "lib/module/index", @@ -83,7 +83,7 @@ "engines": { "node": ">= 16.0.0" }, - "packageManager": "yarn@1.22.15", + "packageManager": "yarn@1.22.22", "jest": { "preset": "react-native", "modulePathIgnorePatterns": [ diff --git a/react-native/src/__tests__/capture/burst-capture.test.ts b/react-native/src/__tests__/capture/burst-capture.test.ts index 0136e09323..8a06a3657d 100644 --- a/react-native/src/__tests__/capture/burst-capture.test.ts +++ b/react-native/src/__tests__/capture/burst-capture.test.ts @@ -14,6 +14,7 @@ import { BurstOrderEnum, BurstModeEnum, } from '../../theta-repository/options'; +import { CapturingStatusEnum } from '../../capture'; describe('burst shooting', () => { const thetaClient = NativeModules.ThetaClientReactNative; @@ -149,6 +150,65 @@ describe('burst shooting', () => { expect(NotifyController.instance.notifyList.size).toBe(0); }); + test('startCaptureWithCapturing', async () => { + let notifyCallback: (notify: BaseNotify) => void = () => { + expect(true).toBeFalsy(); + }; + jest.mocked(NativeEventEmitter_addListener).mockImplementation( + jest.fn((_, callback) => { + notifyCallback = callback; + return { + remove: jest.fn(), + }; + }) + ); + + await initialize(); + + const builder = getBurstCaptureBuilder( + burstCaptureNum, + burstBracketStep, + burstCompensation, + burstMaxExposureTime, + burstEnableIsoControl, + burstOrder + ); + + const sendStatus = (status: CapturingStatusEnum) => { + notifyCallback({ + name: 'BURST-CAPTURING', + params: { + status: status, + }, + }); + }; + + jest + .mocked(thetaClient.buildBurstCapture) + .mockImplementation(jest.fn(async () => {})); + const testUrls = ['http://192.168.1.1/files/100RICOH/R100.JPG']; + jest.mocked(thetaClient.startBurstCapture).mockImplementation( + jest.fn(async () => { + sendStatus(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + return testUrls; + }) + ); + + const capture = await builder.build(); + let isOnCapturing = false; + const fileUrls = await capture.startCapture( + undefined, + undefined, + (status) => { + expect(status).toBe(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + isOnCapturing = true; + } + ); + expect(fileUrls).toBe(testUrls); + expect(NotifyController.instance.notifyList.size).toBe(0); + expect(isOnCapturing).toBeTruthy(); + }); + test('cancelCapture', (done) => { jest.mocked(NativeEventEmitter_addListener).mockImplementation( jest.fn(() => { diff --git a/react-native/src/__tests__/capture/composite-interval-capture.test.ts b/react-native/src/__tests__/capture/composite-interval-capture.test.ts index 264b3f246e..ec81aa17f6 100644 --- a/react-native/src/__tests__/capture/composite-interval-capture.test.ts +++ b/react-native/src/__tests__/capture/composite-interval-capture.test.ts @@ -8,6 +8,7 @@ import { NotifyController, } from '../../theta-repository/notify-controller'; import { NativeEventEmitter_addListener } from '../../__mocks__/react-native'; +import { CapturingStatusEnum } from '../../capture'; describe('interval composite shooting', () => { const thetaClient = NativeModules.ThetaClientReactNative; @@ -108,6 +109,56 @@ describe('interval composite shooting', () => { expect(NotifyController.instance.notifyList.size).toBe(0); }); + test('startCaptureWithCapturing', async () => { + let notifyCallback: (notify: BaseNotify) => void = () => { + expect(true).toBeFalsy(); + }; + jest.mocked(NativeEventEmitter_addListener).mockImplementation( + jest.fn((_, callback) => { + notifyCallback = callback; + return { + remove: jest.fn(), + }; + }) + ); + await initialize(); + const builder = getCompositeIntervalCaptureBuilder(600); + + const sendStatus = (status: CapturingStatusEnum) => { + notifyCallback({ + name: 'COMPOSITE-INTERVAL-CAPTURING', + params: { + status: status, + }, + }); + }; + + jest + .mocked(thetaClient.buildCompositeIntervalCapture) + .mockImplementation(jest.fn(async () => {})); + const testUrls = ['http://192.168.1.1/files/100RICOH/R100.JPG']; + jest.mocked(thetaClient.startCompositeIntervalCapture).mockImplementation( + jest.fn(async () => { + sendStatus(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + return testUrls; + }) + ); + + const capture = await builder.build(); + let isOnCapturing = false; + const fileUrls = await capture.startCapture( + undefined, + undefined, + (status) => { + expect(status).toBe(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + isOnCapturing = true; + } + ); + expect(fileUrls).toBe(testUrls); + expect(NotifyController.instance.notifyList.size).toBe(0); + expect(isOnCapturing).toBeTruthy(); + }); + test('cancelCapture', (done) => { jest.mocked(NativeEventEmitter_addListener).mockImplementation( jest.fn(() => { diff --git a/react-native/src/__tests__/capture/continuous-capture.test.ts b/react-native/src/__tests__/capture/continuous-capture.test.ts index 329ab03d71..b8061e4540 100644 --- a/react-native/src/__tests__/capture/continuous-capture.test.ts +++ b/react-native/src/__tests__/capture/continuous-capture.test.ts @@ -13,7 +13,7 @@ import { OptionNameEnum, PhotoFileFormatEnum, } from '../../theta-repository/options'; -import { ContinuousCapture } from '../../capture'; +import { CapturingStatusEnum, ContinuousCapture } from '../../capture'; describe('continuous shooting', () => { const thetaClient = NativeModules.ThetaClientReactNative; @@ -113,6 +113,53 @@ describe('continuous shooting', () => { expect(NotifyController.instance.notifyList.size).toBe(0); }); + test('startCaptureWithCapturing', async () => { + let notifyCallback: (notify: BaseNotify) => void = () => { + expect(true).toBeFalsy(); + }; + jest.mocked(NativeEventEmitter_addListener).mockImplementation( + jest.fn((_, callback) => { + notifyCallback = callback; + return { + remove: jest.fn(), + }; + }) + ); + await initialize(); + + const builder = getContinuousCaptureBuilder(); + + const sendStatus = (status: CapturingStatusEnum) => { + notifyCallback({ + name: 'CONTINUOUS-CAPTURING', + params: { + status: status, + }, + }); + }; + + jest + .mocked(thetaClient.buildContinuousCapture) + .mockImplementation(jest.fn(async () => {})); + const testUrls = ['http://192.168.1.1/files/100RICOH/R100.JPG']; + jest.mocked(thetaClient.startContinuousCapture).mockImplementation( + jest.fn(async () => { + sendStatus(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + return testUrls; + }) + ); + + const capture = await builder.build(); + let isOnCapturing = false; + const fileUrls = await capture.startCapture(undefined, (status) => { + expect(status).toBe(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + isOnCapturing = true; + }); + expect(fileUrls).toBe(testUrls); + expect(NotifyController.instance.notifyList.size).toBe(0); + expect(isOnCapturing).toBeTruthy(); + }); + test('exception', (done) => { jest.mocked(NativeEventEmitter_addListener).mockImplementation( jest.fn(() => { diff --git a/react-native/src/__tests__/capture/limitless-interval-capture.test.ts b/react-native/src/__tests__/capture/limitless-interval-capture.test.ts index e647efa446..9cc1f53be1 100644 --- a/react-native/src/__tests__/capture/limitless-interval-capture.test.ts +++ b/react-native/src/__tests__/capture/limitless-interval-capture.test.ts @@ -8,6 +8,7 @@ import { NotifyController, } from '../../theta-repository/notify-controller'; import { NativeEventEmitter_addListener } from '../../__mocks__/react-native'; +import { CapturingStatusEnum } from '../../capture'; describe('limitless interval capture', () => { const thetaClient = NativeModules.ThetaClientReactNative; @@ -35,14 +36,18 @@ describe('limitless interval capture', () => { ); const builder = getLimitlessIntervalCaptureBuilder(); expect(builder.options).toBeDefined(); + expect(builder.interval).toBeUndefined(); + builder.setCheckStatusCommandInterval(1); builder.setCaptureInterval(10); + expect(builder.interval).toBe(1); expect(builder.options.captureInterval).toBe(10); let isCallBuild = false; jest.mocked(thetaClient.buildLimitlessIntervalCapture).mockImplementation( jest.fn(async (options) => { + expect(options._capture_interval).toBe(1); expect(options.captureInterval).toBe(10); isCallBuild = true; }) @@ -80,6 +85,63 @@ describe('limitless interval capture', () => { expect(NotifyController.instance.notifyList.size).toBe(0); }); + test('startCaptureWithCapturing', async () => { + let notifyCallback: (notify: BaseNotify) => void = () => { + expect(true).toBeFalsy(); + }; + jest.mocked(NativeEventEmitter_addListener).mockImplementation( + jest.fn((_, callback) => { + notifyCallback = callback; + return { + remove: jest.fn(), + }; + }) + ); + await initialize(); + const builder = getLimitlessIntervalCaptureBuilder(); + + const sendStatus = (status: CapturingStatusEnum) => { + notifyCallback({ + name: 'LIMITLESS-INTERVAL-CAPTURE-CAPTURING', + params: { + status: status, + }, + }); + }; + + jest + .mocked(thetaClient.buildLimitlessIntervalCapture) + .mockImplementation(jest.fn(async () => {})); + const testUrls = ['http://192.168.1.1/files/100RICOH/R100.JPG']; + jest.mocked(thetaClient.startLimitlessIntervalCapture).mockImplementation( + jest.fn(async () => { + sendStatus(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + return testUrls; + }) + ); + + const capture = await builder.build(); + let isOnCapturing = false; + const fileUrls = await capture.startCapture(undefined, (status) => { + expect(status).toBe(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + isOnCapturing = true; + }); + expect(fileUrls).toBe(testUrls); + expect(isOnCapturing).toBeTruthy(); + + let done: (value: unknown) => void; + const promise = new Promise((resolve) => { + done = resolve; + }); + + setTimeout(() => { + expect(NotifyController.instance.notifyList.size).toBe(0); + done(0); + }, 1); + + return promise; + }); + test('stopCapture', (done) => { jest.mocked(NativeEventEmitter_addListener).mockImplementation( jest.fn(() => { diff --git a/react-native/src/__tests__/capture/multi-bracket-capture.test.ts b/react-native/src/__tests__/capture/multi-bracket-capture.test.ts index caa70a1501..a153799576 100644 --- a/react-native/src/__tests__/capture/multi-bracket-capture.test.ts +++ b/react-native/src/__tests__/capture/multi-bracket-capture.test.ts @@ -17,6 +17,7 @@ import { ShutterSpeedEnum, WhiteBalanceEnum, } from '../../theta-repository/options'; +import { CapturingStatusEnum } from '../../capture'; describe('multi bracket shooting', () => { const thetaClient = NativeModules.ThetaClientReactNative; @@ -281,4 +282,66 @@ describe('multi bracket shooting', () => { return promise; }); + + test('capturing events', async () => { + let notifyCallback: (notify: BaseNotify) => void = () => { + expect(true).toBeFalsy(); + }; + jest.mocked(NativeEventEmitter_addListener).mockImplementation( + jest.fn((_, callback) => { + notifyCallback = callback; + return { + remove: jest.fn(), + }; + }) + ); + + await initialize(); + const builder = getMultiBracketCaptureBuilder(); + jest + .mocked(thetaClient.buildMultiBracketCapture) + .mockImplementation(jest.fn(async () => {})); + const testUrl = 'http://192.168.1.1/files/100RICOH/R100.JPG'; + + const sendStatus = (status: CapturingStatusEnum) => { + notifyCallback({ + name: 'MULTI-BRACKET-CAPTURING', + params: { + status: status, + }, + }); + }; + + jest.mocked(thetaClient.startMultiBracketCapture).mockImplementation( + jest.fn(async () => { + sendStatus(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + return testUrl; + }) + ); + + const capture = await builder.build(); + let isOnCapturing = false; + const fileUrl = await capture.startCapture( + undefined, + undefined, + (status) => { + expect(status).toBe(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + isOnCapturing = true; + } + ); + expect(fileUrl).toBe(testUrl); + + let done: (value: unknown) => void; + const promise = new Promise((resolve) => { + done = resolve; + }); + + setTimeout(() => { + expect(NotifyController.instance.notifyList.size).toBe(0); + expect(isOnCapturing).toBeTruthy(); + done(0); + }, 1); + + return promise; + }); }); diff --git a/react-native/src/__tests__/capture/photo-capture.test.ts b/react-native/src/__tests__/capture/photo-capture.test.ts index ee24131f64..5a4a9c5013 100644 --- a/react-native/src/__tests__/capture/photo-capture.test.ts +++ b/react-native/src/__tests__/capture/photo-capture.test.ts @@ -1,22 +1,30 @@ import { NativeModules } from 'react-native'; -import { getPhotoCaptureBuilder } from '../../theta-repository'; +import { getPhotoCaptureBuilder, initialize } from '../../theta-repository'; import { FilterEnum, PhotoFileFormatEnum, PresetEnum, } from '../../theta-repository/options'; +import { NativeEventEmitter_addListener } from '../../__mocks__/react-native'; +import { + BaseNotify, + NotifyController, +} from '../../theta-repository/notify-controller'; +import { CapturingStatusEnum } from '../../capture'; describe('photo capture', () => { const thetaClient = NativeModules.ThetaClientReactNative; beforeEach(() => { jest.clearAllMocks(); + NotifyController.instance.release(); }); afterEach(() => { thetaClient.initialize = jest.fn(); thetaClient.buildPhotoCapture = jest.fn(); thetaClient.takePicture = jest.fn(); + NotifyController.instance.release(); }); test('getPhotoCaptureBuilder', async () => { @@ -65,6 +73,64 @@ describe('photo capture', () => { expect(fileUrl).toBe(testUrl); }); + test('takePictureWithCapturing', async () => { + let notifyCallback: (notify: BaseNotify) => void = () => { + expect(true).toBeFalsy(); + }; + jest.mocked(NativeEventEmitter_addListener).mockImplementation( + jest.fn((_, callback) => { + notifyCallback = callback; + return { + remove: jest.fn(), + }; + }) + ); + + await initialize(); + const builder = getPhotoCaptureBuilder(); + const testUrl = 'http://192.168.1.1/files/100RICOH/R100.JPG'; + + const sendStatus = (status: CapturingStatusEnum) => { + notifyCallback({ + name: 'PHOTO-CAPTURING', + params: { + status: status, + }, + }); + }; + + jest + .mocked(thetaClient.buildPhotoCapture) + .mockImplementation(jest.fn(async () => {})); + jest.mocked(thetaClient.takePicture).mockImplementation( + jest.fn(async () => { + sendStatus(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + return testUrl; + }) + ); + + const capture = await builder.build(); + let isOnCapturing = false; + const fileUrl = await capture.takePicture((status) => { + expect(status).toBe(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + isOnCapturing = true; + }); + expect(fileUrl).toBe(testUrl); + expect(isOnCapturing).toBeTruthy(); + let done: (value: unknown) => void; + const promise = new Promise((resolve) => { + done = resolve; + }); + + setTimeout(() => { + expect(NotifyController.instance.notifyList.size).toBe(0); + expect(isOnCapturing).toBeTruthy(); + done(0); + }, 1); + + return promise; + }); + test('exception', (done) => { const builder = getPhotoCaptureBuilder(); jest diff --git a/react-native/src/__tests__/capture/shot-count-specified-interval-capture.test.ts b/react-native/src/__tests__/capture/shot-count-specified-interval-capture.test.ts index 0283e8b455..43575655b2 100644 --- a/react-native/src/__tests__/capture/shot-count-specified-interval-capture.test.ts +++ b/react-native/src/__tests__/capture/shot-count-specified-interval-capture.test.ts @@ -8,6 +8,7 @@ import { NotifyController, } from '../../theta-repository/notify-controller'; import { NativeEventEmitter_addListener } from '../../__mocks__/react-native'; +import { CapturingStatusEnum } from '../../capture/capture'; describe('interval shooting with the shot count specified', () => { const thetaClient = NativeModules.ThetaClientReactNative; @@ -302,4 +303,68 @@ describe('interval shooting with the shot count specified', () => { return promise; }); + + test('capturing events', async () => { + let notifyCallback: (notify: BaseNotify) => void = () => { + expect(true).toBeFalsy(); + }; + jest.mocked(NativeEventEmitter_addListener).mockImplementation( + jest.fn((_, callback) => { + notifyCallback = callback; + return { + remove: jest.fn(), + }; + }) + ); + + await initialize(); + const builder = getShotCountSpecifiedIntervalCaptureBuilder(2); + jest + .mocked(thetaClient.buildShotCountSpecifiedIntervalCapture) + .mockImplementation(jest.fn(async () => {})); + const testUrls = ['http://192.168.1.1/files/100RICOH/R100.JPG']; + + const sendStatus = (status: CapturingStatusEnum) => { + notifyCallback({ + name: 'SHOT-COUNT-SPECIFIED-INTERVAL-CAPTURING', + params: { + status: status, + }, + }); + }; + + jest + .mocked(thetaClient.startShotCountSpecifiedIntervalCapture) + .mockImplementation( + jest.fn(async () => { + sendStatus(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + return testUrls; + }) + ); + + const capture = await builder.build(); + let isOnCapturing = false; + const fileUrls = await capture.startCapture( + undefined, + undefined, + (status) => { + expect(status).toBe(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + isOnCapturing = true; + } + ); + expect(fileUrls).toBe(testUrls); + + let done: (value: unknown) => void; + const promise = new Promise((resolve) => { + done = resolve; + }); + + setTimeout(() => { + expect(NotifyController.instance.notifyList.size).toBe(0); + expect(isOnCapturing).toBeTruthy(); + done(0); + }, 1); + + return promise; + }); }); diff --git a/react-native/src/__tests__/capture/time-shift-capture.test.ts b/react-native/src/__tests__/capture/time-shift-capture.test.ts index b7698461ea..845c7545a2 100644 --- a/react-native/src/__tests__/capture/time-shift-capture.test.ts +++ b/react-native/src/__tests__/capture/time-shift-capture.test.ts @@ -6,6 +6,7 @@ import { } from '../../theta-repository/notify-controller'; import { NativeEventEmitter_addListener } from '../../__mocks__/react-native'; import { TimeShiftIntervalEnum } from '../../theta-repository/options'; +import { CapturingStatusEnum } from '../../capture'; describe('time shift capture', () => { const thetaClient = NativeModules.ThetaClientReactNative; @@ -296,4 +297,66 @@ describe('time shift capture', () => { return promise; }); + + test('capturing events', async () => { + let notifyCallback: (notify: BaseNotify) => void = () => { + expect(true).toBeFalsy(); + }; + jest.mocked(NativeEventEmitter_addListener).mockImplementation( + jest.fn((_, callback) => { + notifyCallback = callback; + return { + remove: jest.fn(), + }; + }) + ); + + await initialize(); + const builder = getTimeShiftCaptureBuilder(); + jest + .mocked(thetaClient.buildShotCountSpecifiedIntervalCapture) + .mockImplementation(jest.fn(async () => {})); + const testUrl = 'http://192.168.1.1/files/100RICOH/R100.JPG'; + + const sendStatus = (status: CapturingStatusEnum) => { + notifyCallback({ + name: 'TIME-SHIFT-CAPTURING', + params: { + status, + }, + }); + }; + + jest.mocked(thetaClient.startTimeShiftCapture).mockImplementation( + jest.fn(async () => { + sendStatus(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + return testUrl; + }) + ); + + const capture = await builder.build(); + let isOnCapturing = false; + const fileUrl = await capture.startCapture( + undefined, + undefined, + (status) => { + expect(status).toBe(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + isOnCapturing = true; + } + ); + expect(fileUrl).toBe(testUrl); + + let done: (value: unknown) => void; + const promise = new Promise((resolve) => { + done = resolve; + }); + + setTimeout(() => { + expect(NotifyController.instance.notifyList.size).toBe(0); + expect(isOnCapturing).toBeTruthy(); + done(0); + }, 1); + + return promise; + }); }); diff --git a/react-native/src/__tests__/capture/video-capture.test.ts b/react-native/src/__tests__/capture/video-capture.test.ts index 988b93fb2b..aad8f5e1fb 100644 --- a/react-native/src/__tests__/capture/video-capture.test.ts +++ b/react-native/src/__tests__/capture/video-capture.test.ts @@ -9,6 +9,7 @@ import { MaxRecordableTimeEnum, VideoFileFormatEnum, } from '../../theta-repository/options'; +import { CapturingStatusEnum } from '../../capture'; describe('video capture', () => { const thetaClient = NativeModules.ThetaClientReactNative; @@ -37,9 +38,11 @@ describe('video capture', () => { const builder = getVideoCaptureBuilder(); expect(builder.options).toBeDefined(); + builder.setCheckStatusCommandInterval(1); builder.setFileFormat(VideoFileFormatEnum.VIDEO_4K); builder.setMaxRecordableTime(MaxRecordableTimeEnum.RECORDABLE_TIME_1500); + expect(builder.interval).toBe(1); expect(builder.options.fileFormat).toBe(VideoFileFormatEnum.VIDEO_4K); expect(builder.options.maxRecordableTime).toBe( MaxRecordableTimeEnum.RECORDABLE_TIME_1500 @@ -48,6 +51,7 @@ describe('video capture', () => { let isCallBuild = false; jest.mocked(thetaClient.buildVideoCapture).mockImplementation( jest.fn(async (options) => { + expect(options._capture_interval).toBe(1); expect(options.fileFormat).toBe(VideoFileFormatEnum.VIDEO_4K); expect(options.maxRecordableTime).toBe( MaxRecordableTimeEnum.RECORDABLE_TIME_1500 @@ -204,4 +208,62 @@ describe('video capture', () => { return promise; }); + + test('startCaptureWithCapturing', async () => { + let notifyCallback: (notify: BaseNotify) => void = () => { + expect(true).toBeFalsy(); + }; + jest.mocked(NativeEventEmitter_addListener).mockImplementation( + jest.fn((_, callback) => { + notifyCallback = callback; + return { + remove: jest.fn(), + }; + }) + ); + + await initialize(); + const builder = getVideoCaptureBuilder(); + jest + .mocked(thetaClient.buildVideoCapture) + .mockImplementation(jest.fn(async () => {})); + const testUrl = 'http://192.168.1.1/files/100RICOH/R100.MP4'; + + const sendStatus = (status: CapturingStatusEnum) => { + notifyCallback({ + name: 'VIDEO-CAPTURE-CAPTURING', + params: { + status: status, + }, + }); + }; + + jest.mocked(thetaClient.startVideoCapture).mockImplementation( + jest.fn(async () => { + sendStatus(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + return testUrl; + }) + ); + + const capture = await builder.build(); + let isOnCapturing = false; + const fileUrl = await capture.startCapture(undefined, (status) => { + expect(status).toBe(CapturingStatusEnum.SELF_TIMER_COUNTDOWN); + isOnCapturing = true; + }); + expect(fileUrl).toBe(testUrl); + + let done: (value: unknown) => void; + const promise = new Promise((resolve) => { + done = resolve; + }); + + setTimeout(() => { + expect(NotifyController.instance.notifyList.size).toBe(0); + expect(isOnCapturing).toBeTruthy(); + done(0); + }, 1); + + return promise; + }); }); diff --git a/react-native/src/__tests__/theta-repository/theta-state/capture-status.test.ts b/react-native/src/__tests__/theta-repository/theta-state/capture-status.test.ts new file mode 100644 index 0000000000..bbaa6f890f --- /dev/null +++ b/react-native/src/__tests__/theta-repository/theta-state/capture-status.test.ts @@ -0,0 +1,29 @@ +import { CaptureStatusEnum } from '../../../theta-repository/theta-state'; + +describe('CaptureStatusEnum', () => { + const data: [CaptureStatusEnum, string][] = [ + [CaptureStatusEnum.UNKNOWN, 'UNKNOWN'], + [CaptureStatusEnum.SHOOTING, 'SHOOTING'], + [CaptureStatusEnum.IDLE, 'IDLE'], + [CaptureStatusEnum.SELF_TIMER_COUNTDOWN, 'SELF_TIMER_COUNTDOWN'], + [CaptureStatusEnum.BRACKET_SHOOTING, 'BRACKET_SHOOTING'], + [CaptureStatusEnum.CONVERTING, 'CONVERTING'], + [CaptureStatusEnum.TIME_SHIFT_SHOOTING, 'TIME_SHIFT_SHOOTING'], + [CaptureStatusEnum.CONTINUOUS_SHOOTING, 'CONTINUOUS_SHOOTING'], + [ + CaptureStatusEnum.RETROSPECTIVE_IMAGE_RECORDING, + 'RETROSPECTIVE_IMAGE_RECORDING', + ], + [CaptureStatusEnum.BURST_SHOOTING, 'BURST_SHOOTING'], + ]; + + test('length', () => { + expect(data.length).toBe(Object.keys(CaptureStatusEnum).length); + }); + + test('data', () => { + data.forEach((item) => { + expect(item[0]).toBe(item[1]); + }); + }); +}); diff --git a/react-native/src/capture/burst-capture.ts b/react-native/src/capture/burst-capture.ts index a980b63ec0..00892bef5c 100644 --- a/react-native/src/capture/burst-capture.ts +++ b/react-native/src/capture/burst-capture.ts @@ -1,4 +1,4 @@ -import { CaptureBuilder } from './capture'; +import { CaptureBuilder, CapturingStatusEnum } from './capture'; import { NativeModules } from 'react-native'; import { BaseNotify, @@ -17,6 +17,7 @@ const ThetaClientReactNative = NativeModules.ThetaClientReactNative; const NOTIFY_PROGRESS = 'BURST-PROGRESS'; const NOTIFY_STOP_ERROR = 'BURST-STOP-ERROR'; +const NOTIFY_CAPTURING = 'BURST-CAPTURING'; interface CaptureProgressNotify extends BaseNotify { params?: { @@ -30,6 +31,12 @@ interface CaptureStopErrorNotify extends BaseNotify { }; } +interface CapturingNotify extends BaseNotify { + params?: { + status: CapturingStatusEnum; + }; +} + /** * BurstCapture class */ @@ -41,11 +48,14 @@ export class BurstCapture { /** * start burst shooting * @param onProgress the block for burst shooting onProgress + * @param onStopFailed Called when stopCapture error occurs + * @param onCapturing Called when change capture status * @return promise of captured file url */ async startCapture( onProgress?: (completion?: number) => void, - onStopFailed?: (error: any) => void + onStopFailed?: (error: any) => void, + onCapturing?: (status: CapturingStatusEnum) => void ): Promise { if (onProgress) { this.notify.addNotify(NOTIFY_PROGRESS, (event: CaptureProgressNotify) => { @@ -60,6 +70,13 @@ export class BurstCapture { } ); } + if (onCapturing) { + this.notify.addNotify(NOTIFY_CAPTURING, (event: CapturingNotify) => { + if (event.params?.status) { + onCapturing(event.params.status); + } + }); + } return new Promise(async (resolve, reject) => { await ThetaClientReactNative.startBurstCapture() @@ -72,6 +89,7 @@ export class BurstCapture { .finally(() => { this.notify.removeNotify(NOTIFY_PROGRESS); this.notify.removeNotify(NOTIFY_STOP_ERROR); + this.notify.removeNotify(NOTIFY_CAPTURING); }); }); } diff --git a/react-native/src/capture/capture.ts b/react-native/src/capture/capture.ts index 82c91ec9a0..d9c543b9f4 100644 --- a/react-native/src/capture/capture.ts +++ b/react-native/src/capture/capture.ts @@ -124,6 +124,18 @@ export abstract class CaptureBuilder> { } } +/** Capturing status */ +export const CapturingStatusEnum = { + /** Capture in progress */ + CAPTURING: 'CAPTURING', + /** Self-timer in progress */ + SELF_TIMER_COUNTDOWN: 'SELF_TIMER_COUNTDOWN', +} as const; + +/** type definition of CapturingStatusEnum */ +export type CapturingStatusEnum = + (typeof CapturingStatusEnum)[keyof typeof CapturingStatusEnum]; + export class PhotoCaptureBuilderBase< T extends PhotoCaptureBuilderBase > extends CaptureBuilder { diff --git a/react-native/src/capture/composite-interval-capture.ts b/react-native/src/capture/composite-interval-capture.ts index 9838b7ef1b..4dc737ee93 100644 --- a/react-native/src/capture/composite-interval-capture.ts +++ b/react-native/src/capture/composite-interval-capture.ts @@ -1,4 +1,4 @@ -import { CaptureBuilder } from './capture'; +import { CaptureBuilder, CapturingStatusEnum } from './capture'; import { NativeModules } from 'react-native'; import { BaseNotify, @@ -8,6 +8,7 @@ const ThetaClientReactNative = NativeModules.ThetaClientReactNative; const NOTIFY_PROGRESS = 'COMPOSITE-INTERVAL-PROGRESS'; const NOTIFY_STOP_ERROR = 'COMPOSITE-INTERVAL-STOP-ERROR'; +const NOTIFY_CAPTURING = 'COMPOSITE-INTERVAL-CAPTURING'; interface CaptureProgressNotify extends BaseNotify { params?: { @@ -21,6 +22,12 @@ interface CaptureStopErrorNotify extends BaseNotify { }; } +interface CapturingNotify extends BaseNotify { + params?: { + status: CapturingStatusEnum; + }; +} + /** * CompositeIntervalCapture class */ @@ -32,11 +39,14 @@ export class CompositeIntervalCapture { /** * start interval composite shooting * @param onProgress the block for interval composite shooting onProgress + * @param onStopFailed Called when stopCapture error occurs + * @param onCapturing Called when change capture status * @return promise of captured file url */ async startCapture( onProgress?: (completion?: number) => void, - onStopFailed?: (error: any) => void + onStopFailed?: (error: any) => void, + onCapturing?: (status: CapturingStatusEnum) => void ): Promise { if (onProgress) { this.notify.addNotify(NOTIFY_PROGRESS, (event: CaptureProgressNotify) => { @@ -51,6 +61,13 @@ export class CompositeIntervalCapture { } ); } + if (onCapturing) { + this.notify.addNotify(NOTIFY_CAPTURING, (event: CapturingNotify) => { + if (event.params?.status) { + onCapturing(event.params.status); + } + }); + } return new Promise(async (resolve, reject) => { await ThetaClientReactNative.startCompositeIntervalCapture() @@ -63,6 +80,7 @@ export class CompositeIntervalCapture { .finally(() => { this.notify.removeNotify(NOTIFY_PROGRESS); this.notify.removeNotify(NOTIFY_STOP_ERROR); + this.notify.removeNotify(NOTIFY_CAPTURING); }); }); } diff --git a/react-native/src/capture/continuous-capture.ts b/react-native/src/capture/continuous-capture.ts index bc14338154..dc329cba91 100644 --- a/react-native/src/capture/continuous-capture.ts +++ b/react-native/src/capture/continuous-capture.ts @@ -1,4 +1,4 @@ -import { CaptureBuilder } from './capture'; +import { CaptureBuilder, CapturingStatusEnum } from './capture'; import { NativeModules } from 'react-native'; import { BaseNotify, @@ -13,6 +13,7 @@ import { getOptions } from '../theta-repository'; const ThetaClientReactNative = NativeModules.ThetaClientReactNative; const NOTIFY_PROGRESS = 'CONTINUOUS-PROGRESS'; +const NOTIFY_CAPTURING = 'CONTINUOUS-CAPTURING'; interface CaptureProgressNotify extends BaseNotify { params?: { @@ -20,6 +21,12 @@ interface CaptureProgressNotify extends BaseNotify { }; } +interface CapturingNotify extends BaseNotify { + params?: { + status: CapturingStatusEnum; + }; +} + /** * ContinuousCapture class */ @@ -31,16 +38,25 @@ export class ContinuousCapture { /** * start continuous shooting * @param onProgress the block for continuous shooting onProgress + * @param onCapturing Called when change capture status * @return promise of captured file url */ async startCapture( - onProgress?: (completion?: number) => void + onProgress?: (completion?: number) => void, + onCapturing?: (status: CapturingStatusEnum) => void ): Promise { if (onProgress) { this.notify.addNotify(NOTIFY_PROGRESS, (event: CaptureProgressNotify) => { onProgress(event.params?.completion); }); } + if (onCapturing) { + this.notify.addNotify(NOTIFY_CAPTURING, (event: CapturingNotify) => { + if (event.params?.status) { + onCapturing(event.params.status); + } + }); + } return new Promise(async (resolve, reject) => { await ThetaClientReactNative.startContinuousCapture() @@ -52,6 +68,7 @@ export class ContinuousCapture { }) .finally(() => { this.notify.removeNotify(NOTIFY_PROGRESS); + this.notify.removeNotify(NOTIFY_CAPTURING); }); }); } diff --git a/react-native/src/capture/limitless-interval-capture.ts b/react-native/src/capture/limitless-interval-capture.ts index 0bc634651a..547230f576 100644 --- a/react-native/src/capture/limitless-interval-capture.ts +++ b/react-native/src/capture/limitless-interval-capture.ts @@ -1,4 +1,4 @@ -import { CaptureBuilder } from './capture'; +import { CaptureBuilder, CapturingStatusEnum } from './capture'; import { NativeModules } from 'react-native'; import { BaseNotify, @@ -7,6 +7,7 @@ import { const ThetaClientReactNative = NativeModules.ThetaClientReactNative; const NOTIFY_NAME = 'LIMITLESS-INTERVAL-CAPTURE-STOP-ERROR'; +const NOTIFY_CAPTURING = 'LIMITLESS-INTERVAL-CAPTURE-CAPTURING'; interface CaptureStopErrorNotify extends BaseNotify { params?: { @@ -14,6 +15,12 @@ interface CaptureStopErrorNotify extends BaseNotify { }; } +interface CapturingNotify extends BaseNotify { + params?: { + status: CapturingStatusEnum; + }; +} + /** * LimitlessIntervalCapture class */ @@ -26,16 +33,25 @@ export class LimitlessIntervalCapture { /** * start limitless interval capture * @param onStopFailed the block for error of stopCapture + * @param onCapturing Called when change capture status * @return promise of captured file url */ startCapture( - onStopFailed?: (error: any) => void + onStopFailed?: (error: any) => void, + onCapturing?: (status: CapturingStatusEnum) => void ): Promise { if (onStopFailed) { this.notify.addNotify(NOTIFY_NAME, (event: CaptureStopErrorNotify) => { onStopFailed(event.params); }); } + if (onCapturing) { + this.notify.addNotify(NOTIFY_CAPTURING, (event: CapturingNotify) => { + if (event.params?.status) { + onCapturing(event.params.status); + } + }); + } return new Promise(async (resolve, reject) => { await ThetaClientReactNative.startLimitlessIntervalCapture() .then((result?: string[]) => { @@ -46,6 +62,7 @@ export class LimitlessIntervalCapture { }) .finally(() => { this.notify.removeNotify(NOTIFY_NAME); + this.notify.removeNotify(NOTIFY_CAPTURING); }); }); } @@ -62,9 +79,24 @@ export class LimitlessIntervalCapture { * LimitlessIntervalCaptureBuilder class */ export class LimitlessIntervalCaptureBuilder extends CaptureBuilder { + interval?: number; + /** construct LimitlessIntervalCaptureBuilder instance */ constructor() { super(); + this.interval = undefined; + } + + /** + * set interval of checking continuous shooting status command + * @param timeMillis interval + * @returns LimitlessIntervalCaptureBuilder + */ + setCheckStatusCommandInterval( + timeMillis: number + ): LimitlessIntervalCaptureBuilder { + this.interval = timeMillis; + return this; } /** @@ -84,12 +116,15 @@ export class LimitlessIntervalCaptureBuilder extends CaptureBuilder { + let params = { + ...this.options, + // Cannot pass negative values in IOS, use objects + _capture_interval: this.interval ?? -1, + }; return new Promise(async (resolve, reject) => { try { await ThetaClientReactNative.getLimitlessIntervalCaptureBuilder(); - await ThetaClientReactNative.buildLimitlessIntervalCapture( - this.options - ); + await ThetaClientReactNative.buildLimitlessIntervalCapture(params); resolve(new LimitlessIntervalCapture(NotifyController.instance)); } catch (error) { reject(error); diff --git a/react-native/src/capture/multi-bracket-capture.ts b/react-native/src/capture/multi-bracket-capture.ts index 6bb7b6d9ae..b9442ab704 100644 --- a/react-native/src/capture/multi-bracket-capture.ts +++ b/react-native/src/capture/multi-bracket-capture.ts @@ -1,14 +1,15 @@ -import { CaptureBuilder } from './capture'; +import { CaptureBuilder, CapturingStatusEnum } from './capture'; import { NativeModules } from 'react-native'; import { BaseNotify, NotifyController, } from '../theta-repository/notify-controller'; -import type { BracketSetting } from 'src/theta-repository/options'; +import type { BracketSetting } from '../theta-repository/options'; const ThetaClientReactNative = NativeModules.ThetaClientReactNative; const NOTIFY_PROGRESS = 'MULTI-BRACKET-PROGRESS'; const NOTIFY_STOP_ERROR = 'MULTI-BRACKET-STOP-ERROR'; +const NOTIFY_CAPTURING = 'MULTI-BRACKET-CAPTURING'; interface CaptureProgressNotify extends BaseNotify { params?: { @@ -22,6 +23,12 @@ interface CaptureStopErrorNotify extends BaseNotify { }; } +interface CapturingNotify extends BaseNotify { + params?: { + status: CapturingStatusEnum; + }; +} + /** * MultiBracketCapture class */ @@ -34,11 +41,14 @@ export class MultiBracketCapture { /** * start multi bracket shooting * @param onProgress the block for multi bracket shooting onProgress + * @param onStopFailed the block for error of cancelCapture + * @param onCapturing Called when change capture status * @return promise of captured file urls */ async startCapture( onProgress?: (completion?: number) => void, - onStopFailed?: (error: any) => void + onStopFailed?: (error: any) => void, + onCapturing?: (status: CapturingStatusEnum) => void ): Promise { if (onProgress) { this.notify.addNotify(NOTIFY_PROGRESS, (event: CaptureProgressNotify) => { @@ -53,6 +63,13 @@ export class MultiBracketCapture { } ); } + if (onCapturing) { + this.notify.addNotify(NOTIFY_CAPTURING, (event: CapturingNotify) => { + if (event.params?.status) { + onCapturing(event.params.status); + } + }); + } return new Promise(async (resolve, reject) => { await ThetaClientReactNative.startMultiBracketCapture() @@ -65,6 +82,7 @@ export class MultiBracketCapture { .finally(() => { this.notify.removeNotify(NOTIFY_PROGRESS); this.notify.removeNotify(NOTIFY_STOP_ERROR); + this.notify.removeNotify(NOTIFY_CAPTURING); }); }); } diff --git a/react-native/src/capture/photo-capture.ts b/react-native/src/capture/photo-capture.ts index 4a1b3e84fe..9fc38262c9 100644 --- a/react-native/src/capture/photo-capture.ts +++ b/react-native/src/capture/photo-capture.ts @@ -1,22 +1,53 @@ -import { PhotoCaptureBuilderBase } from './capture'; +import { CapturingStatusEnum, PhotoCaptureBuilderBase } from './capture'; import type { FilterEnum, PresetEnum } from '../theta-repository/options'; import { NativeModules } from 'react-native'; +import { + BaseNotify, + NotifyController, +} from '../theta-repository/notify-controller'; const ThetaClientReactNative = NativeModules.ThetaClientReactNative; +const NOTIFY_CAPTURING = 'PHOTO-CAPTURING'; + +interface CapturingNotify extends BaseNotify { + params?: { + status: CapturingStatusEnum; + }; +} + /** * PhotoCapture class */ export class PhotoCapture { + notify: NotifyController; + constructor(notify: NotifyController) { + this.notify = notify; + } + /** + * Take a picture + * + * @param onCapturing Called when change capture status * @return promise of token file url */ - async takePicture(): Promise { + async takePicture( + onCapturing?: (status: CapturingStatusEnum) => void + ): Promise { + if (onCapturing) { + this.notify.addNotify(NOTIFY_CAPTURING, (event: CapturingNotify) => { + if (event.params?.status) { + onCapturing(event.params.status); + } + }); + } try { const fileUrl = await ThetaClientReactNative.takePicture(); return fileUrl ?? undefined; } catch (error) { throw error; + } finally { + this.notify.removeNotify(NOTIFY_CAPTURING); } } } @@ -25,9 +56,22 @@ export class PhotoCapture { * PhotoCaptureBaseBuilder class */ export class PhotoCaptureBuilder extends PhotoCaptureBuilderBase { + interval?: number; + /** construct PhotoCaptureBuilder instance */ constructor() { super(); + this.interval = undefined; + } + + /** + * set interval of checking take picture status command + * @param timeMillis interval + * @returns PhotoCaptureBuilder + */ + setCheckStatusCommandInterval(timeMillis: number): PhotoCaptureBuilder { + this.interval = timeMillis; + return this; } /** @@ -57,11 +101,16 @@ export class PhotoCaptureBuilder extends PhotoCaptureBuilderBase { + let params = { + ...this.options, + // Cannot pass negative values in IOS, use objects + _capture_interval: this.interval ?? -1, + }; return new Promise(async (resolve, reject) => { try { await ThetaClientReactNative.getPhotoCaptureBuilder(); - await ThetaClientReactNative.buildPhotoCapture(this.options); - resolve(new PhotoCapture()); + await ThetaClientReactNative.buildPhotoCapture(params); + resolve(new PhotoCapture(NotifyController.instance)); } catch (error) { reject(error); } diff --git a/react-native/src/capture/shot-count-specified-interval-capture.ts b/react-native/src/capture/shot-count-specified-interval-capture.ts index 5ea209d7c5..4fa05a9224 100644 --- a/react-native/src/capture/shot-count-specified-interval-capture.ts +++ b/react-native/src/capture/shot-count-specified-interval-capture.ts @@ -1,4 +1,4 @@ -import { CaptureBuilder } from './capture'; +import { CaptureBuilder, CapturingStatusEnum } from './capture'; import { NativeModules } from 'react-native'; import { BaseNotify, @@ -8,6 +8,7 @@ const ThetaClientReactNative = NativeModules.ThetaClientReactNative; const NOTIFY_PROGRESS = 'SHOT-COUNT-SPECIFIED-INTERVAL-PROGRESS'; const NOTIFY_STOP_ERROR = 'SHOT-COUNT-SPECIFIED-INTERVAL-STOP-ERROR'; +const NOTIFY_CAPTURING = 'SHOT-COUNT-SPECIFIED-INTERVAL-CAPTURING'; interface CaptureProgressNotify extends BaseNotify { params?: { @@ -21,6 +22,12 @@ interface CaptureStopErrorNotify extends BaseNotify { }; } +interface CapturingNotify extends BaseNotify { + params?: { + status: CapturingStatusEnum; + }; +} + /** * ShotCountSpecifiedIntervalCapture class */ @@ -33,11 +40,13 @@ export class ShotCountSpecifiedIntervalCapture { * start interval shooting with the shot count specified * @param onProgress the block for interval shooting with the shot count specified onProgress * @param onStopFailed the block for error of cancelCapture + * @param onCapturing Called when change capture status * @return promise of captured file url */ async startCapture( onProgress?: (completion?: number) => void, - onStopFailed?: (error: any) => void + onStopFailed?: (error: any) => void, + onCapturing?: (status: CapturingStatusEnum) => void ): Promise { if (onProgress) { this.notify.addNotify(NOTIFY_PROGRESS, (event: CaptureProgressNotify) => { @@ -52,6 +61,13 @@ export class ShotCountSpecifiedIntervalCapture { } ); } + if (onCapturing) { + this.notify.addNotify(NOTIFY_CAPTURING, (event: CapturingNotify) => { + if (event.params?.status) { + onCapturing(event.params.status); + } + }); + } return new Promise(async (resolve, reject) => { await ThetaClientReactNative.startShotCountSpecifiedIntervalCapture() @@ -64,6 +80,7 @@ export class ShotCountSpecifiedIntervalCapture { .finally(() => { this.notify.removeNotify(NOTIFY_PROGRESS); this.notify.removeNotify(NOTIFY_STOP_ERROR); + this.notify.removeNotify(NOTIFY_CAPTURING); }); }); } diff --git a/react-native/src/capture/time-shift-capture.ts b/react-native/src/capture/time-shift-capture.ts index 7db1472890..e71d9798b4 100644 --- a/react-native/src/capture/time-shift-capture.ts +++ b/react-native/src/capture/time-shift-capture.ts @@ -1,4 +1,4 @@ -import { CaptureBuilder } from './capture'; +import { CaptureBuilder, CapturingStatusEnum } from './capture'; import { NativeModules } from 'react-native'; import type { TimeShiftIntervalEnum } from '../theta-repository/options'; import { @@ -9,6 +9,7 @@ const ThetaClientReactNative = NativeModules.ThetaClientReactNative; const NOTIFY_NAME = 'TIME-SHIFT-PROGRESS'; const NOTIFY_STOP_ERROR = 'TIME-SHIFT-STOP-ERROR'; +const NOTIFY_CAPTURING = 'TIME-SHIFT-CAPTURING'; interface CaptureProgressNotify extends BaseNotify { params?: { @@ -22,6 +23,12 @@ interface CaptureStopErrorNotify extends BaseNotify { }; } +interface CapturingNotify extends BaseNotify { + params?: { + status: CapturingStatusEnum; + }; +} + /** * TimeShiftCapture class */ @@ -34,11 +41,13 @@ export class TimeShiftCapture { * start time-shift * @param onProgress the block for time-shift onProgress * @param onStopFailed the block for error of cancelCapture + * @param onCapturing Called when change capture status * @return promise of captured file url */ async startCapture( onProgress?: (completion?: number) => void, - onStopFailed?: (error: any) => void + onStopFailed?: (error: any) => void, + onCapturing?: (status: CapturingStatusEnum) => void ): Promise { if (onProgress) { this.notify.addNotify(NOTIFY_NAME, (event: CaptureProgressNotify) => { @@ -53,6 +62,13 @@ export class TimeShiftCapture { } ); } + if (onCapturing) { + this.notify.addNotify(NOTIFY_CAPTURING, (event: CapturingNotify) => { + if (event.params?.status) { + onCapturing(event.params.status); + } + }); + } return new Promise(async (resolve, reject) => { await ThetaClientReactNative.startTimeShiftCapture() @@ -65,6 +81,7 @@ export class TimeShiftCapture { .finally(() => { this.notify.removeNotify(NOTIFY_NAME); this.notify.removeNotify(NOTIFY_STOP_ERROR); + this.notify.removeNotify(NOTIFY_CAPTURING); }); }); } diff --git a/react-native/src/capture/video-capture.ts b/react-native/src/capture/video-capture.ts index 2de3933270..f0f15e75ca 100644 --- a/react-native/src/capture/video-capture.ts +++ b/react-native/src/capture/video-capture.ts @@ -1,4 +1,4 @@ -import { CaptureBuilder } from './capture'; +import { CaptureBuilder, CapturingStatusEnum } from './capture'; import type { MaxRecordableTimeEnum, VideoFileFormatEnum, @@ -12,6 +12,7 @@ import { const ThetaClientReactNative = NativeModules.ThetaClientReactNative; const NOTIFY_NAME = 'VIDEO-CAPTURE-STOP-ERROR'; +const NOTIFY_CAPTURING = 'VIDEO-CAPTURE-CAPTURING'; interface CaptureStopErrorNotify extends BaseNotify { params?: { @@ -19,6 +20,12 @@ interface CaptureStopErrorNotify extends BaseNotify { }; } +interface CapturingNotify extends BaseNotify { + params?: { + status: CapturingStatusEnum; + }; +} + /** * VideoCapture class */ @@ -31,16 +38,25 @@ export class VideoCapture { /** * start video capture * @param onStopFailed the block for error of stopCapture + * @param onCapturing Called when change capture status * @return promise of captured file url */ startCapture( - onStopFailed?: (error: any) => void + onStopFailed?: (error: any) => void, + onCapturing?: (status: CapturingStatusEnum) => void ): Promise { if (onStopFailed) { this.notify.addNotify(NOTIFY_NAME, (event: CaptureStopErrorNotify) => { onStopFailed(event.params); }); } + if (onCapturing) { + this.notify.addNotify(NOTIFY_CAPTURING, (event: CapturingNotify) => { + if (event.params?.status) { + onCapturing(event.params.status); + } + }); + } return new Promise(async (resolve, reject) => { await ThetaClientReactNative.startVideoCapture() .then((result?: string) => { @@ -51,6 +67,7 @@ export class VideoCapture { }) .finally(() => { this.notify.removeNotify(NOTIFY_NAME); + this.notify.removeNotify(NOTIFY_CAPTURING); }); }); } @@ -67,9 +84,22 @@ export class VideoCapture { * VideoCaptureBuilder class */ export class VideoCaptureBuilder extends CaptureBuilder { + interval?: number; + /** construct VideoCaptureBuilder instance */ constructor() { super(); + this.interval = undefined; + } + + /** + * set interval of checking continuous shooting status command + * @param timeMillis interval + * @returns VideoCaptureBuilder + */ + setCheckStatusCommandInterval(timeMillis: number): VideoCaptureBuilder { + this.interval = timeMillis; + return this; } /** @@ -99,10 +129,15 @@ export class VideoCaptureBuilder extends CaptureBuilder { * @return promise of VideoCapture instance */ build(): Promise { + let params = { + ...this.options, + // Cannot pass negative values in IOS, use objects + _capture_interval: this.interval ?? -1, + }; return new Promise(async (resolve, reject) => { try { await ThetaClientReactNative.getVideoCaptureBuilder(); - await ThetaClientReactNative.buildVideoCapture(this.options); + await ThetaClientReactNative.buildVideoCapture(params); resolve(new VideoCapture(NotifyController.instance)); } catch (error) { reject(error); diff --git a/react-native/src/theta-repository/theta-state/capture-status.ts b/react-native/src/theta-repository/theta-state/capture-status.ts new file mode 100644 index 0000000000..4fa92d8963 --- /dev/null +++ b/react-native/src/theta-repository/theta-state/capture-status.ts @@ -0,0 +1,27 @@ +/** Capture Status constants */ +export const CaptureStatusEnum = { + /** Undefined value */ + UNKNOWN: 'UNKNOWN', + /** Performing continuously shoot */ + SHOOTING: 'SHOOTING', + /** In standby */ + IDLE: 'IDLE', + /** Self-timer is operating */ + SELF_TIMER_COUNTDOWN: 'SELF_TIMER_COUNTDOWN', + /** Performing multi bracket shooting */ + BRACKET_SHOOTING: 'BRACKET_SHOOTING', + /** Converting post file... */ + CONVERTING: 'CONVERTING', + /** Performing timeShift shooting */ + TIME_SHIFT_SHOOTING: 'TIME_SHIFT_SHOOTING', + /** Performing continuous shooting */ + CONTINUOUS_SHOOTING: 'CONTINUOUS_SHOOTING', + /** Waiting for retrospective video... */ + RETROSPECTIVE_IMAGE_RECORDING: 'RETROSPECTIVE_IMAGE_RECORDING', + /** Performing burst shooting */ + BURST_SHOOTING: 'BURST_SHOOTING', +} as const; + +/** type definition of CaptureStatusEnum */ +export type CaptureStatusEnum = + (typeof CaptureStatusEnum)[keyof typeof CaptureStatusEnum]; diff --git a/react-native/src/theta-repository/theta-state/index.ts b/react-native/src/theta-repository/theta-state/index.ts index 51eefdaf70..37d9b616a3 100644 --- a/react-native/src/theta-repository/theta-state/index.ts +++ b/react-native/src/theta-repository/theta-state/index.ts @@ -1,3 +1,4 @@ export * from './camera-error'; +export * from './capture-status'; export * from './state-gps-info'; export * from './theta-state'; diff --git a/react-native/src/theta-repository/theta-state/theta-state.ts b/react-native/src/theta-repository/theta-state/theta-state.ts index ed459627d5..80340ab704 100644 --- a/react-native/src/theta-repository/theta-state/theta-state.ts +++ b/react-native/src/theta-repository/theta-state/theta-state.ts @@ -1,5 +1,6 @@ import type { ShootingFunctionEnum } from '../options/option-function'; import type { CameraErrorEnum } from './camera-error'; +import type { CaptureStatusEnum } from './capture-status'; import type { StateGpsInfo } from './state-gps-info'; /** Battery charging state constants */ @@ -16,30 +17,6 @@ export const ChargingStateEnum = { export type ChargingStateEnum = (typeof ChargingStateEnum)[keyof typeof ChargingStateEnum]; -/** Capture Status constants */ -export const CaptureStatusEnum = { - /** Performing continuously shoot */ - SHOOTING: 'SHOOTING', - /** In standby */ - IDLE: 'IDLE', - /** Self-timer is operating */ - SELF_TIMER_COUNTDOWN: 'SELF_TIMER_COUNTDOWN', - /** Performing multi bracket shooting */ - BRACKET_SHOOTING: 'BRACKET_SHOOTING', - /** Converting post file... */ - CONVERTING: 'CONVERTING', - /** Performing timeShift shooting */ - TIME_SHIFT_SHOOTING: 'TIME_SHIFT_SHOOTING', - /** Performing continuous shooting */ - CONTINUOUS_SHOOTING: 'CONTINUOUS_SHOOTING', - /** Waiting for retrospective video... */ - RETROSPECTIVE_IMAGE_RECORDING: 'RETROSPECTIVE_IMAGE_RECORDING', -} as const; - -/** type definition of CaptureStatusEnum */ -export type CaptureStatusEnum = - (typeof CaptureStatusEnum)[keyof typeof CaptureStatusEnum]; - /** Microphone option constants */ export const MicrophoneOptionEnum = { /** auto */ diff --git a/react-native/theta-client-react-native.podspec b/react-native/theta-client-react-native.podspec index 0844a25c52..888d6cbf21 100644 --- a/react-native/theta-client-react-native.podspec +++ b/react-native/theta-client-react-native.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m,mm,swift}" s.dependency "React-Core" - s.dependency "THETAClient", "1.9.1" + s.dependency "THETAClient", "1.10.0" # Don't install the dependencies when we run `pod install` in the old architecture. if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then diff --git a/react-native/verification-tool/src/App.tsx b/react-native/verification-tool/src/App.tsx index e2b1b4e824..e1a0e48c46 100644 --- a/react-native/verification-tool/src/App.tsx +++ b/react-native/verification-tool/src/App.tsx @@ -23,7 +23,7 @@ import CompositeIntervalCaptureScreen from './screen/composite-interval-capture- import BurstCaptureScreen from './screen/burst-capture-screen/burst-capture-screen'; import ContinuousCaptureScreen from './screen/continuous-capture-screen/continuous-capture-screen'; import MultiBracketCaptureScreen from './screen/multi-bracket-capture-screen/multi-bracket-capture-screen'; -import type { FileInfo } from 'theta-client-react-native'; +import type { FileInfo } from './modules/theta-client'; export type RootStackParamList = { menu: undefined; diff --git a/react-native/verification-tool/src/components/capture/capture-common-options/capture-common-options.tsx b/react-native/verification-tool/src/components/capture/capture-common-options/capture-common-options.tsx index 81c70f6d76..65551ec194 100644 --- a/react-native/verification-tool/src/components/capture/capture-common-options/capture-common-options.tsx +++ b/react-native/verification-tool/src/components/capture/capture-common-options/capture-common-options.tsx @@ -13,7 +13,7 @@ import { IsoAutoHighLimitEnum, IsoEnum, WhiteBalanceEnum, -} from 'theta-client-react-native'; +} from '../../../modules/theta-client'; export const CaptureCommonOptionsEdit: React.FC = ({ onChange, diff --git a/react-native/verification-tool/src/components/list-files-view/list-files-view.tsx b/react-native/verification-tool/src/components/list-files-view/list-files-view.tsx index 614d37cb32..147bb62c40 100644 --- a/react-native/verification-tool/src/components/list-files-view/list-files-view.tsx +++ b/react-native/verification-tool/src/components/list-files-view/list-files-view.tsx @@ -16,7 +16,7 @@ import { StorageEnum, ThetaFiles, listFiles, -} from 'theta-client-react-native'; +} from '../../modules/theta-client'; interface Props extends Pick { style?: StyleProp; diff --git a/react-native/verification-tool/src/components/options/auto-bracket/auto-bracket-edit.tsx b/react-native/verification-tool/src/components/options/auto-bracket/auto-bracket-edit.tsx index 436afc6538..32e5fbe543 100644 --- a/react-native/verification-tool/src/components/options/auto-bracket/auto-bracket-edit.tsx +++ b/react-native/verification-tool/src/components/options/auto-bracket/auto-bracket-edit.tsx @@ -12,7 +12,7 @@ import { IsoEnum, ShutterSpeedEnum, WhiteBalanceEnum, -} from 'theta-client-react-native'; +} from '../../../modules/theta-client'; import { EnumEdit } from '../enum-edit'; const EditItem = ({ diff --git a/react-native/verification-tool/src/components/options/burst-option/burst-option-edit.tsx b/react-native/verification-tool/src/components/options/burst-option/burst-option-edit.tsx index 1cc2293482..098a285795 100644 --- a/react-native/verification-tool/src/components/options/burst-option/burst-option-edit.tsx +++ b/react-native/verification-tool/src/components/options/burst-option/burst-option-edit.tsx @@ -9,7 +9,7 @@ import { BurstEnableIsoControlEnum, BurstMaxExposureTimeEnum, BurstOrderEnum, -} from 'theta-client-react-native'; +} from '../../../modules/theta-client'; export const BurstOptionsEdit: React.FC = ({ onChange, diff --git a/react-native/verification-tool/src/components/options/gps-info/gps-info-edit.tsx b/react-native/verification-tool/src/components/options/gps-info/gps-info-edit.tsx index 90ed7f9eb0..1a23407a58 100644 --- a/react-native/verification-tool/src/components/options/gps-info/gps-info-edit.tsx +++ b/react-native/verification-tool/src/components/options/gps-info/gps-info-edit.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import type { OptionEditProps } from '..'; import { View, Text, Switch } from 'react-native'; -import type { GpsInfo } from 'theta-client-react-native'; +import type { GpsInfo } from '../../../modules/theta-client'; import { InputNumber } from '../../ui/input-number'; import { InputString } from '../../ui/input-string'; import styles from './styles'; diff --git a/react-native/verification-tool/src/components/options/index.ts b/react-native/verification-tool/src/components/options/index.ts index 52091153b9..067b276298 100644 --- a/react-native/verification-tool/src/components/options/index.ts +++ b/react-native/verification-tool/src/components/options/index.ts @@ -1,4 +1,4 @@ -import type { Options } from 'theta-client-react-native'; +import type { Options } from '../../modules/theta-client'; export interface OptionEditProps { onChange: (options: Options) => void; diff --git a/react-native/verification-tool/src/components/options/number-edit/number-edit.tsx b/react-native/verification-tool/src/components/options/number-edit/number-edit.tsx index 74d8b41a7e..61f7354a44 100644 --- a/react-native/verification-tool/src/components/options/number-edit/number-edit.tsx +++ b/react-native/verification-tool/src/components/options/number-edit/number-edit.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import type { OptionEditProps } from '..'; import { InputNumber } from '../../ui/input-number'; -import type { Options } from 'theta-client-react-native'; +import type { Options } from '../../../modules/theta-client'; interface Props extends OptionEditProps { propName: string; diff --git a/react-native/verification-tool/src/components/options/string-edit/string-edit.tsx b/react-native/verification-tool/src/components/options/string-edit/string-edit.tsx index 056f985c8a..684d12d17e 100644 --- a/react-native/verification-tool/src/components/options/string-edit/string-edit.tsx +++ b/react-native/verification-tool/src/components/options/string-edit/string-edit.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import type { OptionEditProps } from '..'; import { InputString } from '../../ui/input-string'; -import type { Options } from 'theta-client-react-native'; +import type { Options } from '../../../modules/theta-client'; interface Props extends OptionEditProps { propName: string; diff --git a/react-native/verification-tool/src/components/options/time-shift/time-shift-edit.tsx b/react-native/verification-tool/src/components/options/time-shift/time-shift-edit.tsx index 3edeebe02f..d5fb401af4 100644 --- a/react-native/verification-tool/src/components/options/time-shift/time-shift-edit.tsx +++ b/react-native/verification-tool/src/components/options/time-shift/time-shift-edit.tsx @@ -1,5 +1,8 @@ import * as React from 'react'; -import { TimeShift, TimeShiftIntervalEnum } from 'theta-client-react-native'; +import { + TimeShift, + TimeShiftIntervalEnum, +} from '../../../modules/theta-client'; import { EnumEdit, type OptionEditProps } from '..'; import { View } from 'react-native'; import { TitledSwitch } from '../../ui/titled-switch'; diff --git a/react-native/verification-tool/src/components/options/top-bottom-correction-rotation/top-bottom-correction-rotation-edit.tsx b/react-native/verification-tool/src/components/options/top-bottom-correction-rotation/top-bottom-correction-rotation-edit.tsx index e7ca07dc99..d91e9c2f70 100644 --- a/react-native/verification-tool/src/components/options/top-bottom-correction-rotation/top-bottom-correction-rotation-edit.tsx +++ b/react-native/verification-tool/src/components/options/top-bottom-correction-rotation/top-bottom-correction-rotation-edit.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import type { OptionEditProps } from '..'; import { View } from 'react-native'; -import type { TopBottomCorrectionRotation } from 'theta-client-react-native'; +import type { TopBottomCorrectionRotation } from '../../../modules/theta-client'; import { InputNumber } from '../../ui/input-number'; export const TopBottomCorrectionRotationEdit: React.FC = ({ diff --git a/react-native/verification-tool/src/modules/theta-client.ts b/react-native/verification-tool/src/modules/theta-client.ts new file mode 100644 index 0000000000..1769d38e03 --- /dev/null +++ b/react-native/verification-tool/src/modules/theta-client.ts @@ -0,0 +1 @@ +export * from 'theta-client-react-native'; diff --git a/react-native/verification-tool/src/screen/burst-capture-screen/burst-capture-screen.tsx b/react-native/verification-tool/src/screen/burst-capture-screen/burst-capture-screen.tsx index c6250d0aca..823f7f20bd 100644 --- a/react-native/verification-tool/src/screen/burst-capture-screen/burst-capture-screen.tsx +++ b/react-native/verification-tool/src/screen/burst-capture-screen/burst-capture-screen.tsx @@ -12,22 +12,26 @@ import { BurstMaxExposureTimeEnum, BurstModeEnum, BurstOrderEnum, + CapturingStatusEnum, Options, getBurstCaptureBuilder, stopSelfTimer, -} from 'theta-client-react-native'; +} from '../../modules/theta-client'; import { CaptureCommonOptionsEdit } from '../../components/capture/capture-common-options'; import { InputNumber } from '../../components/ui/input-number'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { RootStackParamList } from '../../App'; -import { BurstOptionsEdit } from 'verification-tool/src/components/options/burst-option'; +import { BurstOptionsEdit } from '../../components/options/burst-option'; import { EnumEdit } from '../../components/options'; const BurstCaptureScreen: React.FC< NativeStackScreenProps > = ({ navigation }) => { const [interval, setInterval] = React.useState(); - const [message, setMessage] = React.useState('progress = 0'); + const [message, setMessage] = React.useState(''); + const [capturingStatus, setCapturingStatus] = + React.useState(); + const [progress, setProgress] = React.useState(); const [captureOptions, setCaptureOptions] = React.useState(); const [isTaking, setIsTaking] = React.useState(false); const [capture, setCapture] = React.useState(); @@ -107,12 +111,14 @@ const BurstCaptureScreen: React.FC< initCapture(); return; } + setProgress(undefined); + setCapturingStatus(undefined); try { console.log('BurstCapture startCapture'); const urls = await capture.startCapture( (completion) => { if (isTaking) return; - setMessage(`progress = ${completion}`); + setProgress(completion); }, (error) => { if (error instanceof Error) { @@ -120,6 +126,9 @@ const BurstCaptureScreen: React.FC< { text: 'OK' }, ]); } + }, + (status) => { + setCapturingStatus(status); } ); initCapture(); @@ -192,6 +201,10 @@ const BurstCaptureScreen: React.FC< // eslint-disable-next-line react-hooks/exhaustive-deps }, [capture]); + React.useEffect(() => { + setMessage(`progress = ${progress}\ncapturing = ${capturingStatus}`); + }, [capturingStatus, progress]); + return ( = ({ navigation }) => { const [interval, setInterval] = React.useState(); const [shootingTimeSec, setShootingTimeSec] = React.useState(600); - const [message, setMessage] = React.useState('progress = 0'); + const [message, setMessage] = React.useState(''); + const [capturingStatus, setCapturingStatus] = + React.useState(); + const [progress, setProgress] = React.useState(); const [captureOptions, setCaptureOptions] = React.useState(); const [isTaking, setIsTaking] = React.useState(false); const [capture, setCapture] = React.useState(); @@ -88,15 +92,20 @@ const CompositeIntervalCaptureScreen: React.FC< initCapture(); return; } + setProgress(undefined); + setCapturingStatus(undefined); try { console.log('CompositeIntervalCapture startCapture'); const urls = await capture.startCapture( (completion) => { if (isTaking) return; - setMessage(`progress = ${completion}`); + setProgress(completion); }, (error) => { Alert.alert('Cancel error', JSON.stringify(error), [{ text: 'OK' }]); + }, + (status) => { + setCapturingStatus(status); } ); initCapture(); @@ -151,6 +160,10 @@ const CompositeIntervalCaptureScreen: React.FC< // eslint-disable-next-line react-hooks/exhaustive-deps }, [capture]); + React.useEffect(() => { + setMessage(`progress = ${progress}\ncapturing = ${capturingStatus}`); + }, [capturingStatus, progress]); + return ( > = ({ navigation }) => { const [interval, setInterval] = React.useState(); - const [message, setMessage] = React.useState('progress = 0'); + const [message, setMessage] = React.useState(''); + const [capturingStatus, setCapturingStatus] = + React.useState(); + const [progress, setProgress] = React.useState(); const [captureOptions, setCaptureOptions] = React.useState(); const [isTaking, setIsTaking] = React.useState(false); const [capture, setCapture] = React.useState(); @@ -89,16 +93,23 @@ const ContinuousCaptureScreen: React.FC< initCapture(); return; } + setProgress(undefined); + setCapturingStatus(undefined); try { console.log('ContinuousCapture startCapture'); const number = await capture.getContinuousNumber(); setContinuousNumber(number); - const urls = await capture.startCapture((completion) => { - if (isTaking) return; - setMessage(`progress = ${completion}`); - }); + const urls = await capture.startCapture( + (completion) => { + if (isTaking) return; + setProgress(completion); + }, + (status) => { + setCapturingStatus(status); + } + ); initCapture(); if (urls) { Alert.alert(`file ${urls.length} urls : `, urls.join('\n'), [ @@ -143,6 +154,10 @@ const ContinuousCaptureScreen: React.FC< // eslint-disable-next-line react-hooks/exhaustive-deps }, [capture]); + React.useEffect(() => { + setMessage(`progress = ${progress}\ncapturing = ${capturingStatus}`); + }, [capturingStatus, progress]); + return ( > = ({ navigation }) => { + const [interval, setInterval] = React.useState(); + const [message, setMessage] = React.useState(''); + const [capturingStatus, setCapturingStatus] = + React.useState(); const [options, setOptions] = React.useState(); const [isTaking, setIsTaking] = React.useState(false); const [capture, setCapture] = React.useState(); @@ -31,6 +37,9 @@ const LimitlessIntervalCaptureScreen: React.FC< } const builder = getLimitlessIntervalCaptureBuilder(); + if (interval != null) { + builder.setCheckStatusCommandInterval(interval); + } if (options?.captureInterval != null) { builder.setCaptureInterval(options.captureInterval); } @@ -82,12 +91,18 @@ const LimitlessIntervalCaptureScreen: React.FC< initCapture(); return; } + setCapturingStatus(undefined); try { - const urls = await capture.startCapture((error) => { - Alert.alert('stopCapture error', JSON.stringify(error), [ - { text: 'OK' }, - ]); - }); + const urls = await capture.startCapture( + (error) => { + Alert.alert('stopCapture error', JSON.stringify(error), [ + { text: 'OK' }, + ]); + }, + (status) => { + setCapturingStatus(status); + } + ); initCapture(); if (urls) { Alert.alert(`file ${urls.length} urls : `, urls.join('\n'), [ @@ -134,12 +149,17 @@ const LimitlessIntervalCaptureScreen: React.FC< // eslint-disable-next-line react-hooks/exhaustive-deps }, [capture]); + React.useEffect(() => { + setMessage(`capturing = ${capturingStatus}`); + }, [capturingStatus]); + return ( + {message}