mirror of
https://github.com/yattee/yattee.git
synced 2025-04-28 07:50:33 +05:30
New playback settings sheet
This commit is contained in:
parent
c01ff56854
commit
809bcd183a
@ -78,6 +78,7 @@ final class NavigationModel: ObservableObject {
|
|||||||
@Published var presentingPlaylist = false
|
@Published var presentingPlaylist = false
|
||||||
@Published var sidebarSectionChanged = false
|
@Published var sidebarSectionChanged = false
|
||||||
|
|
||||||
|
@Published var presentingPlaybackSettings = false
|
||||||
@Published var presentingOpenVideos = false
|
@Published var presentingOpenVideos = false
|
||||||
@Published var presentingSettings = false
|
@Published var presentingSettings = false
|
||||||
@Published var presentingAccounts = false
|
@Published var presentingAccounts = false
|
||||||
|
@ -95,6 +95,13 @@ final class PlayerModel: ObservableObject {
|
|||||||
@Published var availableStreams = [Stream]() { didSet { handleAvailableStreamsChange() } }
|
@Published var availableStreams = [Stream]() { didSet { handleAvailableStreamsChange() } }
|
||||||
@Published var streamSelection: Stream? { didSet { rebuildTVMenu() } }
|
@Published var streamSelection: Stream? { didSet { rebuildTVMenu() } }
|
||||||
|
|
||||||
|
@Published var captions: Captions? { didSet {
|
||||||
|
mpvBackend.captions = captions
|
||||||
|
if let code = captions?.code {
|
||||||
|
Defaults[.captionsLanguageCode] = code
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
@Published var queue = [PlayerQueueItem]() { didSet { handleQueueChange() } }
|
@Published var queue = [PlayerQueueItem]() { didSet { handleQueueChange() } }
|
||||||
@Published var currentItem: PlayerQueueItem! { didSet { handleCurrentItemChange() } }
|
@Published var currentItem: PlayerQueueItem! { didSet { handleCurrentItemChange() } }
|
||||||
@Published var videoBeingOpened: Video? { didSet { seek.reset() } }
|
@Published var videoBeingOpened: Video? { didSet { seek.reset() } }
|
||||||
@ -666,6 +673,7 @@ final class PlayerModel: ObservableObject {
|
|||||||
|
|
||||||
func handleCurrentItemChange() {
|
func handleCurrentItemChange() {
|
||||||
if currentItem == nil {
|
if currentItem == nil {
|
||||||
|
captions = nil
|
||||||
FeedModel.shared.calculateUnwatchedFeed()
|
FeedModel.shared.calculateUnwatchedFeed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadAvailableStreams(_ video: Video, onCompletion: @escaping (ResponseInfo) -> Void = { _ in }) {
|
func loadAvailableStreams(_ video: Video, onCompletion: @escaping (ResponseInfo) -> Void = { _ in }) {
|
||||||
|
captions = nil
|
||||||
availableStreams = []
|
availableStreams = []
|
||||||
|
|
||||||
guard let playerInstance else { return }
|
guard let playerInstance else { return }
|
||||||
|
@ -104,6 +104,13 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
#endif
|
#endif
|
||||||
|
#if os(iOS)
|
||||||
|
.background(
|
||||||
|
EmptyView().sheet(isPresented: $navigation.presentingPlaybackSettings) {
|
||||||
|
PlaybackSettings()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
#endif
|
||||||
.background(
|
.background(
|
||||||
EmptyView().sheet(isPresented: $navigation.presentingOpenVideos) {
|
EmptyView().sheet(isPresented: $navigation.presentingOpenVideos) {
|
||||||
OpenVideosView()
|
OpenVideosView()
|
||||||
|
@ -44,6 +44,7 @@ struct PlayerControls: View {
|
|||||||
@Default(.playerControlsMusicModeEnabled) private var playerControlsMusicModeEnabled
|
@Default(.playerControlsMusicModeEnabled) private var playerControlsMusicModeEnabled
|
||||||
|
|
||||||
private let controlsOverlayModel = ControlOverlaysModel.shared
|
private let controlsOverlayModel = ControlOverlaysModel.shared
|
||||||
|
private var navigation = NavigationModel.shared
|
||||||
|
|
||||||
var playerControlsLayout: PlayerControlsLayout {
|
var playerControlsLayout: PlayerControlsLayout {
|
||||||
player.playingFullScreen ? fullScreenPlayerControlsLayout : regularPlayerControlsLayout
|
player.playingFullScreen ? fullScreenPlayerControlsLayout : regularPlayerControlsLayout
|
||||||
@ -345,7 +346,11 @@ struct PlayerControls: View {
|
|||||||
private var settingsButton: some View {
|
private var settingsButton: some View {
|
||||||
button("settings", systemImage: "gearshape") {
|
button("settings", systemImage: "gearshape") {
|
||||||
withAnimation(Self.animation) {
|
withAnimation(Self.animation) {
|
||||||
|
#if os(tvOS)
|
||||||
controlsOverlayModel.toggle()
|
controlsOverlayModel.toggle()
|
||||||
|
#else
|
||||||
|
navigation.presentingPlaybackSettings = true
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
|
417
Shared/Player/PlaybackSettings.swift
Normal file
417
Shared/Player/PlaybackSettings.swift
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
import Defaults
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct PlaybackSettings: View {
|
||||||
|
@ObservedObject private var player = PlayerModel.shared
|
||||||
|
private var model = PlayerControlsModel.shared
|
||||||
|
|
||||||
|
@State private var contentSize: CGSize = .zero
|
||||||
|
|
||||||
|
@Default(.showMPVPlaybackStats) private var showMPVPlaybackStats
|
||||||
|
@Default(.qualityProfiles) private var qualityProfiles
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
enum Field: Hashable {
|
||||||
|
case qualityProfile
|
||||||
|
case stream
|
||||||
|
case increaseRate
|
||||||
|
case decreaseRate
|
||||||
|
case captions
|
||||||
|
}
|
||||||
|
|
||||||
|
@FocusState private var focusedField: Field?
|
||||||
|
@State private var presentingButtonHintAlert = false
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
#if DEBUG
|
||||||
|
// TODO: remove
|
||||||
|
if #available(iOS 15.0, macOS 12.0, *) {
|
||||||
|
Self._printChanges()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return ScrollView {
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
HStack {
|
||||||
|
Button {
|
||||||
|
withAnimation(ControlOverlaysModel.animation) {
|
||||||
|
NavigationModel.shared.presentingPlaybackSettings = false
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label("Close", systemImage: "xmark")
|
||||||
|
.padding(.vertical)
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.frame(maxWidth: 50, alignment: .leading)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
.keyboardShortcut(.cancelAction)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
Text("Playback Settings")
|
||||||
|
.font(.headline)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
.frame(maxWidth: 50, alignment: .trailing)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
controlsHeader("Rate")
|
||||||
|
Spacer()
|
||||||
|
HStack(spacing: rateButtonsSpacing) {
|
||||||
|
decreaseRateButton
|
||||||
|
#if os(tvOS)
|
||||||
|
.focused($focusedField, equals: .decreaseRate)
|
||||||
|
#endif
|
||||||
|
rateButton
|
||||||
|
increaseRateButton
|
||||||
|
#if os(tvOS)
|
||||||
|
.focused($focusedField, equals: .increaseRate)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if player.activeBackend == .mpv {
|
||||||
|
HStack {
|
||||||
|
controlsHeader("Captions")
|
||||||
|
Spacer()
|
||||||
|
captionsButton
|
||||||
|
#if os(tvOS)
|
||||||
|
.focused($focusedField, equals: .captions)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
controlsHeader("Quality Profile".localized())
|
||||||
|
Spacer()
|
||||||
|
qualityProfileButton
|
||||||
|
#if os(tvOS)
|
||||||
|
.focused($focusedField, equals: .qualityProfile)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
controlsHeader("Stream".localized())
|
||||||
|
Spacer()
|
||||||
|
streamButton
|
||||||
|
#if os(tvOS)
|
||||||
|
.focused($focusedField, equals: .stream)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
controlsHeader("Backend".localized())
|
||||||
|
Spacer()
|
||||||
|
backendButtons
|
||||||
|
}
|
||||||
|
|
||||||
|
if player.activeBackend == .mpv,
|
||||||
|
showMPVPlaybackStats
|
||||||
|
{
|
||||||
|
Section(header: controlsHeader("Statistics".localized()).padding(.top, 15)) {
|
||||||
|
PlaybackStatsView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if os(iOS)
|
||||||
|
.padding(.top, verticalSizeClass == .regular ? 10 : 0)
|
||||||
|
.padding(.bottom, 15)
|
||||||
|
#else
|
||||||
|
.padding(.top)
|
||||||
|
#endif
|
||||||
|
.padding(.horizontal)
|
||||||
|
.overlay(
|
||||||
|
GeometryReader { geometry in
|
||||||
|
Color.clear.onAppear {
|
||||||
|
contentSize = geometry.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.animation(nil, value: player.activeBackend)
|
||||||
|
.frame(alignment: .topLeading)
|
||||||
|
|
||||||
|
.ignoresSafeArea(.all, edges: .bottom)
|
||||||
|
.backport
|
||||||
|
.playbackSettingsPresentationDetents()
|
||||||
|
#if os(macOS)
|
||||||
|
.frame(width: 500)
|
||||||
|
.frame(minHeight: 350, maxHeight: 450)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private func controlsHeader(_ text: String) -> some View {
|
||||||
|
Text(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var backendButtons: some View {
|
||||||
|
ForEach(PlayerBackendType.allCases, id: \.self) { backend in
|
||||||
|
backendButton(backend)
|
||||||
|
.frame(height: 40)
|
||||||
|
#if os(iOS)
|
||||||
|
.padding(12)
|
||||||
|
.frame(height: 50)
|
||||||
|
.background(RoundedRectangle(cornerRadius: 4).foregroundColor(player.activeBackend == backend ? Color.accentColor : Color.clear))
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func backendButton(_ backend: PlayerBackendType) -> some View {
|
||||||
|
Button {
|
||||||
|
player.saveTime {
|
||||||
|
player.changeActiveBackend(from: player.activeBackend, to: backend)
|
||||||
|
model.resetTimer()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text(backend.label)
|
||||||
|
.fontWeight(player.activeBackend == backend ? .bold : .regular)
|
||||||
|
#if os(iOS)
|
||||||
|
.foregroundColor(player.activeBackend == backend ? .white : .secondary)
|
||||||
|
#else
|
||||||
|
.foregroundColor(player.activeBackend == backend ? .accentColor : .secondary)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#if os(macOS)
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder private var rateButton: some View {
|
||||||
|
#if os(macOS)
|
||||||
|
ratePicker
|
||||||
|
.labelsHidden()
|
||||||
|
.frame(maxWidth: 100)
|
||||||
|
#elseif os(iOS)
|
||||||
|
Menu {
|
||||||
|
ratePicker
|
||||||
|
} label: {
|
||||||
|
Text(player.rateLabel(player.currentRate))
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.frame(width: 70)
|
||||||
|
}
|
||||||
|
.transaction { t in t.animation = .none }
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.frame(width: 70, height: 40)
|
||||||
|
#else
|
||||||
|
Text(player.rateLabel(player.currentRate))
|
||||||
|
.frame(minWidth: 120)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
var ratePicker: some View {
|
||||||
|
Picker("Rate", selection: $player.currentRate) {
|
||||||
|
ForEach(player.backend.suggestedPlaybackRates, id: \.self) { rate in
|
||||||
|
Text(player.rateLabel(rate)).tag(rate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.transaction { t in t.animation = .none }
|
||||||
|
}
|
||||||
|
|
||||||
|
private var increaseRateButton: some View {
|
||||||
|
let increasedRate = player.backend.suggestedPlaybackRates.first { $0 > player.currentRate }
|
||||||
|
return Button {
|
||||||
|
if let rate = increasedRate {
|
||||||
|
player.currentRate = rate
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label("Increase rate", systemImage: "plus")
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
.imageScale(.large)
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
#if os(iOS)
|
||||||
|
.padding(12)
|
||||||
|
.frame(width: 40, height: 40)
|
||||||
|
|
||||||
|
.background(RoundedRectangle(cornerRadius: 4).strokeBorder(Color.accentColor, lineWidth: 1))
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#if os(macOS)
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
#endif
|
||||||
|
.disabled(increasedRate.isNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var decreaseRateButton: some View {
|
||||||
|
let decreasedRate = player.backend.suggestedPlaybackRates.last { $0 < player.currentRate }
|
||||||
|
|
||||||
|
return Button {
|
||||||
|
if let rate = decreasedRate {
|
||||||
|
player.currentRate = rate
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label("Decrease rate", systemImage: "minus")
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
.imageScale(.large)
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
#if os(iOS)
|
||||||
|
.padding(12)
|
||||||
|
.frame(width: 40, height: 40)
|
||||||
|
|
||||||
|
.background(RoundedRectangle(cornerRadius: 4).strokeBorder(Color.accentColor, lineWidth: 1))
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#if os(macOS)
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
#elseif os(iOS)
|
||||||
|
#endif
|
||||||
|
.disabled(decreasedRate.isNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var rateButtonsSpacing: Double {
|
||||||
|
#if os(tvOS)
|
||||||
|
10
|
||||||
|
#else
|
||||||
|
8
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder private var qualityProfileButton: some View {
|
||||||
|
#if os(macOS)
|
||||||
|
qualityProfilePicker
|
||||||
|
.labelsHidden()
|
||||||
|
.frame(maxWidth: 300)
|
||||||
|
#elseif os(iOS)
|
||||||
|
Menu {
|
||||||
|
qualityProfilePicker
|
||||||
|
} label: {
|
||||||
|
Text(player.qualityProfileSelection?.description ?? "Automatic".localized())
|
||||||
|
.frame(maxWidth: 240, alignment: .trailing)
|
||||||
|
}
|
||||||
|
.transaction { t in t.animation = .none }
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
.frame(maxWidth: 240, alignment: .trailing)
|
||||||
|
.frame(height: 40)
|
||||||
|
#else
|
||||||
|
ControlsOverlayButton(focusedField: $focusedField, field: .qualityProfile) {
|
||||||
|
Text(player.qualityProfileSelection?.description ?? "Automatic".localized())
|
||||||
|
.lineLimit(1)
|
||||||
|
.frame(maxWidth: 320)
|
||||||
|
}
|
||||||
|
.contextMenu {
|
||||||
|
Button("Automatic") { player.qualityProfileSelection = nil }
|
||||||
|
|
||||||
|
ForEach(qualityProfiles) { qualityProfile in
|
||||||
|
Button {
|
||||||
|
player.qualityProfileSelection = qualityProfile
|
||||||
|
} label: {
|
||||||
|
Text(qualityProfile.description)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button("Cancel", role: .cancel) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private var qualityProfilePicker: some View {
|
||||||
|
Picker("Quality Profile", selection: $player.qualityProfileSelection) {
|
||||||
|
Text("Automatic").tag(QualityProfile?.none)
|
||||||
|
ForEach(qualityProfiles) { qualityProfile in
|
||||||
|
Text(qualityProfile.description).tag(qualityProfile as QualityProfile?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.transaction { t in t.animation = .none }
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder private var streamButton: some View {
|
||||||
|
#if os(macOS)
|
||||||
|
StreamControl()
|
||||||
|
.labelsHidden()
|
||||||
|
.frame(maxWidth: 300)
|
||||||
|
#elseif os(iOS)
|
||||||
|
Menu {
|
||||||
|
StreamControl()
|
||||||
|
} label: {
|
||||||
|
Text(player.streamSelection?.resolutionAndFormat ?? "loading...")
|
||||||
|
.frame(width: 140, height: 40, alignment: .trailing)
|
||||||
|
.foregroundColor(player.streamSelection == nil ? .secondary : .accentColor)
|
||||||
|
}
|
||||||
|
.transaction { t in t.animation = .none }
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.frame(height: 40, alignment: .trailing)
|
||||||
|
#else
|
||||||
|
StreamControl(focusedField: $focusedField)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder private var captionsButton: some View {
|
||||||
|
#if os(macOS)
|
||||||
|
captionsPicker
|
||||||
|
.labelsHidden()
|
||||||
|
.frame(maxWidth: 300)
|
||||||
|
#elseif os(iOS)
|
||||||
|
Menu {
|
||||||
|
captionsPicker
|
||||||
|
} label: {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
Image(systemName: "text.bubble")
|
||||||
|
if let captions = player.captions {
|
||||||
|
Text(captions.code)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(alignment: .trailing)
|
||||||
|
.frame(height: 40)
|
||||||
|
}
|
||||||
|
.transaction { t in t.animation = .none }
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
#else
|
||||||
|
ControlsOverlayButton(focusedField: $focusedField, field: .captions) {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
Image(systemName: "text.bubble")
|
||||||
|
if let captions = captionsBinding.wrappedValue {
|
||||||
|
Text(captions.code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: 320)
|
||||||
|
}
|
||||||
|
.contextMenu {
|
||||||
|
Button("Disabled") { captionsBinding.wrappedValue = nil }
|
||||||
|
|
||||||
|
ForEach(player.currentVideo?.captions ?? []) { caption in
|
||||||
|
Button(caption.description) { captionsBinding.wrappedValue = caption }
|
||||||
|
}
|
||||||
|
Button("Cancel", role: .cancel) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder private var captionsPicker: some View {
|
||||||
|
let captions = player.currentVideo?.captions ?? []
|
||||||
|
Picker("Captions", selection: $player.captions) {
|
||||||
|
if captions.isEmpty {
|
||||||
|
Text("Not available")
|
||||||
|
} else {
|
||||||
|
Text("Disabled").tag(Captions?.none)
|
||||||
|
}
|
||||||
|
ForEach(captions) { caption in
|
||||||
|
Text(caption.description).tag(Optional(caption))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disabled(captions.isEmpty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PlaybackSettings_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
PlaybackSettings()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension Backport where Content: View {
|
||||||
|
@ViewBuilder func playbackSettingsPresentationDetents() -> some View {
|
||||||
|
if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) {
|
||||||
|
content
|
||||||
|
.presentationDetents([.height(350), .large])
|
||||||
|
} else {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -119,7 +119,11 @@ struct VideoActions: View {
|
|||||||
case .settings:
|
case .settings:
|
||||||
actionButton("Settings", systemImage: "gear") {
|
actionButton("Settings", systemImage: "gear") {
|
||||||
withAnimation(ControlOverlaysModel.animation) {
|
withAnimation(ControlOverlaysModel.animation) {
|
||||||
|
#if os(tvOS)
|
||||||
ControlOverlaysModel.shared.show()
|
ControlOverlaysModel.shared.show()
|
||||||
|
#else
|
||||||
|
navigation.presentingPlaybackSettings = true
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .next:
|
case .next:
|
||||||
|
@ -207,7 +207,7 @@ struct VideoDetails: View {
|
|||||||
.zIndex(1)
|
.zIndex(1)
|
||||||
|
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
if #available(iOS 15, macOS 12, *) {
|
if #available(iOS 16, macOS 13, *) {
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.fill(
|
.fill(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
|
@ -406,6 +406,13 @@ struct VideoPlayerView: View {
|
|||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.statusBar(hidden: fullScreenPlayer)
|
.statusBar(hidden: fullScreenPlayer)
|
||||||
#endif
|
#endif
|
||||||
|
#if os(macOS)
|
||||||
|
.background(
|
||||||
|
EmptyView().sheet(isPresented: $navigation.presentingPlaybackSettings) {
|
||||||
|
PlaybackSettings()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
var detailsNeedBottomPadding: Bool {
|
var detailsNeedBottomPadding: Bool {
|
||||||
|
@ -67,6 +67,9 @@ struct AdvancedSettings: View {
|
|||||||
Text("cache-secs")
|
Text("cache-secs")
|
||||||
.frame(minWidth: 140, alignment: .leading)
|
.frame(minWidth: 140, alignment: .leading)
|
||||||
TextField("cache-secs", text: $mpvCacheSecs)
|
TextField("cache-secs", text: $mpvCacheSecs)
|
||||||
|
#if !os(macOS)
|
||||||
|
.keyboardType(.URL)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.multilineTextAlignment(.trailing)
|
.multilineTextAlignment(.trailing)
|
||||||
|
|
||||||
@ -74,6 +77,9 @@ struct AdvancedSettings: View {
|
|||||||
Text("cache-pause-wait")
|
Text("cache-pause-wait")
|
||||||
.frame(minWidth: 140, alignment: .leading)
|
.frame(minWidth: 140, alignment: .leading)
|
||||||
TextField("cache-pause-wait", text: $mpvCachePauseWait)
|
TextField("cache-pause-wait", text: $mpvCachePauseWait)
|
||||||
|
#if !os(macOS)
|
||||||
|
.keyboardType(.URL)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.multilineTextAlignment(.trailing)
|
.multilineTextAlignment(.trailing)
|
||||||
|
|
||||||
|
@ -45,10 +45,6 @@ struct PlayerControlsSettings: View {
|
|||||||
List {
|
List {
|
||||||
sections
|
sections
|
||||||
}
|
}
|
||||||
#if !os(tvOS)
|
|
||||||
.backport
|
|
||||||
.scrollDismissesKeyboard()
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@ -215,23 +211,23 @@ struct PlayerControlsSettings: View {
|
|||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
Button {
|
Label("Plus", systemImage: "plus")
|
||||||
|
.imageScale(.large)
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.padding(7)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
#if os(iOS)
|
||||||
|
.background(RoundedRectangle(cornerRadius: 4).strokeBorder(lineWidth: 1).foregroundColor(.accentColor))
|
||||||
|
#endif
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
var intValue = Int(value.wrappedValue) ?? 10
|
var intValue = Int(value.wrappedValue) ?? 10
|
||||||
intValue += 5
|
intValue += 5
|
||||||
if intValue <= 0 {
|
if intValue <= 0 {
|
||||||
intValue = 5
|
intValue = 5
|
||||||
}
|
}
|
||||||
value.wrappedValue = String(intValue)
|
value.wrappedValue = String(intValue)
|
||||||
} label: {
|
|
||||||
Label("Plus", systemImage: "plus")
|
|
||||||
.imageScale(.large)
|
|
||||||
.padding(7)
|
|
||||||
.labelStyle(.iconOnly)
|
|
||||||
.frame(minHeight: 35)
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
}
|
}
|
||||||
.background(RoundedRectangle(cornerRadius: 4).stroke(lineWidth: 1))
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@ -250,24 +246,24 @@ struct PlayerControlsSettings: View {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
Button {
|
Label("Minus", systemImage: "minus")
|
||||||
|
.imageScale(.large)
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.padding(7)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
#if os(iOS)
|
||||||
|
.frame(minHeight: 35)
|
||||||
|
.background(RoundedRectangle(cornerRadius: 4).strokeBorder(lineWidth: 1).foregroundColor(.accentColor))
|
||||||
|
#endif
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
var intValue = Int(value.wrappedValue) ?? 10
|
var intValue = Int(value.wrappedValue) ?? 10
|
||||||
intValue -= 5
|
intValue -= 5
|
||||||
if intValue <= 0 {
|
if intValue <= 0 {
|
||||||
intValue = 5
|
intValue = 5
|
||||||
}
|
}
|
||||||
value.wrappedValue = String(intValue)
|
value.wrappedValue = String(intValue)
|
||||||
} label: {
|
|
||||||
Label("Minus", systemImage: "minus")
|
|
||||||
.imageScale(.large)
|
|
||||||
.padding(7)
|
|
||||||
.labelStyle(.iconOnly)
|
|
||||||
.frame(minHeight: 35)
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
}
|
}
|
||||||
.background(RoundedRectangle(cornerRadius: 4).stroke(lineWidth: 1))
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,7 +243,7 @@ struct SettingsView: View {
|
|||||||
case .player:
|
case .player:
|
||||||
return 450
|
return 450
|
||||||
case .controls:
|
case .controls:
|
||||||
return 800
|
return 850
|
||||||
case .quality:
|
case .quality:
|
||||||
return 420
|
return 420
|
||||||
case .history:
|
case .history:
|
||||||
|
@ -687,6 +687,11 @@
|
|||||||
37A362BA2953707F00BDF328 /* ClearQueueButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A362B92953707F00BDF328 /* ClearQueueButton.swift */; };
|
37A362BA2953707F00BDF328 /* ClearQueueButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A362B92953707F00BDF328 /* ClearQueueButton.swift */; };
|
||||||
37A362BB2953707F00BDF328 /* ClearQueueButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A362B92953707F00BDF328 /* ClearQueueButton.swift */; };
|
37A362BB2953707F00BDF328 /* ClearQueueButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A362B92953707F00BDF328 /* ClearQueueButton.swift */; };
|
||||||
37A362BC2953707F00BDF328 /* ClearQueueButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A362B92953707F00BDF328 /* ClearQueueButton.swift */; };
|
37A362BC2953707F00BDF328 /* ClearQueueButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A362B92953707F00BDF328 /* ClearQueueButton.swift */; };
|
||||||
|
37A362BE29537AAA00BDF328 /* PlaybackSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A362BD29537AAA00BDF328 /* PlaybackSettings.swift */; };
|
||||||
|
37A362BF29537AAA00BDF328 /* PlaybackSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A362BD29537AAA00BDF328 /* PlaybackSettings.swift */; };
|
||||||
|
37A362C229537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A362C129537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift */; };
|
||||||
|
37A362C329537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A362C129537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift */; };
|
||||||
|
37A362C429537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A362C129537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift */; };
|
||||||
37A5DBC4285DFF5400CA4DD1 /* SwiftUIPager in Frameworks */ = {isa = PBXBuildFile; productRef = 37A5DBC3285DFF5400CA4DD1 /* SwiftUIPager */; };
|
37A5DBC4285DFF5400CA4DD1 /* SwiftUIPager in Frameworks */ = {isa = PBXBuildFile; productRef = 37A5DBC3285DFF5400CA4DD1 /* SwiftUIPager */; };
|
||||||
37A5DBC6285E06B100CA4DD1 /* SwiftUIPager in Frameworks */ = {isa = PBXBuildFile; productRef = 37A5DBC5285E06B100CA4DD1 /* SwiftUIPager */; };
|
37A5DBC6285E06B100CA4DD1 /* SwiftUIPager in Frameworks */ = {isa = PBXBuildFile; productRef = 37A5DBC5285E06B100CA4DD1 /* SwiftUIPager */; };
|
||||||
37A5DBC8285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */; };
|
37A5DBC8285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */; };
|
||||||
@ -1363,6 +1368,8 @@
|
|||||||
379F141E289ECE7F00DE48B5 /* QualitySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QualitySettings.swift; sourceTree = "<group>"; };
|
379F141E289ECE7F00DE48B5 /* QualitySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QualitySettings.swift; sourceTree = "<group>"; };
|
||||||
37A2B345294723850050933E /* CacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheModel.swift; sourceTree = "<group>"; };
|
37A2B345294723850050933E /* CacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheModel.swift; sourceTree = "<group>"; };
|
||||||
37A362B92953707F00BDF328 /* ClearQueueButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearQueueButton.swift; sourceTree = "<group>"; };
|
37A362B92953707F00BDF328 /* ClearQueueButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearQueueButton.swift; sourceTree = "<group>"; };
|
||||||
|
37A362BD29537AAA00BDF328 /* PlaybackSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackSettings.swift; sourceTree = "<group>"; };
|
||||||
|
37A362C129537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlaybackSettingsPresentationDetents+Backport.swift"; sourceTree = "<group>"; };
|
||||||
37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlBackgroundModifier.swift; sourceTree = "<group>"; };
|
37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlBackgroundModifier.swift; sourceTree = "<group>"; };
|
||||||
37A81BF8294BD1440081D322 /* WatchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchView.swift; sourceTree = "<group>"; };
|
37A81BF8294BD1440081D322 /* WatchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchView.swift; sourceTree = "<group>"; };
|
||||||
37A9965926D6F8CA006E3224 /* HorizontalCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalCells.swift; sourceTree = "<group>"; };
|
37A9965926D6F8CA006E3224 /* HorizontalCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalCells.swift; sourceTree = "<group>"; };
|
||||||
@ -1803,6 +1810,8 @@
|
|||||||
37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */,
|
37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */,
|
||||||
37BE0BD526A1D4A90092E2DB /* AppleAVPlayerViewController.swift */,
|
37BE0BD526A1D4A90092E2DB /* AppleAVPlayerViewController.swift */,
|
||||||
37BA221029526A18000DAD1F /* ControlsGradientView.swift */,
|
37BA221029526A18000DAD1F /* ControlsGradientView.swift */,
|
||||||
|
37A362BD29537AAA00BDF328 /* PlaybackSettings.swift */,
|
||||||
|
37A362C129537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift */,
|
||||||
375F740F289DC35A00747050 /* PlayerBackendView.swift */,
|
375F740F289DC35A00747050 /* PlayerBackendView.swift */,
|
||||||
374DE87F28BB896C0062BBF2 /* PlayerDragGesture.swift */,
|
374DE87F28BB896C0062BBF2 /* PlayerDragGesture.swift */,
|
||||||
3703100127B0713600ECDDAA /* PlayerGestures.swift */,
|
3703100127B0713600ECDDAA /* PlayerGestures.swift */,
|
||||||
@ -3057,6 +3066,7 @@
|
|||||||
37ECED56289FE166002BC2C9 /* SafeArea.swift in Sources */,
|
37ECED56289FE166002BC2C9 /* SafeArea.swift in Sources */,
|
||||||
37CEE4BD2677B670005A1EFE /* SingleAssetStream.swift in Sources */,
|
37CEE4BD2677B670005A1EFE /* SingleAssetStream.swift in Sources */,
|
||||||
37C2211D27ADA33300305B41 /* MPVViewController.swift in Sources */,
|
37C2211D27ADA33300305B41 /* MPVViewController.swift in Sources */,
|
||||||
|
37A362BE29537AAA00BDF328 /* PlaybackSettings.swift in Sources */,
|
||||||
371B7E612759706A00D21217 /* CommentsView.swift in Sources */,
|
371B7E612759706A00D21217 /* CommentsView.swift in Sources */,
|
||||||
37D9BA0629507F69002586BD /* PlayerControlsSettings.swift in Sources */,
|
37D9BA0629507F69002586BD /* PlayerControlsSettings.swift in Sources */,
|
||||||
379DC3D128BA4EB400B09677 /* Seek.swift in Sources */,
|
379DC3D128BA4EB400B09677 /* Seek.swift in Sources */,
|
||||||
@ -3262,6 +3272,7 @@
|
|||||||
37141673267A8E10006CA35D /* Country.swift in Sources */,
|
37141673267A8E10006CA35D /* Country.swift in Sources */,
|
||||||
37FEF11327EFD8580033912F /* PlaceholderCell.swift in Sources */,
|
37FEF11327EFD8580033912F /* PlaceholderCell.swift in Sources */,
|
||||||
37B2631A2735EAAB00FE0D40 /* FavoriteResourceObserver.swift in Sources */,
|
37B2631A2735EAAB00FE0D40 /* FavoriteResourceObserver.swift in Sources */,
|
||||||
|
37A362C229537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift in Sources */,
|
||||||
3748186E26A769D60084E870 /* DetailBadge.swift in Sources */,
|
3748186E26A769D60084E870 /* DetailBadge.swift in Sources */,
|
||||||
3744A96028B99ADD005DE0A7 /* PlayerControlsLayout.swift in Sources */,
|
3744A96028B99ADD005DE0A7 /* PlayerControlsLayout.swift in Sources */,
|
||||||
376BE50B27349108009AD608 /* BrowsingSettings.swift in Sources */,
|
376BE50B27349108009AD608 /* BrowsingSettings.swift in Sources */,
|
||||||
@ -3451,6 +3462,7 @@
|
|||||||
3751B4B327836902000B7DF4 /* SearchPage.swift in Sources */,
|
3751B4B327836902000B7DF4 /* SearchPage.swift in Sources */,
|
||||||
3782B9532755667600990149 /* String+Format.swift in Sources */,
|
3782B9532755667600990149 /* String+Format.swift in Sources */,
|
||||||
37635FE5291EA6CF00C11E79 /* AccentButton.swift in Sources */,
|
37635FE5291EA6CF00C11E79 /* AccentButton.swift in Sources */,
|
||||||
|
37A362C329537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift in Sources */,
|
||||||
378E9C3D2945565500B2D696 /* SubscriptionsView.swift in Sources */,
|
378E9C3D2945565500B2D696 /* SubscriptionsView.swift in Sources */,
|
||||||
3776ADD7287381240078EBC4 /* Captions.swift in Sources */,
|
3776ADD7287381240078EBC4 /* Captions.swift in Sources */,
|
||||||
37E70924271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
|
37E70924271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
|
||||||
@ -3579,6 +3591,7 @@
|
|||||||
371B7E672759786B00D21217 /* Comment+Fixtures.swift in Sources */,
|
371B7E672759786B00D21217 /* Comment+Fixtures.swift in Sources */,
|
||||||
3769C02F2779F18600DDB3EA /* PlaceholderProgressView.swift in Sources */,
|
3769C02F2779F18600DDB3EA /* PlaceholderProgressView.swift in Sources */,
|
||||||
37BA794426DBA973002A0235 /* PlaylistsModel.swift in Sources */,
|
37BA794426DBA973002A0235 /* PlaylistsModel.swift in Sources */,
|
||||||
|
37A362BF29537AAA00BDF328 /* PlaybackSettings.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -3728,6 +3741,7 @@
|
|||||||
37A5DBCA285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */,
|
37A5DBCA285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */,
|
||||||
37E80F46287B7AEC00561799 /* PlayerQueueView.swift in Sources */,
|
37E80F46287B7AEC00561799 /* PlayerQueueView.swift in Sources */,
|
||||||
3786D060294C737300D23E82 /* RequestErrorButton.swift in Sources */,
|
3786D060294C737300D23E82 /* RequestErrorButton.swift in Sources */,
|
||||||
|
37A362C429537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift in Sources */,
|
||||||
37BDFF1929487B99000C6404 /* PlaylistVideosView.swift in Sources */,
|
37BDFF1929487B99000C6404 /* PlaylistVideosView.swift in Sources */,
|
||||||
37C0698427260B2100F7F6CB /* ThumbnailsModel.swift in Sources */,
|
37C0698427260B2100F7F6CB /* ThumbnailsModel.swift in Sources */,
|
||||||
374924DC2921050B0017D862 /* LocationsSettings.swift in Sources */,
|
374924DC2921050B0017D862 /* LocationsSettings.swift in Sources */,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user