Fix fast user switching
Show watched icon on completed episodes. Add seek buttons to MPNowPlayingInfoCenter Fix tvos remote play/pause not showing series name.
This commit is contained in:
parent
c0714761b2
commit
42d9b4b8a7
|
@ -47,6 +47,16 @@ struct LandscapeItemElement: View {
|
|||
ImageView(src: (item.type == "Episode" && !(inSeasonView ?? false) ? item.getSeriesBackdropImage(maxWidth: 445) : item.getBackdropImage(maxWidth: 445)), bh: item.type == "Episode" ? item.getSeriesBackdropImageBlurHash() : item.getBackdropImageBlurHash())
|
||||
.frame(width: 445, height: 250)
|
||||
.cornerRadius(10)
|
||||
.overlay(
|
||||
ZStack {
|
||||
if item.userData?.played ?? false {
|
||||
Image(systemName: "circle.fill")
|
||||
.foregroundColor(.white)
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(Color(.systemBlue))
|
||||
}
|
||||
}.padding(2)
|
||||
.opacity(1), alignment: .topTrailing).opacity(1)
|
||||
.overlay(
|
||||
ZStack(alignment: .leading) {
|
||||
if focused && item.userData?.playedPercentage != nil {
|
||||
|
|
|
@ -69,9 +69,10 @@ struct EpisodeItemView: View {
|
|||
.overlay(RoundedRectangle(cornerRadius: 2)
|
||||
.stroke(Color.secondary, lineWidth: 1))
|
||||
}
|
||||
}.padding(.top, 15)
|
||||
Spacer()
|
||||
}.padding(.top, -15)
|
||||
|
||||
HStack {
|
||||
HStack(alignment: .top) {
|
||||
VStack(alignment: .trailing) {
|
||||
if(studio != nil) {
|
||||
Text("STUDIO")
|
||||
|
@ -112,13 +113,6 @@ struct EpisodeItemView: View {
|
|||
Spacer()
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
if(!(viewModel.item.taglines ?? []).isEmpty) {
|
||||
Text(viewModel.item.taglines?.first ?? "")
|
||||
.font(.body)
|
||||
.italic()
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
Text(viewModel.item.overview ?? "")
|
||||
.font(.body)
|
||||
.fontWeight(.medium)
|
||||
|
@ -174,6 +168,7 @@ struct EpisodeItemView: View {
|
|||
.frame(height: 360)
|
||||
}
|
||||
Spacer()
|
||||
Spacer()
|
||||
}.padding(EdgeInsets(top: 90, leading: 90, bottom: 0, trailing: 90))
|
||||
}.onAppear(perform: onAppear)
|
||||
}
|
||||
|
|
|
@ -21,6 +21,19 @@ struct HomeView: View {
|
|||
ProgressView()
|
||||
} else {
|
||||
LazyVStack(alignment: .leading) {
|
||||
Button {
|
||||
let nc = NotificationCenter.default
|
||||
nc.post(name: Notification.Name("didSignOut"), object: nil)
|
||||
} label: {
|
||||
HStack {
|
||||
ImageView(src: URL(string: "\(ServerEnvironment.current.server.baseURI ?? "")/Users/\(SessionManager.current.user.user_id!)/Images/Primary?width=500")!)
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(25.0)
|
||||
Text(SessionManager.current.user.username ?? "")
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
}.padding(.leading, 90)
|
||||
if !viewModel.resumeItems.isEmpty {
|
||||
ContinueWatchingView(items: viewModel.resumeItems)
|
||||
}
|
||||
|
|
|
@ -82,6 +82,7 @@ struct SeasonItemView: View {
|
|||
Text(viewModel.isFavorited ? "Unfavorite" : "Favorite")
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Button {
|
||||
viewModel.updateWatchState()
|
||||
|
|
|
@ -247,32 +247,42 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
let commandCenter = MPRemoteCommandCenter.shared()
|
||||
commandCenter.playCommand.isEnabled = true
|
||||
commandCenter.pauseCommand.isEnabled = true
|
||||
commandCenter.seekForwardCommand.isEnabled = true
|
||||
commandCenter.seekBackwardCommand.isEnabled = true
|
||||
|
||||
commandCenter.skipBackwardCommand.isEnabled = true
|
||||
commandCenter.skipBackwardCommand.preferredIntervals = [15]
|
||||
|
||||
commandCenter.skipForwardCommand.isEnabled = true
|
||||
commandCenter.skipForwardCommand.preferredIntervals = [30]
|
||||
|
||||
commandCenter.changePlaybackPositionCommand.isEnabled = true
|
||||
commandCenter.enableLanguageOptionCommand.isEnabled = true
|
||||
|
||||
// Add handler for Pause Command
|
||||
commandCenter.pauseCommand.addTarget { _ in
|
||||
self.pause()
|
||||
self.showingControls = true
|
||||
self.controlsView.isHidden = false
|
||||
self.controlsAppearTime = CACurrentMediaTime()
|
||||
return .success
|
||||
}
|
||||
|
||||
// Add handler for Play command
|
||||
commandCenter.playCommand.addTarget { _ in
|
||||
self.play()
|
||||
self.showingControls = false
|
||||
self.controlsView.isHidden = true
|
||||
return .success
|
||||
}
|
||||
|
||||
// Add handler for FF command
|
||||
commandCenter.seekForwardCommand.addTarget { _ in
|
||||
commandCenter.skipForwardCommand.addTarget { skipEvent in
|
||||
self.mediaPlayer.jumpForward(30)
|
||||
self.sendProgressReport(eventName: "timeupdate")
|
||||
return .success
|
||||
}
|
||||
|
||||
// Add handler for RW command
|
||||
commandCenter.seekBackwardCommand.addTarget { _ in
|
||||
commandCenter.skipBackwardCommand.addTarget { skipEvent in
|
||||
self.mediaPlayer.jumpBackward(15)
|
||||
self.sendProgressReport(eventName: "timeupdate")
|
||||
return .success
|
||||
|
@ -314,12 +324,15 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate,
|
|||
var nowPlayingInfo = [String: Any]()
|
||||
|
||||
nowPlayingInfo[MPMediaItemPropertyTitle] = manifest.name ?? "Jellyfin Video"
|
||||
if(manifest.type == "Episode") {
|
||||
nowPlayingInfo[MPMediaItemPropertyArtist] = "\(manifest.seriesName ?? manifest.name ?? "") • \(manifest.getEpisodeLocator())"
|
||||
}
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 0.0
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = AVMediaType.video
|
||||
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = runTicks
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = playbackTicks
|
||||
|
||||
if let imageData = NSData(contentsOf: manifest.getPrimaryImage(maxWidth: 200)) {
|
||||
if let imageData = NSData(contentsOf: manifest.getPrimaryImage(maxWidth: 500)) {
|
||||
if let artworkImage = UIImage(data: imageData as Data) {
|
||||
let artwork = MPMediaItemArtwork.init(boundsSize: artworkImage.size, requestHandler: { (_) -> UIImage in
|
||||
return artworkImage
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19141.11" systemVersion="21A5248p" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19141.11" systemVersion="21A5268h" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Server" representedClassName="Server" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baseURI" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="name" attributeType="String" defaultValueString=""/>
|
||||
|
@ -7,11 +7,12 @@
|
|||
</entity>
|
||||
<entity name="SignedInUser" representedClassName="SignedInUser" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="appletv_id" optional="YES" attributeType="String"/>
|
||||
<attribute name="device_uuid" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="user_id" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="username" attributeType="String" defaultValueString=""/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Server" positionX="-63" positionY="-9" width="128" height="74"/>
|
||||
<element name="SignedInUser" positionX="-63" positionY="9" width="128" height="74"/>
|
||||
<element name="SignedInUser" positionX="-63" positionY="9" width="128" height="89"/>
|
||||
</elements>
|
||||
</model>
|
|
@ -45,11 +45,11 @@ final class SessionManager {
|
|||
|
||||
if user != nil {
|
||||
let authToken = getAuthToken(userID: user.user_id!)
|
||||
generateAuthHeader(with: authToken)
|
||||
generateAuthHeader(with: authToken, deviceID: user.device_uuid)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func generateAuthHeader(with authToken: String?) {
|
||||
fileprivate func generateAuthHeader(with authToken: String?, deviceID devID: String?) {
|
||||
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
var deviceName = UIDevice.current.name
|
||||
deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current)
|
||||
|
@ -57,20 +57,28 @@ final class SessionManager {
|
|||
|
||||
var header = "MediaBrowser "
|
||||
#if os(tvOS)
|
||||
header.append("Client=\"SwiftFin tvOS\", ")
|
||||
header.append("Client=\"Jellyfin tvOS\", ")
|
||||
#else
|
||||
header.append("Client=\"SwiftFin iOS\", ")
|
||||
#endif
|
||||
|
||||
header.append("Device=\"\(deviceName)\", ")
|
||||
|
||||
if(devID == nil) {
|
||||
#if os(tvOS)
|
||||
header.append("DeviceId=\"tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(user?.user_id ?? "")\", ")
|
||||
deviceID = "tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(user?.user_id ?? "")"
|
||||
header.append("DeviceId=\"tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ")
|
||||
deviceID = "tvOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
|
||||
#else
|
||||
header.append("DeviceId=\"iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(user?.user_id ?? "")\", ")
|
||||
deviceID = "iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(user?.user_id ?? "")"
|
||||
header.append("DeviceId=\"iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))\", ")
|
||||
deviceID = "iOS_\(UIDevice.current.identifierForVendor!.uuidString)_\(String(Date().timeIntervalSince1970))"
|
||||
#endif
|
||||
print("generated device id: \(deviceID)")
|
||||
} else {
|
||||
print("device id provided: \(devID!)")
|
||||
header.append("DeviceId=\"\(devID!)\", ")
|
||||
deviceID = devID!
|
||||
}
|
||||
|
||||
header.append("Version=\"\(appVersion ?? "0.0.1")\", ")
|
||||
|
||||
if authToken != nil {
|
||||
|
@ -108,22 +116,24 @@ final class SessionManager {
|
|||
|
||||
func loginWithSavedSession(user: SignedInUser) {
|
||||
let accessToken = getAuthToken(userID: user.user_id!)
|
||||
print("logging in with saved session");
|
||||
|
||||
self.user = user
|
||||
generateAuthHeader(with: accessToken)
|
||||
generateAuthHeader(with: accessToken, deviceID: user.device_uuid)
|
||||
print(JellyfinAPI.customHeaders)
|
||||
let nc = NotificationCenter.default
|
||||
nc.post(name: Notification.Name("didSignIn"), object: nil)
|
||||
}
|
||||
|
||||
func login(username: String, password: String) -> AnyPublisher<SignedInUser, Error> {
|
||||
generateAuthHeader(with: nil)
|
||||
generateAuthHeader(with: nil, deviceID: nil)
|
||||
|
||||
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
|
||||
.map { response -> (SignedInUser, String?) in
|
||||
let user = SignedInUser(context: PersistenceController.shared.container.viewContext)
|
||||
user.username = response.user?.name
|
||||
user.user_id = response.user?.id
|
||||
user.device_uuid = self.deviceID
|
||||
|
||||
#if os(tvOS)
|
||||
// user.appletv_id = tvUserManager.currentUserIdentifier ?? ""
|
||||
|
@ -139,7 +149,7 @@ final class SessionManager {
|
|||
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
||||
keychain.set(accessToken!, forKey: "AccessToken_\(user.user_id!)")
|
||||
|
||||
generateAuthHeader(with: accessToken)
|
||||
generateAuthHeader(with: accessToken, deviceID: user.device_uuid)
|
||||
|
||||
let nc = NotificationCenter.default
|
||||
nc.post(name: Notification.Name("didSignIn"), object: nil)
|
||||
|
@ -151,11 +161,11 @@ final class SessionManager {
|
|||
func logout() {
|
||||
let nc = NotificationCenter.default
|
||||
nc.post(name: Notification.Name("didSignOut"), object: nil)
|
||||
|
||||
dump(user)
|
||||
let keychain = KeychainSwift()
|
||||
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
||||
keychain.delete("AccessToken_\(user?.user_id ?? "")")
|
||||
generateAuthHeader(with: nil)
|
||||
generateAuthHeader(with: nil, deviceID: nil)
|
||||
|
||||
let deleteRequest = NSBatchDeleteRequest(objectIDs: [user.objectID])
|
||||
user = nil
|
||||
|
|
Loading…
Reference in New Issue