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:
166
KSPlayer-main/Demo/SwiftUI/Shared/ContentView.swift
Normal file
166
KSPlayer-main/Demo/SwiftUI/Shared/ContentView.swift
Normal file
@@ -0,0 +1,166 @@
|
||||
import KSPlayer
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
#if !os(tvOS)
|
||||
@Environment(\.openWindow) private var openWindow
|
||||
#endif
|
||||
@EnvironmentObject
|
||||
private var appModel: APPModel
|
||||
private var initialView: some View {
|
||||
#if os(macOS)
|
||||
NavigationSplitView {
|
||||
List(selection: $appModel.tabSelected) {
|
||||
link(to: .Home)
|
||||
link(to: .Favorite)
|
||||
link(to: .Files)
|
||||
}
|
||||
} detail: {
|
||||
appModel.tabSelected.destination(appModel: appModel)
|
||||
}
|
||||
#else
|
||||
TabView(selection: $appModel.tabSelected) {
|
||||
tab(to: .Home)
|
||||
tab(to: .Favorite)
|
||||
tab(to: .Files)
|
||||
tab(to: .Setting)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
initialView
|
||||
.preferredColorScheme(.dark)
|
||||
.background(Color.black)
|
||||
.sheet(isPresented: $appModel.openURLImport) {
|
||||
URLImportView()
|
||||
}
|
||||
.onChange(of: appModel.openURL) { url in
|
||||
if let url {
|
||||
#if !os(tvOS)
|
||||
openWindow(value: url)
|
||||
#endif
|
||||
appModel.openURL = nil
|
||||
}
|
||||
}
|
||||
.onChange(of: appModel.openPlayModel) { model in
|
||||
if let model {
|
||||
#if !os(tvOS)
|
||||
openWindow(value: model)
|
||||
#endif
|
||||
appModel.openPlayModel = nil
|
||||
}
|
||||
}
|
||||
#if !os(tvOS)
|
||||
.onDrop(of: ["public.url", "public.file-url"], isTargeted: nil) { items -> Bool in
|
||||
guard let item = items.first, let identifier = item.registeredTypeIdentifiers.first else {
|
||||
return false
|
||||
}
|
||||
item.loadItem(forTypeIdentifier: identifier, options: nil) { urlData, _ in
|
||||
if let urlData = urlData as? Data {
|
||||
let url = NSURL(absoluteURLWithDataRepresentation: urlData, relativeTo: nil) as URL
|
||||
DispatchQueue.main.async {
|
||||
appModel.open(url: url)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
.fileImporter(isPresented: $appModel.openFileImport, allowedContentTypes: [.movie, .audio, .data]) { result in
|
||||
guard let url = try? result.get() else {
|
||||
return
|
||||
}
|
||||
if url.startAccessingSecurityScopedResource() {
|
||||
appModel.open(url: url)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
.onOpenURL { url in
|
||||
KSLog("onOpenURL")
|
||||
appModel.open(url: url)
|
||||
}
|
||||
}
|
||||
|
||||
func link(to item: TabBarItem) -> some View {
|
||||
item.lable.tag(item)
|
||||
}
|
||||
|
||||
func tab(to item: TabBarItem) -> some View {
|
||||
Group {
|
||||
if item == .Home {
|
||||
NavigationStack(path: $appModel.path) {
|
||||
item.destination(appModel: appModel)
|
||||
}
|
||||
} else {
|
||||
NavigationStack {
|
||||
item.destination(appModel: appModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabItem {
|
||||
item.lable.tag(item)
|
||||
}.tag(item)
|
||||
}
|
||||
}
|
||||
|
||||
enum TabBarItem: Int {
|
||||
case Home
|
||||
case Favorite
|
||||
case Files
|
||||
case Setting
|
||||
var lable: Label<Text, Image> {
|
||||
switch self {
|
||||
case .Home:
|
||||
return Label("Home", systemImage: "house.fill")
|
||||
case .Favorite:
|
||||
return Label("Favorite", systemImage: "star.fill")
|
||||
case .Files:
|
||||
return Label("Files", systemImage: "folder.fill.badge.gearshape")
|
||||
case .Setting:
|
||||
return Label("Setting", systemImage: "gear")
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@ViewBuilder
|
||||
func destination(appModel: APPModel) -> some View {
|
||||
switch self {
|
||||
case .Home:
|
||||
HomeView(m3uURL: appModel.activeM3UModel?.m3uURL)
|
||||
.navigationPlay()
|
||||
case .Favorite:
|
||||
FavoriteView()
|
||||
.navigationPlay()
|
||||
case .Files:
|
||||
FilesView()
|
||||
case .Setting:
|
||||
SettingView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension View {
|
||||
@MainActor
|
||||
@ViewBuilder
|
||||
func navigationPlay() -> some View {
|
||||
navigationDestination(for: URL.self) { url in
|
||||
KSVideoPlayerView(url: url)
|
||||
#if !os(macOS)
|
||||
.toolbar(.hidden, for: .tabBar)
|
||||
#endif
|
||||
}
|
||||
.navigationDestination(for: MovieModel.self) { model in
|
||||
model.view
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension MovieModel {
|
||||
@MainActor
|
||||
var view: some View {
|
||||
KSVideoPlayerView(model: self)
|
||||
#if !os(macOS)
|
||||
.toolbar(.hidden, for: .tabBar)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user