StreamRecorder/Sources/StreamRecorderKit/HLSVariantResolver.swift
yumoqing 71d321b056 refactor: 跨平台架构 (macOS+iOS)
- StreamRecorderKit: 跨平台库
  - StreamRecorderEngine: 协议+类型
  - HLSVariantResolver: HLS master 解析
  - FFmpegRecorder: macOS (Process+ffmpeg)
  - AVFoundationRecorder: iOS (AVAssetReader+Writer)
  - RecorderFactory: 平台工厂
- StreamRecorder: CLI 入口
- 15秒录制测试通过
2026-06-28 17:49:12 +08:00

87 lines
3.2 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
/// HLS
public struct HLSVariantResolver {
public struct Variant {
public let bandwidth: Int
public let resolution: String
public let path: String
public let fullURL: URL
public var width: Int? {
let parts = resolution.components(separatedBy: "x")
return parts.count == 2 ? Int(parts[0]) : nil
}
public var height: Int? {
let parts = resolution.components(separatedBy: "x")
return parts.count == 2 ? Int(parts[1]) : nil
}
}
/// master playlist URL
/// - Parameters:
/// - url: master playlist URL
/// - maxWidth: 1280
/// - Returns: URL master playlist nil
public static func resolve(url: URL, maxWidth: Int = 1280) -> URL? {
guard url.absoluteString.contains(".m3u8"),
let data = try? Data(contentsOf: url),
let content = String(data: data, encoding: .utf8),
content.contains("EXT-X-STREAM-INF") else {
return nil
}
let variants = parseVariants(from: content, baseURL: url)
guard !variants.isEmpty else { return nil }
// <= maxWidth
let sorted = variants.sorted { $0.bandwidth > $1.bandwidth }
let chosen = sorted.first { variant in
guard let w = variant.width else { return true }
return w <= maxWidth
} ?? sorted[0]
return chosen.fullURL
}
private static func parseVariants(from content: String, baseURL: URL) -> [Variant] {
var variants: [Variant] = []
let lines = content.components(separatedBy: "\n")
var i = 0
while i < lines.count - 1 {
let line = lines[i]
if line.hasPrefix("#EXT-X-STREAM-INF:") {
var bandwidth = 0
var resolution = ""
let attrs = line.replacingOccurrences(of: "#EXT-X-STREAM-INF:", with: "")
for attr in attrs.components(separatedBy: ",") {
let trimmed = attr.trimmingCharacters(in: .whitespaces)
if trimmed.hasPrefix("BANDWIDTH=") {
bandwidth = Int(trimmed.replacingOccurrences(of: "BANDWIDTH=", with: "")) ?? 0
} else if trimmed.hasPrefix("RESOLUTION=") {
resolution = trimmed.replacingOccurrences(of: "RESOLUTION=", with: "")
}
}
let nextLine = lines[i + 1].trimmingCharacters(in: .whitespacesAndNewlines)
if !nextLine.isEmpty && !nextLine.hasPrefix("#") {
let variantURL = baseURL.deletingLastPathComponent().appendingPathComponent(nextLine)
variants.append(Variant(
bandwidth: bandwidth,
resolution: resolution,
path: nextLine,
fullURL: variantURL
))
}
}
i += 1
}
return variants
}
}