initial iOS LiveTV coordination

This commit is contained in:
jhays 2022-03-31 21:37:57 -05:00
parent 9c41420ecd
commit 4dac5dd0b9
11 changed files with 1397 additions and 7 deletions

View File

@ -20,6 +20,8 @@ final class LibraryListCoordinator: NavigationCoordinatable {
var search = makeSearch
@Route(.push)
var library = makeLibrary
@Route(.push)
var liveTV = makeLiveTV
let viewModel: LibraryListViewModel
@ -34,6 +36,10 @@ final class LibraryListCoordinator: NavigationCoordinatable {
func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator {
SearchCoordinator(viewModel: viewModel)
}
func makeLiveTV() -> LiveTVCoordinator {
LiveTVCoordinator()
}
@ViewBuilder
func makeStart() -> some View {

View File

@ -24,7 +24,7 @@ final class LiveTVChannelsCoordinator: NavigationCoordinatable {
func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
NavigationViewCoordinator(ItemCoordinator(item: item))
}
func makeVideoPlayer(viewModel: VideoPlayerViewModel) -> NavigationViewCoordinator<LiveTVVideoPlayerCoordinator> {
NavigationViewCoordinator(LiveTVVideoPlayerCoordinator(viewModel: viewModel))
}

View File

@ -0,0 +1,30 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
//
import Foundation
import JellyfinAPI
import Stinsen
import SwiftUI
final class LiveTVCoordinator: NavigationCoordinatable {
let stack = NavigationStack(initial: \LiveTVCoordinator.start)
@Root
var start = makeStart
// @Route(.push)
// var search = makeSearch
@ViewBuilder
func makeStart() -> some View {
LiveTVChannelsView()
}
// func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator {
// SearchCoordinator(viewModel: viewModel)
// }
}

View File

@ -0,0 +1,40 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
//
import Defaults
import Foundation
import JellyfinAPI
import Stinsen
import SwiftUI
final class LiveTVVideoPlayerCoordinator: NavigationCoordinatable {
let stack = NavigationStack(initial: \LiveTVVideoPlayerCoordinator.start)
@Root
var start = makeStart
let viewModel: VideoPlayerViewModel
init(viewModel: VideoPlayerViewModel) {
self.viewModel = viewModel
}
@ViewBuilder
func makeStart() -> some View {
// if Defaults[.Experimental.liveTVNativePlayer] {
// LiveTVNativeVideoPlayerView(viewModel: viewModel)
// .navigationBarHidden(true)
// .ignoresSafeArea()
// } else {
LiveTVPlayerView(viewModel: viewModel)
.navigationBarHidden(true)
.ignoresSafeArea()
// }
}
}

View File

@ -120,7 +120,9 @@ struct LiveTVChannelItemElement: View {
.stroke(isFocused ? Color.blue : Color.clear, lineWidth: 4))
.cornerRadius(20)
.scaleEffect(isFocused ? 1.1 : 1)
#if os(tvOS)
.focusable(true)
#endif
.focused($focused)
.onChange(of: focused) { foc in
withAnimation(.linear(duration: 0.15)) {

View File

@ -259,6 +259,14 @@
C4534981279A3F140045F1E2 /* tvOSLiveTVOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534980279A3F140045F1E2 /* tvOSLiveTVOverlay.swift */; };
C4534983279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534982279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift */; };
C4534985279A40C60045F1E2 /* LiveTVVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534984279A40C50045F1E2 /* LiveTVVideoPlayerView.swift */; };
C45942C527F67DA400C54FE7 /* LiveTVCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942C427F67DA400C54FE7 /* LiveTVCoordinator.swift */; };
C45942C627F695FB00C54FE7 /* LiveTVProgramsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */; };
C45942C927F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942C827F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift */; };
C45942CB27F6984100C54FE7 /* LiveTVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942CA27F6984100C54FE7 /* LiveTVPlayerViewController.swift */; };
C45942CD27F6994A00C54FE7 /* LiveTVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942CC27F6994A00C54FE7 /* LiveTVPlayerView.swift */; };
C45942CE27F69BF300C54FE7 /* LiveTVChannelsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE078A272844AF003F4AD1 /* LiveTVChannelsView.swift */; };
C45942CF27F69BF500C54FE7 /* LiveTVChannelItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */; };
C45942D027F69C2400C54FE7 /* LiveTVChannelsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */; };
C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */; };
C4AE2C3027498D2300AE13CF /* LiveTVHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */; };
C4AE2C3227498D6A00AE13CF /* LiveTVProgramsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */; };
@ -739,6 +747,10 @@
C4534980279A3F140045F1E2 /* tvOSLiveTVOverlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = tvOSLiveTVOverlay.swift; sourceTree = "<group>"; };
C4534982279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = tvOSLiveTVVideoPlayerCoordinator.swift; sourceTree = "<group>"; };
C4534984279A40C50045F1E2 /* LiveTVVideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveTVVideoPlayerView.swift; sourceTree = "<group>"; };
C45942C427F67DA400C54FE7 /* LiveTVCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVCoordinator.swift; sourceTree = "<group>"; };
C45942C827F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSLiveTVVideoPlayerCoordinator.swift; sourceTree = "<group>"; };
C45942CA27F6984100C54FE7 /* LiveTVPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVPlayerViewController.swift; sourceTree = "<group>"; };
C45942CC27F6994A00C54FE7 /* LiveTVPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVPlayerView.swift; sourceTree = "<group>"; };
C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVHomeView.swift; sourceTree = "<group>"; };
C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVProgramsView.swift; sourceTree = "<group>"; };
C4B9B91327E1921B0063535C /* LiveTVNativeVideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVNativeVideoPlayerView.swift; sourceTree = "<group>"; };
@ -1466,6 +1478,7 @@
C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */,
C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */,
C4BE07782726EE82003F4AD1 /* LiveTVTabCoordinator.swift */,
C45942C427F67DA400C54FE7 /* LiveTVCoordinator.swift */,
E193D5412719404B00900D82 /* MainCoordinator */,
C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */,
6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */,
@ -1724,7 +1737,9 @@
E1002B692793E12E00E47059 /* Overlays */,
E1C812B5277A8E5D00918266 /* PlayerOverlayDelegate.swift */,
E1C812B8277A8E5D00918266 /* VLCPlayerView.swift */,
C45942CC27F6994A00C54FE7 /* LiveTVPlayerView.swift */,
E1C812B6277A8E5D00918266 /* VLCPlayerViewController.swift */,
C45942CA27F6984100C54FE7 /* LiveTVPlayerViewController.swift */,
);
path = VideoPlayer;
sourceTree = "<group>";
@ -1801,6 +1816,7 @@
E1C812CF277AE4C700918266 /* VideoPlayerCoordinator */ = {
isa = PBXGroup;
children = (
C45942C827F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift */,
6220D0C526D62D8700B8E046 /* iOSVideoPlayerCoordinator.swift */,
C4534982279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift */,
E1C812D0277AE4E300918266 /* tvOSVideoPlayerCoordinator.swift */,
@ -2374,6 +2390,7 @@
62C29E9F26D1016600C1D2E7 /* iOSMainCoordinator.swift in Sources */,
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */,
53F866442687A45F00DCD1D7 /* PortraitItemButton.swift in Sources */,
C45942CD27F6994A00C54FE7 /* LiveTVPlayerView.swift in Sources */,
E1AD105626D981CE003E4A08 /* PortraitHStackView.swift in Sources */,
62C29EA126D102A500C1D2E7 /* iOSMainTabCoordinator.swift in Sources */,
C4BE076E2720FEA8003F4AD1 /* PortraitItemElement.swift in Sources */,
@ -2392,9 +2409,11 @@
5D160403278A41FD00D22B99 /* VLCPlayer+subtitles.swift in Sources */,
536D3D78267BD5C30004248C /* ViewModel.swift in Sources */,
E1A2C158279A7D76005EC829 /* BundleExtensions.swift in Sources */,
C45942C627F695FB00C54FE7 /* LiveTVProgramsCoordinator.swift in Sources */,
E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */,
E13AD72E2798BC8D00FDCEE8 /* NativePlayerViewController.swift in Sources */,
E13DD3E527177D15009D4DAF /* ServerListView.swift in Sources */,
C45942CB27F6984100C54FE7 /* LiveTVPlayerViewController.swift in Sources */,
E18845F826DEA9C900B0C5B7 /* ItemViewBody.swift in Sources */,
E173DA5426D050F500CC4EB7 /* ServerDetailViewModel.swift in Sources */,
E19169CE272514760085832A /* HTTPScheme.swift in Sources */,
@ -2412,6 +2431,7 @@
E1C812BC277A8E5D00918266 /* PlaybackSpeed.swift in Sources */,
E1E5D5492783CDD700692DFE /* OverlaySettingsView.swift in Sources */,
E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */,
C45942D027F69C2400C54FE7 /* LiveTVChannelsCoordinator.swift in Sources */,
E11D224227378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */,
6264E88C273850380081A12A /* Strings.swift in Sources */,
C4AE2C3227498D6A00AE13CF /* LiveTVProgramsView.swift in Sources */,
@ -2463,6 +2483,7 @@
C4BE07762725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift in Sources */,
E13DD3E927177ED6009D4DAF /* ServerListCoordinator.swift in Sources */,
E1C812BD277A8E5D00918266 /* PlayerOverlayDelegate.swift in Sources */,
C45942C527F67DA400C54FE7 /* LiveTVCoordinator.swift in Sources */,
E13DD3C227164941009D4DAF /* SwiftfinStore.swift in Sources */,
62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
E193D4DB27193CCA00900D82 /* PillStackable.swift in Sources */,
@ -2480,6 +2501,7 @@
091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */,
62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */,
5D64683D277B1649009E09AE /* PreferenceUIHostingSwizzling.swift in Sources */,
C45942C927F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift in Sources */,
E13DD3C827164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */,
E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */,
E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
@ -2496,10 +2518,12 @@
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */,
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */,
E1D4BF8A2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */,
C45942CE27F69BF300C54FE7 /* LiveTVChannelsView.swift in Sources */,
E13DD3F92717E961009D4DAF /* UserListViewModel.swift in Sources */,
E126F741278A656C00A522BF /* ServerStreamType.swift in Sources */,
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
C45942CF27F69BF500C54FE7 /* LiveTVChannelItemElement.swift in Sources */,
09389CC726819B4600AE350E /* VideoPlayerModel.swift in Sources */,
E1D4BF872719D27100A11E64 /* Bitrates.swift in Sources */,
6220D0B726D5EE1100B8E046 /* SearchCoordinator.swift in Sources */,

View File

@ -6,6 +6,7 @@
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
//
import Defaults
import Foundation
import Stinsen
import SwiftUI
@ -15,8 +16,17 @@ struct LibraryListView: View {
var libraryListRouter: LibraryListCoordinator.Router
@StateObject
var viewModel = LibraryListViewModel()
let supportedCollectionTypes = ["movies", "tvshows", "boxsets", "other"]
@Default(.Experimental.liveTVAlphaEnabled)
var liveTVAlphaEnabled
var supportedCollectionTypes: [String] {
if liveTVAlphaEnabled {
return ["movies", "tvshows", "livetv", "boxsets", "other"]
} else {
return ["movies", "tvshows", "boxsets", "other"]
}
}
var body: some View {
ScrollView {
@ -49,9 +59,13 @@ struct LibraryListView: View {
return self.supportedCollectionTypes.contains(collectionType)
}, id: \.id) { library in
Button {
libraryListRouter.route(to: \.library,
if library.collectionType == "livetv" {
libraryListRouter.route(to: \.liveTV)
} else {
libraryListRouter.route(to: \.library,
(viewModel: LibraryViewModel(parentID: library.id),
title: library.name ?? ""))
}
} label: {
ZStack {
ImageView(library.getPrimaryImage(maxWidth: 500), blurHash: library.getPrimaryImageBlurHash())

View File

@ -10,7 +10,201 @@ import Stinsen
import SwiftUI
struct LiveTVProgramsView: View {
var body: some View {
Text("Coming Soon")
}
@EnvironmentObject
var programsRouter: LiveTVProgramsCoordinator.Router
@StateObject
var viewModel = LiveTVProgramsViewModel()
var body: some View {
ScrollView {
LazyVStack(alignment: .leading) {
if !viewModel.recommendedItems.isEmpty,
let items = viewModel.recommendedItems
{
Text("On Now")
.font(.headline)
.fontWeight(.semibold)
.padding(.leading, 90)
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
Spacer().frame(width: 45)
ForEach(items, id: \.id) { item in
Button {
if let chanId = item.channelId,
let chan = viewModel.findChannel(id: chanId)
{
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
}
}
} label: {
#if os(iOS)
#elseif os(tvOS)
LandscapeItemElement(item: item)
#endif
}
.buttonStyle(PlainNavigationLinkButtonStyle())
}
Spacer().frame(width: 45)
}
}.frame(height: 350)
}
if !viewModel.seriesItems.isEmpty,
let items = viewModel.seriesItems
{
Text("Shows")
.font(.headline)
.fontWeight(.semibold)
.padding(.leading, 90)
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
Spacer().frame(width: 45)
ForEach(items, id: \.id) { item in
Button {
if let chanId = item.channelId,
let chan = viewModel.findChannel(id: chanId)
{
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
}
}
} label: {
#if os(iOS)
#elseif os(tvOS)
LandscapeItemElement(item: item)
#endif
}
.buttonStyle(PlainNavigationLinkButtonStyle())
}
Spacer().frame(width: 45)
}
}.frame(height: 350)
}
if !viewModel.movieItems.isEmpty,
let items = viewModel.movieItems
{
Text("Movies")
.font(.headline)
.fontWeight(.semibold)
.padding(.leading, 90)
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
Spacer().frame(width: 45)
ForEach(items, id: \.id) { item in
Button {
if let chanId = item.channelId,
let chan = viewModel.findChannel(id: chanId)
{
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
}
}
} label: {
#if os(iOS)
#elseif os(tvOS)
LandscapeItemElement(item: item)
#endif
}
.buttonStyle(PlainNavigationLinkButtonStyle())
}
Spacer().frame(width: 45)
}
}.frame(height: 350)
}
if !viewModel.sportsItems.isEmpty,
let items = viewModel.sportsItems
{
Text("Sports")
.font(.headline)
.fontWeight(.semibold)
.padding(.leading, 90)
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
Spacer().frame(width: 45)
ForEach(items, id: \.id) { item in
Button {
if let chanId = item.channelId,
let chan = viewModel.findChannel(id: chanId)
{
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
}
}
} label: {
#if os(iOS)
#elseif os(tvOS)
LandscapeItemElement(item: item)
#endif
}
.buttonStyle(PlainNavigationLinkButtonStyle())
}
Spacer().frame(width: 45)
}
}.frame(height: 350)
}
if !viewModel.kidsItems.isEmpty,
let items = viewModel.kidsItems
{
Text("Kids")
.font(.headline)
.fontWeight(.semibold)
.padding(.leading, 90)
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
Spacer().frame(width: 45)
ForEach(items, id: \.id) { item in
Button {
if let chanId = item.channelId,
let chan = viewModel.findChannel(id: chanId)
{
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
}
}
} label: {
#if os(iOS)
#elseif os(tvOS)
LandscapeItemElement(item: item)
#endif
}
.buttonStyle(PlainNavigationLinkButtonStyle())
}
Spacer().frame(width: 45)
}
}.frame(height: 350)
}
if !viewModel.newsItems.isEmpty,
let items = viewModel.newsItems
{
Text("News")
.font(.headline)
.fontWeight(.semibold)
.padding(.leading, 90)
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
Spacer().frame(width: 45)
ForEach(items, id: \.id) { item in
Button {
if let chanId = item.channelId,
let chan = viewModel.findChannel(id: chanId)
{
self.viewModel.fetchVideoPlayerViewModel(item: chan) { playerViewModel in
self.programsRouter.route(to: \.videoPlayer, playerViewModel)
}
}
} label: {
#if os(iOS)
#elseif os(tvOS)
LandscapeItemElement(item: item)
#endif
}
.buttonStyle(PlainNavigationLinkButtonStyle())
}
Spacer().frame(width: 45)
}
}.frame(height: 350)
}
}
}
}
}

View File

@ -17,6 +17,8 @@ struct ExperimentalSettingsView: View {
var syncSubtitleStateWithAdjacent
@Default(.Experimental.nativePlayer)
var nativePlayer
@Default(.Experimental.liveTVAlphaEnabled)
var liveTVAlphaEnabled
var body: some View {
Form {
@ -31,6 +33,14 @@ struct ExperimentalSettingsView: View {
} header: {
L10n.experimental.text
}
Section {
Toggle("Live TV (Alpha)", isOn: $liveTVAlphaEnabled)
} header: {
Text("Live TV")
}
}
}
}

View File

@ -0,0 +1,38 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
//
import SwiftUI
import UIKit
//struct NativePlayerView: UIViewControllerRepresentable {
//
// let viewModel: VideoPlayerViewModel
//
// typealias UIViewControllerType = NativePlayerViewController
//
// func makeUIViewController(context: Context) -> NativePlayerViewController {
//
// NativePlayerViewController(viewModel: viewModel)
// }
//
// func updateUIViewController(_ uiViewController: NativePlayerViewController, context: Context) {}
//}
struct LiveTVPlayerView: UIViewControllerRepresentable {
let viewModel: VideoPlayerViewModel
typealias UIViewControllerType = LiveTVPlayerViewController
func makeUIViewController(context: Context) -> LiveTVPlayerViewController {
LiveTVPlayerViewController(viewModel: viewModel)
}
func updateUIViewController(_ uiViewController: LiveTVPlayerViewController, context: Context) {}
}

File diff suppressed because it is too large Load Diff