Merge pull request #267 from LePips/final-work-for-winter-break-2022

Some polishing
This commit is contained in:
aiden 3 2022-01-08 14:15:26 -05:00 committed by GitHub
commit fa28b4019b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 292 additions and 100 deletions

View File

@ -0,0 +1,22 @@
//
/*
* 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 2021 Aiden Vigue & Jellyfin Contributors
*/
import Foundation
import JellyfinAPI
extension MediaStream {
func externalURL(base: String) -> URL? {
guard let deliveryURL = deliveryUrl else { return nil }
var baseComponents = URLComponents(string: base)
baseComponents?.path += deliveryURL
return baseComponents?.url
}
}

View File

@ -67,6 +67,8 @@ final class VideoPlayerViewModel: ViewModel {
} }
@Published var autoplayEnabled: Bool { @Published var autoplayEnabled: Bool {
willSet { willSet {
previousItemVideoPlayerViewModel?.autoplayEnabled = newValue
nextItemVideoPlayerViewModel?.autoplayEnabled = newValue
Defaults[.autoplayEnabled] = newValue Defaults[.autoplayEnabled] = newValue
} }
} }
@ -115,6 +117,16 @@ final class VideoPlayerViewModel: ViewModel {
return Int64(currentSeconds) * 10_000_000 return Int64(currentSeconds) * 10_000_000
} }
// MARK: Helpers
var currentAudioStream: MediaStream? {
return audioStreams.first(where: { $0.index == selectedAudioStreamIndex })
}
var currentSubtitleStream: MediaStream? {
return subtitleStreams.first(where: { $0.index == selectedSubtitleStreamIndex })
}
// Necessary PassthroughSubject to capture manual scrubbing from sliders // Necessary PassthroughSubject to capture manual scrubbing from sliders
let sliderScrubbingSubject = PassthroughSubject<VideoPlayerViewModel, Never>() let sliderScrubbingSubject = PassthroughSubject<VideoPlayerViewModel, Never>()

View File

@ -7,6 +7,7 @@
* Copyright 2021 Aiden Vigue & Jellyfin Contributors * Copyright 2021 Aiden Vigue & Jellyfin Contributors
*/ */
import CachedAsyncImage
import SwiftUI import SwiftUI
struct ImageView: View { struct ImageView: View {
@ -31,7 +32,7 @@ struct ImageView: View {
private var failureImage: some View { private var failureImage: some View {
ZStack { ZStack {
Rectangle() Rectangle()
.foregroundColor(Color.systemFill) .foregroundColor(Color(UIColor.darkGray))
Text(failureInitials) Text(failureInitials)
.font(.largeTitle) .font(.largeTitle)
@ -40,21 +41,36 @@ struct ImageView: View {
} }
var body: some View { var body: some View {
AsyncImage(url: source) { phase in CachedAsyncImage(url: source, urlCache: .imageCache, transaction: Transaction(animation: .easeInOut)) { phase in
if let image = phase.image { switch phase {
case .success(let image):
image image
.resizable() .resizable()
.aspectRatio(contentMode: .fill) .aspectRatio(contentMode: .fill)
} else if phase.error != nil { case .failure(_):
failureImage failureImage
} else { default:
// TODO: remove once placeholder hash image fixed // TODO: remove once placeholder hash image fixed
#if os(tvOS)
ZStack { ZStack {
Color.gray.ignoresSafeArea() Color.black.ignoresSafeArea()
ProgressView() ProgressView()
} }
#else
ZStack {
Color.gray.ignoresSafeArea()
ProgressView()
}
#endif
} }
} }
} }
} }
extension URLCache {
static let imageCache = URLCache(memoryCapacity: 512*1000*1000, diskCapacity: 10*1000*1000*1000)
}

View File

@ -84,7 +84,7 @@ struct EpisodesRowView: View {
HStack(alignment: .top) { HStack(alignment: .top) {
VStack(alignment: .leading) { VStack(alignment: .leading) {
ImageView(src: episode.getBackdropImage(maxWidth: 445), ImageView(src: episode.getBackdropImage(maxWidth: 500),
bh: episode.getBackdropImageBlurHash()) bh: episode.getBackdropImageBlurHash())
.mask(Rectangle().frame(width: 500, height: 280)) .mask(Rectangle().frame(width: 500, height: 280))
.frame(width: 500, height: 280) .frame(width: 500, height: 280)

View File

@ -22,8 +22,13 @@ struct ContinueWatchingCard: View {
} label: { } label: {
ZStack(alignment: .bottom) { ZStack(alignment: .bottom) {
ImageView(src: item.getBackdropImage(maxWidth: 500)) if item.itemType == .episode {
.frame(width: 500, height: 281.25) ImageView(src: item.getSeriesBackdropImage(maxWidth: 500))
.frame(width: 500, height: 281.25)
} else {
ImageView(src: item.getBackdropImage(maxWidth: 500))
.frame(width: 500, height: 281.25)
}
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
Text(item.getItemProgressString() ?? "") Text(item.getItemProgressString() ?? "")
@ -57,6 +62,7 @@ struct ContinueWatchingCard: View {
.fontWeight(.semibold) .fontWeight(.semibold)
.foregroundColor(.primary) .foregroundColor(.primary)
.lineLimit(1) .lineLimit(1)
.frame(width: 500, alignment: .leading)
if item.itemType == .episode { if item.itemType == .episode {
Text(item.getEpisodeLocator() ?? "") Text(item.getEpisodeLocator() ?? "")
@ -64,8 +70,11 @@ struct ContinueWatchingCard: View {
.fontWeight(.medium) .fontWeight(.medium)
.foregroundColor(.secondary) .foregroundColor(.secondary)
.lineLimit(1) .lineLimit(1)
} else {
Text("")
} }
} }
} }
.padding(.vertical)
} }
} }

View File

@ -25,7 +25,7 @@ struct ContinueWatchingView: View {
.padding(.leading, 50) .padding(.leading, 50)
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
LazyHStack { LazyHStack(alignment: .top) {
ForEach(items, id: \.self) { item in ForEach(items, id: \.self) { item in
ContinueWatchingCard(item: item) ContinueWatchingCard(item: item)
} }

View File

@ -31,6 +31,7 @@ struct CinematicEpisodeItemView: View {
ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920), ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920),
bh: viewModel.item.getBackdropImageBlurHash()) bh: viewModel.item.getBackdropImageBlurHash())
.frame(height: UIScreen.main.bounds.height - 10)
.ignoresSafeArea() .ignoresSafeArea()
ScrollView { ScrollView {

View File

@ -16,10 +16,60 @@ struct LatestMediaView: View {
@Default(.showPosterLabels) var showPosterLabels @Default(.showPosterLabels) var showPosterLabels
var body: some View { var body: some View {
PortraitItemsRowView(rowTitle: L10n.latestWithString(viewModel.library.name ?? ""), VStack(alignment: .leading) {
items: viewModel.items,
showItemTitles: showPosterLabels) { item in L10n.latestWithString(viewModel.library.name ?? "").text
homeRouter.route(to: \.modalItem, item) .font(.title3)
.padding(.horizontal, 50)
ScrollView(.horizontal) {
HStack(alignment: .top) {
ForEach(viewModel.items, id: \.self) { item in
VStack(spacing: 15) {
Button {
homeRouter.route(to: \.modalItem, item)
} label: {
ImageView(src: item.portraitHeaderViewURL(maxWidth: 257))
.frame(width: 257, height: 380)
}
.frame(height: 380)
.buttonStyle(PlainButtonStyle())
if showPosterLabels {
Text(item.title)
.lineLimit(2)
.frame(width: 257)
}
}
}
Button {
homeRouter.route(to: \.library, (viewModel: .init(parentID: viewModel.library.id!,
filters: LibraryFilters(filters: [], sortOrder: [.descending], sortBy: [.dateAdded])),
title: viewModel.library.name ?? ""))
} label: {
ZStack {
Color(UIColor.darkGray)
.opacity(0.5)
VStack(spacing: 20) {
Image(systemName: "chevron.right")
.font(.title)
L10n.seeAll.text
.font(.title3)
}
}
}
.frame(width: 257, height: 380)
.buttonStyle(PlainButtonStyle())
}
.padding(.horizontal, 50)
.padding(.vertical)
}
.edgesIgnoringSafeArea(.horizontal)
} }
.focusSection()
} }
} }

View File

@ -20,8 +20,13 @@ struct NextUpCard: View {
Button { Button {
homeRouter.route(to: \.modalItem, item) homeRouter.route(to: \.modalItem, item)
} label: { } label: {
ImageView(src: item.getBackdropImage(maxWidth: 500)) if item.itemType == .episode {
.frame(width: 500, height: 281.25) ImageView(src: item.getSeriesBackdropImage(maxWidth: 500))
.frame(width: 500, height: 281.25)
} else {
ImageView(src: item.getBackdropImage(maxWidth: 500))
.frame(width: 500, height: 281.25)
}
} }
.buttonStyle(CardButtonStyle()) .buttonStyle(CardButtonStyle())
.padding(.top) .padding(.top)

View File

@ -24,7 +24,7 @@ class VLCPlayerViewController: UIViewController {
// MARK: variables // MARK: variables
private var viewModel: VideoPlayerViewModel private var viewModel: VideoPlayerViewModel
private var vlcMediaPlayer = VLCMediaPlayer() private var vlcMediaPlayer: VLCMediaPlayer
private var lastPlayerTicks: Int64 = 0 private var lastPlayerTicks: Int64 = 0
private var lastProgressReportTicks: Int64 = 0 private var lastProgressReportTicks: Int64 = 0
private var viewModelListeners = Set<AnyCancellable>() private var viewModelListeners = Set<AnyCancellable>()
@ -59,6 +59,7 @@ class VLCPlayerViewController: UIViewController {
init(viewModel: VideoPlayerViewModel) { init(viewModel: VideoPlayerViewModel) {
self.viewModel = viewModel self.viewModel = viewModel
self.vlcMediaPlayer = VLCMediaPlayer()
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
@ -118,14 +119,6 @@ class VLCPlayerViewController: UIViewController {
view.backgroundColor = .black view.backgroundColor = .black
// Outside of 'setupMediaPlayer' such that they
// aren't unnecessarily set more than once
vlcMediaPlayer.delegate = self
vlcMediaPlayer.drawable = videoContentView
// TODO: custom font sizes
vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 16)
setupMediaPlayer(newViewModel: viewModel) setupMediaPlayer(newViewModel: viewModel)
setupPanGestureRecognizer() setupPanGestureRecognizer()
@ -211,20 +204,20 @@ class VLCPlayerViewController: UIViewController {
hideConfirmCloseOverlay() hideConfirmCloseOverlay()
if Defaults[.downActionShowsMenu] { if Defaults[.downActionShowsMenu] {
if !displayingContentOverlay { if !displayingContentOverlay && !displayingOverlay {
didSelectMenu() didSelectMenu()
} }
} }
case .leftArrow: case .leftArrow:
hideConfirmCloseOverlay() hideConfirmCloseOverlay()
if !displayingContentOverlay { if !displayingContentOverlay && !displayingOverlay {
didSelectBackward() didSelectBackward()
} }
case .rightArrow: case .rightArrow:
hideConfirmCloseOverlay() hideConfirmCloseOverlay()
if !displayingContentOverlay { if !displayingContentOverlay && !displayingOverlay {
didSelectForward() didSelectForward()
} }
case .pageUp: () case .pageUp: ()
@ -246,9 +239,6 @@ class VLCPlayerViewController: UIViewController {
hideOverlay() hideOverlay()
} else if displayingContentOverlay { } else if displayingContentOverlay {
hideOverlayContent() hideOverlayContent()
showOverlay()
restartOverlayDismissTimer()
} else if viewModel.confirmClose && !displayingConfirmClose { } else if viewModel.confirmClose && !displayingConfirmClose {
showConfirmCloseOverlay() showConfirmCloseOverlay()
@ -387,6 +377,28 @@ extension VLCPlayerViewController {
/// Use case for this is setting new media within the same VLCPlayerViewController /// Use case for this is setting new media within the same VLCPlayerViewController
func setupMediaPlayer(newViewModel: VideoPlayerViewModel) { func setupMediaPlayer(newViewModel: VideoPlayerViewModel) {
// remove old player
if vlcMediaPlayer.media != nil {
viewModelListeners.forEach({ $0.cancel() })
vlcMediaPlayer.stop()
viewModel.sendStopReport()
viewModel.playerOverlayDelegate = nil
}
vlcMediaPlayer = VLCMediaPlayer()
// setup with new player and view model
vlcMediaPlayer = VLCMediaPlayer()
vlcMediaPlayer.delegate = self
vlcMediaPlayer.drawable = videoContentView
// TODO: Custom subtitle sizes
vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 16)
stopOverlayDismissTimer() stopOverlayDismissTimer()
// Stop current media if there is one // Stop current media if there is one
@ -436,6 +448,13 @@ extension VLCPlayerViewController {
func startPlayback() { func startPlayback() {
vlcMediaPlayer.play() vlcMediaPlayer.play()
// Setup external subtitles
for externalSubtitle in viewModel.subtitleStreams.filter({ $0.deliveryMethod == .external }) {
if let deliveryURL = externalSubtitle.externalURL(base: SessionManager.main.currentLogin.server.currentURI) {
vlcMediaPlayer.addPlaybackSlave(deliveryURL, type: .subtitle, enforce: false)
}
}
setMediaPlayerTimeAtCurrentSlider() setMediaPlayerTimeAtCurrentSlider()
viewModel.sendPlayReport() viewModel.sendPlayReport()
@ -672,7 +691,8 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate {
} }
// If needing to fix subtitle streams during playback // If needing to fix subtitle streams during playback
if vlcMediaPlayer.currentVideoSubTitleIndex != viewModel.selectedSubtitleStreamIndex && viewModel.subtitlesEnabled { if vlcMediaPlayer.currentVideoSubTitleIndex != viewModel.selectedSubtitleStreamIndex &&
viewModel.subtitlesEnabled {
didSelectSubtitleStream(index: viewModel.selectedSubtitleStreamIndex) didSelectSubtitleStream(index: viewModel.selectedSubtitleStreamIndex)
} }

View File

@ -47,7 +47,7 @@ struct tvOSVLCOverlay: View {
if let subtitle = viewModel.subtitle { if let subtitle = viewModel.subtitle {
Text(subtitle) Text(subtitle)
.font(.subheadline) .font(.subheadline)
.foregroundColor(.lightGray) .foregroundColor(.white)
} }
Text(viewModel.title) Text(viewModel.title)

View File

@ -527,12 +527,14 @@ public final class TvOSSlider: UIControl {
@objc @objc
private func leftTapWasTriggered() { private func leftTapWasTriggered() {
setValue(value-stepValue, animated: true) // setValue(value-stepValue, animated: true)
viewModel.playerOverlayDelegate?.didSelectBackward()
} }
@objc @objc
private func rightTapWasTriggered() { private func rightTapWasTriggered() {
setValue(value+stepValue, animated: true) // setValue(value+stepValue, animated: true)
viewModel.playerOverlayDelegate?.didSelectForward()
} }
public override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) { public override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {

View File

@ -270,10 +270,9 @@
E11D224227378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */; }; E11D224227378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */; };
E11D224327378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */; }; E11D224327378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */; };
E12186DE2718F1C50010884C /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = E12186DD2718F1C50010884C /* Defaults */; }; E12186DE2718F1C50010884C /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = E12186DD2718F1C50010884C /* Defaults */; };
E1218C9A271A26BA00EA0737 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C99271A26BA00EA0737 /* Nuke */; };
E1218C9C271A26C400EA0737 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9B271A26C400EA0737 /* Nuke */; };
E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9D271A2CD600EA0737 /* CombineExt */; }; E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9D271A2CD600EA0737 /* CombineExt */; };
E1218CA0271A2CF200EA0737 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9F271A2CF200EA0737 /* Nuke */; }; E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */; };
E122A9142788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */; };
E1267D3E271A1F46003C492E /* PreferenceUIHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */; }; E1267D3E271A1F46003C492E /* PreferenceUIHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */; };
E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; };
E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; };
@ -372,6 +371,8 @@
E1AD105C26D9ABDD003E4A08 /* PillHStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105B26D9ABDD003E4A08 /* PillHStackView.swift */; }; E1AD105C26D9ABDD003E4A08 /* PillHStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105B26D9ABDD003E4A08 /* PillHStackView.swift */; };
E1AD105F26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */; }; E1AD105F26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */; };
E1AD106226D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD106126D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift */; }; E1AD106226D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD106126D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift */; };
E1AE8E7C2789135A00FBDDAA /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1AE8E7B2789135A00FBDDAA /* Nuke */; };
E1AE8E7E2789136D00FBDDAA /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1AE8E7D2789136D00FBDDAA /* Nuke */; };
E1B59FD52786ADE500A5287E /* ContinueWatchingCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B59FD42786ADE500A5287E /* ContinueWatchingCard.swift */; }; E1B59FD52786ADE500A5287E /* ContinueWatchingCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B59FD42786ADE500A5287E /* ContinueWatchingCard.swift */; };
E1B59FD92786AE4600A5287E /* NextUpCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B59FD82786AE4600A5287E /* NextUpCard.swift */; }; E1B59FD92786AE4600A5287E /* NextUpCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B59FD82786AE4600A5287E /* NextUpCard.swift */; };
E1B6DCE8271A23780015B715 /* CombineExt in Frameworks */ = {isa = PBXBuildFile; productRef = E1B6DCE7271A23780015B715 /* CombineExt */; }; E1B6DCE8271A23780015B715 /* CombineExt in Frameworks */ = {isa = PBXBuildFile; productRef = E1B6DCE7271A23780015B715 /* CombineExt */; };
@ -403,9 +404,12 @@
E1D4BF8C2719F39F00A11E64 /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF802719D22800A11E64 /* AppAppearance.swift */; }; E1D4BF8C2719F39F00A11E64 /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF802719D22800A11E64 /* AppAppearance.swift */; };
E1D4BF8D2719F3A300A11E64 /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1D4BF8D2719F3A300A11E64 /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; };
E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */; }; E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */; };
E1D7E5A827892566009D0EF7 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1D7E5A727892566009D0EF7 /* Nuke */; };
E1E00A35278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; }; E1E00A35278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; };
E1E00A36278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; }; E1E00A36278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; };
E1E00A37278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; }; E1E00A37278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; };
E1E0F4D8278911680084F701 /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = E1E0F4D7278911680084F701 /* CachedAsyncImage */; };
E1E0F4DA278911A30084F701 /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = E1E0F4D9278911A30084F701 /* CachedAsyncImage */; };
E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */; }; E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */; };
E1E5D5372783A52C00692DFE /* CinematicEpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5362783A52C00692DFE /* CinematicEpisodeItemView.swift */; }; E1E5D5372783A52C00692DFE /* CinematicEpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5362783A52C00692DFE /* CinematicEpisodeItemView.swift */; };
E1E5D5392783A56B00692DFE /* EpisodesRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5382783A56B00692DFE /* EpisodesRowView.swift */; }; E1E5D5392783A56B00692DFE /* EpisodesRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5382783A56B00692DFE /* EpisodesRowView.swift */; };
@ -645,6 +649,7 @@
E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemDto+VideoPlayerViewModel.swift"; sourceTree = "<group>"; }; E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemDto+VideoPlayerViewModel.swift"; sourceTree = "<group>"; };
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIError.swift; sourceTree = "<group>"; }; E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIError.swift; sourceTree = "<group>"; };
E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailCoordinator.swift; sourceTree = "<group>"; }; E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailCoordinator.swift; sourceTree = "<group>"; };
E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaStreamExtension.swift; sourceTree = "<group>"; };
E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingController.swift; sourceTree = "<group>"; }; E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingController.swift; sourceTree = "<group>"; };
E131691626C583BC0074BFEE /* LogConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogConstructor.swift; sourceTree = "<group>"; }; E131691626C583BC0074BFEE /* LogConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogConstructor.swift; sourceTree = "<group>"; };
E1384943278036C70024FB48 /* VLCPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayerViewController.swift; sourceTree = "<group>"; }; E1384943278036C70024FB48 /* VLCPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayerViewController.swift; sourceTree = "<group>"; };
@ -748,13 +753,14 @@
files = ( files = (
53649AAF269CFAF600A2D8B7 /* Puppy in Frameworks */, 53649AAF269CFAF600A2D8B7 /* Puppy in Frameworks */,
E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */, E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */,
E1218CA0271A2CF200EA0737 /* Nuke in Frameworks */,
6220D0C926D63F3700B8E046 /* Stinsen in Frameworks */, 6220D0C926D63F3700B8E046 /* Stinsen in Frameworks */,
535870912669D7A800D05A09 /* Introspect in Frameworks */, 535870912669D7A800D05A09 /* Introspect in Frameworks */,
536D3D84267BEA550004248C /* ParallaxView in Frameworks */, 536D3D84267BEA550004248C /* ParallaxView in Frameworks */,
53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */, 53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */,
E1A9999B271A343C008E78C0 /* SwiftUICollection in Frameworks */, E1A9999B271A343C008E78C0 /* SwiftUICollection in Frameworks */,
E13DD3CD27164CA7009D4DAF /* CoreStore in Frameworks */, E13DD3CD27164CA7009D4DAF /* CoreStore in Frameworks */,
E1E0F4DA278911A30084F701 /* CachedAsyncImage in Frameworks */,
E1AE8E7E2789136D00FBDDAA /* Nuke in Frameworks */,
E178857D278037FD0094FBCF /* JellyfinAPI in Frameworks */, E178857D278037FD0094FBCF /* JellyfinAPI in Frameworks */,
E12186DE2718F1C50010884C /* Defaults in Frameworks */, E12186DE2718F1C50010884C /* Defaults in Frameworks */,
53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */, 53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */,
@ -771,10 +777,11 @@
E10EAA4D277BB716000269ED /* Sliders in Frameworks */, E10EAA4D277BB716000269ED /* Sliders in Frameworks */,
62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */, 62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */,
E1A99999271A3429008E78C0 /* SwiftUICollection in Frameworks */, E1A99999271A3429008E78C0 /* SwiftUICollection in Frameworks */,
E1218C9A271A26BA00EA0737 /* Nuke in Frameworks */,
E1B6DCEA271A23880015B715 /* SwiftyJSON in Frameworks */, E1B6DCEA271A23880015B715 /* SwiftyJSON in Frameworks */,
53352571265EA0A0006CCA86 /* Introspect in Frameworks */, 53352571265EA0A0006CCA86 /* Introspect in Frameworks */,
E13DD3C62716499E009D4DAF /* CoreStore in Frameworks */, E13DD3C62716499E009D4DAF /* CoreStore in Frameworks */,
E1E0F4D8278911680084F701 /* CachedAsyncImage in Frameworks */,
E1AE8E7C2789135A00FBDDAA /* Nuke in Frameworks */,
625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */, 625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */,
E1B6DCE8271A23780015B715 /* CombineExt in Frameworks */, E1B6DCE8271A23780015B715 /* CombineExt in Frameworks */,
E10EAA45277BB646000269ED /* JellyfinAPI in Frameworks */, E10EAA45277BB646000269ED /* JellyfinAPI in Frameworks */,
@ -786,13 +793,13 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
E1D7E5A827892566009D0EF7 /* Nuke in Frameworks */,
628B95242670CABD0091AF3B /* SwiftUI.framework in Frameworks */, 628B95242670CABD0091AF3B /* SwiftUI.framework in Frameworks */,
531ABF6C2671F5CC00C0FE20 /* WidgetKit.framework in Frameworks */, 531ABF6C2671F5CC00C0FE20 /* WidgetKit.framework in Frameworks */,
E13DD3DD27175CE3009D4DAF /* Defaults in Frameworks */, E13DD3DD27175CE3009D4DAF /* Defaults in Frameworks */,
53649AB5269D423A00A2D8B7 /* Puppy in Frameworks */, 53649AB5269D423A00A2D8B7 /* Puppy in Frameworks */,
536D3D7D267BD5F90004248C /* ActivityIndicator in Frameworks */, 536D3D7D267BD5F90004248C /* ActivityIndicator in Frameworks */,
E13DD3CF27164E1F009D4DAF /* CoreStore in Frameworks */, E13DD3CF27164E1F009D4DAF /* CoreStore in Frameworks */,
E1218C9C271A26C400EA0737 /* Nuke in Frameworks */,
E10EAA47277BB670000269ED /* JellyfinAPI in Frameworks */, E10EAA47277BB670000269ED /* JellyfinAPI in Frameworks */,
3B8BA25B211CA261017ABA16 /* Pods_Swiftfin_Widget.framework in Frameworks */, 3B8BA25B211CA261017ABA16 /* Pods_Swiftfin_Widget.framework in Frameworks */,
); );
@ -1502,10 +1509,11 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E18845F426DD631E00B0C5B7 /* BaseItemDto+Stackable.swift */, E18845F426DD631E00B0C5B7 /* BaseItemDto+Stackable.swift */,
E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */,
E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */, E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */,
E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */,
5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */, 5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */,
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */, E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */,
E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */,
E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */, E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */,
); );
path = JellyfinAPIExtensions; path = JellyfinAPIExtensions;
@ -1634,9 +1642,10 @@
E13DD3CC27164CA7009D4DAF /* CoreStore */, E13DD3CC27164CA7009D4DAF /* CoreStore */,
E12186DD2718F1C50010884C /* Defaults */, E12186DD2718F1C50010884C /* Defaults */,
E1218C9D271A2CD600EA0737 /* CombineExt */, E1218C9D271A2CD600EA0737 /* CombineExt */,
E1218C9F271A2CF200EA0737 /* Nuke */,
E1A9999A271A343C008E78C0 /* SwiftUICollection */, E1A9999A271A343C008E78C0 /* SwiftUICollection */,
E178857C278037FD0094FBCF /* JellyfinAPI */, E178857C278037FD0094FBCF /* JellyfinAPI */,
E1E0F4D9278911A30084F701 /* CachedAsyncImage */,
E1AE8E7D2789136D00FBDDAA /* Nuke */,
); );
productName = "JellyfinPlayer tvOS"; productName = "JellyfinPlayer tvOS";
productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */; productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */;
@ -1671,10 +1680,11 @@
E13DD3D227168E65009D4DAF /* Defaults */, E13DD3D227168E65009D4DAF /* Defaults */,
E1B6DCE7271A23780015B715 /* CombineExt */, E1B6DCE7271A23780015B715 /* CombineExt */,
E1B6DCE9271A23880015B715 /* SwiftyJSON */, E1B6DCE9271A23880015B715 /* SwiftyJSON */,
E1218C99271A26BA00EA0737 /* Nuke */,
E1A99998271A3429008E78C0 /* SwiftUICollection */, E1A99998271A3429008E78C0 /* SwiftUICollection */,
E10EAA44277BB646000269ED /* JellyfinAPI */, E10EAA44277BB646000269ED /* JellyfinAPI */,
E10EAA4C277BB716000269ED /* Sliders */, E10EAA4C277BB716000269ED /* Sliders */,
E1E0F4D7278911680084F701 /* CachedAsyncImage */,
E1AE8E7B2789135A00FBDDAA /* Nuke */,
); );
productName = JellyfinPlayer; productName = JellyfinPlayer;
productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */; productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */;
@ -1699,8 +1709,8 @@
53649AB4269D423A00A2D8B7 /* Puppy */, 53649AB4269D423A00A2D8B7 /* Puppy */,
E13DD3CE27164E1F009D4DAF /* CoreStore */, E13DD3CE27164E1F009D4DAF /* CoreStore */,
E13DD3DC27175CE3009D4DAF /* Defaults */, E13DD3DC27175CE3009D4DAF /* Defaults */,
E1218C9B271A26C400EA0737 /* Nuke */,
E10EAA46277BB670000269ED /* JellyfinAPI */, E10EAA46277BB670000269ED /* JellyfinAPI */,
E1D7E5A727892566009D0EF7 /* Nuke */,
); );
productName = WidgetExtensionExtension; productName = WidgetExtensionExtension;
productReference = 628B95202670CABD0091AF3B /* Swiftfin Widget.appex */; productReference = 628B95202670CABD0091AF3B /* Swiftfin Widget.appex */;
@ -1759,14 +1769,15 @@
536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */, 536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */,
53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */, 53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */,
62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */, 62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */,
E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */, E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */,
E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */, E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */,
E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */, E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */,
E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
E1218C98271A26BA00EA0737 /* XCRemoteSwiftPackageReference "Nuke" */,
C4BFD4E327167B63007739E3 /* XCRemoteSwiftPackageReference "SwiftUICollection" */, C4BFD4E327167B63007739E3 /* XCRemoteSwiftPackageReference "SwiftUICollection" */,
E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */, E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */,
E10EAA4B277BB716000269ED /* XCRemoteSwiftPackageReference "swiftui-sliders" */, E10EAA4B277BB716000269ED /* XCRemoteSwiftPackageReference "swiftui-sliders" */,
E1E0F4D6278911680084F701 /* XCRemoteSwiftPackageReference "SwiftUI-CachedAsyncImage" */,
E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */,
); );
productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; productRefGroup = 5377CBF2263B596A003A4E83 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -2051,6 +2062,7 @@
E1C812CC277AE40A00918266 /* VideoPlayerView.swift in Sources */, E1C812CC277AE40A00918266 /* VideoPlayerView.swift in Sources */,
53CD2A40268A49C2002ABD4E /* ItemView.swift in Sources */, 53CD2A40268A49C2002ABD4E /* ItemView.swift in Sources */,
53CD2A42268A4B38002ABD4E /* MovieItemView.swift in Sources */, 53CD2A42268A4B38002ABD4E /* MovieItemView.swift in Sources */,
E122A9142788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */,
E178859E2780F53B0094FBCF /* SliderView.swift in Sources */, E178859E2780F53B0094FBCF /* SliderView.swift in Sources */,
536D3D7F267BDF100004248C /* LatestMediaView.swift in Sources */, 536D3D7F267BDF100004248C /* LatestMediaView.swift in Sources */,
E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */, E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */,
@ -2191,6 +2203,7 @@
E13DD3F227179378009D4DAF /* UserSignInCoordinator.swift in Sources */, E13DD3F227179378009D4DAF /* UserSignInCoordinator.swift in Sources */,
621338932660107500A81A2A /* StringExtensions.swift in Sources */, 621338932660107500A81A2A /* StringExtensions.swift in Sources */,
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */, 53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */,
E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */,
E1C812C5277A90B200918266 /* URLComponentsExtensions.swift in Sources */, E1C812C5277A90B200918266 /* URLComponentsExtensions.swift in Sources */,
62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */, 62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */,
625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */, 625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */,
@ -2920,14 +2933,6 @@
kind = branch; kind = branch;
}; };
}; };
E1218C98271A26BA00EA0737 /* XCRemoteSwiftPackageReference "Nuke" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kean/Nuke";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 9.0.0;
};
};
E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */ = { E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/CombineCommunity/CombineExt"; repositoryURL = "https://github.com/CombineCommunity/CombineExt";
@ -2936,7 +2941,7 @@
minimumVersion = 1.0.0; minimumVersion = 1.0.0;
}; };
}; };
E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */ = { E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/JohnEstropia/CoreStore.git"; repositoryURL = "https://github.com/JohnEstropia/CoreStore.git";
requirement = { requirement = {
@ -2952,6 +2957,14 @@
minimumVersion = 6.0.0; minimumVersion = 6.0.0;
}; };
}; };
E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kean/Nuke";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 9.6.0;
};
};
E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON"; repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON";
@ -2960,6 +2973,14 @@
minimumVersion = 5.0.0; minimumVersion = 5.0.0;
}; };
}; };
E1E0F4D6278911680084F701 /* XCRemoteSwiftPackageReference "SwiftUI-CachedAsyncImage" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/lorenzofiamingo/SwiftUI-CachedAsyncImage";
requirement = {
branch = main;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
@ -3038,39 +3059,24 @@
package = E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */; package = E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */;
productName = Defaults; productName = Defaults;
}; };
E1218C99271A26BA00EA0737 /* Nuke */ = {
isa = XCSwiftPackageProductDependency;
package = E1218C98271A26BA00EA0737 /* XCRemoteSwiftPackageReference "Nuke" */;
productName = Nuke;
};
E1218C9B271A26C400EA0737 /* Nuke */ = {
isa = XCSwiftPackageProductDependency;
package = E1218C98271A26BA00EA0737 /* XCRemoteSwiftPackageReference "Nuke" */;
productName = Nuke;
};
E1218C9D271A2CD600EA0737 /* CombineExt */ = { E1218C9D271A2CD600EA0737 /* CombineExt */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */; package = E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */;
productName = CombineExt; productName = CombineExt;
}; };
E1218C9F271A2CF200EA0737 /* Nuke */ = {
isa = XCSwiftPackageProductDependency;
package = E1218C98271A26BA00EA0737 /* XCRemoteSwiftPackageReference "Nuke" */;
productName = Nuke;
};
E13DD3C52716499E009D4DAF /* CoreStore */ = { E13DD3C52716499E009D4DAF /* CoreStore */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */; package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */;
productName = CoreStore; productName = CoreStore;
}; };
E13DD3CC27164CA7009D4DAF /* CoreStore */ = { E13DD3CC27164CA7009D4DAF /* CoreStore */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */; package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */;
productName = CoreStore; productName = CoreStore;
}; };
E13DD3CE27164E1F009D4DAF /* CoreStore */ = { E13DD3CE27164E1F009D4DAF /* CoreStore */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */; package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */;
productName = CoreStore; productName = CoreStore;
}; };
E13DD3D227168E65009D4DAF /* Defaults */ = { E13DD3D227168E65009D4DAF /* Defaults */ = {
@ -3098,6 +3104,16 @@
package = C4BFD4E327167B63007739E3 /* XCRemoteSwiftPackageReference "SwiftUICollection" */; package = C4BFD4E327167B63007739E3 /* XCRemoteSwiftPackageReference "SwiftUICollection" */;
productName = SwiftUICollection; productName = SwiftUICollection;
}; };
E1AE8E7B2789135A00FBDDAA /* Nuke */ = {
isa = XCSwiftPackageProductDependency;
package = E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */;
productName = Nuke;
};
E1AE8E7D2789136D00FBDDAA /* Nuke */ = {
isa = XCSwiftPackageProductDependency;
package = E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */;
productName = Nuke;
};
E1B6DCE7271A23780015B715 /* CombineExt */ = { E1B6DCE7271A23780015B715 /* CombineExt */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */; package = E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */;
@ -3108,6 +3124,21 @@
package = E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */; package = E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */;
productName = SwiftyJSON; productName = SwiftyJSON;
}; };
E1D7E5A727892566009D0EF7 /* Nuke */ = {
isa = XCSwiftPackageProductDependency;
package = E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */;
productName = Nuke;
};
E1E0F4D7278911680084F701 /* CachedAsyncImage */ = {
isa = XCSwiftPackageProductDependency;
package = E1E0F4D6278911680084F701 /* XCRemoteSwiftPackageReference "SwiftUI-CachedAsyncImage" */;
productName = CachedAsyncImage;
};
E1E0F4D9278911A30084F701 /* CachedAsyncImage */ = {
isa = XCSwiftPackageProductDependency;
package = E1E0F4D6278911680084F701 /* XCRemoteSwiftPackageReference "SwiftUI-CachedAsyncImage" */;
productName = CachedAsyncImage;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
}; };
rootObject = 5377CBE9263B596A003A4E83 /* Project object */; rootObject = 5377CBE9263B596A003A4E83 /* Project object */;

View File

@ -100,6 +100,15 @@
"version": "1.4.2" "version": "1.4.2"
} }
}, },
{
"package": "CachedAsyncImage",
"repositoryURL": "https://github.com/lorenzofiamingo/SwiftUI-CachedAsyncImage",
"state": {
"branch": "main",
"revision": "eb489a699be1f6e6c1a19fecdd6bfdc556474fd6",
"version": null
}
},
{ {
"package": "Introspect", "package": "Introspect",
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect", "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect",

View File

@ -63,19 +63,20 @@ struct SettingsView: View {
} }
} }
Section(header: Text("Networking")) { // TODO: Implement these for playback
Picker("Default local quality", selection: $inNetworkStreamBitrate) { // Section(header: Text("Networking")) {
ForEach(self.viewModel.bitrates, id: \.self) { bitrate in // Picker("Default local quality", selection: $inNetworkStreamBitrate) {
Text(bitrate.name).tag(bitrate.value) // ForEach(self.viewModel.bitrates, id: \.self) { bitrate in
} // Text(bitrate.name).tag(bitrate.value)
} // }
// }
Picker("Default remote quality", selection: $outOfNetworkStreamBitrate) { //
ForEach(self.viewModel.bitrates, id: \.self) { bitrate in // Picker("Default remote quality", selection: $outOfNetworkStreamBitrate) {
Text(bitrate.name).tag(bitrate.value) // ForEach(self.viewModel.bitrates, id: \.self) { bitrate in
} // Text(bitrate.name).tag(bitrate.value)
} // }
} // }
// }
Section(header: Text("Video Player")) { Section(header: Text("Video Player")) {
Picker("Jump Forward Length", selection: $jumpForwardLength) { Picker("Jump Forward Length", selection: $jumpForwardLength) {

View File

@ -24,7 +24,7 @@ class VLCPlayerViewController: UIViewController {
// MARK: variables // MARK: variables
private var viewModel: VideoPlayerViewModel private var viewModel: VideoPlayerViewModel
private var vlcMediaPlayer = VLCMediaPlayer() private var vlcMediaPlayer: VLCMediaPlayer
private var lastPlayerTicks: Int64 = 0 private var lastPlayerTicks: Int64 = 0
private var lastProgressReportTicks: Int64 = 0 private var lastProgressReportTicks: Int64 = 0
private var viewModelListeners = Set<AnyCancellable>() private var viewModelListeners = Set<AnyCancellable>()
@ -49,6 +49,7 @@ class VLCPlayerViewController: UIViewController {
init(viewModel: VideoPlayerViewModel) { init(viewModel: VideoPlayerViewModel) {
self.viewModel = viewModel self.viewModel = viewModel
self.vlcMediaPlayer = VLCMediaPlayer()
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
@ -97,14 +98,6 @@ class VLCPlayerViewController: UIViewController {
view.backgroundColor = .black view.backgroundColor = .black
// These are kept outside of 'setupMediaPlayer' such that
// they aren't unnecessarily set more than once
vlcMediaPlayer.delegate = self
vlcMediaPlayer.drawable = videoContentView
// TODO: Custom subtitle sizes
vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14)
setupMediaPlayer(newViewModel: viewModel) setupMediaPlayer(newViewModel: viewModel)
refreshJumpBackwardOverlayView(with: viewModel.jumpBackwardLength) refreshJumpBackwardOverlayView(with: viewModel.jumpBackwardLength)
@ -287,9 +280,8 @@ extension VLCPlayerViewController {
/// Use case for this is setting new media within the same VLCPlayerViewController /// Use case for this is setting new media within the same VLCPlayerViewController
func setupMediaPlayer(newViewModel: VideoPlayerViewModel) { func setupMediaPlayer(newViewModel: VideoPlayerViewModel) {
stopOverlayDismissTimer() // remove old player
// Stop current media if there is one
if vlcMediaPlayer.media != nil { if vlcMediaPlayer.media != nil {
viewModelListeners.forEach({ $0.cancel() }) viewModelListeners.forEach({ $0.cancel() })
@ -298,6 +290,20 @@ extension VLCPlayerViewController {
viewModel.playerOverlayDelegate = nil viewModel.playerOverlayDelegate = nil
} }
vlcMediaPlayer = VLCMediaPlayer()
// setup with new player and view model
vlcMediaPlayer = VLCMediaPlayer()
vlcMediaPlayer.delegate = self
vlcMediaPlayer.drawable = videoContentView
// TODO: Custom subtitle sizes
vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14)
stopOverlayDismissTimer()
lastPlayerTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0 lastPlayerTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0
lastProgressReportTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0 lastProgressReportTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0
@ -334,6 +340,13 @@ extension VLCPlayerViewController {
func startPlayback() { func startPlayback() {
vlcMediaPlayer.play() vlcMediaPlayer.play()
// Setup external subtitles
for externalSubtitle in viewModel.subtitleStreams.filter({ $0.deliveryMethod == .external }) {
if let deliveryURL = externalSubtitle.externalURL(base: SessionManager.main.currentLogin.server.currentURI) {
vlcMediaPlayer.addPlaybackSlave(deliveryURL, type: .subtitle, enforce: false)
}
}
setMediaPlayerTimeAtCurrentSlider() setMediaPlayerTimeAtCurrentSlider()
viewModel.sendPlayReport() viewModel.sendPlayReport()
@ -526,7 +539,8 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate {
} }
// If needing to fix subtitle streams during playback // If needing to fix subtitle streams during playback
if vlcMediaPlayer.currentVideoSubTitleIndex != viewModel.selectedSubtitleStreamIndex && viewModel.subtitlesEnabled { if vlcMediaPlayer.currentVideoSubTitleIndex != viewModel.selectedSubtitleStreamIndex &&
viewModel.subtitlesEnabled {
didSelectSubtitleStream(index: viewModel.selectedSubtitleStreamIndex) didSelectSubtitleStream(index: viewModel.selectedSubtitleStreamIndex)
} }