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:
203
KSPlayer-main/README_CN.md
Normal file
203
KSPlayer-main/README_CN.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# KSPlayer
|
||||
|
||||
## 一、介绍
|
||||
KSPlayer是一款基于 AVPlayer, FFmpeg 纯Swift的音视频播放器,支持所有视频格式和全景视频,支持苹果全平台。实现高可用,高性能的音视频播放能力。它包含UI控件模块、字幕模块、播放器内核模块。这些模块都是解耦的,可以通过pod按需接入。
|
||||
|
||||
[原理详解](./Documents/KSPlayer原理详解.md)
|
||||
|
||||
## 二、功能
|
||||
- [x] 首屏秒开
|
||||
- [x] 无缝循环播放
|
||||
- [x] 精确seek
|
||||
- [x] 支持外挂字幕、内挂字幕
|
||||
- [x] 倍速播放
|
||||
- [x] 支持iOS、macOS、tvOS
|
||||
- [x] 支持360°全景视频
|
||||
- [x] 使用Metal进行渲染
|
||||
- [x] 支持所有媒体格式(自动切换KSAVPlayer和KSMEPlayer)
|
||||
- [x] 支持横竖屏切换,支持自动旋转屏幕
|
||||
- [x] 右侧 1/2 位置上下滑动调节屏幕亮度(模拟器调不了亮度,请在真机调试)
|
||||
- [x] 左侧 1/2 位置上下滑动调节音量(模拟器调不了音量,请在真机调试)
|
||||
- [x] 左右滑动调节播放进度
|
||||
- [x] 清晰度切换
|
||||
- [x] H.264 H.265 硬件解码(VideoToolBox)
|
||||
- [x] 首屏耗时,缓冲次数,缓冲时长监控
|
||||
- [x] AirPlay
|
||||
- [x] Bitcode
|
||||
|
||||
## 三、要求
|
||||
- iOS 13 +, macOS 10.15 +, tvOS 13 +
|
||||
|
||||
|
||||
## 四、安装
|
||||
### CocoaPods
|
||||
|
||||
确保使用最新版本 **cocoapods 1.10.1+, 可以使用命令 ` brew install cocoapods` 来安装
|
||||
|
||||
```ruby
|
||||
target 'ProjectName' do
|
||||
use_frameworks!
|
||||
pod 'KSPlayer',:git => 'https://github.com/kingslay/KSPlayer.git', :branch => 'develop'
|
||||
pod 'FFmpegKit',:git => 'https://github.com/kingslay/FFmpegKit.git', :branch => 'main'
|
||||
pod 'OpenSSL',:git => 'https://github.com/kingslay/FFmpegKit.git', :branch => 'main'
|
||||
end
|
||||
```
|
||||
|
||||
#### Swift Package Manager
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/kingslay/KSPlayer.git", .branch("develop"))
|
||||
]
|
||||
```
|
||||
|
||||
### Demo
|
||||
|
||||
进Demo目录打开Demo.xcworkspace。
|
||||
|
||||
## License
|
||||
KSPlayer默认采用 GPL 协议(需开源自己的项目代码),希望大家能自觉尊重 KSPlayer 项目的授权协议。另外有一个付费版本是采用LGPL协议。(联系我们)
|
||||
|
||||
如果由于商业原因,不希望遵守 GPL 协议或 LGPL 协议,那你可以联系我们,经过我们的授权,你可以拥有更加宽松的授权协议。邮箱: kingslay@icloud.com
|
||||
|
||||
## 五、使用
|
||||
|
||||
### 初始化
|
||||
|
||||
```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)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 设置普通视频
|
||||
|
||||
```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"), subtitle: nil))
|
||||
```
|
||||
|
||||
### 多清晰度,带封面视频
|
||||
|
||||
```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)
|
||||
```
|
||||
### 设置 HTTP header
|
||||
|
||||
```swift
|
||||
let header = ["User-Agent":"KSPlayer"]
|
||||
let options = KSOptions()
|
||||
options.avOptions = ["AVURLAssetHTTPHeaderFieldsKey":header]
|
||||
|
||||
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)
|
||||
```
|
||||
|
||||
### 监听状态变化
|
||||
```swift
|
||||
//Listen to when the play time changes
|
||||
playerView.playTimeDidChange = { (currentTime: TimeInterval, totalTime: TimeInterval) in
|
||||
print("playTimeDidChange currentTime: \(currentTime) totalTime: \(totalTime)")
|
||||
}
|
||||
///协议方式
|
||||
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代表首次加载
|
||||
func playerController(bufferedCount: Int, consumeTime: TimeInterval)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 六、进阶用法
|
||||
- 继承 PlayerView 自定义播放逻辑和UI。
|
||||
|
||||
- 设置KSOptions 里面的属性
|
||||
|
||||
```swift
|
||||
open class KSOptions {
|
||||
// public static let shared = 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(1)
|
||||
// ffmpeg only cache http
|
||||
public var cache = false
|
||||
public var outputURL: URL?
|
||||
public var display = DisplayEnum.plane
|
||||
public var videoDelay = 0.0 // s
|
||||
public var videoDisable = false
|
||||
public var audioFilters: String?
|
||||
public var videoFilters: String?
|
||||
public var subtitleDisable = false
|
||||
public var videoAdaptable = true
|
||||
public var syncDecodeAudio = false
|
||||
public var syncDecodeVideo = false
|
||||
public var avOptions = [String: Any]()
|
||||
public var formatContextOptions = [String: Any]()
|
||||
public var hardwareDecode = true
|
||||
public var decoderOptions = [String: Any]()
|
||||
public var probesize: Int64?
|
||||
public var maxAnalyzeDuration: Int64?
|
||||
public var lowres = UInt8(0)
|
||||
public var autoSelectEmbedSubtitle = true
|
||||
public var asynchronousDecompression = false
|
||||
public var autoDeInterlace = false
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 七、效果:
|
||||
|
||||

|
||||
|
||||
Reference in New Issue
Block a user