mirror of
https://github.com/yattee/yattee.git
synced 2026-05-12 18:35:05 +00:00
Add Allow Software-Decoded Formats playback setting
Lets the auto stream selector pick formats whose codec isn't hardware decoded on the current device. Defaults off; when on, 4K VP9/AV1 can be auto-selected on Apple TV models without those decoders. Software-decoded streams also move into the Recommended section so the selection stays visible without enabling advanced stream details.
This commit is contained in:
@@ -25,6 +25,7 @@ enum SettingsKey: String, CaseIterable {
|
|||||||
case preferredSubtitlesLanguage
|
case preferredSubtitlesLanguage
|
||||||
case resumeAction
|
case resumeAction
|
||||||
case tvOSMenuButtonClosesVideo
|
case tvOSMenuButtonClosesVideo
|
||||||
|
case allowSoftwareDecodedFormats
|
||||||
|
|
||||||
// SponsorBlock
|
// SponsorBlock
|
||||||
case sponsorBlockEnabled
|
case sponsorBlockEnabled
|
||||||
@@ -129,7 +130,7 @@ enum SettingsKey: String, CaseIterable {
|
|||||||
/// in both UserDefaults and iCloud, so each platform family syncs independently.
|
/// in both UserDefaults and iCloud, so each platform family syncs independently.
|
||||||
var isPlatformSpecific: Bool {
|
var isPlatformSpecific: Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .preferredQuality, .cellularQuality, .macPlayerMode, .listStyle,
|
case .preferredQuality, .cellularQuality, .allowSoftwareDecodedFormats, .macPlayerMode, .listStyle,
|
||||||
// Home layout — different UI paradigms per platform
|
// Home layout — different UI paradigms per platform
|
||||||
.homeShortcutOrder, .homeShortcutVisibility, .homeShortcutLayout,
|
.homeShortcutOrder, .homeShortcutVisibility, .homeShortcutLayout,
|
||||||
.homeSectionOrder, .homeSectionVisibility, .homeSectionItemsLimit, .homeSectionLayout,
|
.homeSectionOrder, .homeSectionVisibility, .homeSectionItemsLimit, .homeSectionLayout,
|
||||||
|
|||||||
@@ -75,6 +75,20 @@ extension SettingsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When enabled, the auto stream selector will consider video formats whose codec
|
||||||
|
/// is not hardware-decodable on this device. Disabled by default. Useful on Apple TV
|
||||||
|
/// where 4K VP9/AV1 is otherwise excluded from auto-selection.
|
||||||
|
var allowSoftwareDecodedFormats: Bool {
|
||||||
|
get {
|
||||||
|
if let cached = _allowSoftwareDecodedFormats { return cached }
|
||||||
|
return bool(for: .allowSoftwareDecodedFormats, default: false)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
_allowSoftwareDecodedFormats = newValue
|
||||||
|
set(newValue, for: .allowSoftwareDecodedFormats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Preferred audio language code (e.g., "en", "de", "ja").
|
/// Preferred audio language code (e.g., "en", "de", "ja").
|
||||||
/// When set, audio streams in this language will be auto-selected and shown first.
|
/// When set, audio streams in this language will be auto-selected and shown first.
|
||||||
/// nil means no preference (use original/default audio).
|
/// nil means no preference (use original/default audio).
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ final class SettingsManager {
|
|||||||
var _playerVolume: Float?
|
var _playerVolume: Float?
|
||||||
var _resumeAction: ResumeAction?
|
var _resumeAction: ResumeAction?
|
||||||
var _tvOSMenuButtonClosesVideo: Bool?
|
var _tvOSMenuButtonClosesVideo: Bool?
|
||||||
|
var _allowSoftwareDecodedFormats: Bool?
|
||||||
|
|
||||||
// SponsorBlock
|
// SponsorBlock
|
||||||
var _sponsorBlockEnabled: Bool?
|
var _sponsorBlockEnabled: Bool?
|
||||||
@@ -413,6 +414,7 @@ final class SettingsManager {
|
|||||||
_playerVolume = nil
|
_playerVolume = nil
|
||||||
_resumeAction = nil
|
_resumeAction = nil
|
||||||
_tvOSMenuButtonClosesVideo = nil
|
_tvOSMenuButtonClosesVideo = nil
|
||||||
|
_allowSoftwareDecodedFormats = nil
|
||||||
_sponsorBlockEnabled = nil
|
_sponsorBlockEnabled = nil
|
||||||
_sponsorBlockCategories = nil
|
_sponsorBlockCategories = nil
|
||||||
_sponsorBlockAPIURL = nil
|
_sponsorBlockAPIURL = nil
|
||||||
|
|||||||
@@ -12101,6 +12101,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"settings.playback.quality.allowSoftwareDecoded" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Allow Software-Decoded Formats"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings.playback.quality.allowSoftwareDecoded.footer" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Lets the player auto-select formats that aren't hardware decoded on this device. Playback may stutter on weaker devices."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"settings.playback.quality.best" : {
|
"settings.playback.quality.best" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
|
|||||||
@@ -1906,9 +1906,17 @@ final class PlayerService {
|
|||||||
filteredVideoStreams = videoOnlyStreams
|
filteredVideoStreams = videoOnlyStreams
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out codecs with priority 0 (software decode) if hardware options exist
|
// Filter out codecs with priority 0 (software decode) if hardware options exist,
|
||||||
|
// unless the user opted in to software-decoded formats (e.g. to unlock 4K VP9/AV1
|
||||||
|
// on Apple TV models without hardware decoders for those codecs).
|
||||||
|
let allowSoftware = settingsManager?.allowSoftwareDecodedFormats ?? false
|
||||||
|
let streamsToConsider: [Stream]
|
||||||
|
if allowSoftware {
|
||||||
|
streamsToConsider = filteredVideoStreams
|
||||||
|
} else {
|
||||||
let hardwareDecodableStreams = filteredVideoStreams.filter { videoCodecPriority($0.videoCodec) > 0 }
|
let hardwareDecodableStreams = filteredVideoStreams.filter { videoCodecPriority($0.videoCodec) > 0 }
|
||||||
let streamsToConsider = hardwareDecodableStreams.isEmpty ? filteredVideoStreams : hardwareDecodableStreams
|
streamsToConsider = hardwareDecodableStreams.isEmpty ? filteredVideoStreams : hardwareDecodableStreams
|
||||||
|
}
|
||||||
|
|
||||||
// Sort by resolution first, then by codec priority
|
// Sort by resolution first, then by codec priority
|
||||||
let sortedVideo = streamsToConsider.sorted { s1, s2 in
|
let sortedVideo = streamsToConsider.sorted { s1, s2 in
|
||||||
|
|||||||
@@ -92,17 +92,21 @@ extension QualitySelectorView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Recommended video streams (hardware-decodable codecs).
|
/// Recommended video streams (hardware-decodable codecs).
|
||||||
|
/// When `allowSoftwareDecodedFormats` is ON, all video streams are considered recommended.
|
||||||
var recommendedVideoStreams: [Stream] {
|
var recommendedVideoStreams: [Stream] {
|
||||||
videoStreams.filter { (stream: Stream) -> Bool in
|
videoStreams.filter { (stream: Stream) -> Bool in
|
||||||
if stream.url.isFileURL { return true }
|
if stream.url.isFileURL { return true }
|
||||||
if stream.isMuxed { return true }
|
if stream.isMuxed { return true }
|
||||||
|
if allowSoftwareDecodedFormats { return true }
|
||||||
return !requiresSoftwareDecode(stream.videoCodec)
|
return !requiresSoftwareDecode(stream.videoCodec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Other video streams (software decode required).
|
/// Other video streams (software decode required).
|
||||||
|
/// Empty when `allowSoftwareDecodedFormats` is ON — those streams are now recommended.
|
||||||
var otherVideoStreams: [Stream] {
|
var otherVideoStreams: [Stream] {
|
||||||
videoStreams.filter { (stream: Stream) -> Bool in
|
if allowSoftwareDecodedFormats { return [] }
|
||||||
|
return videoStreams.filter { (stream: Stream) -> Bool in
|
||||||
if stream.url.isFileURL { return false }
|
if stream.url.isFileURL { return false }
|
||||||
if stream.isMuxed { return false }
|
if stream.isMuxed { return false }
|
||||||
return requiresSoftwareDecode(stream.videoCodec)
|
return requiresSoftwareDecode(stream.videoCodec)
|
||||||
|
|||||||
@@ -71,6 +71,12 @@ struct QualitySelectorView: View {
|
|||||||
appEnvironment?.settingsManager.showAdvancedStreamDetails ?? false
|
appEnvironment?.settingsManager.showAdvancedStreamDetails ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the user has opted in to software-decoded formats during auto-selection.
|
||||||
|
/// When enabled, software-decoded streams are treated as recommended (no split).
|
||||||
|
var allowSoftwareDecodedFormats: Bool {
|
||||||
|
appEnvironment?.settingsManager.allowSoftwareDecodedFormats ?? false
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Computed Properties
|
// MARK: - Computed Properties
|
||||||
|
|
||||||
/// Available tabs based on streams
|
/// Available tabs based on streams
|
||||||
|
|||||||
@@ -38,7 +38,10 @@ private struct QualitySection: View {
|
|||||||
@Bindable var settings: SettingsManager
|
@Bindable var settings: SettingsManager
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
SettingsFormSection("settings.playback.video.header") {
|
SettingsFormSection(
|
||||||
|
"settings.playback.video.header",
|
||||||
|
footer: "settings.playback.quality.allowSoftwareDecoded.footer"
|
||||||
|
) {
|
||||||
PlatformMenuPicker(
|
PlatformMenuPicker(
|
||||||
String(localized: "settings.playback.quality.preferred"),
|
String(localized: "settings.playback.quality.preferred"),
|
||||||
selection: $settings.preferredQuality
|
selection: $settings.preferredQuality
|
||||||
@@ -58,6 +61,11 @@ private struct QualitySection: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Toggle(
|
||||||
|
String(localized: "settings.playback.quality.allowSoftwareDecoded"),
|
||||||
|
isOn: $settings.allowSoftwareDecodedFormats
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user