From 6ea5afb0d8f2466984bdd3262298c4f805fd6dcb Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Sun, 8 Jun 2025 17:08:38 +0200 Subject: [PATCH] ok yeah no audio for now --- .../CustomPlayer/CustomPlayer.swift | 233 +++++------------- 1 file changed, 57 insertions(+), 176 deletions(-) diff --git a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift index 24652be..b31a04f 100644 --- a/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift +++ b/Sora/Utils/MediaPlayer/CustomPlayer/CustomPlayer.swift @@ -74,7 +74,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele private var pipController: AVPictureInPictureController? private var pipButton: UIButton! - var portraitButtonVisibleConstraints: [NSLayoutConstraint] = [] var portraitButtonHiddenConstraints: [NSLayoutConstraint] = [] var landscapeButtonVisibleConstraints: [NSLayoutConstraint] = [] @@ -82,7 +81,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele var currentMarqueeConstraints: [NSLayoutConstraint] = [] private var currentMenuButtonTrailing: NSLayoutConstraint! - var subtitleForegroundColor: String = "white" var subtitleBackgroundEnabled: Bool = true var subtitleFontSize: Double = 20.0 @@ -179,8 +177,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele watchNextButton, volumeSliderHostingView, pipButton, - airplayButton, - audioTrackButton + airplayButton ].compactMap { $0 } private var originalHiddenStates: [UIView: Bool] = [:] @@ -199,10 +196,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele private var backgroundToken: Any? private var foregroundToken: Any? - private var audioTracks: [(name: String, groupID: String, uri: String)] = [] - private var audioTrackButton: UIButton! - private var lastSelectedAudioTrack: String? - init(module: ScrapingModule, urlString: String, fullUrl: String, @@ -275,7 +268,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele setupDimButton() setupSpeedButton() setupQualityButton() - setupAudioMenuButton() setupMenuButton() setupMarqueeLabel() setupSkip85Button() @@ -457,7 +449,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele } } - @objc private func playerItemDidChange() { DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -1307,7 +1298,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele ) } - func setupMenuButton() { let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .bold) let image = UIImage(systemName: "text.bubble", withConfiguration: config) @@ -1378,7 +1368,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele watchNextButton.tintColor = .white watchNextButton.setTitleColor(.white, for: .normal) - // The shadow: watchNextButton.layer.shadowColor = UIColor.black.cgColor watchNextButton.layer.shadowOffset = CGSize(width: 0, height: 2) watchNextButton.layer.shadowOpacity = 0.6 @@ -1464,34 +1453,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele ]) } - func setupAudioMenuButton() { - let config = UIImage.SymbolConfiguration(pointSize: 15, weight: .bold) - let image = UIImage(systemName: "waveform.circle", withConfiguration: config) - - audioTrackButton = UIButton(type: .system) - audioTrackButton.setImage(image, for: .normal) - audioTrackButton.tintColor = .white - audioTrackButton.showsMenuAsPrimaryAction = true - audioTrackButton.menu = audioTrackSelectionMenu() - audioTrackButton.isHidden = true - - audioTrackButton.layer.shadowColor = UIColor.black.cgColor - audioTrackButton.layer.shadowOffset = CGSize(width: 0, height: 2) - audioTrackButton.layer.shadowOpacity = 0.6 - audioTrackButton.layer.shadowRadius = 4 - audioTrackButton.layer.masksToBounds = false - - controlsContainerView.addSubview(audioTrackButton) - audioTrackButton.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - audioTrackButton.topAnchor.constraint(equalTo: qualityButton.topAnchor), - audioTrackButton.trailingAnchor.constraint(equalTo: qualityButton.leadingAnchor, constant: -6), - audioTrackButton.widthAnchor.constraint(equalToConstant: 40), - audioTrackButton.heightAnchor.constraint(equalToConstant: 40) - ]) - } - func updateSubtitleLabelAppearance() { for subtitleLabel in subtitleLabels { subtitleLabel.font = UIFont.systemFont(ofSize: CGFloat(subtitleFontSize)) @@ -2051,107 +2012,72 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele let lines = content.components(separatedBy: .newlines) var qualities: [(String, String)] = [] - var audioTracks: [(name: String, groupID: String, uri: String)] = [] - func getQualityName(from line: String, url: String) -> String? { - if let resRange = line.range(of: "RESOLUTION=") { - let afterRes = line[resRange.upperBound...] - let resString = afterRes.split(separator: ",").first ?? "" - if let heightStr = resString.split(separator: "x").last, - let height = Int(heightStr) { - switch height { - case 1080...: return "\(height)p (FHD)" - case 720..<1080: return "\(height)p (HD)" - case 480..<720: return "\(height)p (SD)" - default: return "\(height)p" - } - } + qualities.append(("Auto (Recommended)", url.absoluteString)) + + func getQualityName(for height: Int) -> String { + switch height { + case 1080...: return "\(height)p (FHD)" + case 720..<1080: return "\(height)p (HD)" + case 480..<720: return "\(height)p (SD)" + default: return "\(height)p" } - if let match = url.range(of: "rendition=([0-9]+p)", options: .regularExpression) { - let rendition = String(url[match]).replacingOccurrences(of: "rendition=", with: "") - return rendition - } - return nil } for (index, line) in lines.enumerated() { - if line.hasPrefix("#EXT-X-MEDIA:") && line.contains("TYPE=AUDIO") { - let name = line.components(separatedBy: "NAME=\"").last?.components(separatedBy: "\"").first ?? "Unknown" - let groupID = line.components(separatedBy: "GROUP-ID=\"").last?.components(separatedBy: "\"").first ?? "" - let uri = line.components(separatedBy: "URI=\"").last?.components(separatedBy: "\"").first ?? "" - audioTracks.append((name: name, groupID: groupID, uri: uri)) - } if line.contains("#EXT-X-STREAM-INF"), index + 1 < lines.count { - let nextLine = lines[index + 1].trimmingCharacters(in: .whitespacesAndNewlines) - let qualityName = getQualityName(from: line, url: nextLine) ?? "Unknown" - var qualityURL = nextLine - if !nextLine.hasPrefix("http") { - if let baseURL = self.baseM3U8URL { - let baseURLString = baseURL.deletingLastPathComponent().absoluteString - qualityURL = URL(string: nextLine, relativeTo: baseURL)?.absoluteString - ?? baseURLString + "/" + nextLine + if let resolutionRange = line.range(of: "RESOLUTION="), + let resolutionEndRange = line[resolutionRange.upperBound...].range(of: ",") + ?? line[resolutionRange.upperBound...].range(of: "\n") { + + let resolutionPart = String(line[resolutionRange.upperBound.. secondHeight } - self.qualities = sortedQualities - if !audioTracks.isEmpty { - self.audioTracks = audioTracks - self.audioTrackButton.isHidden = false - self.audioTrackButton.menu = self.audioTrackSelectionMenu() + if let auto = autoQuality { + sortedQualities.insert(auto, at: 0) } + self.qualities = sortedQualities completion() } }.resume() } - private func audioTrackSelectionMenu() -> UIMenu { - var menuItems: [UIMenuElement] = [] - if audioTracks.isEmpty { - let unavailable = UIAction(title: "No alternate audio", attributes: .disabled) { _ in } - menuItems.append(unavailable) - } else { - for (name, _, _) in audioTracks { - let action = UIAction(title: name, state: (lastSelectedAudioTrack == name ? .on : .off)) { [weak self] _ in - self?.switchToAudioTrack(named: name) - } - menuItems.append(action) - } - } - return UIMenu(title: "Audio Track", children: menuItems) - } - - private func switchToAudioTrack(named name: String) { - lastSelectedAudioTrack = name - guard let playerItem = player.currentItem else { return } - guard let group = playerItem.asset.mediaSelectionGroup(forMediaCharacteristic: .audible) else { return } - guard let option = group.options.first(where: { $0.displayName == name }) else { return } - playerItem.select(option, in: group) - audioTrackButton.menu = audioTrackSelectionMenu() - } - private func switchToQuality(urlString: String) { guard let url = URL(string: urlString), currentQualityURL?.absoluteString != urlString else { return } let currentTime = player.currentTime() let wasPlaying = player.rate > 0 - let currentRate = player.rate - let audioTrackToApply = lastSelectedAudioTrack - - player.pause() var request = URLRequest(url: url) if let mydict = headers, !mydict.isEmpty { @@ -2167,47 +2093,25 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele let asset = AVURLAsset(url: url, options: ["AVURLAssetHTTPHeaderFieldsKey": request.allHTTPHeaderFields ?? [:]]) let playerItem = AVPlayerItem(asset: asset) - asset.loadValuesAsynchronously(forKeys: ["playable", "tracks"]) { [weak self] in - DispatchQueue.main.async { - guard let self = self else { return } - - let statusObserver = playerItem.observe(\.status, options: [.new]) { [weak self] item, _ in - guard let self = self else { return } - - if item.status == .readyToPlay { - self.player.seek(to: currentTime) { [weak self] _ in - guard let self = self else { return } - - if let audioTrackName = audioTrackToApply { - self.applyAudioTrack(named: audioTrackName, to: item) - } - - if wasPlaying { - self.player.playImmediately(atRate: currentRate) - } - } - } - } - - self.player.replaceCurrentItem(with: playerItem) - self.currentQualityURL = url - - UserDefaults.standard.set(urlString, forKey: "lastSelectedQuality") - self.qualityButton.menu = self.qualitySelectionMenu() - - if let selectedQuality = self.qualities.first(where: { $0.1 == urlString })?.0 { - DropManager.shared.showDrop(title: "Quality: \(selectedQuality)", subtitle: "", duration: 0.5, icon: UIImage(systemName: "eye")) - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { - statusObserver.invalidate() - } - } + player.replaceCurrentItem(with: playerItem) + player.seek(to: currentTime) + if wasPlaying { + player.play() + } + + currentQualityURL = url + + UserDefaults.standard.set(urlString, forKey: "lastSelectedQuality") + qualityButton.menu = qualitySelectionMenu() + + if let selectedQuality = qualities.first(where: { $0.1 == urlString })?.0 { + DropManager.shared.showDrop(title: "Quality: \(selectedQuality)", subtitle: "", duration: 0.5, icon: UIImage(systemName: "eye")) } } private func qualitySelectionMenu() -> UIMenu { var menuItems: [UIMenuElement] = [] + if isHLSStream { if qualities.isEmpty { let loadingAction = UIAction(title: "Loading qualities...", attributes: .disabled) { _ in } @@ -2218,8 +2122,10 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele let selectedQuality = qualities.first(where: { $0.1 == currentURL })?.0 { menuTitle = "Quality: \(selectedQuality)" } + for (name, urlString) in qualities { let isCurrentQuality = currentQualityURL?.absoluteString == urlString + let action = UIAction( title: name, state: isCurrentQuality ? .on : .off, @@ -2229,38 +2135,17 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele ) menuItems.append(action) } + return UIMenu(title: menuTitle, children: menuItems) } } else { let unavailableAction = UIAction(title: "Quality selection unavailable", attributes: .disabled) { _ in } menuItems.append(unavailableAction) } + return UIMenu(title: "Video Quality", children: menuItems) } - private func applyAudioTrack(named name: String, to playerItem: AVPlayerItem) { - if let group = playerItem.asset.mediaSelectionGroup(forMediaCharacteristic: .audible), - let option = group.options.first(where: { $0.displayName == name }) { - playerItem.select(option, in: group) - Logger.shared.log("Applied audio track: \(name)", type: "Debug") - return - } - - if let group = playerItem.asset.mediaSelectionGroup(forMediaCharacteristic: .audible), - let savedTrack = audioTracks.first(where: { $0.name == name }) { - for option in group.options { - if option.displayName.contains(savedTrack.name) || - (option.locale?.identifier.contains(savedTrack.groupID) == true) { - playerItem.select(option, in: group) - Logger.shared.log("Applied similar audio track: \(option.displayName)", type: "Debug") - return - } - } - } - - Logger.shared.log("Could not find matching audio track: \(name)", type: "Warning") - } - private func checkForHLSStream() { guard let url = URL(string: streamURL) else { return } let streamType = module.metadata.streamType.lowercased() @@ -2272,11 +2157,8 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele parseM3U8(url: url) { [weak self] in guard let self = self else { return } - - if self.qualities.count > 1, - let last = UserDefaults.standard.string(forKey: "lastSelectedQuality"), - self.qualities.contains(where: { $0.1 == last }), - last != url.absoluteString { + if let last = UserDefaults.standard.string(forKey: "lastSelectedQuality"), + self.qualities.contains(where: { $0.1 == last }) { self.switchToQuality(urlString: last) } @@ -2290,7 +2172,6 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele } else { isHLSStream = false qualityButton.isHidden = true - audioTrackButton.isHidden = true updateMenuButtonConstraints() } }