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:
2026-01-21 22:12:08 -06:00
commit 872354b834
283 changed files with 338296 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -0,0 +1,98 @@
# AVPlayer踩坑记
AVPlayer是苹果平台上常用的视频播放器组件。使用简单性能好。
## 一、缓冲状态判断
有以下三个属性判断视频能否进行播放
```swift
isPlaybackBufferEmpty //
isPlaybackLikelyToKeepUp //
isPlaybackBufferFull //
```
一开始的写法是:
```swift
if playerItem.isPlaybackLikelyToKeepUp {
self.loadState = .playable
}
if playerItem.isPlaybackBufferEmpty {
self.loadState = .loading
}
```
后来发现有些高码率的视频,会出现一直加载,无法播放的情况。后来发现是因为缓存满了`isPlaybackBufferFull`的值为true, 但是`isPlaybackLikelyToKeepUp`的值还是为false。
所以改成是下面的写法:
```swift
if playerItem.isPlaybackLikelyToKeepUp || playerItem.isPlaybackBufferFull {
self.loadState = .playable
}
if playerItem.isPlaybackBufferEmpty {
self.loadState = .loading
}
```
最后发现有些视频会频繁的切换状态,分析了之后。发现 `isPlaybackBufferFull` `isPlaybackBufferEmpty`会同时为true。从字面上理解这两个值应该是要互斥的但是结果是相反。
所以最后正确的写法应该是:
```swift
if playerItem.isPlaybackBufferEmpty {
self.loadState = .loading
} else if playerItem.isPlaybackLikelyToKeepUp || playerItem.isPlaybackBufferFull {
self.loadState = .playable
}
```
## 二、视频播放黑屏
有些mp4视频在AVPlayer播放有声音但是画面黑屏。但是放在自研播放器`MEPlayer`就可以有声音又有画面了。所以解决方案就是能判断异常视频,并自动切换到`MEPlayer`
通过分析视频流发现帧率是25视频帧的格式是yuv444p。猜测应该是无法硬解yuv444p。所以导致视频帧无法显示后来通过增加AVPlayerItemOutput发现确实没有输出视频帧。
通过对每个属性进行尝试发现可以通过assetTrack的isPlayable属性来判断是否能输出视频帧
代码如下:
```swift
let videoTrack = item.tracks.first { $0.assetTrack?.mediaType.rawValue == AVMediaType.video.rawValue }
if let videoTrack = videoTrack, videoTrack.assetTrack?.isPlayable == false {
error = NSError(domain: AVFoundationErrorDomain, code: -1, userInfo: [NSLocalizedDescriptionKey: "can't player"])
return
}
```
## 三、多音轨
AVPlayer播放多音轨的时候。默认所有的音轨都会打开的。所以要改成只打开一个音轨
代码如下:
```swift
//
item.tracks.filter { $0.assetTrack?.mediaType.rawValue == AVMediaType.audio.rawValue }.dropFirst().forEach { $0.isEnabled = false }
```
## 四、AVQueuePlayer
AVQueuePlayer是AVPlayer的子类主要是用来实现视频顺序播放。也可以搭配AVPlayerLooper实现视频循环播放。
AVQueuePlayer默认是播放完一个视频就会自动切换到下个视频了。
这样就会导致一个问题,如果只是播放单个资源的话。那视频播放播放完之后,你就无法从头开始播放了。因为`currentItem`这个属性被重置为nil了。
这时你可以通过修改属性`actionAtItemEnd`的值。来改变视频播放完之后的行为。默认是`.advance`。直接改成是.`pause`或是`.none`就可以了
## 五、广告时间
以上所说的坑都已经在 [KSPlayer](https://git.code.oa.com/kintanwang/KSPlayer) 填了。KSPlayer是一款基于 AVPlayer, FFmpeg 纯Swift的音视频播放器支持所有视频格式和全景视频支持苹果全平台。实现高可用高性能的音视频播放能力。它包含UI控件模块、字幕模块、播放器内核模块。这些模块都是解耦的可以通过pod按需接入。欢迎大家试用。
#

View File

@@ -0,0 +1,147 @@
# KSPlayer 原理详解
KSPlayer 是一款基于 AVPlayer、FFmpeg 的媒体资源播放器框架。支持RTMP、RTSP 等直播流;同时支持 iOS、macOS、tvOS 三个平台。扩张性强支持自定义播放器内核和UI界面 。本文将采用图解+说明的方式把关键模块的实现原理介绍给大家。
## 一、整体架构
![1](https://github.com/kingslay/KSPlayer/raw/develop/Documents/1.png)
上图展示了 KSPlayer 的主要组件,一共分为四层。每一个层都是解耦的,都可以单独拿出来使用。下面简单介绍图中各组件的分工
### PlayerView
PlayerView 是播放器UI控件。目前有VideoPlayerViewAudioPlayerView两个子类。
VideoPlayerView根据平台特色又有IOSVideoPlayerViewMacVideoPlayerView这两个子类
### KSPlayerLayer
KSPlayerLayer 是播放内核的封装,主要工作是根据配置参数切换播放器内核,管理播放状态,
### MediaPlayerProtocol
MediaPlayerProtocol是播放器内核接口。只要遵守MediaPlayerProtocol协议的播放器内核就可以在KSPlayer使用。默认提供了两种播放器内核KSAVPlayer、KSMEPlayer
1、KSAVPlayer 是基于 AVPlayer 封装而成支持H.264、H.265、MPEG-4格式。
2、KSMEPlayer是自研播放器内核支持所有的主流视频格式。支持硬解和软解。
### 小结
了解了各组件的功能,重新梳理一下完整的播放过程
- PlayerView 收到播放请求。
- 由 KSPlayerLayer 根据配置参数分发给 KSAVPlayer 或 KSMEPlayer 进行播放。
- 如果使用 KSAVPlayer 播放,将视频画面输出给 KSAVPlayerView 中的 AVPlayerLayer 。
- 如果使用 KSMEPlayer 播放,将视频画面输出给 MetalPlayView音频输出至 AudioEnginePlayer。
通过抽象的 MediaPlayerProtocol 将真正负责播放的 KSAVPlayer 、 KSMEPlayer 屏蔽起来这样可以保证无论资源是何种类型对外仅暴露一套统一的接口和回调将播放内核间的差异内部消化尽可能降低使用成本。如果需要接入别的的播放器内核的话如ijkplayer。那只要为ijkplayer实现MediaPlayerProtocol接口即可。
## 二、KSMEPlayer组织结构
![1](https://github.com/kingslay/KSPlayer/raw/develop/Documents/2.png)
上图展示了 KSMEPlayer 的主要组件,下面简单介绍图中各组件的分工
### MEPlayerItem
视频控制的上下文,负责创建视频流,读取数据包。
### PlayerItemTrackProtocol
解码处理负责把数据包解码成数据帧。支持硬解软解。使用VideoToolbox进行视频硬解FFmpeg进行视频软硬解、音频软解、字幕软解
### AudioEnginePlayer
AudioEnginePlayer 负责声音的播放和音频事件的处理。内部使用 AVAudioEngine 做了一层混音,通过混音可以设置声音的输出音量大小和播放倍数
### MetalPlayView
MetalPlayView是视频画面绘制实现类里面会更加参数参数来决定是使用AVSampleBufferDisplayLayer还是Metal来进行绘制。Metal支持全景视频AVSampleBufferDisplayLayer支持HDR。
## 三、KSMEPlayer 运作流程
![1](https://github.com/kingslay/KSPlayer/raw/develop/Documents/3.png)
上图展示了 KSMEPlayer 的协作流程图,下面简单介绍图中各组件
### 线程模型
KSMEPlayer 中共有5个线程。与图中5个蓝色圆圈对应。
- 数据读取 - Read Packet Loop
- 视频解码 - Video Decode Loop
- 音频解码 - Audio Decode Loop
- 视频绘制 - Video Display Loop
- 音频播放 - Audio Playback Loop
这五个线程采用生产者-消费者模式。通过ObjectQueue中的数据个数来作为线程的控制条件
### PlayerItemTrackProtocol
解码处理,目前一共有两个实现类
| 类名 | 同步/异步 | 备注 |
| ----------------------- | -------- | ---- |
| SyncPlayerItemTrack | 同步 |主要用于字幕,如果是纯音频的话,也是可以用同步 |
| AsyncPlayerItemTrack | 异步 | |
#### 备注:
1、视频、音频采用异步的解码方式。是因为视频、音频解码的时间比较久除了这个还有更重要的原因视频、音频解码后的数据帧比数据包大了好几倍为了节约内存要控制数据帧的大小。
2、异步解码过程解码器收到数据包存入数据包队列当独立的解码线程取出数据包并完成解码后再存入数据帧队列
3、同步解码过程解码器收到数据包后立即解码并存入数据帧队列。
4、优先使用视频硬解当视频无法软解或硬解失败的话就自动切换到软解
### DecodeProtocol
解码器接口,目前一共有三个实现类
| 类名 | 解码类型 | 同步/异步 | 备注 |
| ----------------------- | -------- | --------- | ---- |
| FFmpegDecode | 音视频 | 同步 | 软硬解 |
| VideoToolboxDecode | 视频 | 异步 | 硬解 |
| SubtitleDecode | 字幕 | 同步 | 软解 |
### CircularBuffer 数据队列
CircularBuffer是环形队列
数据队列提供`push``pop(wait, where)` `search(where)` 三个方法
| 操作 | 行为 |
| ---------------- | ------------------------------------------------------------ |
| `put` | 队列满了有两个处理方式1.没有现在队列长度那会双倍扩展队列长度如果限制了那会阻塞当前线程直到队列只剩下1/2的数据线程才会通过`NSCondition`被唤醒。避免频繁的进行锁操作 |
| `pop(wait, where)` | 如果参数wait为true那当队列中没有数据时会阻塞当前线程直到向队列中添加新元素时线程才会通过`NSCondition`被唤醒如果wait为false那就会直接返回空 |
| `search(where)` | 只是访问队列里面的数据,不会对队列的游标产生影响 ,一般是用于文字字幕 |
CircularBuffer还支持排序因为视频有可能不是按顺序解码。所以一定要排序下不然画面会来回抖动
### 音视频同步
常用的同步当时有3种
1. 音频时钟
2. 视频时钟
3. 自制时钟
在 KSMEPlayer 中优先使用音频时钟当视频中没有音轨时或是音轨数据都播放完了会使用视频时钟进行同步。音视频同步的接口是OutputRenderSourceDelegate 具体实现类是MEPlayerItem。
### 小结
了解了各组件的功能,重新梳理一下整个流程
- 数据读取线程读取到数据包,根据数据包类型分发给音频解码器、视频解码器、字幕解码器。
- 如果是字幕包,字幕解码器收到字幕包的同时进行解码,并将解码后的字幕帧存入字幕帧队列。
- 音视频解码器收到音视频包存入音视频包队列,当独立的解码线程取出音视频包并完成解码后,再存入音视频帧队列
- 音频播放线程循环从音频帧队列中取出音频帧并播放。
- 视频展示线程循环从视频帧队列中取出视频帧并绘制。
- 字幕控件根据时间戳到字幕帧队列查找对应的字幕帧并展示,不会把字幕帧从字幕队列删除。
## 四、总结
关于 KSPlayer 的原理就阐述到这里,由于本文以理论为主,所以并没有贴代码。感兴趣的同学可以在 [GitHub](https://github.com/kingslay/KSPlayer.git) 上找到全部的代码实现。希望对大家能有所帮助。

View File

@@ -0,0 +1,58 @@
**Privacy Policy**
kingslay built the TracyPlayer app as a Free app. This SERVICE is provided by kingslay at no cost and is intended for use as is.
This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.
If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.
The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which are accessible at TracyPlayer unless otherwise defined in this Privacy Policy.
**Information Collection and Use**
For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information, including but not limited to none. The information that I request will be retained on your device and is not collected by me in any way.
**Log Data**
I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third-party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics.
**Cookies**
Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.
This Service does not use these “cookies” explicitly. However, the app may use third-party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.
**Service Providers**
I may employ third-party companies and individuals due to the following reasons:
* To facilitate our Service;
* To provide the Service on our behalf;
* To perform Service-related services; or
* To assist us in analyzing how our Service is used.
I want to inform users of this Service that these third parties have access to their Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.
**Security**
I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security.
**Links to Other Sites**
This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
**Childrens Privacy**
I do not knowingly collect personally identifiable information from children. I encourage all children to never submit any personally identifiable information through the Application and/or Services. I encourage parents and legal guardians to monitor their children's Internet usage and to help enforce this Policy by instructing their children never to provide personally identifiable information through the Application and/or Services without their permission. If you have reason to believe that a child has provided personally identifiable information to us through the Application and/or Services, please contact us. You must also be at least 16 years of age to consent to the processing of your personally identifiable information in your country (in some countries we may allow your parent or guardian to do so on your behalf).
**Changes to This Privacy Policy**
I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page.
This policy is effective as of 2023-11-17
**Contact Us**
If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at kingslay@icloud.com.
This privacy policy page was created at [privacypolicytemplate.net](https://privacypolicytemplate.net) and modified/generated by [App Privacy Policy Generator](https://app-privacy-policy-generator.nisrulz.com/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

View File

@@ -0,0 +1,29 @@
**Terms & Conditions**
By downloading or using the app, these terms will automatically apply to you you should make sure therefore that you read them carefully before using the app. Youre not allowed to copy or modify the app, any part of the app, or our trademarks in any way. Youre not allowed to attempt to extract the source code of the app, and you also shouldnt try to translate the app into other languages or make derivative versions. The app itself, and all the trademarks, copyright, database rights, and other intellectual property rights related to it, still belong to kingslay.
kingslay is committed to ensuring that the app is as useful and efficient as possible. For that reason, we reserve the right to make changes to the app or to charge for its services, at any time and for any reason. We will never charge you for the app or its services without making it very clear to you exactly what youre paying for.
The TracyPlayer app stores and processes personal data that you have provided to us, to provide my Service. Its your responsibility to keep your phone and access to the app secure. We therefore recommend that you do not jailbreak or root your phone, which is the process of removing software restrictions and limitations imposed by the official operating system of your device. It could make your phone vulnerable to malware/viruses/malicious programs, compromise your phones security features and it could mean that the TracyPlayer app wont work properly or at all.
You should be aware that there are certain things that kingslay will not take responsibility for. Certain functions of the app will require the app to have an active internet connection. The connection can be Wi-Fi or provided by your mobile network provider, but kingslay cannot take responsibility for the app not working at full functionality if you dont have access to Wi-Fi, and you dont have any of your data allowance left.
If youre using the app outside of an area with Wi-Fi, you should remember that the terms of the agreement with your mobile network provider will still apply. As a result, you may be charged by your mobile provider for the cost of data for the duration of the connection while accessing the app, or other third-party charges. In using the app, youre accepting responsibility for any such charges, including roaming data charges if you use the app outside of your home territory (i.e. region or country) without turning off data roaming. If you are not the bill payer for the device on which youre using the app, please be aware that we assume that you have received permission from the bill payer for using the app.
Along the same lines, kingslay cannot always take responsibility for the way you use the app i.e. You need to make sure that your device stays charged if it runs out of battery and you cant turn it on to avail the Service, kingslay cannot accept responsibility.
With respect to kingslays responsibility for your use of the app, when youre using the app, its important to bear in mind that although we endeavor to ensure that it is updated and correct at all times, we do rely on third parties to provide information to us so that we can make it available to you. kingslay accepts no liability for any loss, direct or indirect, you experience as a result of relying wholly on this functionality of the app.
At some point, we may wish to update the app. The app is currently available on iOS the requirements for the system(and for any additional systems we decide to extend the availability of the app to) may change, and youll need to download the updates if you want to keep using the app. kingslay does not promise that it will always update the app so that it is relevant to you and/or works with the iOS version that you have installed on your device. However, you promise to always accept updates to the application when offered to you, We may also wish to stop providing the app, and may terminate use of it at any time without giving notice of termination to you. Unless we tell you otherwise, upon any termination, (a) the rights and licenses granted to you in these terms will end; (b) you must stop using the app, and (if needed) delete it from your device.
**Changes to This Terms and Conditions**
I may update our Terms and Conditions from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Terms and Conditions on this page.
These terms and conditions are effective as of 2023-11-17
**Contact Us**
If you have any questions or suggestions about my Terms and Conditions, do not hesitate to contact me at kingslay@icloud.com.
This Terms and Conditions page was generated by [App Privacy Policy Generator](https://app-privacy-policy-generator.nisrulz.com/)

View File

@@ -0,0 +1,107 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width'>
<title>Privacy Policy</title>
<style> body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; padding:1em; } </style>
</head>
<body>
<strong>Privacy Policy</strong> <p>
kingslay built the TracyPlayer app as
a Free app. This SERVICE is provided by
kingslay at no cost and is intended for use as
is.
</p> <p>
This page is used to inform visitors regarding my
policies with the collection, use, and disclosure of Personal
Information if anyone decided to use my Service.
</p> <p>
If you choose to use my Service, then you agree to
the collection and use of information in relation to this
policy. The Personal Information that I collect is
used for providing and improving the Service. I will not use or share your information with
anyone except as described in this Privacy Policy.
</p> <p>
The terms used in this Privacy Policy have the same meanings
as in our Terms and Conditions, which are accessible at
TracyPlayer unless otherwise defined in this Privacy Policy.
</p> <p><strong>Information Collection and Use</strong></p> <p>
For a better experience, while using our Service, I
may require you to provide us with certain personally
identifiable information, including but not limited to none. The information that
I request will be retained on your device and is not collected by me in any way.
</p> <!----> <p><strong>Log Data</strong></p> <p>
I want to inform you that whenever you
use my Service, in a case of an error in the app
I collect data and information (through third-party
products) on your phone called Log Data. This Log Data may
include information such as your device Internet Protocol
(“IP”) address, device name, operating system version, the
configuration of the app when utilizing my Service,
the time and date of your use of the Service, and other
statistics.
</p> <p><strong>Cookies</strong></p> <p>
Cookies are files with a small amount of data that are
commonly used as anonymous unique identifiers. These are sent
to your browser from the websites that you visit and are
stored on your device's internal memory.
</p> <p>
This Service does not use these “cookies” explicitly. However,
the app may use third-party code and libraries that use
“cookies” to collect information and improve their services.
You have the option to either accept or refuse these cookies
and know when a cookie is being sent to your device. If you
choose to refuse our cookies, you may not be able to use some
portions of this Service.
</p> <p><strong>Service Providers</strong></p> <p>
I may employ third-party companies and
individuals due to the following reasons:
</p> <ul><li>To facilitate our Service;</li> <li>To provide the Service on our behalf;</li> <li>To perform Service-related services; or</li> <li>To assist us in analyzing how our Service is used.</li></ul> <p>
I want to inform users of this Service
that these third parties have access to their Personal
Information. The reason is to perform the tasks assigned to
them on our behalf. However, they are obligated not to
disclose or use the information for any other purpose.
</p> <p><strong>Security</strong></p> <p>
I value your trust in providing us your
Personal Information, thus we are striving to use commercially
acceptable means of protecting it. But remember that no method
of transmission over the internet, or method of electronic
storage is 100% secure and reliable, and I cannot
guarantee its absolute security.
</p> <p><strong>Links to Other Sites</strong></p> <p>
This Service may contain links to other sites. If you click on
a third-party link, you will be directed to that site. Note
that these external sites are not operated by me.
Therefore, I strongly advise you to review the
Privacy Policy of these websites. I have
no control over and assume no responsibility for the content,
privacy policies, or practices of any third-party sites or
services.
</p> <p><strong>Childrens Privacy</strong></p> <!----> <div><p>
I do not knowingly collect personally
identifiable information from children. I
encourage all children to never submit any personally
identifiable information through
the Application and/or Services.
I encourage parents and legal guardians to monitor
their children's Internet usage and to help enforce this Policy by instructing
their children never to provide personally identifiable information through the Application and/or Services without their permission. If you have reason to believe that a child
has provided personally identifiable information to us through the Application and/or Services,
please contact us. You must also be at least 16 years of age to consent to the processing
of your personally identifiable information in your country (in some countries we may allow your parent
or guardian to do so on your behalf).
</p></div> <p><strong>Changes to This Privacy Policy</strong></p> <p>
I may update our Privacy Policy from
time to time. Thus, you are advised to review this page
periodically for any changes. I will
notify you of any changes by posting the new Privacy Policy on
this page.
</p> <p>This policy is effective as of 2023-11-17</p> <p><strong>Contact Us</strong></p> <p>
If you have any questions or suggestions about my
Privacy Policy, do not hesitate to contact me at kingslay@icloud.com.
</p> <p>This privacy policy page was created at <a href="https://privacypolicytemplate.net" target="_blank" rel="noopener noreferrer">privacypolicytemplate.net </a>and modified/generated by <a href="https://app-privacy-policy-generator.nisrulz.com/" target="_blank" rel="noopener noreferrer">App Privacy Policy Generator</a></p>
</body>
</html>

Binary file not shown.