update item after playback close

This commit is contained in:
Ethan Pippin 2022-01-11 00:01:37 -07:00
parent 0df99b9899
commit 53048d7d14
10 changed files with 120 additions and 29 deletions

View File

@ -13,6 +13,9 @@ import UIKit
extension BaseItemDto { extension BaseItemDto {
func createVideoPlayerViewModel() -> AnyPublisher<VideoPlayerViewModel, Error> { func createVideoPlayerViewModel() -> AnyPublisher<VideoPlayerViewModel, Error> {
LogManager.shared.log.debug("Creating video player view model for item: \(id ?? "")")
let builder = DeviceProfileBuilder() let builder = DeviceProfileBuilder()
// TODO: fix bitrate settings // TODO: fix bitrate settings
builder.setMaxBitrate(bitrate: 60_000_000) builder.setMaxBitrate(bitrate: 60_000_000)

View File

@ -20,5 +20,8 @@ enum SwiftfinNotificationCenter {
static let processDeepLink = Notification.Name("processDeepLink") static let processDeepLink = Notification.Name("processDeepLink")
static let didPurge = Notification.Name("didPurge") static let didPurge = Notification.Name("didPurge")
static let didChangeServerCurrentURI = Notification.Name("didChangeCurrentLoginURI") static let didChangeServerCurrentURI = Notification.Name("didChangeCurrentLoginURI")
// Send with an item id to check if current item for item views
static let didSendStopReport = Notification.Name("didSendStopReport")
} }
} }

View File

@ -29,10 +29,6 @@ final class EpisodeItemViewModel: ItemViewModel {
return "\(episodeLocator)\n\(item.name ?? "")" return "\(episodeLocator)\n\(item.name ?? "")"
} }
override func shouldDisplayRuntime() -> Bool {
false
}
func getEpisodeSeries() { func getEpisodeSeries() {
guard let id = item.seriesId else { return } guard let id = item.seriesId else { return }
UserLibraryAPI.getItem(userId: SessionManager.main.currentLogin.user.id, itemId: id) UserLibraryAPI.getItem(userId: SessionManager.main.currentLogin.user.id, itemId: id)
@ -44,4 +40,29 @@ final class EpisodeItemViewModel: ItemViewModel {
}) })
.store(in: &cancellables) .store(in: &cancellables)
} }
override func updateItem() {
ItemsAPI.getItems(userId: SessionManager.main.currentLogin.user.id,
limit: 1,
fields: [
.primaryImageAspectRatio,
.seriesPrimaryImage,
.seasonUserData,
.overview,
.genres,
.people,
.chapters,
],
enableUserData: true,
ids: [item.id ?? ""])
.sink { completion in
self.handleAPIRequestError(completion: completion)
} receiveValue: { response in
if let item = response.items?.first {
self.item = item
self.playButtonItem = item
}
}
.store(in: &cancellables)
}
} }

View File

@ -54,9 +54,27 @@ class ItemViewModel: ViewModel {
getSimilarItems() getSimilarItems()
SwiftfinNotificationCenter.main.addObserver(self,
selector: #selector(receivedStopReport(_:)),
name: SwiftfinNotificationCenter.Keys.didSendStopReport,
object: nil)
refreshItemVideoPlayerViewModel(for: item) refreshItemVideoPlayerViewModel(for: item)
} }
@objc
private func receivedStopReport(_ notification: NSNotification) {
guard let itemID = notification.object as? String else { return }
if itemID == item.id {
updateItem()
} else {
// Remove if necessary. Note that this cannot be in deinit as
// holding as an observer won't allow the object to be deinit-ed
SwiftfinNotificationCenter.main.removeObserver(self)
}
}
func refreshItemVideoPlayerViewModel(for item: BaseItemDto) { func refreshItemVideoPlayerViewModel(for item: BaseItemDto) {
item.createVideoPlayerViewModel() item.createVideoPlayerViewModel()
.sink { completion in .sink { completion in
@ -139,4 +157,7 @@ class ItemViewModel: ViewModel {
.store(in: &cancellables) .store(in: &cancellables)
} }
} }
// Overridden by subclasses
func updateItem() {}
} }

View File

@ -10,4 +10,30 @@ import Combine
import Foundation import Foundation
import JellyfinAPI import JellyfinAPI
final class MovieItemViewModel: ItemViewModel {} final class MovieItemViewModel: ItemViewModel {
override func updateItem() {
ItemsAPI.getItems(userId: SessionManager.main.currentLogin.user.id,
limit: 1,
fields: [
.primaryImageAspectRatio,
.seriesPrimaryImage,
.seasonUserData,
.overview,
.genres,
.people,
.chapters,
],
enableUserData: true,
ids: [item.id ?? ""])
.sink { completion in
self.handleAPIRequestError(completion: completion)
} receiveValue: { response in
if let item = response.items?.first {
self.item = item
self.playButtonItem = item
}
}
.store(in: &cancellables)
}
}

View File

@ -247,7 +247,7 @@ extension VideoPlayerViewModel {
adjacentTo: item.id, adjacentTo: item.id,
limit: 3) limit: 3)
.sink(receiveCompletion: { completion in .sink(receiveCompletion: { completion in
print(completion) self.handleAPIRequestError(completion: completion)
}, receiveValue: { response in }, receiveValue: { response in
// 4 possible states: // 4 possible states:
@ -510,6 +510,8 @@ extension VideoPlayerViewModel {
self.handleAPIRequestError(completion: completion) self.handleAPIRequestError(completion: completion)
} receiveValue: { _ in } receiveValue: { _ in
LogManager.shared.log.debug("Stop report sent for item: \(self.item.id ?? "No ID")") LogManager.shared.log.debug("Stop report sent for item: \(self.item.id ?? "No ID")")
SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSendStopReport,
object: self.item.id)
} }
.store(in: &cancellables) .store(in: &cancellables)
} }
@ -536,3 +538,13 @@ extension VideoPlayerViewModel {
return newURL.url! return newURL.url!
} }
} }
// MARK: Equatable
extension VideoPlayerViewModel: Equatable {
static func == (lhs: VideoPlayerViewModel, rhs: VideoPlayerViewModel) -> Bool {
lhs.item.id == rhs.item.id &&
lhs.item.userData?.playbackPositionTicks == rhs.item.userData?.playbackPositionTicks
}
}

View File

@ -2773,7 +2773,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 66; CURRENT_PROJECT_VERSION = 66;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = TY84JMYEFE;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
EXCLUDED_ARCHS = ""; EXCLUDED_ARCHS = "";
@ -2810,7 +2810,7 @@
CURRENT_PROJECT_VERSION = 66; CURRENT_PROJECT_VERSION = 66;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = TY84JMYEFE;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
EXCLUDED_ARCHS = ""; EXCLUDED_ARCHS = "";
@ -2841,7 +2841,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 66; CURRENT_PROJECT_VERSION = 66;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = TY84JMYEFE;
INFOPLIST_FILE = WidgetExtension/Info.plist; INFOPLIST_FILE = WidgetExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@ -2868,7 +2868,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 66; CURRENT_PROJECT_VERSION = 66;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = TY84JMYEFE;
INFOPLIST_FILE = WidgetExtension/Info.plist; INFOPLIST_FILE = WidgetExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (

View File

@ -54,8 +54,7 @@ struct ItemViewBody: View {
topBarView: { topBarView: {
L10n.seasons.text L10n.seasons.text
.fontWeight(.semibold) .fontWeight(.semibold)
.padding(.bottom) .padding()
.padding(.horizontal)
}, selectedAction: { season in }, selectedAction: { season in
itemRouter.route(to: \.item, season) itemRouter.route(to: \.item, season)
}) })
@ -120,7 +119,7 @@ struct ItemViewBody: View {
// MARK: Cast & Crew // MARK: Cast & Crew
if showCastAndCrew { if showCastAndCrew {
if let castAndCrew = viewModel.item.people, castAndCrew.count > 0 { if let castAndCrew = viewModel.item.people, !castAndCrew.isEmpty {
PortraitImageHStackView(items: castAndCrew.filter { BaseItemPerson.DisplayedType.allCasesRaw.contains($0.type ?? "") }, PortraitImageHStackView(items: castAndCrew.filter { BaseItemPerson.DisplayedType.allCasesRaw.contains($0.type ?? "") },
topBarView: { topBarView: {
L10n.castAndCrew.text L10n.castAndCrew.text

View File

@ -14,6 +14,8 @@ struct ItemLandscapeMainView: View {
var itemRouter: ItemCoordinator.Router var itemRouter: ItemCoordinator.Router
@EnvironmentObject @EnvironmentObject
private var viewModel: ItemViewModel private var viewModel: ItemViewModel
@State
private var playButtonText: String = ""
// MARK: innerBody // MARK: innerBody
@ -72,6 +74,9 @@ struct ItemLandscapeMainView: View {
} }
} }
} }
.onAppear {
playButtonText = viewModel.playButtonText()
}
} }
// MARK: body // MARK: body

View File

@ -15,6 +15,8 @@ struct PortraitHeaderOverlayView: View {
var itemRouter: ItemCoordinator.Router var itemRouter: ItemCoordinator.Router
@EnvironmentObject @EnvironmentObject
private var viewModel: ItemViewModel private var viewModel: ItemViewModel
@State
private var playButtonText: String = ""
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
@ -37,9 +39,9 @@ struct PortraitHeaderOverlayView: View {
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.padding(.bottom, 10) .padding(.bottom, 10)
if viewModel.item.itemType.showDetails { // MARK: Details
// MARK: Runtime
HStack {
if viewModel.shouldDisplayRuntime() { if viewModel.shouldDisplayRuntime() {
if let runtime = viewModel.item.getItemRuntime() { if let runtime = viewModel.item.getItemRuntime() {
Text(runtime) Text(runtime)
@ -49,11 +51,7 @@ struct PortraitHeaderOverlayView: View {
.lineLimit(1) .lineLimit(1)
} }
} }
}
// MARK: Details
HStack {
if let productionYear = viewModel.item.productionYear { if let productionYear = viewModel.item.productionYear {
Text(String(productionYear)) Text(String(productionYear))
.font(.subheadline) .font(.subheadline)
@ -88,7 +86,7 @@ struct PortraitHeaderOverlayView: View {
Image(systemName: "play.fill") Image(systemName: "play.fill")
.foregroundColor(viewModel.playButtonItem == nil ? Color(UIColor.secondaryLabel) : Color.white) .foregroundColor(viewModel.playButtonItem == nil ? Color(UIColor.secondaryLabel) : Color.white)
.font(.system(size: 20)) .font(.system(size: 20))
Text(viewModel.playButtonText()) Text(playButtonText)
.foregroundColor(viewModel.playButtonItem == nil ? Color(UIColor.secondaryLabel) : Color.white) .foregroundColor(viewModel.playButtonItem == nil ? Color(UIColor.secondaryLabel) : Color.white)
.font(.callout) .font(.callout)
.fontWeight(.semibold) .fontWeight(.semibold)
@ -137,7 +135,10 @@ struct PortraitHeaderOverlayView: View {
} }
}.padding(.top, 8) }.padding(.top, 8)
} }
.padding(.horizontal, 16) .onAppear {
playButtonText = viewModel.playButtonText()
}
.padding(.horizontal)
.padding(.bottom, UIDevice.current.userInterfaceIdiom == .pad ? -189 : -64) .padding(.bottom, UIDevice.current.userInterfaceIdiom == .pad ? -189 : -64)
} }
} }