mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-14 05:20:25 +00:00
ok yeah no audio for now
This commit is contained in:
parent
eb70b83bb1
commit
6ea5afb0d8
1 changed files with 57 additions and 176 deletions
|
|
@ -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..<resolutionEndRange.lowerBound])
|
||||
if let heightStr = resolutionPart.components(separatedBy: "x").last,
|
||||
let height = Int(heightStr) {
|
||||
|
||||
let nextLine = lines[index + 1].trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let qualityName = getQualityName(for: height)
|
||||
|
||||
var qualityURL = nextLine
|
||||
if !nextLine.hasPrefix("http") && nextLine.contains(".m3u8") {
|
||||
if let baseURL = self.baseM3U8URL {
|
||||
let baseURLString = baseURL.deletingLastPathComponent().absoluteString
|
||||
qualityURL = URL(string: nextLine, relativeTo: baseURL)?.absoluteString
|
||||
?? baseURLString + "/" + nextLine
|
||||
}
|
||||
}
|
||||
|
||||
if !qualities.contains(where: { $0.0 == qualityName }) {
|
||||
qualities.append((qualityName, qualityURL))
|
||||
}
|
||||
}
|
||||
}
|
||||
if !qualities.contains(where: { $0.0 == qualityName }) {
|
||||
qualities.append((qualityName, qualityURL))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let sortedQualities = qualities.sorted { first, second in
|
||||
let autoQuality = qualities.first
|
||||
var sortedQualities = qualities.dropFirst().sorted { first, second in
|
||||
let firstHeight = Int(first.0.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) ?? 0
|
||||
let secondHeight = Int(second.0.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) ?? 0
|
||||
return firstHeight > 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue