Files
suixinkan_ios_new/suixinkan/Features/Live/Models/LiveModels.swift
汉秋 41dda3cc9b Add OperatingArea and PilotCertification modules with live push readiness.
Migrate operating area and pilot certification from home placeholders, extend Live with playback and push readiness flows, and add pilot certificate OSS upload.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 18:15:59 +08:00

416 lines
12 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.

//
// LiveModels.swift
// suixinkan
//
// Created by Codex on 2026/6/25.
//
import Foundation
///
struct LiveListResponse: Decodable {
let items: [LiveEntity]
let total: Int
let page: Int
let pageSize: Int
enum CodingKeys: String, CodingKey {
case items
case total
case page
case pageSize = "page_size"
}
init(items: [LiveEntity] = [], total: Int = 0, page: Int = 1, pageSize: Int = 10) {
self.items = items
self.total = total
self.page = page
self.pageSize = pageSize
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
items = (try? container.decode([LiveEntity].self, forKey: .items)) ?? []
total = try container.liveDecodeLossyInt(forKey: .total) ?? 0
page = try container.liveDecodeLossyInt(forKey: .page) ?? 1
pageSize = try container.liveDecodeLossyInt(forKey: .pageSize) ?? 10
}
}
///
struct LiveEntity: Decodable, Identifiable, Equatable {
let id: Int
let title: String
let coverImg: String
let pushUrl: String
let playUrl: String
let pullUrl: String
let hlsUrl: String
let flvUrl: String
let liveUrl: 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 playUrl = "play_url"
case pullUrl = "pull_url"
case hlsUrl = "hls_url"
case flvUrl = "flv_url"
case liveUrl = "live_url"
case startTime = "start_time"
case endTime = "end_time"
case duration
case status
case statusLabel = "status_label"
case manualPushMode = "manaul_push_mode"
case manualPushState = "manaul_push_state"
case viewsCount = "views_count"
}
init(
id: Int = 0,
title: String = "",
coverImg: String = "",
pushUrl: String = "",
playUrl: String = "",
pullUrl: String = "",
hlsUrl: String = "",
flvUrl: String = "",
liveUrl: 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.playUrl = playUrl
self.pullUrl = pullUrl
self.hlsUrl = hlsUrl
self.flvUrl = flvUrl
self.liveUrl = liveUrl
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.liveDecodeLossyInt(forKey: .id) ?? 0
title = try container.liveDecodeLossyString(forKey: .title)
coverImg = try container.liveDecodeLossyString(forKey: .coverImg)
pushUrl = try container.liveDecodeLossyString(forKey: .pushUrl)
playUrl = try container.liveDecodeLossyString(forKey: .playUrl)
pullUrl = try container.liveDecodeLossyString(forKey: .pullUrl)
hlsUrl = try container.liveDecodeLossyString(forKey: .hlsUrl)
flvUrl = try container.liveDecodeLossyString(forKey: .flvUrl)
liveUrl = try container.liveDecodeLossyString(forKey: .liveUrl)
startTime = Int64(try container.liveDecodeLossyInt(forKey: .startTime) ?? 0)
endTime = Int64(try container.liveDecodeLossyInt(forKey: .endTime) ?? 0)
duration = Int64(try container.liveDecodeLossyInt(forKey: .duration) ?? 0)
status = try container.liveDecodeLossyInt(forKey: .status) ?? 0
statusLabel = try container.liveDecodeLossyString(forKey: .statusLabel)
manualPushMode = try container.liveDecodeLossyInt(forKey: .manualPushMode) ?? 1
manualPushState = try container.liveDecodeLossyInt(forKey: .manualPushState) ?? 0
viewsCount = try container.liveDecodeLossyInt(forKey: .viewsCount) ?? 0
}
var displayStatus: String {
statusLabel.liveNonEmpty ?? "状态\(status)"
}
var playbackURLCandidates: [String] {
[playUrl, pullUrl, hlsUrl, liveUrl, flvUrl]
}
}
///
struct LiveCreateRequest: Encodable, 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, Equatable {
let liveId: Int
enum CodingKeys: String, CodingKey {
case liveId = "live_id"
}
}
///
struct LivePushModeRequest: Encodable, Equatable {
let liveId: Int
let manualPushMode: Int
enum CodingKeys: String, CodingKey {
case liveId = "live_id"
case manualPushMode = "manual_push_mode"
}
}
///
struct LiveAlbumFolderListResponse: Decodable {
let items: [LiveAlbumFolderItem]
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: [LiveAlbumFolderItem] = [], 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
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
items = (try? container.decode([LiveAlbumFolderItem].self, forKey: .items)) ?? []
page = try container.liveDecodeLossyInt(forKey: .page) ?? 1
pageSize = try container.liveDecodeLossyInt(forKey: .pageSize) ?? 10
total = try container.liveDecodeLossyInt(forKey: .total) ?? 0
totalPages = try container.liveDecodeLossyInt(forKey: .totalPages) ?? 0
}
}
///
struct LiveAlbumFolderItem: Decodable, Identifiable, Equatable {
let id: Int
let albumId: Int
let name: String
let creator: String
let items: [LiveAlbumFileItem]
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: [LiveAlbumFileItem] = []) {
self.id = id
self.albumId = albumId
self.name = name
self.creator = creator
self.items = items
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.liveDecodeLossyInt(forKey: .id) ?? 0
albumId = try container.liveDecodeLossyInt(forKey: .albumId) ?? 0
name = try container.liveDecodeLossyString(forKey: .name)
creator = try container.liveDecodeLossyString(forKey: .creator)
items = (try? container.decode([LiveAlbumFileItem].self, forKey: .items)) ?? []
}
}
///
struct LiveAlbumFileItem: Decodable, Identifiable, Hashable {
let id: Int
let url: String
let type: Int
let size: Int64
let coverImg: String?
enum CodingKeys: String, CodingKey {
case id
case url
case type
case size
case coverImg = "cover_img"
}
init(id: Int = 0, url: String = "", type: Int = 1, size: Int64 = 0, coverImg: String? = nil) {
self.id = id
self.url = url
self.type = type
self.size = size
self.coverImg = coverImg
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.liveDecodeLossyInt(forKey: .id) ?? 0
url = try container.liveDecodeLossyString(forKey: .url)
type = try container.liveDecodeLossyInt(forKey: .type) ?? 1
size = Int64(try container.liveDecodeLossyInt(forKey: .size) ?? 0)
coverImg = try? container.decodeIfPresent(String.self, forKey: .coverImg)
}
var isVideo: Bool { type == 2 }
var previewURL: String {
if let cover = coverImg?.liveTrimmed, !cover.isEmpty {
return cover
}
return url
}
}
///
struct LiveAlbumCreateFolderRequest: Encodable, Equatable {
let scenicId: String
let name: String
let items: [LiveAlbumCreateFileItem]
enum CodingKeys: String, CodingKey {
case scenicId = "scenic_id"
case name
case items
}
}
///
struct LiveAlbumCreateFileItem: Encodable, Equatable {
let url: String
let type: Int
let size: Int64
let coverImg: String?
enum CodingKeys: String, CodingKey {
case url
case type
case size
case coverImg = "cover_img"
}
}
///
struct LiveAlbumDeleteFolderRequest: Encodable, Equatable {
let folderId: Int
enum CodingKeys: String, CodingKey {
case folderId = "folder_id"
}
}
///
struct LiveAlbumDeleteFilesRequest: Encodable, Equatable {
let folderId: Int
let fileIds: [Int]
enum CodingKeys: String, CodingKey {
case folderId = "folder_id"
case fileIds = "file_ids"
}
}
///
struct LiveAlbumLocalUploadFile: Identifiable, Equatable {
let id: UUID
let data: Data
let fileName: String
let fileType: Int
let size: Int64
var uploadedURL: String?
init(id: UUID = UUID(), data: Data, fileName: String, fileType: Int, size: Int64? = nil, uploadedURL: String? = nil) {
self.id = id
self.data = data
self.fileName = fileName
self.fileType = fileType
self.size = size ?? Int64(data.count)
self.uploadedURL = uploadedURL
}
var isVideo: Bool { fileType == 2 }
}
private extension KeyedDecodingContainer {
func liveDecodeLossyString(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)
}
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
return value ? "1" : "0"
}
return ""
}
func liveDecodeLossyInt(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(Double.self, forKey: key) {
return Int(value)
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
if let intValue = Int(trimmed) {
return intValue
}
if let doubleValue = Double(trimmed) {
return Int(doubleValue)
}
}
return nil
}
}
extension String {
/// 使
var liveTrimmed: String {
trimmingCharacters(in: .whitespacesAndNewlines)
}
/// 使
var liveNonEmpty: String? {
let value = liveTrimmed
return value.isEmpty ? nil : value
}
}