From 78a5660bed5896563be47661a427037bb0eab436 Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Thu, 3 Apr 2025 22:34:47 +0200 Subject: [PATCH 1/6] not great --- .../Components/VerticalVolumeSlider.swift | 137 ++++++++++++++++++ .../CustomPlayer/CustomPlayer.swift | 43 +++++- Sulfur.xcodeproj/project.pbxproj | 4 + 3 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalVolumeSlider.swift diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalVolumeSlider.swift b/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalVolumeSlider.swift new file mode 100644 index 0000000..3807967 --- /dev/null +++ b/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalVolumeSlider.swift @@ -0,0 +1,137 @@ +// +// VerticalVolumeSlider.swift +// Custom Seekbar +// +// Created by Pratik on 08/01/23. +// Modified to update screen brightness when used as a brightness slider. +// + +import SwiftUI + +struct VerticalVolumeSlider: View { + @Binding var value: T + let inRange: ClosedRange + let activeFillColor: Color + let fillColor: Color + let emptyColor: Color + let width: CGFloat + let onEditingChanged: (Bool) -> Void + + // private variables + @State private var localRealProgress: T = 0 + @State private var localTempProgress: T = 0 + @GestureState private var isActive: Bool = false + + var body: some View { + GeometryReader { bounds in + ZStack { + GeometryReader { geo in + ZStack(alignment: .bottom) { + RoundedRectangle(cornerRadius: isActive ? width : width/2, style: .continuous) + .fill(emptyColor) + RoundedRectangle(cornerRadius: isActive ? width : width/2, style: .continuous) + .fill(isActive ? activeFillColor : fillColor) + .mask({ + VStack { + Spacer(minLength: 0) + Rectangle() + .frame(height: max(geo.size.height * CGFloat((localRealProgress + localTempProgress)), 0), + alignment: .leading) + } + }) + + Image(systemName: getIconName) + .font(.system(size: 16, weight: .medium, design: .rounded)) + .foregroundColor(fillColor) + .animation(.spring(), value: localRealProgress) + .frame(maxHeight: .infinity, alignment: .bottom) + .padding(.bottom) + .overlay { + Image(systemName: getIconName) + .font(.system(size: 16, weight: .medium, design: .rounded)) + .foregroundColor(.gray) + .animation(.spring(), value: localRealProgress) + .frame(maxHeight: .infinity, alignment: .bottom) + .padding(.bottom) + .mask { + VStack { + Spacer(minLength: 0) + Rectangle() + .frame(height: max(geo.size.height * CGFloat((localRealProgress + localTempProgress)), 0), + alignment: .leading) + } + } + } + .frame(maxWidth: isActive ? .infinity : 0) + .opacity(isActive ? 1 : 0) + } + .clipped() + } + .frame(height: isActive ? bounds.size.height * 1.15 : bounds.size.height, alignment: .center) + // .shadow(color: .black.opacity(0.1), radius: isActive ? 20 : 0, x: 0, y: 0) + .animation(animation, value: isActive) + } + .frame(width: bounds.size.width, height: bounds.size.height, alignment: .center) + .gesture(DragGesture(minimumDistance: 0, coordinateSpace: .local) + .updating($isActive) { value, state, transaction in + state = true + } + .onChanged { gesture in + localTempProgress = T(-gesture.translation.height / bounds.size.height) + value = max(min(getPrgValue(), inRange.upperBound), inRange.lowerBound) + } + .onEnded { _ in + localRealProgress = max(min(localRealProgress + localTempProgress, 1), 0) + localTempProgress = 0 + } + ) + .onChange(of: isActive) { newValue in + value = max(min(getPrgValue(), inRange.upperBound), inRange.lowerBound) + onEditingChanged(newValue) + } + .onAppear { + localRealProgress = getPrgPercentage(value) + } + .onChange(of: value) { newValue in + if !isActive { + localRealProgress = getPrgPercentage(newValue) + } + } + } + .frame(width: isActive ? width * 4 : width, alignment: .center) + .offset(x: isActive ? -10 : 0) + .onChange(of: value) { newValue in + UIScreen.main.brightness = CGFloat(newValue) + } + } + + private var getIconName: String { + var name = "speaker.wave." + switch CGFloat((localRealProgress + localTempProgress)) { + case ..<0.01: + name = "speaker.slash.fill" + case ..<0.3: + name += "1.fill" + case ..<0.6: + name += "2.fill" + default: + name += "3.fill" + } + return name + } + + private var animation: Animation { + return .spring() + } + + private func getPrgPercentage(_ value: T) -> T { + let range = inRange.upperBound - inRange.lowerBound + let correctedStartValue = value - inRange.lowerBound + let percentage = correctedStartValue / range + return percentage + } + + private func getPrgValue() -> T { + return ((localRealProgress + localTempProgress) * (inRange.upperBound - inRange.lowerBound)) + inRange.lowerBound + } +} diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index c8c198f..c61a75f 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -40,8 +40,11 @@ class CustomMediaPlayerViewController: UIViewController { var currentTimeVal: Double = 0.0 var duration: Double = 0.0 var isVideoLoaded = false - var showWatchNextButton = true + var brightnessValue: Double = Double(UIScreen.main.brightness) + var brightnessSliderHostingController: UIHostingController>? + + var showWatchNextButton = true var watchNextButtonTimer: Timer? var isWatchNextRepositioned: Bool = false var isWatchNextVisible: Bool = false @@ -151,6 +154,7 @@ class CustomMediaPlayerViewController: UIViewController { loadSubtitleSettings() setupPlayerViewController() setupControls() + brightnessControl() setupSkipAndDismissGestures() addInvisibleControlOverlays() setupSubtitleLabel() @@ -406,6 +410,43 @@ class CustomMediaPlayerViewController: UIViewController { } + func brightnessControl() { + let brightnessSlider = VerticalVolumeSlider( + value: Binding( + get: { self.brightnessValue }, + set: { newValue in + self.brightnessValue = newValue + // No need to update UIScreen.main.brightness here since it's handled inside VerticalVolumeSlider. + } + ), + inRange: 0...1, + activeFillColor: .white, // Preserves original active color + fillColor: .white.opacity(0.5), // Preserves original fill color + emptyColor: .white.opacity(0.3), // Preserves original empty color + width: 15, // Keeps the original width and corner style + onEditingChanged: { editing in + // Optionally handle editing events here. + } + ) + + // Embed the slider in a UIHostingController. + brightnessSliderHostingController = UIHostingController(rootView: brightnessSlider) + guard let brightnessSliderView = brightnessSliderHostingController?.view else { return } + + brightnessSliderView.backgroundColor = .clear + brightnessSliderView.translatesAutoresizingMaskIntoConstraints = false + controlsContainerView.addSubview(brightnessSliderView) + + // Position the slider on the left side of the player while preserving its original appearance. + NSLayoutConstraint.activate([ + brightnessSliderView.leadingAnchor.constraint(equalTo: controlsContainerView.leadingAnchor, constant: -10), + brightnessSliderView.centerYAnchor.constraint(equalTo: controlsContainerView.centerYAnchor), + brightnessSliderView.widthAnchor.constraint(equalToConstant: 15), // Match the slider's defined width + brightnessSliderView.heightAnchor.constraint(equalToConstant: 170) // Adjust height to properly display the slider + ]) + + } + func addInvisibleControlOverlays() { let playPauseOverlay = UIButton(type: .custom) playPauseOverlay.backgroundColor = .clear diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index 7915612..6aae054 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ 13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD12D32D97400C1EBD7 /* CustomPlayer.swift */; }; 13EA2BD62D32D97400C1EBD7 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD32D32D97400C1EBD7 /* Double+Extension.swift */; }; 13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */; }; + 1E3F5EC82D9F16B7003F310F /* VerticalVolumeSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3F5EC72D9F16B7003F310F /* VerticalVolumeSlider.swift */; }; 1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */; }; 1EAC7A322D888BC50083984D /* MusicProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */; }; 73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */; }; @@ -123,6 +124,7 @@ 13EA2BD12D32D97400C1EBD7 /* CustomPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPlayer.swift; sourceTree = ""; }; 13EA2BD32D32D97400C1EBD7 /* Double+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NormalPlayer.swift; sourceTree = ""; }; + 1E3F5EC72D9F16B7003F310F /* VerticalVolumeSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalVolumeSlider.swift; sourceTree = ""; }; 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLoggerFilter.swift; sourceTree = ""; }; 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = ""; }; 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JavaScriptCore+Extensions.swift"; sourceTree = ""; }; @@ -440,6 +442,7 @@ 13EA2BD22D32D97400C1EBD7 /* Components */ = { isa = PBXGroup; children = ( + 1E3F5EC72D9F16B7003F310F /* VerticalVolumeSlider.swift */, 13EA2BD32D32D97400C1EBD7 /* Double+Extension.swift */, 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */, ); @@ -571,6 +574,7 @@ 133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */, 138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */, 1334FF4D2D786C93007E289F /* TMDB-Seasonal.swift in Sources */, + 1E3F5EC82D9F16B7003F310F /* VerticalVolumeSlider.swift in Sources */, 13DB468D2D90093A008CBC03 /* Anilist-Login.swift in Sources */, 73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */, 132AF1232D9995C300A0140B /* JSController-Details.swift in Sources */, From 85355336c9766c8470604463a30096473de44caf Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Thu, 3 Apr 2025 23:06:01 +0200 Subject: [PATCH 2/6] a beautiful brightness slider --- ...r.swift => VerticalBrightnessSlider.swift} | 43 ++++++++-------- .../CustomPlayer/CustomPlayer.swift | 49 +++++++++++++------ Sulfur.xcodeproj/project.pbxproj | 8 +-- 3 files changed, 58 insertions(+), 42 deletions(-) rename Sora/Utils/MediaPlayer/CustomPlayer/Components/{VerticalVolumeSlider.swift => VerticalBrightnessSlider.swift} (80%) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalVolumeSlider.swift b/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalBrightnessSlider.swift similarity index 80% rename from Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalVolumeSlider.swift rename to Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalBrightnessSlider.swift index 3807967..3e33aea 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalVolumeSlider.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalBrightnessSlider.swift @@ -1,6 +1,6 @@ // -// VerticalVolumeSlider.swift -// Custom Seekbar +// VerticalBrightnessSlider.swift +// Custom Brighness bar // // Created by Pratik on 08/01/23. // Modified to update screen brightness when used as a brightness slider. @@ -8,7 +8,7 @@ import SwiftUI -struct VerticalVolumeSlider: View { +struct VerticalBrightnessSlider: View { @Binding var value: T let inRange: ClosedRange let activeFillColor: Color @@ -41,16 +41,16 @@ struct VerticalVolumeSlider: View { }) Image(systemName: getIconName) - .font(.system(size: 16, weight: .medium, design: .rounded)) - .foregroundColor(fillColor) - .animation(.spring(), value: localRealProgress) + .font(.system(size: isActive ? 16 : 12, weight: .medium, design: .rounded)) + .foregroundColor(isActive ? fillColor : Color.white) // bright white when not active + .animation(.spring(), value: isActive) .frame(maxHeight: .infinity, alignment: .bottom) .padding(.bottom) .overlay { Image(systemName: getIconName) - .font(.system(size: 16, weight: .medium, design: .rounded)) - .foregroundColor(.gray) - .animation(.spring(), value: localRealProgress) + .font(.system(size: isActive ? 16 : 12, weight: .medium, design: .rounded)) + .foregroundColor(isActive ? Color.gray : Color.white.opacity(0.8)) + .animation(.spring(), value: isActive) .frame(maxHeight: .infinity, alignment: .bottom) .padding(.bottom) .mask { @@ -62,8 +62,8 @@ struct VerticalVolumeSlider: View { } } } - .frame(maxWidth: isActive ? .infinity : 0) - .opacity(isActive ? 1 : 0) + //.frame(maxWidth: isActive ? .infinity : 0) + // .opacity(isActive ? 1 : 0) } .clipped() } @@ -98,7 +98,7 @@ struct VerticalVolumeSlider: View { } } } - .frame(width: isActive ? width * 4 : width, alignment: .center) + .frame(width: isActive ? width * 2.2 : width, alignment: .center) .offset(x: isActive ? -10 : 0) .onChange(of: value) { newValue in UIScreen.main.brightness = CGFloat(newValue) @@ -106,18 +106,17 @@ struct VerticalVolumeSlider: View { } private var getIconName: String { - var name = "speaker.wave." - switch CGFloat((localRealProgress + localTempProgress)) { - case ..<0.01: - name = "speaker.slash.fill" - case ..<0.3: - name += "1.fill" - case ..<0.6: - name += "2.fill" + let brightnessLevel = CGFloat(localRealProgress + localTempProgress) + switch brightnessLevel { + case ..<0.2: + return "moon.fill" + case 0.2..<0.38: + return "sun.min" + case 0.38..<0.7: + return "sun.max" default: - name += "3.fill" + return "sun.max.fill" } - return name } private var animation: Animation { diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index c61a75f..13e6ecb 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -42,7 +42,7 @@ class CustomMediaPlayerViewController: UIViewController { var isVideoLoaded = false var brightnessValue: Double = Double(UIScreen.main.brightness) - var brightnessSliderHostingController: UIHostingController>? + var brightnessSliderHostingController: UIHostingController>? var showWatchNextButton = true var watchNextButtonTimer: Timer? @@ -411,40 +411,57 @@ class CustomMediaPlayerViewController: UIViewController { func brightnessControl() { - let brightnessSlider = VerticalVolumeSlider( + let brightnessSlider = VerticalBrightnessSlider( value: Binding( get: { self.brightnessValue }, set: { newValue in self.brightnessValue = newValue - // No need to update UIScreen.main.brightness here since it's handled inside VerticalVolumeSlider. + // UIScreen.main.brightness is updated inside VerticalVolumeSlider. } ), inRange: 0...1, activeFillColor: .white, // Preserves original active color fillColor: .white.opacity(0.5), // Preserves original fill color emptyColor: .white.opacity(0.3), // Preserves original empty color - width: 15, // Keeps the original width and corner style + width: 16, // Keeps the original width and corner style onEditingChanged: { editing in // Optionally handle editing events here. - } + } ) - // Embed the slider in a UIHostingController. - brightnessSliderHostingController = UIHostingController(rootView: brightnessSlider) - guard let brightnessSliderView = brightnessSliderHostingController?.view else { return } + // Create a container view for the brightness slider. + let brightnessContainer = UIView() + brightnessContainer.translatesAutoresizingMaskIntoConstraints = false + // Optionally, set backgroundColor to clear. + brightnessContainer.backgroundColor = .clear - brightnessSliderView.backgroundColor = .clear - brightnessSliderView.translatesAutoresizingMaskIntoConstraints = false - controlsContainerView.addSubview(brightnessSliderView) + // Add the container to the controls container. + controlsContainerView.addSubview(brightnessContainer) - // Position the slider on the left side of the player while preserving its original appearance. + // Constrain the container so that its bounds follow the slider's visual position. NSLayoutConstraint.activate([ - brightnessSliderView.leadingAnchor.constraint(equalTo: controlsContainerView.leadingAnchor, constant: -10), - brightnessSliderView.centerYAnchor.constraint(equalTo: controlsContainerView.centerYAnchor), - brightnessSliderView.widthAnchor.constraint(equalToConstant: 15), // Match the slider's defined width - brightnessSliderView.heightAnchor.constraint(equalToConstant: 170) // Adjust height to properly display the slider + brightnessContainer.leadingAnchor.constraint(equalTo: controlsContainerView.leadingAnchor, constant: 1.8), + brightnessContainer.centerYAnchor.constraint(equalTo: controlsContainerView.centerYAnchor, constant: -10), + brightnessContainer.widthAnchor.constraint(equalToConstant: 16), + brightnessContainer.heightAnchor.constraint(equalToConstant: 170) ]) + // Embed the brightness slider in a UIHostingController. + brightnessSliderHostingController = UIHostingController(rootView: brightnessSlider) + guard let brightnessSliderView = brightnessSliderHostingController?.view else { return } + brightnessSliderView.backgroundColor = .clear + brightnessSliderView.translatesAutoresizingMaskIntoConstraints = false + + // Add the slider view to the container. + brightnessContainer.addSubview(brightnessSliderView) + + // Constrain the slider view to the container’s bounds. + NSLayoutConstraint.activate([ + brightnessSliderView.topAnchor.constraint(equalTo: brightnessContainer.topAnchor), + brightnessSliderView.bottomAnchor.constraint(equalTo: brightnessContainer.bottomAnchor), + brightnessSliderView.leadingAnchor.constraint(equalTo: brightnessContainer.leadingAnchor), + brightnessSliderView.trailingAnchor.constraint(equalTo: brightnessContainer.trailingAnchor) + ]) } func addInvisibleControlOverlays() { diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index 6aae054..a0d0a49 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -62,7 +62,7 @@ 13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD12D32D97400C1EBD7 /* CustomPlayer.swift */; }; 13EA2BD62D32D97400C1EBD7 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD32D32D97400C1EBD7 /* Double+Extension.swift */; }; 13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */; }; - 1E3F5EC82D9F16B7003F310F /* VerticalVolumeSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3F5EC72D9F16B7003F310F /* VerticalVolumeSlider.swift */; }; + 1E3F5EC82D9F16B7003F310F /* VerticalBrightnessSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3F5EC72D9F16B7003F310F /* VerticalBrightnessSlider.swift */; }; 1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */; }; 1EAC7A322D888BC50083984D /* MusicProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */; }; 73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */; }; @@ -124,7 +124,7 @@ 13EA2BD12D32D97400C1EBD7 /* CustomPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPlayer.swift; sourceTree = ""; }; 13EA2BD32D32D97400C1EBD7 /* Double+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NormalPlayer.swift; sourceTree = ""; }; - 1E3F5EC72D9F16B7003F310F /* VerticalVolumeSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalVolumeSlider.swift; sourceTree = ""; }; + 1E3F5EC72D9F16B7003F310F /* VerticalBrightnessSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalBrightnessSlider.swift; sourceTree = ""; }; 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLoggerFilter.swift; sourceTree = ""; }; 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = ""; }; 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JavaScriptCore+Extensions.swift"; sourceTree = ""; }; @@ -442,7 +442,7 @@ 13EA2BD22D32D97400C1EBD7 /* Components */ = { isa = PBXGroup; children = ( - 1E3F5EC72D9F16B7003F310F /* VerticalVolumeSlider.swift */, + 1E3F5EC72D9F16B7003F310F /* VerticalBrightnessSlider.swift */, 13EA2BD32D32D97400C1EBD7 /* Double+Extension.swift */, 1EAC7A312D888BC50083984D /* MusicProgressSlider.swift */, ); @@ -574,7 +574,7 @@ 133D7C6E2D2BE2500075467E /* SoraApp.swift in Sources */, 138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */, 1334FF4D2D786C93007E289F /* TMDB-Seasonal.swift in Sources */, - 1E3F5EC82D9F16B7003F310F /* VerticalVolumeSlider.swift in Sources */, + 1E3F5EC82D9F16B7003F310F /* VerticalBrightnessSlider.swift in Sources */, 13DB468D2D90093A008CBC03 /* Anilist-Login.swift in Sources */, 73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */, 132AF1232D9995C300A0140B /* JSController-Details.swift in Sources */, From 441cb22abc81b469fdbd22f9964adf12ac9f1e1a Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Thu, 3 Apr 2025 23:19:57 +0200 Subject: [PATCH 3/6] mb done --- .../CustomPlayer/CustomPlayer.swift | 22 +++++++++++++++++++ .../SettingsSubViews/SettingsViewPlayer.swift | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 13e6ecb..2e1380f 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -44,6 +44,10 @@ class CustomMediaPlayerViewController: UIViewController { var brightnessValue: Double = Double(UIScreen.main.brightness) var brightnessSliderHostingController: UIHostingController>? + private var isHoldPauseEnabled: Bool { + UserDefaults.standard.bool(forKey: "holdForPauseEnabled") + } + var showWatchNextButton = true var watchNextButtonTimer: Timer? var isWatchNextRepositioned: Bool = false @@ -178,6 +182,10 @@ class CustomMediaPlayerViewController: UIViewController { self?.checkForHLSStream() } + if isHoldPauseEnabled { + holdForPause() + } + player.play() if let url = subtitlesURL, !url.isEmpty { @@ -409,6 +417,12 @@ class CustomMediaPlayerViewController: UIViewController { ]) } + func holdForPause() { + let holdForPauseGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleHoldForPause(_:))) + holdForPauseGesture.minimumPressDuration = 2.0 + holdForPauseGesture.numberOfTouchesRequired = 2 + view.addGestureRecognizer(holdForPauseGesture) + } func brightnessControl() { let brightnessSlider = VerticalBrightnessSlider( @@ -1024,6 +1038,14 @@ class CustomMediaPlayerViewController: UIViewController { player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) } + @objc private func handleHoldForPause(_ gesture: UILongPressGestureRecognizer) { + guard isHoldPauseEnabled else { return } + + if gesture.state == .began { + togglePlayPause() + } + } + func speedChangerMenu() -> UIMenu { let speeds: [Double] = [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0] let playbackSpeedActions = speeds.map { speed in diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift index 2792468..1406013 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift @@ -15,6 +15,7 @@ struct SettingsViewPlayer: View { @AppStorage("holdSpeedPlayer") private var holdSpeedPlayer: Double = 2.0 @AppStorage("skipIncrement") private var skipIncrement: Double = 10.0 @AppStorage("skipIncrementHold") private var skipIncrementHold: Double = 30.0 + @AppStorage("holdForPauseEnabled") private var holdForPauseEnabled = false private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "Sora"] @@ -40,6 +41,9 @@ struct SettingsViewPlayer: View { Toggle("Force Landscape", isOn: $isAlwaysLandscape) .tint(.accentColor) + + Toggle("Two finger hold for pause",isOn: $holdForPauseEnabled) + .tint(.accentColor) } Section(header: Text("Speed Settings")) { From 0f3ed42340ce9ed7a77dbdf9bad54c4cb4feaca2 Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Thu, 3 Apr 2025 23:42:44 +0200 Subject: [PATCH 4/6] added brightness bar and check desc. https://discord.com/channels/1293430817841741899/1357429131054289118/1357442581784559847 --- .../Components/VerticalBrightnessSlider.swift | 2 +- .../CustomPlayer/CustomPlayer.swift | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalBrightnessSlider.swift b/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalBrightnessSlider.swift index 3e33aea..7bae219 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalBrightnessSlider.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalBrightnessSlider.swift @@ -98,7 +98,7 @@ struct VerticalBrightnessSlider: View { } } } - .frame(width: isActive ? width * 2.2 : width, alignment: .center) + .frame(width: isActive ? width * 1.9 : width, alignment: .center) .offset(x: isActive ? -10 : 0) .onChange(of: value) { newValue in UIScreen.main.brightness = CGFloat(newValue) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 2e1380f..6cc7b08 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -419,7 +419,7 @@ class CustomMediaPlayerViewController: UIViewController { func holdForPause() { let holdForPauseGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleHoldForPause(_:))) - holdForPauseGesture.minimumPressDuration = 2.0 + holdForPauseGesture.minimumPressDuration = 1 holdForPauseGesture.numberOfTouchesRequired = 2 view.addGestureRecognizer(holdForPauseGesture) } @@ -437,7 +437,7 @@ class CustomMediaPlayerViewController: UIViewController { activeFillColor: .white, // Preserves original active color fillColor: .white.opacity(0.5), // Preserves original fill color emptyColor: .white.opacity(0.3), // Preserves original empty color - width: 16, // Keeps the original width and corner style + width: 22, // Keeps the original width and corner style onEditingChanged: { editing in // Optionally handle editing events here. } @@ -454,9 +454,9 @@ class CustomMediaPlayerViewController: UIViewController { // Constrain the container so that its bounds follow the slider's visual position. NSLayoutConstraint.activate([ - brightnessContainer.leadingAnchor.constraint(equalTo: controlsContainerView.leadingAnchor, constant: 1.8), + brightnessContainer.leadingAnchor.constraint(equalTo: controlsContainerView.leadingAnchor, constant: -4), brightnessContainer.centerYAnchor.constraint(equalTo: controlsContainerView.centerYAnchor, constant: -10), - brightnessContainer.widthAnchor.constraint(equalToConstant: 16), + brightnessContainer.widthAnchor.constraint(equalToConstant: 22), brightnessContainer.heightAnchor.constraint(equalToConstant: 170) ]) @@ -505,9 +505,8 @@ class CustomMediaPlayerViewController: UIViewController { } } - let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeDown(_:))) - swipeDownGesture.direction = .down - view.addGestureRecognizer(swipeDownGesture) + let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) + view.addGestureRecognizer(panGesture) } func showSkipFeedback(direction: String) { @@ -1465,6 +1464,19 @@ class CustomMediaPlayerViewController: UIViewController { } } + @objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) { + let translation = gesture.translation(in: view) + + switch gesture.state { + case .ended: + if translation.y > 100 { + dismiss(animated: true, completion: nil) + } + default: + break + } + } + private func beginHoldSpeed() { guard let player = player else { return } originalRate = player.rate From 2ead0dc6d48d4ab1294698a1a30ca603457d0f48 Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Thu, 3 Apr 2025 23:44:54 +0200 Subject: [PATCH 5/6] rem dev notes --- .../Components/VerticalBrightnessSlider.swift | 2 +- .../MediaPlayer/CustomPlayer/CustomPlayer.swift | 17 ++++------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalBrightnessSlider.swift b/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalBrightnessSlider.swift index 7bae219..1da58ea 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalBrightnessSlider.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/Components/VerticalBrightnessSlider.swift @@ -42,7 +42,7 @@ struct VerticalBrightnessSlider: View { Image(systemName: getIconName) .font(.system(size: isActive ? 16 : 12, weight: .medium, design: .rounded)) - .foregroundColor(isActive ? fillColor : Color.white) // bright white when not active + .foregroundColor(isActive ? fillColor : Color.white) .animation(.spring(), value: isActive) .frame(maxHeight: .infinity, alignment: .bottom) .padding(.bottom) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 6cc7b08..9aa362c 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -430,29 +430,23 @@ class CustomMediaPlayerViewController: UIViewController { get: { self.brightnessValue }, set: { newValue in self.brightnessValue = newValue - // UIScreen.main.brightness is updated inside VerticalVolumeSlider. } ), inRange: 0...1, - activeFillColor: .white, // Preserves original active color - fillColor: .white.opacity(0.5), // Preserves original fill color - emptyColor: .white.opacity(0.3), // Preserves original empty color - width: 22, // Keeps the original width and corner style + activeFillColor: .white, + fillColor: .white.opacity(0.5), + emptyColor: .white.opacity(0.3), + width: 22, onEditingChanged: { editing in - // Optionally handle editing events here. } ) - // Create a container view for the brightness slider. let brightnessContainer = UIView() brightnessContainer.translatesAutoresizingMaskIntoConstraints = false - // Optionally, set backgroundColor to clear. brightnessContainer.backgroundColor = .clear - // Add the container to the controls container. controlsContainerView.addSubview(brightnessContainer) - // Constrain the container so that its bounds follow the slider's visual position. NSLayoutConstraint.activate([ brightnessContainer.leadingAnchor.constraint(equalTo: controlsContainerView.leadingAnchor, constant: -4), brightnessContainer.centerYAnchor.constraint(equalTo: controlsContainerView.centerYAnchor, constant: -10), @@ -460,16 +454,13 @@ class CustomMediaPlayerViewController: UIViewController { brightnessContainer.heightAnchor.constraint(equalToConstant: 170) ]) - // Embed the brightness slider in a UIHostingController. brightnessSliderHostingController = UIHostingController(rootView: brightnessSlider) guard let brightnessSliderView = brightnessSliderHostingController?.view else { return } brightnessSliderView.backgroundColor = .clear brightnessSliderView.translatesAutoresizingMaskIntoConstraints = false - // Add the slider view to the container. brightnessContainer.addSubview(brightnessSliderView) - // Constrain the slider view to the container’s bounds. NSLayoutConstraint.activate([ brightnessSliderView.topAnchor.constraint(equalTo: brightnessContainer.topAnchor), brightnessSliderView.bottomAnchor.constraint(equalTo: brightnessContainer.bottomAnchor), From 6c43d792b2881f0f5c181e28bbc632688c28c072 Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Thu, 3 Apr 2025 23:52:40 +0200 Subject: [PATCH 6/6] fixed buffer bar not updating fixed = sometimes when stream is opened from continue watching section the bar doesnt update --- Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift | 4 +++- .../SettingsView/SettingsSubViews/SettingsViewPlayer.swift | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 9aa362c..5530058 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -141,7 +141,9 @@ class CustomMediaPlayerViewController: UIViewController { let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(fullUrl)") if lastPlayedTime > 0 { let seekTime = CMTime(seconds: lastPlayedTime, preferredTimescale: 1) - self.player.seek(to: seekTime) + self.player.seek(to: seekTime) { [weak self] _ in + self?.updateBufferValue() + } } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift index 1406013..53c1ce9 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift @@ -42,7 +42,7 @@ struct SettingsViewPlayer: View { Toggle("Force Landscape", isOn: $isAlwaysLandscape) .tint(.accentColor) - Toggle("Two finger hold for pause",isOn: $holdForPauseEnabled) + Toggle("Two Finger Hold for Pause",isOn: $holdForPauseEnabled) .tint(.accentColor) }