From f5d25ba346c7b31f4698254267d04606250bab0c Mon Sep 17 00:00:00 2001 From: Vladislav Alekseev Date: Tue, 21 Feb 2023 21:14:59 +0000 Subject: [PATCH] Add SimulatorVideoRecorder --- Package.swift | 6 +- README.md | 5 ++ Sources/.DS_Store | Bin 6148 -> 0 bytes .../CancellableRecording.swift | 14 +++++ .../CancellableRecordingImpl.swift | 36 ++++++++++++ .../SimulatorVideoRecorder.swift | 55 ++++++++++++++++++ 6 files changed, 115 insertions(+), 1 deletion(-) delete mode 100644 Sources/.DS_Store create mode 100644 Sources/SimulatorVideoRecorder/CancellableRecording.swift create mode 100644 Sources/SimulatorVideoRecorder/CancellableRecordingImpl.swift create mode 100644 Sources/SimulatorVideoRecorder/SimulatorVideoRecorder.swift diff --git a/Package.swift b/Package.swift index e0a5778..eef4ae9 100644 --- a/Package.swift +++ b/Package.swift @@ -26,10 +26,14 @@ let package = Package( .product(name: "Starscream", package: "Starscream"), .product(name: "SynchronousWaiter", package: "CommandLineToolkit"), "EmceePluginModels", + "SimulatorVideoRecorder", ] ), .target( name: "EmceePluginModels" - ) + ), + .target( + name: "SimulatorVideoRecorder" + ), ] ) diff --git a/README.md b/README.md index a627510..6a64a3b 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,8 @@ exit(try main()) ``` `Plugin` class will automatically stop streaming events when web socket gets disconnected, allowing `join()` method to return. + +## Helpers + +You can use `SimulatorVideoRecorder` to capture the video of the simulator. +You should cancel all ongoing recording after you receive `didRun` event. diff --git a/Sources/.DS_Store b/Sources/.DS_Store deleted file mode 100644 index 3fa22d299559c31885df577bd2847daa6d44aa1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~&r1S96vyAFshb1_MI8sa3Rz$oT^7@05N*_X z_s)|TIK5V{AJQQAhvhv$|`=^)9=kO&GZ;B?v!$;bN*0H=} zVb0>bxE;DUS#q7@^6GS>P(+XDK?7{grUd68z(aB&#H&GUfrb90w}AUElEmED@(Ji| zlP@fPDrN?TfFbbD2+;Y!F_GF@6Q%O#z)U^?P&3#p4a@YFpct;z)|x2A9yF#xQB^3@ zPYkBQv7Ku_TWg|Jg#*)%52iPJn0+R&Ns#>J`zcK&)KN)0d zhJYckQUqA0?$m3zBz?C|EspM5j_Cmt8TlnjWe8^aI+g{xigz(d!#++0R9kDJ6kE{D O9|4KMWQM?>68Hk9?4^?c diff --git a/Sources/SimulatorVideoRecorder/CancellableRecording.swift b/Sources/SimulatorVideoRecorder/CancellableRecording.swift new file mode 100644 index 0000000..06f84b4 --- /dev/null +++ b/Sources/SimulatorVideoRecorder/CancellableRecording.swift @@ -0,0 +1,14 @@ +/* + * Copyright (c) Avito Tech LLC + */ + +import Foundation +import PathLib + +public protocol CancellableRecording { + /// Stops recording and returns a path to a file where video is stored. + func stopRecording() -> AbsolutePath + + /// Cancels recording and does not write any data to the file. Thus, does not return any path. + func cancelRecording() +} diff --git a/Sources/SimulatorVideoRecorder/CancellableRecordingImpl.swift b/Sources/SimulatorVideoRecorder/CancellableRecordingImpl.swift new file mode 100644 index 0000000..7744b1f --- /dev/null +++ b/Sources/SimulatorVideoRecorder/CancellableRecordingImpl.swift @@ -0,0 +1,36 @@ +/* + * Copyright (c) Avito Tech LLC + */ + +import Foundation +import PathLib +import ProcessController + +class CancellableRecordingImpl: CancellableRecording { + private let outputPath: AbsolutePath + private let recordingProcess: ProcessController + + public init( + outputPath: AbsolutePath, + recordingProcess: ProcessController + ) { + self.outputPath = outputPath + self.recordingProcess = recordingProcess + } + + func stopRecording() -> AbsolutePath { + recordingProcess.interruptAndForceKillIfNeeded {} + recordingProcess.waitForProcessToDie() + return outputPath + } + + func cancelRecording() { + recordingProcess.terminateAndForceKillIfNeeded() + recordingProcess.waitForProcessToDie() + + let fileManager = FileManager() + if fileManager.fileExists(atPath: outputPath.pathString) { + try? fileManager.removeItem(atPath: outputPath.pathString) + } + } +} diff --git a/Sources/SimulatorVideoRecorder/SimulatorVideoRecorder.swift b/Sources/SimulatorVideoRecorder/SimulatorVideoRecorder.swift new file mode 100644 index 0000000..ab7cf53 --- /dev/null +++ b/Sources/SimulatorVideoRecorder/SimulatorVideoRecorder.swift @@ -0,0 +1,55 @@ +/* + * Copyright (c) Avito Tech LLC + */ + +import Foundation +import PathLib +import ProcessController + +public final class SimulatorVideoRecorder { + public enum CodecType: String { + case h264 + case hevc + } + + private let processControllerProvider: ProcessControllerProvider + private let simulatorUuid: String + private let simulatorSetPath: AbsolutePath + + public init( + processControllerProvider: ProcessControllerProvider, + simulatorUuid: String, + simulatorSetPath: AbsolutePath + ) { + self.processControllerProvider = processControllerProvider + self.simulatorUuid = simulatorUuid + self.simulatorSetPath = simulatorSetPath + } + + public func startRecording( + codecType: CodecType, + outputPath: AbsolutePath + ) throws -> CancellableRecording { + let processController = try processControllerProvider.createProcessController( + subprocess: Subprocess( + arguments: [ + "/usr/bin/xcrun", + "simctl", + "--set", + simulatorSetPath, + "io", + simulatorUuid, + "recordVideo", + "--codec=\(codecType.rawValue)", + outputPath + ] + ) + ) + try processController.start() + + return CancellableRecordingImpl( + outputPath: outputPath, + recordingProcess: processController + ) + } +}