Files
simvision/KSPlayer-main/Sources/KSPlayer/AVPlayer/PlayerDefines.swift
Michael Simard 872354b834 Initial commit: SimVision tvOS streaming app
Features:
- VOD library with movie grouping and version detection
- TV show library with season/episode organization
- TMDB integration for trending shows and recently aired episodes
- Recent releases section with TMDB release date sorting
- Watch history tracking with continue watching
- Playlist caching (12-hour TTL) for offline support
- M3U playlist parsing with XStream API support
- Authentication with credential storage

Technical:
- SwiftUI for tvOS
- Actor-based services for thread safety
- Persistent caching for playlists, TMDB data, and watch history
- KSPlayer integration for video playback

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 22:12:08 -06:00

379 lines
10 KiB
Swift

//
// PlayerDefines.swift
// KSPlayer
//
// Created by kintan on 2018/3/9.
//
import AVFoundation
import CoreMedia
import CoreServices
#if canImport(UIKit)
import UIKit
public extension KSOptions {
@MainActor
static var windowScene: UIWindowScene? {
UIApplication.shared.connectedScenes.first as? UIWindowScene
}
@MainActor
static var sceneSize: CGSize {
let window = windowScene?.windows.first
return window?.bounds.size ?? .zero
}
}
#else
import AppKit
import SwiftUI
public typealias UIView = NSView
public typealias UIPasteboard = NSPasteboard
public extension KSOptions {
static var sceneSize: CGSize {
NSScreen.main?.frame.size ?? .zero
}
}
#endif
// extension MediaPlayerTrack {
// static func == (lhs: Self, rhs: Self) -> Bool {
// lhs.trackID == rhs.trackID
// }
// }
public enum DynamicRange: Int32 {
case sdr = 0
case hdr10 = 2
case hlg = 3
case dolbyVision = 5
#if canImport(UIKit)
var hdrMode: AVPlayer.HDRMode {
switch self {
case .sdr:
return AVPlayer.HDRMode(rawValue: 0)
case .hdr10:
return .hdr10 // 2
case .hlg:
return .hlg // 1
case .dolbyVision:
return .dolbyVision // 4
}
}
#endif
public static var availableHDRModes: [DynamicRange] {
#if os(macOS)
if NSScreen.main?.maximumPotentialExtendedDynamicRangeColorComponentValue ?? 1.0 > 1.0 {
return [.hdr10]
} else {
return [.sdr]
}
#else
let availableHDRModes = AVPlayer.availableHDRModes
if availableHDRModes == AVPlayer.HDRMode(rawValue: 0) {
return [.sdr]
} else {
var modes = [DynamicRange]()
if availableHDRModes.contains(.dolbyVision) {
modes.append(.dolbyVision)
}
if availableHDRModes.contains(.hdr10) {
modes.append(.hdr10)
}
if availableHDRModes.contains(.hlg) {
modes.append(.hlg)
}
return modes
}
#endif
}
}
extension DynamicRange: CustomStringConvertible {
public var description: String {
switch self {
case .sdr:
return "SDR"
case .hdr10:
return "HDR10"
case .hlg:
return "HLG"
case .dolbyVision:
return "Dolby Vision"
}
}
}
extension DynamicRange {
var colorPrimaries: CFString {
switch self {
case .sdr:
return kCVImageBufferColorPrimaries_ITU_R_709_2
case .hdr10, .hlg, .dolbyVision:
return kCVImageBufferColorPrimaries_ITU_R_2020
}
}
var transferFunction: CFString {
switch self {
case .sdr:
return kCVImageBufferTransferFunction_ITU_R_709_2
case .hdr10:
return kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ
case .hlg, .dolbyVision:
return kCVImageBufferTransferFunction_ITU_R_2100_HLG
}
}
var yCbCrMatrix: CFString {
switch self {
case .sdr:
return kCVImageBufferYCbCrMatrix_ITU_R_709_2
case .hdr10, .hlg, .dolbyVision:
return kCVImageBufferYCbCrMatrix_ITU_R_2020
}
}
}
@MainActor
public enum DisplayEnum {
case plane
// swiftlint:disable identifier_name
case vr
// swiftlint:enable identifier_name
case vrBox
}
public struct VideoAdaptationState {
public struct BitRateState {
let bitRate: Int64
let time: TimeInterval
}
public let bitRates: [Int64]
public let duration: TimeInterval
public internal(set) var fps: Float
public internal(set) var bitRateStates: [BitRateState]
public internal(set) var currentPlaybackTime: TimeInterval = 0
public internal(set) var isPlayable: Bool = false
public internal(set) var loadedCount: Int = 0
}
public enum ClockProcessType {
case remain
case next
case dropNextFrame
case dropNextPacket
case dropGOPPacket
case flush
case seek
}
//
public protocol CapacityProtocol {
var fps: Float { get }
var packetCount: Int { get }
var frameCount: Int { get }
var frameMaxCount: Int { get }
var isEndOfFile: Bool { get }
var mediaType: AVFoundation.AVMediaType { get }
}
extension CapacityProtocol {
var loadedTime: TimeInterval {
TimeInterval(packetCount + frameCount) / TimeInterval(fps)
}
}
public struct LoadingState {
public let loadedTime: TimeInterval
public let progress: TimeInterval
public let packetCount: Int
public let frameCount: Int
public let isEndOfFile: Bool
public let isPlayable: Bool
public let isFirst: Bool
public let isSeek: Bool
}
public let KSPlayerErrorDomain = "KSPlayerErrorDomain"
public enum KSPlayerErrorCode: Int {
case unknown
case formatCreate
case formatOpenInput
case formatOutputCreate
case formatWriteHeader
case formatFindStreamInfo
case readFrame
case codecContextCreate
case codecContextSetParam
case codecContextFindDecoder
case codesContextOpen
case codecVideoSendPacket
case codecAudioSendPacket
case codecVideoReceiveFrame
case codecAudioReceiveFrame
case auidoSwrInit
case codecSubtitleSendPacket
case videoTracksUnplayable
case subtitleUnEncoding
case subtitleUnParse
case subtitleFormatUnSupport
case subtitleParamsEmpty
}
extension KSPlayerErrorCode: CustomStringConvertible {
public var description: String {
switch self {
case .formatCreate:
return "avformat_alloc_context return nil"
case .formatOpenInput:
return "avformat can't open input"
case .formatOutputCreate:
return "avformat_alloc_output_context2 fail"
case .formatWriteHeader:
return "avformat_write_header fail"
case .formatFindStreamInfo:
return "avformat_find_stream_info return nil"
case .codecContextCreate:
return "avcodec_alloc_context3 return nil"
case .codecContextSetParam:
return "avcodec can't set parameters to context"
case .codesContextOpen:
return "codesContext can't Open"
case .codecVideoReceiveFrame:
return "avcodec can't receive video frame"
case .codecAudioReceiveFrame:
return "avcodec can't receive audio frame"
case .videoTracksUnplayable:
return "VideoTracks are not even playable."
case .codecSubtitleSendPacket:
return "avcodec can't decode subtitle"
case .subtitleUnEncoding:
return "Subtitle encoding format is not supported."
case .subtitleUnParse:
return "Subtitle parsing error"
case .subtitleFormatUnSupport:
return "Current subtitle format is not supported"
case .subtitleParamsEmpty:
return "Subtitle Params is empty"
case .auidoSwrInit:
return "swr_init swrContext fail"
default:
return "unknown"
}
}
}
extension NSError {
convenience init(errorCode: KSPlayerErrorCode, userInfo: [String: Any] = [:]) {
var userInfo = userInfo
userInfo[NSLocalizedDescriptionKey] = errorCode.description
self.init(domain: KSPlayerErrorDomain, code: errorCode.rawValue, userInfo: userInfo)
}
convenience init(description: String) {
var userInfo = [String: Any]()
userInfo[NSLocalizedDescriptionKey] = description
self.init(domain: KSPlayerErrorDomain, code: 0, userInfo: userInfo)
}
}
#if !SWIFT_PACKAGE
extension Bundle {
static let module = Bundle(for: KSPlayerLayer.self).path(forResource: "KSPlayer_KSPlayer", ofType: "bundle").flatMap { Bundle(path: $0) } ?? Bundle.main
}
#endif
public enum TimeType {
case min
case hour
case minOrHour
case millisecond
}
public extension TimeInterval {
func toString(for type: TimeType) -> String {
Int(ceil(self)).toString(for: type)
}
}
public extension Int {
func toString(for type: TimeType) -> String {
var second = self
var min = second / 60
second -= min * 60
switch type {
case .min:
return String(format: "%02d:%02d", min, second)
case .hour:
let hour = min / 60
min -= hour * 60
return String(format: "%d:%02d:%02d", hour, min, second)
case .minOrHour:
let hour = min / 60
if hour > 0 {
min -= hour * 60
return String(format: "%d:%02d:%02d", hour, min, second)
} else {
return String(format: "%02d:%02d", min, second)
}
case .millisecond:
var time = self * 100
let millisecond = time % 100
time /= 100
let sec = time % 60
time /= 60
let min = time % 60
time /= 60
let hour = time % 60
if hour > 0 {
return String(format: "%d:%02d:%02d.%02d", hour, min, sec, millisecond)
} else {
return String(format: "%02d:%02d.%02d", min, sec, millisecond)
}
}
}
}
public extension FixedWidthInteger {
var kmFormatted: String {
Double(self).kmFormatted
}
}
open class AbstractAVIOContext {
let bufferSize: Int32
let writable: Bool
public init(bufferSize: Int32 = 32 * 1024, writable: Bool = false) {
self.bufferSize = bufferSize
self.writable = writable
}
open func read(buffer _: UnsafePointer<UInt8>?, size: Int32) -> Int32 {
size
}
open func write(buffer _: UnsafePointer<UInt8>?, size: Int32) -> Int32 {
size
}
/**
#define SEEK_SET 0 /* set file offset to offset */
#define SEEK_CUR 1 /* set file offset to current plus offset */
#define SEEK_END 2 /* set file offset to EOF plus offset */
*/
open func seek(offset: Int64, whence _: Int32) -> Int64 {
offset
}
open func fileSize() -> Int64 {
-1
}
open func close() {}
deinit {}
}