diff --git a/Shared/Extensions/ViewExtensions/Modifiers/ScenePhaseChangeModifier.swift b/Shared/Extensions/ViewExtensions/Modifiers/ScenePhaseChangeModifier.swift new file mode 100644 index 000000000..0be392a1e --- /dev/null +++ b/Shared/Extensions/ViewExtensions/Modifiers/ScenePhaseChangeModifier.swift @@ -0,0 +1,26 @@ +// +// 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 (c) 2023 Jellyfin & Jellyfin Contributors +// + +import SwiftUI + +struct ScenePhaseChangeModifier: ViewModifier { + + @Environment(\.scenePhase) + private var scenePhase + + let phase: ScenePhase + let action: () -> Void + + func body(content: Content) -> some View { + content.onChange(of: scenePhase) { newValue in + if newValue == phase { + action() + } + } + } +} diff --git a/Shared/Extensions/ViewExtensions/ViewExtensions.swift b/Shared/Extensions/ViewExtensions/ViewExtensions.swift index 397b6c21a..2e79f53de 100644 --- a/Shared/Extensions/ViewExtensions/ViewExtensions.swift +++ b/Shared/Extensions/ViewExtensions/ViewExtensions.swift @@ -218,4 +218,8 @@ extension View { .ignoresSafeArea() } } + + func onScenePhase(_ phase: ScenePhase, _ action: @escaping () -> Void) -> some View { + modifier(ScenePhaseChangeModifier(phase: phase, action: action)) + } } diff --git a/Shared/Services/SwiftfinDefaults.swift b/Shared/Services/SwiftfinDefaults.swift index 8eb446a12..6d08af249 100644 --- a/Shared/Services/SwiftfinDefaults.swift +++ b/Shared/Services/SwiftfinDefaults.swift @@ -194,6 +194,11 @@ extension Defaults.Keys { ) static let subtitleSize: Key = .init("subtitleSize", default: 16, suite: .generalSuite) } + + enum Transition { + static let pauseOnBackground: Key = .init("pauseOnBackground", default: false, suite: .generalSuite) + static let playOnActive: Key = .init("playOnActive", default: false, suite: .generalSuite) + } } // Experimental settings diff --git a/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView.swift b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView.swift index affd878bf..f495fec59 100644 --- a/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView.swift +++ b/Swiftfin tvOS/Views/SettingsView/VideoPlayerSettingsView.swift @@ -21,6 +21,11 @@ struct VideoPlayerSettingsView: View { @Default(.VideoPlayer.resumeOffset) private var resumeOffset + @Default(.VideoPlayer.Transition.pauseOnBackground) + private var pauseOnBackground + @Default(.VideoPlayer.Transition.playOnActive) + private var playOnActive + @EnvironmentObject private var router: VideoPlayerSettingsCoordinator.Router @@ -57,6 +62,12 @@ struct VideoPlayerSettingsView: View { } footer: { Text("Settings only affect some subtitle types") } + + Section { + + Toggle("Pause on background", isOn: $pauseOnBackground) + Toggle("Play on active", isOn: $playOnActive) + } } .navigationTitle("Video Player") .blurFullScreenCover(isPresented: $isPresentingResumeOffsetStepper) { diff --git a/Swiftfin tvOS/Views/VideoPlayer/NativeVideoPlayer.swift b/Swiftfin tvOS/Views/VideoPlayer/NativeVideoPlayer.swift index 035ad4732..b03f8c93e 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/NativeVideoPlayer.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/NativeVideoPlayer.swift @@ -14,6 +14,9 @@ import SwiftUI struct NativeVideoPlayer: View { + @Environment(\.scenePhase) + var scenePhase + @EnvironmentObject private var router: VideoPlayerCoordinator.Router diff --git a/Swiftfin tvOS/Views/VideoPlayer/VideoPlayer.swift b/Swiftfin tvOS/Views/VideoPlayer/VideoPlayer.swift index 50914b205..941601307 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/VideoPlayer.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/VideoPlayer.swift @@ -19,6 +19,9 @@ struct VideoPlayer: View { case smallMenu } + @Environment(\.scenePhase) + private var scenePhase + @EnvironmentObject private var router: VideoPlayerCoordinator.Router @@ -100,6 +103,16 @@ struct VideoPlayer: View { guard !newValue else { return } videoPlayerManager.proxy.setTime(.seconds(currentProgressHandler.scrubbedSeconds)) } + .onScenePhase(.active) { + if Defaults[.VideoPlayer.Transition.playOnActive] { + videoPlayerManager.proxy.play() + } + } + .onScenePhase(.background) { + if Defaults[.VideoPlayer.Transition.pauseOnBackground] { + videoPlayerManager.proxy.pause() + } + } } } diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index e7f555159..cfe46610c 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -727,6 +727,8 @@ E1FE69A728C29B720021BC93 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FE69A628C29B720021BC93 /* ProgressBar.swift */; }; E1FE69A828C29B720021BC93 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FE69A628C29B720021BC93 /* ProgressBar.swift */; }; E1FE69AA28C29CC20021BC93 /* LandscapePosterProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FE69A928C29CC20021BC93 /* LandscapePosterProgressBar.swift */; }; + E43918662AD5C8310045A18C /* ScenePhaseChangeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43918652AD5C8310045A18C /* ScenePhaseChangeModifier.swift */; }; + E43918672AD5C8310045A18C /* ScenePhaseChangeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43918652AD5C8310045A18C /* ScenePhaseChangeModifier.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -1277,6 +1279,7 @@ E1FCD09526C47118007C8DCF /* ErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessage.swift; sourceTree = ""; }; E1FE69A628C29B720021BC93 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; E1FE69A928C29CC20021BC93 /* LandscapePosterProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandscapePosterProgressBar.swift; sourceTree = ""; }; + E43918652AD5C8310045A18C /* ScenePhaseChangeModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScenePhaseChangeModifier.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2244,6 +2247,7 @@ E19E551E2897326C003CE330 /* BottomEdgeGradientModifier.swift */, E129428428F080B500796AC6 /* OnReceiveNotificationModifier.swift */, E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */, + E43918652AD5C8310045A18C /* ScenePhaseChangeModifier.swift */, ); path = Modifiers; sourceTree = ""; @@ -3173,6 +3177,7 @@ E193D53927193F8E00900D82 /* SearchCoordinator.swift in Sources */, C4BE078C272844AF003F4AD1 /* LiveTVChannelsView.swift in Sources */, E148128928C154BF003B8787 /* ItemFilter.swift in Sources */, + E43918672AD5C8310045A18C /* ScenePhaseChangeModifier.swift in Sources */, E154966F296CA2EF00C4EF88 /* LogManager.swift in Sources */, E1575E75293E77B5001665B1 /* LibraryViewType.swift in Sources */, E193D53427193F7F00900D82 /* HomeCoordinator.swift in Sources */, @@ -3513,6 +3518,7 @@ E1C8CE7C28FF015000DF5D7B /* TrailingTimestampType.swift in Sources */, E1FE69A728C29B720021BC93 /* ProgressBar.swift in Sources */, E13332912953B91000EE76AB /* DownloadTaskCoordinator.swift in Sources */, + E43918662AD5C8310045A18C /* ScenePhaseChangeModifier.swift in Sources */, E1B33ED128EB860A0073B0FD /* LargePlaybackButtons.swift in Sources */, E1549664296CA2EF00C4EF88 /* SwiftfinStore.swift in Sources */, E113133228BDC72000930F75 /* FilterView.swift in Sources */, diff --git a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift index 3e4de4c62..1e6323c08 100644 --- a/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift +++ b/Swiftfin/Views/SettingsView/VideoPlayerSettingsView/VideoPlayerSettingsView.swift @@ -54,6 +54,11 @@ struct VideoPlayerSettingsView: View { @Default(.VideoPlayer.Overlay.timestampType) private var timestampType + @Default(.VideoPlayer.Transition.pauseOnBackground) + private var pauseOnBackground + @Default(.VideoPlayer.Transition.playOnActive) + private var playOnActive + @EnvironmentObject private var router: VideoPlayerSettingsCoordinator.Router @@ -149,6 +154,12 @@ struct VideoPlayerSettingsView: View { EnumPicker(title: "Trailing Value", selection: $trailingTimestampType) } + + Section("Transition") { + + Toggle("Pause on background", isOn: $pauseOnBackground) + Toggle("Play on active", isOn: $playOnActive) + } } .navigationTitle("Video Player") .onChange(of: barActionButtons) { newValue in diff --git a/Swiftfin/Views/VideoPlayer/NativeVideoPlayer.swift b/Swiftfin/Views/VideoPlayer/NativeVideoPlayer.swift index 0a3b39495..4a044f0de 100644 --- a/Swiftfin/Views/VideoPlayer/NativeVideoPlayer.swift +++ b/Swiftfin/Views/VideoPlayer/NativeVideoPlayer.swift @@ -14,6 +14,9 @@ import SwiftUI struct NativeVideoPlayer: View { + @Environment(\.scenePhase) + var scenePhase + @EnvironmentObject private var router: VideoPlayerCoordinator.Router diff --git a/Swiftfin/Views/VideoPlayer/VideoPlayer.swift b/Swiftfin/Views/VideoPlayer/VideoPlayer.swift index 44cabcc33..79dc3590e 100644 --- a/Swiftfin/Views/VideoPlayer/VideoPlayer.swift +++ b/Swiftfin/Views/VideoPlayer/VideoPlayer.swift @@ -24,6 +24,9 @@ struct VideoPlayer: View { case chapters } + @Environment(\.scenePhase) + private var scenePhase + class GestureStateHandler { var beganPanWithOverlay: Bool = false @@ -241,6 +244,16 @@ struct VideoPlayer: View { audioOffset = 0 subtitleOffset = 0 } + .onScenePhase(.active) { + if Defaults[.VideoPlayer.Transition.playOnActive] { + videoPlayerManager.proxy.play() + } + } + .onScenePhase(.background) { + if Defaults[.VideoPlayer.Transition.pauseOnBackground] { + videoPlayerManager.proxy.pause() + } + } } }