mirror of
https://github.com/yattee/yattee.git
synced 2025-11-24 10:18:16 +00:00
AVPlayer has a fundamental limitation with MP4/AVC1 progressive downloads where the moov atom position affects playback start time. When moov is at the end of the file, AVPlayer must download the entire file before starting playback. MPV doesn't have this limitation. This commit adds an advanced setting to optionally enable these formats in AVPlayer with appropriate warnings: - Added new setting: "Enable non-streamable formats (MP4/AVC1)" - Default: disabled (formats hidden, MPV handles them) - When enabled: MP4/AVC1 formats up to 1080p appear in AVPlayer quality selector - Resolution limit: 1080p maximum (higher resolutions can't be played properly) - Clear warning about slow loading and 1080p limitation - Automatic stream refresh when setting is toggled - Full import/export support for the setting
399 lines
13 KiB
Swift
399 lines
13 KiB
Swift
import Defaults
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
struct SettingsView: View {
|
|
static let matrixURL = URL(string: "https://tinyurl.com/matrix-yattee")!
|
|
static let discordURL = URL(string: "https://yattee.stream/discord")!
|
|
|
|
#if os(macOS)
|
|
private enum Tabs: Hashable {
|
|
case browsing, player, controls, quality, history, sponsorBlock, locations, advanced, importExport, help
|
|
}
|
|
|
|
@State private var selection: Tabs = .browsing
|
|
#endif
|
|
|
|
@Environment(\.colorScheme) private var colorScheme
|
|
|
|
#if os(iOS)
|
|
@Environment(\.presentationMode) private var presentationMode
|
|
#endif
|
|
|
|
@ObservedObject private var accounts = AccountsModel.shared
|
|
@ObservedObject private var model = SettingsModel.shared
|
|
|
|
@Default(.instances) private var instances
|
|
|
|
@State private var filesToShare = []
|
|
|
|
@ObservedObject private var navigation = NavigationModel.shared
|
|
@ObservedObject private var settingsModel = SettingsModel.shared
|
|
|
|
var body: some View {
|
|
settings
|
|
.modifier(ImportSettingsSheetViewModifier(isPresented: $settingsModel.presentingSettingsImportSheet, settingsFile: $settingsModel.settingsImportURL))
|
|
#if !os(tvOS)
|
|
.modifier(ImportSettingsFileImporterViewModifier(isPresented: $navigation.presentingSettingsFileImporter))
|
|
#endif
|
|
#if os(iOS)
|
|
.backport
|
|
.scrollDismissesKeyboardInteractively()
|
|
#endif
|
|
.alert(isPresented: $model.presentingAlert) { model.alert }
|
|
}
|
|
|
|
var settings: some View {
|
|
#if os(macOS)
|
|
TabView(selection: $selection) {
|
|
Form {
|
|
BrowsingSettings()
|
|
}
|
|
.tabItem {
|
|
Label("Browsing", systemImage: "list.and.film")
|
|
}
|
|
.tag(Tabs.browsing)
|
|
|
|
Form {
|
|
PlayerSettings()
|
|
}
|
|
.tabItem {
|
|
Label("Player", systemImage: "play.rectangle")
|
|
}
|
|
.tag(Tabs.player)
|
|
|
|
Form {
|
|
PlayerControlsSettings()
|
|
}
|
|
.tabItem {
|
|
Label("Controls", systemImage: "hand.tap")
|
|
}
|
|
.tag(Tabs.controls)
|
|
|
|
Form {
|
|
QualitySettings()
|
|
}
|
|
.tabItem {
|
|
Label("Quality", systemImage: "4k.tv")
|
|
}
|
|
.tag(Tabs.quality)
|
|
|
|
Form {
|
|
HistorySettings()
|
|
}
|
|
.tabItem {
|
|
Label("History", systemImage: "clock.arrow.circlepath")
|
|
}
|
|
.tag(Tabs.history)
|
|
|
|
if !accounts.isEmpty {
|
|
Form {
|
|
SponsorBlockSettings()
|
|
}
|
|
.tabItem {
|
|
Label("SponsorBlock", systemImage: "dollarsign.circle")
|
|
}
|
|
.tag(Tabs.sponsorBlock)
|
|
}
|
|
Form {
|
|
LocationsSettings()
|
|
}
|
|
.tabItem {
|
|
Label("Locations", systemImage: "globe")
|
|
}
|
|
.tag(Tabs.locations)
|
|
|
|
Group {
|
|
AdvancedSettings()
|
|
}
|
|
.tabItem {
|
|
Label("Advanced", systemImage: "wrench.and.screwdriver")
|
|
}
|
|
.tag(Tabs.advanced)
|
|
|
|
Group {
|
|
ExportSettings()
|
|
}
|
|
.tabItem {
|
|
Label("Export", systemImage: "square.and.arrow.up")
|
|
}
|
|
.tag(Tabs.importExport)
|
|
|
|
Form {
|
|
Help()
|
|
}
|
|
.tabItem {
|
|
Label("Help", systemImage: "questionmark.circle")
|
|
}
|
|
.tag(Tabs.help)
|
|
}
|
|
.padding(20)
|
|
.frame(width: 700, height: windowHeight)
|
|
#else
|
|
NavigationView {
|
|
settingsList
|
|
.navigationTitle("Settings")
|
|
}
|
|
#if os(tvOS)
|
|
.background(Color.background(scheme: colorScheme).ignoresSafeArea())
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
struct SettingsLabel: LabelStyle {
|
|
func makeBody(configuration: Configuration) -> some View {
|
|
#if os(tvOS)
|
|
Label {
|
|
configuration.title.padding(.leading, 10)
|
|
} icon: {
|
|
configuration.icon
|
|
}
|
|
#else
|
|
Label(configuration)
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if !os(macOS)
|
|
var settingsList: some View {
|
|
List {
|
|
#if os(tvOS)
|
|
if !accounts.isEmpty {
|
|
Section(header: Text("Current Location")) {
|
|
NavigationLink(destination: AccountsView()) {
|
|
if let account = accounts.current {
|
|
Text(account.isPublic ? account.description : "\(account.description) — \(account.instance.shortDescription)")
|
|
} else {
|
|
Text("Not Selected")
|
|
}
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
Divider()
|
|
}
|
|
#endif
|
|
|
|
Section {
|
|
NavigationLink {
|
|
BrowsingSettings()
|
|
} label: {
|
|
Label("Browsing", systemImage: "list.and.film").labelStyle(SettingsLabel())
|
|
}
|
|
#if os(tvOS)
|
|
.buttonStyle(.plain)
|
|
#endif
|
|
|
|
NavigationLink {
|
|
PlayerSettings()
|
|
} label: {
|
|
Label("Player", systemImage: "play.rectangle").labelStyle(SettingsLabel())
|
|
}
|
|
#if os(tvOS)
|
|
.buttonStyle(.plain)
|
|
#endif
|
|
|
|
NavigationLink {
|
|
PlayerControlsSettings()
|
|
} label: {
|
|
Label("Controls", systemImage: "hand.tap").labelStyle(SettingsLabel())
|
|
}
|
|
#if os(tvOS)
|
|
.buttonStyle(.plain)
|
|
#endif
|
|
|
|
NavigationLink {
|
|
QualitySettings()
|
|
} label: {
|
|
Label("Quality", systemImage: "4k.tv").labelStyle(SettingsLabel())
|
|
}
|
|
#if os(tvOS)
|
|
.buttonStyle(.plain)
|
|
#endif
|
|
|
|
NavigationLink {
|
|
HistorySettings()
|
|
} label: {
|
|
Label("History", systemImage: "clock.arrow.circlepath").labelStyle(SettingsLabel())
|
|
}
|
|
#if os(tvOS)
|
|
.buttonStyle(.plain)
|
|
#endif
|
|
|
|
if !accounts.isEmpty {
|
|
NavigationLink {
|
|
SponsorBlockSettings()
|
|
} label: {
|
|
Label("SponsorBlock", systemImage: "dollarsign.circle").labelStyle(SettingsLabel())
|
|
}
|
|
#if os(tvOS)
|
|
.buttonStyle(.plain)
|
|
#endif
|
|
}
|
|
|
|
NavigationLink {
|
|
LocationsSettings()
|
|
} label: {
|
|
Label("Locations", systemImage: "globe").labelStyle(SettingsLabel())
|
|
}
|
|
#if os(tvOS)
|
|
.buttonStyle(.plain)
|
|
#endif
|
|
|
|
NavigationLink {
|
|
AdvancedSettings()
|
|
} label: {
|
|
Label("Advanced", systemImage: "wrench.and.screwdriver").labelStyle(SettingsLabel())
|
|
}
|
|
#if os(tvOS)
|
|
.buttonStyle(.plain)
|
|
#endif
|
|
}
|
|
#if os(tvOS)
|
|
.padding(.horizontal, 20)
|
|
#endif
|
|
|
|
importView
|
|
|
|
Section(footer: helpFooter) {
|
|
NavigationLink {
|
|
Help()
|
|
} label: {
|
|
Label("Help", systemImage: "questionmark.circle").labelStyle(SettingsLabel())
|
|
}
|
|
#if os(tvOS)
|
|
.buttonStyle(.plain)
|
|
#endif
|
|
}
|
|
#if os(tvOS)
|
|
.padding(.horizontal, 20)
|
|
#endif
|
|
|
|
#if !os(tvOS)
|
|
Section(header: Text("Contact"), footer: versionString) {
|
|
Link(destination: Self.discordURL) {
|
|
HStack {
|
|
Image("Discord")
|
|
.resizable()
|
|
.renderingMode(.template)
|
|
.frame(maxWidth: 30, maxHeight: 30)
|
|
.padding(.trailing, 6)
|
|
Text("Discord Server")
|
|
}
|
|
}
|
|
|
|
Link(destination: Self.matrixURL) {
|
|
HStack {
|
|
Image("Matrix")
|
|
.resizable()
|
|
.renderingMode(.template)
|
|
.frame(maxWidth: 30, maxHeight: 30)
|
|
.padding(.trailing, 6)
|
|
Text("Matrix Chat")
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarLeading) {
|
|
#if !os(tvOS)
|
|
Button("Done") {
|
|
presentationMode.wrappedValue.dismiss()
|
|
}
|
|
.keyboardShortcut(.cancelAction)
|
|
#endif
|
|
}
|
|
}
|
|
.frame(maxWidth: 1000)
|
|
#if os(iOS)
|
|
.listStyle(.insetGrouped)
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
var importView: some View {
|
|
Section {
|
|
#if os(tvOS)
|
|
NavigationLink(destination: LazyView(ImportSettings())) {
|
|
Label("Import Settings", systemImage: "square.and.arrow.down")
|
|
.labelStyle(SettingsLabel())
|
|
}
|
|
.buttonStyle(.plain)
|
|
.padding(.horizontal, 20)
|
|
#else
|
|
Button(action: importSettings) {
|
|
Label("Import Settings...", systemImage: "square.and.arrow.down")
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.contentShape(Rectangle())
|
|
}
|
|
.foregroundColor(.accentColor)
|
|
.buttonStyle(.plain)
|
|
|
|
NavigationLink(destination: LazyView(ExportSettings())) {
|
|
Label("Export Settings", systemImage: "square.and.arrow.up")
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.contentShape(Rectangle())
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
func importSettings() {
|
|
navigation.presentingSettingsFileImporter = true
|
|
}
|
|
|
|
#if os(macOS)
|
|
private var windowHeight: Double {
|
|
switch selection {
|
|
case .browsing:
|
|
return 800
|
|
case .player:
|
|
return 850
|
|
case .controls:
|
|
return 970
|
|
case .quality:
|
|
return 450
|
|
case .history:
|
|
return 600
|
|
case .sponsorBlock:
|
|
return 980
|
|
case .locations:
|
|
return 600
|
|
case .advanced:
|
|
return 700
|
|
case .importExport:
|
|
return 580
|
|
case .help:
|
|
return 650
|
|
}
|
|
}
|
|
#endif
|
|
|
|
var helpFooter: some View {
|
|
#if os(tvOS)
|
|
versionString
|
|
#else
|
|
EmptyView()
|
|
#endif
|
|
}
|
|
|
|
private var versionString: some View {
|
|
Text("Yattee \(YatteeApp.version) (build \(YatteeApp.build))")
|
|
#if os(tvOS)
|
|
.foregroundColor(.secondary)
|
|
#endif
|
|
}
|
|
}
|
|
|
|
struct SettingsView_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
SettingsView()
|
|
.injectFixtureEnvironmentObjects()
|
|
#if os(macOS)
|
|
.frame(width: 600, height: 300)
|
|
#else
|
|
.navigationViewStyle(.stack)
|
|
#endif
|
|
}
|
|
}
|