import Foundation // MARK: - 二维码载荷(当前播放视频 URL + 频道信息) // // 扫码后直接把该条目保存进扫码方的媒体库,并带上频道/分组/台标等元数据。 // // 编码格式:优先 JSON(携带频道信息), // 若编码后过长(> ~900 字符,QR 版本会过大影响识别率), // 退化为纯 URL —— 扫码方仍能添加,只是没有频道元数据。 public struct QRMediaPayload: Codable, Equatable { /// 媒体 URL(必填) public var url: String /// 频道/节目名称 public var name: String? /// 类型:"stream" / "url" / "file" public var type: String? /// 频道分组(M3U group-title) public var group: String? /// 台标 URL(M3U tvg-logo) public var logo: String? /// 发送方标识,便于扫码方区分来源 public var from: String? public init(url: String, name: String? = nil, type: String? = nil, group: String? = nil, logo: String? = nil, from: String? = nil) { self.url = url self.name = name self.type = type self.group = group self.logo = logo self.from = from } /// 短键名,压缩二维码体积 enum CodingKeys: String, CodingKey { case url = "u" case name = "n" case type = "t" case group = "g" case logo = "l" case from = "f" } /// 二维码中实际使用的文本:短负载用 JSON,超长退回纯 URL public static let maxJSONLength = 900 public func encoded() -> String { let enc = JSONEncoder() enc.outputFormatting = [.withoutEscapingSlashes] if let data = try? enc.encode(self), let json = String(data: data, encoding: .utf8), json.count <= Self.maxJSONLength { return json } return url // 退化:纯 URL } /// 解析扫码结果:支持 JSON 载荷,也兼容纯 URL(旧二维码/手输) public static func decode(_ text: String) -> QRMediaPayload? { let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return nil } if trimmed.hasPrefix("{") { if let data = trimmed.data(using: .utf8), let p = try? JSONDecoder().decode(QRMediaPayload.self, from: data), !p.url.isEmpty { return p } return nil // 是 JSON 但解析失败 → 不当成 URL 处理 } // 纯 URL 回退 if let u = URL(string: trimmed), let scheme = u.scheme?.lowercased(), scheme == "http" || scheme == "https" || scheme == "file" { return QRMediaPayload(url: trimmed, name: u.lastPathComponent.isEmpty ? u.host : u.lastPathComponent) } return nil } }