App-Wide Bitrate Limit (#1147)

* Creation of bitrate selections that mirror Jellyfin-Web. The goal is to eventually allow for these same selections to be available for usage in the Player itself to set the max bitrate per playback session. This App-Wide setting is for things like preserving data (Mobile) or for areas that have perpetually have low bandwidth (AppleTV). These settings currently default to 'Auto' which is the current limit of 360,000,000 bps / 360 mpbs. I have added a spot in BaseItemDTO+VideoPlayerViewModel to get the smaller amount between 360 Mpbs and the App Maximum Setting. This exists so I can go back and update this to get the Minumum between the Player Session max bitrate and the App Setting max bitrate.

Test on iPhone 10S, AppleTV 3rd Gen, and the iPhone 15 Pro via enumulator.

* Fix Bitrate naming (360p vs 480p) and remove the setting nested in a second section.

* Creation of a Maximum setting with 360mbps and an auto that gets the bitrate at playback.

* Remove comments for code where I want to eventually put it for better clarify

* Linting fixes

* Change the Playback Bitrate to an Int from a String since the Bitrate is valuable but the string isn't. Run the SwiftFormat on the maxBitrate function.

* Migrate the settings to their own menu with both the bitrate and the optional test size when auto is used.

* Creation of an enum filterValues function for Bitrate. This way, the selection on the Player Overlay (eventually) can be filtered to only include bitrates that are less than or equal to the App Setting for Maximum Bitrate. This should help prevent confusion / remove bandwidth conflicts.

The eventual Player Overlay setting should never conflict with the App-Wide Setting and should only offer options that are less than the App-Wide Setting.

* Change the videoPlayerViewModel to take parameters instead of defaults. Move the defaults up one level to be called there. Split the bitrate test from the getMaxBitrate to better guard against dividing against 0 and also split out the logic to be easier to read.

Change the PlaybackBitrate filter to always include Auto and, when auto, include ALL bitrates. This filter is not currently used.

* Remove the PlaybackBitrate FilterValues since this is not needed and will be created ad-hoc.

* Update the bitrateTestDuration verbage to better reflect that you're changing the size of the bitrate test and not just increasing the duration. Re-use the existing largest to smallest labels since there isn't a ton of benefit using "Longest to Shortest" so this should re-use existing localization. Comment the Labels.

No functional changes. Only an update to labels.

* Delete the Bitrate.json file but retain the Resources folder.

* Remove Resource Folder.

---------

Co-authored-by: Joe Kribs <joseph@kribs.net>
This commit is contained in:
Joe 2024-07-23 05:18:28 -06:00 committed by GitHub
parent 5296404038
commit 56bd62db80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 316 additions and 83 deletions

View File

@ -23,6 +23,8 @@ final class SettingsCoordinator: NavigationCoordinatable {
@Route(.push) @Route(.push)
var nativePlayerSettings = makeNativePlayerSettings var nativePlayerSettings = makeNativePlayerSettings
@Route(.push) @Route(.push)
var maximumBitrateSettings = makeMaximumBitrateSettings
@Route(.push)
var quickConnect = makeQuickConnectAuthorize var quickConnect = makeQuickConnectAuthorize
@Route(.push) @Route(.push)
var resetUserPassword = makeResetUserPassword var resetUserPassword = makeResetUserPassword
@ -65,6 +67,8 @@ final class SettingsCoordinator: NavigationCoordinatable {
var serverDetail = makeServerDetail var serverDetail = makeServerDetail
@Route(.modal) @Route(.modal)
var videoPlayerSettings = makeVideoPlayerSettings var videoPlayerSettings = makeVideoPlayerSettings
@Route(.modal)
var maximumBitrateSettings = makeMaximumBitrateSettings
#endif #endif
#if os(iOS) #if os(iOS)
@ -73,6 +77,11 @@ final class SettingsCoordinator: NavigationCoordinatable {
NativeVideoPlayerSettingsView() NativeVideoPlayerSettingsView()
} }
@ViewBuilder
func makeMaximumBitrateSettings() -> some View {
MaximumBitrateSettingsView()
}
@ViewBuilder @ViewBuilder
func makeQuickConnectAuthorize() -> some View { func makeQuickConnectAuthorize() -> some View {
QuickConnectAuthorizeView() QuickConnectAuthorizeView()
@ -166,6 +175,12 @@ final class SettingsCoordinator: NavigationCoordinatable {
func makeVideoPlayerSettings() -> NavigationViewCoordinator<VideoPlayerSettingsCoordinator> { func makeVideoPlayerSettings() -> NavigationViewCoordinator<VideoPlayerSettingsCoordinator> {
NavigationViewCoordinator(VideoPlayerSettingsCoordinator()) NavigationViewCoordinator(VideoPlayerSettingsCoordinator())
} }
func makeMaximumBitrateSettings() -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
NavigationViewCoordinator {
MaximumBitrateSettingsView()
}
}
#endif #endif
@ViewBuilder @ViewBuilder

View File

@ -13,20 +13,19 @@ import JellyfinAPI
import Logging import Logging
extension BaseItemDto { extension BaseItemDto {
func videoPlayerViewModel(with mediaSource: MediaSourceInfo) async throws -> VideoPlayerViewModel { func videoPlayerViewModel(with mediaSource: MediaSourceInfo) async throws -> VideoPlayerViewModel {
let currentVideoPlayerType = Defaults[.VideoPlayer.videoPlayerType] let currentVideoPlayerType = Defaults[.VideoPlayer.videoPlayerType]
// TODO: fix bitrate settings let currentVideoBitrate = Defaults[.VideoPlayer.appMaximumBitrate]
let tempOverkillBitrate = 360_000_000
let profile = DeviceProfile.build(for: currentVideoPlayerType, maxBitrate: tempOverkillBitrate) let maxBitrate = try await getMaxBitrate(for: currentVideoBitrate)
let profile = DeviceProfile.build(for: currentVideoPlayerType, maxBitrate: maxBitrate)
let userSession = Container.shared.currentUserSession()! let userSession = Container.shared.currentUserSession()!
let playbackInfo = PlaybackInfoDto(deviceProfile: profile) let playbackInfo = PlaybackInfoDto(deviceProfile: profile)
let playbackInfoParameters = Paths.GetPostedPlaybackInfoParameters( let playbackInfoParameters = Paths.GetPostedPlaybackInfoParameters(
userID: userSession.user.id, userID: userSession.user.id,
maxStreamingBitrate: tempOverkillBitrate maxStreamingBitrate: maxBitrate
) )
let request = Paths.getPostedPlaybackInfo( let request = Paths.getPostedPlaybackInfo(
@ -47,11 +46,11 @@ extension BaseItemDto {
} }
func liveVideoPlayerViewModel(with mediaSource: MediaSourceInfo, logger: Logger) async throws -> VideoPlayerViewModel { func liveVideoPlayerViewModel(with mediaSource: MediaSourceInfo, logger: Logger) async throws -> VideoPlayerViewModel {
let currentVideoPlayerType = Defaults[.VideoPlayer.videoPlayerType] let currentVideoPlayerType = Defaults[.VideoPlayer.videoPlayerType]
// TODO: fix bitrate settings let currentVideoBitrate = Defaults[.VideoPlayer.appMaximumBitrate]
let tempOverkillBitrate = 360_000_000
var profile = DeviceProfile.build(for: currentVideoPlayerType, maxBitrate: tempOverkillBitrate) let maxBitrate = try await getMaxBitrate(for: currentVideoBitrate)
var profile = DeviceProfile.build(for: currentVideoPlayerType, maxBitrate: maxBitrate)
if Defaults[.Experimental.liveTVForceDirectPlay] { if Defaults[.Experimental.liveTVForceDirectPlay] {
profile.directPlayProfiles = [DirectPlayProfile(type: .video)] profile.directPlayProfiles = [DirectPlayProfile(type: .video)]
} }
@ -61,7 +60,7 @@ extension BaseItemDto {
let playbackInfo = PlaybackInfoDto(deviceProfile: profile) let playbackInfo = PlaybackInfoDto(deviceProfile: profile)
let playbackInfoParameters = Paths.GetPostedPlaybackInfoParameters( let playbackInfoParameters = Paths.GetPostedPlaybackInfoParameters(
userID: userSession.user.id, userID: userSession.user.id,
maxStreamingBitrate: tempOverkillBitrate maxStreamingBitrate: maxBitrate
) )
let request = Paths.getPostedPlaybackInfo( let request = Paths.getPostedPlaybackInfo(
@ -100,4 +99,27 @@ extension BaseItemDto {
playSessionID: response.value.playSessionID! playSessionID: response.value.playSessionID!
) )
} }
private func getMaxBitrate(for bitrate: PlaybackBitrate) async throws -> Int {
let settingBitrate = Defaults[.VideoPlayer.appMaximumBitrateTest]
guard bitrate != .auto else {
return try await testBitrate(with: settingBitrate.rawValue)
}
return bitrate.rawValue
}
private func testBitrate(with testSize: Int) async throws -> Int {
precondition(testSize > 0, "testSize must be greater than zero")
let userSession = Container.shared.currentUserSession()!
let testStartTime = Date()
try await userSession.client.send(Paths.getBitrateTestBytes(size: testSize))
let testDuration = Date().timeIntervalSince(testStartTime)
let testSizeBits = Double(testSize * 8)
let testBitrate = testSizeBits / testDuration
return Int(testBitrate)
}
} }

View File

@ -0,0 +1,67 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//
import Defaults
import Foundation
import JellyfinAPI
enum PlaybackBitrate: Int, CaseIterable, Defaults.Serializable, Displayable {
case auto = 0
case max = 360_000_000
case mbps120 = 120_000_000
case mbps80 = 80_000_000
case mbps60 = 60_000_000
case mbps40 = 40_000_000
case mbps20 = 20_000_000
case mbps15 = 15_000_000
case mbps10 = 10_000_000
case mbps8 = 8_000_000
case mbps6 = 6_000_000
case mbps4 = 4_000_000
case mbps3 = 3_000_000
case kbps1500 = 1_500_000
case kbps720 = 720_000
case kbps420 = 420_000
var displayTitle: String {
switch self {
case .auto:
return L10n.bitrateAuto
case .max:
return L10n.bitrateMax
case .mbps120:
return L10n.bitrateMbps120
case .mbps80:
return L10n.bitrateMbps80
case .mbps60:
return L10n.bitrateMbps60
case .mbps40:
return L10n.bitrateMbps40
case .mbps20:
return L10n.bitrateMbps20
case .mbps15:
return L10n.bitrateMbps15
case .mbps10:
return L10n.bitrateMbps10
case .mbps8:
return L10n.bitrateMbps8
case .mbps6:
return L10n.bitrateMbps6
case .mbps4:
return L10n.bitrateMbps4
case .mbps3:
return L10n.bitrateMbps3
case .kbps1500:
return L10n.bitrateKbps1500
case .kbps720:
return L10n.bitrateKbps720
case .kbps420:
return L10n.bitrateKbps420
}
}
}

View File

@ -0,0 +1,34 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//
import Defaults
import Foundation
import JellyfinAPI
enum PlaybackBitrateTestSize: Int, CaseIterable, Defaults.Serializable, Displayable {
case largest = 10_000_000
case larger = 7_500_000
case regular = 5_000_000
case smaller = 2_500_000
case smallest = 1_000_000
var displayTitle: String {
switch self {
case .largest:
return L10n.largest
case .larger:
return L10n.larger
case .regular:
return L10n.regular
case .smaller:
return L10n.smaller
case .smallest:
return L10n.smallest
}
}
}

View File

@ -1,58 +0,0 @@
[
{
"name": "4K - 120 Mbps",
"value": 120000000
},
{
"name": "4K - 100 Mbps",
"value": 100000000
},
{
"name": "4K - 80 Mbps",
"value": 80000000
},
{
"name": "1080p - 60 Mbps",
"value": 60000000
},
{
"name": "1080p - 40 Mbps",
"value": 40000000
},
{
"name": "1080p - 20 Mbps",
"value": 20000000
},
{
"name": "1080p - 15 Mbps",
"value": 15000000
},
{
"name": "1080p - 10 Mbps",
"value": 10000000
},
{
"name": "720p - 8 Mbps",
"value": 8000000
},
{
"name": "720p - 6 Mbps",
"value": 6000000
},
{
"name": "720p - 4 Mbps",
"value": 4000000
},
{
"name": "480p - 3 Mbps",
"value": 3000000
},
{
"name": "480p - 1.5 Mbps",
"value": 1500000
},
{
"name": "480p - 740 Kbps",
"value": 740000
}
]

View File

@ -171,6 +171,8 @@ extension Defaults.Keys {
enum VideoPlayer { enum VideoPlayer {
static let appMaximumBitrate: Key<PlaybackBitrate> = UserKey("appMaximumBitrate", default: .auto)
static let appMaximumBitrateTest: Key<PlaybackBitrateTestSize> = UserKey("appMaximumBitrateTest", default: .regular)
static let autoPlayEnabled: Key<Bool> = UserKey("autoPlayEnabled", default: true) static let autoPlayEnabled: Key<Bool> = UserKey("autoPlayEnabled", default: true)
static let barActionButtons: Key<[VideoPlayerActionButton]> = UserKey( static let barActionButtons: Key<[VideoPlayerActionButton]> = UserKey(
"barActionButtons", "barActionButtons",

View File

@ -54,6 +54,40 @@ internal enum L10n {
internal static let back = L10n.tr("Localizable", "back", fallback: "Back") internal static let back = L10n.tr("Localizable", "back", fallback: "Back")
/// Bar Buttons /// Bar Buttons
internal static let barButtons = L10n.tr("Localizable", "barButtons", fallback: "Bar Buttons") internal static let barButtons = L10n.tr("Localizable", "barButtons", fallback: "Bar Buttons")
/// Auto
internal static let bitrateAuto = L10n.tr("Localizable", "bitrateAuto", fallback: "Auto")
/// 480p - 1.5 Mbps
internal static let bitrateKbps1500 = L10n.tr("Localizable", "bitrateKbps1500", fallback: "480p - 1.5 Mbps")
/// 360p - 420 Kbps
internal static let bitrateKbps420 = L10n.tr("Localizable", "bitrateKbps420", fallback: "360p - 420 Kbps")
/// 480p - 720 Kbps
internal static let bitrateKbps720 = L10n.tr("Localizable", "bitrateKbps720", fallback: "480p - 720 Kbps")
/// Maximum
internal static let bitrateMax = L10n.tr("Localizable", "bitrateMax", fallback: "Maximum")
/// 1080p - 10 Mbps
internal static let bitrateMbps10 = L10n.tr("Localizable", "bitrateMbps10", fallback: "1080p - 10 Mbps")
/// 4K - 120 Mbps
internal static let bitrateMbps120 = L10n.tr("Localizable", "bitrateMbps120", fallback: "4K - 120 Mbps")
/// 1080p - 15 Mbps
internal static let bitrateMbps15 = L10n.tr("Localizable", "bitrateMbps15", fallback: "1080p - 15 Mbps")
/// 1080p - 20 Mbps
internal static let bitrateMbps20 = L10n.tr("Localizable", "bitrateMbps20", fallback: "1080p - 20 Mbps")
/// 480p - 3 Mbps
internal static let bitrateMbps3 = L10n.tr("Localizable", "bitrateMbps3", fallback: "480p - 3 Mbps")
/// 720p - 4 Mbps
internal static let bitrateMbps4 = L10n.tr("Localizable", "bitrateMbps4", fallback: "720p - 4 Mbps")
/// 1080p - 40 Mbps
internal static let bitrateMbps40 = L10n.tr("Localizable", "bitrateMbps40", fallback: "1080p - 40 Mbps")
/// 720p - 6 Mbps
internal static let bitrateMbps6 = L10n.tr("Localizable", "bitrateMbps6", fallback: "720p - 6 Mbps")
/// 1080p - 60 Mbps
internal static let bitrateMbps60 = L10n.tr("Localizable", "bitrateMbps60", fallback: "1080p - 60 Mbps")
/// 720p - 8 Mbps
internal static let bitrateMbps8 = L10n.tr("Localizable", "bitrateMbps8", fallback: "720p - 8 Mbps")
/// 4K - 80 Mbps
internal static let bitrateMbps80 = L10n.tr("Localizable", "bitrateMbps80", fallback: "4K - 80 Mbps")
/// Larger tests result in a more accurate bitrate but may delay playback
internal static let bitrateTestDescription = L10n.tr("Localizable", "bitrateTestDescription", fallback: "Larger tests result in a more accurate bitrate but may delay playback")
/// Blue /// Blue
internal static let blue = L10n.tr("Localizable", "blue", fallback: "Blue") internal static let blue = L10n.tr("Localizable", "blue", fallback: "Blue")
/// Bugs and Features /// Bugs and Features
@ -244,6 +278,8 @@ internal enum L10n {
} }
/// Logs /// Logs
internal static let logs = L10n.tr("Localizable", "logs", fallback: "Logs") internal static let logs = L10n.tr("Localizable", "logs", fallback: "Logs")
/// Maximum Bitrate
internal static let maximumBitrate = L10n.tr("Localizable", "maximumBitrate", fallback: "Maximum Bitrate")
/// Media /// Media
internal static let media = L10n.tr("Localizable", "media", fallback: "Media") internal static let media = L10n.tr("Localizable", "media", fallback: "Media")
/// Menu Buttons /// Menu Buttons
@ -570,6 +606,8 @@ internal enum L10n {
internal static let systemControlGesturesEnabled = L10n.tr("Localizable", "systemControlGesturesEnabled", fallback: "System Control Gestures Enabled") internal static let systemControlGesturesEnabled = L10n.tr("Localizable", "systemControlGesturesEnabled", fallback: "System Control Gestures Enabled")
/// Tags /// Tags
internal static let tags = L10n.tr("Localizable", "tags", fallback: "Tags") internal static let tags = L10n.tr("Localizable", "tags", fallback: "Tags")
/// Test Size
internal static let testSize = L10n.tr("Localizable", "testSize", fallback: "Test Size")
/// Timestamp /// Timestamp
internal static let timestamp = L10n.tr("Localizable", "timestamp", fallback: "Timestamp") internal static let timestamp = L10n.tr("Localizable", "timestamp", fallback: "Timestamp")
/// Timestamp Type /// Timestamp Type

View File

@ -0,0 +1,47 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//
import Defaults
import SwiftUI
struct MaximumBitrateSettingsView: View {
@Default(.VideoPlayer.appMaximumBitrate)
private var appMaximumBitrate
@Default(.VideoPlayer.appMaximumBitrateTest)
private var appMaximumBitrateTest
var body: some View {
SplitFormWindowView()
.descriptionView {
Image(systemName: "network")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 400)
}
.contentView {
Section {
CaseIterablePicker(
L10n.maximumBitrate,
selection: $appMaximumBitrate
)
if appMaximumBitrate == PlaybackBitrate.auto {
CaseIterablePicker(
L10n.testSize,
selection: $appMaximumBitrateTest
)
}
} footer: {
if appMaximumBitrate == PlaybackBitrate.auto {
Text(L10n.bitrateTestDescription)
}
}
}
.navigationTitle(L10n.maximumBitrate)
}
}

View File

@ -64,6 +64,12 @@ struct SettingsView: View {
.onSelect { .onSelect {
router.route(to: \.videoPlayerSettings) router.route(to: \.videoPlayerSettings)
} }
ChevronButton(L10n.maximumBitrate)
.onSelect {
router.route(to: \.maximumBitrateSettings)
}
} header: { } header: {
L10n.videoPlayer.text L10n.videoPlayer.text
} }

View File

@ -14,6 +14,12 @@
4E16FD572C01A32700110147 /* LetterPickerOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */; }; 4E16FD572C01A32700110147 /* LetterPickerOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */; };
4E16FD582C01A32700110147 /* LetterPickerOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */; }; 4E16FD582C01A32700110147 /* LetterPickerOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */; };
4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; }; 4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; };
4E73E2A62C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */; };
4E73E2A72C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */; };
4E73E2AE2C420207002D2A78 /* MaximumBitrateSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E73E2AD2C420207002D2A78 /* MaximumBitrateSettingsView.swift */; };
4E73E2B02C4211CA002D2A78 /* MaximumBitrateSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E73E2AF2C4211CA002D2A78 /* MaximumBitrateSettingsView.swift */; };
4E762AAE2C3A1A95004D1579 /* PlaybackBitrate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */; };
4E762AAF2C3A1A95004D1579 /* PlaybackBitrate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */; };
4E8B34EA2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; }; 4E8B34EA2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; };
4E8B34EB2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; }; 4E8B34EB2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; };
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E6267ABD79005D8AB9 /* HomeView.swift */; }; 531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E6267ABD79005D8AB9 /* HomeView.swift */; };
@ -28,7 +34,6 @@
534D4FF726A7D7CC000A7A48 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 534D4FEB26A7D7CC000A7A48 /* Localizable.strings */; }; 534D4FF726A7D7CC000A7A48 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 534D4FEB26A7D7CC000A7A48 /* Localizable.strings */; };
535870632669D21600D05A09 /* SwiftfinApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870622669D21600D05A09 /* SwiftfinApp.swift */; }; 535870632669D21600D05A09 /* SwiftfinApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870622669D21600D05A09 /* SwiftfinApp.swift */; };
535870672669D21700D05A09 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 535870662669D21700D05A09 /* Assets.xcassets */; }; 535870672669D21700D05A09 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 535870662669D21700D05A09 /* Assets.xcassets */; };
5358707E2669D64F00D05A09 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
535870AD2669D8DD00D05A09 /* ItemFilterCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870AC2669D8DD00D05A09 /* ItemFilterCollection.swift */; }; 535870AD2669D8DD00D05A09 /* ItemFilterCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870AC2669D8DD00D05A09 /* ItemFilterCollection.swift */; };
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAE9E2649E569005FA86D /* ItemView.swift */; }; 535BAE9F2649E569005FA86D /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAE9E2649E569005FA86D /* ItemView.swift */; };
5364F455266CA0DC0026ECBA /* BaseItemPerson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* BaseItemPerson.swift */; }; 5364F455266CA0DC0026ECBA /* BaseItemPerson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* BaseItemPerson.swift */; };
@ -145,7 +150,6 @@
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; }; 62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; };
6334175B287DDFB9000603CE /* QuickConnectAuthorizeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6334175A287DDFB9000603CE /* QuickConnectAuthorizeView.swift */; }; 6334175B287DDFB9000603CE /* QuickConnectAuthorizeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6334175A287DDFB9000603CE /* QuickConnectAuthorizeView.swift */; };
6334175D287DE0D0000603CE /* QuickConnectAuthorizeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6334175C287DE0D0000603CE /* QuickConnectAuthorizeViewModel.swift */; }; 6334175D287DE0D0000603CE /* QuickConnectAuthorizeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6334175C287DE0D0000603CE /* QuickConnectAuthorizeViewModel.swift */; };
AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
BD0BA22B2AD6503B00306A8D /* OnlineVideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD0BA22A2AD6503B00306A8D /* OnlineVideoPlayerManager.swift */; }; BD0BA22B2AD6503B00306A8D /* OnlineVideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD0BA22A2AD6503B00306A8D /* OnlineVideoPlayerManager.swift */; };
BD0BA22C2AD6503B00306A8D /* OnlineVideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD0BA22A2AD6503B00306A8D /* OnlineVideoPlayerManager.swift */; }; BD0BA22C2AD6503B00306A8D /* OnlineVideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD0BA22A2AD6503B00306A8D /* OnlineVideoPlayerManager.swift */; };
BD0BA22E2AD6508C00306A8D /* DownloadVideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD0BA22D2AD6508C00306A8D /* DownloadVideoPlayerManager.swift */; }; BD0BA22E2AD6508C00306A8D /* DownloadVideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD0BA22D2AD6508C00306A8D /* DownloadVideoPlayerManager.swift */; };
@ -932,6 +936,10 @@
4E16FD522C01840C00110147 /* LetterPickerBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerBar.swift; sourceTree = "<group>"; }; 4E16FD522C01840C00110147 /* LetterPickerBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerBar.swift; sourceTree = "<group>"; };
4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerOrientation.swift; sourceTree = "<group>"; }; 4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerOrientation.swift; sourceTree = "<group>"; };
4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = "<group>"; }; 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = "<group>"; };
4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackBitrateTestSize.swift; sourceTree = "<group>"; };
4E73E2AD2C420207002D2A78 /* MaximumBitrateSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaximumBitrateSettingsView.swift; sourceTree = "<group>"; };
4E73E2AF2C4211CA002D2A78 /* MaximumBitrateSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaximumBitrateSettingsView.swift; sourceTree = "<group>"; };
4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaybackBitrate.swift; sourceTree = "<group>"; };
4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemFilter.swift; sourceTree = "<group>"; }; 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemFilter.swift; sourceTree = "<group>"; };
531690E6267ABD79005D8AB9 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; }; 531690E6267ABD79005D8AB9 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
531AC8BE26750DE20091C7EB /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; }; 531AC8BE26750DE20091C7EB /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
@ -1048,7 +1056,6 @@
6334175A287DDFB9000603CE /* QuickConnectAuthorizeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectAuthorizeView.swift; sourceTree = "<group>"; }; 6334175A287DDFB9000603CE /* QuickConnectAuthorizeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectAuthorizeView.swift; sourceTree = "<group>"; };
6334175C287DE0D0000603CE /* QuickConnectAuthorizeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectAuthorizeViewModel.swift; sourceTree = "<group>"; }; 6334175C287DE0D0000603CE /* QuickConnectAuthorizeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectAuthorizeViewModel.swift; sourceTree = "<group>"; };
637FCAF3287B5B2600C0A353 /* UDPBroadcast.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = UDPBroadcast.xcframework; path = Carthage/Build/UDPBroadcast.xcframework; sourceTree = "<group>"; }; 637FCAF3287B5B2600C0A353 /* UDPBroadcast.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = UDPBroadcast.xcframework; path = Carthage/Build/UDPBroadcast.xcframework; sourceTree = "<group>"; };
AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = "<group>"; };
BD0BA22A2AD6503B00306A8D /* OnlineVideoPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnlineVideoPlayerManager.swift; sourceTree = "<group>"; }; BD0BA22A2AD6503B00306A8D /* OnlineVideoPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnlineVideoPlayerManager.swift; sourceTree = "<group>"; };
BD0BA22D2AD6508C00306A8D /* DownloadVideoPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadVideoPlayerManager.swift; sourceTree = "<group>"; }; BD0BA22D2AD6508C00306A8D /* DownloadVideoPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadVideoPlayerManager.swift; sourceTree = "<group>"; };
BD3957742C112A330078CEF8 /* ButtonSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonSection.swift; sourceTree = "<group>"; }; BD3957742C112A330078CEF8 /* ButtonSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonSection.swift; sourceTree = "<group>"; };
@ -1672,6 +1679,15 @@
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4E3A785D2C3B87A400D33C11 /* PlaybackBitrate */ = {
isa = PBXGroup;
children = (
4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */,
4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */,
);
path = PlaybackBitrate;
sourceTree = "<group>";
};
5310694F2684E7EE00CFFDBA /* VideoPlayer */ = { 5310694F2684E7EE00CFFDBA /* VideoPlayer */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1784,7 +1800,6 @@
E1FCD08E26C466F3007C8DCF /* Errors */, E1FCD08E26C466F3007C8DCF /* Errors */,
621338912660106C00A81A2A /* Extensions */, 621338912660106C00A81A2A /* Extensions */,
535870AB2669D8D300D05A09 /* Objects */, 535870AB2669D8D300D05A09 /* Objects */,
AE8C3157265D6F5E008AA076 /* Resources */,
091B5A852683142E00D78B61 /* ServerDiscovery */, 091B5A852683142E00D78B61 /* ServerDiscovery */,
E1549654296CA2EF00C4EF88 /* Services */, E1549654296CA2EF00C4EF88 /* Services */,
6286F09F271C0AA500C40ED5 /* Strings */, 6286F09F271C0AA500C40ED5 /* Strings */,
@ -1810,6 +1825,7 @@
E1DE2B4E2B983F3200F6715F /* LibraryParent */, E1DE2B4E2B983F3200F6715F /* LibraryParent */,
E1AA331E2782639D00F6439C /* OverlayType.swift */, E1AA331E2782639D00F6439C /* OverlayType.swift */,
E1C925F62887504B002A7A66 /* PanDirectionGestureRecognizer.swift */, E1C925F62887504B002A7A66 /* PanDirectionGestureRecognizer.swift */,
4E3A785D2C3B87A400D33C11 /* PlaybackBitrate */,
E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */, E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */,
E1937A60288F32DB00CB80AA /* Poster.swift */, E1937A60288F32DB00CB80AA /* Poster.swift */,
E1CCF12D28ABF989006CAC9E /* PosterDisplayType.swift */, E1CCF12D28ABF989006CAC9E /* PosterDisplayType.swift */,
@ -2185,14 +2201,6 @@
path = Coordinators; path = Coordinators;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
AE8C3157265D6F5E008AA076 /* Resources */ = {
isa = PBXGroup;
children = (
AE8C3158265D6F90008AA076 /* bitrates.json */,
);
path = Resources;
sourceTree = "<group>";
};
BD0BA2292AD6501300306A8D /* VideoPlayerManager */ = { BD0BA2292AD6501300306A8D /* VideoPlayerManager */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -3468,6 +3476,7 @@
E1E5D54B2783E27200692DFE /* ExperimentalSettingsView.swift */, E1E5D54B2783E27200692DFE /* ExperimentalSettingsView.swift */,
E104C86F296E087200C1C3F9 /* IndicatorSettingsView.swift */, E104C86F296E087200C1C3F9 /* IndicatorSettingsView.swift */,
E16AF11B292C98A7001422A8 /* GestureSettingsView.swift */, E16AF11B292C98A7001422A8 /* GestureSettingsView.swift */,
4E73E2AD2C420207002D2A78 /* MaximumBitrateSettingsView.swift */,
E15756332936851D00976E1F /* NativeVideoPlayerSettingsView.swift */, E15756332936851D00976E1F /* NativeVideoPlayerSettingsView.swift */,
E1545BD62BDC559500D9578F /* UserProfileSettingsView */, E1545BD62BDC559500D9578F /* UserProfileSettingsView */,
E1BE1CEB2BDB68BC008176A9 /* SettingsView */, E1BE1CEB2BDB68BC008176A9 /* SettingsView */,
@ -3482,6 +3491,7 @@
E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */, E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */,
E1E5D5502783E67700692DFE /* ExperimentalSettingsView.swift */, E1E5D5502783E67700692DFE /* ExperimentalSettingsView.swift */,
E104C872296E0D0A00C1C3F9 /* IndicatorSettingsView.swift */, E104C872296E0D0A00C1C3F9 /* IndicatorSettingsView.swift */,
4E73E2AF2C4211CA002D2A78 /* MaximumBitrateSettingsView.swift */,
5398514426B64DA100101B49 /* SettingsView.swift */, 5398514426B64DA100101B49 /* SettingsView.swift */,
E1549679296CB4B000C4EF88 /* VideoPlayerSettingsView.swift */, E1549679296CB4B000C4EF88 /* VideoPlayerSettingsView.swift */,
); );
@ -3772,7 +3782,6 @@
53913C0826D323FE00EB3286 /* Localizable.strings in Resources */, 53913C0826D323FE00EB3286 /* Localizable.strings in Resources */,
53913C1126D323FE00EB3286 /* Localizable.strings in Resources */, 53913C1126D323FE00EB3286 /* Localizable.strings in Resources */,
535870672669D21700D05A09 /* Assets.xcassets in Resources */, 535870672669D21700D05A09 /* Assets.xcassets in Resources */,
5358707E2669D64F00D05A09 /* bitrates.json in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -3796,7 +3805,6 @@
53913BEF26D323FE00EB3286 /* Localizable.strings in Resources */, 53913BEF26D323FE00EB3286 /* Localizable.strings in Resources */,
53913C0726D323FE00EB3286 /* Localizable.strings in Resources */, 53913C0726D323FE00EB3286 /* Localizable.strings in Resources */,
53913C1026D323FE00EB3286 /* Localizable.strings in Resources */, 53913C1026D323FE00EB3286 /* Localizable.strings in Resources */,
AE8C3159265D6F90008AA076 /* bitrates.json in Resources */,
5377CBF9263B596B003A4E83 /* Assets.xcassets in Resources */, 5377CBF9263B596B003A4E83 /* Assets.xcassets in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -3919,6 +3927,7 @@
E187A60529AD2E25008387E6 /* StepperView.swift in Sources */, E187A60529AD2E25008387E6 /* StepperView.swift in Sources */,
E1575E71293E77B5001665B1 /* RepeatingTimer.swift in Sources */, E1575E71293E77B5001665B1 /* RepeatingTimer.swift in Sources */,
E1D4BF8B2719D3D000A11E64 /* AppSettingsCoordinator.swift in Sources */, E1D4BF8B2719D3D000A11E64 /* AppSettingsCoordinator.swift in Sources */,
4E73E2B02C4211CA002D2A78 /* MaximumBitrateSettingsView.swift in Sources */,
E10231452BCF8A51009D71FC /* ChannelProgram.swift in Sources */, E10231452BCF8A51009D71FC /* ChannelProgram.swift in Sources */,
E146A9D92BE6E9830034DA1E /* StoredValue.swift in Sources */, E146A9D92BE6E9830034DA1E /* StoredValue.swift in Sources */,
E13DD3FA2717E961009D4DAF /* SelectUserViewModel.swift in Sources */, E13DD3FA2717E961009D4DAF /* SelectUserViewModel.swift in Sources */,
@ -3982,6 +3991,7 @@
E14EA16A2BF7333B00DE757A /* UserProfileImageViewModel.swift in Sources */, E14EA16A2BF7333B00DE757A /* UserProfileImageViewModel.swift in Sources */,
E1575E98293E7B1E001665B1 /* UIApplication.swift in Sources */, E1575E98293E7B1E001665B1 /* UIApplication.swift in Sources */,
E12376B12A33DB33001F5B44 /* MediaSourceInfoCoordinator.swift in Sources */, E12376B12A33DB33001F5B44 /* MediaSourceInfoCoordinator.swift in Sources */,
4E73E2A72C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */,
E17885A4278105170094FBCF /* SFSymbolButton.swift in Sources */, E17885A4278105170094FBCF /* SFSymbolButton.swift in Sources */,
E13DD3ED27178A54009D4DAF /* UserSignInViewModel.swift in Sources */, E13DD3ED27178A54009D4DAF /* UserSignInViewModel.swift in Sources */,
E1763A272BF303C9004DF6AB /* ServerSelectionMenu.swift in Sources */, E1763A272BF303C9004DF6AB /* ServerSelectionMenu.swift in Sources */,
@ -4163,6 +4173,7 @@
E102315F2BCF8B75009D71FC /* VideoPlayerWrapperCoordinator.swift in Sources */, E102315F2BCF8B75009D71FC /* VideoPlayerWrapperCoordinator.swift in Sources */,
E1C9260D2887565C002A7A66 /* CinematicScrollView.swift in Sources */, E1C9260D2887565C002A7A66 /* CinematicScrollView.swift in Sources */,
E1575E90293E7B1E001665B1 /* EdgeInsets.swift in Sources */, E1575E90293E7B1E001665B1 /* EdgeInsets.swift in Sources */,
4E762AAF2C3A1A95004D1579 /* PlaybackBitrate.swift in Sources */,
E1E6C43D29AECC310064123F /* BarActionButtons.swift in Sources */, E1E6C43D29AECC310064123F /* BarActionButtons.swift in Sources */,
E1E6C44529AECCF20064123F /* PlayNextItemActionButton.swift in Sources */, E1E6C44529AECCF20064123F /* PlayNextItemActionButton.swift in Sources */,
6264E88D273850380081A12A /* Strings.swift in Sources */, 6264E88D273850380081A12A /* Strings.swift in Sources */,
@ -4329,6 +4340,7 @@
E11895B32893844A0042947B /* BackgroundParallaxHeaderModifier.swift in Sources */, E11895B32893844A0042947B /* BackgroundParallaxHeaderModifier.swift in Sources */,
E19D41B02BF2B7540082B8B2 /* URLSessionConfiguration.swift in Sources */, E19D41B02BF2B7540082B8B2 /* URLSessionConfiguration.swift in Sources */,
E172D3AD2BAC9DF8007B4647 /* SeasonItemViewModel.swift in Sources */, E172D3AD2BAC9DF8007B4647 /* SeasonItemViewModel.swift in Sources */,
4E762AAE2C3A1A95004D1579 /* PlaybackBitrate.swift in Sources */,
536D3D78267BD5C30004248C /* ViewModel.swift in Sources */, 536D3D78267BD5C30004248C /* ViewModel.swift in Sources */,
E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */, E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */,
E175AFF3299AC117004DCF52 /* DebugSettingsView.swift in Sources */, E175AFF3299AC117004DCF52 /* DebugSettingsView.swift in Sources */,
@ -4542,6 +4554,7 @@
E18E01DD288747230022598C /* iPadOSSeriesItemContentView.swift in Sources */, E18E01DD288747230022598C /* iPadOSSeriesItemContentView.swift in Sources */,
E14EA1692BF7330A00DE757A /* UserProfileImageViewModel.swift in Sources */, E14EA1692BF7330A00DE757A /* UserProfileImageViewModel.swift in Sources */,
E18ACA952A15A3E100BB4F35 /* (null) in Sources */, E18ACA952A15A3E100BB4F35 /* (null) in Sources */,
4E73E2AE2C420207002D2A78 /* MaximumBitrateSettingsView.swift in Sources */,
E1D5C39B28DF993400CDBEFB /* ThumbSlider.swift in Sources */, E1D5C39B28DF993400CDBEFB /* ThumbSlider.swift in Sources */,
E1DC983D296DEB9B00982F06 /* UnwatchedIndicator.swift in Sources */, E1DC983D296DEB9B00982F06 /* UnwatchedIndicator.swift in Sources */,
E1FE69AA28C29CC20021BC93 /* LandscapePosterProgressBar.swift in Sources */, E1FE69AA28C29CC20021BC93 /* LandscapePosterProgressBar.swift in Sources */,
@ -4642,6 +4655,7 @@
E1A3E4C72BB74E50005C59F8 /* EpisodeCard.swift in Sources */, E1A3E4C72BB74E50005C59F8 /* EpisodeCard.swift in Sources */,
E1153DB42BBA80FB00424D36 /* EmptyCard.swift in Sources */, E1153DB42BBA80FB00424D36 /* EmptyCard.swift in Sources */,
E1D3043528D1763100587289 /* SeeAllButton.swift in Sources */, E1D3043528D1763100587289 /* SeeAllButton.swift in Sources */,
4E73E2A62C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */,
E172D3B22BACA569007B4647 /* EpisodeContent.swift in Sources */, E172D3B22BACA569007B4647 /* EpisodeContent.swift in Sources */,
E13F05EC28BC9000003499D2 /* LibraryDisplayType.swift in Sources */, E13F05EC28BC9000003499D2 /* LibraryDisplayType.swift in Sources */,
4E16FD572C01A32700110147 /* LetterPickerOrientation.swift in Sources */, 4E16FD572C01A32700110147 /* LetterPickerOrientation.swift in Sources */,

View File

@ -0,0 +1,41 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//
import Defaults
import SwiftUI
struct MaximumBitrateSettingsView: View {
@Default(.VideoPlayer.appMaximumBitrate)
private var appMaximumBitrate
@Default(.VideoPlayer.appMaximumBitrateTest)
private var appMaximumBitrateTest
var body: some View {
Form {
Section {
CaseIterablePicker(
L10n.maximumBitrate,
selection: $appMaximumBitrate
)
if appMaximumBitrate == PlaybackBitrate.auto {
CaseIterablePicker(
L10n.testSize,
selection: $appMaximumBitrateTest
)
}
} footer: {
if appMaximumBitrate == PlaybackBitrate.auto {
Text(L10n.bitrateTestDescription)
}
}
}
.navigationTitle(L10n.maximumBitrate)
}
}

View File

@ -71,6 +71,11 @@ struct SettingsView: View {
.onSelect { .onSelect {
router.route(to: \.videoPlayerSettings) router.route(to: \.videoPlayerSettings)
} }
ChevronButton(L10n.maximumBitrate)
.onSelect {
router.route(to: \.maximumBitrateSettings)
}
} }
Section(L10n.accessibility) { Section(L10n.accessibility) {