diff --git a/Package.swift b/Package.swift index e0a5778..a826ef1 100644 --- a/Package.swift +++ b/Package.swift @@ -26,10 +26,18 @@ let package = Package( .product(name: "Starscream", package: "Starscream"), .product(name: "SynchronousWaiter", package: "CommandLineToolkit"), "EmceePluginModels", + "SimulatorVideoRecorder", ] ), .target( name: "EmceePluginModels" - ) + ), + .target( + name: "SimulatorVideoRecorder", + dependencies: [ + .product(name: "PathLib", package: "CommandLineToolkit"), + .product(name: "ProcessController", package: "CommandLineToolkit"), + ] + ), ] ) 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 3fa22d2..0000000 Binary files a/Sources/.DS_Store and /dev/null differ 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 + ) + } +}