Skip to content

Commit

Permalink
Add HKStreamRecorderTests
Browse files Browse the repository at this point in the history
  • Loading branch information
shogo4405 committed Nov 1, 2024
1 parent b48c1f7 commit 099c48b
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 22 deletions.
1 change: 1 addition & 0 deletions Examples/iOS/Screencast/SampleHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ final class SampleHandler: RPBroadcastSampleHandler, @unchecked Sendable {
Task { @MainActor in
if let volume = slider?.value {
var audioMixerSettings = await mixer.audioMixerSettings
audioMixerSettings.isMuted = true
audioMixerSettings.tracks[1] = .default
audioMixerSettings.tracks[1]?.volume = volume * 0.5
await mixer.setAudioMixerSettings(audioMixerSettings)
Expand Down
12 changes: 12 additions & 0 deletions HaishinKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
BC0587C12BD2A123006751C8 /* AudioMixerBySingleTrackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0587C02BD2A123006751C8 /* AudioMixerBySingleTrackTests.swift */; };
BC0587C32BD2A5E8006751C8 /* AudioMixerByMultiTrackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0587C22BD2A5E8006751C8 /* AudioMixerByMultiTrackTests.swift */; };
BC0587D22BD2CA7F006751C8 /* AudioStreamBasicDescription+DebugExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0587D12BD2CA7F006751C8 /* AudioStreamBasicDescription+DebugExtension.swift */; };
BC0628352CD25466005EB88E /* HKStreamRecorderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0628342CD2545E005EB88E /* HKStreamRecorderTests.swift */; };
BC0B5B122BE8CFA800D83F8E /* CMVideoDimention+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0B5B112BE8CFA800D83F8E /* CMVideoDimention+Extension.swift */; };
BC0B5B142BE8DFE300D83F8E /* AVLayerVideoGravity+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0B5B132BE8DFE300D83F8E /* AVLayerVideoGravity+Extension.swift */; };
BC0B5B172BE919D000D83F8E /* ScreenObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0B5B162BE919D000D83F8E /* ScreenObjectTests.swift */; };
Expand Down Expand Up @@ -555,6 +556,7 @@
BC0587C02BD2A123006751C8 /* AudioMixerBySingleTrackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMixerBySingleTrackTests.swift; sourceTree = "<group>"; };
BC0587C22BD2A5E8006751C8 /* AudioMixerByMultiTrackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMixerByMultiTrackTests.swift; sourceTree = "<group>"; };
BC0587D12BD2CA7F006751C8 /* AudioStreamBasicDescription+DebugExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AudioStreamBasicDescription+DebugExtension.swift"; sourceTree = "<group>"; };
BC0628342CD2545E005EB88E /* HKStreamRecorderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKStreamRecorderTests.swift; sourceTree = "<group>"; };
BC0B5B112BE8CFA800D83F8E /* CMVideoDimention+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CMVideoDimention+Extension.swift"; sourceTree = "<group>"; };
BC0B5B132BE8DFE300D83F8E /* AVLayerVideoGravity+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVLayerVideoGravity+Extension.swift"; sourceTree = "<group>"; };
BC0B5B162BE919D000D83F8E /* ScreenObjectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenObjectTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1005,6 +1007,7 @@
BC0B5B1D2BE9310800D83F8E /* CMVideoSampleBufferFactory.swift */,
295018191FFA196800358E10 /* Codec */,
BC03945D2AA8AFDD006EDE38 /* Extension */,
BC0628332CD2544E005EB88E /* HKStream */,
29798E5D1CE60E5300F5CBD0 /* Info.plist */,
291C2ACF1CE9FF2B006F042B /* ISO */,
BC0BF4F329866FB700D72CB4 /* Mixer */,
Expand Down Expand Up @@ -1150,6 +1153,14 @@
path = DebugDescription;
sourceTree = "<group>";
};
BC0628332CD2544E005EB88E /* HKStream */ = {
isa = PBXGroup;
children = (
BC0628342CD2545E005EB88E /* HKStreamRecorderTests.swift */,
);
path = HKStream;
sourceTree = "<group>";
};
BC0B5B152BE919B700D83F8E /* Screen */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1850,6 +1861,7 @@
BC56452C2C4972BD00CC79C5 /* CMSampleBuffer+ExtensionTests.swift in Sources */,
BC7C56C329A1F28700C41A9B /* TSReaderTests.swift in Sources */,
BC7C56D129A78D4F00C41A9B /* ADTSHeaderTests.swift in Sources */,
BC0628352CD25466005EB88E /* HKStreamRecorderTests.swift in Sources */,
BC3E384429C216BB007CD972 /* ADTSReaderTests.swift in Sources */,
BC1720A92C03473200F65941 /* AVCDecoderConfigurationRecordTests.swift in Sources */,
295018201FFA1BD700358E10 /* AudioCodecTests.swift in Sources */,
Expand Down
66 changes: 44 additions & 22 deletions Sources/HKStream/HKStreamRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,16 @@ public actor HKStreamRecorder {
public private(set) var movieFragmentInterval: Double?
public private(set) var videoTrackId: UInt8? = UInt8.max
public private(set) var audioTrackId: UInt8? = UInt8.max
#if os(iOS)
public private(set) lazy var moviesDirectory: URL = {
URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
}()
#else
#if os(macOS) && !targetEnvironment(macCatalyst)
/// The default file save location.
public private(set) var moviesDirectory: URL = {
URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.moviesDirectory, .userDomainMask, true)[0])
}()
#else
/// The default file save location.
public private(set) lazy var moviesDirectory: URL = {
URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
}()
#endif
private var isReadyForStartWriting: Bool {
guard let writer = writer else {
Expand Down Expand Up @@ -130,29 +132,36 @@ public actor HKStreamRecorder {
}

/// Starts recording.
///
/// For iOS, if the URL is unspecified, the file will be saved in .documentDirectory. You can specify a folder of your choice, but please use an absolute path.
///
/// ```
/// try? await recorder.startRecording(nil)
/// // -> $documentDirectory/B644F60F-0959-4F54-9D14-7F9949E02AD8.mp4
///
/// try? await recorder.startRecording(URL(string: "dir/sample.mp4"))
/// // -> $documentDirectory/dir/sample.mp4
///
/// try? await recorder.startRecording(await recorder.moviesDirectory.appendingPathComponent("sample.mp4"))
/// // -> $documentDirectory/sample.mp4
///
/// try? await recorder.startRecording(URL(string: "dir"))
/// // -> $documentDirectory/dir/33FA7D32-E0A8-4E2C-9980-B54B60654044.mp4
/// ```
///
/// - Note: Folders are not created automatically, so it’s expected that the target directory is created in advance.
/// - Parameters:
/// - file: The file path for recording. If nil is specified, a unique file path will be returned automatically.
/// - url: The file path for recording. If nil is specified, a unique file path will be returned automatically.
/// - settings: Settings for recording.
public func startRecording(_ file: URL? = nil, settings: [AVMediaType: [String: any Sendable]] = HKStreamRecorder.defaultSettings) async throws {
/// - Throws: `Error.fileAlreadyExists` when case file already exists.
/// - Throws: `Error.notSupportedFileType` when case species not supported format.
public func startRecording(_ url: URL? = nil, settings: [AVMediaType: [String: any Sendable]] = HKStreamRecorder.defaultSettings) async throws {
guard !isRecording else {
throw Error.invalidState
}

var outputURL: URL
if let file {
if file.hasDirectoryPath {
outputURL = file
} else {
outputURL = moviesDirectory.appendingPathComponent(file.absoluteString)
}
if file.pathExtension == "" {
outputURL = outputURL.appendingPathExtension(Self.defaultPathExtension)
}
} else {
outputURL = moviesDirectory.appendingPathComponent(UUID().uuidString).appendingPathExtension(Self.defaultPathExtension)
}

if FileManager.default.fileExists(atPath: outputURL.absoluteString) {
let outputURL = makeOutputURL(url)
if FileManager.default.fileExists(atPath: outputURL.path) {
throw Error.fileAlreadyExists(outputURL: outputURL)
}

Expand Down Expand Up @@ -216,6 +225,19 @@ public actor HKStreamRecorder {
}
}

private func makeOutputURL(_ url: URL?) -> URL {
guard let url else {
return moviesDirectory.appendingPathComponent(UUID().uuidString).appendingPathExtension(Self.defaultPathExtension)
}
// AVAssetWriter requires a isFileURL condition.
guard url.isFileURL else {
return url.pathExtension != "" ?
moviesDirectory.appendingPathComponent(url.path) :
moviesDirectory.appendingPathComponent(url.path).appendingPathComponent(UUID().uuidString).appendingPathExtension(Self.defaultPathExtension)
}
return url.pathExtension != "" ? url : url.appendingPathComponent(UUID().uuidString).appendingPathExtension(Self.defaultPathExtension)
}

private func append(_ sampleBuffer: CMSampleBuffer) {
guard isRecording else {
return
Expand Down
49 changes: 49 additions & 0 deletions Tests/HKStream/HKStreamRecorderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Foundation
import Testing

@testable import HaishinKit

@Suite struct HKStreamRecorderTests {
@Test func startRunning_nil() async {
let recorder = HKStreamRecorder()
try! await recorder.startRecording(nil)
let moviesDirectory = await recorder.moviesDirectory
// $moviesDirectory/B644F60F-0959-4F54-9D14-7F9949E02AD8.mp4
#expect(((await recorder.outputURL?.path.contains(moviesDirectory.path())) != nil))
}

@Test func startRunning_fileName() async {
let recorder = HKStreamRecorder()
try? await recorder.startRecording(URL(string: "dir/sample.mp4"))
let moviesDirectory = await recorder.moviesDirectory
// $moviesDirectory/dir/sample.mp4
#expect(((await recorder.outputURL?.path.contains("dir/sample.mp4")) != nil))
}

@Test func startRunning_fullPath() async {
let recorder = HKStreamRecorder()
let fullPath = await recorder.moviesDirectory.appendingPathComponent("sample.mp4")
// $moviesDirectory/sample.mp4
try? await recorder.startRecording(fullPath)
#expect(await recorder.outputURL == fullPath)
}

@Test func startRunning_dir() async {
let recorder = HKStreamRecorder()
try? await recorder.startRecording(URL(string: "dir"))
// $moviesDirectory/dir/33FA7D32-E0A8-4E2C-9980-B54B60654044.mp4
#expect(((await recorder.outputURL?.path.contains("dir")) != nil))
}

@Test func startRunning_fileAlreadyExists() async {
let recorder = HKStreamRecorder()
let filePath = await recorder.moviesDirectory.appendingPathComponent("duplicate-file.mp4")
FileManager.default.createFile(atPath: filePath.path, contents: nil)
do {
try await recorder.startRecording(filePath)
fatalError()
} catch {
try? FileManager.default.removeItem(atPath: filePath.path)
}
}
}

0 comments on commit 099c48b

Please sign in to comment.