Files
simvision/KSPlayer-main/Sources/KSPlayer/Video/KSPlayerItem.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

148 lines
5.1 KiB
Swift

//
// KSPlayerItem.swift
// Pods
//
// Created by kintan on 16/5/21.
//
//
import AVFoundation
import Foundation
import MediaPlayer
public class KSPlayerResource: Equatable, Hashable {
public static func == (lhs: KSPlayerResource, rhs: KSPlayerResource) -> Bool {
lhs.definitions == rhs.definitions
}
public let name: String
public let definitions: [KSPlayerResourceDefinition]
public let cover: URL?
public let subtitleDataSouce: SubtitleDataSouce?
public var nowPlayingInfo: KSNowPlayableMetadata?
public let extinf: [String: String]?
/**
Player recource item with url, used to play single difinition video
- parameter name: video name
- parameter url: video url
- parameter cover: video cover, will show before playing, and hide when play
- parameter subtitleURLs: video subtitles
*/
public convenience init(url: URL, options: KSOptions = KSOptions(), name: String = "", cover: URL? = nil, subtitleURLs: [URL]? = nil, extinf: [String: String]? = nil) {
let definition = KSPlayerResourceDefinition(url: url, definition: "", options: options)
let subtitleDataSouce: URLSubtitleDataSouce?
if let subtitleURLs {
subtitleDataSouce = URLSubtitleDataSouce(urls: subtitleURLs)
} else {
subtitleDataSouce = nil
}
self.init(name: name, definitions: [definition], cover: cover, subtitleDataSouce: subtitleDataSouce, extinf: extinf)
}
/**
Play resouce with multi definitions
- parameter name: video name
- parameter definitions: video definitions
- parameter cover: video cover
- parameter subtitle: video subtitle
*/
public init(name: String, definitions: [KSPlayerResourceDefinition], cover: URL? = nil, subtitleDataSouce: SubtitleDataSouce? = nil, extinf: [String: String]? = nil) {
self.name = name
self.cover = cover
self.subtitleDataSouce = subtitleDataSouce
self.definitions = definitions
self.extinf = extinf
nowPlayingInfo = KSNowPlayableMetadata(title: name)
}
public func hash(into hasher: inout Hasher) {
hasher.combine(definitions)
}
}
extension KSPlayerResource: Identifiable {
public var id: KSPlayerResource { self }
}
public struct KSPlayerResourceDefinition: Hashable {
public static func == (lhs: KSPlayerResourceDefinition, rhs: KSPlayerResourceDefinition) -> Bool {
lhs.url == rhs.url
}
public let url: URL
public let definition: String
public let options: KSOptions
public init(url: URL) {
self.init(url: url, definition: url.lastPathComponent)
}
/**
Video recource item with defination name and specifying options
- parameter url: video url
- parameter definition: url deifination
- parameter options: specifying options for the initialization of the AVURLAsset
*/
public init(url: URL, definition: String, options: KSOptions = KSOptions()) {
self.url = url
self.definition = definition
self.options = options
}
public func hash(into hasher: inout Hasher) {
hasher.combine(url)
}
}
extension KSPlayerResourceDefinition: Identifiable {
public var id: Self { self }
}
public struct KSNowPlayableMetadata {
private let mediaType: MPNowPlayingInfoMediaType?
private let isLiveStream: Bool?
private let title: String
private let artist: String?
private let artwork: MPMediaItemArtwork?
private let albumArtist: String?
private let albumTitle: String?
var nowPlayingInfo: [String: Any] {
var nowPlayingInfo = [String: Any]()
nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = mediaType?.rawValue
nowPlayingInfo[MPNowPlayingInfoPropertyIsLiveStream] = isLiveStream
nowPlayingInfo[MPMediaItemPropertyTitle] = title
nowPlayingInfo[MPMediaItemPropertyArtist] = artist
if #available(OSX 10.13.2, *) {
nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
}
nowPlayingInfo[MPMediaItemPropertyAlbumArtist] = albumArtist
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = albumTitle
return nowPlayingInfo
}
public init(mediaType: MPNowPlayingInfoMediaType? = nil, isLiveStream: Bool? = nil, title: String, artist: String? = nil,
artwork: MPMediaItemArtwork? = nil, albumArtist: String? = nil, albumTitle: String? = nil)
{
self.mediaType = mediaType
self.isLiveStream = isLiveStream
self.title = title
self.artist = artist
self.artwork = artwork
self.albumArtist = albumArtist
self.albumTitle = albumTitle
}
public init(mediaType: MPNowPlayingInfoMediaType? = nil, isLiveStream: Bool? = nil, title: String, artist: String? = nil, image: UIImage, albumArtist: String? = nil, albumTitle: String? = nil) {
self.mediaType = mediaType
self.isLiveStream = isLiveStream
self.title = title
self.artist = artist
self.albumArtist = albumArtist
self.albumTitle = albumTitle
artwork = MPMediaItemArtwork(boundsSize: image.size) { _ in image }
}
}