fix: avoid unbounded HTTP throttling and make buffer readout opt-in
This commit is contained in:
@@ -17,6 +17,7 @@ final class PlayerModel: ObservableObject {
|
||||
@Published private(set) var youtubeHeight: Int?
|
||||
@Published private(set) var youtubeQuality: Int?
|
||||
@Published private(set) var automaticYouTubeQuality = false
|
||||
@Published private(set) var showsBufferInfo = false
|
||||
@Published var seekPosition = 0.0
|
||||
@Published var isSeeking = false
|
||||
@Published private(set) var volume = 1.0
|
||||
@@ -65,6 +66,7 @@ final class PlayerModel: ObservableObject {
|
||||
self.defaults = defaults
|
||||
self.qualityClock = qualityClock
|
||||
automaticYouTubeQuality = defaults.bool(forKey: "automaticYouTubeQuality")
|
||||
showsBufferInfo = defaults.bool(forKey: "showsBufferInfo")
|
||||
playbackProgress = PlaybackProgress(defaults: defaults)
|
||||
let player = player_create()
|
||||
handle = player
|
||||
@@ -158,6 +160,12 @@ final class PlayerModel: ObservableObject {
|
||||
resolveYouTube(url, height: nil)
|
||||
}
|
||||
|
||||
func setShowsBufferInfo(_ enabled: Bool) {
|
||||
showsBufferInfo = enabled
|
||||
defaults.set(enabled, forKey: "showsBufferInfo")
|
||||
revealControls()
|
||||
}
|
||||
|
||||
func setAutomaticYouTubeQuality(_ enabled: Bool) {
|
||||
automaticYouTubeQuality = enabled
|
||||
defaults.set(enabled, forKey: "automaticYouTubeQuality")
|
||||
@@ -822,8 +830,9 @@ private struct PlayerContent: View {
|
||||
@State private var controlOffset = CGSize.zero
|
||||
@State private var dragOrigin = CGSize.zero
|
||||
|
||||
private var controlHeight: CGFloat { model.isNetworkMedia ? 88 : 68 }
|
||||
private var compactControlHeight: CGFloat { model.isNetworkMedia ? 52 : 40 }
|
||||
private var showsBufferReadout: Bool { model.isNetworkMedia && model.showsBufferInfo }
|
||||
private var controlHeight: CGFloat { showsBufferReadout ? 88 : 68 }
|
||||
private var compactControlHeight: CGFloat { showsBufferReadout ? 52 : 40 }
|
||||
|
||||
private var bufferedStatus: String? {
|
||||
model.bufferedRange.map {
|
||||
@@ -931,6 +940,10 @@ private struct PlayerContent: View {
|
||||
Text(model.youtubeHeight.map { "Current: \($0)p" } ?? "Current resolution unknown")
|
||||
}
|
||||
Divider()
|
||||
Toggle("Show Buffer Info", isOn: Binding(
|
||||
get: { model.showsBufferInfo },
|
||||
set: { model.setShowsBufferInfo($0) }))
|
||||
Divider()
|
||||
Button(model.isCompact ? "Exit Compact Mode" : "Enter Compact Mode", action: model.toggleCompact)
|
||||
.disabled(!model.canToggleCompact)
|
||||
Toggle("Snap to Screen Edges", isOn: Binding(
|
||||
@@ -959,10 +972,10 @@ private struct PlayerContent: View {
|
||||
.disabled(model.compactWindow.isTransitioning)
|
||||
actionsMenu
|
||||
}
|
||||
if model.isNetworkMedia { bufferReadout }
|
||||
if showsBufferReadout { bufferReadout }
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, model.isNetworkMedia ? 6 : 8)
|
||||
.padding(.vertical, showsBufferReadout ? 6 : 8)
|
||||
.foregroundStyle(.white.opacity(0.85))
|
||||
.background(PlayerGlass().clipShape(RoundedRectangle(cornerRadius: 10)))
|
||||
.onHover(perform: model.setControlsHovered)
|
||||
@@ -1040,7 +1053,7 @@ private struct PlayerContent: View {
|
||||
}
|
||||
.font(.system(size: 11).monospacedDigit())
|
||||
.padding(.horizontal, 16)
|
||||
if model.isNetworkMedia { bufferReadout }
|
||||
if showsBufferReadout { bufferReadout }
|
||||
}
|
||||
.padding(.vertical, 11)
|
||||
// Optical centering accounts for symbol insets and the native seek thumb.
|
||||
|
||||
@@ -485,7 +485,7 @@ struct Player {
|
||||
steadyAnchor = Clock::now();
|
||||
}
|
||||
|
||||
playback::NetworkInput *openNetworkInput(int slot, const std::string &url, const std::string &headers) {
|
||||
playback::NetworkInput *openNetworkInput(int slot, const std::string &url, const std::string &headers, bool live) {
|
||||
inputs[slot] = std::make_unique<playback::NetworkInput>(stopping, requestedEpoch, activeEpoch, wake, cacheBudget);
|
||||
auto &input = *inputs[slot];
|
||||
input.format = avformat_alloc_context();
|
||||
@@ -494,6 +494,10 @@ struct Player {
|
||||
AVDictionary *options = nullptr;
|
||||
int result = av_dict_set(&options, "protocol_whitelist", "http,https,tcp,tls,crypto", 0);
|
||||
if (result >= 0) result = av_dict_set(&options, "rw_timeout", "15000000", 0);
|
||||
// Some YouTube CDNs throttle open-ended ranges; FFmpeg continues bounded requests.
|
||||
if (result >= 0 && !live) result = av_dict_set(&options, "request_size", "10485760", 0);
|
||||
if (result >= 0 && !live) result = av_dict_set(&options, "multiple_requests", "1", 0);
|
||||
// Leave short-seek read-ahead disabled: cache misses must escape stalled responses.
|
||||
if (result >= 0 && !headers.empty()) result = av_dict_set(&options, "headers", headers.c_str(), 0);
|
||||
if (result < 0) {
|
||||
av_dict_free(&options);
|
||||
@@ -520,7 +524,7 @@ struct Player {
|
||||
status = PLAYER_OPENING;
|
||||
publish();
|
||||
if (network) {
|
||||
auto *primary = openNetworkInput(0, command.path, command.videoHeaders);
|
||||
auto *primary = openNetworkInput(0, command.path, command.videoHeaders, command.live);
|
||||
format = primary->format;
|
||||
openTrack(1, format, primary, AVMEDIA_TYPE_VIDEO, -1);
|
||||
if (command.audioURL.empty()) {
|
||||
@@ -528,7 +532,7 @@ struct Player {
|
||||
} else {
|
||||
if (!tracks[1].codec)
|
||||
throw std::runtime_error("The selected video stream contains no playable video");
|
||||
auto *audio = openNetworkInput(1, command.audioURL, command.audioHeaders);
|
||||
auto *audio = openNetworkInput(1, command.audioURL, command.audioHeaders, command.live);
|
||||
openTrack(0, audio->format, audio, AVMEDIA_TYPE_AUDIO, -1);
|
||||
if (!tracks[0].codec)
|
||||
throw std::runtime_error("The selected audio stream contains no playable audio");
|
||||
|
||||
Reference in New Issue
Block a user