10 KiB
KSPlayerLayer
KSPlayerLayer is the core playback controller that manages the media player instance and provides a high-level API for playback control.
Overview
KSPlayerLayer wraps MediaPlayerProtocol implementations (KSAVPlayer or KSMEPlayer) and handles:
- Player lifecycle management
- Playback state transitions
- Remote control integration
- Picture-in-Picture support
- Background/foreground handling
Creating a KSPlayerLayer
Basic Initialization
let url = URL(string: "https://example.com/video.mp4")!
let options = KSOptions()
let playerLayer = KSPlayerLayer(
url: url,
isAutoPlay: true, // Default: KSOptions.isAutoPlay
options: options,
delegate: self
)
Constructor Parameters
public init(
url: URL,
isAutoPlay: Bool = KSOptions.isAutoPlay,
options: KSOptions,
delegate: KSPlayerLayerDelegate? = nil
)
Properties
Core Properties
| Property | Type | Description |
|---|---|---|
url |
URL |
Current media URL (read-only after init) |
options |
KSOptions |
Player configuration (read-only) |
player |
MediaPlayerProtocol |
Underlying player instance |
state |
KSPlayerState |
Current playback state (read-only) |
delegate |
KSPlayerLayerDelegate? |
Event delegate |
Published Properties (for Combine/SwiftUI)
@Published public var bufferingProgress: Int = 0 // 0-100
@Published public var loopCount: Int = 0 // Loop iteration count
@Published public var isPipActive: Bool = false // Picture-in-Picture state
Playback Control Methods
play()
Start or resume playback:
playerLayer.play()
pause()
Pause playback:
playerLayer.pause()
stop()
Stop playback and reset player state:
playerLayer.stop()
seek(time:autoPlay:completion:)
Seek to a specific time:
playerLayer.seek(time: 30.0, autoPlay: true) { finished in
if finished {
print("Seek completed")
}
}
Parameters:
time: TimeInterval- Target time in secondsautoPlay: Bool- Whether to auto-play after seekingcompletion: @escaping ((Bool) -> Void)- Called when seek completes
prepareToPlay()
Prepare the player (called automatically when isAutoPlay is true):
playerLayer.prepareToPlay()
URL Management
set(url:options:)
Change the video URL:
let newURL = URL(string: "https://example.com/another-video.mp4")!
playerLayer.set(url: newURL, options: KSOptions())
set(urls:options:)
Set a playlist of URLs:
let urls = [
URL(string: "https://example.com/video1.mp4")!,
URL(string: "https://example.com/video2.mp4")!,
URL(string: "https://example.com/video3.mp4")!
]
playerLayer.set(urls: urls, options: KSOptions())
The player automatically advances to the next URL when playback finishes.
Accessing the Player
Player Properties
Access underlying player properties through playerLayer.player:
// Duration
let duration = playerLayer.player.duration
// Current time
let currentTime = playerLayer.player.currentPlaybackTime
// Playing state
let isPlaying = playerLayer.player.isPlaying
// Seekable
let canSeek = playerLayer.player.seekable
// Natural size
let videoSize = playerLayer.player.naturalSize
// File size (estimated)
let fileSize = playerLayer.player.fileSize
Player Control
// Volume (0.0 to 1.0)
playerLayer.player.playbackVolume = 0.5
// Mute
playerLayer.player.isMuted = true
// Playback rate
playerLayer.player.playbackRate = 1.5
// Content mode
playerLayer.player.contentMode = .scaleAspectFit
Tracks
// Get audio tracks
let audioTracks = playerLayer.player.tracks(mediaType: .audio)
// Get video tracks
let videoTracks = playerLayer.player.tracks(mediaType: .video)
// Select a track
if let englishTrack = audioTracks.first(where: { $0.languageCode == "en" }) {
playerLayer.player.select(track: englishTrack)
}
External Playback (AirPlay)
// Enable AirPlay
playerLayer.player.allowsExternalPlayback = true
// Check if actively using AirPlay
let isAirPlaying = playerLayer.player.isExternalPlaybackActive
// Auto-switch to external when screen connected
playerLayer.player.usesExternalPlaybackWhileExternalScreenIsActive = true
Picture-in-Picture
// Available on tvOS 14.0+, iOS 14.0+
if #available(tvOS 14.0, iOS 14.0, *) {
// Toggle PiP
playerLayer.isPipActive.toggle()
// Or access controller directly
playerLayer.player.pipController?.start(view: playerLayer)
playerLayer.player.pipController?.stop(restoreUserInterface: true)
}
Dynamic Info
if let dynamicInfo = playerLayer.player.dynamicInfo {
print("FPS: \(dynamicInfo.displayFPS)")
print("A/V Sync: \(dynamicInfo.audioVideoSyncDiff)")
print("Dropped frames: \(dynamicInfo.droppedVideoFrameCount)")
print("Audio bitrate: \(dynamicInfo.audioBitrate)")
print("Video bitrate: \(dynamicInfo.videoBitrate)")
// Metadata
if let title = dynamicInfo.metadata["title"] {
print("Title: \(title)")
}
}
Chapters
let chapters = playerLayer.player.chapters
for chapter in chapters {
print("\(chapter.title): \(chapter.start) - \(chapter.end)")
}
Thumbnails
Task {
if let thumbnail = await playerLayer.player.thumbnailImageAtCurrentTime() {
let image = UIImage(cgImage: thumbnail)
// Use thumbnail
}
}
KSPlayerLayerDelegate
Implement the delegate to receive events:
extension MyViewController: KSPlayerLayerDelegate {
func player(layer: KSPlayerLayer, state: KSPlayerState) {
switch state {
case .initialized:
print("Player initialized")
case .preparing:
print("Preparing...")
case .readyToPlay:
print("Ready - Duration: \(layer.player.duration)")
case .buffering:
print("Buffering...")
case .bufferFinished:
print("Playing")
case .paused:
print("Paused")
case .playedToTheEnd:
print("Finished")
case .error:
print("Error occurred")
}
}
func player(layer: KSPlayerLayer, currentTime: TimeInterval, totalTime: TimeInterval) {
let progress = totalTime > 0 ? currentTime / totalTime : 0
print("Progress: \(Int(progress * 100))%")
}
func player(layer: KSPlayerLayer, finish error: Error?) {
if let error = error {
print("Playback error: \(error.localizedDescription)")
} else {
print("Playback completed successfully")
}
}
func player(layer: KSPlayerLayer, bufferedCount: Int, consumeTime: TimeInterval) {
// bufferedCount: 0 = initial load
// consumeTime: time spent buffering
print("Buffer #\(bufferedCount), took \(consumeTime)s")
}
}
Player View Integration
The player's view can be added to your view hierarchy:
if let playerView = playerLayer.player.view {
containerView.addSubview(playerView)
playerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
playerView.topAnchor.constraint(equalTo: containerView.topAnchor),
playerView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
playerView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
playerView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
])
}
Remote Control
Remote control is automatically registered when options.registerRemoteControll is true (default).
Supported Commands
- Play/Pause
- Stop
- Next/Previous track (for playlists)
- Skip forward/backward (15 seconds)
- Change playback position
- Change playback rate
- Change repeat mode
- Language/audio track selection
Customizing Remote Control
// Disable auto-registration
options.registerRemoteControll = false
// Manually register later
playerLayer.registerRemoteControllEvent()
Now Playing Info
import MediaPlayer
// Set custom Now Playing info
MPNowPlayingInfoCenter.default().nowPlayingInfo = [
MPMediaItemPropertyTitle: "Video Title",
MPMediaItemPropertyArtist: "Artist Name",
MPMediaItemPropertyPlaybackDuration: playerLayer.player.duration
]
Background/Foreground Handling
KSPlayerLayer automatically handles app lifecycle:
- Background: Pauses video (unless
KSOptions.canBackgroundPlayistrue) - Foreground: Resumes display
// Enable background playback
KSOptions.canBackgroundPlay = true
Player Type Switching
The player automatically switches from firstPlayerType to secondPlayerType on failure:
// Configure player types before creating KSPlayerLayer
KSOptions.firstPlayerType = KSAVPlayer.self
KSOptions.secondPlayerType = KSMEPlayer.self
Complete Example
class VideoPlayerController: UIViewController, KSPlayerLayerDelegate {
private var playerLayer: KSPlayerLayer!
private var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
containerView = UIView()
view.addSubview(containerView)
containerView.frame = view.bounds
let url = URL(string: "https://example.com/video.mp4")!
let options = KSOptions()
options.isLoopPlay = true
playerLayer = KSPlayerLayer(
url: url,
options: options,
delegate: self
)
if let playerView = playerLayer.player.view {
containerView.addSubview(playerView)
playerView.frame = containerView.bounds
playerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
// MARK: - KSPlayerLayerDelegate
func player(layer: KSPlayerLayer, state: KSPlayerState) {
print("State: \(state)")
}
func player(layer: KSPlayerLayer, currentTime: TimeInterval, totalTime: TimeInterval) {
// Update progress UI
}
func player(layer: KSPlayerLayer, finish error: Error?) {
if let error = error {
showError(error)
}
}
func player(layer: KSPlayerLayer, bufferedCount: Int, consumeTime: TimeInterval) {
if bufferedCount == 0 {
print("Initial load took \(consumeTime)s")
}
}
deinit {
playerLayer.stop()
}
}