371 lines
11 KiB
Swift
371 lines
11 KiB
Swift
//
|
|
// LiveModels.swift
|
|
// suixinkan
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// 直播状态,对齐 Android `AliveEntity.status`。
|
|
enum LiveStatus: Int, Sendable, Equatable {
|
|
case pending = 1
|
|
case living = 2
|
|
case paused = 3
|
|
case finished = 4
|
|
case unknown = 0
|
|
|
|
/// 是否允许开始、暂停或结束。
|
|
var isOperable: Bool { self != .finished }
|
|
}
|
|
|
|
/// 直播推流模式,对齐 Android `manualPushMode`。
|
|
enum LivePushMode: Int, CaseIterable, Sendable, Equatable {
|
|
case clarityFirst = 1
|
|
case smoothFirst = 2
|
|
|
|
/// 展示文案。
|
|
var title: String {
|
|
switch self {
|
|
case .clarityFirst: "清晰度优先"
|
|
case .smoothFirst: "流畅度优先"
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 直播列表与详情实体,对齐 Android `AliveEntity`。
|
|
struct LiveItem: Decodable, Sendable, Equatable, Hashable, Identifiable {
|
|
let id: Int
|
|
let title: String
|
|
let coverImg: String
|
|
let pushUrl: String
|
|
let startTime: Int64
|
|
let endTime: Int64
|
|
let duration: Int64
|
|
let status: Int
|
|
let statusLabel: String
|
|
let manualPushMode: Int
|
|
let manualPushState: Int
|
|
let viewsCount: Int
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id
|
|
case title
|
|
case coverImg = "cover_img"
|
|
case pushUrl = "push_url"
|
|
case startTime = "start_time"
|
|
case endTime = "end_time"
|
|
case duration
|
|
case status
|
|
case statusLabel = "status_label"
|
|
case manualPushMode = "manual_push_mode"
|
|
case manualPushState = "manual_push_state"
|
|
case legacyManualPushMode = "manaul_push_mode"
|
|
case legacyManualPushState = "manaul_push_state"
|
|
case viewsCount = "views_count"
|
|
}
|
|
|
|
init(
|
|
id: Int = 0,
|
|
title: String = "",
|
|
coverImg: String = "",
|
|
pushUrl: String = "",
|
|
startTime: Int64 = 0,
|
|
endTime: Int64 = 0,
|
|
duration: Int64 = 0,
|
|
status: Int = 0,
|
|
statusLabel: String = "",
|
|
manualPushMode: Int = 1,
|
|
manualPushState: Int = 0,
|
|
viewsCount: Int = 0
|
|
) {
|
|
self.id = id
|
|
self.title = title
|
|
self.coverImg = coverImg
|
|
self.pushUrl = pushUrl
|
|
self.startTime = startTime
|
|
self.endTime = endTime
|
|
self.duration = duration
|
|
self.status = status
|
|
self.statusLabel = statusLabel
|
|
self.manualPushMode = manualPushMode
|
|
self.manualPushState = manualPushState
|
|
self.viewsCount = viewsCount
|
|
}
|
|
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
id = try container.decodeLossyInt(forKey: .id) ?? 0
|
|
title = try container.decodeLossyString(forKey: .title)
|
|
coverImg = try container.decodeLossyString(forKey: .coverImg).unescapedSlashes
|
|
pushUrl = try container.decodeLossyString(forKey: .pushUrl).unescapedSlashes
|
|
startTime = try container.decodeLossyInt64(forKey: .startTime) ?? 0
|
|
endTime = try container.decodeLossyInt64(forKey: .endTime) ?? 0
|
|
duration = try container.decodeLossyInt64(forKey: .duration) ?? 0
|
|
status = try container.decodeLossyInt(forKey: .status) ?? 0
|
|
statusLabel = try container.decodeLossyString(forKey: .statusLabel)
|
|
manualPushMode = try container.decodeLossyInt(forKey: .manualPushMode)
|
|
?? container.decodeLossyInt(forKey: .legacyManualPushMode)
|
|
?? 1
|
|
manualPushState = try container.decodeLossyInt(forKey: .manualPushState)
|
|
?? container.decodeLossyInt(forKey: .legacyManualPushState)
|
|
?? 0
|
|
viewsCount = try container.decodeLossyInt(forKey: .viewsCount) ?? 0
|
|
}
|
|
|
|
/// 类型化直播状态。
|
|
var liveStatus: LiveStatus { LiveStatus(rawValue: status) ?? .unknown }
|
|
|
|
/// 标题兜底。
|
|
var displayTitle: String { title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "暂无标题" : title }
|
|
|
|
/// 当前操作按钮标题。
|
|
var controlTitle: String { liveStatus == .living ? "暂停" : "开始" }
|
|
}
|
|
|
|
/// 直播分页响应,对齐 Android `ItemListResponse<AliveEntity>`。
|
|
struct LiveListResponse: Decodable, Sendable, Equatable {
|
|
let page: Int
|
|
let pageSize: Int
|
|
let total: Int
|
|
let totalPages: Int
|
|
let items: [LiveItem]
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case page
|
|
case pageSize = "page_size"
|
|
case total
|
|
case totalPages = "total_pages"
|
|
case items
|
|
}
|
|
|
|
init(page: Int = 1, pageSize: Int = 10, total: Int = 0, totalPages: Int = 0, items: [LiveItem] = []) {
|
|
self.page = page
|
|
self.pageSize = pageSize
|
|
self.total = total
|
|
self.totalPages = totalPages
|
|
self.items = items
|
|
}
|
|
}
|
|
|
|
/// 新增直播请求。
|
|
struct LiveCreateRequest: Encodable, Sendable, Equatable {
|
|
let scenicId: String
|
|
let title: String
|
|
let coverImg: String
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case scenicId = "scenic_id"
|
|
case title
|
|
case coverImg = "cover_img"
|
|
}
|
|
}
|
|
|
|
/// 直播控制请求。
|
|
struct LiveControlRequest: Encodable, Sendable, Equatable {
|
|
let liveId: Int
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case liveId = "live_id"
|
|
}
|
|
}
|
|
|
|
/// 直播推流模式请求。
|
|
struct LivePushModeRequest: Encodable, Sendable, Equatable {
|
|
let liveId: Int
|
|
let manualPushMode: Int
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case liveId = "live_id"
|
|
case manualPushMode = "manual_push_mode"
|
|
}
|
|
}
|
|
|
|
/// 直播相册文件实体,对齐 Android `AliveAlbumFileEntity`。
|
|
struct LiveAlbumFile: Codable, Sendable, Equatable, Hashable, Identifiable {
|
|
let url: String
|
|
let type: Int
|
|
let size: Int64
|
|
let coverImg: String?
|
|
let id: Int
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case url
|
|
case type
|
|
case size
|
|
case coverImg = "cover_img"
|
|
case id
|
|
}
|
|
|
|
init(url: String, type: Int, size: Int64, coverImg: String? = nil, id: Int = 0) {
|
|
self.url = url.unescapedSlashes
|
|
self.type = type
|
|
self.size = size
|
|
self.coverImg = coverImg?.unescapedSlashes
|
|
self.id = id
|
|
}
|
|
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
url = try container.decodeLossyString(forKey: .url).unescapedSlashes
|
|
type = try container.decodeLossyInt(forKey: .type) ?? 2
|
|
size = try container.decodeLossyInt64(forKey: .size) ?? 0
|
|
let cover = try container.decodeIfPresent(String.self, forKey: .coverImg)
|
|
coverImg = cover?.unescapedSlashes
|
|
id = try container.decodeLossyInt(forKey: .id) ?? 0
|
|
}
|
|
}
|
|
|
|
/// 直播相册文件夹实体,对齐 Android `AliveAlbumFolderEntity`。
|
|
struct LiveAlbumFolder: Decodable, Sendable, Equatable, Hashable, Identifiable {
|
|
let id: Int
|
|
let albumId: Int
|
|
let name: String
|
|
let creator: String
|
|
let items: [LiveAlbumFile]
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id
|
|
case albumId = "album_id"
|
|
case name
|
|
case creator
|
|
case items
|
|
}
|
|
|
|
init(id: Int = 0, albumId: Int = 0, name: String = "", creator: String = "", items: [LiveAlbumFile] = []) {
|
|
self.id = id
|
|
self.albumId = albumId
|
|
self.name = name
|
|
self.creator = creator
|
|
self.items = items
|
|
}
|
|
|
|
/// 标题兜底。
|
|
var displayName: String { name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "暂无标题" : name }
|
|
}
|
|
|
|
/// 直播相册列表响应。
|
|
struct LiveAlbumFolderListResponse: Decodable, Sendable, Equatable {
|
|
let items: [LiveAlbumFolder]
|
|
let page: Int
|
|
let pageSize: Int
|
|
let total: Int
|
|
let totalPages: Int
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case items
|
|
case page
|
|
case pageSize = "page_size"
|
|
case total
|
|
case totalPages = "total_pages"
|
|
}
|
|
|
|
init(items: [LiveAlbumFolder] = [], page: Int = 1, pageSize: Int = 10, total: Int = 0, totalPages: Int = 0) {
|
|
self.items = items
|
|
self.page = page
|
|
self.pageSize = pageSize
|
|
self.total = total
|
|
self.totalPages = totalPages
|
|
}
|
|
}
|
|
|
|
/// 创建直播相册文件夹请求。
|
|
struct LiveAlbumFolderCreateRequest: Encodable, Sendable, Equatable {
|
|
let scenicId: String
|
|
let name: String
|
|
let items: [LiveAlbumFile]
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case scenicId = "scenic_id"
|
|
case name
|
|
case items
|
|
}
|
|
}
|
|
|
|
/// 删除直播相册文件夹请求。
|
|
struct LiveAlbumFolderDeleteRequest: Encodable, Sendable, Equatable {
|
|
let folderId: Int
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case folderId = "folder_id"
|
|
}
|
|
}
|
|
|
|
/// 删除直播相册文件请求。
|
|
struct LiveAlbumFileDeleteRequest: Encodable, Sendable, Equatable {
|
|
let folderId: Int
|
|
let fileIds: [Int]
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case folderId = "folder_id"
|
|
case fileIds = "file_ids"
|
|
}
|
|
}
|
|
|
|
/// 本地待上传直播相册素材。
|
|
struct LivePickedMedia: Sendable, Equatable {
|
|
let data: Data
|
|
let fileName: String
|
|
let size: Int64
|
|
let width: Int
|
|
let height: Int
|
|
}
|
|
|
|
private extension KeyedDecodingContainer {
|
|
func decodeLossyString(forKey key: Key) throws -> String {
|
|
if let value = try? decodeIfPresent(String.self, forKey: key) {
|
|
return value
|
|
}
|
|
if let value = try? decodeIfPresent(Int.self, forKey: key) {
|
|
return String(value)
|
|
}
|
|
if let value = try? decodeIfPresent(Int64.self, forKey: key) {
|
|
return String(value)
|
|
}
|
|
if let value = try? decodeIfPresent(Double.self, forKey: key) {
|
|
return String(value)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func decodeLossyInt(forKey key: Key) throws -> Int? {
|
|
if let value = try? decodeIfPresent(Int.self, forKey: key) {
|
|
return value
|
|
}
|
|
if let value = try? decodeIfPresent(Int64.self, forKey: key) {
|
|
return Int(value)
|
|
}
|
|
if let value = try? decodeIfPresent(String.self, forKey: key) {
|
|
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
if let intValue = Int(text) { return intValue }
|
|
if let doubleValue = Double(text) { return Int(doubleValue) }
|
|
}
|
|
if let value = try? decodeIfPresent(Double.self, forKey: key) {
|
|
return Int(value)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func decodeLossyInt64(forKey key: Key) throws -> Int64? {
|
|
if let value = try? decodeIfPresent(Int64.self, forKey: key) {
|
|
return value
|
|
}
|
|
if let value = try? decodeIfPresent(Int.self, forKey: key) {
|
|
return Int64(value)
|
|
}
|
|
if let value = try? decodeIfPresent(String.self, forKey: key) {
|
|
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
if let intValue = Int64(text) { return intValue }
|
|
if let doubleValue = Double(text) { return Int64(doubleValue) }
|
|
}
|
|
if let value = try? decodeIfPresent(Double.self, forKey: key) {
|
|
return Int64(value)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private extension String {
|
|
var unescapedSlashes: String {
|
|
replacingOccurrences(of: "\\/", with: "/")
|
|
}
|
|
}
|