94 lines
2.9 KiB
Swift
94 lines
2.9 KiB
Swift
import Foundation
|
||
|
||
/// 录制进度回调
|
||
public protocol StreamRecorderDelegate: AnyObject {
|
||
func recorderDidStart(_ recorder: StreamRecorderEngine)
|
||
func recorder(_ recorder: StreamRecorderEngine, didUpdateProgress progress: RecordingProgress)
|
||
func recorder(_ recorder: StreamRecorderEngine, didFinishWithResult result: RecordingResult)
|
||
func recorder(_ recorder: StreamRecorderEngine, didFailWithError error: Error)
|
||
}
|
||
|
||
/// 录制进度
|
||
public struct RecordingProgress {
|
||
public let currentTime: Double // 当前已录制时长(秒)
|
||
public let totalDuration: Double // 总时长(秒)
|
||
public let bytesWritten: Int64 // 已写入字节数
|
||
public let bitrate: Double // 当前码率 (kbps)
|
||
|
||
public var percentage: Double {
|
||
guard totalDuration > 0 else { return 0 }
|
||
return min(currentTime / totalDuration * 100, 100)
|
||
}
|
||
}
|
||
|
||
/// 录制结果
|
||
public struct RecordingResult {
|
||
public let outputPath: String
|
||
public let duration: Double
|
||
public let fileSize: Int64
|
||
public let hasVideo: Bool
|
||
public let hasAudio: Bool
|
||
public let videoInfo: VideoInfo?
|
||
public let audioInfo: AudioInfo?
|
||
public let audioLevel: AudioLevel?
|
||
}
|
||
|
||
public struct VideoInfo {
|
||
public let codec: String
|
||
public let width: Int
|
||
public let height: Int
|
||
public let fps: Double
|
||
public let pixelFormat: String
|
||
}
|
||
|
||
public struct AudioInfo {
|
||
public let codec: String
|
||
public let sampleRate: Int
|
||
public let channels: Int
|
||
}
|
||
|
||
public struct AudioLevel {
|
||
public let meanVolume: Double // dB
|
||
public let maxVolume: Double // dB
|
||
public let isSilent: Bool
|
||
}
|
||
|
||
public enum RecordingError: Error, LocalizedError {
|
||
case invalidURL
|
||
case noFFmpeg
|
||
case noMediaTracks
|
||
case processFailed(Int, String? = nil)
|
||
case outputNotFound(String? = nil)
|
||
case unknown(String)
|
||
|
||
public var errorDescription: String? {
|
||
switch self {
|
||
case .invalidURL: return "无效的 URL"
|
||
case .noFFmpeg: return "未找到 ffmpeg,请安装: brew install ffmpeg"
|
||
case .noMediaTracks: return "未找到任何媒体轨道"
|
||
case .processFailed(let code, let stderr):
|
||
if let s = stderr, !s.isEmpty {
|
||
let preview = String(s.prefix(300))
|
||
return "录制失败 (exit code: \(code)):\n\(preview)"
|
||
}
|
||
return "录制失败 (exit code: \(code))"
|
||
case .outputNotFound(let stderr):
|
||
if let s = stderr, !s.isEmpty {
|
||
let preview = String(s.prefix(300))
|
||
return "输出文件不存在\n\(preview)"
|
||
}
|
||
return "输出文件不存在"
|
||
case .unknown(let msg): return msg
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 录制引擎协议
|
||
public protocol StreamRecorderEngine: AnyObject {
|
||
var delegate: StreamRecorderDelegate? { get set }
|
||
var isRecording: Bool { get }
|
||
|
||
func start(url: URL, outputPath: String, duration: Double) async throws
|
||
func stop()
|
||
}
|