ios live tv and experimental settings
This commit is contained in:
parent
5531c912ea
commit
d649dd88cf
|
@ -27,14 +27,14 @@ final class LiveTVVideoPlayerCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeStart() -> some View {
|
func makeStart() -> some View {
|
||||||
// if Defaults[.Experimental.liveTVNativePlayer] {
|
if Defaults[.Experimental.liveTVNativePlayer] {
|
||||||
// LiveTVNativeVideoPlayerView(viewModel: viewModel)
|
LiveTVNativePlayerView(viewModel: viewModel)
|
||||||
// .navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
// .ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
// } else {
|
} else {
|
||||||
LiveTVPlayerView(viewModel: viewModel)
|
LiveTVPlayerView(viewModel: viewModel)
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -263,6 +263,7 @@
|
||||||
C4534981279A3F140045F1E2 /* tvOSLiveTVOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534980279A3F140045F1E2 /* tvOSLiveTVOverlay.swift */; };
|
C4534981279A3F140045F1E2 /* tvOSLiveTVOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534980279A3F140045F1E2 /* tvOSLiveTVOverlay.swift */; };
|
||||||
C4534983279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534982279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift */; };
|
C4534983279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534982279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift */; };
|
||||||
C4534985279A40C60045F1E2 /* LiveTVVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534984279A40C50045F1E2 /* LiveTVVideoPlayerView.swift */; };
|
C4534985279A40C60045F1E2 /* LiveTVVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534984279A40C50045F1E2 /* LiveTVVideoPlayerView.swift */; };
|
||||||
|
C45640D0281A43EF007096DE /* LiveTVNativePlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45640CF281A43EF007096DE /* LiveTVNativePlayerViewController.swift */; };
|
||||||
C45942C527F67DA400C54FE7 /* LiveTVCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942C427F67DA400C54FE7 /* LiveTVCoordinator.swift */; };
|
C45942C527F67DA400C54FE7 /* LiveTVCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942C427F67DA400C54FE7 /* LiveTVCoordinator.swift */; };
|
||||||
C45942C627F695FB00C54FE7 /* LiveTVProgramsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */; };
|
C45942C627F695FB00C54FE7 /* LiveTVProgramsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */; };
|
||||||
C45942C927F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942C827F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift */; };
|
C45942C927F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942C827F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift */; };
|
||||||
|
@ -751,6 +752,7 @@
|
||||||
C4534980279A3F140045F1E2 /* tvOSLiveTVOverlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = tvOSLiveTVOverlay.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
C4534984279A40C50045F1E2 /* LiveTVVideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveTVVideoPlayerView.swift; sourceTree = "<group>"; };
|
||||||
|
C45640CF281A43EF007096DE /* LiveTVNativePlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveTVNativePlayerViewController.swift; sourceTree = "<group>"; };
|
||||||
C45942C427F67DA400C54FE7 /* LiveTVCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVCoordinator.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>"; };
|
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>"; };
|
C45942CA27F6984100C54FE7 /* LiveTVPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVPlayerViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1742,6 +1744,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E13AD72D2798BC8D00FDCEE8 /* NativePlayerViewController.swift */,
|
E13AD72D2798BC8D00FDCEE8 /* NativePlayerViewController.swift */,
|
||||||
|
C45640CF281A43EF007096DE /* LiveTVNativePlayerViewController.swift */,
|
||||||
E1002B692793E12E00E47059 /* Overlays */,
|
E1002B692793E12E00E47059 /* Overlays */,
|
||||||
E1C812B5277A8E5D00918266 /* PlayerOverlayDelegate.swift */,
|
E1C812B5277A8E5D00918266 /* PlayerOverlayDelegate.swift */,
|
||||||
E1C812B8277A8E5D00918266 /* VLCPlayerView.swift */,
|
E1C812B8277A8E5D00918266 /* VLCPlayerView.swift */,
|
||||||
|
@ -2515,6 +2518,7 @@
|
||||||
5D64683D277B1649009E09AE /* PreferenceUIHostingSwizzling.swift in Sources */,
|
5D64683D277B1649009E09AE /* PreferenceUIHostingSwizzling.swift in Sources */,
|
||||||
C45942C927F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift in Sources */,
|
C45942C927F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift in Sources */,
|
||||||
E13DD3C827164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */,
|
E13DD3C827164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */,
|
||||||
|
C45640D0281A43EF007096DE /* LiveTVNativePlayerViewController.swift in Sources */,
|
||||||
E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */,
|
E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */,
|
||||||
E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
|
E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
|
||||||
E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */,
|
E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */,
|
||||||
|
|
|
@ -22,10 +22,10 @@
|
||||||
"color" : {
|
"color" : {
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "0.100",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.000",
|
"blue" : "1.000",
|
||||||
"green" : "0.000",
|
"green" : "1.000",
|
||||||
"red" : "0.000"
|
"red" : "1.000"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
@ -40,10 +40,10 @@
|
||||||
"color" : {
|
"color" : {
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "0.100",
|
"alpha" : "1.000",
|
||||||
"blue" : "1.000",
|
"blue" : "0.000",
|
||||||
"green" : "1.000",
|
"green" : "0.000",
|
||||||
"red" : "1.000"
|
"red" : "0.000"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "1.000",
|
||||||
|
"green" : "1.000",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "0.100",
|
||||||
|
"blue" : "0.000",
|
||||||
|
"green" : "0.000",
|
||||||
|
"red" : "0.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "0.100",
|
||||||
|
"blue" : "1.000",
|
||||||
|
"green" : "1.000",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "1.000",
|
||||||
|
"green" : "1.000",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "0.250",
|
||||||
|
"blue" : "0.000",
|
||||||
|
"green" : "0.000",
|
||||||
|
"red" : "0.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "0.250",
|
||||||
|
"blue" : "1.000",
|
||||||
|
"green" : "1.000",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "1.000",
|
||||||
|
"green" : "1.000",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.000",
|
||||||
|
"green" : "0.000",
|
||||||
|
"red" : "0.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "1.000",
|
||||||
|
"green" : "1.000",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,97 +62,91 @@ struct LiveTVChannelItemWideElement: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
HStack {
|
ZStack {
|
||||||
ZStack(alignment: .center) {
|
HStack {
|
||||||
ImageView(channel.getPrimaryImage(maxWidth: 128))
|
ZStack(alignment: .center) {
|
||||||
.aspectRatio(contentMode: .fit)
|
ImageView(channel.getPrimaryImage(maxWidth: 128))
|
||||||
.padding(.init(top: 0, leading: 0, bottom: 8, trailing: 0))
|
.aspectRatio(contentMode: .fit)
|
||||||
VStack(alignment: .center) {
|
.padding(.init(top: 0, leading: 0, bottom: 8, trailing: 0))
|
||||||
Spacer()
|
VStack(alignment: .center) {
|
||||||
.frame(maxHeight: .infinity)
|
Spacer()
|
||||||
GeometryReader { gp in
|
.frame(maxHeight: .infinity)
|
||||||
ZStack(alignment: .leading) {
|
GeometryReader { gp in
|
||||||
RoundedRectangle(cornerRadius: 3)
|
ZStack(alignment: .leading) {
|
||||||
.fill(Color.gray)
|
RoundedRectangle(cornerRadius: 3)
|
||||||
.opacity(0.4)
|
.fill(Color.gray)
|
||||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 6, maxHeight: 6)
|
.opacity(0.4)
|
||||||
RoundedRectangle(cornerRadius: 6)
|
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 6, maxHeight: 6)
|
||||||
.fill(Color.jellyfinPurple)
|
RoundedRectangle(cornerRadius: 6)
|
||||||
.frame(width: CGFloat(progressPercent * gp.size.width), height: 6)
|
.fill(Color.jellyfinPurple)
|
||||||
|
.frame(width: CGFloat(progressPercent * gp.size.width), height: 6)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.frame(height: 6, alignment: .center)
|
||||||
|
.padding(.init(top: 0, leading: 4, bottom: 0, trailing: 4))
|
||||||
}
|
}
|
||||||
.frame(height: 6, alignment: .center)
|
if loading {
|
||||||
.padding(.init(top: 0, leading: 4, bottom: 0, trailing: 4))
|
|
||||||
}
|
ProgressView()
|
||||||
if loading {
|
|
||||||
|
|
||||||
ProgressView()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.aspectRatio(1.0, contentMode: .fit)
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
let channelNumber = channel.number != nil ? "\(channel.number ?? "") " : ""
|
|
||||||
let channelName = "\(channelNumber)\(channel.name ?? "?")"
|
|
||||||
Text(channelName)
|
|
||||||
.font(.body)
|
|
||||||
.lineLimit(1)
|
|
||||||
.frame(alignment: .leading)
|
|
||||||
HStack(alignment: .top) {
|
|
||||||
Text(currentProgramText.timeDisplay)
|
|
||||||
.font(.footnote)
|
|
||||||
.lineLimit(2)
|
|
||||||
.foregroundColor(.green)
|
|
||||||
.frame(width: 40)
|
|
||||||
Text(currentProgramText.title)
|
|
||||||
.font(.footnote)
|
|
||||||
.lineLimit(2)
|
|
||||||
.foregroundColor(.green)
|
|
||||||
}
|
|
||||||
if nextProgramsText.count > 0,
|
|
||||||
let nextItem = nextProgramsText[0] {
|
|
||||||
HStack(alignment: .top) {
|
|
||||||
Text(nextItem.timeDisplay)
|
|
||||||
.font(.footnote)
|
|
||||||
.lineLimit(2)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
.frame(width: 40)
|
|
||||||
Text(nextItem.title)
|
|
||||||
.font(.footnote)
|
|
||||||
.lineLimit(2)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if nextProgramsText.count > 1,
|
.aspectRatio(1.0, contentMode: .fit)
|
||||||
let nextItem2 = nextProgramsText[1] {
|
VStack(alignment: .leading) {
|
||||||
HStack(alignment: .top) {
|
let channelNumber = channel.number != nil ? "\(channel.number ?? "") " : ""
|
||||||
Text(nextItem2.timeDisplay)
|
let channelName = "\(channelNumber)\(channel.name ?? "?")"
|
||||||
.font(.footnote)
|
Text(channelName)
|
||||||
.lineLimit(2)
|
.font(.body)
|
||||||
.foregroundColor(.gray)
|
.lineLimit(1)
|
||||||
.frame(width: 40)
|
.foregroundColor(Color.jellyfinPurple)
|
||||||
Text(nextItem2.title)
|
.frame(alignment: .leading)
|
||||||
.font(.footnote)
|
.padding(.init(top: 0, leading: 0, bottom: 4, trailing: 0))
|
||||||
.lineLimit(2)
|
programLabel(timeText: currentProgramText.timeDisplay, titleText: currentProgramText.title, color: Color("TextHighlightColor"))
|
||||||
.foregroundColor(.gray)
|
if nextProgramsText.count > 0,
|
||||||
|
let nextItem = nextProgramsText[0] {
|
||||||
|
programLabel(timeText: nextItem.timeDisplay, titleText: nextItem.title, color: Color.gray)
|
||||||
}
|
}
|
||||||
|
if nextProgramsText.count > 1,
|
||||||
|
let nextItem2 = nextProgramsText[1] {
|
||||||
|
programLabel(timeText: nextItem2.timeDisplay, titleText: nextItem2.title, color: Color.gray)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
Spacer()
|
.frame(alignment: .leading)
|
||||||
|
.padding()
|
||||||
|
.opacity(loading ? 0.5 : 1.0)
|
||||||
|
}
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 10, style: .continuous).fill(Color("BackgroundSecondaryColor"))
|
||||||
|
)
|
||||||
|
.frame(height: 128)
|
||||||
|
.onTapGesture {
|
||||||
|
onSelect { loadingState in
|
||||||
|
loading = loadingState
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.frame(alignment: .leading)
|
|
||||||
.padding()
|
|
||||||
.opacity(loading ? 0.5 : 1.0)
|
|
||||||
}
|
}
|
||||||
.background(
|
.background{
|
||||||
RoundedRectangle(cornerRadius: 16, style: .continuous).fill(Color("BackgroundColor"))
|
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||||||
)
|
.fill(Color("BackgroundColor"))
|
||||||
.frame(height: 128)
|
.shadow(color: Color("ShadowColor"), radius: 4, x: 0, y: 0)
|
||||||
.onTapGesture {
|
}
|
||||||
onSelect { loadingState in
|
}
|
||||||
loading = loadingState
|
|
||||||
}
|
@ViewBuilder
|
||||||
|
func programLabel(timeText: String, titleText: String, color: Color) -> some View {
|
||||||
|
HStack(alignment: .top) {
|
||||||
|
Text(timeText)
|
||||||
|
.font(.footnote)
|
||||||
|
.lineLimit(2)
|
||||||
|
.foregroundColor(color)
|
||||||
|
.frame(width: 38, alignment: .leading)
|
||||||
|
Text(titleText)
|
||||||
|
.font(.footnote)
|
||||||
|
.lineLimit(2)
|
||||||
|
.foregroundColor(color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,7 @@ struct LiveTVChannelsView: View {
|
||||||
)
|
)
|
||||||
let groupSize = NSCollectionLayoutSize(
|
let groupSize = NSCollectionLayoutSize(
|
||||||
widthDimension: .fractionalWidth(1.0),
|
widthDimension: .fractionalWidth(1.0),
|
||||||
heightDimension: .absolute(132)
|
heightDimension: .absolute(144)
|
||||||
)
|
)
|
||||||
let group = NSCollectionLayoutGroup.horizontal(
|
let group = NSCollectionLayoutGroup.horizontal(
|
||||||
layoutSize: groupSize,
|
layoutSize: groupSize,
|
||||||
|
@ -114,7 +114,7 @@ struct LiveTVChannelsView: View {
|
||||||
} else {
|
} else {
|
||||||
if isPortrait {
|
if isPortrait {
|
||||||
let itemSize = NSCollectionLayoutSize(
|
let itemSize = NSCollectionLayoutSize(
|
||||||
widthDimension: .absolute(UIScreen.main.bounds.width - 2),
|
widthDimension: .absolute(UIScreen.main.bounds.width - 32),
|
||||||
heightDimension: .fractionalHeight(1)
|
heightDimension: .fractionalHeight(1)
|
||||||
)
|
)
|
||||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||||||
|
@ -124,7 +124,7 @@ struct LiveTVChannelsView: View {
|
||||||
)
|
)
|
||||||
let groupSize = NSCollectionLayoutSize(
|
let groupSize = NSCollectionLayoutSize(
|
||||||
widthDimension: .fractionalWidth(1.0),
|
widthDimension: .fractionalWidth(1.0),
|
||||||
heightDimension: .absolute(132)
|
heightDimension: .absolute(144)
|
||||||
)
|
)
|
||||||
let group = NSCollectionLayoutGroup.horizontal(
|
let group = NSCollectionLayoutGroup.horizontal(
|
||||||
layoutSize: groupSize,
|
layoutSize: groupSize,
|
||||||
|
@ -149,7 +149,7 @@ struct LiveTVChannelsView: View {
|
||||||
)
|
)
|
||||||
let groupSize = NSCollectionLayoutSize(
|
let groupSize = NSCollectionLayoutSize(
|
||||||
widthDimension: .fractionalWidth(1.0),
|
widthDimension: .fractionalWidth(1.0),
|
||||||
heightDimension: .absolute(132)
|
heightDimension: .absolute(144)
|
||||||
)
|
)
|
||||||
let group = NSCollectionLayoutGroup.horizontal(
|
let group = NSCollectionLayoutGroup.horizontal(
|
||||||
layoutSize: groupSize,
|
layoutSize: groupSize,
|
||||||
|
|
|
@ -19,7 +19,11 @@ struct ExperimentalSettingsView: View {
|
||||||
var nativePlayer
|
var nativePlayer
|
||||||
@Default(.Experimental.liveTVAlphaEnabled)
|
@Default(.Experimental.liveTVAlphaEnabled)
|
||||||
var liveTVAlphaEnabled
|
var liveTVAlphaEnabled
|
||||||
|
@Default(.Experimental.liveTVForceDirectPlay)
|
||||||
|
var liveTVForceDirectPlay
|
||||||
|
@Default(.Experimental.liveTVNativePlayer)
|
||||||
|
var liveTVNativePlayer
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
|
@ -38,6 +42,10 @@ struct ExperimentalSettingsView: View {
|
||||||
|
|
||||||
Toggle("Live TV (Alpha)", isOn: $liveTVAlphaEnabled)
|
Toggle("Live TV (Alpha)", isOn: $liveTVAlphaEnabled)
|
||||||
|
|
||||||
|
Toggle("Live TV Force Direct Play", isOn: $liveTVForceDirectPlay)
|
||||||
|
|
||||||
|
Toggle("Live TV Native Player", isOn: $liveTVNativePlayer)
|
||||||
|
|
||||||
} header: {
|
} header: {
|
||||||
Text("Live TV")
|
Text("Live TV")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
//
|
||||||
|
// 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 AVKit
|
||||||
|
import Combine
|
||||||
|
import JellyfinAPI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class LiveTVNativePlayerViewController: AVPlayerViewController {
|
||||||
|
|
||||||
|
let viewModel: VideoPlayerViewModel
|
||||||
|
|
||||||
|
var timeObserverToken: Any?
|
||||||
|
|
||||||
|
var lastProgressTicks: Int64 = 0
|
||||||
|
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
init(viewModel: VideoPlayerViewModel) {
|
||||||
|
|
||||||
|
self.viewModel = viewModel
|
||||||
|
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
||||||
|
let player: AVPlayer
|
||||||
|
|
||||||
|
if let transcodedStreamURL = viewModel.transcodedStreamURL {
|
||||||
|
player = AVPlayer(url: transcodedStreamURL)
|
||||||
|
} else {
|
||||||
|
player = AVPlayer(url: viewModel.hlsStreamURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
player.appliesMediaSelectionCriteriaAutomatically = false
|
||||||
|
|
||||||
|
let timeScale = CMTimeScale(NSEC_PER_SEC)
|
||||||
|
let time = CMTime(seconds: 5, preferredTimescale: timeScale)
|
||||||
|
|
||||||
|
timeObserverToken = player.addPeriodicTimeObserver(forInterval: time, queue: .main) { [weak self] time in
|
||||||
|
if time.seconds != 0 {
|
||||||
|
self?.sendProgressReport(seconds: time.seconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.player = player
|
||||||
|
|
||||||
|
self.allowsPictureInPicturePlayback = true
|
||||||
|
self.player?.allowsExternalPlayback = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createMetadataItem(for identifier: AVMetadataIdentifier,
|
||||||
|
value: Any) -> AVMetadataItem
|
||||||
|
{
|
||||||
|
let item = AVMutableMetadataItem()
|
||||||
|
item.identifier = identifier
|
||||||
|
item.value = value as? NSCopying & NSObjectProtocol
|
||||||
|
// Specify "und" to indicate an undefined language.
|
||||||
|
item.extendedLanguageTag = "und"
|
||||||
|
return item.copy() as! AVMetadataItem
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
|
stop()
|
||||||
|
removePeriodicTimeObserver()
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePeriodicTimeObserver() {
|
||||||
|
if let timeObserverToken = timeObserverToken {
|
||||||
|
player?.removeTimeObserver(timeObserverToken)
|
||||||
|
self.timeObserverToken = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
player?.seek(to: CMTimeMake(value: viewModel.currentSecondTicks, timescale: 10_000_000),
|
||||||
|
toleranceBefore: CMTimeMake(value: 1, timescale: 1), toleranceAfter: CMTimeMake(value: 1, timescale: 1),
|
||||||
|
completionHandler: { _ in
|
||||||
|
self.play()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func play() {
|
||||||
|
player?.play()
|
||||||
|
|
||||||
|
viewModel.sendPlayReport()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sendProgressReport(seconds: Double) {
|
||||||
|
viewModel.setSeconds(Int64(seconds))
|
||||||
|
viewModel.sendProgressReport()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stop() {
|
||||||
|
self.player?.pause()
|
||||||
|
viewModel.sendStopReport()
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,19 +9,19 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
//struct NativePlayerView: UIViewControllerRepresentable {
|
struct LiveTVNativePlayerView: UIViewControllerRepresentable {
|
||||||
//
|
|
||||||
// let viewModel: VideoPlayerViewModel
|
let viewModel: VideoPlayerViewModel
|
||||||
//
|
|
||||||
// typealias UIViewControllerType = NativePlayerViewController
|
typealias UIViewControllerType = LiveTVNativePlayerViewController
|
||||||
//
|
|
||||||
// func makeUIViewController(context: Context) -> NativePlayerViewController {
|
func makeUIViewController(context: Context) -> LiveTVNativePlayerViewController {
|
||||||
//
|
|
||||||
// NativePlayerViewController(viewModel: viewModel)
|
LiveTVNativePlayerViewController(viewModel: viewModel)
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// func updateUIViewController(_ uiViewController: NativePlayerViewController, context: Context) {}
|
func updateUIViewController(_ uiViewController: LiveTVNativePlayerViewController, context: Context) {}
|
||||||
//}
|
}
|
||||||
|
|
||||||
struct LiveTVPlayerView: UIViewControllerRepresentable {
|
struct LiveTVPlayerView: UIViewControllerRepresentable {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue