From 7ed8955bc5786df3499cd81c1615875d6d984d62 Mon Sep 17 00:00:00 2001 From: ywatanabe-dev <102775353+ywatanabe-dev@users.noreply.github.com> Date: Wed, 17 Jan 2024 09:24:22 +0900 Subject: [PATCH 1/9] Support https for live preview --- .../com/ricoh360/thetaclient/PreviewClient.kt | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt index 996d08c2b1..2144797fb5 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt @@ -15,6 +15,7 @@ import io.ktor.network.sockets.aSocket import io.ktor.network.sockets.openReadChannel import io.ktor.network.sockets.openWriteChannel import io.ktor.network.sockets.tcpNoDelay +import io.ktor.network.tls.tls import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.ByteWriteChannel import io.ktor.utils.io.cancel @@ -26,6 +27,7 @@ import io.ktor.utils.io.discard import io.ktor.utils.io.writeFully import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withTimeout +import kotlin.coroutines.coroutineContext /** * http client interface for preview only @@ -74,7 +76,7 @@ internal class PreviewClientImpl : PreviewClient { /** parse url string */ class URL(url: String) { /** protocol, only http or https */ - private var protocol: String = "http" + var protocol: String = "http" /** host string */ var host: String = "localhost" @@ -89,6 +91,9 @@ internal class PreviewClientImpl : PreviewClient { val match = Regex("(http|https)://([^:/]+)(:[0-9]+)?(/.*$)?").find(url) match?.groups?.get(1)?.value?.let { protocol = it + if (protocol == "https") { + port = 443 + } } match?.groups?.get(2)?.value?.let { host = it @@ -129,9 +134,6 @@ internal class PreviewClientImpl : PreviewClient { /** socket connected to endpoint */ private var socket: ASocket? = null - /** parsed endpoint */ - private var endpoint: URL? = null - /** io selector manager */ private var selector: SelectorManager? = null @@ -171,7 +173,6 @@ internal class PreviewClientImpl : PreviewClient { selector = null input = null output = null - endpoint = null pos = 0 curr = 0 chunked = false @@ -186,18 +187,25 @@ internal class PreviewClientImpl : PreviewClient { /** * connect to [endpoint] */ - private suspend fun connect(endpoint: String): PreviewClientImpl { + private suspend fun connect(url: URL): PreviewClientImpl { reset() - this.endpoint = URL(endpoint) selector = SelectorManager(Dispatchers.Default) val builder = aSocket(selector!!).tcpNoDelay().tcp() val self = this + val context = coroutineContext withTimeout(connectionTimeout) { val socket = builder.connect( - InetSocketAddress(self.endpoint!!.host, self.endpoint!!.port), + InetSocketAddress(url.host, url.port), ) { socketTimeout = Companion.socketTimeout receiveBufferSize = buffer.size + }.let { + when (url.protocol) { + "https" -> it.tls(context) { + serverName = url.host + } + else -> it + } } input = socket.openReadChannel() output = socket.openWriteChannel(autoFlush = true) @@ -451,9 +459,10 @@ internal class PreviewClientImpl : PreviewClient { contentType: String, digest: String? = null, ): PreviewClient { - connect(endpoint) + val url = URL(endpoint) + connect(url) write("$method $path HTTP/1.1\r\n") - write("Host: ${this.endpoint?.host}\r\n") + write("Host: ${url.host}\r\n") write("Connection: close\r\n") val bodies = body.toByteArray() if (bodies.isNotEmpty()) { From f37c901e08a424d7c56c713b30adf50b3d8e79c5 Mon Sep 17 00:00:00 2001 From: osakila Date: Thu, 18 Jan 2024 09:42:50 +0900 Subject: [PATCH 2/9] Add proguard setting to kmm --- demos/demo-flutter/android/app/proguard-rules.pro | 2 -- kotlin-multiplatform/build.gradle.kts | 1 + kotlin-multiplatform/proguard-rules.pro | 3 +++ 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100755 kotlin-multiplatform/proguard-rules.pro diff --git a/demos/demo-flutter/android/app/proguard-rules.pro b/demos/demo-flutter/android/app/proguard-rules.pro index c2ed707461..e69de29bb2 100755 --- a/demos/demo-flutter/android/app/proguard-rules.pro +++ b/demos/demo-flutter/android/app/proguard-rules.pro @@ -1,2 +0,0 @@ - --keep class com.ricoh360.thetaclient.** { *; } diff --git a/kotlin-multiplatform/build.gradle.kts b/kotlin-multiplatform/build.gradle.kts index b98a034f88..60f738589b 100644 --- a/kotlin-multiplatform/build.gradle.kts +++ b/kotlin-multiplatform/build.gradle.kts @@ -109,6 +109,7 @@ android { defaultConfig { minSdk = 26 setProperty("archivesBaseName", "theta-client") + consumerProguardFiles("proguard-rules.pro") } } diff --git a/kotlin-multiplatform/proguard-rules.pro b/kotlin-multiplatform/proguard-rules.pro new file mode 100755 index 0000000000..4637cdaadd --- /dev/null +++ b/kotlin-multiplatform/proguard-rules.pro @@ -0,0 +1,3 @@ + +-keep class com.ricoh360.thetaclient.** { *; } +-keep class kotlinx.serialization.** { *; } From 3e46b4f01bd9a012c9d61cd8808bb1b57b1c595d Mon Sep 17 00:00:00 2001 From: simago Date: Tue, 23 Jan 2024 10:48:00 +0900 Subject: [PATCH 3/9] Update comments of handheld HDR --- flutter/lib/theta_client_flutter.dart | 5 +++-- .../kotlin/com/ricoh360/thetaclient/ThetaRepository.kt | 5 +++-- .../ricoh360/thetaclient/transferred/setOptionsCommand.kt | 5 +++-- react-native/src/theta-repository/options/option-filter.ts | 5 +++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/flutter/lib/theta_client_flutter.dart b/flutter/lib/theta_client_flutter.dart index 53a36c18fc..c18aea924d 100644 --- a/flutter/lib/theta_client_flutter.dart +++ b/flutter/lib/theta_client_flutter.dart @@ -2483,8 +2483,9 @@ enum FilterEnum { /// Image processing filter. Handheld HDR. /// - /// RICOH THETA Z1 firmware v1.20.1 or later and RICOH THETA V firmware v3.10.1 or later. - /// RICOH THETA X is not supported. + /// RICOH THETA X firmware v2.40.0 or later, + /// RICOH THETA Z1 firmware v1.20.1 or later, + /// and RICOH THETA V firmware v3.10.1 or later. hhHdr('HH_HDR'); final String rawValue; 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 051799813d..97c6a93cfd 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt @@ -3571,8 +3571,9 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? /** * Image processing filter. Handheld HDR. * - * RICOH THETA Z1 firmware v1.20.1 or later and RICOH THETA V firmware v3.10.1 or later. - * RICOH THETA X is not supported + * RICOH THETA X firmware v2.40.0 or later, + * RICOH THETA Z1 firmware v1.20.1 or later, + * and RICOH THETA V firmware v3.10.1 or later. */ HH_HDR(ImageFilter.HH_HDR); diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt index 3e6f9a9389..67f607e835 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/setOptionsCommand.kt @@ -2123,8 +2123,9 @@ internal enum class ImageFilter { * used until the previous shooting command is completed * (i.e. state of Commands/Status is “done”). * - * (RICOH THETA Z1 firmware v1.20.1 or later and RICOH THETA V - * firmware v3.10.1 or later. RICOH THETA X is not supported) + * (RICOH THETA X firmware v2.40.0 or later, + * RICOH THETA Z1 firmware v1.20.1 or later, + * and RICOH THETA V firmware v3.10.1 or later) */ @SerialName("Hh hdr") HH_HDR, diff --git a/react-native/src/theta-repository/options/option-filter.ts b/react-native/src/theta-repository/options/option-filter.ts index 138b133bc8..c81f5a51da 100644 --- a/react-native/src/theta-repository/options/option-filter.ts +++ b/react-native/src/theta-repository/options/option-filter.ts @@ -15,8 +15,9 @@ export const FilterEnum = { /** * Handheld HDR. * - * RICOH THETA Z1 firmware v1.20.1 or later and RICOH THETA V firmware v3.10.1 or later. - * RICOH THETA X is not supported. + * RICOH THETA X firmware v2.40.0 or later, + * RICOH THETA Z1 firmware v1.20.1 or later, + * and RICOH THETA V firmware v3.10.1 or later. */ HH_HDR: 'HH_HDR', } as const; From 1d2be313a76e846beb0488917b0fdaca84173017 Mon Sep 17 00:00:00 2001 From: osakila Date: Mon, 11 Mar 2024 18:31:05 +0900 Subject: [PATCH 4/9] Update ktor and krypto --- .../app/src/main/AndroidManifest.xml | 1 + .../thetaClientDemo/ThetaViewModelTest.kt | 2 +- demos/demo-android/build.gradle | 2 +- flutter/android/build.gradle | 12 ++--- kotlin-multiplatform/build.gradle.kts | 6 +-- .../com/ricoh360/thetaclient/ThetaApi.kt | 6 +-- .../kotlin/com/ricoh360/thetaclient/Util.kt | 2 +- .../thetaclient/capture/BurstCaptureTest.kt | 7 +-- .../capture/CompositeIntervalCaptureTest.kt | 7 ++- .../capture/MultiBracketCaptureTest.kt | 47 ++++++++++--------- .../ShotCountSpecifiedIntervalCaptureTest.kt | 7 ++- .../capture/TimeShiftCaptureTest.kt | 7 ++- react-native/android/build.gradle | 2 +- 13 files changed, 54 insertions(+), 54 deletions(-) diff --git a/demos/demo-android/app/src/main/AndroidManifest.xml b/demos/demo-android/app/src/main/AndroidManifest.xml index b0c04a3119..4c79d8a938 100755 --- a/demos/demo-android/app/src/main/AndroidManifest.xml +++ b/demos/demo-android/app/src/main/AndroidManifest.xml @@ -13,6 +13,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.ThetaSdkSampleApp" + android:usesCleartextTraffic="true" tools:targetApi="31"> postCommandApi( endpoint: String, - body: CommandApiRequest, + body: T, ): HttpResponse { return syncExecutor(requestSemaphore, ApiClient.timeout.requestTimeout) { httpClient.post(getApiUrl(endpoint, CommandApi.PATH)) { @@ -836,7 +836,7 @@ internal object ThetaApi { append("Content-Type", "application/json; charset=utf-8") append("Cache-Control", "no-cache") } - setBody(body) + setBody(body) } } } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/Util.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/Util.kt index f480b435f5..87a1f6277d 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/Util.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/Util.kt @@ -1,6 +1,6 @@ package com.ricoh360.thetaclient -import com.soywiz.krypto.md5 +import korlibs.crypto.md5 import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.withTimeout 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 a6daf25c57..9e4cac83eb 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 @@ -7,12 +7,12 @@ import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.* import io.ktor.client.network.sockets.* import io.ktor.http.* +import io.ktor.http.content.TextContent import io.ktor.utils.io.* import kotlinx.coroutines.* import kotlinx.coroutines.test.runTest import kotlin.test.* -@OptIn(ExperimentalCoroutinesApi::class) class BurstCaptureTest { private val burstOption = BurstOption( BurstCaptureNum.BURST_CAPTURE_NUM_1, @@ -134,10 +134,11 @@ class BurstCaptureTest { var isStop = false val deferredStart = CompletableDeferred() MockApiClient.onRequest = { request -> - val path = if (request.body.toString().contains("camera.stopCapture")) { + 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 (request.body.toString().contains("camera.setOptions")) { + } else if (textBody.text.contains("camera.setOptions")) { "src/commonTest/resources/setOptions/set_options_done.json" } else { if (!deferredStart.isCompleted) { 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 bff5931754..e3a5eba101 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 @@ -6,7 +6,6 @@ import com.ricoh360.thetaclient.MockApiClient import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode import io.ktor.client.network.sockets.* -import io.ktor.client.request.* import io.ktor.http.* import io.ktor.http.content.* import io.ktor.utils.io.* @@ -14,7 +13,6 @@ import kotlinx.coroutines.* import kotlinx.coroutines.test.runTest import kotlin.test.* -@OptIn(ExperimentalCoroutinesApi::class) class CompositeIntervalCaptureTest { private val endpoint = "http://192.168.1.1:80/" @@ -115,10 +113,11 @@ class CompositeIntervalCaptureTest { var isStop = false val deferredStart = CompletableDeferred() MockApiClient.onRequest = { request -> - val path = if (request.body.toString().contains("camera.stopCapture")) { + 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 (request.body.toString().contains("camera.setOptions")) { + } else if (textBody.text.contains("camera.setOptions")) { "src/commonTest/resources/setOptions/set_options_done.json" } else { if (!deferredStart.isCompleted) { 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 3da34cd782..3efdc6299e 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 @@ -6,12 +6,12 @@ import com.ricoh360.thetaclient.MockApiClient import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode import io.ktor.http.* +import io.ktor.http.content.TextContent import io.ktor.utils.io.* import kotlinx.coroutines.* import kotlinx.coroutines.test.runTest import kotlin.test.* -@OptIn(ExperimentalCoroutinesApi::class) class MultiBracketCaptureTest { private val endpoint = "http://192.168.1.1:80/" @@ -224,7 +224,6 @@ class MultiBracketCaptureTest { 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_shooting.json").readText(), - Resource("src/commonTest/resources/MultiBracketCapture/state_shooting.json").readText(), Resource("src/commonTest/resources/MultiBracketCapture/state_idle.json").readText(), ) @@ -241,7 +240,7 @@ class MultiBracketCaptureTest { } } - val response = if (index >= 5) responseArray[5] else responseArray[index] + val response = if (index >= responseArray.size) responseArray[responseArray.size - 1] else responseArray[index] ByteReadChannel(response) } val deferred = CompletableDeferred() @@ -258,7 +257,9 @@ class MultiBracketCaptureTest { shutterSpeed = ThetaRepository.ShutterSpeedEnum.SHUTTER_SPEED_ONE_OVER_250, iso = ThetaRepository.IsoEnum.ISO_200, colorTemperature = 6000, - ).build() + ) + .setCheckStatusCommandInterval(100) + .build() var files: List? = null multiBracketCapture.startCapture(object : MultiBracketCapture.StartCaptureCallback { @@ -302,10 +303,11 @@ class MultiBracketCaptureTest { var isStop = false val deferredStart = CompletableDeferred() MockApiClient.onRequest = { request -> - val path = if (request.body.toString().contains("camera.stopCapture")) { + 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 (request.body.toString().contains("camera.setOptions")) { + } else if (textBody.text.contains("camera.setOptions")) { "src/commonTest/resources/setOptions/set_options_done.json" } else { if (!deferredStart.isCompleted) { @@ -450,26 +452,25 @@ class MultiBracketCaptureTest { ).build() var files: List? = listOf() - val capturing = - capture.startCapture(object : MultiBracketCapture.StartCaptureCallback { - override fun onCaptureCompleted(fileUrls: List?) { - files = fileUrls - deferred.complete(Unit) - } + capture.startCapture(object : MultiBracketCapture.StartCaptureCallback { + override fun onCaptureCompleted(fileUrls: List?) { + files = fileUrls + deferred.complete(Unit) + } - override fun onProgress(completion: Float) { - assertEquals(completion, 0f, "onProgress") - } + override fun onProgress(completion: Float) { + assertEquals(completion, 0f, "onProgress") + } - override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue(false, "error start interval shooting with the shot count specified") - deferred.complete(Unit) - } + override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue(false, "error start interval shooting with the shot count specified") + deferred.complete(Unit) + } - override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { - assertTrue(false, "onStopFailed") - } - }) + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { + assertTrue(false, "onStopFailed") + } + }) runBlocking { withTimeout(7000) { 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 64178c66bb..0c95dcb1df 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 @@ -6,7 +6,6 @@ import com.ricoh360.thetaclient.MockApiClient import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode import io.ktor.client.network.sockets.* -import io.ktor.client.request.* import io.ktor.http.* import io.ktor.http.content.* import io.ktor.utils.io.* @@ -14,7 +13,6 @@ import kotlinx.coroutines.* import kotlinx.coroutines.test.runTest import kotlin.test.* -@OptIn(ExperimentalCoroutinesApi::class) class ShotCountSpecifiedIntervalCaptureTest { private val endpoint = "http://192.168.1.1:80/" @@ -240,10 +238,11 @@ class ShotCountSpecifiedIntervalCaptureTest { // setup var isStop = false MockApiClient.onRequest = { request -> - val path = if (request.body.toString().contains("camera.stopCapture")) { + 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 (request.body.toString().contains("camera.setOptions")) { + } else if (textBody.text.contains("camera.setOptions")) { "src/commonTest/resources/setOptions/set_options_done.json" } else { if (isStop) 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 f739c6a8e9..5c5b68a214 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 @@ -9,7 +9,6 @@ import com.ricoh360.thetaclient.transferred.FirstShootingEnum import com.ricoh360.thetaclient.transferred.Preset import com.ricoh360.thetaclient.transferred.TimeShift import io.ktor.client.network.sockets.* -import io.ktor.client.request.* import io.ktor.http.* import io.ktor.http.content.* import io.ktor.utils.io.* @@ -17,7 +16,6 @@ import kotlinx.coroutines.* import kotlinx.coroutines.test.runTest import kotlin.test.* -@OptIn(ExperimentalCoroutinesApi::class) class TimeShiftCaptureTest { private val endpoint = "http://192.168.1.1:80/" @@ -281,10 +279,11 @@ class TimeShiftCaptureTest { // setup var isStop = false MockApiClient.onRequest = { request -> - val path = if (request.body.toString().contains("camera.stopCapture")) { + val textBody = request.body as TextContent + val path = if (textBody.text.contains("camera.stopCapture")) { isStop = true "src/commonTest/resources/TimeShiftCapture/stop_capture_done.json" - } else if (request.body.toString().contains("camera.setOptions")) { + } else if (textBody.text.contains("camera.setOptions")) { "src/commonTest/resources/setOptions/set_options_done.json" } else { if (isStop) diff --git a/react-native/android/build.gradle b/react-native/android/build.gradle index 312883bddf..903db1dff4 100644 --- a/react-native/android/build.gradle +++ b/react-native/android/build.gradle @@ -134,7 +134,7 @@ dependencies { //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" implementation "com.ricoh360.thetaclient:theta-client:1.7.1" // From node_modules From 45753406b2d7a475b17671500f616a4b89231086 Mon Sep 17 00:00:00 2001 From: osakila Date: Thu, 28 Mar 2024 09:27:26 +0900 Subject: [PATCH 5/9] Add wait before shooting --- .../com/ricoh360/thetaclient/Platform.kt | 4 + .../com/ricoh360/thetaclient/Platform.kt | 2 + .../com/ricoh360/thetaclient/ThetaApi.kt | 55 +++- .../ricoh360/thetaclient/ThetaRepository.kt | 1 + .../thetaclient/capture/BurstCaptureTest.kt | 18 +- .../capture/CompositeIntervalCaptureTest.kt | 18 +- .../capture/ContinuousCaptureTest.kt | 8 +- .../capture/LimitlessIntervalCaptureTest.kt | 95 +++++-- .../capture/MultiBracketCaptureTest.kt | 3 + .../ShotCountSpecifiedIntervalCaptureTest.kt | 20 +- .../capture/TimeShiftCaptureTest.kt | 18 +- .../thetaclient/capture/VideoCaptureTest.kt | 93 +++++-- .../thetaclient/repository/GetOptionsTest.kt | 66 ++++- .../thetaclient/repository/SetOptionsTest.kt | 254 ++++++++++++++++++ .../get_options_capture_mode_image.json | 1 + .../get_options_capture_mode_video.json | 1 + .../getOptions/get_options_filter_hdr.json | 1 + .../getOptions/get_options_filter_off.json | 1 + .../com/ricoh360/thetaclient/Platform.kt | 5 + 19 files changed, 577 insertions(+), 87 deletions(-) create mode 100644 kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_capture_mode_image.json create mode 100644 kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_capture_mode_video.json create mode 100644 kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_filter_hdr.json create mode 100644 kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_filter_off.json diff --git a/kotlin-multiplatform/src/androidMain/kotlin/com/ricoh360/thetaclient/Platform.kt b/kotlin-multiplatform/src/androidMain/kotlin/com/ricoh360/thetaclient/Platform.kt index 9c674c409f..367e0c64b1 100644 --- a/kotlin-multiplatform/src/androidMain/kotlin/com/ricoh360/thetaclient/Platform.kt +++ b/kotlin-multiplatform/src/androidMain/kotlin/com/ricoh360/thetaclient/Platform.kt @@ -29,3 +29,7 @@ actual fun frameFrom(packet: Pair): FrameSource { actual fun randomUUID(): String { return UUID.randomUUID().toString() } + +actual fun currentTimeMillis(): Long { + return System.currentTimeMillis() +} diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/Platform.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/Platform.kt index 76a27c72d0..b2015d228a 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/Platform.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/Platform.kt @@ -21,3 +21,5 @@ expect class FrameSource expect fun frameFrom(packet: Pair): FrameSource internal expect fun randomUUID(): String + +internal expect fun currentTimeMillis(): Long diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt index 8623e4d94a..ccd244a6e1 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt @@ -25,6 +25,8 @@ import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json +internal const val ALLOWED_CAPTURE_INTERVAL = 1000 + /** * Http client using [Ktor](https://jp.ktor.work/clients/index.html) */ @@ -39,7 +41,14 @@ internal object ThetaApi { val multipartPostClient: MultipartPostClient // just for updateFirmware protcol get() = getMultipartPostClient() - var requestSemaphore = Semaphore(1) + val requestSemaphore = Semaphore(1) + var lastSetTimeConsumingOptionTime: Long = 0 + var currentOptions = Options() + + fun initOptions() { + currentOptions = Options() + lastSetTimeConsumingOptionTime = 0 + } /** * Call [/osc/info](https://github.com/ricohapi/theta-api-specs/blob/main/theta-web-api-v2.1/protocols/info.md) @@ -269,6 +278,7 @@ internal object ThetaApi { */ @Throws(Throwable::class) fun callGetLivePreviewCommand(endpoint: String): Flow = flow { + waitCaptureStartTime() var retry = 4 // retry count when preview command failed val WAIT = 500L // time between retry (ms) while (retry-- > 0) { @@ -321,6 +331,7 @@ internal object ThetaApi { endpoint: String, frameHandler: suspend (Pair) -> Boolean, ) { + waitCaptureStartTime() var retry = 4 // retry count when preview command failed val WAIT = 500L // time between retry (ms) while (retry-- > 0) { @@ -457,6 +468,7 @@ internal object ThetaApi { endpoint: String, params: StartCaptureParams, ): StartCaptureResponse { + waitCaptureStartTime() val request = StartCaptureRequest(parameters = params) return postCommandApi(endpoint, request).body() } @@ -495,6 +507,7 @@ internal object ThetaApi { suspend fun callTakePictureCommand( endpoint: String, ): TakePictureResponse { + waitCaptureStartTime() val request = TakePictureRequest() return postCommandApi(endpoint, request).body() } @@ -562,6 +575,34 @@ internal object ThetaApi { return postCommandApi(endpoint, request).body() } + fun updateConsumingOptions(options: Options, isUpdateTime: Boolean = true) { + var isUpdateOptions = false + options._filter?.let { + if (currentOptions._filter != it) { + currentOptions._filter = it + isUpdateOptions = true + } + } + options.captureMode?.let { + if (currentOptions.captureMode != it) { + currentOptions.captureMode = it + isUpdateOptions = true + } + } + if (isUpdateTime && isUpdateOptions) { + lastSetTimeConsumingOptionTime = currentTimeMillis() + } + } + + suspend fun waitCaptureStartTime() { + val interval = currentTimeMillis() - lastSetTimeConsumingOptionTime + if (interval < ALLOWED_CAPTURE_INTERVAL) { + println("waitCaptureStartTime wait: ${ALLOWED_CAPTURE_INTERVAL - interval}") + delay(ALLOWED_CAPTURE_INTERVAL - interval) + } + lastSetTimeConsumingOptionTime = 0 + } + /** * Call [camera.setOptions](https://github.com/ricohapi/theta-api-specs/blob/main/theta-web-api-v2.1/commands/camera.set_options.md) * @param endpoint Endpoint of Theta web API @@ -581,7 +622,11 @@ internal object ThetaApi { params: SetOptionsParams, ): SetOptionsResponse { val request = SetOptionsRequest(parameters = params) - return postCommandApi(endpoint, request).body() + val response: SetOptionsResponse = postCommandApi(endpoint, request).body() + if (response.state == CommandState.DONE) { + updateConsumingOptions(params.options) + } + return response } /** @@ -603,7 +648,11 @@ internal object ThetaApi { params: GetOptionsParams, ): GetOptionsResponse { val request = GetOptionsRequest(parameters = params) - return postCommandApi(endpoint, request).body() + val response: GetOptionsResponse = postCommandApi(endpoint, request).body() + response.results?.options?.let { + updateConsumingOptions(it, false) + } + return response } /** 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 97c6a93cfd..1bb50667c6 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt @@ -136,6 +136,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? timeout?.let { ApiClient.timeout = it } ?: run { ApiClient.timeout = Timeout() } config?.clientMode?.let { ApiClient.digestAuth = it } ?: run { ApiClient.digestAuth = null } initConfig = config + ThetaApi.initOptions() } /** 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 9e4cac83eb..4a195ac4d4 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 @@ -192,7 +192,7 @@ class BurstCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferredStart.await() } } @@ -535,7 +535,7 @@ class BurstCaptureTest { }) runBlocking { - withTimeout(500) { + withTimeout(5000) { deferred.await() } } @@ -633,7 +633,7 @@ class BurstCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -660,7 +660,7 @@ class BurstCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -690,7 +690,7 @@ class BurstCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -746,7 +746,7 @@ class BurstCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -781,7 +781,7 @@ class BurstCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -838,7 +838,7 @@ class BurstCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -871,7 +871,7 @@ class BurstCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } 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 e3a5eba101..aec253bf04 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 @@ -164,7 +164,7 @@ class CompositeIntervalCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferredStart.await() } } @@ -457,7 +457,7 @@ class CompositeIntervalCaptureTest { }) runBlocking { - withTimeout(500) { + withTimeout(5000) { deferred.await() } } @@ -548,7 +548,7 @@ class CompositeIntervalCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -575,7 +575,7 @@ class CompositeIntervalCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -605,7 +605,7 @@ class CompositeIntervalCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -661,7 +661,7 @@ class CompositeIntervalCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -696,7 +696,7 @@ class CompositeIntervalCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -753,7 +753,7 @@ class CompositeIntervalCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -786,7 +786,7 @@ class CompositeIntervalCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } 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 09a2967f70..ebe1b8d63a 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 @@ -301,7 +301,7 @@ class ContinuousCaptureTest { }) runBlocking { - withTimeout(500) { + withTimeout(5000) { deferred.await() } } @@ -383,7 +383,7 @@ class ContinuousCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -406,7 +406,7 @@ class ContinuousCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -432,7 +432,7 @@ class ContinuousCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } 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 4d85620e7c..d41b11770e 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 @@ -81,7 +81,8 @@ class LimitlessIntervalCaptureTest { // execute val thetaRepository = ThetaRepository(endpoint) - val capture = thetaRepository.getLimitlessIntervalCaptureBuilder().build() + val capture = thetaRepository.getLimitlessIntervalCaptureBuilder() + .build() assertNull(capture.getCaptureInterval(), "set option captureInterval") @@ -111,10 +112,13 @@ class LimitlessIntervalCaptureTest { capturing.stopCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } + runBlocking { + delay(2000) + } // check result assertTrue(files?.firstOrNull()?.startsWith("http://") ?: false, "start capture limitless interval") @@ -401,7 +405,7 @@ class LimitlessIntervalCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -426,7 +430,7 @@ class LimitlessIntervalCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -442,8 +446,6 @@ class LimitlessIntervalCaptureTest { Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), Resource("src/commonTest/resources/LimitlessIntervalCapture/start_capture_error.json").readText(), // startCapture error - "Status error UnitTest", // status error not json - "timeout UnitTest" // timeout ) var counter = 0 MockApiClient.onRequest = { _ -> @@ -452,18 +454,17 @@ class LimitlessIntervalCaptureTest { 0 -> MockApiClient.status = HttpStatusCode.OK 1 -> MockApiClient.status = HttpStatusCode.OK 2 -> MockApiClient.status = HttpStatusCode.ServiceUnavailable - 3 -> MockApiClient.status = HttpStatusCode.ServiceUnavailable - 4 -> throw ConnectTimeoutException("timeout") } ByteReadChannel(responseArray[index]) } val thetaRepository = ThetaRepository(endpoint) val capture = thetaRepository.getLimitlessIntervalCaptureBuilder() + .setCaptureInterval(100) .build() // execute status error and json response - var deferred = CompletableDeferred() + val deferred = CompletableDeferred() capture.startCapture(object : LimitlessIntervalCapture.StartCaptureCallback { override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { assertTrue(false, "capture limitless interval") @@ -485,13 +486,41 @@ class LimitlessIntervalCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } + } + + /** + * Error exception to startCapture call + */ + @Test + fun startCaptureJsonExceptionTest() = 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(), + "Status error UnitTest", // status error not json + ) + var counter = 0 + MockApiClient.onRequest = { _ -> + val index = counter++ + when (index) { + 0 -> MockApiClient.status = HttpStatusCode.OK + 1 -> MockApiClient.status = HttpStatusCode.OK + 2 -> MockApiClient.status = HttpStatusCode.ServiceUnavailable + } + ByteReadChannel(responseArray[index]) + } + + val thetaRepository = ThetaRepository(endpoint) + val capture = thetaRepository.getLimitlessIntervalCaptureBuilder() + .setCaptureInterval(100) + .build() // execute status error and not json response - deferred = CompletableDeferred() + val deferred = CompletableDeferred() capture.startCapture(object : LimitlessIntervalCapture.StartCaptureCallback { override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { assertTrue(false, "capture limitless interval") @@ -510,13 +539,41 @@ class LimitlessIntervalCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(10000) { deferred.await() } } + } + + /** + * Error exception to startCapture call + */ + @Test + fun startCaptureTimeoutExceptionTest() = 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(), + "timeout UnitTest" // timeout + ) + var counter = 0 + MockApiClient.onRequest = { _ -> + val index = counter++ + when (index) { + 0 -> MockApiClient.status = HttpStatusCode.OK + 1 -> MockApiClient.status = HttpStatusCode.OK + 2 -> throw ConnectTimeoutException("timeout") + } + ByteReadChannel(responseArray[index]) + } + + val thetaRepository = ThetaRepository(endpoint) + val capture = thetaRepository.getLimitlessIntervalCaptureBuilder() + .setCaptureInterval(100) + .build() // execute timeout exception - deferred = CompletableDeferred() + val deferred = CompletableDeferred() capture.startCapture(object : LimitlessIntervalCapture.StartCaptureCallback { override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { assertTrue(false, "capture limitless interval") @@ -535,7 +592,7 @@ class LimitlessIntervalCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -584,7 +641,7 @@ class LimitlessIntervalCaptureTest { capturing.stopCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -613,7 +670,7 @@ class LimitlessIntervalCaptureTest { capturing.stopCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -668,7 +725,7 @@ class LimitlessIntervalCaptureTest { capturing.stopCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -697,7 +754,7 @@ class LimitlessIntervalCaptureTest { capturing.stopCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -729,7 +786,7 @@ class LimitlessIntervalCaptureTest { capturing.stopCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } 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 3efdc6299e..7f235469be 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 @@ -72,6 +72,7 @@ class MultiBracketCaptureTest { val thetaRepository = ThetaRepository(endpoint) thetaRepository.cameraModel = ThetaRepository.ThetaModel.THETA_X val multiBracketCapture = thetaRepository.getMultiBracketCaptureBuilder() + .setCheckStatusCommandInterval(100) .addBracketParameters( colorTemperature = 5000, exposureCompensation = ThetaRepository.ExposureCompensationEnum.M0_3, @@ -163,6 +164,7 @@ class MultiBracketCaptureTest { val thetaRepository = ThetaRepository(endpoint) thetaRepository.cameraModel = ThetaRepository.ThetaModel.THETA_V val multiBracketCapture = thetaRepository.getMultiBracketCaptureBuilder() + .setCheckStatusCommandInterval(100) .addBracketSettingList( listOf( ThetaRepository.BracketSetting( @@ -328,6 +330,7 @@ class MultiBracketCaptureTest { val thetaRepository = ThetaRepository(endpoint) thetaRepository.cameraModel = ThetaRepository.ThetaModel.THETA_X val capture = thetaRepository.getMultiBracketCaptureBuilder() + .setCheckStatusCommandInterval(100) .addBracketParameters( colorTemperature = 5000, exposureCompensation = ThetaRepository.ExposureCompensationEnum.M0_3, 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 0c95dcb1df..6c546d3866 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 @@ -290,7 +290,7 @@ class ShotCountSpecifiedIntervalCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferredStart.await() } } @@ -639,7 +639,7 @@ class ShotCountSpecifiedIntervalCaptureTest { }) runBlocking { - withTimeout(500) { + withTimeout(5000) { deferred.await() } } @@ -669,7 +669,7 @@ class ShotCountSpecifiedIntervalCaptureTest { }) runBlocking { - withTimeout(2000) { + withTimeout(5000) { deferred.await() } } @@ -730,7 +730,7 @@ class ShotCountSpecifiedIntervalCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -757,7 +757,7 @@ class ShotCountSpecifiedIntervalCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -787,7 +787,7 @@ class ShotCountSpecifiedIntervalCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -841,7 +841,7 @@ class ShotCountSpecifiedIntervalCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -874,7 +874,7 @@ class ShotCountSpecifiedIntervalCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -931,7 +931,7 @@ class ShotCountSpecifiedIntervalCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -964,7 +964,7 @@ class ShotCountSpecifiedIntervalCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } 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 5c5b68a214..a74f826c45 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 @@ -331,7 +331,7 @@ class TimeShiftCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferredStart.await() } } @@ -714,7 +714,7 @@ class TimeShiftCaptureTest { }) runBlocking { - withTimeout(500) { + withTimeout(5000) { deferred.await() } } @@ -799,7 +799,7 @@ class TimeShiftCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -827,7 +827,7 @@ class TimeShiftCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -855,7 +855,7 @@ class TimeShiftCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -914,7 +914,7 @@ class TimeShiftCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferredStart.await() } } @@ -977,7 +977,7 @@ class TimeShiftCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -1006,7 +1006,7 @@ class TimeShiftCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -1035,7 +1035,7 @@ class TimeShiftCaptureTest { capturing.cancelCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } 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 413e4f89fe..6887e80995 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 @@ -99,17 +99,19 @@ class VideoCaptureTest { } }) runBlocking { - withTimeout(5000) { + withTimeout(10000) { deferredStart.await() } } capturing.stopCapture() - runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } + runBlocking { + delay(2000) + } // check result assertTrue(file?.startsWith("http://") ?: false, "start capture video") @@ -256,7 +258,7 @@ class VideoCaptureTest { @Test fun settingMaxRecordableTimeTest() = runTest { // setup - val valueList = ThetaRepository.MaxRecordableTimeEnum.values() + val valueList = ThetaRepository.MaxRecordableTimeEnum.entries.toTypedArray() val responseArray = arrayOf( Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), @@ -310,7 +312,7 @@ class VideoCaptureTest { @Test fun settingFileFormatTest() = runTest { // setup - val valueList = ThetaRepository.VideoFileFormatEnum.values() + val valueList = ThetaRepository.VideoFileFormatEnum.entries.toTypedArray() val responseArray = arrayOf( Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), @@ -529,7 +531,7 @@ class VideoCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -554,7 +556,7 @@ class VideoCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -569,8 +571,6 @@ class VideoCaptureTest { val responseArray = arrayOf( Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), Resource("src/commonTest/resources/VideoCapture/start_capture_error.json").readText(), // startCapture error - "Status error UnitTest", // status error not json - "timeout UnitTest" // timeout ) var counter = 0 MockApiClient.onRequest = { _ -> @@ -578,8 +578,6 @@ class VideoCaptureTest { when (index) { 0 -> MockApiClient.status = HttpStatusCode.OK 1 -> MockApiClient.status = HttpStatusCode.ServiceUnavailable - 2 -> MockApiClient.status = HttpStatusCode.ServiceUnavailable - 3 -> throw ConnectTimeoutException("timeout") } ByteReadChannel(responseArray[index]) } @@ -589,7 +587,7 @@ class VideoCaptureTest { .build() // execute status error and json response - var deferred = CompletableDeferred() + val deferred = CompletableDeferred() videoCapture.startCapture(object : VideoCapture.StartCaptureCallback { override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { assertTrue(false, "capture video") @@ -611,13 +609,38 @@ class VideoCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(10000) { deferred.await() } } + } + + /** + * Error exception to startCapture call + */ + @Test + fun startCaptureNotJsonExceptionTest() = runTest { + // setup + val responseArray = arrayOf( + Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), + "Status error UnitTest", // status error not json + ) + var counter = 0 + MockApiClient.onRequest = { _ -> + val index = counter++ + when (index) { + 0 -> MockApiClient.status = HttpStatusCode.OK + 1 -> MockApiClient.status = HttpStatusCode.ServiceUnavailable + } + ByteReadChannel(responseArray[index]) + } + + val thetaRepository = ThetaRepository(endpoint) + val videoCapture = thetaRepository.getVideoCaptureBuilder() + .build() + val deferred = CompletableDeferred() // execute status error and not json response - deferred = CompletableDeferred() videoCapture.startCapture(object : VideoCapture.StartCaptureCallback { override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { assertTrue(false, "capture video") @@ -636,13 +659,39 @@ class VideoCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(10000) { deferred.await() } } + } + + /** + * Error exception to startCapture call + */ + @Test + fun startCaptureTimeoutExceptionTest() = runTest { + // setup + val responseArray = arrayOf( + Resource("src/commonTest/resources/setOptions/set_options_done.json").readText(), + "timeout UnitTest" // timeout + ) + var counter = 0 + MockApiClient.onRequest = { _ -> + val index = counter++ + when (index) { + 0 -> MockApiClient.status = HttpStatusCode.OK + 1 -> throw ConnectTimeoutException("timeout") + } + ByteReadChannel(responseArray[index]) + } + + val thetaRepository = ThetaRepository(endpoint) + val videoCapture = thetaRepository.getVideoCaptureBuilder() + .build() + + val deferred = CompletableDeferred() // execute timeout exception - deferred = CompletableDeferred() videoCapture.startCapture(object : VideoCapture.StartCaptureCallback { override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { assertTrue(false, "capture video") @@ -661,7 +710,7 @@ class VideoCaptureTest { }) runBlocking { - withTimeout(1000) { + withTimeout(10000) { deferred.await() } } @@ -710,7 +759,7 @@ class VideoCaptureTest { videoCapturing.stopCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -739,7 +788,7 @@ class VideoCaptureTest { videoCapturing.stopCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -794,7 +843,7 @@ class VideoCaptureTest { videoCapturing.stopCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -823,7 +872,7 @@ class VideoCaptureTest { videoCapturing.stopCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } @@ -855,7 +904,7 @@ class VideoCaptureTest { videoCapturing.stopCapture() runBlocking { - withTimeout(1000) { + withTimeout(5000) { deferred.await() } } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetOptionsTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetOptionsTest.kt index b05e49989a..0c5f567498 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetOptionsTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetOptionsTest.kt @@ -2,8 +2,11 @@ package com.ricoh360.thetaclient.repository import com.goncalossilva.resources.Resource 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.GetOptionsRequest +import com.ricoh360.thetaclient.transferred.ImageFilter import io.ktor.client.network.sockets.ConnectTimeoutException import io.ktor.client.request.HttpRequestData import io.ktor.http.HttpStatusCode @@ -77,7 +80,7 @@ class GetOptionsTest { optionNames.forEach { assertNotNull(options.getValue(it), "option ${it.value}") } - ThetaRepository.OptionNameEnum.values().forEach { + ThetaRepository.OptionNameEnum.entries.forEach { if (!optionNames.contains(it)) { assertNull(options.getValue(it), "option ${it.value}") } @@ -114,7 +117,7 @@ class GetOptionsTest { optionNames.forEach { assertNotNull(options.getValue(it), "option ${it.value}") } - ThetaRepository.OptionNameEnum.values().forEach { + ThetaRepository.OptionNameEnum.entries.forEach { if (!optionNames.contains(it)) { assertNull(options.getValue(it), "option ${it.value}") } @@ -236,4 +239,63 @@ class GetOptionsTest { assertTrue(e.message!!.indexOf("time", 0, true) >= 0, "timeout exception") } } + + /** + * Get consuming option + */ + @Test + fun getConsumingOptionTest() = runTest { + val responseArray = arrayOf( + Resource("src/commonTest/resources/getOptions/get_options_filter_hdr.json").readText(), + Resource("src/commonTest/resources/getOptions/get_options_filter_off.json").readText(), + Resource("src/commonTest/resources/getOptions/get_options_capture_mode_image.json").readText(), + Resource("src/commonTest/resources/getOptions/get_options_capture_mode_video.json").readText(), + ) + var counter = 0 + MockApiClient.onRequest = { _ -> + val index = counter++ + ByteReadChannel(responseArray[index]) + } + + val thetaRepository = ThetaRepository(endpoint) + assertNull(ThetaApi.currentOptions._filter, "_filter") + thetaRepository.getOptions(listOf(ThetaRepository.OptionNameEnum.Filter)) + assertEquals(ThetaApi.currentOptions._filter, ImageFilter.HDR, "_filter") + thetaRepository.getOptions(listOf(ThetaRepository.OptionNameEnum.Filter)) + assertEquals(ThetaApi.currentOptions._filter, ImageFilter.OFF, "_filter") + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "_filter") + + assertNull(ThetaApi.currentOptions.captureMode, "captureMode") + thetaRepository.getOptions(listOf(ThetaRepository.OptionNameEnum.CameraMode)) + assertEquals(ThetaApi.currentOptions.captureMode, CaptureMode.IMAGE, "captureMode") + thetaRepository.getOptions(listOf(ThetaRepository.OptionNameEnum.CameraMode)) + assertEquals(ThetaApi.currentOptions.captureMode, CaptureMode.VIDEO, "captureMode") + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "captureMode") + } + + /** + * Get consuming option exception + */ + @Test + fun getConsumingOptionExceptionTest() = runTest { + MockApiClient.onRequest = { _ -> + MockApiClient.status = HttpStatusCode.ServiceUnavailable + ByteReadChannel(Resource("src/commonTest/resources/getOptions/get_options_error.json").readText()) + } + + val thetaRepository = ThetaRepository(endpoint) + try { + thetaRepository.getOptions(listOf(ThetaRepository.OptionNameEnum.Filter)) + assertTrue(false, "response is normal.") + } catch (_: ThetaRepository.ThetaWebApiException) { } + assertNull(ThetaApi.currentOptions._filter, "_filter") + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "_filter") + + try { + thetaRepository.getOptions(listOf(ThetaRepository.OptionNameEnum.CaptureMode)) + assertTrue(false, "response is normal.") + } catch (_: ThetaRepository.ThetaWebApiException) { } + assertNull(ThetaApi.currentOptions.captureMode, "captureMode") + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "captureMode") + } } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/SetOptionsTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/SetOptionsTest.kt index 699e128e03..be153a43b3 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/SetOptionsTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/SetOptionsTest.kt @@ -1,8 +1,16 @@ package com.ricoh360.thetaclient.repository import com.goncalossilva.resources.Resource +import com.ricoh360.thetaclient.ALLOWED_CAPTURE_INTERVAL import com.ricoh360.thetaclient.MockApiClient +import com.ricoh360.thetaclient.ThetaApi import com.ricoh360.thetaclient.ThetaRepository +import com.ricoh360.thetaclient.capture.PhotoCapture +import com.ricoh360.thetaclient.capture.ShotCountSpecifiedIntervalCapture +import com.ricoh360.thetaclient.currentTimeMillis +import com.ricoh360.thetaclient.transferred.CaptureMode +import com.ricoh360.thetaclient.transferred.CommandApiRequest +import com.ricoh360.thetaclient.transferred.ImageFilter import com.ricoh360.thetaclient.transferred.Options import com.ricoh360.thetaclient.transferred.SetOptionsRequest import io.ktor.client.network.sockets.ConnectTimeoutException @@ -10,11 +18,17 @@ import io.ktor.client.request.HttpRequestData import io.ktor.http.HttpStatusCode import io.ktor.http.content.TextContent import io.ktor.utils.io.ByteReadChannel +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancel +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withTimeout import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -28,11 +42,17 @@ class SetOptionsTest { @BeforeTest fun setup() { MockApiClient.status = HttpStatusCode.OK + ThetaApi.lastSetTimeConsumingOptionTime = 0 + MockApiClient.onPreviewRequest = null + MockApiClient.onPreviewHasNextPart = null } @AfterTest fun teardown() { MockApiClient.status = HttpStatusCode.OK + ThetaApi.lastSetTimeConsumingOptionTime = 0 + MockApiClient.onPreviewRequest = null + MockApiClient.onPreviewHasNextPart = null } private fun checkRequest(request: HttpRequestData, options: Options) { @@ -170,4 +190,238 @@ class SetOptionsTest { assertTrue(e.message!!.indexOf("time", 0, true) >= 0, "timeout exception") } } + + private fun getRequestCommand(request: HttpRequestData): String { + val body = request.body as TextContent + 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. + } + + @Serializable + data class CommandApiRequestAny( + override val name: String, + override val parameters: JsonObject, + ) : CommandApiRequest + + val setOptionsRequest = js.decodeFromString(body.text) + return setOptionsRequest.name + } + + /** + * Test waiting to TakePicture filter settings + */ + @Test + fun waitTakePictureTest() = runTest { + var startTime: Long = 0 + MockApiClient.onRequest = { request -> + val command = getRequestCommand(request) + if (command == "camera.takePicture") { + val interval = currentTimeMillis() - startTime + assertTrue(interval >= ALLOWED_CAPTURE_INTERVAL, "interval: $interval") + } + ByteReadChannel(Resource("src/commonTest/resources/setOptions/set_options_done.json").readText()) + } + + val thetaRepository = ThetaRepository(endpoint) + val options = ThetaRepository.Options(filter = ThetaRepository.FilterEnum.HDR) + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0) + thetaRepository.setOptions(options) + assertTrue(ThetaApi.lastSetTimeConsumingOptionTime > 0) + startTime = ThetaApi.lastSetTimeConsumingOptionTime + val photoCapture = thetaRepository.getPhotoCaptureBuilder() + .build() + + // execute + val deferred = CompletableDeferred() + photoCapture.takePicture(object : PhotoCapture.TakePictureCallback { + override fun onSuccess(fileUrl: String?) { + println("onSuccess") + deferred.complete(Unit) + } + + override fun onError(exception: ThetaRepository.ThetaRepositoryException) { + println("onError") + deferred.complete(Unit) + } + }) + + runBlocking { + withTimeout(5000) { + deferred.await() + } + } + } + + /** + * Test waiting to StartCapture filter settings + */ + @Test + fun waitStartCaptureTest() = runTest { + var startTime: Long = 0 + MockApiClient.onRequest = { request -> + val command = getRequestCommand(request) + when (command) { + "camera.startCapture" -> { + val interval = currentTimeMillis() - startTime + assertTrue(interval >= ALLOWED_CAPTURE_INTERVAL, "interval: $interval") + ByteReadChannel(Resource("src/commonTest/resources/ShotCountSpecifiedIntervalCapture/start_capture_cancel.json").readText()) + } + + else -> { + ByteReadChannel(Resource("src/commonTest/resources/setOptions/set_options_done.json").readText()) + } + + } + } + + val thetaRepository = ThetaRepository(endpoint) + val options = ThetaRepository.Options(filter = ThetaRepository.FilterEnum.HDR) + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0) + thetaRepository.setOptions(options) + assertTrue(ThetaApi.lastSetTimeConsumingOptionTime > 0) + startTime = ThetaApi.lastSetTimeConsumingOptionTime + val capture = thetaRepository.getShotCountSpecifiedIntervalCaptureBuilder(2) + .setCheckStatusCommandInterval(100) + .build() + + // execute + val deferred = CompletableDeferred() + capture.startCapture(object : ShotCountSpecifiedIntervalCapture.StartCaptureCallback { + override fun onCaptureCompleted(fileUrls: List?) { + deferred.complete(Unit) + } + + override fun onProgress(completion: Float) { + } + + override fun onCaptureFailed(exception: ThetaRepository.ThetaRepositoryException) { + deferred.complete(Unit) + } + + override fun onStopFailed(exception: ThetaRepository.ThetaRepositoryException) { + deferred.complete(Unit) + } + }) + + runBlocking { + withTimeout(5000) { + deferred.await() + } + } + } + + /** + * Test waiting to LivePreview filter settings + */ + @Test + fun waitLivePreviewTest() = runBlocking { + MockApiClient.onRequest = { _ -> + ByteReadChannel(Resource("src/commonTest/resources/setOptions/set_options_done.json").readText()) + } + + val thetaRepository = ThetaRepository(endpoint) + val options = ThetaRepository.Options(filter = ThetaRepository.FilterEnum.HDR) + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0) + thetaRepository.setOptions(options) + assertTrue(ThetaApi.lastSetTimeConsumingOptionTime > 0) + val startTime = ThetaApi.lastSetTimeConsumingOptionTime + thetaRepository.getLivePreview { + val interval = currentTimeMillis() - startTime + assertTrue(interval >= ALLOWED_CAPTURE_INTERVAL, "interval: $interval") + return@getLivePreview false + } + } + + /** + * Test waiting to LivePreviewFlow filter settings + */ + @Test + fun waitLivePreviewFlowTest() = runTest { + MockApiClient.onRequest = { _ -> + ByteReadChannel(Resource("src/commonTest/resources/setOptions/set_options_done.json").readText()) + } + + val thetaRepository = ThetaRepository(endpoint) + val options = ThetaRepository.Options(filter = ThetaRepository.FilterEnum.HDR) + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0) + thetaRepository.setOptions(options) + assertTrue(ThetaApi.lastSetTimeConsumingOptionTime > 0) + val startTime = ThetaApi.lastSetTimeConsumingOptionTime + try { + runBlocking { + try { + thetaRepository.getLivePreview().collect { byteReadPacket -> + val interval = currentTimeMillis() - startTime + assertTrue(interval >= ALLOWED_CAPTURE_INTERVAL, "interval: $interval") + byteReadPacket.release() + cancel() + } + } catch (exception: Exception) { + assertTrue(true, "cancel job") + } + } + } catch (exception: Exception) { + assertTrue(true, "cancel job") + } + } + + /** + * Set consuming option + */ + @Test + fun setConsumingOptionTest() = runTest { + MockApiClient.onRequest = { _ -> + ByteReadChannel(Resource("src/commonTest/resources/setOptions/set_options_done.json").readText()) + } + + val thetaRepository = ThetaRepository(endpoint) + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0) + thetaRepository.setOptions(ThetaRepository.Options(shutterVolume = 100)) + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "normal option") + + thetaRepository.setOptions(ThetaRepository.Options(filter = ThetaRepository.FilterEnum.HDR)) + assertTrue(ThetaApi.lastSetTimeConsumingOptionTime > 0) + ThetaApi.lastSetTimeConsumingOptionTime = 0 + thetaRepository.setOptions(ThetaRepository.Options(filter = ThetaRepository.FilterEnum.HDR)) + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "same option") + thetaRepository.setOptions(ThetaRepository.Options(filter = ThetaRepository.FilterEnum.OFF)) + assertTrue(ThetaApi.lastSetTimeConsumingOptionTime > 0) + assertEquals(ThetaApi.currentOptions._filter, ImageFilter.OFF, "_filter") + + thetaRepository.setOptions(ThetaRepository.Options(captureMode = ThetaRepository.CaptureModeEnum.IMAGE)) + assertTrue(ThetaApi.lastSetTimeConsumingOptionTime > 0) + ThetaApi.lastSetTimeConsumingOptionTime = 0 + thetaRepository.setOptions(ThetaRepository.Options(captureMode = ThetaRepository.CaptureModeEnum.IMAGE)) + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "same option") + thetaRepository.setOptions(ThetaRepository.Options(captureMode = ThetaRepository.CaptureModeEnum.VIDEO)) + assertTrue(ThetaApi.lastSetTimeConsumingOptionTime > 0) + assertEquals(ThetaApi.currentOptions.captureMode, CaptureMode.VIDEO, "captureMode") + } + + /** + * Set consuming option exception + */ + @Test + fun setConsumingOptionExceptionTest() = runTest { + MockApiClient.onRequest = { _ -> + ByteReadChannel(Resource("src/commonTest/resources/setOptions/set_options_error.json").readText()) + } + + var thetaRepository = ThetaRepository(endpoint) + try { + thetaRepository.setOptions(ThetaRepository.Options(filter = ThetaRepository.FilterEnum.HDR)) + assertTrue(false, "response is normal.") + } catch (_: ThetaRepository.ThetaWebApiException) { } + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "no change") + + thetaRepository = ThetaRepository(endpoint) + try { + thetaRepository.setOptions(ThetaRepository.Options(captureMode = ThetaRepository.CaptureModeEnum.IMAGE)) + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "same option") + assertTrue(false, "response is normal.") + } catch (_: ThetaRepository.ThetaWebApiException) { } + assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "no change") + } } diff --git a/kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_capture_mode_image.json b/kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_capture_mode_image.json new file mode 100644 index 0000000000..8ea753a915 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_capture_mode_image.json @@ -0,0 +1 @@ +{"name":"camera.getOptions","results":{"options":{"captureMode":"image"}},"state":"done"} diff --git a/kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_capture_mode_video.json b/kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_capture_mode_video.json new file mode 100644 index 0000000000..39f299f76e --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_capture_mode_video.json @@ -0,0 +1 @@ +{"name":"camera.getOptions","results":{"options":{"captureMode":"video"}},"state":"done"} diff --git a/kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_filter_hdr.json b/kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_filter_hdr.json new file mode 100644 index 0000000000..eb0b772000 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_filter_hdr.json @@ -0,0 +1 @@ +{"name":"camera.getOptions","results":{"options":{"_filter":"hdr"}},"state":"done"} diff --git a/kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_filter_off.json b/kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_filter_off.json new file mode 100644 index 0000000000..b1064f2f72 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/getOptions/get_options_filter_off.json @@ -0,0 +1 @@ +{"name":"camera.getOptions","results":{"options":{"_filter":"off"}},"state":"done"} diff --git a/kotlin-multiplatform/src/iosMain/kotlin/com/ricoh360/thetaclient/Platform.kt b/kotlin-multiplatform/src/iosMain/kotlin/com/ricoh360/thetaclient/Platform.kt index 83596954e9..bae6192bae 100644 --- a/kotlin-multiplatform/src/iosMain/kotlin/com/ricoh360/thetaclient/Platform.kt +++ b/kotlin-multiplatform/src/iosMain/kotlin/com/ricoh360/thetaclient/Platform.kt @@ -42,3 +42,8 @@ actual fun frameFrom(packet: Pair): FrameSource { actual fun randomUUID(): String { return NSUUID.UUID().UUIDString } + +actual fun currentTimeMillis(): Long { + val interval = NSDate().timeIntervalSince1970 + return (interval / 1000.0).toLong() +} From a58d05256ded14a1737a4756cd7c7aa97b796132 Mon Sep 17 00:00:00 2001 From: osakila Date: Thu, 28 Mar 2024 14:09:48 +0900 Subject: [PATCH 6/9] Update state * Add unknown value log * Add state gps information to kmp * Add state gps information to react native * Add state gps information to react native * Add boardTemp & batteryTemp to state --- .../theta_client_flutter/ConvertUtil.kt | 18 ++- flutter/ios/Classes/ConvertUtil.swift | 26 +++- flutter/lib/state/state_gps_info.dart | 9 ++ flutter/lib/state/theta_state.dart | 103 +++++++++++++ flutter/lib/theta_client_flutter.dart | 86 +---------- .../theta_client_flutter_method_channel.dart | 1 + ...eta_client_flutter_platform_interface.dart | 1 + flutter/lib/utils/convert_utils.dart | 22 +++ ...ta_client_flutter_method_channel_test.dart | 15 ++ flutter/test/theta_client_flutter_test.dart | 17 ++- .../ricoh360/thetaclient/ThetaRepository.kt | 30 +++- .../thetaclient/transferred/serializer.kt | 9 +- .../thetaclient/transferred/stateApi.kt | 46 ++++++ .../repository/GetThetaStateTest.kt | 143 ++++++++++++++++++ .../resources/state/state_x_gpsinfo.json | 45 ++++++ .../resources/state/state_z1_gps_off.json | 30 ++++ .../resources/state/state_z1_gps_on.json | 36 +++++ .../thetaclientreactnative/Converter.kt | 13 ++ .../ThetaClientSdkModule.kt | 12 ++ react-native/ios/ConvertUtil.swift | 26 +++- .../src/theta-repository/theta-state/index.ts | 1 + .../theta-state/state-gps-info.ts | 9 ++ .../theta-state/theta-state.ts | 9 ++ 23 files changed, 613 insertions(+), 94 deletions(-) create mode 100644 flutter/lib/state/state_gps_info.dart create mode 100644 flutter/lib/state/theta_state.dart create mode 100644 kotlin-multiplatform/src/commonTest/resources/state/state_x_gpsinfo.json create mode 100644 kotlin-multiplatform/src/commonTest/resources/state/state_z1_gps_off.json create mode 100644 kotlin-multiplatform/src/commonTest/resources/state/state_z1_gps_on.json create mode 100644 react-native/src/theta-repository/theta-state/state-gps-info.ts 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 beabaf5ee7..11fb5efdb6 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,9 @@ 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_GPS_INFO = "gpsInfo" +const val KEY_STATE_EXTERNAL_GPS_INFO = "externalGpsInfo" +const val KEY_STATE_INTERNAL_GPS_INFO = "internalGpsInfo" fun toResult(thetaInfo: ThetaInfo): Map { return mapOf( @@ -45,7 +48,7 @@ fun toCameraErrorList(cameraErrorList: List?): List? { } fun toResult(thetaState: ThetaState): Map { - return mapOf( + val result = mutableMapOf( "fingerprint" to thetaState.fingerprint, "batteryLevel" to thetaState.batteryLevel, "storageUri" to thetaState.storageUri, @@ -67,6 +70,13 @@ fun toResult(thetaState: ThetaState): Map { "cameraError" to toCameraErrorList(thetaState.cameraError), "isBatteryInsert" to thetaState.isBatteryInsert, ) + thetaState.externalGpsInfo?.let { + result.put(KEY_STATE_EXTERNAL_GPS_INFO, toResult(it)) + } + thetaState.internalGpsInfo?.let { + result.put(KEY_STATE_INTERNAL_GPS_INFO, toResult(it)) + } + return result } fun toResult(fileInfoList: List): List> { @@ -422,6 +432,12 @@ fun toResult(gpsInfo: GpsInfo): Map { ) } +fun toResult(stateGpsInfo: StateGpsInfo): Map { + return stateGpsInfo.gpsInfo?.let { + mapOf(KEY_GPS_INFO to toResult(it)) + } ?: mapOf() +} + fun toResult(proxy: Proxy): Map { return mapOf( "use" to proxy.use, diff --git a/flutter/ios/Classes/ConvertUtil.swift b/flutter/ios/Classes/ConvertUtil.swift index 64d813d7e3..83a2877788 100644 --- a/flutter/ios/Classes/ConvertUtil.swift +++ b/flutter/ios/Classes/ConvertUtil.swift @@ -8,6 +8,11 @@ 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_GPS_INFO = "gpsInfo" +let KEY_STATE_EXTERNAL_GPS_INFO = "externalGpsInfo" +let KEY_STATE_INTERNAL_GPS_INFO = "internalGpsInfo" +let KEY_STATE_BOARD_TEMP = "boardTemp" +let KEY_STATE_BATTERY_TEMP = "batteryTemp" public class ConvertUtil: NSObject {} @@ -123,7 +128,7 @@ func convertResult(cameraErrorList: [ThetaRepository.CameraErrorEnum]?) -> [Stri } func convertResult(thetaState: ThetaRepository.ThetaState) -> [String: Any?] { - return [ + var result = [ "fingerprint": thetaState.fingerprint, "batteryLevel": thetaState.batteryLevel, "storageUri": thetaState.storageUri, @@ -144,7 +149,17 @@ func convertResult(thetaState: ThetaRepository.ThetaState) -> [String: Any?] { "isSdCard": convertKotlinBooleanToBool(value: thetaState.isSdCard), "cameraError": convertResult(cameraErrorList: thetaState.cameraError), "isBatteryInsert": convertKotlinBooleanToBool(value: thetaState.isBatteryInsert), - ] + KEY_STATE_BOARD_TEMP: thetaState.boardTemp, + KEY_STATE_BATTERY_TEMP: thetaState.batteryTemp, + ] as [String : Any?] + + if let externalGpsInfo = thetaState.externalGpsInfo { + result[KEY_STATE_EXTERNAL_GPS_INFO] = convertResult(stateGpsInfo: externalGpsInfo) + } + if let internalGpsInfo = thetaState.internalGpsInfo { + result[KEY_STATE_INTERNAL_GPS_INFO] = convertResult(stateGpsInfo: internalGpsInfo) + } + return result } func setCaptureBuilderParams(params: [String: Any], builder: CaptureBuilder) { @@ -617,6 +632,13 @@ func convertResult(gpsInfo: ThetaRepository.GpsInfo) -> [String: Any] { ] } +func convertResult(stateGpsInfo: ThetaRepository.StateGpsInfo) -> [String: Any] { + guard let gpsInfo = stateGpsInfo.gpsInfo else { return [:] } + return [ + KEY_GPS_INFO: convertResult(gpsInfo: gpsInfo), + ] +} + func convertResult(proxy: ThetaRepository.Proxy) -> [String: Any] { return [ "use": proxy.use, diff --git a/flutter/lib/state/state_gps_info.dart b/flutter/lib/state/state_gps_info.dart new file mode 100644 index 0000000000..a8c491f828 --- /dev/null +++ b/flutter/lib/state/state_gps_info.dart @@ -0,0 +1,9 @@ +import '../theta_client_flutter.dart'; + +/// GPS information of state +class StateGpsInfo { + /// GPS information + GpsInfo? gpsInfo; + + StateGpsInfo(this.gpsInfo); +} diff --git a/flutter/lib/state/theta_state.dart b/flutter/lib/state/theta_state.dart new file mode 100644 index 0000000000..c77ebcfab0 --- /dev/null +++ b/flutter/lib/state/theta_state.dart @@ -0,0 +1,103 @@ +import '../theta_client_flutter.dart'; +import 'state_gps_info.dart'; + +/// Mutable values representing Theta status. +class ThetaState { + /// Fingerprint (unique identifier) of the current camera state + String fingerprint; + + /// Battery level between 0.0 and 1.0 + double batteryLevel; + + /// Storage URI + String? storageUri; + + /// Storage ID + String? storageID; + + /// Continuously shoots state + CaptureStatusEnum captureStatus; + + /// Recorded time of movie (seconds) + int recordedTime; + + /// Recordable time of movie (seconds) + int recordableTime; + + /// Number of still images captured during continuous shooting, Unit: images + int? capturedPictures; + + /// Elapsed time for interval composite shooting (sec) + int? compositeShootingElapsedTime; + + /// URL of the last saved file + String latestFileUrl; + + /// Charging state + ChargingStateEnum chargingState; + + /// API version currently set (1: v2.0, 2: v2.1) + int apiVersion; + + /// Plugin running state (true: running, false: stop) + bool? isPluginRunning; + + /// Plugin web server state (true: enabled, false: disabled) + bool? isPluginWebServer; + + /// Shooting function status + ShootingFunctionEnum? function; + + /// My setting changed state + bool? isMySettingChanged; + + /// Identifies the microphone used while recording video + MicrophoneOptionEnum? currentMicrophone; + + /// True if record to SD card + bool isSdCard; + + /// Error information of the camera + List? cameraError; + + /// true: Battery inserted; false: Battery not inserted + bool? isBatteryInsert; + + /// Location data is obtained through an external device using WebAPI or BLE-API. + StateGpsInfo? externalGpsInfo; + + /// Location data is obtained through an internal GPS module. RICOH THETA Z1 does not have a built-in GPS module. + StateGpsInfo? internalGpsInfo; + + /// This represents the current temperature inside the camera as an integer value, ranging from -10°C to 100°C with a precision of 1°C. + int? boardTemp; + + /// This represents the current temperature inside the battery as an integer value, ranging from -10°C to 100°C with a precision of 1°C. + int? batteryTemp; + + ThetaState( + this.fingerprint, + this.batteryLevel, + this.storageUri, + this.storageID, + this.captureStatus, + this.recordedTime, + this.recordableTime, + this.capturedPictures, + this.compositeShootingElapsedTime, + this.latestFileUrl, + this.chargingState, + this.apiVersion, + this.isPluginRunning, + this.isPluginWebServer, + this.function, + this.isMySettingChanged, + this.currentMicrophone, + this.isSdCard, + this.cameraError, + this.isBatteryInsert, + this.externalGpsInfo, + this.internalGpsInfo, + this.boardTemp, + this.batteryTemp); +} diff --git a/flutter/lib/theta_client_flutter.dart b/flutter/lib/theta_client_flutter.dart index c18aea924d..c36d98bf38 100644 --- a/flutter/lib/theta_client_flutter.dart +++ b/flutter/lib/theta_client_flutter.dart @@ -5,6 +5,7 @@ import 'package:theta_client_flutter/digest_auth.dart'; import 'capture/capture_builder.dart'; import 'options/importer.dart'; +import 'state/theta_state.dart'; import 'theta_client_flutter_platform_interface.dart'; export 'capture/capture.dart'; @@ -1317,91 +1318,6 @@ enum CameraErrorEnum { } } -/// Mutable values representing Theta status. -class ThetaState { - /// Fingerprint (unique identifier) of the current camera state - String fingerprint; - - /// Battery level between 0.0 and 1.0 - double batteryLevel; - - /// Storage URI - String? storageUri; - - /// Storage ID - String? storageID; - - /// Continuously shoots state - CaptureStatusEnum captureStatus; - - /// Recorded time of movie (seconds) - int recordedTime; - - /// Recordable time of movie (seconds) - int recordableTime; - - /// Number of still images captured during continuous shooting, Unit: images - int? capturedPictures; - - /// Elapsed time for interval composite shooting (sec) - int? compositeShootingElapsedTime; - - /// URL of the last saved file - String latestFileUrl; - - /// Charging state - ChargingStateEnum chargingState; - - /// API version currently set (1: v2.0, 2: v2.1) - int apiVersion; - - /// Plugin running state (true: running, false: stop) - bool? isPluginRunning; - - /// Plugin web server state (true: enabled, false: disabled) - bool? isPluginWebServer; - - /// Shooting function status - ShootingFunctionEnum? function; - - /// My setting changed state - bool? isMySettingChanged; - - /// Identifies the microphone used while recording video - MicrophoneOptionEnum? currentMicrophone; - - /// True if record to SD card - bool isSdCard; - - /// Error information of the camera - List? cameraError; - - /// true: Battery inserted; false: Battery not inserted - bool? isBatteryInsert; - - ThetaState( - this.fingerprint, - this.batteryLevel, - this.storageUri, - this.storageID, - this.captureStatus, - this.recordedTime, - this.recordableTime, - this.capturedPictures, - this.compositeShootingElapsedTime, - this.latestFileUrl, - this.chargingState, - this.apiVersion, - this.isPluginRunning, - this.isPluginWebServer, - this.function, - this.isMySettingChanged, - this.currentMicrophone, - this.isSdCard, - this.cameraError, - this.isBatteryInsert); -} - /// 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 54e907897f..03abb1a1c5 100644 --- a/flutter/lib/theta_client_flutter_method_channel.dart +++ b/flutter/lib/theta_client_flutter_method_channel.dart @@ -5,6 +5,7 @@ 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; diff --git a/flutter/lib/theta_client_flutter_platform_interface.dart b/flutter/lib/theta_client_flutter_platform_interface.dart index 9eb08184b7..1799c791ca 100644 --- a/flutter/lib/theta_client_flutter_platform_interface.dart +++ b/flutter/lib/theta_client_flutter_platform_interface.dart @@ -2,6 +2,7 @@ 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 { diff --git a/flutter/lib/utils/convert_utils.dart b/flutter/lib/utils/convert_utils.dart index 3d40f2f482..43165ef3e9 100644 --- a/flutter/lib/utils/convert_utils.dart +++ b/flutter/lib/utils/convert_utils.dart @@ -1,6 +1,9 @@ import 'package:theta_client_flutter/digest_auth.dart'; import 'package:theta_client_flutter/theta_client_flutter.dart'; +import '../state/theta_state.dart'; +import '../state/state_gps_info.dart'; + class ConvertUtils { static List? convertAutoBracketOption(List? data) { if (data == null) { @@ -151,6 +154,12 @@ class ConvertUtils { } static ThetaState convertThetaState(Map data) { + var externalGpsInfo = data['externalGpsInfo'] != null + ? convertStateGpsInfo(data['externalGpsInfo']) + : null; + var internalGpsInfo = data['internalGpsInfo'] != null + ? convertStateGpsInfo(data['internalGpsInfo']) + : null; var thetaState = ThetaState( data['fingerprint'], data['batteryLevel'], @@ -172,6 +181,10 @@ class ConvertUtils { data['isSdCard'], ConvertUtils.toCameraErrorList(data['cameraError']), data['isBatteryInsert'], + externalGpsInfo, + internalGpsInfo, + data['boardTemp'], + data['batteryTemp'], ); return thetaState; } @@ -222,6 +235,15 @@ class ConvertUtils { return gpsInfo; } + static StateGpsInfo convertStateGpsInfo(Map data) { + var gpsInfo = data['gpsInfo']; + if (gpsInfo == null) { + return StateGpsInfo(null); + } else { + return StateGpsInfo(convertGpsInfo(gpsInfo)); + } + } + static Map convertProxyParam(Proxy proxy) { return { 'use': proxy.use, diff --git a/flutter/test/theta_client_flutter_method_channel_test.dart b/flutter/test/theta_client_flutter_method_channel_test.dart index 0208c7c2d2..20e74eb8b8 100644 --- a/flutter/test/theta_client_flutter_method_channel_test.dart +++ b/flutter/test/theta_client_flutter_method_channel_test.dart @@ -134,6 +134,13 @@ void main() { ]; const isBatteryInsert = false; + Map gpsInfoMap = { + 'latitude': 1.0, + 'longitude': 2.0, + 'altitude': 3.0, + 'dateTimeZone': '2022:01:01 00:01:00+09:00' + }; + List convertCameraErrorParam( List cameraErrorList) { var stringList = List.empty(growable: true); @@ -166,6 +173,10 @@ void main() { 'isSdCard': isSdCard, 'cameraError': convertCameraErrorParam(cameraError), 'isBatteryInsert': isBatteryInsert, + 'externalGpsInfo': { + 'gpsInfo': gpsInfoMap, + }, + 'internalGpsInfo': {}, }; return state; }); @@ -193,6 +204,10 @@ void main() { expect(thetaState.isSdCard, isSdCard); expect(thetaState.cameraError, cameraError); expect(thetaState.isBatteryInsert, isBatteryInsert); + var externalGpsInfo = thetaState.externalGpsInfo?.gpsInfo; + expect(externalGpsInfo, isNotNull); + expect(externalGpsInfo?.latitude, 1.0); + expect(thetaState.internalGpsInfo?.gpsInfo, isNull); }); test('getThetaState nullable', () async { diff --git a/flutter/test/theta_client_flutter_test.dart b/flutter/test/theta_client_flutter_test.dart index 16f1fdeb63..ee74810059 100644 --- a/flutter/test/theta_client_flutter_test.dart +++ b/flutter/test/theta_client_flutter_test.dart @@ -2,6 +2,8 @@ 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'; @@ -610,6 +612,11 @@ void main() { CameraErrorEnum.batteryHighTemperature ]; const isBatteryInsert = false; + var externalGpsInfo = + StateGpsInfo(GpsInfo(1.0, 2.0, 3.0, '2022:01:01 00:01:00+09:00')); + var internalGpsInfo = StateGpsInfo(null); + const boardTemp = 40; + const batteryTemp = 50; onGetThetaState = () { return Future.value(ThetaState( fingerprint, @@ -631,7 +638,11 @@ void main() { currentMicrophone, isSdCard, cameraError, - isBatteryInsert)); + isBatteryInsert, + externalGpsInfo, + internalGpsInfo, + boardTemp, + batteryTemp)); }; var thetaState = await thetaClientPlugin.getThetaState(); @@ -657,6 +668,10 @@ void main() { expect(thetaState.isSdCard, isSdCard); expect(thetaState.cameraError, cameraError); expect(thetaState.isBatteryInsert, isBatteryInsert); + expect(thetaState.externalGpsInfo, externalGpsInfo); + expect(thetaState.internalGpsInfo?.gpsInfo, null); + expect(thetaState.boardTemp, 40); + expect(thetaState.batteryTemp, 50); }); test('getOptions', () async { 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 1bb50667c6..15167ceb88 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt @@ -3705,6 +3705,20 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? } } + /** + * GPS information of state + */ + data class StateGpsInfo ( + /** + * GPS information + */ + val gpsInfo: GpsInfo? = null + ) { + internal constructor(stateGpsInfo: com.ricoh360.thetaclient.transferred.StateGpsInfo) : this( + gpsInfo = stateGpsInfo.gpsInfo?.let { GpsInfo(it) } + ) + } + /** * Turns position information assigning ON/OFF. * @@ -5980,6 +5994,10 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? * @property isSdCard True if record to SD card * @property cameraError Error information of the camera * @property isBatteryInsert true: Battery inserted; false: Battery not inserted + * @property externalGpsInfo Location data is obtained through an external device using WebAPI or BLE-API. + * @property internalGpsInfo Location data is obtained through an internal GPS module. RICOH THETA Z1 does not have a built-in GPS module. + * @property boardTemp This represents the current temperature inside the camera as an integer value, ranging from -10°C to 100°C with a precision of 1°C. + * @property batteryTemp This represents the current temperature inside the battery as an integer value, ranging from -10°C to 100°C with a precision of 1°C. */ data class ThetaState( val fingerprint: String, @@ -6001,7 +6019,11 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? val currentMicrophone: MicrophoneOptionEnum?, val isSdCard: Boolean, val cameraError: List?, - val isBatteryInsert: Boolean? + val isBatteryInsert: Boolean?, + val externalGpsInfo: StateGpsInfo?, + val internalGpsInfo: StateGpsInfo?, + val boardTemp: Int?, + val batteryTemp: Int?, ) { internal constructor(response: StateApiResponse) : this( response.fingerprint, @@ -6023,7 +6045,11 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? response.state._currentMicrophone?.let { MicrophoneOptionEnum.get(response.state._currentMicrophone) }, response.state._currentStorage == StorageOption.SD, response.state._cameraError?.map { it -> CameraErrorEnum.get(it) }, - response.state._batteryInsert + response.state._batteryInsert, + response.state._externalGpsInfo?.let { StateGpsInfo(it) }, + response.state._internalGpsInfo?.let { StateGpsInfo(it) }, + response.state._boardTemp, + response.state._batteryTemp, ) } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/serializer.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/serializer.kt index 620867460b..11ed8c9925 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/serializer.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/serializer.kt @@ -202,6 +202,13 @@ internal abstract class EnumIgnoreUnknownSerializer>( } override fun deserialize(decoder: Decoder): T { - return revLookup[decoder.decodeString()] ?: defaultValue + val decodeString = decoder.decodeString() + return when (val value = revLookup[decodeString]) { + null -> { + println("Web API unknown value. ${defaultValue::class.simpleName}: $decodeString") + defaultValue + } + else -> value + } } } 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 39de0c5cb8..cc867ca799 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 @@ -185,6 +185,41 @@ internal data class CameraState( * RICOH THETA X or later */ val _batteryInsert: Boolean? = null, + + /** + * Location data is obtained through an external device using WebAPI or BLE-API. + * Please refer to the object specification for [GpsInfo] as well. + * + * RICOH THETA X firmware v2.20.1 or later + * RICOH THETA Z1 firmware v3.10.2 or later + */ + val _externalGpsInfo: StateGpsInfo? = null, + + /** + * Location data is obtained through an internal GPS module. + * Please refer to the object specification for [GpsInfo] as well. + * RICOH THETA Z1 does not have a built-in GPS module. + * + * RICOH THETA X firmware v2.20.1 or later + * RICOH THETA Z1 firmware v3.10.2 or later + */ + val _internalGpsInfo: StateGpsInfo? = null, + + /** + * This represents the current temperature inside the camera as an integer value, ranging from -10°C to 100°C with a precision of 1°C. + * + * RICOH THETA X firmware v2.20.1 or later + * RICOH THETA Z1 firmware v3.10.2 or later + */ + val _boardTemp: Int? = null, + + /** + * This represents the current temperature inside the battery as an integer value, ranging from -10°C to 100°C with a precision of 1°C. + * + * RICOH THETA X firmware v2.20.1 or later + * RICOH THETA Z1 firmware v3.10.2 or later + */ + val _batteryTemp: Int? = null, ) /** @@ -523,3 +558,14 @@ internal enum class CameraError { // 0x00200000: Battery temperature error // BATTERY_HIGH_TEMPERATURE("BATTERY_HIGH_TEMPERATURE"), } + +/** + * GPS information of state + */ +@Serializable +internal data class StateGpsInfo ( + /** + * GPS information + */ + val gpsInfo: GpsInfo? = null +) 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 3fe62fbf72..adb3330f43 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 @@ -101,6 +101,149 @@ class GetThetaStateTest { assertTrue(!thetaState.isSdCard, "state isSdCard") assertEquals(thetaState.cameraError!![0], ThetaRepository.CameraErrorEnum.HIGH_TEMPERATURE_WARNING, "state cameraError") assertTrue(thetaState.isBatteryInsert!!, "state isBatteryInsert") + + assertNull(thetaState.boardTemp, "state boardTemp") + assertNull(thetaState.batteryTemp, "state batteryTemp") + } + + /** + * call getThetaState with gps for THETA X. + */ + @Test + fun getThetaStateGpsForXTest() = runTest { + // setup + val jsonString = Resource("src/commonTest/resources/state/state_x_gpsinfo.json").readText() + MockApiClient.onRequest = { request -> + assertEquals(request.url.encodedPath, "/osc/state", "request path") + ByteReadChannel(jsonString) + } + + // test + val thetaRepository = ThetaRepository(endpoint) + val thetaState = thetaRepository.getThetaState() + + // check + assertTrue(thetaState.fingerprint.isNotEmpty(), "state fingerprint") + assertTrue(thetaState.batteryLevel > 0, "state batteryLevel") + assertEquals(thetaState.storageUri?.startsWith("http://"), true, "state storageUri") + assertEquals(thetaState.storageID?.isNotEmpty(), true, "state storageUri") + assertEquals(thetaState.captureStatus, ThetaRepository.CaptureStatusEnum.IDLE, "state captureStatus") + assertTrue(thetaState.recordedTime >= 0, "state recordedTime") + assertTrue(thetaState.recordableTime >= 0, "state recordableTime") + assertTrue(thetaState.capturedPictures!! >= 0, "state capturedPictures") + assertNull(thetaState.compositeShootingElapsedTime, "compositeShootingElapsedTime") + assertTrue(thetaState.latestFileUrl.startsWith("http://"), "state latestFileUrl") + assertEquals(thetaState.chargingState, ThetaRepository.ChargingStateEnum.CHARGING, "state chargingState") + assertEquals(thetaState.apiVersion, 2, "state apiVersion") + assertEquals(thetaState.isPluginRunning, false, "state isPluginRunning") + assertEquals(thetaState.isPluginWebServer, false, "state isPluginWebServer") + assertEquals(thetaState.function, ThetaRepository.ShootingFunctionEnum.NORMAL, "state function") + assertEquals(thetaState.isMySettingChanged, false, "state isMySettingChanged") + assertEquals(thetaState.currentMicrophone, ThetaRepository.MicrophoneOptionEnum.INTERNAL, "state currentMicrophone") + assertTrue(!thetaState.isSdCard, "state isSdCard") + assertEquals(thetaState.cameraError?.size, 0, "state cameraError") + assertEquals(thetaState.isBatteryInsert, true, "state isBatteryInsert") + + assertNotNull(thetaState.externalGpsInfo?.gpsInfo, "state externalGpsInfo") + assertNotNull(thetaState.externalGpsInfo?.gpsInfo?.altitude, "state externalGpsInfo.altitude") + assertNotNull(thetaState.externalGpsInfo?.gpsInfo?.dateTimeZone, "state externalGpsInfo.dateTimeZone") + assertNotNull(thetaState.externalGpsInfo?.gpsInfo?.latitude, "state externalGpsInfo.latitude") + assertNotNull(thetaState.externalGpsInfo?.gpsInfo?.longitude, "state externalGpsInfo.longitude") + + assertNotNull(thetaState.internalGpsInfo?.gpsInfo, "state internalGpsInfo") + assertEquals(thetaState.internalGpsInfo?.gpsInfo, ThetaRepository.GpsInfo.disabled, "state internalGpsInfo disabled") + + assertEquals(thetaState.boardTemp, 28, "state boardTemp") + assertEquals(thetaState.batteryTemp, 30, "state batteryTemp") + } + + /** + * call getThetaState with gps on for THETA Z1. + */ + @Test + fun getThetaStateGpsOnForZ1Test() = runTest { + // setup + val jsonString = Resource("src/commonTest/resources/state/state_z1_gps_on.json").readText() + MockApiClient.onRequest = { request -> + assertEquals(request.url.encodedPath, "/osc/state", "request path") + ByteReadChannel(jsonString) + } + + // test + val thetaRepository = ThetaRepository(endpoint) + val thetaState = thetaRepository.getThetaState() + + // check + assertTrue(thetaState.fingerprint.isNotEmpty(), "state fingerprint") + assertTrue(thetaState.batteryLevel > 0, "state batteryLevel") + assertEquals(thetaState.storageUri?.startsWith("http://"), true, "state storageUri") + assertNull(thetaState.storageID, "state storageUri") + assertEquals(thetaState.captureStatus, ThetaRepository.CaptureStatusEnum.IDLE, "state captureStatus") + assertTrue(thetaState.recordedTime >= 0, "state recordedTime") + assertTrue(thetaState.recordableTime >= 0, "state recordableTime") + assertTrue((thetaState.capturedPictures ?: -1) >= 0, "state capturedPictures") + assertTrue((thetaState.compositeShootingElapsedTime ?: -1) >= 0, "compositeShootingElapsedTime") + assertTrue(thetaState.latestFileUrl.startsWith("http://"), "state latestFileUrl") + assertEquals(thetaState.chargingState, ThetaRepository.ChargingStateEnum.CHARGING, "state chargingState") + assertEquals(thetaState.apiVersion, 2, "state apiVersion") + assertEquals(thetaState.isPluginRunning, false, "state isPluginRunning") + assertEquals(thetaState.isPluginWebServer, true, "state isPluginWebServer") + assertEquals(thetaState.function, ThetaRepository.ShootingFunctionEnum.NORMAL, "state function") + assertEquals(thetaState.isMySettingChanged, false, "state isMySettingChanged") + assertNull(thetaState.currentMicrophone, "state currentMicrophone") + assertTrue(!thetaState.isSdCard, "state isSdCard") + assertEquals(thetaState.cameraError?.get(0), ThetaRepository.CameraErrorEnum.COMPASS_CALIBRATION, "state cameraError") + assertNull(thetaState.isBatteryInsert, "state isBatteryInsert") + + assertNotNull(thetaState.externalGpsInfo?.gpsInfo, "state externalGpsInfo") + assertNotNull(thetaState.externalGpsInfo?.gpsInfo?.altitude, "state externalGpsInfo.altitude") + assertNotNull(thetaState.externalGpsInfo?.gpsInfo?.dateTimeZone, "state externalGpsInfo.dateTimeZone") + assertNotNull(thetaState.externalGpsInfo?.gpsInfo?.latitude, "state externalGpsInfo.latitude") + assertNotNull(thetaState.externalGpsInfo?.gpsInfo?.longitude, "state externalGpsInfo.longitude") + + assertNull(thetaState.internalGpsInfo?.gpsInfo, "state internalGpsInfo") + } + + /** + * call getThetaState with gps off for THETA Z1. + */ + @Test + fun getThetaStateGpsOffForZ1Test() = runTest { + // setup + val jsonString = Resource("src/commonTest/resources/state/state_z1_gps_off.json").readText() + MockApiClient.onRequest = { request -> + assertEquals(request.url.encodedPath, "/osc/state", "request path") + ByteReadChannel(jsonString) + } + + // test + val thetaRepository = ThetaRepository(endpoint) + val thetaState = thetaRepository.getThetaState() + + // check + assertTrue(thetaState.fingerprint.isNotEmpty(), "state fingerprint") + assertTrue(thetaState.batteryLevel > 0, "state batteryLevel") + assertEquals(thetaState.storageUri?.startsWith("http://"), true, "state storageUri") + assertNull(thetaState.storageID, "state storageUri") + assertEquals(thetaState.captureStatus, ThetaRepository.CaptureStatusEnum.IDLE, "state captureStatus") + assertTrue(thetaState.recordedTime >= 0, "state recordedTime") + assertTrue(thetaState.recordableTime >= 0, "state recordableTime") + assertTrue((thetaState.capturedPictures ?: -1) >= 0, "state capturedPictures") + assertTrue((thetaState.compositeShootingElapsedTime ?: -1) >= 0, "compositeShootingElapsedTime") + assertEquals(thetaState.latestFileUrl, "", "state latestFileUrl") + assertEquals(thetaState.chargingState, ThetaRepository.ChargingStateEnum.CHARGING, "state chargingState") + assertEquals(thetaState.apiVersion, 2, "state apiVersion") + assertEquals(thetaState.isPluginRunning, false, "state isPluginRunning") + assertEquals(thetaState.isPluginWebServer, true, "state isPluginWebServer") + assertEquals(thetaState.function, ThetaRepository.ShootingFunctionEnum.NORMAL, "state function") + assertEquals(thetaState.isMySettingChanged, false, "state isMySettingChanged") + assertNull(thetaState.currentMicrophone, "state currentMicrophone") + assertTrue(!thetaState.isSdCard, "state isSdCard") + assertEquals(thetaState.cameraError?.get(0), ThetaRepository.CameraErrorEnum.COMPASS_CALIBRATION, "state cameraError") + assertNull(thetaState.isBatteryInsert, "state isBatteryInsert") + + assertNull(thetaState.externalGpsInfo?.gpsInfo, "state externalGpsInfo") + assertNull(thetaState.internalGpsInfo?.gpsInfo, "state internalGpsInfo") } /** diff --git a/kotlin-multiplatform/src/commonTest/resources/state/state_x_gpsinfo.json b/kotlin-multiplatform/src/commonTest/resources/state/state_x_gpsinfo.json new file mode 100644 index 0000000000..ee9e5cef3f --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/state/state_x_gpsinfo.json @@ -0,0 +1,45 @@ +{ + "fingerprint": "FIG_2339", + "state": { + "_apiVersion": 2, + "_batteryInsert": true, + "batteryLevel": 0.87, + "_batteryState": "charging", + "_batteryTemp": 30, + "_boardTemp": 28, + "_cameraError": [ + + ], + "_captureStatus": "idle", + "_capturedPictures": 0, + "_currentMicrophone": "Internal", + "_currentStorage": "IN", + "_externalGpsInfo": { + "gpsInfo": { + "_altitude": 47.80000305175781, + "_dateTimeZone": "2024:02:02 07:55:51+00:00", + "_datum": "WGS84", + "lat": 35.4855932, + "lng": 134.2400305 + } + }, + "_function": "normal", + "_internalGpsInfo": { + "gpsInfo": { + "_altitude": 0, + "_dateTimeZone": "", + "_datum": "", + "lat": 65535, + "lng": 65535 + } + }, + "_latestFileUrl": "http://192.168.1.1/files/100RICOH/R0010023.JPG", + "_mySettingChanged": false, + "_pluginRunning": false, + "_pluginWebServer": false, + "_recordableTime": 3694, + "_recordedTime": 0, + "_storageID": "412176649172527ab3d5edabb50a7d69", + "storageUri": "http://192.168.1.1/files/" + } +} \ No newline at end of file diff --git a/kotlin-multiplatform/src/commonTest/resources/state/state_z1_gps_off.json b/kotlin-multiplatform/src/commonTest/resources/state/state_z1_gps_off.json new file mode 100644 index 0000000000..5c7ef34855 --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/state/state_z1_gps_off.json @@ -0,0 +1,30 @@ +{ + "fingerprint": "FIG_0005", + "state": { + "_apiVersion": 2, + "batteryLevel": 0.88, + "_batteryState": "charging", + "_batteryTemp": 23, + "_boardTemp": 24, + "_cameraError": [ + "COMPASS_CALIBRATION" + ], + "_captureStatus": "idle", + "_capturedPictures": 0, + "_compositeShootingElapsedTime": 0, + "_externalGpsInfo": { + + }, + "_function": "normal", + "_internalGpsInfo": { + + }, + "_latestFileUrl": "", + "_mySettingChanged": false, + "_pluginRunning": false, + "_pluginWebServer": true, + "_recordableTime": 0, + "_recordedTime": 0, + "storageUri": "http://192.168.1.1/files/150100524436344d4201375fda9dc400/" + } +} \ No newline at end of file diff --git a/kotlin-multiplatform/src/commonTest/resources/state/state_z1_gps_on.json b/kotlin-multiplatform/src/commonTest/resources/state/state_z1_gps_on.json new file mode 100644 index 0000000000..2138948abc --- /dev/null +++ b/kotlin-multiplatform/src/commonTest/resources/state/state_z1_gps_on.json @@ -0,0 +1,36 @@ +{ + "fingerprint": "FIG_0019", + "state": { + "_apiVersion": 2, + "batteryLevel": 0.95, + "_batteryState": "charging", + "_batteryTemp": 29, + "_boardTemp": 31, + "_cameraError": [ + "COMPASS_CALIBRATION" + ], + "_captureStatus": "idle", + "_capturedPictures": 0, + "_compositeShootingElapsedTime": 0, + "_externalGpsInfo": { + "gpsInfo": { + "_altitude": 47.900001525878906, + "_dateTimeZone": "2024:03:08 01:14:32+00:00", + "_datum": "WGS84", + "lat": 35.4856307, + "lng": 134.2400393 + } + }, + "_function": "normal", + "_internalGpsInfo": { + + }, + "_latestFileUrl": "http://192.168.1.1/files/150100524436344d4201375fda9dc400/100RICOH/R0010006.JPG", + "_mySettingChanged": false, + "_pluginRunning": false, + "_pluginWebServer": true, + "_recordableTime": 0, + "_recordedTime": 0, + "storageUri": "http://192.168.1.1/files/150100524436344d4201375fda9dc400/" + } +} \ No newline at end of file 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 e25420ddf8..c44e3d3d1b 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 @@ -14,6 +14,11 @@ const val KEY_NOTIFY_NAME = "name" const val KEY_NOTIFY_PARAMS = "params" const val KEY_NOTIFY_PARAM_COMPLETION = "completion" const val KEY_NOTIFY_PARAM_MESSAGE = "message" +const val KEY_GPS_INFO = "gpsInfo" +const val KEY_STATE_EXTERNAL_GPS_INFO = "externalGpsInfo" +const val KEY_STATE_INTERNAL_GPS_INFO = "internalGpsInfo" +const val KEY_STATE_BOARD_TEMP = "boardTemp" +const val KEY_STATE_BATTERY_TEMP = "batteryTemp" val optionItemNameToEnum: Map = mutableMapOf( "aiAutoThumbnail" to OptionNameEnum.AiAutoThumbnail, @@ -489,6 +494,14 @@ fun toResult(gpsInfo: GpsInfo): WritableMap { return result } +fun toResult(stateGpsInfo: StateGpsInfo): WritableMap { + val result = Arguments.createMap() + stateGpsInfo.gpsInfo?.let { + result.putMap(KEY_GPS_INFO, toResult(it)) + } + return result +} + fun toResult(proxy: Proxy): WritableMap { val result = Arguments.createMap() result.putBoolean("use", proxy.use) 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 50c856772c..b59f653323 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 @@ -251,6 +251,18 @@ class ThetaClientReactNativeModule( state.isBatteryInsert?.also { result.putString("isBatteryInsert", state.isBatteryInsert.toString()) } ?: result.putNull("isBatteryInsert") + state.externalGpsInfo?.also { + result.putMap(KEY_STATE_EXTERNAL_GPS_INFO, toResult(it)) + } ?: result.putNull(KEY_STATE_EXTERNAL_GPS_INFO) + state.internalGpsInfo?.also { + result.putMap(KEY_STATE_INTERNAL_GPS_INFO, toResult(it)) + } ?: result.putNull(KEY_STATE_INTERNAL_GPS_INFO) + state.boardTemp?.also { + result.putInt(KEY_STATE_BOARD_TEMP, it) + } ?: result.putNull(KEY_STATE_BOARD_TEMP) + state.batteryTemp?.also { + result.putInt(KEY_STATE_BATTERY_TEMP, it) + } ?: result.putNull(KEY_STATE_BATTERY_TEMP) promise.resolve(result) } catch (t: Throwable) { promise.reject(t) diff --git a/react-native/ios/ConvertUtil.swift b/react-native/ios/ConvertUtil.swift index 2483cf2f4d..0453fbcd8e 100644 --- a/react-native/ios/ConvertUtil.swift +++ b/react-native/ios/ConvertUtil.swift @@ -59,6 +59,10 @@ let KEY_TOP_BOTTOM_CORRECTION_ROTATION_ROLL = "roll" let KEY_TOP_BOTTOM_CORRECTION_ROTATION_YAW = "yaw" let KEY_TIMESHIFT_CAPTURE_INTERVAL = "_capture_interval" let KEY_AUTO_BRACKET = "autoBracket" +let KEY_STATE_EXTERNAL_GPS_INFO = "externalGpsInfo" +let KEY_STATE_INTERNAL_GPS_INFO = "internalGpsInfo" +let KEY_STATE_BOARD_TEMP = "boardTemp" +let KEY_STATE_BATTERY_TEMP = "batteryTemp" public class ConvertUtil: NSObject {} @@ -821,7 +825,7 @@ func convertResult(cameraErrorList: [ThetaRepository.CameraErrorEnum]?) -> [Stri } func convertResult(thetaState: ThetaRepository.ThetaState) -> [String: Any?] { - return [ + var result = [ "fingerprint": thetaState.fingerprint, "batteryLevel": thetaState.batteryLevel, "storageUri": thetaState.storageUri, @@ -842,7 +846,18 @@ func convertResult(thetaState: ThetaRepository.ThetaState) -> [String: Any?] { "isSdCard": convertKotlinBooleanToBool(value: thetaState.isSdCard), "cameraError": convertResult(cameraErrorList: thetaState.cameraError), "isBatteryInsert": convertKotlinBooleanToBool(value: thetaState.isBatteryInsert), - ] + KEY_STATE_BOARD_TEMP: thetaState.boardTemp, + KEY_STATE_BATTERY_TEMP: thetaState.batteryTemp, + ] as [String : Any?] + + if let externalGpsInfo = thetaState.externalGpsInfo { + result[KEY_STATE_EXTERNAL_GPS_INFO] = convertResult(stateGpsInfo: externalGpsInfo) + } + if let internalGpsInfo = thetaState.internalGpsInfo { + result[KEY_STATE_INTERNAL_GPS_INFO] = convertResult(stateGpsInfo: internalGpsInfo) + } + + return result } func convertResult(burstOption: ThetaRepository.BurstOption) -> [String: Any] { @@ -911,6 +926,13 @@ func convertResult(gpsInfo: ThetaRepository.GpsInfo) -> [String: Any] { ] } +func convertResult(stateGpsInfo: ThetaRepository.StateGpsInfo) -> [String: Any] { + guard let gpsInfo = stateGpsInfo.gpsInfo else { return [:] } + return [ + KEY_GPS_INFO: convertResult(gpsInfo: gpsInfo), + ] +} + func convertResult(proxy: ThetaRepository.Proxy) -> [String: Any] { var result: [String: Any] = [:] result[KEY_PROXY_USE] = proxy.use diff --git a/react-native/src/theta-repository/theta-state/index.ts b/react-native/src/theta-repository/theta-state/index.ts index a2b21ead27..51eefdaf70 100644 --- a/react-native/src/theta-repository/theta-state/index.ts +++ b/react-native/src/theta-repository/theta-state/index.ts @@ -1,2 +1,3 @@ export * from './camera-error'; +export * from './state-gps-info'; export * from './theta-state'; diff --git a/react-native/src/theta-repository/theta-state/state-gps-info.ts b/react-native/src/theta-repository/theta-state/state-gps-info.ts new file mode 100644 index 0000000000..375979d0f8 --- /dev/null +++ b/react-native/src/theta-repository/theta-state/state-gps-info.ts @@ -0,0 +1,9 @@ +import type { GpsInfo } from '../options'; + +/** + * GPS information of state + */ +export type StateGpsInfo = { + /** GPS information */ + gpsInfo?: GpsInfo; +}; 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 b9534ef796..b01554c8a5 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 { StateGpsInfo } from './state-gps-info'; /** Battery charging state constants */ export const ChargingStateEnum = { @@ -95,4 +96,12 @@ export type ThetaState = { cameraError: Array | null; /** true: Battery inserted; false: Battery not inserted */ isBatteryInsert: boolean | null; + /** Location data is obtained through an external device using WebAPI or BLE-API. */ + externalGpsInfo: StateGpsInfo | null; + /** Location data is obtained through an internal GPS module. RICOH THETA Z1 does not have a built-in GPS module. */ + internalGpsInfo: StateGpsInfo | null; + /** This represents the current temperature inside the camera as an integer value, ranging from -10°C to 100°C with a precision of 1°C. */ + boardTemp: number | null; + /** This represents the current temperature inside the battery as an integer value, ranging from -10°C to 100°C with a precision of 1°C. */ + batteryTemp: number | null; }; From 2f1ed1eadf0ae8cf2d039807f0da9e837832cd0a Mon Sep 17 00:00:00 2001 From: ywatanabe-dev <102775353+ywatanabe-dev@users.noreply.github.com> Date: Tue, 2 Apr 2024 09:48:16 +0900 Subject: [PATCH 7/9] Add bitrate setting documents --- docs/tutorial-android.ja.md | 22 +++++++++++ docs/tutorial-android.md | 23 ++++++++++- docs/tutorial-flutter.ja.md | 33 ++++++++++++++++ docs/tutorial-flutter.md | 34 ++++++++++++++++- docs/tutorial-ios.ja.md | 37 ++++++++++++++++++ docs/tutorial-ios.md | 38 ++++++++++++++++++- docs/tutorial-react-native.ja.md | 18 +++++++++ docs/tutorial-react-native.md | 19 +++++++++- .../com/ricoh360/thetaclient/PreviewClient.kt | 4 +- 9 files changed, 222 insertions(+), 6 deletions(-) diff --git a/docs/tutorial-android.ja.md b/docs/tutorial-android.ja.md index cc87e60242..6ff8bfe961 100644 --- a/docs/tutorial-android.ja.md +++ b/docs/tutorial-android.ja.md @@ -109,6 +109,28 @@ thetaRepository.getPhotoCaptureBuilder() - ISO 上限 (THETA V ファームウェア v2.50.1 以前、THETA S、THETA SC では指定しても無視される) - 200, 250, 320, 400, 500, 640, 800, 1000, 1250, 1600, 2000, 2500, 3200 +#### 静止画撮影でビットレートの数値を設定する場合(THETA X) + +THETA X では静止画撮影のビットレートの数値を設定することができます([api-spec](https://github.com/ricohapi/theta-api-specs/blob/main/theta-web-api-v2.1/options/_bitrate.md)参照)。 +THETA Client でこの値を設定する際は、以下の手順が必要です: + +1. `PhotoCapture`オブジェクトを生成 +1. `setOptions()`で`bitrate`を設定 +1. `takePicture()`で撮影 + +この手順が必要な理由は、`PhotoCapture.Builder.build()`内で`captureMode`の値が`image`にセットされる際に、 +カメラ側の`bitrate`の値がリセットされるためです。 + +```kotlin +val photoCapture = thetaRepository.getPhotoCaptureBuilder().build() + +val options = ThetaRepository.Options() +options.bitrate = ThetaRepository.BitrateNumber(1048576) +thetaRepository.setOptions(options) + +photoCapture.takePicture(TakenCallback()) +``` + ## 動画を撮影する まず`VideoCapture.Builder`を使って撮影設定を行い、`VideoCapture`オブジェクトを生成します。 diff --git a/docs/tutorial-android.md b/docs/tutorial-android.md index 7765cd72fb..ee4b7b2bbf 100644 --- a/docs/tutorial-android.md +++ b/docs/tutorial-android.md @@ -1,4 +1,4 @@ -# THETA client tutorial for Android +# THETA client tutorial for Android ## Advance preparation @@ -98,6 +98,27 @@ See [Display a preview](#preview) for instructions on how to view preview. - ISO upper limit (THETA V firmware v2.50.1 or earlier, ignored even if specified in THETA S or THETA SC) - 200, 250, 320, 400, 500, 640, 800, 1000, 1250, 1600, 2000, 2500, 3200 +#### Set the bitrate value for still image capture (THETA X) + +The bitrate value for still image capture can be set in THETA X(see [api-spec](https://github.com/ricohapi/theta-api-specs/blob/main/theta-web-api-v2.1/options/_bitrate.md)). +To set this value with THETA Client, follow the steps below. + +1. Create a `PhotoCapture` object +1. Call `setOptions()` to set the `bitrate` value +1. Call `takePicture()` + +The reason this step is necessary is that the `bitrate` value on the camera is reset when the `captureMode` value is set to `image` in `PhotoCapture.Builder.build()`. + +```kotlin +val photoCapture = thetaRepository.getPhotoCaptureBuilder().build() + +val options = ThetaRepository.Options() +options.bitrate = ThetaRepository.BitrateNumber(1048576) +thetaRepository.setOptions(options) + +photoCapture.takePicture(TakenCallback()) +``` + ## Shoot a video First, shooting settings are performed using `VideoCapture.Builder` to create `VideoCapture` objects. diff --git a/docs/tutorial-flutter.ja.md b/docs/tutorial-flutter.ja.md index 652fe243f5..4c1a9f5126 100644 --- a/docs/tutorial-flutter.ja.md +++ b/docs/tutorial-flutter.ja.md @@ -325,6 +325,39 @@ _thetaClientFlutter.getPhotoCaptureBuilder() | colorTemperature | CT settings (specified by the colorTemperature option) | RICOH THETA S firmware v01.82 or later and RICOH THETA SC firmware v01.10 or later | | underwater | Underwater | RICOH THETA V firmware v3.21.1 or later | +#### 静止画撮影でビットレートの数値を設定する場合(THETA X) + +THETA X では静止画撮影のビットレートの数値を設定することができます([api-spec](https://github.com/ricohapi/theta-api-specs/blob/main/theta-web-api-v2.1/options/_bitrate.md)参照)。 +THETA Client でこの値を設定する際は、以下の手順が必要です: + +1. `PhotoCapture`オブジェクトを生成 +1. `setOptions()`で`bitrate`を設定 +1. `takePicture()`で撮影 + +この手順が必要な理由は、`PhotoCaptureBuilder.build()`内で`captureMode`の値が`image`にセットされる際に、 +カメラに設定された`bitrate`の値がリセットされるためです。 + +```dart +_thetaClientFlutter.getPhotoCaptureBuilder() + .build() + .then((photoCapture) { + // success build photoCapture + }) + .onError((error, stackTrace) { + // handle error + }); + +final options = Options(); +options.bitrate = BitrateNumber(1048576); +await _thetaClientFlutter.setOptions(options); + +photoCapture.takePicture((fileUrl) { + // send HTTP GET request for fileUrl and receive JPEG file +}, (exception) { + // catch error while take picture +}); +``` + ## 動画を撮影する まず`getVideoCaptureBuilder()`を使って撮影設定を行い、`VideoCapture`オブジェクトを生成します。 diff --git a/docs/tutorial-flutter.md b/docs/tutorial-flutter.md index 1e6bbe6ad7..78e19e1602 100644 --- a/docs/tutorial-flutter.md +++ b/docs/tutorial-flutter.md @@ -1,4 +1,4 @@ -# THETA Client Tutorial for Flutter +# THETA Client Tutorial for Flutter ## Create a Flutter Project @@ -152,6 +152,38 @@ Next, we call `PhotoCapture.takePicture()` to shoot still pictures. }); ``` +#### Set the bitrate value for still image capture (THETA X) + +The bitrate value for still image capture can be set in THETA X(see [api-spec](https://github.com/ricohapi/theta-api-specs/blob/main/theta-web-api-v2.1/options/_bitrate.md)). +To set this value with THETA Client, follow the steps below. + +1. Create a `PhotoCapture` object +1. Call `setOptions()` to set the `bitrate` value +1. Call `takePicture()` + +The reason this step is necessary is that the `bitrate` value on the camera is reset when the `captureMode` value is set to `image` in `PhotoCaptureBuilder.build()`. + +```dart +_thetaClientFlutter.getPhotoCaptureBuilder() + .build() + .then((photoCapture) { + // success build photoCapture + }) + .onError((error, stackTrace) { + // handle error + }); + +final options = Options(); +options.bitrate = BitrateNumber(1048576); +await _thetaClientFlutter.setOptions(options); + +photoCapture.takePicture((fileUrl) { + // send HTTP GET request for fileUrl and receive JPEG file +}, (exception) { + // catch error while take picture +}); +``` + ## Shoot a video First, you set up shooting using `getVideoCaptureBuilder()` and create `VideoCapture` objects. diff --git a/docs/tutorial-ios.ja.md b/docs/tutorial-ios.ja.md index 07a5b1396e..f6373e03c7 100644 --- a/docs/tutorial-ios.ja.md +++ b/docs/tutorial-ios.ja.md @@ -297,6 +297,43 @@ do { | colorTemperature | CT settings (specified by the colorTemperature option) | RICOH THETA S firmware v01.82 or later and RICOH THETA SC firmware v01.10 or later | | underwater | Underwater | RICOH THETA V firmware v3.21.1 or later | +#### 静止画撮影でビットレートの数値を設定する場合(THETA X) + +THETA X では静止画撮影のビットレートの数値を設定することができます([api-spec](https://github.com/ricohapi/theta-api-specs/blob/main/theta-web-api-v2.1/options/_bitrate.md)参照)。 +THETA Client でこの値を設定する際は、以下の手順が必要です: + +1. `PhotoCapture`オブジェクトを生成 +1. `setOptions()`で`bitrate`を設定 +1. `takePicture()`で撮影 + +この手順が必要な理由は、`PhotoCapture.Builder.build()`内で`captureMode`の値が`image`にセットされる際に、 +カメラに設定された`bitrate`の値がリセットされるためです。 + +```swift +let photoCapture: PhotoCapture = try await withCheckedThrowingContinuation { continuation in + thetaRepository!.getPhotoCaptureBuilder().build { capture, error in + ... + } +} +try await withCheckedThrowingContinuation { continuation in + let options = ThetaRepository.Options() + options.bitrate = ThetaRepository.BitrateNumber(value: 1048576) + thetaRepository!.setOptions(options: options) { error in + ... + } +} +class Callback: PhotoCaptureTakePictureCallback { + ... +} +let photoUrl: String? = try await withCheckedThrowingContinuation { continuation in + photoCapture.takePicture( + callback: Callback { fileUrl, error in + ... + } + ) +} +``` + ## 動画を撮影する まず`ThetaRepository.getVideoCaptureBuilder()`を使って撮影設定を行い、`VideoCapture`オブジェクトを生成します。 diff --git a/docs/tutorial-ios.md b/docs/tutorial-ios.md index 8b04aadfb2..3e2cedfb16 100644 --- a/docs/tutorial-ios.md +++ b/docs/tutorial-ios.md @@ -1,4 +1,4 @@ -# THETA CLient Tutorial for iOS +# THETA CLient Tutorial for iOS ## Available models @@ -304,6 +304,42 @@ GpsInfo shall be prepared as follows: | colorTemperature | CT settings (specified by the colorTemperature option) | RICOH THETA S firmware v01.82 or later and RICOH THETA SC firmware v01.10 or later | | underwater | Underwater | RICOH THETA V firmware v3.21.1 or later | +#### Set the bitrate value for still image capture (THETA X) + +The bitrate value for still image capture can be set in THETA X(see [api-spec](https://github.com/ricohapi/theta-api-specs/blob/main/theta-web-api-v2.1/options/_bitrate.md)). +To set this value with THETA Client, follow the steps below. + +1. Create a `PhotoCapture` object +1. Call `setOptions()` to set the `_bitrate` value +1. Call `takePicture()` + +The reason this step is necessary is that the `bitrate` value on the camera is reset when the `captureMode` value is set to `image` in `PhotoCapture.Builder.build()`. + +```swift +let photoCapture: PhotoCapture = try await withCheckedThrowingContinuation { continuation in + thetaRepository!.getPhotoCaptureBuilder().build { capture, error in + ... + } +} +try await withCheckedThrowingContinuation { continuation in + let options = ThetaRepository.Options() + options.bitrate = ThetaRepository.BitrateNumber(value: 1048576) + thetaRepository!.setOptions(options: options) { error in + ... + } +} +class Callback: PhotoCaptureTakePictureCallback { + ... +} +let photoUrl: String? = try await withCheckedThrowingContinuation { continuation in + photoCapture.takePicture( + callback: Callback { fileUrl, error in + ... + } + ) +} +``` + ## Shoot a video First, use the `ThetaRepository.getVideoCaptureBuilder()` to set the shooting and create the `VideoCapture` object. diff --git a/docs/tutorial-react-native.ja.md b/docs/tutorial-react-native.ja.md index 06687f2f3a..aa660fede3 100644 --- a/docs/tutorial-react-native.ja.md +++ b/docs/tutorial-react-native.ja.md @@ -293,6 +293,24 @@ photoCapture.takePicture() | COLOR_TEMPERATURE | CT settings (specified by the colorTemperature option) | RICOH THETA S firmware v01.82 or later and RICOH THETA SC firmware v01.10 or later | | UNDERWATER | Underwater | RICOH THETA V firmware v3.21.1 or later | +#### 静止画撮影でビットレートの数値を設定する場合(THETA X) + +THETA X では静止画撮影のビットレートの数値を設定することができます([api-spec](https://github.com/ricohapi/theta-api-specs/blob/main/theta-web-api-v2.1/options/_bitrate.md)参照)。 +THETA Client でこの値を設定する際は、以下の手順が必要です: + +1. `PhotoCapture`オブジェクトを生成 +1. `setOptions()`で`bitrate`を設定 +1. `takePicture()`で撮影 + +この手順が必要な理由は、`PhotoCaptureBuilder.build()`内で`captureMode`の値が`image`にセットされる際に、 +カメラに設定された`bitrate`の値がリセットされるためです。 + +```typescript +const photoCapture = await getPhotoCaptureBuilder().build(); +await setOptions({ bitrate: 1048576 }); +const url = await photoCapture.takePicture(); +``` + ## 動画を撮影する まず`getVideoCaptureBuilder()`を使って撮影設定を行い、`VideoCapture`オブジェクトを生成します。 diff --git a/docs/tutorial-react-native.md b/docs/tutorial-react-native.md index 7be514fe6e..c6a86fdf76 100644 --- a/docs/tutorial-react-native.md +++ b/docs/tutorial-react-native.md @@ -1,4 +1,4 @@ -# THETA Client Tutorial for React Native +# THETA Client Tutorial for React Native ## Advance preparation @@ -116,6 +116,23 @@ photoCapture.takePicture() }); ``` +#### Set the bitrate value for still image capture (THETA X) + +The bitrate value for still image capture can be set in THETA X(see [api-spec](https://github.com/ricohapi/theta-api-specs/blob/main/theta-web-api-v2.1/options/_bitrate.md)). +To set this value with THETA Client, follow the steps below. + +1. Create a `PhotoCapture` object +1. Call `setOptions()` to set the `bitrate` value +1. Call `takePicture()` + +The reason this step is necessary is that the `bitrate` value on the camera is reset when the `captureMode` value is set to `image` in `PhotoCaptureBuilder.build()`. + +```typescript +const photoCapture = await getPhotoCaptureBuilder().build(); +await setOptions({ bitrate: 1048576 }); +const url = await photoCapture.takePicture(); +``` + ## Shoot a video First, you set up shooting using `getVideoCaptureBuilder()` and create `VideoCapture` objects. diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt index 2144797fb5..a93783f55d 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt @@ -26,8 +26,8 @@ import io.ktor.utils.io.core.toByteArray import io.ktor.utils.io.discard import io.ktor.utils.io.writeFully import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.withTimeout -import kotlin.coroutines.coroutineContext /** * http client interface for preview only @@ -192,7 +192,7 @@ internal class PreviewClientImpl : PreviewClient { selector = SelectorManager(Dispatchers.Default) val builder = aSocket(selector!!).tcpNoDelay().tcp() val self = this - val context = coroutineContext + val context = currentCoroutineContext() withTimeout(connectionTimeout) { val socket = builder.connect( InetSocketAddress(url.host, url.port), From 2886b2717163ee07fd6590ed10e2840f7442991c Mon Sep 17 00:00:00 2001 From: osakila Date: Wed, 3 Apr 2024 09:51:31 +0900 Subject: [PATCH 8/9] Fix memory leak --- demos/demo-android/app/build.gradle | 19 ++++++++++--------- .../thetaClientDemo/ThetaViewModel.kt | 18 ++++++++++++++++-- demos/demo-android/build.gradle | 2 +- .../com/ricoh360/thetaclient/ApiClient.kt | 1 + .../com/ricoh360/thetaclient/PreviewClient.kt | 2 +- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/demos/demo-android/app/build.gradle b/demos/demo-android/app/build.gradle index 75b2417ee4..d4be47a7d9 100755 --- a/demos/demo-android/app/build.gradle +++ b/demos/demo-android/app/build.gradle @@ -56,24 +56,25 @@ android { namespace 'com.ricoh360.thetaclient.thetaClientDemo' } + dependencies { + def lifecycle_version = "2.7.0" implementation 'androidx.core:core-ktx:1.12.0' implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.material:material:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2' - implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2' - implementation 'androidx.activity:activity-compose:1.8.0' - implementation "androidx.navigation:navigation-compose:2.7.5" - implementation 'androidx.webkit:webkit:1.8.0' + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version" + implementation 'androidx.activity:activity-compose:1.8.2' + implementation "androidx.navigation:navigation-compose:2.7.7" + implementation 'androidx.webkit:webkit:1.10.0' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" - implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0' implementation 'com.jakewharton.timber:timber:5.0.1' implementation 'io.coil-kt:coil-compose:2.2.2' - implementation "io.ktor:ktor-client-cio:2.1.3" - implementation "com.ricoh360.thetaclient:theta-client:1.7.1" + // implementation "com.ricoh360.thetaclient:theta-client:1.7.1" 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/ThetaViewModel.kt b/demos/demo-android/app/src/main/java/com/ricoh360/thetaclient/thetaClientDemo/ThetaViewModel.kt index 6dddd1078c..6798b6be44 100755 --- a/demos/demo-android/app/src/main/java/com/ricoh360/thetaclient/thetaClientDemo/ThetaViewModel.kt +++ b/demos/demo-android/app/src/main/java/com/ricoh360/thetaclient/thetaClientDemo/ThetaViewModel.kt @@ -12,6 +12,7 @@ import io.ktor.utils.io.streams.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import timber.log.Timber /** @@ -31,7 +32,7 @@ class ThetaViewModel( val thetaFilesState: StateFlow = _thetaFilesState private val _previewFlow = MutableStateFlow(null) - val previewFlow: StateFlow = _previewFlow + val previewFlow: StateFlow = _previewFlow.asStateFlow() private var previewJob: Job? = null init { @@ -70,11 +71,24 @@ class ThetaViewModel( } previewJob = viewModelScope.launch(ioDispatcher) { kotlin.runCatching { + var beforeBitmap: Bitmap? = null thetaRepository.getLivePreview() .collect { byteReadPacket -> ensureActive() byteReadPacket.inputStream().use { - _previewFlow.emit(BitmapFactory.decodeStream(it)) + val options = BitmapFactory.Options() + options.inMutable = true + val bitmap = BitmapFactory.decodeStream(it, null, options) + _previewFlow.emit(bitmap) + // Explicitly release the bitmap since it may not be released by the cpu load. + viewModelScope.launch(Dispatchers.Main) { + beforeBitmap?.let { + if (!it.isRecycled) { + it.recycle() + } + } + beforeBitmap = bitmap + } } byteReadPacket.release() } diff --git a/demos/demo-android/build.gradle b/demos/demo-android/build.gradle index d5c47dd963..4bcac071ac 100755 --- a/demos/demo-android/build.gradle +++ b/demos/demo-android/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - compose_version = '1.5.3' // depends on Kotlin 1.7.10 + compose_version = '1.5.3' // depends on Kotlin 1.9.10 kotlin_version = '1.9.10' coroutines_version = '1.7.3' } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ApiClient.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ApiClient.kt index 126e8767da..34b393d334 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ApiClient.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ApiClient.kt @@ -19,6 +19,7 @@ internal object ApiClient { var timeout = ThetaRepository.Timeout() set(value) { field = value + httpClient?.close() httpClient = null } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt index a93783f55d..7fd24fa812 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt @@ -188,7 +188,6 @@ internal class PreviewClientImpl : PreviewClient { * connect to [endpoint] */ private suspend fun connect(url: URL): PreviewClientImpl { - reset() selector = SelectorManager(Dispatchers.Default) val builder = aSocket(selector!!).tcpNoDelay().tcp() val self = this @@ -459,6 +458,7 @@ internal class PreviewClientImpl : PreviewClient { contentType: String, digest: String? = null, ): PreviewClient { + close() // To prevent resource leaks val url = URL(endpoint) connect(url) write("$method $path HTTP/1.1\r\n") From ebaad524c31478a46aaf67cbbb2514886561665e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E5=B2=A1=E3=80=80=E4=BE=91=E5=87=9B?= <129148471+LassicYM@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:28:15 +0900 Subject: [PATCH 9/9] update version 1.8.0 and formatted code. --- demos/demo-android/app/build.gradle | 2 +- demos/demo-ios/Podfile | 2 +- demos/demo-react-native/package.json | 2 +- docs/tutorial-android.ja.md | 2 +- docs/tutorial-android.md | 2 +- flutter/android/build.gradle | 2 +- flutter/ios/Classes/ConvertUtil.swift | 2 +- flutter/ios/theta_client_flutter.podspec | 4 +-- flutter/lib/state/theta_state.dart | 3 +-- flutter/lib/utils/convert_utils.dart | 2 +- flutter/pubspec.yaml | 2 +- kotlin-multiplatform/build.gradle.kts | 2 +- .../com/ricoh360/thetaclient/PreviewClient.kt | 1 + .../com/ricoh360/thetaclient/ThetaApi.kt | 4 +-- .../ricoh360/thetaclient/ThetaRepository.kt | 2 +- .../thetaclient/transferred/serializer.kt | 1 + .../thetaclient/transferred/stateApi.kt | 2 +- .../com/ricoh360/thetaclient/CheckRequest.kt | 1 - .../com/ricoh360/thetaclient/PreviewTest.kt | 1 - .../thetaclient/capture/BurstCaptureTest.kt | 27 ++++++++++++++----- .../capture/CompositeIntervalCaptureTest.kt | 19 ++++++++----- .../capture/MultiBracketCaptureTest.kt | 15 ++++++++--- .../ShotCountSpecifiedIntervalCaptureTest.kt | 19 ++++++++----- .../capture/TimeShiftCaptureTest.kt | 19 ++++++++----- .../repository/ConvertVideoFormatsTest.kt | 1 - .../repository/DeleteAccessPointTest.kt | 1 - .../thetaclient/repository/DeleteFilesTest.kt | 1 - .../thetaclient/repository/GetMetadataTest.kt | 1 - .../thetaclient/repository/GetOptionsTest.kt | 7 ++--- .../thetaclient/repository/ListFilesTest.kt | 1 - .../repository/SetAccessPointTest.kt | 1 - .../thetaclient/repository/SetOptionsTest.kt | 7 ++--- react-native/android/build.gradle | 2 +- react-native/ios/ConvertUtil.swift | 2 +- react-native/package.json | 2 +- .../theta-client-react-native.podspec | 2 +- 36 files changed, 102 insertions(+), 64 deletions(-) diff --git a/demos/demo-android/app/build.gradle b/demos/demo-android/app/build.gradle index d4be47a7d9..9da7bcab0d 100755 --- a/demos/demo-android/app/build.gradle +++ b/demos/demo-android/app/build.gradle @@ -74,7 +74,7 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0' implementation 'com.jakewharton.timber:timber:5.0.1' implementation 'io.coil-kt:coil-compose:2.2.2' - // implementation "com.ricoh360.thetaclient:theta-client:1.7.1" + implementation "com.ricoh360.thetaclient:theta-client:1.8.0" testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0' testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" diff --git a/demos/demo-ios/Podfile b/demos/demo-ios/Podfile index 6e3f5dd3da..c3c1d1130f 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.7.1' + pod 'THETAClient', '1.8.0' end diff --git a/demos/demo-react-native/package.json b/demos/demo-react-native/package.json index 666e08e96f..cb9f6425e1 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.7.1", + "theta-client-react-native": "1.8.0", "react": "18.2.0", "react-native": "0.71.14", "react-native-safe-area-context": "^4.4.1", diff --git a/docs/tutorial-android.ja.md b/docs/tutorial-android.ja.md index 6ff8bfe961..ca82f82ced 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.7.1" + implementation "com.ricoh360.thetaclient:theta-client:1.8.0" ``` - 本 SDK を使用したアプリケーションが動作するスマートフォンと THETA を無線 LAN 接続しておきます。 diff --git a/docs/tutorial-android.md b/docs/tutorial-android.md index ee4b7b2bbf..d7cec4466c 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.7.1" + implementation "com.ricoh360.thetaclient:theta-client:1.8.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 dd823c402b..6b7a256b53 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.7.1") + implementation("com.ricoh360.thetaclient:theta-client:1.8.0") } diff --git a/flutter/ios/Classes/ConvertUtil.swift b/flutter/ios/Classes/ConvertUtil.swift index 83a2877788..42bb705862 100644 --- a/flutter/ios/Classes/ConvertUtil.swift +++ b/flutter/ios/Classes/ConvertUtil.swift @@ -151,7 +151,7 @@ func convertResult(thetaState: ThetaRepository.ThetaState) -> [String: Any?] { "isBatteryInsert": convertKotlinBooleanToBool(value: thetaState.isBatteryInsert), KEY_STATE_BOARD_TEMP: thetaState.boardTemp, KEY_STATE_BATTERY_TEMP: thetaState.batteryTemp, - ] as [String : Any?] + ] as [String: Any?] if let externalGpsInfo = thetaState.externalGpsInfo { result[KEY_STATE_EXTERNAL_GPS_INFO] = convertResult(stateGpsInfo: externalGpsInfo) diff --git a/flutter/ios/theta_client_flutter.podspec b/flutter/ios/theta_client_flutter.podspec index 1e618d5993..d3ee015ddc 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.7.1' + s.version = '1.8.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.7.1' + s.dependency 'THETAClient', '1.8.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/state/theta_state.dart b/flutter/lib/state/theta_state.dart index c77ebcfab0..75f0ece558 100644 --- a/flutter/lib/state/theta_state.dart +++ b/flutter/lib/state/theta_state.dart @@ -75,8 +75,7 @@ class ThetaState { /// This represents the current temperature inside the battery as an integer value, ranging from -10°C to 100°C with a precision of 1°C. int? batteryTemp; - ThetaState( - this.fingerprint, + ThetaState(this.fingerprint, this.batteryLevel, this.storageUri, this.storageID, diff --git a/flutter/lib/utils/convert_utils.dart b/flutter/lib/utils/convert_utils.dart index 43165ef3e9..014764e0b6 100644 --- a/flutter/lib/utils/convert_utils.dart +++ b/flutter/lib/utils/convert_utils.dart @@ -1,8 +1,8 @@ import 'package:theta_client_flutter/digest_auth.dart'; import 'package:theta_client_flutter/theta_client_flutter.dart'; -import '../state/theta_state.dart'; import '../state/state_gps_info.dart'; +import '../state/theta_state.dart'; class ConvertUtils { static List? convertAutoBracketOption(List? data) { diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 2df5d427d4..35ed11c87c 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.7.1 +version: 1.8.0 homepage: environment: diff --git a/kotlin-multiplatform/build.gradle.kts b/kotlin-multiplatform/build.gradle.kts index c343ac4379..9532d9b306 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.7.1" +val thetaClientVersion = "1.8.0" group = "com.ricoh360.thetaclient" version = thetaClientVersion diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt index 7fd24fa812..2f1aef57af 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/PreviewClient.kt @@ -203,6 +203,7 @@ internal class PreviewClientImpl : PreviewClient { "https" -> it.tls(context) { serverName = url.host } + else -> it } } diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt index ccd244a6e1..1e3001407a 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaApi.kt @@ -622,7 +622,7 @@ internal object ThetaApi { params: SetOptionsParams, ): SetOptionsResponse { val request = SetOptionsRequest(parameters = params) - val response: SetOptionsResponse = postCommandApi(endpoint, request).body() + val response: SetOptionsResponse = postCommandApi(endpoint, request).body() if (response.state == CommandState.DONE) { updateConsumingOptions(params.options) } @@ -875,7 +875,7 @@ internal object ThetaApi { /** * Post request {body} to {endpoint} APIs then return its response */ - private suspend inline fun postCommandApi( + private suspend inline fun postCommandApi( endpoint: String, body: T, ): HttpResponse { 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 15167ceb88..118c460776 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/ThetaRepository.kt @@ -3708,7 +3708,7 @@ class ThetaRepository internal constructor(val endpoint: String, config: Config? /** * GPS information of state */ - data class StateGpsInfo ( + data class StateGpsInfo( /** * GPS information */ diff --git a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/serializer.kt b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/serializer.kt index 11ed8c9925..ef907460b1 100644 --- a/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/serializer.kt +++ b/kotlin-multiplatform/src/commonMain/kotlin/com/ricoh360/thetaclient/transferred/serializer.kt @@ -208,6 +208,7 @@ internal abstract class EnumIgnoreUnknownSerializer>( println("Web API unknown value. ${defaultValue::class.simpleName}: $decodeString") defaultValue } + else -> value } } 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 cc867ca799..eeebfd5bd8 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 @@ -563,7 +563,7 @@ internal enum class CameraError { * GPS information of state */ @Serializable -internal data class StateGpsInfo ( +internal data class StateGpsInfo( /** * GPS information */ diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/CheckRequest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/CheckRequest.kt index f774243050..802f37ea70 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/CheckRequest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/CheckRequest.kt @@ -5,7 +5,6 @@ import io.ktor.client.request.* import io.ktor.http.content.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlin.test.assertEquals diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/PreviewTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/PreviewTest.kt index c0892f6f21..d56ccdac0e 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/PreviewTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/PreviewTest.kt @@ -7,7 +7,6 @@ import io.ktor.http.content.* import io.ktor.utils.io.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlin.test.* 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 4a195ac4d4..8142eb4378 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 @@ -4,14 +4,29 @@ import com.goncalossilva.resources.Resource import com.ricoh360.thetaclient.CheckRequest import com.ricoh360.thetaclient.MockApiClient import com.ricoh360.thetaclient.ThetaRepository -import com.ricoh360.thetaclient.transferred.* -import io.ktor.client.network.sockets.* -import io.ktor.http.* +import com.ricoh360.thetaclient.transferred.BurstBracketStep +import com.ricoh360.thetaclient.transferred.BurstCaptureNum +import com.ricoh360.thetaclient.transferred.BurstCompensation +import com.ricoh360.thetaclient.transferred.BurstEnableIsoControl +import com.ricoh360.thetaclient.transferred.BurstMaxExposureTime +import com.ricoh360.thetaclient.transferred.BurstMode +import com.ricoh360.thetaclient.transferred.BurstOption +import com.ricoh360.thetaclient.transferred.BurstOrder +import com.ricoh360.thetaclient.transferred.CaptureMode +import io.ktor.client.network.sockets.ConnectTimeoutException +import io.ktor.http.HttpStatusCode import io.ktor.http.content.TextContent -import io.ktor.utils.io.* -import kotlinx.coroutines.* +import io.ktor.utils.io.ByteReadChannel +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest -import kotlin.test.* +import kotlinx.coroutines.withTimeout +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue class BurstCaptureTest { private val burstOption = BurstOption( 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 aec253bf04..2cb4b53cfb 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 @@ -5,13 +5,20 @@ import com.ricoh360.thetaclient.CheckRequest import com.ricoh360.thetaclient.MockApiClient import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode -import io.ktor.client.network.sockets.* -import io.ktor.http.* -import io.ktor.http.content.* -import io.ktor.utils.io.* -import kotlinx.coroutines.* +import io.ktor.client.network.sockets.ConnectTimeoutException +import io.ktor.http.HttpStatusCode +import io.ktor.http.content.TextContent +import io.ktor.utils.io.ByteReadChannel +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest -import kotlin.test.* +import kotlinx.coroutines.withTimeout +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue class CompositeIntervalCaptureTest { private val endpoint = "http://192.168.1.1:80/" 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 7f235469be..b22099071b 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 @@ -5,12 +5,19 @@ import com.ricoh360.thetaclient.CheckRequest import com.ricoh360.thetaclient.MockApiClient import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode -import io.ktor.http.* +import io.ktor.http.HttpStatusCode import io.ktor.http.content.TextContent -import io.ktor.utils.io.* -import kotlinx.coroutines.* +import io.ktor.utils.io.ByteReadChannel +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest -import kotlin.test.* +import kotlinx.coroutines.withTimeout +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue class MultiBracketCaptureTest { 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 6c546d3866..9658e60bf6 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 @@ -5,13 +5,20 @@ import com.ricoh360.thetaclient.CheckRequest import com.ricoh360.thetaclient.MockApiClient import com.ricoh360.thetaclient.ThetaRepository import com.ricoh360.thetaclient.transferred.CaptureMode -import io.ktor.client.network.sockets.* -import io.ktor.http.* -import io.ktor.http.content.* -import io.ktor.utils.io.* -import kotlinx.coroutines.* +import io.ktor.client.network.sockets.ConnectTimeoutException +import io.ktor.http.HttpStatusCode +import io.ktor.http.content.TextContent +import io.ktor.utils.io.ByteReadChannel +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest -import kotlin.test.* +import kotlinx.coroutines.withTimeout +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue class ShotCountSpecifiedIntervalCaptureTest { private val endpoint = "http://192.168.1.1:80/" 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 a74f826c45..766e34c796 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 @@ -8,13 +8,20 @@ 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.* -import io.ktor.http.* -import io.ktor.http.content.* -import io.ktor.utils.io.* -import kotlinx.coroutines.* +import io.ktor.client.network.sockets.ConnectTimeoutException +import io.ktor.http.HttpStatusCode +import io.ktor.http.content.TextContent +import io.ktor.utils.io.ByteReadChannel +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest -import kotlin.test.* +import kotlinx.coroutines.withTimeout +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue class TimeShiftCaptureTest { private val endpoint = "http://192.168.1.1:80/" diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ConvertVideoFormatsTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ConvertVideoFormatsTest.kt index 9228a38ba3..6fc401c8c4 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ConvertVideoFormatsTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ConvertVideoFormatsTest.kt @@ -15,7 +15,6 @@ import io.ktor.utils.io.ByteReadChannel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlin.test.AfterTest import kotlin.test.BeforeTest diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/DeleteAccessPointTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/DeleteAccessPointTest.kt index 523d8512e3..53161131f7 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/DeleteAccessPointTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/DeleteAccessPointTest.kt @@ -12,7 +12,6 @@ import io.ktor.utils.io.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlin.test.* diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/DeleteFilesTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/DeleteFilesTest.kt index 8b8051633c..cc973bff23 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/DeleteFilesTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/DeleteFilesTest.kt @@ -12,7 +12,6 @@ import io.ktor.utils.io.ByteReadChannel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlin.test.AfterTest import kotlin.test.BeforeTest diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetMetadataTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetMetadataTest.kt index 4690310862..892e497dc4 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetMetadataTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetMetadataTest.kt @@ -12,7 +12,6 @@ import io.ktor.utils.io.ByteReadChannel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlin.test.AfterTest import kotlin.test.BeforeTest diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetOptionsTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetOptionsTest.kt index 0c5f567498..28ba8d63f6 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetOptionsTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/GetOptionsTest.kt @@ -15,7 +15,6 @@ import io.ktor.utils.io.ByteReadChannel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlin.test.AfterTest import kotlin.test.BeforeTest @@ -287,14 +286,16 @@ class GetOptionsTest { try { thetaRepository.getOptions(listOf(ThetaRepository.OptionNameEnum.Filter)) assertTrue(false, "response is normal.") - } catch (_: ThetaRepository.ThetaWebApiException) { } + } catch (_: ThetaRepository.ThetaWebApiException) { + } assertNull(ThetaApi.currentOptions._filter, "_filter") assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "_filter") try { thetaRepository.getOptions(listOf(ThetaRepository.OptionNameEnum.CaptureMode)) assertTrue(false, "response is normal.") - } catch (_: ThetaRepository.ThetaWebApiException) { } + } catch (_: ThetaRepository.ThetaWebApiException) { + } assertNull(ThetaApi.currentOptions.captureMode, "captureMode") assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "captureMode") } diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ListFilesTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ListFilesTest.kt index fb16190371..0888d95d5f 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ListFilesTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/ListFilesTest.kt @@ -15,7 +15,6 @@ import io.ktor.utils.io.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlin.test.* diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/SetAccessPointTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/SetAccessPointTest.kt index b15a831b49..8df071618f 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/SetAccessPointTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/SetAccessPointTest.kt @@ -14,7 +14,6 @@ import io.ktor.utils.io.ByteReadChannel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlin.test.AfterTest import kotlin.test.BeforeTest diff --git a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/SetOptionsTest.kt b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/SetOptionsTest.kt index be153a43b3..1cd3e54cf1 100644 --- a/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/SetOptionsTest.kt +++ b/kotlin-multiplatform/src/commonTest/kotlin/com/ricoh360/thetaclient/repository/SetOptionsTest.kt @@ -26,7 +26,6 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withTimeout import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlin.test.AfterTest @@ -413,7 +412,8 @@ class SetOptionsTest { try { thetaRepository.setOptions(ThetaRepository.Options(filter = ThetaRepository.FilterEnum.HDR)) assertTrue(false, "response is normal.") - } catch (_: ThetaRepository.ThetaWebApiException) { } + } catch (_: ThetaRepository.ThetaWebApiException) { + } assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "no change") thetaRepository = ThetaRepository(endpoint) @@ -421,7 +421,8 @@ class SetOptionsTest { thetaRepository.setOptions(ThetaRepository.Options(captureMode = ThetaRepository.CaptureModeEnum.IMAGE)) assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "same option") assertTrue(false, "response is normal.") - } catch (_: ThetaRepository.ThetaWebApiException) { } + } catch (_: ThetaRepository.ThetaWebApiException) { + } assertEquals(ThetaApi.lastSetTimeConsumingOptionTime, 0, "no change") } } diff --git a/react-native/android/build.gradle b/react-native/android/build.gradle index 903db1dff4..ae28cf09f6 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.7.1" + implementation "com.ricoh360.thetaclient:theta-client:1.8.0" // From node_modules } diff --git a/react-native/ios/ConvertUtil.swift b/react-native/ios/ConvertUtil.swift index 0453fbcd8e..1bb9ee6e4a 100644 --- a/react-native/ios/ConvertUtil.swift +++ b/react-native/ios/ConvertUtil.swift @@ -848,7 +848,7 @@ func convertResult(thetaState: ThetaRepository.ThetaState) -> [String: Any?] { "isBatteryInsert": convertKotlinBooleanToBool(value: thetaState.isBatteryInsert), KEY_STATE_BOARD_TEMP: thetaState.boardTemp, KEY_STATE_BATTERY_TEMP: thetaState.batteryTemp, - ] as [String : Any?] + ] as [String: Any?] if let externalGpsInfo = thetaState.externalGpsInfo { result[KEY_STATE_EXTERNAL_GPS_INFO] = convertResult(stateGpsInfo: externalGpsInfo) diff --git a/react-native/package.json b/react-native/package.json index b32aa96842..71a1b7cd25 100644 --- a/react-native/package.json +++ b/react-native/package.json @@ -1,6 +1,6 @@ { "name": "theta-client-react-native", - "version": "1.7.1", + "version": "1.8.0", "description": "This library provides a way to control RICOH THETA using.", "main": "lib/commonjs/index", "module": "lib/module/index", diff --git a/react-native/theta-client-react-native.podspec b/react-native/theta-client-react-native.podspec index 049c6ccf09..e1029c34a0 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.7.1" + s.dependency "THETAClient", "1.8.0" # Don't install the dependencies when we run `pod install` in the old architecture. if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then