Merge branch 'main' into PangMo5/ci-macos-12

This commit is contained in:
PangMo5 2022-04-12 01:55:18 +09:00
commit 957b35b870
16 changed files with 219 additions and 106 deletions

View File

@ -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()
}
}
}

View File

@ -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))!

View File

@ -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

View File

@ -21,6 +21,10 @@ protocol EpisodesRowManager: ViewModel {
extension EpisodesRowManager {
var sortedSeasons: [BaseItemDto] {
Array(seasonsEpisodes.keys).sorted(by: { $0.indexNumber ?? 0 < $1.indexNumber ?? 0 })
}
// Also retrieves the current season episodes if available
func retrieveSeasons() {
TvShowsAPI.getSeasons(seriesId: item.seriesId ?? "",

View File

@ -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

View File

@ -1,80 +0,0 @@
//
// 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 JellyfinAPI
import SwiftUI
struct LiveTVChannelItemElement: View {
@Environment(\.isFocused)
var envFocused: Bool
@State
var focused: Bool = false
var channel: BaseItemDto
var program: BaseItemDto?
var startString = " "
var endString = " "
var progressPercent = Double(0)
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)
VStack {
HStack {
Text(startString)
.font(.footnote)
.lineLimit(1)
.frame(alignment: .leading)
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)
}
}
}
}
.padding()
.background(Color.clear)
.border(focused ? Color.blue : Color.clear, width: 4)
.onChange(of: envFocused) { envFocus in
withAnimation(.linear(duration: 0.15)) {
self.focused = envFocus
}
}
.scaleEffect(focused ? 1.1 : 1)
}
}

View File

@ -0,0 +1,136 @@
//
// 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 JellyfinAPI
import SwiftUI
struct LiveTVChannelItemElement: View {
@FocusState
private var focused: Bool
@State
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 {
ZStack {
VStack {
HStack {
Text(channel.number ?? "")
.font(.footnote)
.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)
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()
}
}
.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.isFocused = foc
}
}
.onLongPressGesture(minimumDuration: 0.01, pressing: { _ in }) {
onSelect { loadingState in
loading = loadingState
}
}
}
}

View File

@ -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)
}
}
})
}
}

View File

@ -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 {
@ -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")
}
}
}
}

View File

@ -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) {}
}

View File

@ -262,7 +262,7 @@
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 */; };
C4AE2C3327498DBE00AE13CF /* LiveTVChannelItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */; };
C4B9B91427E1921B0063535C /* LiveTVNativeVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B9B91327E1921B0063535C /* LiveTVNativeVideoPlayerView.swift */; };
C4BE0764271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0762271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift */; };
C4BE0767271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0765271FC109003F4AD1 /* TVLibrariesViewModel.swift */; };
C4BE076A271FC164003F4AD1 /* TVLibrariesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0768271FC164003F4AD1 /* TVLibrariesView.swift */; };
@ -741,6 +741,7 @@
C4534984279A40C50045F1E2 /* LiveTVVideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveTVVideoPlayerView.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>"; };
C4BE0762271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesCoordinator.swift; sourceTree = "<group>"; };
C4BE0765271FC109003F4AD1 /* TVLibrariesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesViewModel.swift; sourceTree = "<group>"; };
C4BE0768271FC164003F4AD1 /* TVLibrariesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesView.swift; sourceTree = "<group>"; };
@ -999,6 +1000,7 @@
E17885A7278130690094FBCF /* Overlays */,
E1C812C8277AE40900918266 /* VideoPlayerView.swift */,
C4534984279A40C50045F1E2 /* LiveTVVideoPlayerView.swift */,
C4B9B91327E1921B0063535C /* LiveTVNativeVideoPlayerView.swift */,
C453497E279A2DA50045F1E2 /* LiveTVPlayerViewController.swift */,
E1384943278036C70024FB48 /* VLCPlayerViewController.swift */,
E13AD72F2798C60F00FDCEE8 /* NativePlayerViewController.swift */,
@ -1563,6 +1565,7 @@
C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */,
53A83C32268A309300DF3D92 /* LibraryView.swift */,
C4BE078A272844AF003F4AD1 /* LiveTVChannelsView.swift */,
C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */,
C4BE078D27298817003F4AD1 /* LiveTVHomeView.swift */,
C4BE07732725EB66003F4AD1 /* LiveTVProgramsView.swift */,
C40CD927271F8DAB000FB198 /* MovieLibrariesView.swift */,
@ -1758,7 +1761,6 @@
531AC8BE26750DE20091C7EB /* ImageView.swift */,
E1047E2227E5880000CB0D4A /* InitialFailureView.swift */,
621338B22660A07800A81A2A /* LazyView.swift */,
C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */,
53E4E648263F725B00F67C6B /* MultiSelectorView.swift */,
6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */,
531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */,
@ -2171,6 +2173,7 @@
E193D53327193F7D00900D82 /* FilterCoordinator.swift in Sources */,
6267B3DC2671139500A7371D /* ImageExtensions.swift in Sources */,
E103A6A9278AB6FF00820EC7 /* CinematicNextUpCardView.swift in Sources */,
C4B9B91427E1921B0063535C /* LiveTVNativeVideoPlayerView.swift in Sources */,
E107BB9427880A8F00354E07 /* CollectionItemViewModel.swift in Sources */,
C4534985279A40C60045F1E2 /* LiveTVVideoPlayerView.swift in Sources */,
E1A2C15A279A7D76005EC829 /* BundleExtensions.swift in Sources */,
@ -2437,7 +2440,6 @@
E13DD3FC2717EAE8009D4DAF /* UserListView.swift in Sources */,
6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */,
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
C4AE2C3327498DBE00AE13CF /* LiveTVChannelItemElement.swift in Sources */,
E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */,
E1AA33222782648000F6439C /* OverlaySliderColor.swift in Sources */,
E1D4BF842719D25A00A11E64 /* TrackLanguage.swift in Sources */,

View File

@ -29,7 +29,8 @@ struct EpisodesRowView<RowManager>: View where RowManager: EpisodesRowManager {
}
} else {
Menu {
ForEach(Array(viewModel.seasonsEpisodes.keys).sorted(by: { $0.name ?? "" < $1.name ?? "" }), id: \.self) { season in
ForEach(viewModel.sortedSeasons,
id: \.self) { season in
Button {
viewModel.select(season: season)
} label: {