diff --git a/Model/Player/Backends/AVPlayerBackend.swift b/Model/Player/Backends/AVPlayerBackend.swift index 89ccce7d..a26e65f9 100644 --- a/Model/Player/Backends/AVPlayerBackend.swift +++ b/Model/Player/Backends/AVPlayerBackend.swift @@ -122,7 +122,7 @@ final class AVPlayerBackend: PlayerBackend { } func canPlay(_ stream: Stream) -> Bool { - stream.kind == .hls || stream.kind == .stream || (stream.kind == .adaptive && stream.format == .mp4) + stream.kind == .hls || stream.kind == .stream } func playStream( diff --git a/Model/Player/PlayerModel.swift b/Model/Player/PlayerModel.swift index 343a012b..587f3d56 100644 --- a/Model/Player/PlayerModel.swift +++ b/Model/Player/PlayerModel.swift @@ -684,7 +684,7 @@ final class PlayerModel: ObservableObject { } // First, we need to create an array with supported formats. - let formatOrderPiP: [QualityProfile.Format] = [.hls, .stream, .mp4] + let formatOrderPiP: [QualityProfile.Format] = [.stream, .hls] guard let video = currentVideo else { return } guard let stream = avPlayerBackend.bestPlayable(availableStreams, maxResolution: .hd720p30, formatOrder: formatOrderPiP) else { return } @@ -1057,7 +1057,7 @@ final class PlayerModel: ObservableObject { func updateCurrentArtwork() { guard let video = currentVideo, - let thumbnailURL = video.thumbnailURL(quality: .medium) + let thumbnailURL = video.thumbnailURL(quality: Constants.isIPhone ? .medium : .maxres) else { return } diff --git a/Model/QualityProfile.swift b/Model/QualityProfile.swift index 2b216763..ea7e3fa3 100644 --- a/Model/QualityProfile.swift +++ b/Model/QualityProfile.swift @@ -6,12 +6,12 @@ struct QualityProfile: Hashable, Identifiable, Defaults.Serializable { static var defaultProfile = Self(id: "default", backend: .mpv, resolution: .hd720p60, formats: [.stream], order: Array(Format.allCases.indices)) enum Format: String, CaseIterable, Identifiable, Defaults.Serializable { - case hls - case stream case avc1 + case stream + case webm case mp4 case av1 - case webm + case hls var id: String { rawValue @@ -30,18 +30,18 @@ struct QualityProfile: Hashable, Identifiable, Defaults.Serializable { var streamFormat: Stream.Format? { switch self { - case .hls: - return nil - case .stream: - return nil case .avc1: return .avc1 + case .stream: + return nil + case .webm: + return .webm case .mp4: return .mp4 case .av1: return .av1 - case .webm: - return .webm + case .hls: + return nil } } } @@ -59,14 +59,16 @@ struct QualityProfile: Hashable, Identifiable, Defaults.Serializable { } var formatsDescription: String { - if formats.count == Format.allCases.count { + switch formats.count { + case Format.allCases.count: return "Any format".localized() - } - if formats.count <= 3 { + case 0: + return "No format selected".localized() + case 1 ... 3: return formats.map(\.description).joined(separator: ", ") + default: + return String(format: "%@ formats".localized(), String(formats.count)) } - - return String(format: "%@ formats".localized(), String(formats.count)) } func isPreferred(_ stream: Stream) -> Bool { diff --git a/Shared/Constants.swift b/Shared/Constants.swift index 75d7d374..c6585b53 100644 --- a/Shared/Constants.swift +++ b/Shared/Constants.swift @@ -39,6 +39,30 @@ enum Constants { #endif } + static var isTvOS: Bool { + #if os(tvOS) + true + #else + false + #endif + } + + static var isMacOS: Bool { + #if os(macOS) + true + #else + false + #endif + } + + static var isIOS: Bool { + #if os(iOS) + true + #else + false + #endif + } + static var progressViewScale: Double { #if os(macOS) 0.4 diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index f6e918f4..52a80787 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -20,14 +20,14 @@ extension Defaults.Keys { static let showOpenActionsToolbarItem = Key("showOpenActionsToolbarItem", default: false) #if os(iOS) static let showDocuments = Key("showDocuments", default: false) - static let lockPortraitWhenBrowsing = Key("lockPortraitWhenBrowsing", default: UIDevice.current.userInterfaceIdiom == .phone) + static let lockPortraitWhenBrowsing = Key("lockPortraitWhenBrowsing", default: Constants.isIPhone) #endif #if !os(tvOS) #if os(macOS) static let accountPickerDisplaysUsernameDefault = true #else - static let accountPickerDisplaysUsernameDefault = UIDevice.current.userInterfaceIdiom == .pad + static let accountPickerDisplaysUsernameDefault = Constants.isIPad #endif static let accountPickerDisplaysUsername = Key("accountPickerDisplaysUsername", default: accountPickerDisplaysUsernameDefault) #endif @@ -41,9 +41,9 @@ extension Defaults.Keys { static let showChannelAvatarInVideosListing = Key("showChannelAvatarInVideosListing", default: true) static let playerButtonSingleTapGesture = Key("playerButtonSingleTapGesture", default: .togglePlayer) - static let playerButtonDoubleTapGesture = Key("playerButtonDoubleTapGesture", default: .nothing) - static let playerButtonShowsControlButtonsWhenMinimized = Key("playerButtonShowsControlButtonsWhenMinimized", default: false) - static let playerButtonIsExpanded = Key("playerButtonIsExpanded", default: false) + static let playerButtonDoubleTapGesture = Key("playerButtonDoubleTapGesture", default: .togglePlayerVisibility) + static let playerButtonShowsControlButtonsWhenMinimized = Key("playerButtonShowsControlButtonsWhenMinimized", default: true) + static let playerButtonIsExpanded = Key("playerButtonIsExpanded", default: true) static let playerBarMaxWidth = Key("playerBarMaxWidth", default: "600") static let channelOnThumbnail = Key("channelOnThumbnail", default: false) static let timeOnThumbnail = Key("timeOnThumbnail", default: true) @@ -64,7 +64,7 @@ extension Defaults.Keys { static let closeVideoOnEOF = Key("closeVideoOnEOF", default: false) #if !os(macOS) - static let pauseOnEnteringBackground = Key("pauseOnEnteringBackground", default: true) + static let pauseOnEnteringBackground = Key("pauseOnEnteringBackground", default: false) #endif #if os(iOS) @@ -79,7 +79,7 @@ extension Defaults.Keys { static let showChapters = Key("showChapters", default: true) static let showChapterThumbnails = Key("showChapterThumbnails", default: true) - static let showChapterThumbnailsOnlyWhenDifferent = Key("showChapterThumbnailsOnlyWhenDifferent", default: true) + static let showChapterThumbnailsOnlyWhenDifferent = Key("showChapterThumbnailsOnlyWhenDifferent", default: false) static let expandChapters = Key("expandChapters", default: true) static let showRelated = Key("showRelated", default: true) static let showInspector = Key("showInspector", default: .onlyLocal) @@ -94,10 +94,10 @@ extension Defaults.Keys { #if os(iOS) static let honorSystemOrientationLock = Key("honorSystemOrientationLock", default: true) - static let enterFullscreenInLandscape = Key("enterFullscreenInLandscape", default: UIDevice.current.userInterfaceIdiom == .phone) + static let enterFullscreenInLandscape = Key("enterFullscreenInLandscape", default: Constants.isIPhone) static let rotateToLandscapeOnEnterFullScreen = Key( "rotateToLandscapeOnEnterFullScreen", - default: UIDevice.current.userInterfaceIdiom == .phone ? .landscapeRight : .disabled + default: Constants.isIPhone ? .landscapeRight : .disabled ) #endif @@ -116,14 +116,14 @@ extension Defaults.Keys { // MARK: GROUP - Controls - static let avPlayerUsesSystemControls = Key("avPlayerUsesSystemControls", default: true) + static let avPlayerUsesSystemControls = Key("avPlayerUsesSystemControls", default: Constants.isTvOS) static let horizontalPlayerGestureEnabled = Key("horizontalPlayerGestureEnabled", default: true) static let seekGestureSensitivity = Key("seekGestureSensitivity", default: 30.0) static let seekGestureSpeed = Key("seekGestureSpeed", default: 0.5) #if os(iOS) - static let playerControlsLayoutDefault = UIDevice.current.userInterfaceIdiom == .pad ? PlayerControlsLayout.medium : .small - static let fullScreenPlayerControlsLayoutDefault = UIDevice.current.userInterfaceIdiom == .pad ? PlayerControlsLayout.medium : .small + static let playerControlsLayoutDefault = Constants.isIPad ? PlayerControlsLayout.medium : .small + static let fullScreenPlayerControlsLayoutDefault = Constants.isIPad ? PlayerControlsLayout.medium : .small #elseif os(tvOS) static let playerControlsLayoutDefault = PlayerControlsLayout.tvRegular static let fullScreenPlayerControlsLayoutDefault = PlayerControlsLayout.tvRegular @@ -175,61 +175,152 @@ extension Defaults.Keys { // MARK: GROUP - Quality - static let hd2160pMPVProfile = QualityProfile(id: "hd2160pMPVProfile", backend: .mpv, resolution: .hd2160p60, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices)) - static let hd1080pMPVProfile = QualityProfile(id: "hd1080pMPVProfile", backend: .mpv, resolution: .hd1080p60, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices)) - static let hd720pMPVProfile = QualityProfile(id: "hd720pMPVProfile", backend: .mpv, resolution: .hd720p60, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices)) - static let hd720pAVPlayerProfile = QualityProfile(id: "hd720pAVPlayerProfile", backend: .appleAVPlayer, resolution: .hd720p30, formats: [.hls, .stream], order: Array(QualityProfile.Format.allCases.indices)) - static let sd360pAVPlayerProfile = QualityProfile(id: "sd360pAVPlayerProfile", backend: .appleAVPlayer, resolution: .sd360p30, formats: [.hls, .stream], order: Array(QualityProfile.Format.allCases.indices)) + static let hd2160p60MPVProfile = QualityProfile(id: "hd2160p60MPVProfile", backend: .mpv, resolution: .hd2160p60, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices)) + static let hd1080p60MPVProfile = QualityProfile(id: "hd1080p60MPVProfile", backend: .mpv, resolution: .hd1080p60, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices)) + static let hd1080pMPVProfile = QualityProfile(id: "hd1080pMPVProfile", backend: .mpv, resolution: .hd1080p30, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices)) + static let hd720p60MPVProfile = QualityProfile(id: "hd720p60MPVProfile", backend: .mpv, resolution: .hd720p60, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices)) + static let hd720pMPVProfile = QualityProfile(id: "hd720pMPVProfile", backend: .mpv, resolution: .hd720p30, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices)) + static let sd360pMPVProfile = QualityProfile(id: "sd360pMPVProfile", backend: .mpv, resolution: .sd360p30, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices)) + static let hd720pAVPlayerProfile = QualityProfile(id: "hd720pAVPlayerProfile", backend: .appleAVPlayer, resolution: .hd720p30, formats: [.stream, .hls], order: Array(QualityProfile.Format.allCases.indices)) + static let sd360pAVPlayerProfile = QualityProfile(id: "sd360pAVPlayerProfile", backend: .appleAVPlayer, resolution: .sd360p30, formats: [.stream, .hls], order: Array(QualityProfile.Format.allCases.indices)) #if os(iOS) - static let qualityProfilesDefault = UIDevice.current.userInterfaceIdiom == .pad ? [ - hd2160pMPVProfile, - hd1080pMPVProfile, - hd720pMPVProfile, - hd720pAVPlayerProfile, - sd360pAVPlayerProfile - ] : [ - hd1080pMPVProfile, - hd720pMPVProfile, - hd720pAVPlayerProfile, - sd360pAVPlayerProfile - ] + enum QualityProfiles { + // iPad-specific settings + enum iPad { + static let qualityProfilesDefault = [ + hd1080p60MPVProfile, + hd1080pMPVProfile, + hd720p60MPVProfile, + hd720pMPVProfile + ] + + static let batteryCellularProfileDefault = hd720pMPVProfile.id + static let batteryNonCellularProfileDefault = hd720p60MPVProfile.id + static let chargingCellularProfileDefault = hd1080pMPVProfile.id + static let chargingNonCellularProfileDefault = hd1080p60MPVProfile.id + } + + // iPhone-specific settings + enum iPhone { + static let qualityProfilesDefault = [ + hd1080p60MPVProfile, + hd1080pMPVProfile, + hd720p60MPVProfile, + hd720pMPVProfile, + sd360pMPVProfile + ] + + static let batteryCellularProfileDefault = sd360pMPVProfile.id + static let batteryNonCellularProfileDefault = hd720p60MPVProfile.id + static let chargingCellularProfileDefault = hd720pMPVProfile.id + static let chargingNonCellularProfileDefault = hd1080p60MPVProfile.id + } + + // Access the correct profile based on device type + static var currentProfile: (qualityProfilesDefault: [QualityProfile], batteryCellularProfileDefault: String, batteryNonCellularProfileDefault: String, chargingCellularProfileDefault: String, chargingNonCellularProfileDefault: String) { + if Constants.isIPad { + return ( + qualityProfilesDefault: iPad.qualityProfilesDefault, + batteryCellularProfileDefault: iPad.batteryCellularProfileDefault, + batteryNonCellularProfileDefault: iPad.batteryNonCellularProfileDefault, + chargingCellularProfileDefault: iPad.chargingCellularProfileDefault, + chargingNonCellularProfileDefault: iPad.chargingNonCellularProfileDefault + ) + } + + return ( + qualityProfilesDefault: iPhone.qualityProfilesDefault, + batteryCellularProfileDefault: iPhone.batteryCellularProfileDefault, + batteryNonCellularProfileDefault: iPhone.batteryNonCellularProfileDefault, + chargingCellularProfileDefault: iPhone.chargingCellularProfileDefault, + chargingNonCellularProfileDefault: iPhone.chargingNonCellularProfileDefault + ) + } + } - static let batteryCellularProfileDefault = hd720pAVPlayerProfile.id - static let batteryNonCellularProfileDefault = hd720pAVPlayerProfile.id - static let chargingCellularProfileDefault = hd720pAVPlayerProfile.id - static let chargingNonCellularProfileDefault = hd1080pMPVProfile.id #elseif os(tvOS) - static let qualityProfilesDefault = [ - hd2160pMPVProfile, - hd1080pMPVProfile, - hd720pMPVProfile, - hd720pAVPlayerProfile - ] - static let batteryCellularProfileDefault = hd1080pMPVProfile.id - static let batteryNonCellularProfileDefault = hd1080pMPVProfile.id - static let chargingCellularProfileDefault = hd1080pMPVProfile.id - static let chargingNonCellularProfileDefault = hd1080pMPVProfile.id + enum QualityProfiles { + // tvOS-specific settings + enum tvOS { + static let qualityProfilesDefault = [ + hd2160p60MPVProfile, + hd1080p60MPVProfile, + hd720p60MPVProfile, + hd720pAVPlayerProfile + ] + + static let batteryCellularProfileDefault = hd1080p60MPVProfile.id + static let batteryNonCellularProfileDefault = hd1080p60MPVProfile.id + static let chargingCellularProfileDefault = hd1080p60MPVProfile.id + static let chargingNonCellularProfileDefault = hd1080p60MPVProfile.id + } + + // Access the correct profile based on device type + static var currentProfile: (qualityProfilesDefault: [QualityProfile], batteryCellularProfileDefault: String, batteryNonCellularProfileDefault: String, chargingCellularProfileDefault: String, chargingNonCellularProfileDefault: String) { + ( + qualityProfilesDefault: tvOS.qualityProfilesDefault, + batteryCellularProfileDefault: tvOS.batteryCellularProfileDefault, + batteryNonCellularProfileDefault: tvOS.batteryNonCellularProfileDefault, + chargingCellularProfileDefault: tvOS.chargingCellularProfileDefault, + chargingNonCellularProfileDefault: tvOS.chargingNonCellularProfileDefault + ) + } + } #else - static let qualityProfilesDefault = [ - hd2160pMPVProfile, - hd1080pMPVProfile, - hd720pMPVProfile, - hd720pAVPlayerProfile - ] - static let batteryCellularProfileDefault = hd1080pMPVProfile.id - static let batteryNonCellularProfileDefault = hd1080pMPVProfile.id - static let chargingCellularProfileDefault = hd1080pMPVProfile.id - static let chargingNonCellularProfileDefault = hd1080pMPVProfile.id + enum QualityProfiles { + // macOS-specific settings + enum macOS { + static let qualityProfilesDefault = [ + hd2160p60MPVProfile, + hd1080p60MPVProfile, + hd1080pMPVProfile, + hd720p60MPVProfile + ] + + static let batteryCellularProfileDefault = hd1080p60MPVProfile.id + static let batteryNonCellularProfileDefault = hd1080p60MPVProfile.id + static let chargingCellularProfileDefault = hd1080p60MPVProfile.id + static let chargingNonCellularProfileDefault = hd1080p60MPVProfile.id + } + + // Access the correct profile for other platforms + static var currentProfile: (qualityProfilesDefault: [QualityProfile], batteryCellularProfileDefault: String, batteryNonCellularProfileDefault: String, chargingCellularProfileDefault: String, chargingNonCellularProfileDefault: String) { + ( + qualityProfilesDefault: macOS.qualityProfilesDefault, + batteryCellularProfileDefault: macOS.batteryCellularProfileDefault, + batteryNonCellularProfileDefault: macOS.batteryNonCellularProfileDefault, + chargingCellularProfileDefault: macOS.chargingCellularProfileDefault, + chargingNonCellularProfileDefault: macOS.chargingNonCellularProfileDefault + ) + } + } #endif - static let batteryCellularProfile = Key("batteryCellularProfile", default: batteryCellularProfileDefault) - static let batteryNonCellularProfile = Key("batteryNonCellularProfile", default: batteryNonCellularProfileDefault) - static let chargingCellularProfile = Key("chargingCellularProfile", default: chargingCellularProfileDefault) - static let chargingNonCellularProfile = Key("chargingNonCellularProfile", default: chargingNonCellularProfileDefault) - static let forceAVPlayerForLiveStreams = Key("forceAVPlayerForLiveStreams", default: true) - - static let qualityProfiles = Key<[QualityProfile]>("qualityProfiles", default: qualityProfilesDefault) + static let batteryCellularProfile = Key( + "batteryCellularProfile", + default: QualityProfiles.currentProfile.batteryCellularProfileDefault + ) + static let batteryNonCellularProfile = Key( + "batteryNonCellularProfile", + default: QualityProfiles.currentProfile.batteryNonCellularProfileDefault + ) + static let chargingCellularProfile = Key( + "chargingCellularProfile", + default: QualityProfiles.currentProfile.chargingCellularProfileDefault + ) + static let chargingNonCellularProfile = Key( + "chargingNonCellularProfile", + default: QualityProfiles.currentProfile.chargingNonCellularProfileDefault + ) + static let forceAVPlayerForLiveStreams = Key( + "forceAVPlayerForLiveStreams", + default: true + ) + static let qualityProfiles = Key<[QualityProfile]>( + "qualityProfiles", + default: QualityProfiles.currentProfile.qualityProfilesDefault + ) // MARK: GROUP - History diff --git a/Shared/Settings/QualityProfileForm.swift b/Shared/Settings/QualityProfileForm.swift index d07a7e43..9bd7033b 100644 --- a/Shared/Settings/QualityProfileForm.swift +++ b/Shared/Settings/QualityProfileForm.swift @@ -301,7 +301,7 @@ struct QualityProfileForm: View { func isFormatDisabled(_ format: QualityProfile.Format) -> Bool { guard backend == .appleAVPlayer else { return false } - let avPlayerFormats = [QualityProfile.Format.hls, .stream, .mp4] + let avPlayerFormats = [.stream, QualityProfile.Format.hls] return !avPlayerFormats.contains(format) }