basic movie item view
This commit is contained in:
parent
2919b25a5d
commit
897d158707
|
@ -24,6 +24,38 @@ struct PortraitItemElement: View {
|
|||
.cornerRadius(10)
|
||||
.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
|
||||
withAnimation(.linear(duration: 0.15)) {
|
||||
|
|
|
@ -25,7 +25,7 @@ struct ContinueWatchingView: View {
|
|||
LazyHStack {
|
||||
Spacer().frame(width: 45)
|
||||
ForEach(items, id: \.id) { item in
|
||||
NavigationLink(destination: VideoPlayerView(item: item)) {
|
||||
NavigationLink(destination: LazyView { ItemView(item: item) }) {
|
||||
LandscapeItemElement(item: item)
|
||||
}
|
||||
.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
|
|
|
@ -33,7 +33,9 @@ struct HomeView: View {
|
|||
VStack(alignment: .leading) {
|
||||
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 {
|
||||
Text("Latest \(library?.name ?? "")")
|
||||
.font(.headline)
|
||||
|
@ -45,6 +47,7 @@ struct HomeView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
Spacer().frame(height: 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
struct JellyfinPlayer_tvOSApp: App {
|
||||
let persistenceController = PersistenceController.shared
|
||||
|
|
|
@ -42,7 +42,7 @@ struct LatestMediaView: View {
|
|||
LazyHStack {
|
||||
Spacer().frame(width: 45)
|
||||
ForEach(items, id: \.id) { item in
|
||||
NavigationLink(destination: Text("itemv")) {
|
||||
NavigationLink(destination: LazyView { ItemView(item: item) }) {
|
||||
PortraitItemElement(item: item)
|
||||
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
}
|
||||
|
|
|
@ -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!
|
||||
//
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ struct NextUpView: View {
|
|||
LazyHStack {
|
||||
Spacer().frame(width: 45)
|
||||
ForEach(items, id: \.id) { item in
|
||||
NavigationLink(destination: VideoPlayerView(item: item)) {
|
||||
NavigationLink(destination: LazyView { ItemView(item: item) }) {
|
||||
LandscapeItemElement(item: item)
|
||||
}.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ struct VideoPlayerView: UIViewControllerRepresentable {
|
|||
|
||||
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
|
||||
viewController.manifest = item
|
||||
|
||||
|
|
|
@ -244,17 +244,12 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
// Pause and load captions into memory.
|
||||
mediaPlayer.pause()
|
||||
|
||||
var shouldHaveSubtitleTracks = 0
|
||||
subtitleTrackArray.forEach { sub in
|
||||
if sub.id != -1 && sub.delivery == .external && sub.codec != "subrip" {
|
||||
shouldHaveSubtitleTracks = shouldHaveSubtitleTracks + 1
|
||||
if sub.id != -1 && sub.delivery == .external {
|
||||
mediaPlayer.addPlaybackSlave(sub.url!, type: .subtitle, enforce: false)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for captions to load
|
||||
while mediaPlayer.numberOfSubtitlesTracks != shouldHaveSubtitleTracks {}
|
||||
|
||||
// Select default track & resume playback
|
||||
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack
|
||||
mediaPlayer.pause()
|
||||
|
@ -714,18 +709,8 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
|
||||
// Move time along transport bar
|
||||
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
|
||||
if time != lastTime {
|
||||
if abs(time-lastTime) > 0.00005 {
|
||||
self.currentTimeLabel.text = formatSecondsToHMS(Double(mediaPlayer.time.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
|
||||
}
|
||||
}
|
||||
|
||||
lastTime = time
|
||||
}
|
||||
|
||||
lastTime = time
|
||||
|
||||
if CACurrentMediaTime() - lastProgressReportTime > 5 {
|
||||
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack
|
||||
sendProgressReport(eventName: "timeupdate")
|
||||
lastProgressReportTime = CACurrentMediaTime()
|
||||
}
|
||||
|
||||
if loading {
|
||||
loading = false
|
||||
DispatchQueue.main.async { [self] in
|
||||
activityIndicator.isHidden = true
|
||||
activityIndicator.stopAnimating()
|
||||
}
|
||||
updateNowPlayingCenter(time: nil, playing: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Settings Delegate
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
5310695A2684E7EE00CFFDBA /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069532684E7EE00CFFDBA /* VideoPlayer.swift */; };
|
||||
5310695B2684E7EE00CFFDBA /* AudioView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069542684E7EE00CFFDBA /* AudioView.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 */; };
|
||||
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E6267ABD79005D8AB9 /* HomeView.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 */; };
|
||||
53A431BD266B0FF20016769F /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 53A431BC266B0FF20016769F /* 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 */; };
|
||||
53ABFDDE267974E300886593 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ABFDDD267974E300886593 /* SplashView.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 */; };
|
||||
53AD124D267029D60094A276 /* SeriesItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA526572F0700E7EA70 /* SeriesItemView.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 */; };
|
||||
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DE4BD1267098F300739748 /* SearchBarView.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -276,11 +279,14 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -400,7 +406,7 @@
|
|||
531069502684E7EE00CFFDBA /* InfoTabBarViewController.swift */,
|
||||
531069532684E7EE00CFFDBA /* VideoPlayer.swift */,
|
||||
531069552684E7EE00CFFDBA /* VideoPlayerViewController.swift */,
|
||||
531069562684E7EE00CFFDBA /* VideoPlayerStoryboard.storyboard */,
|
||||
531069562684E7EE00CFFDBA /* VideoPlayer.storyboard */,
|
||||
);
|
||||
path = VideoPlayer;
|
||||
sourceTree = "<group>";
|
||||
|
@ -449,6 +455,9 @@
|
|||
531690E6267ABD79005D8AB9 /* HomeView.swift */,
|
||||
531690F8267AD135005D8AB9 /* README.md */,
|
||||
536D3D7E267BDF100004248C /* LatestMediaView.swift */,
|
||||
53A83C32268A309300DF3D92 /* LibraryView.swift */,
|
||||
53CD2A3F268A49C2002ABD4E /* ItemView.swift */,
|
||||
53CD2A41268A4B38002ABD4E /* MovieItemView.swift */,
|
||||
);
|
||||
path = "JellyfinPlayer tvOS";
|
||||
sourceTree = "<group>";
|
||||
|
@ -806,7 +815,7 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5310695D2684E7EE00CFFDBA /* VideoPlayerStoryboard.storyboard in Resources */,
|
||||
5310695D2684E7EE00CFFDBA /* VideoPlayer.storyboard in Resources */,
|
||||
5358706A2669D21700D05A09 /* Preview Assets.xcassets in Resources */,
|
||||
535870672669D21700D05A09 /* Assets.xcassets in Resources */,
|
||||
5358707E2669D64F00D05A09 /* bitrates.json in Resources */,
|
||||
|
@ -948,6 +957,8 @@
|
|||
62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */,
|
||||
536D3D88267C17350004248C /* PublicUserButton.swift in Sources */,
|
||||
62E632EA267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
|
||||
53CD2A40268A49C2002ABD4E /* ItemView.swift in Sources */,
|
||||
53CD2A42268A4B38002ABD4E /* MovieItemView.swift in Sources */,
|
||||
536D3D7F267BDF100004248C /* LatestMediaView.swift in Sources */,
|
||||
091B5A8E268315D400D78B61 /* UDPBroadCastConnection.swift in Sources */,
|
||||
531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */,
|
||||
|
@ -955,6 +966,7 @@
|
|||
531690F7267ACC00005D8AB9 /* LandscapeItemElement.swift in Sources */,
|
||||
62E632E1267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
|
||||
535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */,
|
||||
53A83C33268A309300DF3D92 /* LibraryView.swift in Sources */,
|
||||
62E632ED267D410B0063E547 /* SeriesItemViewModel.swift in Sources */,
|
||||
62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */,
|
||||
5310695A2684E7EE00CFFDBA /* VideoPlayer.swift in Sources */,
|
||||
|
@ -1471,8 +1483,8 @@
|
|||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/kean/NukeUI";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.3.0;
|
||||
kind = exactVersion;
|
||||
version = 0.3.0;
|
||||
};
|
||||
};
|
||||
625CB5782678C4A400530A6E /* XCRemoteSwiftPackageReference "ActivityIndicator" */ = {
|
||||
|
|
|
@ -69,8 +69,8 @@
|
|||
"repositoryURL": "https://github.com/kean/NukeUI",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "4516371912149ac024dec361827931b46a69c217",
|
||||
"version": "0.6.2"
|
||||
"revision": "d2580b8d22b29c6244418d8e4b568f3162191460",
|
||||
"version": "0.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -67,7 +67,7 @@ struct ConnectToServerView: View {
|
|||
Text(publicUser.name ?? "").font(.subheadline).fontWeight(.semibold)
|
||||
Spacer()
|
||||
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)
|
||||
.cornerRadius(30.0)
|
||||
} else {
|
||||
|
|
|
@ -70,7 +70,7 @@ extension BaseItemDto {
|
|||
}
|
||||
|
||||
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)!
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ extension BaseItemDto {
|
|||
let imageTag = (self.parentBackdropImageTags ?? [""])[0]
|
||||
|
||||
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)!
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ extension BaseItemDto {
|
|||
let imageType = "Primary"
|
||||
let imageTag = self.seriesPrimaryImageTag ?? ""
|
||||
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)!
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ extension BaseItemDto {
|
|||
|
||||
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)!
|
||||
}
|
||||
|
||||
|
|
|
@ -24,16 +24,14 @@ struct ImageView: View {
|
|||
}
|
||||
|
||||
var body: some View {
|
||||
LazyImage(source: source) { state in
|
||||
if let image = state.image {
|
||||
image
|
||||
} else if state.error != nil {
|
||||
Rectangle()
|
||||
.fill(Color.gray)
|
||||
} else {
|
||||
Image(uiImage: UIImage(blurHash: blurhash, size: CGSize(width: 16, height: 16))!)
|
||||
.resizable()
|
||||
}
|
||||
LazyImage(source: source)
|
||||
.placeholder {
|
||||
Image(uiImage: UIImage(blurHash: blurhash, size: CGSize(width: 8, height: 8))!)
|
||||
.resizable()
|
||||
}
|
||||
.failure {
|
||||
Rectangle()
|
||||
.background(Color.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -210,17 +210,9 @@ open class UDPBroadcastConnection {
|
|||
}
|
||||
|
||||
guard sent > 0 else {
|
||||
if let errorString = String(validatingUTF8: strerror(errno)) {
|
||||
// debugPrint("UDP connection failed to send data: \(errorString)")
|
||||
}
|
||||
closeConnection()
|
||||
throw ConnectionError.sendingMessageFailed(code: errno)
|
||||
}
|
||||
|
||||
if sent == broadcastMessageLength {
|
||||
// Success
|
||||
// debugPrint("UDP connection sent \(broadcastMessageLength) bytes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -87,11 +87,45 @@ final class LibraryViewModel: ViewModel {
|
|||
})
|
||||
.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() {
|
||||
currentPage += 1
|
||||
requestItems(with: filters)
|
||||
}
|
||||
|
||||
func requestNextPageAsync() {
|
||||
currentPage += 1
|
||||
requestItemsAsync(with: filters)
|
||||
}
|
||||
|
||||
func requestPreviousPage() {
|
||||
currentPage -= 1
|
||||
|
|
Loading…
Reference in New Issue