basic movie item view

This commit is contained in:
Aiden Vigue 2021-06-28 16:43:13 -04:00
parent 2919b25a5d
commit 897d158707
No known key found for this signature in database
GPG Key ID: B9A09843AB079D5B
19 changed files with 431 additions and 57 deletions

View File

@ -24,6 +24,38 @@ struct PortraitItemElement: View {
.cornerRadius(10) .cornerRadius(10)
.shadow(radius: focused ? 10.0 : 0) .shadow(radius: focused ? 10.0 : 0)
.shadow(radius: focused ? 10.0 : 0) .shadow(radius: focused ? 10.0 : 0)
.overlay(
ZStack {
if item.userData?.isFavorite ?? false {
Image(systemName: "circle.fill")
.foregroundColor(.white)
.opacity(0.6)
Image(systemName: "heart.fill")
.foregroundColor(Color(.systemRed))
.font(.system(size: 10))
}
}
.padding(2)
.opacity(1)
, alignment: .bottomLeading)
.overlay(
ZStack {
if item.userData?.played ?? false {
Image(systemName: "circle.fill")
.foregroundColor(.white)
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color(.systemBlue))
} else {
if(item.userData?.unplayedItemCount != nil) {
Image(systemName: "circle.fill")
.foregroundColor(Color(.systemBlue))
Text(String(item.userData!.unplayedItemCount ?? 0))
.foregroundColor(.white)
.font(.caption2)
}
}
}.padding(2)
.opacity(1), alignment: .topTrailing).opacity(1)
} }
.onChange(of: envFocused) { envFocus in .onChange(of: envFocused) { envFocus in
withAnimation(.linear(duration: 0.15)) { withAnimation(.linear(duration: 0.15)) {

View File

@ -25,7 +25,7 @@ struct ContinueWatchingView: View {
LazyHStack { LazyHStack {
Spacer().frame(width: 45) Spacer().frame(width: 45)
ForEach(items, id: \.id) { item in ForEach(items, id: \.id) { item in
NavigationLink(destination: VideoPlayerView(item: item)) { NavigationLink(destination: LazyView { ItemView(item: item) }) {
LandscapeItemElement(item: item) LandscapeItemElement(item: item)
} }
.buttonStyle(PlainNavigationLinkButtonStyle()) .buttonStyle(PlainNavigationLinkButtonStyle())

View File

@ -33,7 +33,9 @@ struct HomeView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
let library = viewModel.libraries.first(where: { $0.id == libraryID }) let library = viewModel.libraries.first(where: { $0.id == libraryID })
NavigationLink(destination: Text("library_latest")) { NavigationLink(destination: LazyView {
LibraryView(viewModel: .init(parentID: libraryID, filters: viewModel.recentFilterSet), title: library?.name ?? "")
}) {
HStack { HStack {
Text("Latest \(library?.name ?? "")") Text("Latest \(library?.name ?? "")")
.font(.headline) .font(.headline)
@ -45,6 +47,7 @@ struct HomeView: View {
} }
} }
} }
Spacer().frame(height: 30)
} }
} }
} }

View File

@ -0,0 +1,47 @@
/* JellyfinPlayer/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 SwiftUI
import Introspect
import JellyfinAPI
class VideoPlayerItem: ObservableObject {
@Published var shouldShowPlayer: Bool = false
@Published var itemToPlay: BaseItemDto = BaseItemDto()
}
struct ItemView: View {
private var item: BaseItemDto
@StateObject private var videoPlayerItem: VideoPlayerItem = VideoPlayerItem()
@State private var videoIsLoading: Bool = false; // This variable is only changed by the underlying VLC view.
@State private var isLoading: Bool = false
@State private var viewDidLoad: Bool = false
init(item: BaseItemDto) {
self.item = item
}
var body: some View {
ZStack {
NavigationLink(destination: VideoPlayerView(item: videoPlayerItem.itemToPlay), isActive: $videoPlayerItem.shouldShowPlayer) {
EmptyView()
}
.buttonStyle(PlainNavigationLinkButtonStyle())
.focusable(false)
Group {
if item.type == "Movie" {
MovieItemView(item: item)
} else {
Text("Type: \(item.type ?? "") not implemented yet :(")
}
}
.environmentObject(videoPlayerItem)
}
}
}

View File

@ -7,7 +7,6 @@
import SwiftUI import SwiftUI
import UIKit import UIKit
@main @main
struct JellyfinPlayer_tvOSApp: App { struct JellyfinPlayer_tvOSApp: App {
let persistenceController = PersistenceController.shared let persistenceController = PersistenceController.shared

View File

@ -42,7 +42,7 @@ struct LatestMediaView: View {
LazyHStack { LazyHStack {
Spacer().frame(width: 45) Spacer().frame(width: 45)
ForEach(items, id: \.id) { item in ForEach(items, id: \.id) { item in
NavigationLink(destination: Text("itemv")) { NavigationLink(destination: LazyView { ItemView(item: item) }) {
PortraitItemElement(item: item) PortraitItemElement(item: item)
}.buttonStyle(PlainNavigationLinkButtonStyle()) }.buttonStyle(PlainNavigationLinkButtonStyle())
} }

View File

@ -0,0 +1,84 @@
/*
* JellyfinPlayer/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 SwiftUI
struct LibraryView: View {
@StateObject var viewModel: LibraryViewModel
var title: String
// MARK: tracks for grid
var defaultFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], tags: [], sortBy: [.name])
@State var isShowingSearchView = false
@State var isShowingFilterView = false
@State private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 250)
var body: some View {
Group {
if viewModel.isLoading == true {
ProgressView()
} else if !viewModel.items.isEmpty {
ScrollView(.vertical) {
Spacer().frame(height: 16)
LazyVGrid(columns: tracks) {
ForEach(viewModel.items, id: \.id) { item in
if(item.type != "Folder") {
NavigationLink(destination: LazyView { ItemView(item: item) }) {
PortraitItemElement(item: item)
}.buttonStyle(PlainNavigationLinkButtonStyle())
.onAppear() {
if item == viewModel.items.last && viewModel.hasNextPage {
print("Last item visible, load more items.")
viewModel.requestNextPageAsync()
}
}
}
}
}
Spacer().frame(height: 16)
}
} else {
Text("No results.")
}
}
.navigationBarTitle(title)
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
if viewModel.hasPreviousPage {
Button {
viewModel.requestPreviousPage()
} label: {
Image(systemName: "chevron.left")
}.disabled(viewModel.isLoading)
}
if viewModel.hasNextPage {
Button {
viewModel.requestNextPage()
} label: {
Image(systemName: "chevron.right")
}.disabled(viewModel.isLoading)
}
}
}/*
.sheet(isPresented: $isShowingFilterView) {
LibraryFilterView(filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType, parentId: viewModel.parentID ?? "")
}
.background(
NavigationLink(destination: LibrarySearchView(viewModel: .init(parentID: viewModel.parentID)),
isActive: $isShowingSearchView) {
EmptyView()
}
)
*/
}
}
// stream BM^S by nicki!
//

View File

@ -0,0 +1,180 @@
//
/*
* 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 SwiftUI
import JellyfinAPI
struct MovieItemView: View {
let item: BaseItemDto
@EnvironmentObject private var playbackInfo: VideoPlayerItem
@State var actors: [BaseItemPerson] = [];
@State var studio: String? = nil;
@State var director: String? = nil;
@Namespace private var namespace
func onAppear() {
actors = []
director = nil
studio = nil
var actor_index = 0;
item.people?.forEach { person in
if(person.type == "Actor") {
if(actor_index < 8) {
actors.append(person)
}
actor_index = actor_index + 1;
}
if(person.type == "Director") {
director = person.name ?? ""
}
}
}
var body: some View {
ZStack {
ImageView(src: item.getBackdropImage(maxWidth: 1920), bh: item.getBackdropImageBlurHash())
.opacity(0.4)
ScrollView {
LazyVStack {
HStack {
VStack(alignment: .leading) {
Text(item.name ?? "")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.primary)
HStack {
if item.productionYear != nil {
Text(String(item.productionYear!)).font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.secondary)
.lineLimit(1)
}
Text(item.getItemRuntime()).font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.secondary)
.lineLimit(1)
if item.officialRating != nil {
Text(item.officialRating!).font(.subheadline)
.fontWeight(.semibold)
.foregroundColor(.secondary)
.lineLimit(1)
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
.overlay(RoundedRectangle(cornerRadius: 2)
.stroke(Color.secondary, lineWidth: 1))
}
}
HStack {
VStack(alignment: .trailing) {
if(studio != nil) {
Text("STUDIO")
.font(.body)
.fontWeight(.semibold)
.foregroundColor(.primary)
Text(studio!)
.font(.body)
.fontWeight(.semibold)
.foregroundColor(.secondary)
.padding(.bottom, 40)
}
if(director != nil) {
Text("DIRECTOR")
.font(.body)
.fontWeight(.semibold)
.foregroundColor(.primary)
Text(director!)
.font(.body)
.fontWeight(.semibold)
.foregroundColor(.secondary)
.padding(.bottom, 40)
}
if(!actors.isEmpty) {
Text("CAST")
.font(.body)
.fontWeight(.semibold)
.foregroundColor(.primary)
ForEach(actors, id: \.id) { person in
Text(person.name!)
.font(.body)
.fontWeight(.semibold)
.foregroundColor(.secondary)
}
}
Spacer()
}
VStack(alignment: .leading) {
Text(item.taglines?.first ?? "")
.font(.body)
.italic()
.fontWeight(.medium)
.foregroundColor(.primary)
Text(item.overview ?? "")
.font(.body)
.fontWeight(.medium)
.foregroundColor(.primary)
HStack {
VStack {
Button {
playbackInfo.shouldShowPlayer = true
} label: {
Image(systemName: "heart.fill")
.font(.system(size: 40))
.padding(.vertical, 12).padding(.horizontal, 20)
}
Text("Favorite")
.font(.caption)
}
VStack {
Button {
playbackInfo.itemToPlay = item
playbackInfo.shouldShowPlayer = true
} label: {
Image(systemName: "play.fill")
.font(.system(size: 40))
.padding(.vertical, 12).padding(.horizontal, 20)
}.prefersDefaultFocus(in: namespace)
Text("Play")
.font(.caption)
}
VStack {
Button {
playbackInfo.shouldShowPlayer = true
} label: {
Image(systemName: "eye.fill")
.font(.system(size: 40))
.padding(.vertical, 12).padding(.horizontal, 20)
}
Text("Mark Watched")
.font(.caption)
}
}.padding(.top, 15)
Spacer()
}
}.padding(.top, 50)
}
VStack {
ImageView(src: item.getPrimaryImage(maxWidth: 450), bh: item.getPrimaryImageBlurHash())
.frame(width: 450, height: 675)
.cornerRadius(10)
Spacer()
}
}
}.padding(EdgeInsets(top: 90, leading: 90, bottom: 0, trailing: 90))
}
}.onAppear(perform: onAppear)
.focusScope(namespace)
}
}

View File

@ -24,7 +24,7 @@ struct NextUpView: View {
LazyHStack { LazyHStack {
Spacer().frame(width: 45) Spacer().frame(width: 45)
ForEach(items, id: \.id) { item in ForEach(items, id: \.id) { item in
NavigationLink(destination: VideoPlayerView(item: item)) { NavigationLink(destination: LazyView { ItemView(item: item) }) {
LandscapeItemElement(item: item) LandscapeItemElement(item: item)
}.buttonStyle(PlainNavigationLinkButtonStyle()) }.buttonStyle(PlainNavigationLinkButtonStyle())
} }

View File

@ -15,7 +15,7 @@ struct VideoPlayerView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController { func makeUIViewController(context: Context) -> some UIViewController {
let storyboard = UIStoryboard(name: "VideoPlayerStoryboard", bundle: nil) let storyboard = UIStoryboard(name: "VideoPlayer", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "VideoPlayer") as! VideoPlayerViewController let viewController = storyboard.instantiateViewController(withIdentifier: "VideoPlayer") as! VideoPlayerViewController
viewController.manifest = item viewController.manifest = item

View File

@ -244,17 +244,12 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
// Pause and load captions into memory. // Pause and load captions into memory.
mediaPlayer.pause() mediaPlayer.pause()
var shouldHaveSubtitleTracks = 0
subtitleTrackArray.forEach { sub in subtitleTrackArray.forEach { sub in
if sub.id != -1 && sub.delivery == .external && sub.codec != "subrip" { if sub.id != -1 && sub.delivery == .external {
shouldHaveSubtitleTracks = shouldHaveSubtitleTracks + 1
mediaPlayer.addPlaybackSlave(sub.url!, type: .subtitle, enforce: false) mediaPlayer.addPlaybackSlave(sub.url!, type: .subtitle, enforce: false)
} }
} }
// Wait for captions to load
while mediaPlayer.numberOfSubtitlesTracks != shouldHaveSubtitleTracks {}
// Select default track & resume playback // Select default track & resume playback
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack
mediaPlayer.pause() mediaPlayer.pause()
@ -714,18 +709,8 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
// Move time along transport bar // Move time along transport bar
func mediaPlayerTimeChanged(_ aNotification: Notification!) { func mediaPlayerTimeChanged(_ aNotification: Notification!) {
if loading {
loading = false
DispatchQueue.main.async { [self] in
activityIndicator.isHidden = true
activityIndicator.stopAnimating()
}
updateNowPlayingCenter(time: nil, playing: true)
}
let time = mediaPlayer.position let time = mediaPlayer.position
if time != lastTime { if abs(time-lastTime) > 0.00005 {
self.currentTimeLabel.text = formatSecondsToHMS(Double(mediaPlayer.time.intValue/1000)) self.currentTimeLabel.text = formatSecondsToHMS(Double(mediaPlayer.time.intValue/1000))
self.remainingTimeLabel.text = "-" + formatSecondsToHMS(Double(abs(mediaPlayer.remainingTime.intValue/1000))) self.remainingTimeLabel.text = "-" + formatSecondsToHMS(Double(abs(mediaPlayer.remainingTime.intValue/1000)))
@ -749,15 +734,23 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
controlsAppearTime = 999_999_999_999_999 controlsAppearTime = 999_999_999_999_999
} }
} }
lastTime = time
} }
lastTime = time
if CACurrentMediaTime() - lastProgressReportTime > 5 { if CACurrentMediaTime() - lastProgressReportTime > 5 {
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack
sendProgressReport(eventName: "timeupdate") sendProgressReport(eventName: "timeupdate")
lastProgressReportTime = CACurrentMediaTime() lastProgressReportTime = CACurrentMediaTime()
} }
if loading {
loading = false
DispatchQueue.main.async { [self] in
activityIndicator.isHidden = true
activityIndicator.stopAnimating()
}
updateNowPlayingCenter(time: nil, playing: true)
}
} }
// MARK: Settings Delegate // MARK: Settings Delegate

View File

@ -21,7 +21,7 @@
5310695A2684E7EE00CFFDBA /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069532684E7EE00CFFDBA /* VideoPlayer.swift */; }; 5310695A2684E7EE00CFFDBA /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069532684E7EE00CFFDBA /* VideoPlayer.swift */; };
5310695B2684E7EE00CFFDBA /* AudioView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069542684E7EE00CFFDBA /* AudioView.swift */; }; 5310695B2684E7EE00CFFDBA /* AudioView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069542684E7EE00CFFDBA /* AudioView.swift */; };
5310695C2684E7EE00CFFDBA /* VideoPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069552684E7EE00CFFDBA /* VideoPlayerViewController.swift */; }; 5310695C2684E7EE00CFFDBA /* VideoPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069552684E7EE00CFFDBA /* VideoPlayerViewController.swift */; };
5310695D2684E7EE00CFFDBA /* VideoPlayerStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 531069562684E7EE00CFFDBA /* VideoPlayerStoryboard.storyboard */; }; 5310695D2684E7EE00CFFDBA /* VideoPlayer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 531069562684E7EE00CFFDBA /* VideoPlayer.storyboard */; };
531690E5267ABD5C005D8AB9 /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E4267ABD5C005D8AB9 /* MainTabView.swift */; }; 531690E5267ABD5C005D8AB9 /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E4267ABD5C005D8AB9 /* MainTabView.swift */; };
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E6267ABD79005D8AB9 /* HomeView.swift */; }; 531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E6267ABD79005D8AB9 /* HomeView.swift */; };
531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */; }; 531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */; };
@ -80,6 +80,7 @@
53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A089CF264DA9DA00D57806 /* MovieItemView.swift */; }; 53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A089CF264DA9DA00D57806 /* MovieItemView.swift */; };
53A431BD266B0FF20016769F /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 53A431BC266B0FF20016769F /* JellyfinAPI */; }; 53A431BD266B0FF20016769F /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 53A431BC266B0FF20016769F /* JellyfinAPI */; };
53A431BF266B0FFE0016769F /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 53A431BE266B0FFE0016769F /* JellyfinAPI */; }; 53A431BF266B0FFE0016769F /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 53A431BE266B0FFE0016769F /* JellyfinAPI */; };
53A83C33268A309300DF3D92 /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A83C32268A309300DF3D92 /* LibraryView.swift */; };
53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53ABFDDB267972BF00886593 /* TVServices.framework */; }; 53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53ABFDDB267972BF00886593 /* TVServices.framework */; };
53ABFDDE267974E300886593 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ABFDDD267974E300886593 /* SplashView.swift */; }; 53ABFDDE267974E300886593 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ABFDDD267974E300886593 /* SplashView.swift */; };
53ABFDE4267974EF00886593 /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5742678C33500530A6E /* LibraryListViewModel.swift */; }; 53ABFDE4267974EF00886593 /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5742678C33500530A6E /* LibraryListViewModel.swift */; };
@ -93,6 +94,8 @@
53ABFDEE26799DCD00886593 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531AC8BE26750DE20091C7EB /* ImageView.swift */; }; 53ABFDEE26799DCD00886593 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531AC8BE26750DE20091C7EB /* ImageView.swift */; };
53AD124D267029D60094A276 /* SeriesItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA526572F0700E7EA70 /* SeriesItemView.swift */; }; 53AD124D267029D60094A276 /* SeriesItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA526572F0700E7EA70 /* SeriesItemView.swift */; };
53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA326572C1300E7EA70 /* SeasonItemView.swift */; }; 53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA326572C1300E7EA70 /* SeasonItemView.swift */; };
53CD2A40268A49C2002ABD4E /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53CD2A3F268A49C2002ABD4E /* ItemView.swift */; };
53CD2A42268A4B38002ABD4E /* MovieItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53CD2A41268A4B38002ABD4E /* MovieItemView.swift */; };
53DE4BD02670961400739748 /* EpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA72657424A00E7EA70 /* EpisodeItemView.swift */; }; 53DE4BD02670961400739748 /* EpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA72657424A00E7EA70 /* EpisodeItemView.swift */; };
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DE4BD1267098F300739748 /* SearchBarView.swift */; }; 53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DE4BD1267098F300739748 /* SearchBarView.swift */; };
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DF641D263D9C0600A7CD1A /* LibraryView.swift */; }; 53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DF641D263D9C0600A7CD1A /* LibraryView.swift */; };
@ -214,7 +217,7 @@
531069532684E7EE00CFFDBA /* VideoPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = "<group>"; }; 531069532684E7EE00CFFDBA /* VideoPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = "<group>"; };
531069542684E7EE00CFFDBA /* AudioView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioView.swift; sourceTree = "<group>"; }; 531069542684E7EE00CFFDBA /* AudioView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioView.swift; sourceTree = "<group>"; };
531069552684E7EE00CFFDBA /* VideoPlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewController.swift; sourceTree = "<group>"; }; 531069552684E7EE00CFFDBA /* VideoPlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewController.swift; sourceTree = "<group>"; };
531069562684E7EE00CFFDBA /* VideoPlayerStoryboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = VideoPlayerStoryboard.storyboard; sourceTree = "<group>"; }; 531069562684E7EE00CFFDBA /* VideoPlayer.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = VideoPlayer.storyboard; sourceTree = "<group>"; };
531690E4267ABD5C005D8AB9 /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = "<group>"; }; 531690E4267ABD5C005D8AB9 /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = "<group>"; };
531690E6267ABD79005D8AB9 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; }; 531690E6267ABD79005D8AB9 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingView.swift; sourceTree = "<group>"; }; 531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingView.swift; sourceTree = "<group>"; };
@ -276,11 +279,14 @@
53987CA72657424A00E7EA70 /* EpisodeItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeItemView.swift; sourceTree = "<group>"; }; 53987CA72657424A00E7EA70 /* EpisodeItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeItemView.swift; sourceTree = "<group>"; };
539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; }; 539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
53A089CF264DA9DA00D57806 /* MovieItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieItemView.swift; sourceTree = "<group>"; }; 53A089CF264DA9DA00D57806 /* MovieItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieItemView.swift; sourceTree = "<group>"; };
53A83C32268A309300DF3D92 /* LibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = "<group>"; };
53ABFDDA267972BF00886593 /* JellyfinPlayer tvOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "JellyfinPlayer tvOS.entitlements"; sourceTree = "<group>"; }; 53ABFDDA267972BF00886593 /* JellyfinPlayer tvOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "JellyfinPlayer tvOS.entitlements"; sourceTree = "<group>"; };
53ABFDDB267972BF00886593 /* TVServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TVServices.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS15.0.sdk/System/Library/Frameworks/TVServices.framework; sourceTree = DEVELOPER_DIR; }; 53ABFDDB267972BF00886593 /* TVServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TVServices.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS15.0.sdk/System/Library/Frameworks/TVServices.framework; sourceTree = DEVELOPER_DIR; };
53ABFDDD267974E300886593 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = "<group>"; }; 53ABFDDD267974E300886593 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = "<group>"; };
53ABFDEA2679753200886593 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; }; 53ABFDEA2679753200886593 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; };
53AD124C2670278D0094A276 /* JellyfinPlayer.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = JellyfinPlayer.entitlements; sourceTree = "<group>"; }; 53AD124C2670278D0094A276 /* JellyfinPlayer.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = JellyfinPlayer.entitlements; sourceTree = "<group>"; };
53CD2A3F268A49C2002ABD4E /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; };
53CD2A41268A4B38002ABD4E /* MovieItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieItemView.swift; sourceTree = "<group>"; };
53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MobileVLCKit.xcframework; path = Carthage/Build/MobileVLCKit.xcframework; sourceTree = "<group>"; }; 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MobileVLCKit.xcframework; path = Carthage/Build/MobileVLCKit.xcframework; sourceTree = "<group>"; };
53DE4BD1267098F300739748 /* SearchBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBarView.swift; sourceTree = "<group>"; }; 53DE4BD1267098F300739748 /* SearchBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBarView.swift; sourceTree = "<group>"; };
53DF641D263D9C0600A7CD1A /* LibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = "<group>"; }; 53DF641D263D9C0600A7CD1A /* LibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = "<group>"; };
@ -400,7 +406,7 @@
531069502684E7EE00CFFDBA /* InfoTabBarViewController.swift */, 531069502684E7EE00CFFDBA /* InfoTabBarViewController.swift */,
531069532684E7EE00CFFDBA /* VideoPlayer.swift */, 531069532684E7EE00CFFDBA /* VideoPlayer.swift */,
531069552684E7EE00CFFDBA /* VideoPlayerViewController.swift */, 531069552684E7EE00CFFDBA /* VideoPlayerViewController.swift */,
531069562684E7EE00CFFDBA /* VideoPlayerStoryboard.storyboard */, 531069562684E7EE00CFFDBA /* VideoPlayer.storyboard */,
); );
path = VideoPlayer; path = VideoPlayer;
sourceTree = "<group>"; sourceTree = "<group>";
@ -449,6 +455,9 @@
531690E6267ABD79005D8AB9 /* HomeView.swift */, 531690E6267ABD79005D8AB9 /* HomeView.swift */,
531690F8267AD135005D8AB9 /* README.md */, 531690F8267AD135005D8AB9 /* README.md */,
536D3D7E267BDF100004248C /* LatestMediaView.swift */, 536D3D7E267BDF100004248C /* LatestMediaView.swift */,
53A83C32268A309300DF3D92 /* LibraryView.swift */,
53CD2A3F268A49C2002ABD4E /* ItemView.swift */,
53CD2A41268A4B38002ABD4E /* MovieItemView.swift */,
); );
path = "JellyfinPlayer tvOS"; path = "JellyfinPlayer tvOS";
sourceTree = "<group>"; sourceTree = "<group>";
@ -806,7 +815,7 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
5310695D2684E7EE00CFFDBA /* VideoPlayerStoryboard.storyboard in Resources */, 5310695D2684E7EE00CFFDBA /* VideoPlayer.storyboard in Resources */,
5358706A2669D21700D05A09 /* Preview Assets.xcassets in Resources */, 5358706A2669D21700D05A09 /* Preview Assets.xcassets in Resources */,
535870672669D21700D05A09 /* Assets.xcassets in Resources */, 535870672669D21700D05A09 /* Assets.xcassets in Resources */,
5358707E2669D64F00D05A09 /* bitrates.json in Resources */, 5358707E2669D64F00D05A09 /* bitrates.json in Resources */,
@ -948,6 +957,8 @@
62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */, 62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */,
536D3D88267C17350004248C /* PublicUserButton.swift in Sources */, 536D3D88267C17350004248C /* PublicUserButton.swift in Sources */,
62E632EA267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */, 62E632EA267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
53CD2A40268A49C2002ABD4E /* ItemView.swift in Sources */,
53CD2A42268A4B38002ABD4E /* MovieItemView.swift in Sources */,
536D3D7F267BDF100004248C /* LatestMediaView.swift in Sources */, 536D3D7F267BDF100004248C /* LatestMediaView.swift in Sources */,
091B5A8E268315D400D78B61 /* UDPBroadCastConnection.swift in Sources */, 091B5A8E268315D400D78B61 /* UDPBroadCastConnection.swift in Sources */,
531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */, 531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */,
@ -955,6 +966,7 @@
531690F7267ACC00005D8AB9 /* LandscapeItemElement.swift in Sources */, 531690F7267ACC00005D8AB9 /* LandscapeItemElement.swift in Sources */,
62E632E1267D30CA0063E547 /* LibraryViewModel.swift in Sources */, 62E632E1267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */, 535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */,
53A83C33268A309300DF3D92 /* LibraryView.swift in Sources */,
62E632ED267D410B0063E547 /* SeriesItemViewModel.swift in Sources */, 62E632ED267D410B0063E547 /* SeriesItemViewModel.swift in Sources */,
62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */, 62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */,
5310695A2684E7EE00CFFDBA /* VideoPlayer.swift in Sources */, 5310695A2684E7EE00CFFDBA /* VideoPlayer.swift in Sources */,
@ -1471,8 +1483,8 @@
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kean/NukeUI"; repositoryURL = "https://github.com/kean/NukeUI";
requirement = { requirement = {
kind = upToNextMajorVersion; kind = exactVersion;
minimumVersion = 0.3.0; version = 0.3.0;
}; };
}; };
625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */ = { 625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */ = {

View File

@ -69,8 +69,8 @@
"repositoryURL": "https://github.com/kean/NukeUI", "repositoryURL": "https://github.com/kean/NukeUI",
"state": { "state": {
"branch": null, "branch": null,
"revision": "4516371912149ac024dec361827931b46a69c217", "revision": "d2580b8d22b29c6244418d8e4b568f3162191460",
"version": "0.6.2" "version": "0.3.0"
} }
}, },
{ {

View File

@ -67,7 +67,7 @@ struct ConnectToServerView: View {
Text(publicUser.name ?? "").font(.subheadline).fontWeight(.semibold) Text(publicUser.name ?? "").font(.subheadline).fontWeight(.semibold)
Spacer() Spacer()
if publicUser.primaryImageTag != nil { if publicUser.primaryImageTag != nil {
ImageView(src: URL(string: "\(ServerEnvironment.current.server.baseURI ?? "")/Users/\(publicUser.id ?? "")/Images/Primary?width=120&quality=80&tag=\(publicUser.primaryImageTag!)")!) ImageView(src: URL(string: "\(ServerEnvironment.current.server.baseURI ?? "")/Users/\(publicUser.id ?? "")/Images/Primary?width=60&quality=80&tag=\(publicUser.primaryImageTag!)")!)
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.cornerRadius(30.0) .cornerRadius(30.0)
} else { } else {

View File

@ -70,7 +70,7 @@ extension BaseItemDto {
} }
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(imageItemId)/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)" let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(imageItemId)/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)"
return URL(string: urlString)! return URL(string: urlString)!
} }
@ -79,7 +79,7 @@ extension BaseItemDto {
let imageTag = (self.parentBackdropImageTags ?? [""])[0] let imageTag = (self.parentBackdropImageTags ?? [""])[0]
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(self.parentBackdropItemId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)" let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(self.parentBackdropItemId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)"
return URL(string: urlString)! return URL(string: urlString)!
} }
@ -87,7 +87,7 @@ extension BaseItemDto {
let imageType = "Primary" let imageType = "Primary"
let imageTag = self.seriesPrimaryImageTag ?? "" let imageTag = self.seriesPrimaryImageTag ?? ""
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(self.seriesId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)" let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(self.seriesId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)"
return URL(string: urlString)! return URL(string: urlString)!
} }
@ -103,7 +103,7 @@ extension BaseItemDto {
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(imageItemId)/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)" let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(imageItemId)/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=96&tag=\(imageTag)"
return URL(string: urlString)! return URL(string: urlString)!
} }

View File

@ -24,16 +24,14 @@ struct ImageView: View {
} }
var body: some View { var body: some View {
LazyImage(source: source) { state in LazyImage(source: source)
if let image = state.image { .placeholder {
image Image(uiImage: UIImage(blurHash: blurhash, size: CGSize(width: 8, height: 8))!)
} else if state.error != nil { .resizable()
Rectangle() }
.fill(Color.gray) .failure {
} else { Rectangle()
Image(uiImage: UIImage(blurHash: blurhash, size: CGSize(width: 16, height: 16))!) .background(Color.gray)
.resizable()
}
} }
} }
} }

View File

@ -210,17 +210,9 @@ open class UDPBroadcastConnection {
} }
guard sent > 0 else { guard sent > 0 else {
if let errorString = String(validatingUTF8: strerror(errno)) {
// debugPrint("UDP connection failed to send data: \(errorString)")
}
closeConnection() closeConnection()
throw ConnectionError.sendingMessageFailed(code: errno) throw ConnectionError.sendingMessageFailed(code: errno)
} }
if sent == broadcastMessageLength {
// Success
// debugPrint("UDP connection sent \(broadcastMessageLength) bytes")
}
} }
} }

View File

@ -88,11 +88,45 @@ final class LibraryViewModel: ViewModel {
.store(in: &cancellables) .store(in: &cancellables)
} }
func requestItemsAsync(with filters: LibraryFilters) {
let personIDs: [String] = [person].compactMap(\.?.id)
let studioIDs: [String] = [studio].compactMap(\.?.id)
let genreIDs: [String]
if filters.withGenres.isEmpty {
genreIDs = [genre].compactMap(\.?.id)
} else {
genreIDs = filters.withGenres.compactMap(\.id)
}
let sortBy = filters.sortBy.map(\.rawValue)
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: filters.filters.contains(.isFavorite) ? true : false,
searchTerm: nil, sortOrder: filters.sortOrder, parentId: parentID,
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
filters: filters.filters, sortBy: sortBy, tags: filters.tags,
enableUserData: true, personIds: personIDs, studioIds: studioIDs, genreIds: genreIDs, enableImages: true)
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestCompletion(completion: completion)
}, receiveValue: { [weak self] response in
guard let self = self else { return }
let totalPages = ceil(Double(response.totalRecordCount ?? 0) / 100.0)
self.totalPages = Int(totalPages)
self.hasPreviousPage = self.currentPage > 0
self.hasNextPage = self.currentPage < self.totalPages - 1
self.items.append(contentsOf: response.items ?? [])
})
.store(in: &cancellables)
}
func requestNextPage() { func requestNextPage() {
currentPage += 1 currentPage += 1
requestItems(with: filters) requestItems(with: filters)
} }
func requestNextPageAsync() {
currentPage += 1
requestItemsAsync(with: filters)
}
func requestPreviousPage() { func requestPreviousPage() {
currentPage -= 1 currentPage -= 1
requestItems(with: filters) requestItems(with: filters)