Skip to content

Commit 070ae9b

Browse files
committed
feat: Update localization strings and add connection status handling in UI components
1 parent 43838bc commit 070ae9b

File tree

7 files changed

+243
-44
lines changed

7 files changed

+243
-44
lines changed

app/xcode/Sources/VCamLocalization/Generated.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ public enum L10n {
110110
public static let confirmSignOut = LocalizedString(lookupKey: "confirmSignOut")
111111
/// Connect
112112
public static let connect = LocalizedString(lookupKey: "connect")
113-
/// Connecting
113+
/// Connected
114+
public static let connected = LocalizedString(lookupKey: "connected")
115+
/// Connecting...
114116
public static let connecting = LocalizedString(lookupKey: "connecting")
115117
/// Contrast
116118
public static let contrast = LocalizedString(lookupKey: "contrast")
@@ -148,6 +150,8 @@ public enum L10n {
148150
public static let diffusion = LocalizedString(lookupKey: "diffusion")
149151
/// Disconnect
150152
public static let disconnect = LocalizedString(lookupKey: "disconnect")
153+
/// Disconnected
154+
public static let disconnected = LocalizedString(lookupKey: "disconnected")
151155
/// Display
152156
public static let display = LocalizedString(lookupKey: "display")
153157
/// https://docs.vcamapp.com/v/en/virtual-camera/virtualcamera

app/xcode/Sources/VCamLocalization/VCamResources/en.lproj/Localizable.strings

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,10 @@
249249

250250
// MARK: - Integration
251251
"connect" = "Connect";
252-
"connecting" = "Connecting";
252+
"connecting" = "Connecting...";
253+
"connected" = "Connected";
253254
"disconnect" = "Disconnect";
255+
"disconnected" = "Disconnected";
254256
"helpMocopIP" = "The port in the PC connection settings on the mocopi side should be \"12351\".";
255257

256258
// MARK: - License

app/xcode/Sources/VCamLocalization/VCamResources/ja.lproj/Localizable.strings

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,10 @@
249249

250250
// MARK: - Integration
251251
"connect" = "接続";
252-
"connecting" = "接続中";
252+
"connecting" = "接続中...";
253+
"connected" = "接続済";
253254
"disconnect" = "接続解除";
255+
"disconnected" = "未接続";
254256
"helpMocopIP" = "mocopi側のPC接続設定でのポートは「12351」にしてください";
255257

256258
// MARK: - License
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// ConnectionStatus.swift
3+
//
4+
//
5+
// Created by Tatsuya Tanaka on 2023/01/02.
6+
//
7+
8+
public enum ConnectionStatus {
9+
case disconnected
10+
case connecting
11+
case connected
12+
}

app/xcode/Sources/VCamTracking/FacialMocapReceiver.swift

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Network
99
import Combine
1010
import VCamBridge
1111
import Accelerate
12+
import VCamLogger
1213

1314
@Observable
1415
public final class FacialMocapReceiver {
@@ -22,6 +23,12 @@ public final class FacialMocapReceiver {
2223

2324
@MainActor public private(set) var connectionStatus = ConnectionStatus.disconnected
2425

26+
@ObservationIgnored private var shouldAutoReconnect = true
27+
@ObservationIgnored private var lastConnectedIP: String?
28+
@ObservationIgnored private var timeoutTask: Task<Void, Never>?
29+
30+
private static let dataTimeoutSeconds: UInt64 = 2
31+
2532
private final class SmoothingBox {
2633
var smoothing: TrackingSmoothing
2734

@@ -30,12 +37,6 @@ public final class FacialMocapReceiver {
3037
}
3138
}
3239

33-
public enum ConnectionStatus {
34-
case disconnected
35-
case connecting
36-
case connected
37-
}
38-
3940
enum ReceiverResult {
4041
case success
4142
case cancel
@@ -60,7 +61,9 @@ public final class FacialMocapReceiver {
6061

6162
@MainActor
6263
public func connect(ip: String) async throws {
63-
await stop()
64+
await stopInternal()
65+
shouldAutoReconnect = true
66+
lastConnectedIP = ip
6467

6568
#if FEATURE_3
6669
let port = NWEndpoint.Port(integerLiteral: 49983)
@@ -72,45 +75,90 @@ public final class FacialMocapReceiver {
7275
switch result {
7376
case .success: ()
7477
case .cancel, .error:
75-
self?.stopAsync()
78+
Task { @MainActor in
79+
await self?.handleDisconnection()
80+
}
7681
}
7782
}
7883

7984
requestConnection(ip: ip, port: port) { [weak self] result in
8085
switch result {
8186
case .success: ()
8287
case .cancel, .error:
83-
self?.stopAsync()
88+
Task { @MainActor in
89+
await self?.handleDisconnection()
90+
}
8491
}
8592
}
8693
}
8794

8895
@MainActor
8996
public func stop() async {
97+
shouldAutoReconnect = false
98+
await stopInternal()
99+
}
100+
101+
@MainActor
102+
private func stopInternal() async {
103+
timeoutTask?.cancel()
104+
timeoutTask = nil
105+
90106
if let listener = listener {
91107
listener.stateUpdateHandler = nil
92108
listener.newConnectionHandler = nil
93109
listener.cancel()
94110
self.listener = nil
95111
}
96112

113+
connection?.stateUpdateHandler = nil
97114
connection?.cancel()
98115
connection = nil
99116
connectionStatus = .disconnected
100117

101118
stopResamplers()
102119
}
103120

121+
@MainActor
122+
private func resetTimeoutTimer() {
123+
timeoutTask?.cancel()
124+
timeoutTask = Task { @MainActor [weak self] in
125+
do {
126+
try await Task.sleep(nanoseconds: Self.dataTimeoutSeconds * NSEC_PER_SEC)
127+
Logger.log("Data timeout - resetting listener")
128+
await self?.handleTimeout()
129+
} catch {}
130+
}
131+
}
132+
133+
@MainActor
134+
private func handleTimeout() async {
135+
guard connectionStatus == .connected, shouldAutoReconnect, let ip = lastConnectedIP else { return }
136+
await stopInternal()
137+
do {
138+
try await connect(ip: ip)
139+
} catch {
140+
Logger.log("Restart failed: \(error.localizedDescription)")
141+
}
142+
}
143+
104144
func updateSmoothing(_ smoothing: TrackingSmoothing) {
105145
smoothingBox.smoothing = smoothing
106146
if !smoothing.isEnabled {
107147
stopResamplers()
108148
}
109149
}
110150

111-
private func stopAsync() {
112-
Task {
113-
await self.stop()
151+
@MainActor
152+
private func handleDisconnection() async {
153+
guard shouldAutoReconnect, let ip = lastConnectedIP else {
154+
await stopInternal()
155+
return
156+
}
157+
await stopInternal()
158+
do {
159+
try await connect(ip: ip)
160+
} catch {
161+
Logger.log("Restart failed: \(error.localizedDescription)")
114162
}
115163
}
116164

@@ -148,10 +196,13 @@ public final class FacialMocapReceiver {
148196
}
149197

150198
private extension NWConnection {
151-
func receiveData(with oniFacialMocapReceived: @escaping (FacialMocapData) -> Void) {
199+
func receiveData(
200+
with oniFacialMocapReceived: @escaping (FacialMocapData) -> Void,
201+
onDataReceived: @escaping () -> Void
202+
) {
152203
receive(minimumIncompleteLength: 1, maximumLength: 8192) { [weak self] content, contentContext, isComplete, error in
153204
defer {
154-
self?.receiveData(with: oniFacialMocapReceived)
205+
self?.receiveData(with: oniFacialMocapReceived, onDataReceived: onDataReceived)
155206
}
156207

157208
guard error == nil,
@@ -160,7 +211,7 @@ private extension NWConnection {
160211
let mocapData = FacialMocapData(rawData: rawData) else {
161212
return
162213
}
163-
214+
onDataReceived()
164215
oniFacialMocapReceived(mocapData)
165216
}
166217
}
@@ -194,17 +245,26 @@ extension FacialMocapReceiver {
194245
switch state {
195246
case .setup, .preparing: ()
196247
case .waiting(let error):
248+
Logger.log("Connection waiting: \(error.localizedDescription)")
197249
if case .posix(let posixError) = error, posixError == .ECONNREFUSED {
198250
try? await Task.sleep(nanoseconds: NSEC_PER_SEC * 2)
199251
try? await self.startServer(port: port, completion: completion)
200252
}
201253
case .ready:
254+
Logger.log("Connection ready")
202255
self.connectionStatus = .connected
203-
connection.receiveData(with: self.oniFacialMocapReceived)
256+
self.resetTimeoutTimer()
257+
connection.receiveData(with: self.oniFacialMocapReceived, onDataReceived: { [weak self] in
258+
Task { @MainActor in
259+
self?.resetTimeoutTimer()
260+
}
261+
})
204262
case .cancelled:
205-
self.stopAsync()
206-
case .failed:
207-
try? await self.startServer(port: port, completion: completion)
263+
Logger.log("Connection cancelled")
264+
await self.handleDisconnection()
265+
case .failed(let error):
266+
Logger.log("Connection failed: \(error.localizedDescription)")
267+
await self.handleDisconnection()
208268
@unknown default: ()
209269
}
210270
}

0 commit comments

Comments
 (0)