增加注解
This commit is contained in:
@@ -4,10 +4,15 @@ import Foundation
|
||||
|
||||
/// 直播频道分组
|
||||
struct LiveChannelGroup: Codable, Identifiable, Hashable {
|
||||
/// 以分组名作为稳定标识,便于 SwiftUI 列表 diff。
|
||||
var id: String { groupName }
|
||||
/// 分组名称(如“央视”“卫视”)。
|
||||
var groupName: String = ""
|
||||
/// 分组在列表中的顺序索引。
|
||||
var groupIndex: Int = 0
|
||||
/// 分组下频道列表。
|
||||
var channels: [LiveChannelItem] = []
|
||||
/// 是否为加密分组(当前实现仅保留字段,未启用密码校验)。
|
||||
var isPassword: Bool = false
|
||||
|
||||
init(groupName: String = "", groupIndex: Int = 0) {
|
||||
@@ -18,12 +23,19 @@ struct LiveChannelGroup: Codable, Identifiable, Hashable {
|
||||
|
||||
/// 直播频道
|
||||
struct LiveChannelItem: Codable, Identifiable, Hashable {
|
||||
/// 频道标识由名称+索引组成,规避同名频道冲突。
|
||||
var id: String { "\(channelName)_\(channelIndex)" }
|
||||
/// 频道名。
|
||||
var channelName: String = ""
|
||||
/// 频道在分组内的顺序索引。
|
||||
var channelIndex: Int = 0
|
||||
/// 多线路播放地址。
|
||||
var channelUrls: [String] = []
|
||||
/// 当前选中的线路索引。
|
||||
var sourceIndex: Int = 0
|
||||
/// 可用线路总数。
|
||||
var sourceNum: Int { channelUrls.count }
|
||||
/// 台标地址(预留)。
|
||||
var logo: String = ""
|
||||
|
||||
init(channelName: String = "", channelIndex: Int = 0) {
|
||||
@@ -31,11 +43,14 @@ struct LiveChannelItem: Codable, Identifiable, Hashable {
|
||||
self.channelIndex = channelIndex
|
||||
}
|
||||
|
||||
/// 当前线路对应的播放地址。
|
||||
/// 当索引越界时兜底返回第一条线路,避免直接播放失败。
|
||||
var currentUrl: String? {
|
||||
guard sourceIndex >= 0, sourceIndex < channelUrls.count else { return channelUrls.first }
|
||||
return channelUrls[sourceIndex]
|
||||
}
|
||||
|
||||
/// 轮换到下一条线路。
|
||||
mutating func nextSource() {
|
||||
if channelUrls.count > 0 {
|
||||
sourceIndex = (sourceIndex + 1) % channelUrls.count
|
||||
@@ -51,6 +66,7 @@ struct Epginfo: Codable, Identifiable, Hashable {
|
||||
var endTime: String = ""
|
||||
var index: Int = 0
|
||||
|
||||
/// 根据 `HH:mm` 时间段判断节目是否正在播出。
|
||||
var isLive: Bool {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "HH:mm"
|
||||
@@ -65,8 +81,12 @@ struct Epginfo: Codable, Identifiable, Hashable {
|
||||
/// EPG 日期分组
|
||||
struct LiveEpgDate: Codable, Identifiable, Hashable {
|
||||
var id: String { datePresent }
|
||||
var datePresent: String = "" // 显示日期
|
||||
var date: String = "" // 查询日期
|
||||
/// 供 UI 展示的日期文案。
|
||||
var datePresent: String = ""
|
||||
/// 供接口查询的原始日期值。
|
||||
var date: String = ""
|
||||
/// 在日期列表中的位置索引。
|
||||
var index: Int = 0
|
||||
/// 是否被当前 UI 选中。
|
||||
var isSelected: Bool = false
|
||||
}
|
||||
|
||||
@@ -2,28 +2,47 @@ import Foundation
|
||||
|
||||
/// 电影/视频数据模型 - 对应 Android 版 Movie.java
|
||||
struct Movie: Codable {
|
||||
/// 列表数据主体。
|
||||
var videoList: [Video] = []
|
||||
/// 总页数。
|
||||
var pagecount: Int = 0
|
||||
/// 当前页码。
|
||||
var page: Int = 0
|
||||
/// 总条数。
|
||||
var total: Int = 0
|
||||
/// 每页条数。
|
||||
var limit: Int = 0
|
||||
|
||||
/// 单个视频条目
|
||||
struct Video: Codable, Identifiable, Hashable {
|
||||
/// 视频唯一 ID(接口可能返回 Int 或 String,见自定义解码)。
|
||||
var id: String
|
||||
/// 片名。
|
||||
var name: String = ""
|
||||
/// 海报地址。
|
||||
var pic: String = ""
|
||||
var note: String = "" // 备注(如"更新至第20集")
|
||||
/// 备注(如“更新至第20集”)。
|
||||
var note: String = ""
|
||||
/// 年份。
|
||||
var year: String = ""
|
||||
/// 地区。
|
||||
var area: String = ""
|
||||
var type: String = "" // 类型/分类名
|
||||
/// 类型/分类名。
|
||||
var type: String = ""
|
||||
/// 导演。
|
||||
var director: String = ""
|
||||
/// 演员。
|
||||
var actor: String = ""
|
||||
var des: String = "" // 简介
|
||||
var sourceKey: String = "" // 来源站点 key
|
||||
var tid: String = "" // 分类 ID
|
||||
var last: String = "" // 最后更新
|
||||
var dt: String = "" // 日期
|
||||
/// 简介。
|
||||
var des: String = ""
|
||||
/// 来源站点 key,用于跨源隔离收藏与历史。
|
||||
var sourceKey: String = ""
|
||||
/// 分类 ID。
|
||||
var tid: String = ""
|
||||
/// 最后更新时间。
|
||||
var last: String = ""
|
||||
/// 播放来源信息(部分接口会复用该字段)。
|
||||
var dt: String = ""
|
||||
|
||||
init(id: String = UUID().uuidString, name: String = "", pic: String = "",
|
||||
note: String = "", sourceKey: String = "") {
|
||||
@@ -51,6 +70,7 @@ struct Movie: Codable {
|
||||
case sourceKey
|
||||
}
|
||||
|
||||
/// 自定义解码以兼容多源字段类型差异(如 `vod_id` / `type_id` 可能是 Int 或 String)。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
// 支持 String 或 Int 类型的 id
|
||||
|
||||
@@ -2,13 +2,18 @@ import Foundation
|
||||
|
||||
/// 分类排序模型 - 对应 Android 版 MovieSort.java
|
||||
struct MovieSort: Codable {
|
||||
/// 分类列表(包含首页推荐、影视分类等)。
|
||||
var sortList: [SortData] = []
|
||||
|
||||
/// 单个分类数据
|
||||
struct SortData: Codable, Identifiable, Hashable {
|
||||
/// 分类唯一标识(接口字段通常为 type_id)。
|
||||
var id: String
|
||||
/// 分类显示名。
|
||||
var name: String = ""
|
||||
/// 标记位(不同源可定义不同语义,常用于首页/推荐标识)。
|
||||
var flag: String = ""
|
||||
/// 分类下可选筛选项(年份、地区、类型等)。
|
||||
var filters: [SortFilter] = []
|
||||
|
||||
init(id: String = "", name: String = "", flag: String = "") {
|
||||
@@ -17,6 +22,8 @@ struct MovieSort: Codable {
|
||||
self.flag = flag
|
||||
}
|
||||
|
||||
/// 生成首页推荐占位分类。
|
||||
/// 该分类不走常规分类接口,直接渲染首页推荐列表。
|
||||
static func home() -> SortData {
|
||||
SortData(id: "home", name: "推荐", flag: "1")
|
||||
}
|
||||
@@ -24,13 +31,18 @@ struct MovieSort: Codable {
|
||||
|
||||
/// 筛选条件
|
||||
struct SortFilter: Codable, Hashable {
|
||||
/// 接口参数键,例如 `year`、`area`。
|
||||
var key: String = ""
|
||||
/// UI 展示名称。
|
||||
var name: String = ""
|
||||
/// 可选值集合。
|
||||
var values: [SortFilterValue] = []
|
||||
|
||||
struct SortFilterValue: Codable, Hashable {
|
||||
var n: String = "" // 显示名
|
||||
var v: String = "" // 值
|
||||
/// 展示名。
|
||||
var n: String = ""
|
||||
/// 真实参数值。
|
||||
var v: String = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@ import Foundation
|
||||
|
||||
/// 播放器引擎类型
|
||||
enum PlayerEngine: Int, CaseIterable, Identifiable {
|
||||
/// 系统 AVPlayer 内核。
|
||||
case system = 0
|
||||
/// VLC 内核(需编译时可导入 VLCKitSPM)。
|
||||
case vlc = 10
|
||||
|
||||
var id: Int { rawValue }
|
||||
|
||||
/// UI 展示名。
|
||||
var title: String {
|
||||
switch self {
|
||||
case .system:
|
||||
@@ -16,6 +19,7 @@ enum PlayerEngine: Int, CaseIterable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
/// 当前构建产物是否包含 VLC 能力。
|
||||
static var isVLCAvailable: Bool {
|
||||
#if canImport(VLCKitSPM)
|
||||
return true
|
||||
@@ -24,6 +28,8 @@ enum PlayerEngine: Int, CaseIterable, Identifiable {
|
||||
#endif
|
||||
}
|
||||
|
||||
/// 实际可供用户选择的播放器列表。
|
||||
/// 当 VLC 不可用时,仅暴露系统播放器,避免无效配置。
|
||||
static var availableEngines: [PlayerEngine] {
|
||||
var engines: [PlayerEngine] = [.system]
|
||||
if isVLCAvailable {
|
||||
@@ -32,6 +38,7 @@ enum PlayerEngine: Int, CaseIterable, Identifiable {
|
||||
return engines
|
||||
}
|
||||
|
||||
/// 从持久化值恢复播放器选项,并自动兜底到可用引擎。
|
||||
static func fromStoredValue(_ rawValue: Int) -> PlayerEngine {
|
||||
guard let engine = PlayerEngine(rawValue: rawValue) else {
|
||||
return .system
|
||||
@@ -47,8 +54,11 @@ enum PlayerEngine: Int, CaseIterable, Identifiable {
|
||||
|
||||
/// 视频解码模式
|
||||
enum VideoDecodeMode: Int, CaseIterable, Identifiable {
|
||||
/// 自动策略,优先硬解,失败时用户可切换。
|
||||
case auto = 0
|
||||
/// 强制硬解。
|
||||
case hardware = 1
|
||||
/// 强制软解。
|
||||
case software = 2
|
||||
|
||||
var id: Int { rawValue }
|
||||
@@ -69,6 +79,7 @@ enum VideoDecodeMode: Int, CaseIterable, Identifiable {
|
||||
}
|
||||
|
||||
/// VLC 媒体选项
|
||||
/// 返回 `avcodec-hw` 对应的值。
|
||||
var vlcHardwareDecodeOption: String? {
|
||||
switch self {
|
||||
case .auto:
|
||||
@@ -84,8 +95,11 @@ enum VideoDecodeMode: Int, CaseIterable, Identifiable {
|
||||
|
||||
/// VLC 缓冲策略
|
||||
enum VLCBufferMode: Int, CaseIterable, Identifiable {
|
||||
/// 低延迟优先,适合直播但容错较低。
|
||||
case lowLatency = 0
|
||||
/// 兼顾延迟与稳定性,作为默认策略。
|
||||
case balanced = 1
|
||||
/// 稳定流畅优先,允许更高缓冲。
|
||||
case smooth = 2
|
||||
|
||||
var id: Int { rawValue }
|
||||
@@ -111,6 +125,10 @@ enum VLCBufferMode: Int, CaseIterable, Identifiable {
|
||||
self == .lowLatency
|
||||
}
|
||||
|
||||
/// 根据直播/点播场景输出三类缓存值(单位毫秒)。
|
||||
/// - Parameters:
|
||||
/// - isLive: 是否直播场景
|
||||
/// - Returns: network/live/file 三类缓存配置
|
||||
func cacheConfig(isLive: Bool) -> (network: Int, live: Int, file: Int) {
|
||||
switch self {
|
||||
case .lowLatency:
|
||||
|
||||
@@ -2,15 +2,24 @@ import Foundation
|
||||
|
||||
/// 视频源站点配置 - 对应 Android 版 SourceBean.java
|
||||
struct SourceBean: Codable, Identifiable, Hashable {
|
||||
/// 以源 key 作为稳定标识。
|
||||
var id: String { key }
|
||||
|
||||
/// 源唯一键。
|
||||
let key: String
|
||||
/// 源显示名。
|
||||
let name: String
|
||||
/// 源接口地址。
|
||||
let api: String
|
||||
let searchable: Int // 0:关闭搜索 1:启用搜索
|
||||
let filterable: Int // 0:首页不可选 1:首页可选
|
||||
let playerType: Int // 0:系统 1:IJK 2:EXO
|
||||
let type: Int // 0:xml 1:json 3:jar 4:remote
|
||||
/// 搜索开关:0 关闭,1 开启。
|
||||
let searchable: Int
|
||||
/// 是否允许出现在首页分类:0 不可选,1 可选。
|
||||
let filterable: Int
|
||||
/// 源声明的播放器类型(历史字段,Swift 端目前主要走统一播放器策略)。
|
||||
let playerType: Int
|
||||
/// 源协议类型:0 XML,1 JSON,3 JAR,4 Remote。
|
||||
let type: Int
|
||||
/// 扩展参数(remote 源常用)。
|
||||
let ext: String?
|
||||
|
||||
init(key: String = "", name: String = "", api: String = "",
|
||||
|
||||
@@ -2,16 +2,27 @@ import Foundation
|
||||
|
||||
/// 视频详情模型 - 对应 Android 版 VodInfo.java
|
||||
struct VodInfo: Codable, Identifiable {
|
||||
/// 视频唯一 ID。
|
||||
var id: String
|
||||
/// 标题。
|
||||
var name: String = ""
|
||||
/// 海报地址。
|
||||
var pic: String = ""
|
||||
/// 备注(更新状态等)。
|
||||
var note: String = ""
|
||||
/// 年份。
|
||||
var year: String = ""
|
||||
/// 地区。
|
||||
var area: String = ""
|
||||
/// 类型名。
|
||||
var typeName: String = ""
|
||||
/// 导演。
|
||||
var director: String = ""
|
||||
/// 演员。
|
||||
var actor: String = ""
|
||||
/// 简介。
|
||||
var des: String = ""
|
||||
/// 来源站点 key。
|
||||
var sourceKey: String = ""
|
||||
|
||||
/// 播放来源(线路)列表
|
||||
@@ -19,13 +30,17 @@ struct VodInfo: Codable, Identifiable {
|
||||
/// key: flag名称, value: 剧集列表
|
||||
var playUrlMap: [String: [Episode]] = [:]
|
||||
|
||||
var playFlag: String = "" // 当前选中线路
|
||||
var playIndex: Int = 0 // 当前播放剧集索引
|
||||
/// 当前选中线路。
|
||||
var playFlag: String = ""
|
||||
/// 当前播放剧集索引。
|
||||
var playIndex: Int = 0
|
||||
|
||||
/// 单集信息
|
||||
struct Episode: Codable, Identifiable, Hashable {
|
||||
var id: String { name }
|
||||
/// 集标题。
|
||||
let name: String
|
||||
/// 集播放地址。
|
||||
let url: String
|
||||
|
||||
init(name: String, url: String) {
|
||||
@@ -48,7 +63,7 @@ struct VodInfo: Codable, Identifiable {
|
||||
info.des = video.des.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression)
|
||||
info.sourceKey = video.sourceKey
|
||||
|
||||
// 解析播放列表
|
||||
// 解析播放列表:
|
||||
// playFrom 格式: "线路1$$$线路2$$$线路3"
|
||||
// playUrl 格式: "第1集$url1#第2集$url2$$$第1集$url3#第2集$url4"
|
||||
let flags = playFrom.components(separatedBy: "$$$").filter { !$0.isEmpty }
|
||||
@@ -73,10 +88,12 @@ struct VodInfo: Codable, Identifiable {
|
||||
return info
|
||||
}
|
||||
|
||||
/// 当前线路下的剧集。
|
||||
var currentEpisodes: [Episode] {
|
||||
playUrlMap[playFlag] ?? []
|
||||
}
|
||||
|
||||
/// 当前线路 + 当前索引对应的剧集对象。
|
||||
var currentEpisode: Episode? {
|
||||
let eps = currentEpisodes
|
||||
guard playIndex >= 0, playIndex < eps.count else { return nil }
|
||||
|
||||
Reference in New Issue
Block a user