Skip to content

Commit

Permalink
Merge pull request jellyfin#398 from jhays/jhays/tv-player-fix
Browse files Browse the repository at this point in the history
LiveTV respect Force Direct Play and Native Player settings
  • Loading branch information
LePips authored Mar 26, 2022
2 parents c6a0e55 + 28e8a99 commit 6d04215
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,14 @@ final class LiveTVVideoPlayerCoordinator: NavigationCoordinatable {

@ViewBuilder
func makeStart() -> some View {
LiveTVVideoPlayerView(viewModel: viewModel)
.navigationBarHidden(true)
.ignoresSafeArea()
if Defaults[.Experimental.liveTVNativePlayer] {
LiveTVNativeVideoPlayerView(viewModel: viewModel)
.navigationBarHidden(true)
.ignoresSafeArea()
} else {
LiveTVVideoPlayerView(viewModel: viewModel)
.navigationBarHidden(true)
.ignoresSafeArea()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ extension BaseItemDto {
mediaSourceId: mediaSourceID)
directStreamURL = URL(string: directStreamBuilder.URLString)!

if let transcodeURL = currentMediaSource.transcodingUrl {
if let transcodeURL = currentMediaSource.transcodingUrl, !Defaults[.Experimental.liveTVForceDirectPlay] {
streamType = .transcode
transcodedStreamURL = URLComponents(string: SessionManager.main.currentLogin.server.currentURI
.appending(transcodeURL))!
Expand Down
4 changes: 3 additions & 1 deletion Shared/SwiftfinStore/SwiftfinStoreDefaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ extension Defaults.Keys {
static let syncSubtitleStateWithAdjacent = Key<Bool>("experimental.syncSubtitleState", default: false,
suite: SwiftfinStore.Defaults.generalSuite)
static let forceDirectPlay = Key<Bool>("forceDirectPlay", default: false, suite: SwiftfinStore.Defaults.generalSuite)
static let liveTVAlphaEnabled = Key<Bool>("liveTVAlphaEnabled", default: false, suite: SwiftfinStore.Defaults.generalSuite)
static let nativePlayer = Key<Bool>("nativePlayer", default: false, suite: SwiftfinStore.Defaults.generalSuite)
static let liveTVAlphaEnabled = Key<Bool>("liveTVAlphaEnabled", default: false, suite: SwiftfinStore.Defaults.generalSuite)
static let liveTVForceDirectPlay = Key<Bool>("liveTVForceDirectPlay", default: false, suite: SwiftfinStore.Defaults.generalSuite)
static let liveTVNativePlayer = Key<Bool>("liveTVNativePlayer", default: false, suite: SwiftfinStore.Defaults.generalSuite)
}

// tvos specific
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ final class VideoPlayerViewModel: ViewModel {
}

func setSeconds(_ seconds: Int64) {
let videoDuration = item.runTimeTicks!
guard let runTimeTicks = item.runTimeTicks else { return }
let videoDuration = runTimeTicks
let percentage = Double(seconds * 10_000_000) / Double(videoDuration)

sliderPercentage = percentage
Expand Down
144 changes: 100 additions & 44 deletions Shared/Views/LiveTVChannelItemElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,71 +10,127 @@ import JellyfinAPI
import SwiftUI

struct LiveTVChannelItemElement: View {
@Environment(\.isFocused)
var envFocused: Bool
@FocusState
private var focused: Bool
@State
var focused: Bool = false
private var loading: Bool = false
@State
private var isFocused: Bool = false

var channel: BaseItemDto
var program: BaseItemDto?
var startString = " "
var endString = " "
var progressPercent = Double(0)
var onSelect: (@escaping (Bool) -> Void) -> Void

private var detailText: String {
guard let program = program else {
return ""
}
var text = ""
if let season = program.parentIndexNumber,
let episode = program.indexNumber
{
text.append("\(season)x\(episode) ")
} else if let episode = program.indexNumber {
text.append("\(episode) ")
}
if let title = program.episodeTitle {
text.append("\(title) ")
}
if let year = program.productionYear {
text.append("\(year) ")
}
if let rating = program.officialRating {
text.append("\(rating)")
}
return text
}

var body: some View {
VStack {
HStack {
Spacer()
Text(channel.number ?? "")
.font(.footnote)
.frame(alignment: .trailing)
}.frame(alignment: .top)
ImageView(channel.getPrimaryImage(maxWidth: 125))
.frame(width: 125, alignment: .center)
.offset(x: 0, y: -32)
Text(channel.name ?? "?")
.font(.footnote)
.lineLimit(1)
.frame(alignment: .center)
Text(program?.name ?? L10n.notAvailableSlash)
.font(.body)
.lineLimit(1)
.foregroundColor(.green)
ZStack {
VStack {
HStack {
Text(startString)
Text(channel.number ?? "")
.font(.footnote)
.lineLimit(1)
.frame(alignment: .leading)

.padding()
Spacer()
}.frame(alignment: .top)
Spacer()
}
VStack {
ImageView(channel.getPrimaryImage(maxWidth: 128))
.aspectRatio(contentMode: .fit)
.frame(width: 128, alignment: .center)
.padding(.init(top: 8, leading: 0, bottom: 0, trailing: 0))
Text(channel.name ?? "?")
.font(.footnote)
.lineLimit(1)
.frame(alignment: .center)
Text(program?.name ?? L10n.notAvailableSlash)
.font(.body)
.lineLimit(1)
.foregroundColor(.green)
Text(detailText)
.font(.body)
.lineLimit(1)
.foregroundColor(.green)
Spacer()
HStack(alignment: .bottom) {
VStack {
Spacer()
HStack {
Text(startString)
.font(.footnote)
.lineLimit(1)
.frame(alignment: .leading)

Text(endString)
.font(.footnote)
.lineLimit(1)
.frame(alignment: .trailing)
}
GeometryReader { gp in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 6)
.fill(Color.gray)
.opacity(0.4)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 12, maxHeight: 12)
RoundedRectangle(cornerRadius: 6)
.fill(Color.jellyfinPurple)
.frame(width: CGFloat(progressPercent * gp.size.width), height: 12)
Spacer()

Text(endString)
.font(.footnote)
.lineLimit(1)
.frame(alignment: .trailing)
}
GeometryReader { gp in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 6)
.fill(Color.gray)
.opacity(0.4)
.frame(minWidth: 100, maxWidth: .infinity, minHeight: 12, maxHeight: 12)
RoundedRectangle(cornerRadius: 6)
.fill(Color.jellyfinPurple)
.frame(width: CGFloat(progressPercent * gp.size.width), height: 12)
}
.frame(alignment: .bottom)
}
}
}
}
.padding()
.opacity(loading ? 0.5 : 1.0)

if loading {
ProgressView()
}
}
.padding()
.background(Color.clear)
.border(focused ? Color.blue : Color.clear, width: 4)
.onChange(of: envFocused) { envFocus in
.overlay(RoundedRectangle(cornerRadius: 20)
.stroke(isFocused ? Color.blue : Color.clear, lineWidth: 4))
.cornerRadius(20)
.scaleEffect(isFocused ? 1.1 : 1)
.focusable(true)
.focused($focused)
.onChange(of: focused) { foc in
withAnimation(.linear(duration: 0.15)) {
self.focused = envFocus
self.isFocused = foc
}
}
.onLongPressGesture(minimumDuration: 0.01, pressing: { _ in }) {
onSelect { loadingState in
loading = loadingState
}
}
.scaleEffect(focused ? 1.1 : 1)
}
}
50 changes: 25 additions & 25 deletions Swiftfin tvOS/Views/LibraryListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,31 @@ struct LibraryListView: View {
self.mainCoordinator.root(\.liveTV)
}
label: {
ZStack {
HStack {
Spacer()
VStack {
Text(library.name ?? "")
.foregroundColor(.white)
.font(.title2)
.fontWeight(.semibold)
}
Spacer()
}.padding(32)
}
.frame(minWidth: 100, maxWidth: .infinity)
.frame(height: 100)
}
.cornerRadius(10)
.shadow(radius: 5)
.padding(.bottom, 5)
}
} else {
Button {
self.libraryListRouter.route(to: \.library,
(viewModel: LibraryViewModel(parentID: library.id), title: library.name ?? ""))
}
label: {
ZStack {
HStack {
Spacer()
Expand All @@ -56,31 +81,6 @@ struct LibraryListView: View {
.cornerRadius(10)
.shadow(radius: 5)
.padding(.bottom, 5)
}
} else {
Button {
self.libraryListRouter.route(to: \.library,
(viewModel: LibraryViewModel(parentID: library.id), title: library.name ?? ""))
}
label: {
ZStack {
HStack {
Spacer()
VStack {
Text(library.name ?? "")
.foregroundColor(.white)
.font(.title2)
.fontWeight(.semibold)
}
Spacer()
}.padding(32)
}
.frame(minWidth: 100, maxWidth: .infinity)
.frame(height: 100)
}
.cornerRadius(10)
.shadow(radius: 5)
.padding(.bottom, 5)
}
}
} else {
Expand Down
27 changes: 15 additions & 12 deletions Swiftfin tvOS/Views/LiveTVChannelsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,21 @@ struct LiveTVChannelsView: View {
let item = cell.item
let channel = item.channel
if channel.type != "Folder" {
Button {
self.viewModel.fetchVideoPlayerViewModel(item: channel) { playerViewModel in
self.router.route(to: \.videoPlayer, playerViewModel)
}
} label: {
LiveTVChannelItemElement(channel: channel,
program: item.program,
startString: item.program?.getLiveStartTimeString(formatter: viewModel.timeFormatter) ?? " ",
endString: item.program?.getLiveEndTimeString(formatter: viewModel.timeFormatter) ?? " ",
progressPercent: item.program?.getLiveProgressPercentage() ?? 0)
}
.buttonStyle(PlainNavigationLinkButtonStyle())
let progressPercent = item.program?.getLiveProgressPercentage() ?? 0
LiveTVChannelItemElement(channel: channel,
program: item.program,
startString: item.program?.getLiveStartTimeString(formatter: viewModel.timeFormatter) ?? " ",
endString: item.program?.getLiveEndTimeString(formatter: viewModel.timeFormatter) ?? " ",
progressPercent: progressPercent > 1.0 ? 1.0 : progressPercent,
onSelect: { loadingAction in
loadingAction(true)
self.viewModel.fetchVideoPlayerViewModel(item: channel) { playerViewModel in
self.router.route(to: \.videoPlayer, playerViewModel)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
loadingAction(false)
}
}
})
}
}

Expand Down
23 changes: 19 additions & 4 deletions Swiftfin tvOS/Views/SettingsView/ExperimentalSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ struct ExperimentalSettingsView: View {
var forceDirectPlay
@Default(.Experimental.syncSubtitleStateWithAdjacent)
var syncSubtitleStateWithAdjacent
@Default(.Experimental.liveTVAlphaEnabled)
var liveTVAlphaEnabled
@Default(.Experimental.nativePlayer)
var nativePlayer

@Default(.Experimental.liveTVAlphaEnabled)
var liveTVAlphaEnabled
@Default(.Experimental.liveTVForceDirectPlay)
var liveTVForceDirectPlay
@Default(.Experimental.liveTVNativePlayer)
var liveTVNativePlayer

var body: some View {
Form {
Section {
Expand All @@ -28,13 +33,23 @@ struct ExperimentalSettingsView: View {

Toggle("Sync Subtitles with Adjacent Episodes", isOn: $syncSubtitleStateWithAdjacent)

Toggle("Live TV (Alpha)", isOn: $liveTVAlphaEnabled)

Toggle("Native Player", isOn: $nativePlayer)

} header: {
L10n.experimental.text
}

Section {

Toggle("Live TV (Alpha)", isOn: $liveTVAlphaEnabled)

Toggle("Live TV Force Direct Play", isOn: $liveTVForceDirectPlay)

Toggle("Live TV Native Player", isOn: $liveTVNativePlayer)

} header: {
Text("Live TV")
}
}
}
}
23 changes: 23 additions & 0 deletions Swiftfin tvOS/Views/VideoPlayer/LiveTVNativeVideoPlayerView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// 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 LiveTVNativeVideoPlayerView: UIViewControllerRepresentable {

let viewModel: VideoPlayerViewModel

typealias UIViewControllerType = NativePlayerViewController

func makeUIViewController(context: Context) -> NativePlayerViewController {
NativePlayerViewController(viewModel: viewModel)
}

func updateUIViewController(_ uiViewController: NativePlayerViewController, context: Context) {}
}
Loading

0 comments on commit 6d04215

Please sign in to comment.