diff --git a/Sora/Utils/Extensions/finTopView.swift b/Sora/Utils/Extensions/finTopView.swift new file mode 100644 index 0000000..adcdfd7 --- /dev/null +++ b/Sora/Utils/Extensions/finTopView.swift @@ -0,0 +1,27 @@ +// +// finTopView.swift +// Sulfur +// +// Created by Francesco on 04/03/25. +// + +import UIKit + +class findTopViewController { + static func findViewController(_ viewController: UIViewController) -> UIViewController { + if let presented = viewController.presentedViewController { + return findViewController(presented) + } + + if let navigationController = viewController as? UINavigationController { + return findViewController(navigationController.visibleViewController ?? navigationController) + } + + if let tabBarController = viewController as? UITabBarController, + let selected = tabBarController.selectedViewController { + return findViewController(selected) + } + + return viewController + } +} diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 038499e..e3c8713 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -57,6 +57,10 @@ class CustomMediaPlayerViewController: UIViewController { var sliderViewModel = SliderViewModel() var isSliderEditing = false + var watchNextButtonNormalConstraints: [NSLayoutConstraint] = [] + var watchNextButtonControlsConstraints: [NSLayoutConstraint] = [] + var isControlsVisible = false + init(module: ScrapingModule, urlString: String, fullUrl: String, @@ -106,8 +110,9 @@ class CustomMediaPlayerViewController: UIViewController { setupControls() setupSubtitleLabel() setupDismissButton() - setupSpeedButton() setupMenuButton() + setupSpeedButton() + setupWatchNextButton() addTimeObserver() startUpdateTimer() setupAudioSession() @@ -244,17 +249,6 @@ class CustomMediaPlayerViewController: UIViewController { sliderHostView.heightAnchor.constraint(equalToConstant: 30) ]) - watchNextButton = UIButton(type: .system) - watchNextButton.setTitle("Watch Next", for: .normal) - watchNextButton.setImage(UIImage(systemName: "forward.fill"), for: .normal) - watchNextButton.backgroundColor = .white - watchNextButton.layer.cornerRadius = 16 - watchNextButton.setTitleColor(.black, for: .normal) - watchNextButton.addTarget(self, action: #selector(watchNextTapped), for: .touchUpInside) - watchNextButton.isHidden = true - controlsContainerView.addSubview(watchNextButton) - watchNextButton.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ playPauseButton.centerXAnchor.constraint(equalTo: controlsContainerView.centerXAnchor), playPauseButton.centerYAnchor.constraint(equalTo: controlsContainerView.centerYAnchor), @@ -269,12 +263,7 @@ class CustomMediaPlayerViewController: UIViewController { forwardButton.centerYAnchor.constraint(equalTo: playPauseButton.centerYAnchor), forwardButton.leadingAnchor.constraint(equalTo: playPauseButton.trailingAnchor, constant: 30), forwardButton.widthAnchor.constraint(equalToConstant: 40), - forwardButton.heightAnchor.constraint(equalToConstant: 40), - - watchNextButton.trailingAnchor.constraint(equalTo: controlsContainerView.trailingAnchor, constant: -10), - watchNextButton.bottomAnchor.constraint(equalTo: controlsContainerView.bottomAnchor, constant: -80), - watchNextButton.heightAnchor.constraint(equalToConstant: 50), - watchNextButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 120) + forwardButton.heightAnchor.constraint(equalToConstant: 40) ]) } @@ -288,9 +277,9 @@ class CustomMediaPlayerViewController: UIViewController { subtitleLabel.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ subtitleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), - subtitleLabel.bottomAnchor.constraint(equalTo: sliderHostingController?.view.topAnchor ?? view.safeAreaLayoutGuide.bottomAnchor), - subtitleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 20), - subtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -20) + subtitleLabel.bottomAnchor.constraint(equalTo: sliderHostingController?.view.bottomAnchor ?? view.safeAreaLayoutGuide.bottomAnchor), + subtitleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 36), + subtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -36) ]) } @@ -323,9 +312,10 @@ class CustomMediaPlayerViewController: UIViewController { controlsContainerView.addSubview(menuButton) menuButton.translatesAutoresizingMaskIntoConstraints = false + guard let sliderView = sliderHostingController?.view else { return } NSLayoutConstraint.activate([ - menuButton.bottomAnchor.constraint(equalTo: controlsContainerView.bottomAnchor, constant: -50), - menuButton.trailingAnchor.constraint(equalTo: speedButton.leadingAnchor), + menuButton.bottomAnchor.constraint(equalTo: sliderView.topAnchor), + menuButton.trailingAnchor.constraint(equalTo: sliderView.trailingAnchor), menuButton.widthAnchor.constraint(equalToConstant: 40), menuButton.heightAnchor.constraint(equalToConstant: 40) ]) @@ -341,15 +331,46 @@ class CustomMediaPlayerViewController: UIViewController { controlsContainerView.addSubview(speedButton) speedButton.translatesAutoresizingMaskIntoConstraints = false - guard let sliderView = sliderHostingController?.view else { return } NSLayoutConstraint.activate([ - speedButton.bottomAnchor.constraint(equalTo: sliderView.topAnchor), - speedButton.trailingAnchor.constraint(equalTo: sliderView.trailingAnchor), + speedButton.bottomAnchor.constraint(equalTo: controlsContainerView.bottomAnchor, constant: -50), + speedButton.trailingAnchor.constraint(equalTo: menuButton.leadingAnchor), speedButton.widthAnchor.constraint(equalToConstant: 40), speedButton.heightAnchor.constraint(equalToConstant: 40) ]) } + func setupWatchNextButton() { + watchNextButton = UIButton(type: .system) + watchNextButton.setTitle("Watch Next", for: .normal) + watchNextButton.setImage(UIImage(systemName: "forward.fill"), for: .normal) + watchNextButton.tintColor = .black + watchNextButton.backgroundColor = .white + watchNextButton.layer.cornerRadius = 25 + watchNextButton.setTitleColor(.black, for: .normal) + watchNextButton.addTarget(self, action: #selector(watchNextTapped), for: .touchUpInside) + watchNextButton.isHidden = true + watchNextButton.alpha = 0.8 + + view.addSubview(watchNextButton) + watchNextButton.translatesAutoresizingMaskIntoConstraints = false + + watchNextButtonNormalConstraints = [ + watchNextButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + watchNextButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -40), + watchNextButton.heightAnchor.constraint(equalToConstant: 50), + watchNextButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 120) + ] + + watchNextButtonControlsConstraints = [ + watchNextButton.trailingAnchor.constraint(equalTo: speedButton.leadingAnchor), + watchNextButton.bottomAnchor.constraint(equalTo: speedButton.bottomAnchor, constant: -5), + watchNextButton.heightAnchor.constraint(equalToConstant: 50), + watchNextButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 120) + ] + + NSLayoutConstraint.activate(watchNextButtonControlsConstraints) + } + func updateSubtitleLabelAppearance() { subtitleLabel.font = UIFont.systemFont(ofSize: CGFloat(subtitleFontSize)) subtitleLabel.textColor = subtitleUIColor() @@ -405,7 +426,6 @@ class CustomMediaPlayerViewController: UIViewController { && self.duration != 0 { if UserDefaults.standard.bool(forKey: "hideNextButton") { - self.watchNextButton.isHidden = false DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { self.watchNextButton.isHidden = true } @@ -444,8 +464,22 @@ class CustomMediaPlayerViewController: UIViewController { } @objc func toggleControls() { + isControlsVisible.toggle() + UIView.animate(withDuration: 0.2) { - self.controlsContainerView.alpha = self.controlsContainerView.alpha == 0 ? 1 : 0 + self.controlsContainerView.alpha = self.isControlsVisible ? 1 : 0 + + if self.isControlsVisible { + NSLayoutConstraint.deactivate(self.watchNextButtonNormalConstraints) + NSLayoutConstraint.activate(self.watchNextButtonControlsConstraints) + self.watchNextButton.alpha = 1.0 + } else { + NSLayoutConstraint.deactivate(self.watchNextButtonControlsConstraints) + NSLayoutConstraint.activate(self.watchNextButtonNormalConstraints) + self.watchNextButton.alpha = 0.8 + } + + self.view.layoutIfNeeded() } } diff --git a/Sora/Utils/MediaPlayer/VideoPlayer.swift b/Sora/Utils/MediaPlayer/VideoPlayer.swift index aede1b0..77ab5aa 100644 --- a/Sora/Utils/MediaPlayer/VideoPlayer.swift +++ b/Sora/Utils/MediaPlayer/VideoPlayer.swift @@ -148,4 +148,11 @@ class VideoPlayerViewController: UIViewController { override var prefersStatusBarHidden: Bool { return true } + + deinit { + player?.pause() + if let timeObserverToken = timeObserverToken { + player?.removeTimeObserver(timeObserverToken) + } + } } diff --git a/Sora/Views/HomeView.swift b/Sora/Views/HomeView.swift index b3a57eb..c21fa0c 100644 --- a/Sora/Views/HomeView.swift +++ b/Sora/Views/HomeView.swift @@ -66,7 +66,7 @@ struct HomeView: View { if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let rootVC = windowScene.windows.first?.rootViewController { - rootVC.present(customMediaPlayer, animated: true, completion: nil) + findTopViewController.findViewController(rootVC).present(customMediaPlayer, animated: true, completion: nil) } } else { let videoPlayerViewController = VideoPlayerViewController(module: item.module) @@ -80,7 +80,7 @@ struct HomeView: View { if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let rootVC = windowScene.windows.first?.rootViewController { - rootVC.present(videoPlayerViewController, animated: true, completion: nil) + findTopViewController.findViewController(rootVC).present(videoPlayerViewController, animated: true, completion: nil) } } }) { diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 646847e..24ac21a 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -519,7 +519,7 @@ struct MediaInfoView: View { if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let rootVC = windowScene.windows.first?.rootViewController { - rootVC.present(customMediaPlayer, animated: true, completion: nil) + findTopViewController.findViewController(rootVC).present(customMediaPlayer, animated: true, completion: nil) } return default: @@ -541,7 +541,7 @@ struct MediaInfoView: View { if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let rootVC = windowScene.windows.first?.rootViewController { - rootVC.present(videoPlayerViewController, animated: true, completion: nil) + findTopViewController.findViewController(rootVC).present(videoPlayerViewController, animated: true, completion: nil) } } } diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index d1a9dc8..c680885 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -31,6 +31,9 @@ 133D7C942D2BE2640075467E /* JSController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D7C8B2D2BE2640075467E /* JSController.swift */; }; 133D7C972D2BE2AF0075467E /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 133D7C962D2BE2AF0075467E /* Kingfisher */; }; 133F55BB2D33B55100E08EEA /* LibraryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133F55BA2D33B55100E08EEA /* LibraryManager.swift */; }; + 1359ED142D76F49900C13034 /* finTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1359ED132D76F49900C13034 /* finTopView.swift */; }; + 1359ED172D76FA6500C13034 /* FFmpeg-iOS-Lame in Frameworks */ = {isa = PBXBuildFile; productRef = 1359ED162D76FA6500C13034 /* FFmpeg-iOS-Lame */; }; + 1359ED1A2D76FA7D00C13034 /* Drops in Frameworks */ = {isa = PBXBuildFile; productRef = 1359ED192D76FA7D00C13034 /* Drops */; }; 135CCBE22D4D1138008B9C0E /* SettingsViewPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */; }; 136F21B92D5B8DD8006409AC /* AniList-MediaInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136F21B82D5B8DD8006409AC /* AniList-MediaInfo.swift */; }; 136F21BC2D5B8F29006409AC /* AniList-DetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136F21BB2D5B8F29006409AC /* AniList-DetailsView.swift */; }; @@ -44,7 +47,6 @@ 13C0E5EC2D5F85F800E7F619 /* ContinueWatchingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C0E5EB2D5F85F800E7F619 /* ContinueWatchingItem.swift */; }; 13CBA0882D60F19C00EFE70A /* VTTSubtitlesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CBA0872D60F19C00EFE70A /* VTTSubtitlesLoader.swift */; }; 13CBEFDA2D5F7D1200D011EE /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CBEFD92D5F7D1200D011EE /* String.swift */; }; - 13D842522D4523B800EBBFA6 /* Drops in Frameworks */ = {isa = PBXBuildFile; productRef = 13D842512D4523B800EBBFA6 /* Drops */; }; 13D842552D45267500EBBFA6 /* DropManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13D842542D45267500EBBFA6 /* DropManager.swift */; }; 13D99CF72D4E73C300250A86 /* ModuleAdditionSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13D99CF62D4E73C300250A86 /* ModuleAdditionSettingsView.swift */; }; 13DC0C462D302C7500D0F966 /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DC0C452D302C7500D0F966 /* VideoPlayer.swift */; }; @@ -81,6 +83,7 @@ 133D7C892D2BE2640075467E /* Modules.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Modules.swift; sourceTree = ""; }; 133D7C8B2D2BE2640075467E /* JSController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSController.swift; sourceTree = ""; }; 133F55BA2D33B55100E08EEA /* LibraryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryManager.swift; sourceTree = ""; }; + 1359ED132D76F49900C13034 /* finTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = finTopView.swift; sourceTree = ""; }; 135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewPlayer.swift; sourceTree = ""; }; 136F21B82D5B8DD8006409AC /* AniList-MediaInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AniList-MediaInfo.swift"; sourceTree = ""; }; 136F21BB2D5B8F29006409AC /* AniList-DetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AniList-DetailsView.swift"; sourceTree = ""; }; @@ -110,7 +113,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 13D842522D4523B800EBBFA6 /* Drops in Frameworks */, + 1359ED1A2D76FA7D00C13034 /* Drops in Frameworks */, + 1359ED172D76FA6500C13034 /* FFmpeg-iOS-Lame in Frameworks */, 133D7C972D2BE2AF0075467E /* Kingfisher in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -266,6 +270,7 @@ children = ( 1327FBA82D758DEA00FC6689 /* UIDevice+Model.swift */, 133D7C872D2BE2640075467E /* URLSession.swift */, + 1359ED132D76F49900C13034 /* finTopView.swift */, 13CBEFD92D5F7D1200D011EE /* String.swift */, 13103E8A2D58E028000F0673 /* View.swift */, ); @@ -405,7 +410,8 @@ name = Sulfur; packageProductDependencies = ( 133D7C962D2BE2AF0075467E /* Kingfisher */, - 13D842512D4523B800EBBFA6 /* Drops */, + 1359ED162D76FA6500C13034 /* FFmpeg-iOS-Lame */, + 1359ED192D76FA7D00C13034 /* Drops */, ); productName = Sora; productReference = 133D7C6A2D2BE2500075467E /* Sulfur.app */; @@ -436,7 +442,8 @@ mainGroup = 133D7C612D2BE2500075467E; packageReferences = ( 133D7C952D2BE2AF0075467E /* XCRemoteSwiftPackageReference "Kingfisher" */, - 13D842502D4523B800EBBFA6 /* XCRemoteSwiftPackageReference "Drops" */, + 1359ED152D76FA6500C13034 /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */, + 1359ED182D76FA7D00C13034 /* XCRemoteSwiftPackageReference "Drops" */, ); productRefGroup = 133D7C6B2D2BE2500075467E /* Products */; projectDirPath = ""; @@ -467,6 +474,7 @@ 135CCBE22D4D1138008B9C0E /* SettingsViewPlayer.swift in Sources */, 1327FBA72D758CEA00FC6689 /* Analytics.swift in Sources */, 13DC0C462D302C7500D0F966 /* VideoPlayer.swift in Sources */, + 1359ED142D76F49900C13034 /* finTopView.swift in Sources */, 1399FAD62D3AB3DB00E97C31 /* Logger.swift in Sources */, 13B7F4C12D58FFDD0045714A /* Shimmer.swift in Sources */, 139935662D468C450065CEFF /* ModuleManager.swift in Sources */, @@ -646,6 +654,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Sora/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Sora; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -686,6 +695,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Sora/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Sora; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -742,12 +752,20 @@ version = 7.9.1; }; }; - 13D842502D4523B800EBBFA6 /* XCRemoteSwiftPackageReference "Drops" */ = { + 1359ED152D76FA6500C13034 /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kewlbear/FFmpeg-iOS-Lame"; + requirement = { + branch = main; + kind = branch; + }; + }; + 1359ED182D76FA7D00C13034 /* XCRemoteSwiftPackageReference "Drops" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/omaralbeik/Drops.git"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.0; + branch = main; + kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -758,9 +776,14 @@ package = 133D7C952D2BE2AF0075467E /* XCRemoteSwiftPackageReference "Kingfisher" */; productName = Kingfisher; }; - 13D842512D4523B800EBBFA6 /* Drops */ = { + 1359ED162D76FA6500C13034 /* FFmpeg-iOS-Lame */ = { isa = XCSwiftPackageProductDependency; - package = 13D842502D4523B800EBBFA6 /* XCRemoteSwiftPackageReference "Drops" */; + package = 1359ED152D76FA6500C13034 /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */; + productName = "FFmpeg-iOS-Lame"; + }; + 1359ED192D76FA7D00C13034 /* Drops */ = { + isa = XCSwiftPackageProductDependency; + package = 1359ED182D76FA7D00C13034 /* XCRemoteSwiftPackageReference "Drops" */; productName = Drops; }; /* End XCSwiftPackageProductDependency section */ diff --git a/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7c62e6b..9784b35 100644 --- a/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -4,10 +4,28 @@ { "package": "Drops", "repositoryURL": "https://github.com/omaralbeik/Drops.git", + "state": { + "branch": "main", + "revision": "5824681795286c36bdc4a493081a63e64e2a064e", + "version": null + } + }, + { + "package": "FFmpeg-iOS-Lame", + "repositoryURL": "https://github.com/kewlbear/FFmpeg-iOS-Lame", + "state": { + "branch": "main", + "revision": "1808fa5a1263c5e216646cd8421fc7dcb70520cc", + "version": null + } + }, + { + "package": "FFmpeg-iOS-Support", + "repositoryURL": "https://github.com/kewlbear/FFmpeg-iOS-Support", "state": { "branch": null, - "revision": "a183ee6f79f21c940092a19c2cba756555422371", - "version": "1.7.0" + "revision": "be3bd9149ac53760e8725652eee99c405b2be47a", + "version": "0.0.2" } }, {