From 15c54b3d0cd7e6bd26a23e4a4ce8573880513138 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Fri, 25 Apr 2025 21:43:33 +0200 Subject: [PATCH 01/11] fixed stuffs --- Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 50e8bc7..5e385b7 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -858,7 +858,11 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele private func setupHoldSpeedIndicator() { let config = UIImage.SymbolConfiguration(pointSize: 14, weight: .bold) let image = UIImage(systemName: "forward.fill", withConfiguration: config) - let speed = UserDefaults.standard.float(forKey: "holdSpeedPlayer") + var speed = UserDefaults.standard.float(forKey: "holdSpeedPlayer") + + if speed == 0.0 { + speed = 2.0 + } holdSpeedIndicator = UIButton(type: .system) holdSpeedIndicator.setTitle(" \(speed)", for: .normal) From 713046ce64d9536f7caf6d96417b2b38d6b76d24 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Sat, 26 Apr 2025 16:30:36 +0200 Subject: [PATCH 02/11] OHHHH it was "CURRENT" --- .../AniList/Mutations/AniListPushUpdates.swift | 2 +- Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift | 4 ---- Sora/Utils/Modules/CommunityLib.swift | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Sora/Tracking Services/AniList/Mutations/AniListPushUpdates.swift b/Sora/Tracking Services/AniList/Mutations/AniListPushUpdates.swift index 4589bd9..4383e1f 100644 --- a/Sora/Tracking Services/AniList/Mutations/AniListPushUpdates.swift +++ b/Sora/Tracking Services/AniList/Mutations/AniListPushUpdates.swift @@ -57,7 +57,7 @@ class AniListMutation { let variables: [String: Any] = [ "mediaId": animeId, "progress": episodeNumber, - "status": "WATCHING" + "status": "CURRENT" ] let requestBody: [String: Any] = [ diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 5e385b7..34521f3 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -1639,21 +1639,17 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele isDimmed.toggle() dimButtonTimer?.invalidate() - // animate black overlay UIView.animate(withDuration: 0.25) { self.blackCoverView.alpha = self.isDimmed ? 1.0 : 0.4 } - // fade controls instead of hiding UIView.animate(withDuration: 0.25) { for view in self.controlsToHide { view.alpha = self.isDimmed ? 0 : 1 } - // keep the dim button visible/in front self.dimButton.alpha = self.isDimmed ? 0 : 1 } - // swap your trailing constraints on the dim‑button dimButtonToSlider.isActive = !isDimmed dimButtonToRight.isActive = isDimmed UIView.animate(withDuration: 0.25) { self.view.layoutIfNeeded() } diff --git a/Sora/Utils/Modules/CommunityLib.swift b/Sora/Utils/Modules/CommunityLib.swift index 079f30a..10ae24f 100644 --- a/Sora/Utils/Modules/CommunityLib.swift +++ b/Sora/Utils/Modules/CommunityLib.swift @@ -6,7 +6,7 @@ // import SwiftUI -@preconcurrency import WebKit +import WebKit private struct ModuleLink: Identifiable { let id = UUID() From f0d83a7cd5eea9b9b5a31c43d0a3dcd8b896cac0 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Sat, 26 Apr 2025 22:37:22 +0200 Subject: [PATCH 03/11] now they use UIStackView --- .../CustomPlayer/CustomPlayer.swift | 102 +++++++----------- 1 file changed, 41 insertions(+), 61 deletions(-) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 34521f3..acaecb9 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -6,11 +6,11 @@ // import UIKit -import MarqueeLabel import AVKit import SwiftUI -import AVFoundation import MediaPlayer +import AVFoundation +import MarqueeLabel class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDelegate { let module: ScrapingModule @@ -71,9 +71,11 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele var subtitleFontSize: Double = 20.0 var subtitleShadowRadius: Double = 1.0 var subtitlesLoader = VTTSubtitlesLoader() + var subtitleStackView: UIStackView! + var subtitleLabels: [UILabel] = [] var subtitlesEnabled: Bool = true { didSet { - subtitleLabel.isHidden = !subtitlesEnabled + subtitleStackView.isHidden = !subtitlesEnabled } } @@ -83,7 +85,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele var playPauseButton: UIImageView! var backwardButton: UIImageView! var forwardButton: UIImageView! - var subtitleLabel: UILabel! var topSubtitleLabel: UILabel! var dismissButton: UIButton! var menuButton: UIButton! @@ -241,8 +242,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele updateSkipButtonsVisibility() setupHoldSpeedIndicator() - view.bringSubviewToFront(subtitleLabel) - view.bringSubviewToFront(topSubtitleLabel) + view.bringSubviewToFront(subtitleStackView) AniListMutation().fetchMalID(animeId: aniListID) { [weak self] result in switch result { @@ -717,48 +717,42 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele } func setupSubtitleLabel() { - subtitleLabel = UILabel() - subtitleLabel?.textAlignment = .center - subtitleLabel?.numberOfLines = 0 - subtitleLabel?.font = UIFont.systemFont(ofSize: CGFloat(subtitleFontSize)) - if let subtitleLabel = subtitleLabel { - view.addSubview(subtitleLabel) - subtitleLabel.translatesAutoresizingMaskIntoConstraints = false + subtitleStackView = UIStackView() + subtitleStackView.axis = .vertical + subtitleStackView.alignment = .center + subtitleStackView.distribution = .fill + subtitleStackView.spacing = 2 + + if let subtitleStackView = subtitleStackView { + view.addSubview(subtitleStackView) + subtitleStackView.translatesAutoresizingMaskIntoConstraints = false - subtitleBottomToSliderConstraint = subtitleLabel.bottomAnchor.constraint( + subtitleBottomToSliderConstraint = subtitleStackView.bottomAnchor.constraint( equalTo: sliderHostingController?.view.topAnchor ?? view.bottomAnchor, constant: -20 ) - subtitleBottomToSafeAreaConstraint = subtitleLabel.bottomAnchor.constraint( + subtitleBottomToSafeAreaConstraint = subtitleStackView.bottomAnchor.constraint( equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -subtitleBottomPadding ) NSLayoutConstraint.activate([ - subtitleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), - subtitleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 36), - subtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -36) + subtitleStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + subtitleStackView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 36), + subtitleStackView.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -36) ]) subtitleBottomToSafeAreaConstraint?.isActive = true } - topSubtitleLabel = UILabel() - topSubtitleLabel?.textAlignment = .center - topSubtitleLabel?.numberOfLines = 0 - topSubtitleLabel?.font = UIFont.systemFont(ofSize: CGFloat(subtitleFontSize)) - topSubtitleLabel?.isHidden = true - if let topSubtitleLabel = topSubtitleLabel { - view.addSubview(topSubtitleLabel) - topSubtitleLabel.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - topSubtitleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), - topSubtitleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 30), - topSubtitleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 36), - topSubtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -36) - ]) + for _ in 0..<2 { + let label = UILabel() + label.textAlignment = .center + label.numberOfLines = 0 + label.font = UIFont.systemFont(ofSize: CGFloat(subtitleFontSize)) + subtitleLabels.append(label) + subtitleStackView.addArrangedSubview(label) } updateSubtitleLabelAppearance() @@ -1305,7 +1299,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele } func updateSubtitleLabelAppearance() { - if let subtitleLabel = subtitleLabel { + for subtitleLabel in subtitleLabels { subtitleLabel.font = UIFont.systemFont(ofSize: CGFloat(subtitleFontSize)) subtitleLabel.textColor = subtitleUIColor() subtitleLabel.backgroundColor = subtitleBackgroundEnabled @@ -1318,20 +1312,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele subtitleLabel.layer.shadowOpacity = 1.0 subtitleLabel.layer.shadowOffset = .zero } - - if let topSubtitleLabel = topSubtitleLabel { - topSubtitleLabel.font = UIFont.systemFont(ofSize: CGFloat(subtitleFontSize)) - topSubtitleLabel.textColor = subtitleUIColor() - topSubtitleLabel.backgroundColor = subtitleBackgroundEnabled - ? UIColor.black.withAlphaComponent(0.6) - : .clear - topSubtitleLabel.layer.cornerRadius = 5 - topSubtitleLabel.clipsToBounds = true - topSubtitleLabel.layer.shadowColor = UIColor.black.cgColor - topSubtitleLabel.layer.shadowRadius = CGFloat(subtitleShadowRadius) - topSubtitleLabel.layer.shadowOpacity = 1.0 - topSubtitleLabel.layer.shadowOffset = .zero - } } func addTimeObserver() { @@ -1362,24 +1342,24 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele if self.subtitlesEnabled { let cues = self.subtitlesLoader.cues.filter { self.currentTimeVal >= $0.startTime && self.currentTimeVal <= $0.endTime } if cues.count > 0 { - self.subtitleLabel.text = cues[0].text.strippedHTML - self.subtitleLabel.isHidden = false + self.subtitleLabels[0].text = cues[0].text.strippedHTML + self.subtitleLabels[0].isHidden = false } else { - self.subtitleLabel.text = "" - self.subtitleLabel.isHidden = !self.subtitlesEnabled + self.subtitleLabels[0].text = "" + self.subtitleLabels[0].isHidden = !self.subtitlesEnabled } if cues.count > 1 { - self.topSubtitleLabel.text = cues[1].text.strippedHTML - self.topSubtitleLabel.isHidden = false + self.subtitleLabels[1].text = cues[1].text.strippedHTML + self.subtitleLabels[1].isHidden = false } else { - self.topSubtitleLabel.text = "" - self.topSubtitleLabel.isHidden = true + self.subtitleLabels[1].text = "" + self.subtitleLabels[1].isHidden = true } } else { - self.subtitleLabel.text = "" - self.subtitleLabel.isHidden = true - self.topSubtitleLabel.text = "" - self.topSubtitleLabel.isHidden = true + self.subtitleLabels[0].text = "" + self.subtitleLabels[0].isHidden = true + self.subtitleLabels[1].text = "" + self.subtitleLabels[1].isHidden = true } let segmentsColor = self.getSegmentsColor() @@ -2174,7 +2154,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele self.holdSpeedIndicator.alpha = 0.8 } } - + private func endHoldSpeed() { player?.rate = originalRate From 3ca0610544b180fe5a5468db97998dbfbd5dd3a9 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Sat, 26 Apr 2025 23:01:17 +0200 Subject: [PATCH 04/11] subtitle delay maybe --- .../CustomPlayer/CustomPlayer.swift | 67 ++++++++++++++++++- .../Helpers/SubtitleSettingsManager.swift | 1 + .../SettingsSubViews/SettingsViewPlayer.swift | 31 +++++++++ 3 files changed, 96 insertions(+), 3 deletions(-) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index acaecb9..174809d 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -167,6 +167,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele private var volumeValue: Double = 0.0 private var volumeViewModel = VolumeViewModel() var volumeSliderHostingView: UIView? + private var subtitleDelay: Double = 0.0 init(module: ScrapingModule, urlString: String, @@ -1340,7 +1341,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele UserDefaults.standard.set(self.duration, forKey: "totalTime_\(self.fullUrl)") if self.subtitlesEnabled { - let cues = self.subtitlesLoader.cues.filter { self.currentTimeVal >= $0.startTime && self.currentTimeVal <= $0.endTime } + let adjustedTime = self.currentTimeVal - self.subtitleDelay + let cues = self.subtitlesLoader.cues.filter { adjustedTime >= $0.startTime && adjustedTime <= $0.endTime } if cues.count > 0 { self.subtitleLabels[0].text = cues[0].text.strippedHTML self.subtitleLabels[0].isHidden = false @@ -1876,7 +1878,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele self.switchToQuality(urlString: last) } - // reveal + animate self.qualityButton.isHidden = false self.qualityButton.menu = self.qualitySelectionMenu() self.updateMenuButtonConstraints() @@ -2014,8 +2015,41 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele ] let paddingMenu = UIMenu(title: "Bottom Padding", children: paddingActions) + let delayActions = [ + UIAction(title: "-0.5s") { [weak self] _ in + guard let self = self else { return } + self.adjustSubtitleDelay(by: -0.5) + }, + UIAction(title: "-0.2s") { [weak self] _ in + guard let self = self else { return } + self.adjustSubtitleDelay(by: -0.2) + }, + UIAction(title: "+0.2s") { [weak self] _ in + guard let self = self else { return } + self.adjustSubtitleDelay(by: 0.2) + }, + UIAction(title: "+0.5s") { [weak self] _ in + guard let self = self else { return } + self.adjustSubtitleDelay(by: 0.5) + }, + UIAction(title: "Custom...") { [weak self] _ in + guard let self = self else { return } + self.presentCustomDelayAlert() + } + ] + + let resetDelayAction = UIAction(title: "Reset Timing") { [weak self] _ in + guard let self = self else { return } + SubtitleSettingsManager.shared.update { settings in settings.subtitleDelay = 0.0 } + self.subtitleDelay = 0.0 + self.loadSubtitleSettings() + DropManager.shared.showDrop(title: "Subtitle Timing Reset", subtitle: "", duration: 0.5, icon: UIImage(systemName: "clock.arrow.circlepath")) + } + + let delayMenu = UIMenu(title: "Subtitle Timing", children: delayActions + [resetDelayAction]) + let subtitleOptionsMenu = UIMenu(title: "Subtitle Options", children: [ - subtitlesToggleAction, colorMenu, fontSizeMenu, shadowMenu, backgroundMenu, paddingMenu + subtitlesToggleAction, colorMenu, fontSizeMenu, shadowMenu, backgroundMenu, paddingMenu, delayMenu ]) menuElements = [subtitleOptionsMenu] @@ -2024,6 +2058,32 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele return UIMenu(title: "", children: menuElements) } + func adjustSubtitleDelay(by amount: Double) { + let newValue = subtitleDelay + amount + let roundedValue = Double(round(newValue * 10) / 10) + SubtitleSettingsManager.shared.update { settings in settings.subtitleDelay = roundedValue } + self.subtitleDelay = roundedValue + self.loadSubtitleSettings() + } + + func presentCustomDelayAlert() { + let alert = UIAlertController(title: "Enter Custom Delay", message: nil, preferredStyle: .alert) + alert.addTextField { textField in + textField.placeholder = "Delay in seconds" + textField.keyboardType = .decimalPad + textField.text = String(format: "%.1f", self.subtitleDelay) + } + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + alert.addAction(UIAlertAction(title: "Done", style: .default) { _ in + if let text = alert.textFields?.first?.text, let newDelay = Double(text) { + SubtitleSettingsManager.shared.update { settings in settings.subtitleDelay = newDelay } + self.subtitleDelay = newDelay + self.loadSubtitleSettings() + } + }) + present(alert, animated: true) + } + func presentCustomPaddingAlert() { let alert = UIAlertController(title: "Enter Custom Padding", message: nil, preferredStyle: .alert) alert.addTextField { textField in @@ -2071,6 +2131,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele self.subtitleShadowRadius = settings.shadowRadius self.subtitleBackgroundEnabled = settings.backgroundEnabled self.subtitleBottomPadding = settings.bottomPadding + self.subtitleDelay = settings.subtitleDelay } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/Helpers/SubtitleSettingsManager.swift b/Sora/Utils/MediaPlayer/CustomPlayer/Helpers/SubtitleSettingsManager.swift index cb8f82a..57e96b3 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/Helpers/SubtitleSettingsManager.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/Helpers/SubtitleSettingsManager.swift @@ -13,6 +13,7 @@ struct SubtitleSettings: Codable { var shadowRadius: Double = 1.0 var backgroundEnabled: Bool = true var bottomPadding: CGFloat = 20.0 + var subtitleDelay: Double = 0.0 } class SubtitleSettingsManager { diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift index d07110d..9591f17 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift @@ -117,6 +117,7 @@ struct SubtitleSettingsSection: View { @State private var shadowRadius: Double = SubtitleSettingsManager.shared.settings.shadowRadius @State private var backgroundEnabled: Bool = SubtitleSettingsManager.shared.settings.backgroundEnabled @State private var bottomPadding: CGFloat = SubtitleSettingsManager.shared.settings.bottomPadding + @State private var subtitleDelay: Double = SubtitleSettingsManager.shared.settings.subtitleDelay private let colors = ["white", "yellow", "green", "blue", "red", "purple"] private let shadowOptions = [0, 1, 3, 6] @@ -186,6 +187,36 @@ struct SubtitleSettingsSection: View { } } } + + VStack(alignment: .leading) { + Text("Subtitle Timing Adjustment: \(String(format: "%.1fs", subtitleDelay))") + .padding(.bottom, 1) + + HStack { + Text("-10s") + .font(.system(size: 12)) + .foregroundColor(.gray) + + Slider(value: $subtitleDelay, in: -10...10, step: 0.1) + .onChange(of: subtitleDelay) { newValue in + SubtitleSettingsManager.shared.update { settings in + settings.subtitleDelay = newValue + } + } + + Text("+10s") + .font(.system(size: 12)) + .foregroundColor(.gray) + } + } + + Button("Reset Subtitle Timing") { + subtitleDelay = 0.0 + SubtitleSettingsManager.shared.update { settings in + settings.subtitleDelay = 0.0 + } + } + .foregroundColor(.accentColor) } } } From 8f76989ae899b02b63532fca32c48f09bf7015c4 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Sat, 26 Apr 2025 23:02:10 +0200 Subject: [PATCH 05/11] nvm this better --- .../SettingsSubViews/SettingsViewPlayer.swift | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift index 9591f17..983dc51 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift @@ -195,7 +195,7 @@ struct SubtitleSettingsSection: View { HStack { Text("-10s") .font(.system(size: 12)) - .foregroundColor(.gray) + .foregroundColor(.secondary) Slider(value: $subtitleDelay, in: -10...10, step: 0.1) .onChange(of: subtitleDelay) { newValue in @@ -206,17 +206,9 @@ struct SubtitleSettingsSection: View { Text("+10s") .font(.system(size: 12)) - .foregroundColor(.gray) + .foregroundColor(.secondary) } } - - Button("Reset Subtitle Timing") { - subtitleDelay = 0.0 - SubtitleSettingsManager.shared.update { settings in - settings.subtitleDelay = 0.0 - } - } - .foregroundColor(.accentColor) } } } From 7f4a60bf56d480688abf0b2459e0494eac6eae1b Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Sun, 27 Apr 2025 11:28:29 +0200 Subject: [PATCH 06/11] better text --- .../SettingsView/SettingsSubViews/SettingsViewPlayer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift index 983dc51..dd5143f 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift @@ -189,7 +189,7 @@ struct SubtitleSettingsSection: View { } VStack(alignment: .leading) { - Text("Subtitle Timing Adjustment: \(String(format: "%.1fs", subtitleDelay))") + Text("Subtitle Delay: \(String(format: "%.1fs", subtitleDelay))") .padding(.bottom, 1) HStack { From 982f482d8483ef2b1e228d4c907b46137ec821ab Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Tue, 29 Apr 2025 18:49:22 +0200 Subject: [PATCH 07/11] =?UTF-8?q?no=20way=20=F0=9F=98=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ContinueWatching/DownloadManager.swift | 95 ++++++++++++++++++ Sora/Views/DownloadView.swift | 96 ++++++------------- Sulfur.xcodeproj/project.pbxproj | 20 +++- 3 files changed, 141 insertions(+), 70 deletions(-) create mode 100644 Sora/Utils/ContinueWatching/DownloadManager.swift diff --git a/Sora/Utils/ContinueWatching/DownloadManager.swift b/Sora/Utils/ContinueWatching/DownloadManager.swift new file mode 100644 index 0000000..9c3d90d --- /dev/null +++ b/Sora/Utils/ContinueWatching/DownloadManager.swift @@ -0,0 +1,95 @@ +// +// DownloadManager.swift +// Sulfur +// +// Created by Francesco on 29/04/25. +// + +import SwiftUI +import AVKit +import AVFoundation + +class DownloadManager: NSObject, ObservableObject { + @Published var activeDownloads: [(URL, Double)] = [] + @Published var localPlaybackURL: URL? + + private var assetDownloadURLSession: AVAssetDownloadURLSession! + private var activeDownloadTasks: [URLSessionTask: URL] = [:] + + override init() { + super.init() + initializeDownloadSession() + loadLocalContent() + } + + private func initializeDownloadSession() { + let configuration = URLSessionConfiguration.background(withIdentifier: "hls-downloader") + assetDownloadURLSession = AVAssetDownloadURLSession( + configuration: configuration, + assetDownloadDelegate: self, + delegateQueue: .main + ) + } + + func downloadAsset(from url: URL) { + let asset = AVURLAsset(url: url) + let task = assetDownloadURLSession.makeAssetDownloadTask( + asset: asset, + assetTitle: "Offline Video", + assetArtworkData: nil, + options: nil + ) + + task?.resume() + activeDownloadTasks[task!] = url + } + + private func loadLocalContent() { + guard let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return } + + do { + let contents = try FileManager.default.contentsOfDirectory( + at: documents, + includingPropertiesForKeys: nil, + options: .skipsHiddenFiles + ) + + if let localURL = contents.first(where: { $0.pathExtension == "movpkg" }) { + localPlaybackURL = localURL + } + } catch { + print("Error loading local content: \(error)") + } + } +} + +extension DownloadManager: AVAssetDownloadDelegate { + func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) { + activeDownloadTasks.removeValue(forKey: assetDownloadTask) + localPlaybackURL = location + } + + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + guard let error = error else { return } + print("Download error: \(error.localizedDescription)") + activeDownloadTasks.removeValue(forKey: task) + } + + func urlSession(_ session: URLSession, + assetDownloadTask: AVAssetDownloadTask, + didLoad timeRange: CMTimeRange, + totalTimeRangesLoaded loadedTimeRanges: [NSValue], + timeRangeExpectedToLoad: CMTimeRange) { + + guard let url = activeDownloadTasks[assetDownloadTask] else { return } + let progress = loadedTimeRanges + .map { $0.timeRangeValue.duration.seconds / timeRangeExpectedToLoad.duration.seconds } + .reduce(0, +) + + if let index = activeDownloads.firstIndex(where: { $0.0 == url }) { + activeDownloads[index].1 = progress + } else { + activeDownloads.append((url, progress)) + } + } +} diff --git a/Sora/Views/DownloadView.swift b/Sora/Views/DownloadView.swift index 8717e2a..d98a28a 100644 --- a/Sora/Views/DownloadView.swift +++ b/Sora/Views/DownloadView.swift @@ -2,82 +2,46 @@ // DownloadView.swift // Sulfur // -// Created by Francesco on 12/03/25. +// Created by Francesco on 29/04/25. // import SwiftUI - -struct DownloadItem: Identifiable { - let id = UUID() - let title: String - let episode: Int - let type: String - var progress: Double - var status: String -} - -class DownloadViewModel: ObservableObject { - @Published var downloads: [DownloadItem] = [] - - init() { - NotificationCenter.default.addObserver(self, selector: #selector(updateStatus(_:)), name: .DownloadManagerStatusUpdate, object: nil) - } - - @objc func updateStatus(_ notification: Notification) { - guard let info = notification.userInfo, - let title = info["title"] as? String, - let episode = info["episode"] as? Int, - let type = info["type"] as? String, - let status = info["status"] as? String, - let progress = info["progress"] as? Double else { return } - - if let index = downloads.firstIndex(where: { $0.title == title && $0.episode == episode }) { - downloads[index] = DownloadItem(title: title, episode: episode, type: type, progress: progress, status: status) - } else { - let newDownload = DownloadItem(title: title, episode: episode, type: type, progress: progress, status: status) - downloads.append(newDownload) - } - } -} +import AVKit struct DownloadView: View { - @StateObject var viewModel = DownloadViewModel() + @StateObject private var viewModel = DownloadManager() + @State private var hlsURL = "https://test-streams.mux.dev/x36xhzz/url_6/193039199_mp4_h264_aac_hq_7.m3u8" var body: some View { NavigationView { - List(viewModel.downloads) { download in - HStack(spacing: 16) { - Image(systemName: iconName(for: download)) - .resizable() - .frame(width: 30, height: 30) - .foregroundColor(.accentColor) - - VStack(alignment: .leading, spacing: 4) { - Text("\(download.title) - Episode \(download.episode)") - .font(.headline) - - ProgressView(value: download.progress) - .progressViewStyle(LinearProgressViewStyle(tint: .accentColor)) - .frame(height: 8) - - Text(download.status) - .font(.subheadline) - .foregroundColor(.secondary) - } - Spacer() + VStack { + TextField("Enter HLS URL", text: $hlsURL) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .padding() + + Button("Download Stream") { + viewModel.downloadAsset(from: URL(string: hlsURL)!) } - .padding(.vertical, 8) + .padding() + + List(viewModel.activeDownloads, id: \.0) { (url, progress) in + VStack(alignment: .leading) { + Text(url.absoluteString) + ProgressView(value: progress) + .progressViewStyle(LinearProgressViewStyle()) + } + } + + NavigationLink("Play Offline Content") { + if let url = viewModel.localPlaybackURL { + VideoPlayer(player: AVPlayer(url: url)) + } else { + Text("No offline content available") + } + } + .padding() } - .navigationTitle("Downloads") - } - .navigationViewStyle(StackNavigationViewStyle()) - } - - func iconName(for download: DownloadItem) -> String { - if download.type == "hls" { - return download.status.lowercased().contains("converting") ? "arrow.triangle.2.circlepath.circle.fill" : "checkmark.circle.fill" - } else { - return download.progress >= 1.0 ? "checkmark.circle.fill" : "arrow.down.circle.fill" + .navigationTitle("HLS Downloader") } } } diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index 89079d9..4415b7b 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -7,10 +7,11 @@ objects = { /* Begin PBXBuildFile section */ - 130217CC2D81C55E0011EFF5 /* DownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130217CB2D81C55E0011EFF5 /* DownloadView.swift */; }; 130C6BFA2D53AB1F00DC1432 /* SettingsViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */; }; 13103E8B2D58E028000F0673 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13103E8A2D58E028000F0673 /* View.swift */; }; 13103E8E2D58E04A000F0673 /* SkeletonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13103E8D2D58E04A000F0673 /* SkeletonCell.swift */; }; + 131270172DC13A010093AA9C /* DownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131270162DC13A010093AA9C /* DownloadManager.swift */; }; + 131270192DC13A3C0093AA9C /* DownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131270182DC13A3C0093AA9C /* DownloadView.swift */; }; 131845F92D47C62D00CA7A54 /* SettingsViewGeneral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */; }; 1327FBA72D758CEA00FC6689 /* Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1327FBA62D758CEA00FC6689 /* Analytics.swift */; }; 1327FBA92D758DEA00FC6689 /* UIDevice+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1327FBA82D758DEA00FC6689 /* UIDevice+Model.swift */; }; @@ -69,11 +70,12 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 130217CB2D81C55E0011EFF5 /* DownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadView.swift; sourceTree = ""; }; 130C6BF82D53A4C200DC1432 /* Sora.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Sora.entitlements; sourceTree = ""; }; 130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewData.swift; sourceTree = ""; }; 13103E8A2D58E028000F0673 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; 13103E8D2D58E04A000F0673 /* SkeletonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonCell.swift; sourceTree = ""; }; + 131270162DC13A010093AA9C /* DownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManager.swift; sourceTree = ""; }; + 131270182DC13A3C0093AA9C /* DownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadView.swift; sourceTree = ""; }; 131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewGeneral.swift; sourceTree = ""; }; 1327FBA62D758CEA00FC6689 /* Analytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Analytics.swift; sourceTree = ""; }; 1327FBA82D758DEA00FC6689 /* UIDevice+Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+Model.swift"; sourceTree = ""; }; @@ -171,6 +173,13 @@ path = SkeletonCells; sourceTree = ""; }; + 131270152DC139CD0093AA9C /* DownloadManager */ = { + isa = PBXGroup; + children = ( + ); + path = DownloadManager; + sourceTree = ""; + }; 1327FBA52D758CEA00FC6689 /* Analytics */ = { isa = PBXGroup; children = ( @@ -226,7 +235,7 @@ 1399FAD22D3AB34F00E97C31 /* SettingsView */, 133F55B92D33B53E00E08EEA /* LibraryView */, 133D7C7C2D2BE2630075467E /* SearchView.swift */, - 130217CB2D81C55E0011EFF5 /* DownloadView.swift */, + 131270182DC13A3C0093AA9C /* DownloadView.swift */, ); path = Views; sourceTree = ""; @@ -257,6 +266,7 @@ 133D7C852D2BE2640075467E /* Utils */ = { isa = PBXGroup; children = ( + 131270152DC139CD0093AA9C /* DownloadManager */, 13C0E5E82D5F85DD00E7F619 /* ContinueWatching */, 13103E8C2D58E037000F0673 /* SkeletonCells */, 13DC0C442D302C6A00D0F966 /* MediaPlayer */, @@ -364,6 +374,7 @@ children = ( 13C0E5E92D5F85EA00E7F619 /* ContinueWatchingManager.swift */, 13C0E5EB2D5F85F800E7F619 /* ContinueWatchingItem.swift */, + 131270162DC13A010093AA9C /* DownloadManager.swift */, ); path = ContinueWatching; sourceTree = ""; @@ -521,6 +532,7 @@ buildActionMask = 2147483647; files = ( 135CCBE22D4D1138008B9C0E /* SettingsViewPlayer.swift in Sources */, + 131270172DC13A010093AA9C /* DownloadManager.swift in Sources */, 1327FBA72D758CEA00FC6689 /* Analytics.swift in Sources */, 13DC0C462D302C7500D0F966 /* VideoPlayer.swift in Sources */, 1359ED142D76F49900C13034 /* finTopView.swift in Sources */, @@ -558,8 +570,8 @@ 133D7C942D2BE2640075467E /* JSController.swift in Sources */, 133D7C922D2BE2640075467E /* URLSession.swift in Sources */, 133D7C912D2BE2640075467E /* SettingsViewModule.swift in Sources */, + 131270192DC13A3C0093AA9C /* DownloadView.swift in Sources */, 13E62FC22DABC5830007E259 /* Trakt-Login.swift in Sources */, - 130217CC2D81C55E0011EFF5 /* DownloadView.swift in Sources */, 133F55BB2D33B55100E08EEA /* LibraryManager.swift in Sources */, 13E62FC42DABC58C0007E259 /* Trakt-Token.swift in Sources */, 133D7C8E2D2BE2640075467E /* LibraryView.swift in Sources */, From a2c2c04d74b4791973a8e6590c43ad438fd9c138 Mon Sep 17 00:00:00 2001 From: cranci <100066266+cranci1@users.noreply.github.com> Date: Wed, 30 Apr 2025 18:30:37 +0200 Subject: [PATCH 08/11] Update SettingsViewPlayer.swift SenPlayrt --- .../SettingsView/SettingsSubViews/SettingsViewPlayer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift index dd5143f..28b6d77 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift @@ -19,7 +19,7 @@ struct SettingsViewPlayer: View { @AppStorage("doubleTapSeekEnabled") private var doubleTapSeekEnabled: Bool = false @AppStorage("skipIntroOutroVisible") private var skipIntroOutroVisible: Bool = true - private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "Sora"] + private let mediaPlayers = ["Default", "VLC", "OutPlayer", "Infuse", "nPlayer", "SenPlayer", "Sora"] var body: some View { Form { From 1be1eeaf31df670ca786c057c2874ba25c83f0a3 Mon Sep 17 00:00:00 2001 From: cranci <100066266+cranci1@users.noreply.github.com> Date: Wed, 30 Apr 2025 18:33:02 +0200 Subject: [PATCH 09/11] Update MediaInfoView.swift --- Sora/Views/MediaInfoView/MediaInfoView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 709e3c2..6dec97e 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -859,6 +859,8 @@ struct MediaInfoView: View { scheme = "outplayer://\(url)" case "nPlayer": scheme = "nplayer-\(url)" + case "SenPlayer": + scheme = "SenPlayer://x-callback-url/play?url=\(url)" case "Default": let videoPlayerViewController = VideoPlayerViewController(module: module) videoPlayerViewController.streamUrl = url From 17428c45bf98ae304f7f28b972a5ba854a390b89 Mon Sep 17 00:00:00 2001 From: cranci <100066266+cranci1@users.noreply.github.com> Date: Wed, 30 Apr 2025 18:37:47 +0200 Subject: [PATCH 10/11] Update Info.plist --- Sora/Info.plist | 1 + 1 file changed, 1 insertion(+) diff --git a/Sora/Info.plist b/Sora/Info.plist index b9e2c4d..6e00c4a 100644 --- a/Sora/Info.plist +++ b/Sora/Info.plist @@ -25,6 +25,7 @@ infuse vlc nplayer-https + senplayer NSAppTransportSecurity From d57b803129b4286d71e2df5c0be9fb53e9306178 Mon Sep 17 00:00:00 2001 From: cranci <100066266+cranci1@users.noreply.github.com> Date: Wed, 30 Apr 2025 18:52:20 +0200 Subject: [PATCH 11/11] Update CustomPlayer.swift --- Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 174809d..28c10da 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -2038,15 +2038,14 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele } ] - let resetDelayAction = UIAction(title: "Reset Timing") { [weak self] _ in + let resetDelayAction = UIAction(title: "Reset Delay") { [weak self] _ in guard let self = self else { return } SubtitleSettingsManager.shared.update { settings in settings.subtitleDelay = 0.0 } self.subtitleDelay = 0.0 self.loadSubtitleSettings() - DropManager.shared.showDrop(title: "Subtitle Timing Reset", subtitle: "", duration: 0.5, icon: UIImage(systemName: "clock.arrow.circlepath")) } - let delayMenu = UIMenu(title: "Subtitle Timing", children: delayActions + [resetDelayAction]) + let delayMenu = UIMenu(title: "Subtitle Delay", children: delayActions + [resetDelayAction]) let subtitleOptionsMenu = UIMenu(title: "Subtitle Options", children: [ subtitlesToggleAction, colorMenu, fontSizeMenu, shadowMenu, backgroundMenu, paddingMenu, delayMenu