commits say it all (#66)
Some checks are pending
Build and Release IPA / Build IPA (push) Waiting to run

This commit is contained in:
Seiike 2025-04-07 06:35:37 +02:00 committed by GitHub
parent 7fb6d2d92e
commit 1f3ea9c267
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 383 additions and 252 deletions

View file

@ -9,174 +9,134 @@
import SwiftUI
struct MusicProgressSlider<T: BinaryFloatingPoint>: View {
@Binding var value: T
let inRange: ClosedRange<T>
let bufferValue: T
let activeFillColor: Color
let fillColor: Color
let bufferColor: Color
let emptyColor: Color
let height: CGFloat
let onEditingChanged: (Bool) -> Void
@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 {
VStack {
ZStack(alignment: .center) {
Capsule()
.fill(emptyColor)
Capsule()
.fill(bufferColor)
.mask({
HStack {
Rectangle()
.frame(
width: max(
(bounds.size.width
* CGFloat(getPrgPercentage(bufferValue)))
.isFinite
? bounds.size.width
* CGFloat(getPrgPercentage(bufferValue))
: 0,
0
),
alignment: .leading
)
Spacer(minLength: 0)
}
})
Capsule()
.fill(isActive ? activeFillColor : fillColor)
.mask({
HStack {
Rectangle()
.frame(
width: max(
(bounds.size.width
* CGFloat(localRealProgress + localTempProgress))
.isFinite
? bounds.size.width
* CGFloat(localRealProgress + localTempProgress)
: 0,
0
),
alignment: .leading
)
Spacer(minLength: 0)
}
})
}
struct MusicProgressSlider<T: BinaryFloatingPoint>: View {
@Binding var value: T
@Binding var bufferValue: T // NEW
let inRange: ClosedRange<T>
let activeFillColor: Color
let fillColor: Color
let emptyColor: Color
let height: CGFloat
let onEditingChanged: (Bool) -> Void
@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 {
VStack {
// Base track + buffer indicator + current progress
ZStack(alignment: .center) {
HStack {
let shouldShowHours = inRange.upperBound >= 3600
Text(value.asTimeString(style: .positional, showHours: shouldShowHours))
Spacer(minLength: 0)
Text("-" + (inRange.upperBound - value).asTimeString(
style: .positional,
showHours: shouldShowHours
))
}
.font(.system(size: 12))
.foregroundColor(isActive ? fillColor : emptyColor)
// Entire background track
Capsule()
.fill(emptyColor)
// 1) The buffer fill portion (behind the actual progress)
Capsule() // NEW
.fill(fillColor.opacity(0.3)) // or any "bufferColor"
.mask({
HStack {
Rectangle()
.frame(
width: max(
bounds.size.width * CGFloat(getPrgPercentage(bufferValue)),
0
),
alignment: .leading
)
Spacer(minLength: 0)
}
})
// 2) The actual playback progress
Capsule()
.fill(isActive ? activeFillColor : fillColor)
.mask({
HStack {
Rectangle()
.frame(
width: max(
bounds.size.width * CGFloat(localRealProgress + localTempProgress),
0
),
alignment: .leading
)
Spacer(minLength: 0)
}
})
}
.frame(
width: isActive ? bounds.size.width * 1.04 : bounds.size.width,
alignment: .center
)
.animation(animation, value: isActive)
}
.frame(
width: bounds.size.width,
height: bounds.size.height,
alignment: .center
)
.contentShape(Rectangle())
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.updating($isActive) { _, state, _ in
state = true
}
.onChanged { gesture in
localTempProgress = T(gesture.translation.width / bounds.size.width)
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)
// Time labels
HStack {
let shouldShowHours = inRange.upperBound >= 3600
Text(value.asTimeString(style: .positional, showHours: shouldShowHours))
Spacer(minLength: 0)
Text("-" + (inRange.upperBound - value)
.asTimeString(style: .positional, showHours: shouldShowHours))
}
.font(.system(size: 12))
.foregroundColor(isActive ? fillColor : emptyColor)
}
.frame(width: isActive ? bounds.size.width * 1.04 : bounds.size.width,
alignment: .center)
.animation(animation, value: isActive)
}
.frame(width: bounds.size.width, height: bounds.size.height, alignment: .center)
.contentShape(Rectangle())
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.updating($isActive) { _, state, _ in
state = true
}
.onChanged { gesture in
localTempProgress = T(gesture.translation.width / bounds.size.width)
value = clampValue(getPrgValue())
}
.onEnded { _ in
localRealProgress = getPrgPercentage(value)
localTempProgress = 0
}
)
.onChange(of: isActive) { newValue in
value = clampValue(getPrgValue())
onEditingChanged(newValue)
}
.onAppear {
localRealProgress = getPrgPercentage(value)
}
.onChange(of: value) { newValue in
if !isActive {
localRealProgress = getPrgPercentage(newValue)
}
}
}
.frame(height: isActive ? height * 1.25 : height, alignment: .center)
}
private var animation: Animation {
if isActive {
return .spring()
} else {
return .spring(response: 0.5, dampingFraction: 0.5, blendDuration: 0.6)
}
isActive
? .spring()
: .spring(response: 0.5, dampingFraction: 0.5, blendDuration: 0.6)
}
private func getPrgPercentage(_ value: T) -> T {
private func clampValue(_ val: T) -> T {
max(min(val, inRange.upperBound), inRange.lowerBound)
}
private func getPrgPercentage(_ val: T) -> T {
let clampedValue = clampValue(val)
let range = inRange.upperBound - inRange.lowerBound
let correctedStartValue = value - inRange.lowerBound
let percentage = correctedStartValue / range
return percentage
let pct = (clampedValue - inRange.lowerBound) / range
return max(min(pct, 1), 0)
}
private func getPrgValue() -> T {
return ((localRealProgress + localTempProgress) * (inRange.upperBound - inRange.lowerBound)) + inRange.lowerBound}
// MARK: - Helpers
private func fraction(of val: T) -> T {
let total = inRange.upperBound - inRange.lowerBound
let normalized = val - inRange.lowerBound
return (total > 0) ? (normalized / total) : 0
}
private func clampedFraction(_ f: T) -> T {
max(0, min(f, 1))
}
private func getCurrentValue() -> T {
let total = inRange.upperBound - inRange.lowerBound
let frac = clampedFraction(localRealProgress + localTempProgress)
return frac * total + inRange.lowerBound
}
private func clampedValue(_ raw: T) -> T {
max(inRange.lowerBound, min(raw, inRange.upperBound))
}
private func playedWidth(boundsWidth: CGFloat) -> CGFloat {
let frac = fraction(of: value)
return max(0, min(boundsWidth * CGFloat(frac), boundsWidth))
}
private func bufferWidth(boundsWidth: CGFloat) -> CGFloat {
let frac = fraction(of: bufferValue)
return max(0, min(boundsWidth * CGFloat(frac), boundsWidth))
((localRealProgress + localTempProgress) * (inRange.upperBound - inRange.lowerBound))
+ inRange.lowerBound
}
}

View file

@ -6,6 +6,7 @@
//
import UIKit
import MarqueeLabel
import AVKit
import SwiftUI
import AVFoundation
@ -55,6 +56,13 @@ class CustomMediaPlayerViewController: UIViewController {
var lastDuration: Double = 0.0
var watchNextButtonAppearedAt: Double?
// MARK: - Constraint Sets
var portraitButtonVisibleConstraints: [NSLayoutConstraint] = []
var portraitButtonHiddenConstraints: [NSLayoutConstraint] = []
var landscapeButtonVisibleConstraints: [NSLayoutConstraint] = []
var landscapeButtonHiddenConstraints: [NSLayoutConstraint] = []
var currentMarqueeConstraints: [NSLayoutConstraint] = []
var subtitleForegroundColor: String = "white"
var subtitleBackgroundEnabled: Bool = true
var subtitleFontSize: Double = 20.0
@ -66,6 +74,7 @@ class CustomMediaPlayerViewController: UIViewController {
}
}
var marqueeLabel: MarqueeLabel!
var playerViewController: AVPlayerViewController!
var controlsContainerView: UIView!
var playPauseButton: UIImageView!
@ -102,7 +111,7 @@ class CustomMediaPlayerViewController: UIViewController {
private var playerItemKVOContext = 0
private var loadedTimeRangesObservation: NSKeyValueObservation?
private var playerTimeControlStatusObserver: NSKeyValueObservation?
init(module: ScrapingModule,
urlString: String,
@ -168,6 +177,7 @@ class CustomMediaPlayerViewController: UIViewController {
setupSpeedButton()
setupQualityButton()
setupMenuButton()
setupMarqueeLabel()
setupSkip85Button()
setupWatchNextButton()
addTimeObserver()
@ -179,7 +189,7 @@ class CustomMediaPlayerViewController: UIViewController {
self?.updateBufferValue()
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
self?.checkForHLSStream()
}
@ -188,6 +198,7 @@ class CustomMediaPlayerViewController: UIViewController {
holdForPause()
}
player.play()
if let url = subtitlesURL, !url.isEmpty {
@ -203,6 +214,32 @@ class CustomMediaPlayerViewController: UIViewController {
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
self.updateMarqueeConstraints()
})
}
/// In layoutSubviews, check if the text width is larger than the available space and update the labels properties.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Safely unwrap marqueeLabel
guard let marqueeLabel = marqueeLabel else {
return // or handle the error gracefully
}
let availableWidth = marqueeLabel.frame.width
let textWidth = marqueeLabel.intrinsicContentSize.width
if textWidth > availableWidth {
marqueeLabel.lineBreakMode = .byTruncatingTail
} else {
marqueeLabel.lineBreakMode = .byClipping
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self,
@ -371,19 +408,39 @@ class CustomMediaPlayerViewController: UIViewController {
forwardButton.translatesAutoresizingMaskIntoConstraints = false
let sliderView = MusicProgressSlider(
value: Binding(get: { self.sliderViewModel.sliderValue },
set: { self.sliderViewModel.sliderValue = $0 }),
value: Binding(
get: { self.sliderViewModel.sliderValue },
set: { self.sliderViewModel.sliderValue = $0 }
),
bufferValue: Binding(
get: { self.sliderViewModel.bufferValue }, // NEW
set: { self.sliderViewModel.bufferValue = $0 } // NEW
),
inRange: 0...(duration > 0 ? duration : 1.0),
bufferValue: self.sliderViewModel.bufferValue,
activeFillColor: .white,
fillColor: .white.opacity(0.5),
bufferColor: .white.opacity(0.2),
emptyColor: .white.opacity(0.3),
height: 30,
onEditingChanged: { editing in
self.isSliderEditing = editing
if !editing {
self.player.seek(to: CMTime(seconds: self.sliderViewModel.sliderValue, preferredTimescale: 600))
if editing {
self.isSliderEditing = true
} else {
let wasPlaying = self.isPlaying
let targetTime = CMTime(seconds: self.sliderViewModel.sliderValue,
preferredTimescale: 600)
self.player.seek(to: targetTime) { [weak self] finished in
guard let self = self else { return }
let final = self.player.currentTime().seconds
self.sliderViewModel.sliderValue = final
self.currentTimeVal = final
self.updateBufferValue()
self.isSliderEditing = false
if wasPlaying {
self.player.play()
}
}
}
}
)
@ -440,7 +497,7 @@ class CustomMediaPlayerViewController: UIViewController {
emptyColor: .white.opacity(0.3),
width: 22,
onEditingChanged: { editing in
}
}
)
let brightnessContainer = UIView()
@ -616,22 +673,90 @@ class CustomMediaPlayerViewController: UIViewController {
dismissButton.widthAnchor.constraint(equalToConstant: 40),
dismissButton.heightAnchor.constraint(equalToConstant: 40)
])
}
func setupMarqueeLabel() {
// Create the MarqueeLabel and configure its scrolling behavior
marqueeLabel = MarqueeLabel()
marqueeLabel.text = "\(titleText) • Ep \(episodeNumber)"
marqueeLabel.type = .continuous
marqueeLabel.textColor = .white
marqueeLabel.font = UIFont.systemFont(ofSize: 15, weight: .medium)
let episodeLabel = UILabel()
episodeLabel.text = "\(titleText) • Ep \(episodeNumber)"
episodeLabel.textColor = .white
episodeLabel.font = UIFont.systemFont(ofSize: 15, weight: .medium)
episodeLabel.numberOfLines = 1
episodeLabel.lineBreakMode = .byTruncatingTail
marqueeLabel.speed = .rate(30) // Adjust scrolling speed as needed
marqueeLabel.fadeLength = 10.0 // Fading at the labels edges
marqueeLabel.leadingBuffer = 1.0 // Left inset for scrolling
marqueeLabel.trailingBuffer = 16.0 // Right inset for scrolling
marqueeLabel.animationDelay = 2.5
controlsContainerView.addSubview(episodeLabel)
episodeLabel.translatesAutoresizingMaskIntoConstraints = false
// Set default lineBreakMode (will be updated later based on available width)
marqueeLabel.lineBreakMode = .byTruncatingTail
marqueeLabel.textAlignment = .left
NSLayoutConstraint.activate([
episodeLabel.centerYAnchor.constraint(equalTo: dismissButton.centerYAnchor),
episodeLabel.leadingAnchor.constraint(equalTo: dismissButton.trailingAnchor, constant: 12),
episodeLabel.trailingAnchor.constraint(lessThanOrEqualTo: controlsContainerView.trailingAnchor, constant: -16)
])
controlsContainerView.addSubview(marqueeLabel)
marqueeLabel.translatesAutoresizingMaskIntoConstraints = false
// Define four sets of constraints:
// 1. Portrait mode with button visible
portraitButtonVisibleConstraints = [
marqueeLabel.leadingAnchor.constraint(equalTo: dismissButton.trailingAnchor, constant: 12),
marqueeLabel.trailingAnchor.constraint(equalTo: menuButton.leadingAnchor, constant: -16),
marqueeLabel.centerYAnchor.constraint(equalTo: dismissButton.centerYAnchor)
]
// 2. Portrait mode with button hidden
portraitButtonHiddenConstraints = [
marqueeLabel.leadingAnchor.constraint(equalTo: dismissButton.trailingAnchor, constant: 12),
marqueeLabel.trailingAnchor.constraint(equalTo: controlsContainerView.trailingAnchor, constant: -16),
marqueeLabel.centerYAnchor.constraint(equalTo: dismissButton.centerYAnchor)
]
// 3. Landscape mode with button visible (using smaller margins)
landscapeButtonVisibleConstraints = [
marqueeLabel.leadingAnchor.constraint(equalTo: dismissButton.trailingAnchor, constant: 8),
marqueeLabel.trailingAnchor.constraint(equalTo: menuButton.leadingAnchor, constant: -8),
marqueeLabel.centerYAnchor.constraint(equalTo: dismissButton.centerYAnchor)
]
// 4. Landscape mode with button hidden
landscapeButtonHiddenConstraints = [
marqueeLabel.leadingAnchor.constraint(equalTo: dismissButton.trailingAnchor, constant: 8),
marqueeLabel.trailingAnchor.constraint(equalTo: controlsContainerView.trailingAnchor, constant: -8),
marqueeLabel.centerYAnchor.constraint(equalTo: dismissButton.centerYAnchor)
]
// Activate an initial set based on the current orientation and menuButton state
updateMarqueeConstraints()
}
func updateMarqueeConstraints() {
// First, remove any existing marquee constraints.
NSLayoutConstraint.deactivate(currentMarqueeConstraints)
// Decide on spacing constants based on orientation.
let isPortrait = UIDevice.current.orientation.isPortrait || view.bounds.height > view.bounds.width
let leftSpacing: CGFloat = isPortrait ? 2 : 1
let rightSpacing: CGFloat = isPortrait ? 16 : 8
// Determine which button to use for the trailing anchor.
var trailingAnchor: NSLayoutXAxisAnchor = controlsContainerView.trailingAnchor // default fallback
if let menu = menuButton, !menu.isHidden {
trailingAnchor = menu.leadingAnchor
} else if let quality = qualityButton, !quality.isHidden {
trailingAnchor = quality.leadingAnchor
} else if let speed = speedButton, !speed.isHidden {
trailingAnchor = speed.leadingAnchor
}
// Create new constraints for the marquee label.
currentMarqueeConstraints = [
marqueeLabel.leadingAnchor.constraint(equalTo: dismissButton.trailingAnchor, constant: leftSpacing),
marqueeLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -rightSpacing),
marqueeLabel.centerYAnchor.constraint(equalTo: dismissButton.centerYAnchor)
]
NSLayoutConstraint.activate(currentMarqueeConstraints)
view.layoutIfNeeded()
}
func setupMenuButton() {
@ -789,6 +914,7 @@ class CustomMediaPlayerViewController: UIViewController {
let currentItem = self.player.currentItem,
currentItem.duration.seconds.isFinite else { return }
self.updateBufferValue()
let currentDuration = currentItem.duration.seconds
if currentDuration.isNaN || currentDuration <= 0 { return }
@ -811,21 +937,24 @@ class CustomMediaPlayerViewController: UIViewController {
DispatchQueue.main.async {
self.sliderHostingController?.rootView = MusicProgressSlider(
value: Binding(get: {
max(0, min(self.sliderViewModel.sliderValue, self.duration))
}, set: {
self.sliderViewModel.sliderValue = max(0, min($0, self.duration))
}),
inRange: 0...(self.duration > 0 ? self.duration : 1.0),
bufferValue: self.sliderViewModel.bufferValue,
value: Binding(
get: { max(0, min(self.sliderViewModel.sliderValue, self.duration)) },
set: {
self.sliderViewModel.sliderValue = max(0, min($0, self.duration))
}
),
bufferValue: Binding(get: { self.sliderViewModel.bufferValue },
set: { self.sliderViewModel.bufferValue = $0 }), inRange: 0...(self.duration > 0 ? self.duration : 1.0),
activeFillColor: .white,
fillColor: .white.opacity(0.6),
bufferColor: .white.opacity(0.36),
emptyColor: .white.opacity(0.3),
height: 30,
onEditingChanged: { editing in
if !editing {
let targetTime = CMTime(seconds: self.sliderViewModel.sliderValue, preferredTimescale: 600)
let targetTime = CMTime(
seconds: self.sliderViewModel.sliderValue,
preferredTimescale: 600
)
self.player.seek(to: targetTime) { [weak self] finished in
self?.updateBufferValue()
}
@ -835,9 +964,9 @@ class CustomMediaPlayerViewController: UIViewController {
}
let isNearEnd = (self.duration - self.currentTimeVal) <= (self.duration * 0.10)
&& self.currentTimeVal != self.duration
&& self.showWatchNextButton
&& self.duration != 0
&& self.currentTimeVal != self.duration
&& self.showWatchNextButton
&& self.duration != 0
if isNearEnd {
if !self.isWatchNextVisible {
@ -942,10 +1071,10 @@ class CustomMediaPlayerViewController: UIViewController {
let finalSkip = holdValue > 0 ? holdValue : 30
currentTimeVal = max(currentTimeVal - finalSkip, 0)
player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) { [weak self] finished in
guard let self = self else { return }
self.updateBufferValue()
}
}
guard let self = self else { return }
self.updateBufferValue()
}
}
}
@objc func seekForwardLongPress(_ gesture: UILongPressGestureRecognizer) {
@ -954,10 +1083,10 @@ class CustomMediaPlayerViewController: UIViewController {
let finalSkip = holdValue > 0 ? holdValue : 30
currentTimeVal = min(currentTimeVal + finalSkip, duration)
player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) { [weak self] finished in
guard let self = self else { return }
self.updateBufferValue()
}
}
guard let self = self else { return }
self.updateBufferValue()
}
}
}
@objc func seekBackward() {
@ -965,20 +1094,20 @@ class CustomMediaPlayerViewController: UIViewController {
let finalSkip = skipValue > 0 ? skipValue : 10
currentTimeVal = max(currentTimeVal - finalSkip, 0)
player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) { [weak self] finished in
guard let self = self else { return }
self.updateBufferValue()
}
}
guard let self = self else { return }
self.updateBufferValue()
}
}
@objc func seekForward() {
let skipValue = UserDefaults.standard.double(forKey: "skipIncrement")
let finalSkip = skipValue > 0 ? skipValue : 10
currentTimeVal = min(currentTimeVal + finalSkip, duration)
player.seek(to: CMTime(seconds: currentTimeVal, preferredTimescale: 600)) { [weak self] finished in
guard let self = self else { return }
self.updateBufferValue()
}
}
guard let self = self else { return }
self.updateBufferValue()
}
}
@objc func handleDoubleTap(_ gesture: UITapGestureRecognizer) {
let tapLocation = gesture.location(in: view)
@ -997,21 +1126,23 @@ class CustomMediaPlayerViewController: UIViewController {
@objc func togglePlayPause() {
if isPlaying {
player.pause()
isPlaying = false
playPauseButton.image = UIImage(systemName: "play.fill")
if !isControlsVisible {
isControlsVisible = true
UIView.animate(withDuration: 0.5) {
UIView.animate(withDuration: 0.2) {
self.controlsContainerView.alpha = 1.0
self.skip85Button.alpha = 0.8
self.view.layoutIfNeeded()
}
}
player.pause()
playPauseButton.image = UIImage(systemName: "play.fill")
} else {
player.play()
isPlaying = true
playPauseButton.image = UIImage(systemName: "pause.fill")
}
isPlaying.toggle()
}
@objc func dismissTapped() {
@ -1032,7 +1163,7 @@ class CustomMediaPlayerViewController: UIViewController {
@objc private func handleHoldForPause(_ gesture: UILongPressGestureRecognizer) {
guard isHoldPauseEnabled else { return }
if gesture.state == .began {
togglePlayPause()
}
@ -1088,7 +1219,7 @@ class CustomMediaPlayerViewController: UIViewController {
if line.contains("#EXT-X-STREAM-INF"), index + 1 < lines.count {
if let resolutionRange = line.range(of: "RESOLUTION="),
let resolutionEndRange = line[resolutionRange.upperBound...].range(of: ",")
?? line[resolutionRange.upperBound...].range(of: "\n") {
?? line[resolutionRange.upperBound...].range(of: "\n") {
let resolutionPart = String(line[resolutionRange.upperBound..<resolutionEndRange.lowerBound])
if let heightStr = resolutionPart.components(separatedBy: "x").last,
@ -1102,7 +1233,7 @@ class CustomMediaPlayerViewController: UIViewController {
if let baseURL = self.baseM3U8URL {
let baseURLString = baseURL.deletingLastPathComponent().absoluteString
qualityURL = URL(string: nextLine, relativeTo: baseURL)?.absoluteString
?? baseURLString + "/" + nextLine
?? baseURLString + "/" + nextLine
}
}
@ -1222,6 +1353,7 @@ class CustomMediaPlayerViewController: UIViewController {
self.qualityButton.isHidden = false
self.qualityButton.menu = self.qualitySelectionMenu()
self.updateMarqueeConstraints()
}
} else {
isHLSStream = false
@ -1487,6 +1619,20 @@ class CustomMediaPlayerViewController: UIViewController {
player?.rate = lastPlayedSpeed > 0 ? lastPlayedSpeed : 1.0
}
}
func setupTimeControlStatusObservation() {
playerTimeControlStatusObserver = player.observe(\.timeControlStatus, options: [.new]) { [weak self] player, _ in
guard let self = self else { return }
if player.timeControlStatus == .paused,
let reason = player.reasonForWaitingToPlay {
// If we are paused for a stall/minimize stalls reason, forcibly resume:
Logger.shared.log("Paused reason: \(reason)", type: "Error")
if reason == .toMinimizeStalls || reason == .evaluatingBufferingRate {
player.play()
}
}
}
}
}

View file

@ -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 */; };
1E048A722DA3262900D9DD3F /* MarqueeLabel in Frameworks */ = {isa = PBXBuildFile; productRef = 1E048A712DA3262900D9DD3F /* MarqueeLabel */; };
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 */; };
@ -135,6 +136,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1E048A722DA3262900D9DD3F /* MarqueeLabel in Frameworks */,
132E35232D959E410007800E /* Kingfisher in Frameworks */,
132E35202D959E1D0007800E /* FFmpeg-iOS-Lame in Frameworks */,
132E351D2D959DDB0007800E /* Drops in Frameworks */,
@ -469,6 +471,7 @@
132E351C2D959DDB0007800E /* Drops */,
132E351F2D959E1D0007800E /* FFmpeg-iOS-Lame */,
132E35222D959E410007800E /* Kingfisher */,
1E048A712DA3262900D9DD3F /* MarqueeLabel */,
);
productName = Sora;
productReference = 133D7C6A2D2BE2500075467E /* Sulfur.app */;
@ -501,6 +504,7 @@
132E351B2D959DDB0007800E /* XCRemoteSwiftPackageReference "Drops" */,
132E351E2D959E1D0007800E /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */,
132E35212D959E410007800E /* XCRemoteSwiftPackageReference "Kingfisher" */,
1E048A702DA3262900D9DD3F /* XCRemoteSwiftPackageReference "MarqueeLabel" */,
);
productRefGroup = 133D7C6B2D2BE2500075467E /* Products */;
projectDirPath = "";
@ -838,6 +842,14 @@
version = 7.9.1;
};
};
1E048A702DA3262900D9DD3F /* XCRemoteSwiftPackageReference "MarqueeLabel" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/cbpowell/MarqueeLabel";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 4.5.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@ -856,6 +868,11 @@
package = 132E35212D959E410007800E /* XCRemoteSwiftPackageReference "Kingfisher" */;
productName = Kingfisher;
};
1E048A712DA3262900D9DD3F /* MarqueeLabel */ = {
isa = XCSwiftPackageProductDependency;
package = 1E048A702DA3262900D9DD3F /* XCRemoteSwiftPackageReference "MarqueeLabel" */;
productName = MarqueeLabel;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 133D7C622D2BE2500075467E /* Project object */;

View file

@ -1,43 +1,51 @@
{
"object": {
"pins": [
{
"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": "be3bd9149ac53760e8725652eee99c405b2be47a",
"version": "0.0.2"
}
},
{
"package": "Kingfisher",
"repositoryURL": "https://github.com/onevcat/Kingfisher.git",
"state": {
"branch": null,
"revision": "b6f62758f21a8c03cd64f4009c037cfa580a256e",
"version": "7.9.1"
}
"originHash" : "e772caa8d6a8793d24bf04e3d77695cd5ac695f3605d2b657e40115caedf8863",
"pins" : [
{
"identity" : "drops",
"kind" : "remoteSourceControl",
"location" : "https://github.com/omaralbeik/Drops.git",
"state" : {
"branch" : "main",
"revision" : "5824681795286c36bdc4a493081a63e64e2a064e"
}
]
},
"version": 1
},
{
"identity" : "ffmpeg-ios-lame",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kewlbear/FFmpeg-iOS-Lame",
"state" : {
"branch" : "main",
"revision" : "1808fa5a1263c5e216646cd8421fc7dcb70520cc"
}
},
{
"identity" : "ffmpeg-ios-support",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kewlbear/FFmpeg-iOS-Support",
"state" : {
"revision" : "be3bd9149ac53760e8725652eee99c405b2be47a",
"version" : "0.0.2"
}
},
{
"identity" : "kingfisher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/onevcat/Kingfisher.git",
"state" : {
"revision" : "b6f62758f21a8c03cd64f4009c037cfa580a256e",
"version" : "7.9.1"
}
},
{
"identity" : "marqueelabel",
"kind" : "remoteSourceControl",
"location" : "https://github.com/cbpowell/MarqueeLabel",
"state" : {
"revision" : "877e810534cda9afabb8143ae319b7c3341b121b",
"version" : "4.5.0"
}
}
],
"version" : 3
}