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>
359 lines
14 KiB
Markdown
359 lines
14 KiB
Markdown

|
||

|
||

|
||
# KSPlayer
|
||
|
||
KSPlayer is a powerful media play framework for iOS, tvOS, macOS, xrOS, visionOS, Mac Catalyst. based on AVPlayer and FFmpeg, support AppKit/UIKit/SwiftUI.
|
||
|
||
English | [简体中文](./README_CN.md)
|
||
|
||
## Communication
|
||
|
||
If you have a commercial project that requires a custom player, or would like to receive a paid consultation, please email me.
|
||
|
||
- Email : kingslay@icloud.com
|
||
|
||
## License
|
||
KSPlayer defaults to the GPL license (requires open-sourcing your own project code), and we hope everyone will consciously respect the licensing agreement of the KSPlayer project. Additionally, there is a paid version that adopts the LGPL license (contact us).
|
||
|
||
If due to commercial reasons, you prefer not to adhere to the GPL license or the LGPL license, you can contact us. Through our authorization, you can obtain a more flexible licensing agreement.
|
||
|
||
## Features
|
||
Functional differences between GPL version and LGPL version
|
||
Some features of the LGPL version require a one-time payment, which I have used 💰 to mark them out.
|
||
|
||
| Feature | LGPL | GPL |
|
||
| ----------- | --------- | ------ |
|
||
|Dolby AC-4|✅|❌|
|
||
|Swift Concurrency|✅|❌|
|
||
|AV1 hardware decoding|✅|❌|
|
||
|Word-by-word subtitles|✅|❌|
|
||
|All demuxers, All decoders|✅|❌|
|
||
|Use System Caption Appearance|✅|❌|
|
||
|Record video clips at any time|✅|❌|
|
||
|Smoothly Play 8K or 120 FPS Video|✅|❌|
|
||
|Video download and format conversion|✅|❌|
|
||
|External image subtitles, such as SUP|✅|❌|
|
||
|Main subtitles and Secondary subtitles|✅|❌|
|
||
|Picture in Picture supports subtitle display|✅|❌|
|
||
|Annex-B async hardware decoding(Live Stream)|✅|❌|
|
||
|Use the fonts in the video to render subtitles|✅|❌|
|
||
|Use memory cache for fast seek in short time range|✅|❌|
|
||
|Full display of ass subtitles effect(Render as image using libass)|✅|❌|
|
||
|FFmpeg version|8.0.1|6.1.0|
|
||
|Video upscaling |💰|❌|
|
||
|Precache data to Hard Drive|💰|❌|
|
||
|Video switching with zero delay|💰|❌|
|
||
|Dovi P5 displays HDR (not overheating)|💰|❌|
|
||
|Live streaming supports rewind viewing|💰|❌|
|
||
|ISO Blu-ray disc playback on all Apple platforms|💰|❌|
|
||
|Simultaneous playback of separate audio and video URLs|💰|❌|
|
||
|Offline AI real-time subtitle generation and translation|💰|❌|
|
||
|Play videos in a small window in the App (resumable, supports macOS、iOS and tvOS)|💰|❌|
|
||
|Record video|✅|✅|
|
||
|360° panorama video|✅|✅|
|
||
|Picture in Picture|✅|✅|
|
||
|Hardware accelerator|✅|✅|
|
||
|Seamless loop playback|✅|✅|
|
||
|De-interlace auto detect|✅|✅|
|
||
|Multichannel Audio/Spatial Audio|✅|✅|
|
||
|4k/HDR/HDR10/HDR10+/Dolby Vision|✅|✅|
|
||
|Custom url protocols such as nfs/smb/UPnP |✅|✅|
|
||
|Text subtitle/Image subtitle/Closed Captions|✅|✅|
|
||
|Search Online Subtitles(shooter/assrt/opensubtitles)|✅|✅|
|
||
|Low latency 4K live video streaming (less than 200ms on LAN)|✅|✅|
|
||
|Automatically switch to multi-bitrate streams based on network|✅|✅|
|
||
|
||
|
||
## Requirements
|
||
|
||
- iOS 13+, macOS 10.15+, tvOS 13+, xrOS 1+
|
||
|
||
## The list of App using this SDK
|
||
| App Store Link | Logo |
|
||
| -------------- | ---- |
|
||
|[APTV](https://apps.apple.com/app/aptv/id1630403500)||
|
||
|[homeTV IPTV Player](https://apps.apple.com/app/hometv-iptv-player/id1636701357)||
|
||
|[IPTV +](https://apps.apple.com/app/iptv-my-smart-iptv-player/id1525121231)||
|
||
|[LillyPlayer Video Player](https://apps.apple.com/app/lillyplayer-video-player/id1446967273)||
|
||
|[SenPlayer](https://apps.apple.com/app/senplayer-hdr-media-player/id6443975850)||
|
||
|[Smart IPTV](https://apps.apple.com/app/smart-iptv-tv-and-movies-ott/id1492738910)||
|
||
|[Snappier IPTV](https://apps.apple.com/app/snappier-iptv/id1579702567)||
|
||
|[Spatial Video Studio](https://apps.apple.com/app/id6523429904)||
|
||
|[SWIPTV - IPTV Smart Player](https://apps.apple.com/app/swiptv-iptv-smart-player/id1658538188)||
|
||
|[TracyPlayer](https://apps.apple.com/app/tracyplayer/id6450770064)||
|
||
|[UHF - Love your IPTV](https://apps.apple.com/app/uhf-love-your-iptv/id6443751726)||
|
||
|[Zen IPTV](https://apps.apple.com/fr/app/zen-iptv/id6458223193)||
|
||
|
||
|
||
## Demo
|
||
|
||
```bash
|
||
cd Demo
|
||
pod install
|
||
```
|
||
- Open Demo/Demo.xcworkspace with Xcode.
|
||
|
||
## Quick Start
|
||
|
||
#### CocoaPods
|
||
|
||
Make sure to use the latest version **cocoapods 1.10.1+**, which can be installed using the command `brew install cocoapods`
|
||
|
||
```ruby
|
||
target 'ProjectName' do
|
||
use_frameworks!
|
||
pod 'KSPlayer',:git => 'https://github.com/kingslay/KSPlayer.git', :branch => 'main'
|
||
pod 'DisplayCriteria',:git => 'https://github.com/kingslay/KSPlayer.git', :branch => 'main'
|
||
pod 'FFmpegKit',:git => 'https://github.com/kingslay/FFmpegKit.git', :branch => 'main'
|
||
pod 'Libass',:git => 'https://github.com/kingslay/FFmpegKit.git', :branch => 'main'
|
||
end
|
||
```
|
||
|
||
### Swift Package Manager
|
||
|
||
```swift
|
||
dependencies: [
|
||
.package(url: "https://github.com/kingslay/KSPlayer.git", .branch("main"))
|
||
]
|
||
```
|
||
|
||
## Usage
|
||
|
||
#### Initialization
|
||
|
||
```swift
|
||
KSOptions.secondPlayerType = KSMEPlayer.self
|
||
playerView = IOSVideoPlayerView()
|
||
view.addSubview(playerView)
|
||
playerView.translatesAutoresizingMaskIntoConstraints = false
|
||
NSLayoutConstraint.activate([
|
||
playerView.topAnchor.constraint(equalTo: view.readableContentGuide.topAnchor),
|
||
playerView.leftAnchor.constraint(equalTo: view.leftAnchor),
|
||
playerView.rightAnchor.constraint(equalTo: view.rightAnchor),
|
||
playerView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||
])
|
||
playerView.backBlock = { [unowned self] in
|
||
if UIApplication.shared.statusBarOrientation.isLandscape {
|
||
self.playerView.updateUI(isLandscape: false)
|
||
} else {
|
||
self.navigationController?.popViewController(animated: true)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Setting up a regular video
|
||
|
||
```swift
|
||
playerView.set(url:URL(string: "http://baobab.wdjcdn.com/14525705791193.mp4")!)
|
||
playerView.set(resource: KSPlayerResource(url: url, name: name!, cover: URL(string: "http://img.wdjimg.com/image/video/447f973848167ee5e44b67c8d4df9839_0_0.jpeg"), subtitleURL: URL(string: "http://example.ksplay.subtitle")))
|
||
```
|
||
|
||
#### Multi-definition, with cover video
|
||
|
||
```swift
|
||
let res0 = KSPlayerResourceDefinition(url: URL(string: "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")!,
|
||
definition: "高清")
|
||
let res1 = KSPlayerResourceDefinition(url: URL(string: "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")!,
|
||
definition: "标清")
|
||
|
||
let asset = KSPlayerResource(name: "Big Buck Bunny",
|
||
definitions: [res0, res1],
|
||
cover: URL(string: "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Big_buck_bunny_poster_big.jpg/848px-Big_buck_bunny_poster_big.jpg"))
|
||
playerView.set(resource: asset)
|
||
```
|
||
|
||
#### Setting up an HTTP header
|
||
|
||
```swift
|
||
let options = KSOptions()
|
||
options.appendHeader(["Referer":"https:www.xxx.com"])
|
||
let definition = KSPlayerResourceDefinition(url: URL(string: "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")!,
|
||
definition: "高清",
|
||
options: options)
|
||
let asset = KSPlayerResource(name: "Video Name",
|
||
definitions: [definition])
|
||
playerView.set(resource: asset)
|
||
```
|
||
|
||
#### Listening status change
|
||
|
||
```swift
|
||
// Listen to play time change
|
||
playerView.playTimeDidChange = { (currentTime: TimeInterval, totalTime: TimeInterval) in
|
||
print("playTimeDidChange currentTime: \(currentTime) totalTime: \(totalTime)")
|
||
}
|
||
|
||
// Delegates
|
||
public protocol PlayerControllerDelegate: class {
|
||
func playerController(state: KSPlayerState)
|
||
func playerController(currentTime: TimeInterval, totalTime: TimeInterval)
|
||
func playerController(finish error: Error?)
|
||
func playerController(maskShow: Bool)
|
||
func playerController(action: PlayerButtonType)
|
||
// `bufferedCount: 0` indicates first time loading
|
||
func playerController(bufferedCount: Int, consumeTime: TimeInterval)
|
||
}
|
||
```
|
||
|
||
## Advanced Usage
|
||
|
||
- ### Inherits PlayerView's custom play logic and UI.
|
||
|
||
```swift
|
||
class CustomVideoPlayerView: IOSVideoPlayerView {
|
||
override func updateUI(isLandscape: Bool) {
|
||
super.updateUI(isLandscape: isLandscape)
|
||
toolBar.playbackRateButton.isHidden = true
|
||
}
|
||
|
||
override func onButtonPressed(type: PlayerButtonType, button: UIButton) {
|
||
if type == .landscape {
|
||
// Your own button press behaviour here
|
||
} else {
|
||
super.onButtonPressed(type: type, button: button)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
|
||
- ### Selecting Tracks
|
||
|
||
```swift
|
||
override open func player(layer: KSPlayerLayer, state: KSPlayerState) {
|
||
super.player(layer: layer, state: state)
|
||
if state == .readyToPlay, let player = layer.player {
|
||
let tracks = player.tracks(mediaType: .audio)
|
||
let track = tracks[1]
|
||
/// the name of the track
|
||
let name = track.name
|
||
/// the language of the track
|
||
let language = track.language
|
||
/// selecting the one
|
||
player.select(track: track)
|
||
}
|
||
}
|
||
```
|
||
|
||
- ### Set the properties in KSOptions
|
||
|
||
```swift
|
||
open class KSOptions {
|
||
/// 最低缓存视频时间
|
||
@Published
|
||
public var preferredForwardBufferDuration = KSOptions.preferredForwardBufferDuration
|
||
/// 最大缓存视频时间
|
||
public var maxBufferDuration = KSOptions.maxBufferDuration
|
||
/// 是否开启秒开
|
||
public var isSecondOpen = KSOptions.isSecondOpen
|
||
/// 开启精确seek
|
||
public var isAccurateSeek = KSOptions.isAccurateSeek
|
||
/// Applies to short videos only
|
||
public var isLoopPlay = KSOptions.isLoopPlay
|
||
/// 是否自动播放,默认false
|
||
public var isAutoPlay = KSOptions.isAutoPlay
|
||
/// seek完是否自动播放
|
||
public var isSeekedAutoPlay = KSOptions.isSeekedAutoPlay
|
||
/*
|
||
AVSEEK_FLAG_BACKWARD: 1
|
||
AVSEEK_FLAG_BYTE: 2
|
||
AVSEEK_FLAG_ANY: 4
|
||
AVSEEK_FLAG_FRAME: 8
|
||
*/
|
||
public var seekFlags = Int32(0)
|
||
// ffmpeg only cache http
|
||
public var cache = false
|
||
public var outputURL: URL?
|
||
public var display = DisplayEnum.plane
|
||
public var avOptions = [String: Any]()
|
||
public var formatContextOptions = [String: Any]()
|
||
public var decoderOptions = [String: Any]()
|
||
public var probesize: Int64?
|
||
public var maxAnalyzeDuration: Int64?
|
||
public var lowres = UInt8(0)
|
||
public var startPlayTime: TimeInterval = 0
|
||
public var startPlayRate: Float = 1.0
|
||
public var registerRemoteControll: Bool = true // 默认支持来自系统控制中心的控制
|
||
public var referer: String?
|
||
public var userAgent: String?
|
||
// audio
|
||
public var audioFilters = [String]()
|
||
public var syncDecodeAudio = false
|
||
// sutile
|
||
public var autoSelectEmbedSubtitle = true
|
||
public var subtitleDisable = false
|
||
public var isSeekImageSubtitle = false
|
||
// video
|
||
public var videoDelay = 0.0 // s
|
||
public var autoDeInterlace = false
|
||
public var autoRotate = true
|
||
public var destinationDynamicRange: DynamicRange?
|
||
public var videoAdaptable = true
|
||
public var videoFilters = [String]()
|
||
public var syncDecodeVideo = false
|
||
public var hardwareDecode = KSOptions.hardwareDecode
|
||
public var asynchronousDecompression = true
|
||
public var videoDisable = false
|
||
public var canStartPictureInPictureAutomaticallyFromInline = true
|
||
}
|
||
|
||
```
|
||
|
||
|
||
## Effect
|
||
|
||

|
||
|
||
## Developments and Tests
|
||
|
||
Any contributing and pull requests are warmly welcome. However, before you plan to implement some features or try to fix an uncertain issue, it is recommended to open a discussion first. It would be appreciated if your pull requests could build and with all tests green. :)
|
||
|
||
|
||
## Backers & Sponsors
|
||
|
||
Open-source projects cannot live long without your help. If you find KSPlayer to be useful, please consider supporting this
|
||
project by becoming a sponsor.
|
||
|
||
Become a sponsor through [GitHub Sponsors](https://github.com/sponsors/kingslay/). :heart:
|
||
|
||
Your user icon or company logo shows up this with a link to your home page.
|
||
|Name| App name | App Logo |
|
||
| ----------- | ----------- |----------- |
|
||
|[UnknownCoder807](https://github.com/UnknownCoder807)|[Snappier](https://apps.apple.com/app/snappier-iptv/id1579702567)||
|
||
|[skrew](https://github.com/skrew)||
|
||
|[Kimentanm](https://github.com/Kimentanm)||
|
||
|[nakiostudio](https://github.com/nakiostudio)|[UHF](https://apps.apple.com/app/uhf-love-your-iptv/id6443751726)||
|
||
|[CodingByJerez](https://github.com/CodingByJerez)||
|
||
|[andrefmsilva](https://github.com/andrefmsilva)||
|
||
|[romaingyh](https://github.com/romaingyh)|[Zen IPTV](https://apps.apple.com/fr/app/zen-iptv/id6458223193)||
|
||
|[FantasyKingdom](https://github.com/FantasyKingdom)|[Senplayer](https://apps.apple.com/us/app/senplayer-hdr-media-player/id6443975850)||
|
||
|[aart-rainey](https://github.com/aart-rainey)||
|
||
|[nihalahmed](https://github.com/nihalahmed)||
|
||
|[johnil](https://github.com/johnil)||
|
||
|[MeloDreek](https://github.com/MeloDreek)||
|
||
|[nsplay1990](https://github.com/nsplay1990)||
|
||
|[AppleChillVibez](https://github.com/AppleChillVibez)||
|
||
|[stekc](https://github.com/stekc)||
|
||
|[AstroChivs](https://github.com/AstroChivs)||
|
||
|[bmob222](https://github.com/bmob222)||
|
||
|[pateltejas](https://github.com/pateltejas)||
|
||
|[ewanl2001](https://github.com/ewanl2001)||
|
||
|[themisterholliday](https://github.com/themisterholliday)||
|
||
|[JulienDev](https://github.com/JulienDev)||
|
||
|[Sheinices](https://github.com/Sheinices)||
|
||
|[Etheirystech](https://github.com/Etheirystech)||
|
||
|[loicleser](https://github.com/loicleser)||
|
||
|
||
|
||
Thanks to [nightfall708](https://github.com/nightfall708) for sponsoring a mac mini
|
||
|
||
Thanks to [cdguy](https://github.com/cdguy) [UnknownCoder807](https://github.com/UnknownCoder807) [skrew](https://github.com/skrew) and LillyPlayer community for sponsoring a LG S95QR Sound Bar
|
||
|
||
Thanks to [skrew](https://github.com/skrew) and LillyPlayer community for sponsoring a 2022 Apple TV 4K
|
||
|
||
Thanks to [bgoncal](https://github.com/bgoncal) for sponsoring a HomePod mini
|
||
|
||

|