10 KiB
SwiftUI Usage
KSPlayer provides full SwiftUI support with KSVideoPlayer (a UIViewRepresentable) and KSVideoPlayerView (a complete player view with controls).
Minimum Requirements: iOS 16.0, macOS 13.0, tvOS 16.0
KSVideoPlayerView
KSVideoPlayerView is a complete video player with built-in controls, subtitle display, and settings.
Basic Usage
import KSPlayer
import SwiftUI
struct VideoScreen: View {
let url = URL(string: "https://example.com/video.mp4")!
var body: some View {
KSVideoPlayerView(url: url, options: KSOptions())
}
}
With Custom Title
KSVideoPlayerView(
url: url,
options: KSOptions(),
title: "My Video Title"
)
With Coordinator and Subtitle Data Source
struct VideoScreen: View {
@StateObject private var coordinator = KSVideoPlayer.Coordinator()
let url: URL
let subtitleDataSource: SubtitleDataSouce?
var body: some View {
KSVideoPlayerView(
coordinator: coordinator,
url: url,
options: KSOptions(),
title: "Video Title",
subtitleDataSouce: subtitleDataSource
)
}
}
KSVideoPlayer
KSVideoPlayer is the lower-level UIViewRepresentable that provides the video rendering surface. Use this when you want full control over the UI.
Basic Usage
import KSPlayer
import SwiftUI
struct CustomPlayerView: View {
@StateObject private var coordinator = KSVideoPlayer.Coordinator()
let url: URL
let options: KSOptions
var body: some View {
KSVideoPlayer(coordinator: coordinator, url: url, options: options)
.onStateChanged { layer, state in
print("State changed: \(state)")
}
.onPlay { currentTime, totalTime in
print("Playing: \(currentTime)/\(totalTime)")
}
.onFinish { layer, error in
if let error = error {
print("Error: \(error)")
}
}
}
}
Initializer
public struct KSVideoPlayer {
public init(
coordinator: Coordinator,
url: URL,
options: KSOptions
)
}
KSVideoPlayer.Coordinator
The Coordinator manages player state and provides bindings for SwiftUI views.
Creating a Coordinator
@StateObject private var coordinator = KSVideoPlayer.Coordinator()
Published Properties
@MainActor
public final class Coordinator: ObservableObject {
// Playback state (read-only computed property)
public var state: KSPlayerState { get }
// Mute control
@Published public var isMuted: Bool = false
// Volume (0.0 to 1.0)
@Published public var playbackVolume: Float = 1.0
// Content mode toggle
@Published public var isScaleAspectFill: Bool = false
// Playback rate (1.0 = normal)
@Published public var playbackRate: Float = 1.0
// Controls visibility
@Published public var isMaskShow: Bool = true
// Subtitle model
public var subtitleModel: SubtitleModel
// Time model for progress display
public var timemodel: ControllerTimeModel
// The underlying player layer
public var playerLayer: KSPlayerLayer?
}
Coordinator Methods
// Skip forward/backward by seconds
public func skip(interval: Int)
// Seek to specific time
public func seek(time: TimeInterval)
// Show/hide controls with optional auto-hide
public func mask(show: Bool, autoHide: Bool = true)
// Reset player state (called automatically on view dismissal)
public func resetPlayer()
Using Coordinator for Playback Control
struct PlayerView: View {
@StateObject private var coordinator = KSVideoPlayer.Coordinator()
let url: URL
var body: some View {
VStack {
KSVideoPlayer(coordinator: coordinator, url: url, options: KSOptions())
HStack {
Button("Play") {
coordinator.playerLayer?.play()
}
Button("Pause") {
coordinator.playerLayer?.pause()
}
Button("-15s") {
coordinator.skip(interval: -15)
}
Button("+15s") {
coordinator.skip(interval: 15)
}
}
Slider(value: $coordinator.playbackVolume, in: 0...1)
Toggle("Mute", isOn: $coordinator.isMuted)
}
}
}
View Modifiers
onStateChanged
Called when playback state changes:
KSVideoPlayer(coordinator: coordinator, url: url, options: options)
.onStateChanged { layer, state in
switch state {
case .initialized: break
case .preparing: break
case .readyToPlay:
// Access metadata
if let title = layer.player.dynamicInfo?.metadata["title"] {
print("Title: \(title)")
}
case .buffering: break
case .bufferFinished: break
case .paused: break
case .playedToTheEnd: break
case .error: break
}
}
onPlay
Called periodically during playback with current and total time:
.onPlay { currentTime, totalTime in
let progress = currentTime / totalTime
print("Progress: \(Int(progress * 100))%")
}
onFinish
Called when playback ends (naturally or with error):
.onFinish { layer, error in
if let error = error {
print("Playback failed: \(error.localizedDescription)")
} else {
print("Playback completed")
}
}
onBufferChanged
Called when buffering status changes:
.onBufferChanged { bufferedCount, consumeTime in
// bufferedCount: 0 = initial loading
print("Buffer count: \(bufferedCount), time: \(consumeTime)")
}
onSwipe (iOS only)
Called on swipe gestures:
#if canImport(UIKit)
.onSwipe { direction in
switch direction {
case .up: print("Swipe up")
case .down: print("Swipe down")
case .left: print("Swipe left")
case .right: print("Swipe right")
default: break
}
}
#endif
ControllerTimeModel
Used for displaying playback time:
public class ControllerTimeModel: ObservableObject {
@Published public var currentTime: Int = 0
@Published public var totalTime: Int = 1
}
Usage:
struct TimeDisplay: View {
@ObservedObject var timeModel: ControllerTimeModel
var body: some View {
Text("\(timeModel.currentTime) / \(timeModel.totalTime)")
}
}
// In your player view:
TimeDisplay(timeModel: coordinator.timemodel)
Subtitle Integration
Access subtitles through the coordinator:
struct SubtitlePicker: View {
@ObservedObject var subtitleModel: SubtitleModel
var body: some View {
Picker("Subtitle", selection: $subtitleModel.selectedSubtitleInfo) {
Text("Off").tag(nil as (any SubtitleInfo)?)
ForEach(subtitleModel.subtitleInfos, id: \.subtitleID) { info in
Text(info.name).tag(info as (any SubtitleInfo)?)
}
}
}
}
// Usage:
SubtitlePicker(subtitleModel: coordinator.subtitleModel)
Complete Example
import KSPlayer
import SwiftUI
@available(iOS 16.0, *)
struct FullPlayerView: View {
@StateObject private var coordinator = KSVideoPlayer.Coordinator()
@State private var url: URL
@State private var title: String
@Environment(\.dismiss) private var dismiss
init(url: URL, title: String) {
_url = State(initialValue: url)
_title = State(initialValue: title)
}
var body: some View {
ZStack {
KSVideoPlayer(coordinator: coordinator, url: url, options: KSOptions())
.onStateChanged { layer, state in
if state == .readyToPlay {
if let movieTitle = layer.player.dynamicInfo?.metadata["title"] {
title = movieTitle
}
}
}
.onFinish { _, error in
if error != nil {
dismiss()
}
}
.ignoresSafeArea()
.onTapGesture {
coordinator.isMaskShow.toggle()
}
// Custom controls overlay
if coordinator.isMaskShow {
VStack {
HStack {
Button("Back") { dismiss() }
Spacer()
Text(title)
}
.padding()
Spacer()
HStack(spacing: 40) {
Button(action: { coordinator.skip(interval: -15) }) {
Image(systemName: "gobackward.15")
}
Button(action: {
if coordinator.state.isPlaying {
coordinator.playerLayer?.pause()
} else {
coordinator.playerLayer?.play()
}
}) {
Image(systemName: coordinator.state.isPlaying ? "pause.fill" : "play.fill")
}
Button(action: { coordinator.skip(interval: 15) }) {
Image(systemName: "goforward.15")
}
}
.font(.largeTitle)
Spacer()
}
.foregroundColor(.white)
}
}
.preferredColorScheme(.dark)
}
}
URL Change Handling
The player automatically detects URL changes:
struct DynamicPlayerView: View {
@StateObject private var coordinator = KSVideoPlayer.Coordinator()
@State private var currentURL: URL
var body: some View {
VStack {
KSVideoPlayer(coordinator: coordinator, url: currentURL, options: KSOptions())
Button("Load Next Video") {
currentURL = URL(string: "https://example.com/next-video.mp4")!
}
}
}
}