From 371844e3134807f98a095c978638ee413737ded6 Mon Sep 17 00:00:00 2001 From: osakila Date: Fri, 12 Jul 2024 09:37:19 +0900 Subject: [PATCH] Improve self-timer in VideoCapture --- .../theta_client_flutter/ConvertUtil.kt | 5 + .../ThetaClientFlutterPlugin.kt | 10 +- flutter/ios/Classes/ConvertUtil.swift | 5 + .../SwiftThetaClientFlutterPlugin.swift | 11 ++- flutter/lib/capture/capture.dart | 13 ++- flutter/lib/capture/capture_builder.dart | 12 ++- .../theta_client_flutter_method_channel.dart | 27 +++++- ...eta_client_flutter_platform_interface.dart | 5 +- .../video_capture_method_channel_test.dart | 64 +++++++++++- flutter/test/capture/video_capture_test.dart | 90 ++++++++++++++--- flutter/test/theta_client_flutter_test.dart | 18 ++-- .../thetaclient/capture/VideoCapture.kt | 97 ++++++++++++------- .../thetaclient/capture/VideoCaptureTest.kt | 52 ++++++++-- .../VideoCapture/state_self_timer.json | 1 + .../thetaclientreactnative/Converter.kt | 7 ++ .../ThetaClientSdkModule.kt | 8 ++ react-native/ios/ConvertUtil.swift | 5 + react-native/ios/ThetaClientReactNative.swift | 11 +++ .../__tests__/capture/video-capture.test.ts | 62 ++++++++++++ react-native/src/capture/video-capture.ts | 41 +++++++- .../burst-capture-screen.tsx | 2 +- .../composite-interval-capture-screen.tsx | 2 +- ...ount-specified-interval-capture-screen.tsx | 2 +- .../screen/video-capture-screen/styles.tsx | 6 ++ .../video-capture-screen.tsx | 42 ++++++-- 25 files changed, 503 insertions(+), 95 deletions(-) create mode 100644 kotlin-multiplatform/src/commonTest/resources/VideoCapture/state_self_timer.json 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 5c3d5a2f78..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 @@ -388,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) 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 8ab54edbba..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 @@ -66,7 +66,6 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { const val notifyIdTimeShiftProgress = 10011 const val notifyIdTimeShiftStopError = 10012 const val notifyIdTimeShiftCapturing = 10013 - const val notifyIdVideoCaptureStopError = 10003 const val notifyIdLimitlessIntervalCaptureStopError = 10004 const val notifyIdLimitlessIntervalCaptureCapturing = 10005 const val notifyIdShotCountSpecifiedIntervalCaptureProgress = 10021 @@ -84,6 +83,8 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { 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) { @@ -811,6 +812,13 @@ class ThetaClientFlutterPlugin : FlutterPlugin, MethodCallHandler { toMessageNotifyParam(exception.message ?: exception.toString()) ) } + + override fun onCapturing(status: CapturingStatusEnum) { + sendNotifyEvent( + notifyIdVideoCaptureCapturing, + toCapturingNotifyParam(status) + ) + } }) } diff --git a/flutter/ios/Classes/ConvertUtil.swift b/flutter/ios/Classes/ConvertUtil.swift index c281a9cbb6..17f678fbe8 100644 --- a/flutter/ios/Classes/ConvertUtil.swift +++ b/flutter/ios/Classes/ConvertUtil.swift @@ -298,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) diff --git a/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift b/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift index 885838b571..d4cf66a3a4 100644 --- a/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift +++ b/flutter/ios/Classes/SwiftThetaClientFlutterPlugin.swift @@ -7,7 +7,6 @@ let NOTIFY_LIVE_PREVIEW = 10001 let NOTIFY_TIME_SHIFT_PROGRESS = 10011 let NOTIFY_TIME_SHIFT_STOP_ERROR = 10012 let NOTIFY_TIME_SHIFT_CAPTURING = 10013 -let NOTIFY_VIDEO_CAPTURE_STOP_ERROR = 10003 let NOTIFY_LIMITLESS_INTERVAL_CAPTURE_STOP_ERROR = 10004 let NOTIFY_LIMITLESS_INTERVAL_CAPTURE_CAPTURING = 10005 let NOTIFY_SHOT_COUNT_SPECIFIED_INTERVAL_CAPTURE_PROGRESS = 10021 @@ -25,6 +24,8 @@ 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? { @@ -83,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)) + } } } @@ -731,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 diff --git a/flutter/lib/capture/capture.dart b/flutter/lib/capture/capture.dart index 457a765d75..2278e5e3ff 100644 --- a/flutter/lib/capture/capture.dart +++ b/flutter/lib/capture/capture.dart @@ -149,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() { @@ -164,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(); diff --git a/flutter/lib/capture/capture_builder.dart b/flutter/lib/capture/capture_builder.dart index 92b684b83e..467c4c47a9 100644 --- a/flutter/lib/capture/capture_builder.dart +++ b/flutter/lib/capture/capture_builder.dart @@ -180,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; @@ -196,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); } diff --git a/flutter/lib/theta_client_flutter_method_channel.dart b/flutter/lib/theta_client_flutter_method_channel.dart index 42e94c2b22..ccd0760e08 100644 --- a/flutter/lib/theta_client_flutter_method_channel.dart +++ b/flutter/lib/theta_client_flutter_method_channel.dart @@ -11,7 +11,6 @@ const notifyIdLivePreview = 10001; const notifyIdTimeShiftProgress = 10011; const notifyIdTimeShiftStopError = 10012; const notifyIdTimeShiftCapturing = 10013; -const notifyIdVideoCaptureStopError = 10003; const notifyIdLimitlessIntervalCaptureStopError = 10004; const notifyIdLimitlessIntervalCaptureCapturing = 10005; const notifyIdShotCountSpecifiedIntervalCaptureProgress = 10021; @@ -29,6 +28,8 @@ 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 { @@ -349,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(); @@ -368,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; diff --git a/flutter/lib/theta_client_flutter_platform_interface.dart b/flutter/lib/theta_client_flutter_platform_interface.dart index 9b1f7d21f2..382f7e7d66 100644 --- a/flutter/lib/theta_client_flutter_platform_interface.dart +++ b/flutter/lib/theta_client_flutter_platform_interface.dart @@ -129,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.'); } 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/theta_client_flutter_test.dart b/flutter/test/theta_client_flutter_test.dart index 98a9b8762e..2221606763 100644 --- a/flutter/test/theta_client_flutter_test.dart +++ b/flutter/test/theta_client_flutter_test.dart @@ -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 @@ -447,10 +448,11 @@ onCallStartTimeShiftCapture = (onProgress, onStopFailed, onCapturing) => 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; 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/commonTest/kotlin/com/ricoh360/thetaclient/capture/VideoCaptureTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/capture/VideoCaptureTest.kt index 841801a771..f4af0ada73 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 @@ -38,34 +39,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 } } } @@ -77,7 +91,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") @@ -98,6 +114,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) { @@ -116,6 +139,7 @@ class VideoCaptureTest { // check result assertTrue(file?.startsWith("http://") ?: false, "start capture video") + assertTrue(stateCounter >= 2, "count onCapturing") } /** @@ -164,7 +188,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") @@ -543,7 +569,9 @@ class VideoCaptureTest { val thetaRepository = ThetaRepository(endpoint) val videoCapture = thetaRepository.getVideoCaptureBuilder() + .setCheckStatusCommandInterval(100) .build() + ThetaApi.lastSetTimeConsumingOptionTime = 0 // execute error response var deferred = CompletableDeferred() @@ -621,7 +649,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() @@ -674,7 +704,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 @@ -724,7 +756,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/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/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt b/react-native/android/src/main/java/com/ricoh360/thetaclientreactnative/Converter.kt index 01a70c01bd..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 @@ -204,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)) } 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 dcee6486f1..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 @@ -742,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()) } @@ -2146,6 +2153,7 @@ class ThetaClientReactNativeModule( 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" diff --git a/react-native/ios/ConvertUtil.swift b/react-native/ios/ConvertUtil.swift index c96973bc63..86b4e532ee 100644 --- a/react-native/ios/ConvertUtil.swift +++ b/react-native/ios/ConvertUtil.swift @@ -576,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 diff --git a/react-native/ios/ThetaClientReactNative.swift b/react-native/ios/ThetaClientReactNative.swift index 643a9181a0..8c09c6769a 100644 --- a/react-native/ios/ThetaClientReactNative.swift +++ b/react-native/ios/ThetaClientReactNative.swift @@ -73,6 +73,7 @@ class ThetaClientReactNative: RCTEventEmitter { 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" @@ -830,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( 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/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/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 3d8a73ed62..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 @@ -21,7 +21,7 @@ import { CaptureCommonOptionsEdit } from '../../components/capture/capture-commo 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< diff --git a/react-native/verification-tool/src/screen/composite-interval-capture-screen/composite-interval-capture-screen.tsx b/react-native/verification-tool/src/screen/composite-interval-capture-screen/composite-interval-capture-screen.tsx index 1d51fa1361..22d490fa00 100644 --- a/react-native/verification-tool/src/screen/composite-interval-capture-screen/composite-interval-capture-screen.tsx +++ b/react-native/verification-tool/src/screen/composite-interval-capture-screen/composite-interval-capture-screen.tsx @@ -12,7 +12,7 @@ import { } from '../../modules/theta-client'; import { CaptureCommonOptionsEdit } from '../../components/capture/capture-common-options'; import { InputNumber } from '../../components/ui/input-number'; -import { NumberEdit } from 'verification-tool/src/components/options/number-edit'; +import { NumberEdit } from '../../components/options/number-edit'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { RootStackParamList } from '../../App'; diff --git a/react-native/verification-tool/src/screen/shot-count-specified-interval-capture-screen/shot-count-specified-interval-capture-screen.tsx b/react-native/verification-tool/src/screen/shot-count-specified-interval-capture-screen/shot-count-specified-interval-capture-screen.tsx index 3c94be206c..ef1b767cfb 100644 --- a/react-native/verification-tool/src/screen/shot-count-specified-interval-capture-screen/shot-count-specified-interval-capture-screen.tsx +++ b/react-native/verification-tool/src/screen/shot-count-specified-interval-capture-screen/shot-count-specified-interval-capture-screen.tsx @@ -12,7 +12,7 @@ import { } from '../../modules/theta-client'; import { CaptureCommonOptionsEdit } from '../../components/capture/capture-common-options'; import { InputNumber } from '../../components/ui/input-number'; -import { NumberEdit } from 'verification-tool/src/components/options/number-edit'; +import { NumberEdit } from '../../components/options/number-edit'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { RootStackParamList } from '../../App'; diff --git a/react-native/verification-tool/src/screen/video-capture-screen/styles.tsx b/react-native/verification-tool/src/screen/video-capture-screen/styles.tsx index 37bd379ae1..90de8fd362 100644 --- a/react-native/verification-tool/src/screen/video-capture-screen/styles.tsx +++ b/react-native/verification-tool/src/screen/video-capture-screen/styles.tsx @@ -22,6 +22,12 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'center', }, + itemText: { + color: 'black', + fontSize: 16, + paddingHorizontal: 10, + paddingVertical: 2, + }, }); export default styles; diff --git a/react-native/verification-tool/src/screen/video-capture-screen/video-capture-screen.tsx b/react-native/verification-tool/src/screen/video-capture-screen/video-capture-screen.tsx index 665bd07d7f..303ba78efd 100644 --- a/react-native/verification-tool/src/screen/video-capture-screen/video-capture-screen.tsx +++ b/react-native/verification-tool/src/screen/video-capture-screen/video-capture-screen.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import { View, Alert, ScrollView } from 'react-native'; +import { View, Alert, ScrollView, Text } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import styles from './styles'; import Button from '../../components/ui/button'; import { CaptureModeEnum, + CapturingStatusEnum, MaxRecordableTimeEnum, Options, VideoCapture, @@ -16,11 +17,16 @@ import { import { CaptureCommonOptionsEdit } from '../../components/capture/capture-common-options'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { RootStackParamList } from '../../App'; -import { EnumEdit } from 'verification-tool/src/components/options/enum-edit'; +import { EnumEdit } from '../../components/options/enum-edit'; +import { InputNumber } from '../../components/ui/input-number'; const VideoCaptureScreen: React.FC< NativeStackScreenProps > = ({ navigation }) => { + const [interval, setInterval] = React.useState(); + const [message, setMessage] = React.useState(''); + const [capturingStatus, setCapturingStatus] = + React.useState(); const [captureOptions, setCaptureOptions] = React.useState(); const [isTaking, setIsTaking] = React.useState(false); const [capture, setCapture] = React.useState(); @@ -31,6 +37,9 @@ const VideoCaptureScreen: React.FC< } const builder = getVideoCaptureBuilder(); + if (interval != null) { + builder.setCheckStatusCommandInterval(interval); + } captureOptions?.maxRecordableTime && builder.setMaxRecordableTime(captureOptions.maxRecordableTime); captureOptions?.fileFormat && @@ -79,13 +88,19 @@ const VideoCaptureScreen: React.FC< initCapture(); return; } + setCapturingStatus(undefined); try { console.log('startVideoCapture startCapture'); - const url = await capture.startCapture((error) => { - Alert.alert('stopCapture error', JSON.stringify(error), [ - { text: 'OK' }, - ]); - }); + const url = await capture.startCapture( + (error) => { + Alert.alert('stopCapture error', JSON.stringify(error), [ + { text: 'OK' }, + ]); + }, + (status) => { + setCapturingStatus(status); + } + ); initCapture(); Alert.alert('video file url : ', url, [{ text: 'OK' }]); } catch (error) { @@ -134,12 +149,17 @@ const VideoCaptureScreen: React.FC< // eslint-disable-next-line react-hooks/exhaustive-deps }, [capture]); + React.useEffect(() => { + setMessage(`capturing = ${capturingStatus}`); + }, [capturingStatus]); + return ( + {message}