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>
This commit is contained in:
378
KSPlayer-main/Sources/KSPlayer/AVPlayer/PlayerDefines.swift
Normal file
378
KSPlayer-main/Sources/KSPlayer/AVPlayer/PlayerDefines.swift
Normal file
@@ -0,0 +1,378 @@
|
||||
//
|
||||
// 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 {}
|
||||
}
|
||||
Reference in New Issue
Block a user