[iOS] Playback Quality - Learn More (#1316)
Some checks failed
Build 🔨 / Build 🔨 (Swiftfin tvOS) (push) Has been cancelled
Build 🔨 / Build 🔨 (Swiftfin) (push) Has been cancelled

* Playback Quality - Learn More

* TODO: Fix leading not working on second line.

* Remove layoutDirection.

* Implement for tvOS. Slightly different spacing.

* VStack

* WIP - tvOS Implementaiton. SUBJECT TO CHANGE / ELIMINATION.

* Background Icon & formatting

* wip

* Review Changes. Remove unused Strings, clean up comments.

* Remove duplicate items used for testing

* Remove tvOS scrollIfLargerThanContainer for now.

---------

Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
Joe Kribs 2024-11-21 13:47:11 -07:00 committed by GitHub
parent 687cfa6b5f
commit 994e99d141
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 315 additions and 62 deletions

View File

@ -129,3 +129,29 @@ extension FormatStyle where Self == LastSeenFormatStyle {
static var lastSeen: LastSeenFormatStyle { LastSeenFormatStyle() }
}
struct IntBitRateFormatStyle: FormatStyle {
func format(_ value: Int) -> String {
let units = [
L10n.bitsPerSecond,
L10n.kilobitsPerSecond,
L10n.megabitsPerSecond,
L10n.gigabitsPerSecond,
L10n.terabitsPerSecond,
]
var adjustedValue = Double(value)
var unitIndex = 0
while adjustedValue >= 1000, unitIndex < units.count - 1 {
adjustedValue /= 1000
unitIndex += 1
}
let formattedValue = String(format: "%.1f", adjustedValue)
return "\(formattedValue) \(units[unitIndex])"
}
}
extension FormatStyle where Self == IntBitRateFormatStyle {
static var bitRate: IntBitRateFormatStyle { IntBitRateFormatStyle() }
}

View File

@ -108,6 +108,8 @@ internal enum L10n {
internal static let authorize = L10n.tr("Localizable", "authorize", fallback: "Authorize")
/// PlaybackCompatibility Default Category
internal static let auto = L10n.tr("Localizable", "auto", fallback: "Auto")
/// Optimizes playback using default settings for most devices. Some formats may require server transcoding for non-compatible media types.
internal static let autoDescription = L10n.tr("Localizable", "autoDescription", fallback: "Optimizes playback using default settings for most devices. Some formats may require server transcoding for non-compatible media types.")
/// Auto Play
internal static let autoPlay = L10n.tr("Localizable", "autoPlay", fallback: "Auto Play")
/// Back
@ -116,12 +118,14 @@ internal enum L10n {
internal static let barButtons = L10n.tr("Localizable", "barButtons", fallback: "Bar Buttons")
/// Behavior
internal static let behavior = L10n.tr("Localizable", "behavior", fallback: "Behavior")
/// Tests your server connection to assess internet speed and adjust bandwidth automatically.
internal static let birateAutoDescription = L10n.tr("Localizable", "birateAutoDescription", fallback: "Tests your server connection to assess internet speed and adjust bandwidth automatically.")
/// Option for automatic bitrate selection
internal static let bitrateAuto = L10n.tr("Localizable", "bitrateAuto", fallback: "Auto")
/// Default Bitrate
internal static let bitrateDefault = L10n.tr("Localizable", "bitrateDefault", fallback: "Default Bitrate")
/// Default Bitrate Description
internal static let bitrateDefaultDescription = L10n.tr("Localizable", "bitrateDefaultDescription", fallback: "Limits the internet bandwidth used during video playback")
internal static let bitrateDefaultDescription = L10n.tr("Localizable", "bitrateDefaultDescription", fallback: "Limits the internet bandwidth used during playback.")
/// Option to set the bitrate to 480p quality at 1.5 Mbps
internal static let bitrateKbps1500 = L10n.tr("Localizable", "bitrateKbps1500", fallback: "480p - 1.5 Mbps")
/// Option to set the bitrate to 360p quality at 420 Kbps
@ -130,6 +134,10 @@ internal enum L10n {
internal static let bitrateKbps720 = L10n.tr("Localizable", "bitrateKbps720", fallback: "480p - 720 Kbps")
/// Option for the maximum bitrate
internal static let bitrateMax = L10n.tr("Localizable", "bitrateMax", fallback: "Maximum")
/// Maximizes bandwidth usage, up to %@, for each playback stream to ensure the highest quality.
internal static func bitrateMaxDescription(_ p1: Any) -> String {
return L10n.tr("Localizable", "bitrateMaxDescription", String(describing: p1), fallback: "Maximizes bandwidth usage, up to %@, for each playback stream to ensure the highest quality.")
}
/// Option to set the bitrate to 1080p quality at 10 Mbps
internal static let bitrateMbps10 = L10n.tr("Localizable", "bitrateMbps10", fallback: "1080p - 10 Mbps")
/// Option to set the bitrate to 4K quality at 120 Mbps
@ -154,10 +162,10 @@ internal enum L10n {
internal static let bitrateMbps80 = L10n.tr("Localizable", "bitrateMbps80", fallback: "4K - 80 Mbps")
/// Bitrate Automatic Section Header
internal static let bitrateTest = L10n.tr("Localizable", "bitrateTest", fallback: "Bitrate Test")
/// Description for bitrate test duration description
internal static let bitrateTestDescription = L10n.tr("Localizable", "bitrateTestDescription", fallback: "Determines the length of the 'Auto' bitrate test used to find the available internet bandwidth")
/// Description for bitrate test duration indicating longer tests provide more accurate bitrates but may delay playback
internal static let bitrateTestDisclaimer = L10n.tr("Localizable", "bitrateTestDisclaimer", fallback: "Longer tests are more accurate but may result in a delayed playback")
internal static let bitrateTestDisclaimer = L10n.tr("Localizable", "bitrateTestDisclaimer", fallback: "Longer tests are more accurate but may result in a delayed playback.")
/// bps
internal static let bitsPerSecond = L10n.tr("Localizable", "bitsPerSecond", fallback: "bps")
/// Blue
internal static let blue = L10n.tr("Localizable", "blue", fallback: "Blue")
/// Bugs and Features
@ -220,6 +228,8 @@ internal enum L10n {
internal static let compatibility = L10n.tr("Localizable", "compatibility", fallback: "Compatibility")
/// PlaybackCompatibility Compatible Category
internal static let compatible = L10n.tr("Localizable", "compatible", fallback: "Most Compatible")
/// Converts all media to H.264 video and AAC audio for maximum compatibility. May require server transcoding for non-compatible media types.
internal static let compatibleDescription = L10n.tr("Localizable", "compatibleDescription", fallback: "Converts all media to H.264 video and AAC audio for maximum compatibility. May require server transcoding for non-compatible media types.")
/// Confirm Task Fuction
internal static let confirm = L10n.tr("Localizable", "confirm", fallback: "Confirm")
/// Confirm Close
@ -262,6 +272,8 @@ internal enum L10n {
internal static let currentPosition = L10n.tr("Localizable", "currentPosition", fallback: "Current Position")
/// PlaybackCompatibility Custom Category
internal static let custom = L10n.tr("Localizable", "custom", fallback: "Custom")
/// Allows advanced customization of device profiles for native playback. Incorrect settings may affect playback.
internal static let customDescription = L10n.tr("Localizable", "customDescription", fallback: "Allows advanced customization of device profiles for native playback. Incorrect settings may affect playback.")
/// Custom Device Name
internal static let customDeviceName = L10n.tr("Localizable", "customDeviceName", fallback: "Custom Device Name")
/// Your custom device name '%1$@' has been saved.
@ -269,11 +281,11 @@ internal enum L10n {
return L10n.tr("Localizable", "customDeviceNameSaved", String(describing: p1), fallback: "Your custom device name '%1$@' has been saved.")
}
/// Custom profile is Added to the Existing Profiles
internal static let customDeviceProfileAdd = L10n.tr("Localizable", "customDeviceProfileAdd", fallback: "The custom device profiles will be added to the default Swiftfin device profiles")
internal static let customDeviceProfileAdd = L10n.tr("Localizable", "customDeviceProfileAdd", fallback: "The custom device profiles will be added to the default Swiftfin device profiles.")
/// Device Profile Section Description
internal static let customDeviceProfileDescription = L10n.tr("Localizable", "customDeviceProfileDescription", fallback: "Dictates back to the Jellyfin Server what this device hardware is capable of playing")
internal static let customDeviceProfileDescription = L10n.tr("Localizable", "customDeviceProfileDescription", fallback: "Dictates back to the Jellyfin Server what this device hardware is capable of playing.")
/// Custom profile will replace the Existing Profiles
internal static let customDeviceProfileReplace = L10n.tr("Localizable", "customDeviceProfileReplace", fallback: "The custom device profiles will replace the default Swiftfin device profiles")
internal static let customDeviceProfileReplace = L10n.tr("Localizable", "customDeviceProfileReplace", fallback: "The custom device profiles will replace the default Swiftfin device profiles.")
/// Settings View - Customize
internal static let customize = L10n.tr("Localizable", "customize", fallback: "Customize")
/// Section Header for a Custom Device Profile
@ -342,10 +354,14 @@ internal enum L10n {
internal static let device = L10n.tr("Localizable", "device", fallback: "Device")
/// Section Header for Device Profiles
internal static let deviceProfile = L10n.tr("Localizable", "deviceProfile", fallback: "Device Profile")
/// Decide which media plays natively or requires server transcoding for compatibility.
internal static let deviceProfileDescription = L10n.tr("Localizable", "deviceProfileDescription", fallback: "Decide which media plays natively or requires server transcoding for compatibility.")
/// Devices
internal static let devices = L10n.tr("Localizable", "devices", fallback: "Devices")
/// PlaybackCompatibility DirectPlay Category
internal static let direct = L10n.tr("Localizable", "direct", fallback: "Direct Play")
/// Plays content in its original format. May cause playback issues on unsupported media types.
internal static let directDescription = L10n.tr("Localizable", "directDescription", fallback: "Plays content in its original format. May cause playback issues on unsupported media types.")
/// DIRECTOR
internal static let director = L10n.tr("Localizable", "director", fallback: "DIRECTOR")
/// PlayMethod - Direct Play
@ -428,6 +444,8 @@ internal enum L10n {
internal static let genres = L10n.tr("Localizable", "genres", fallback: "Genres")
/// Gestures
internal static let gestures = L10n.tr("Localizable", "gestures", fallback: "Gestures")
/// Gbps
internal static let gigabitsPerSecond = L10n.tr("Localizable", "gigabitsPerSecond", fallback: "Gbps")
/// Green
internal static let green = L10n.tr("Localizable", "green", fallback: "Green")
/// Grid
@ -486,6 +504,8 @@ internal enum L10n {
}
/// Kids
internal static let kids = L10n.tr("Localizable", "kids", fallback: "Kids")
/// kbps
internal static let kilobitsPerSecond = L10n.tr("Localizable", "kilobitsPerSecond", fallback: "kbps")
/// Larger
internal static let larger = L10n.tr("Localizable", "larger", fallback: "Larger")
/// Largest
@ -535,9 +555,11 @@ internal enum L10n {
/// Option to set the maximum bitrate for playback
internal static let maximumBitrate = L10n.tr("Localizable", "maximumBitrate", fallback: "Maximum Bitrate")
/// Playback May Fail
internal static let mayResultInPlaybackFailure = L10n.tr("Localizable", "mayResultInPlaybackFailure", fallback: "This setting may result in media failing to start playback")
internal static let mayResultInPlaybackFailure = L10n.tr("Localizable", "mayResultInPlaybackFailure", fallback: "This setting may result in media failing to start playback.")
/// Media
internal static let media = L10n.tr("Localizable", "media", fallback: "Media")
/// Mbps
internal static let megabitsPerSecond = L10n.tr("Localizable", "megabitsPerSecond", fallback: "Mbps")
/// Menu Buttons
internal static let menuButtons = L10n.tr("Localizable", "menuButtons", fallback: "Menu Buttons")
/// Metadata
@ -1016,6 +1038,8 @@ internal enum L10n {
internal static let taskTriggerInterval = L10n.tr("Localizable", "taskTriggerInterval", fallback: "Sets the duration (in minutes) in between task triggers.")
/// Sets the maximum runtime (in hours) for this task trigger.
internal static let taskTriggerTimeLimit = L10n.tr("Localizable", "taskTriggerTimeLimit", fallback: "Sets the maximum runtime (in hours) for this task trigger.")
/// Tbps
internal static let terabitsPerSecond = L10n.tr("Localizable", "terabitsPerSecond", fallback: "Tbps")
/// Option to set the test size for bitrate testing
internal static let testSize = L10n.tr("Localizable", "testSize", fallback: "Test Size")
/// Time

View File

@ -0,0 +1,45 @@
//
// 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 SwiftUI
struct LearnMoreModal: View {
private let items: [TextPair]
// MARK: - Initializer
init(@ArrayBuilder<TextPair> items: () -> [TextPair]) {
self.items = items()
}
// MARK: - Body
var body: some View {
VStack(alignment: .leading, spacing: 16) {
ForEach(items) { content in
VStack(alignment: .leading, spacing: 8) {
Text(content.title)
.font(.headline)
.foregroundStyle(.primary)
Text(content.subtitle)
.font(.subheadline)
.foregroundStyle(.secondary)
}
.padding(8)
}
}
.padding(24)
.background {
RoundedRectangle(cornerRadius: 10)
.fill(Material.regular)
}
.padding()
}
}

View File

@ -20,13 +20,22 @@ struct PlaybackQualitySettingsView: View {
@EnvironmentObject
private var router: PlaybackQualitySettingsCoordinator.Router
// MARK: - Focus Management
@FocusState
private var focusedItem: FocusableItem?
private enum FocusableItem: Hashable {
case maximumBitrate
case compatibility
}
// MARK: - Body
var body: some View {
SplitFormWindowView()
.descriptionView {
Image(systemName: "play.rectangle.on.rectangle")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 400)
descriptionView
}
.contentView {
Section {
@ -34,12 +43,11 @@ struct PlaybackQualitySettingsView: View {
title: L10n.maximumBitrate,
selection: $appMaximumBitrate
)
.focused($focusedItem, equals: .maximumBitrate)
} header: {
L10n.bitrateDefault.text
} footer: {
VStack(alignment: .leading) {
L10n.bitrateDefaultDescription.text
}
L10n.bitrateDefaultDescription.text
}
.animation(.none, value: appMaximumBitrate)
@ -49,13 +57,8 @@ struct PlaybackQualitySettingsView: View {
title: L10n.testSize,
selection: $appMaximumBitrateTest
)
} header: {
L10n.bitrateTest.text
} footer: {
VStack(alignment: .leading, spacing: 8) {
L10n.bitrateTestDescription.text
L10n.bitrateTestDisclaimer.text
}
L10n.bitrateTestDisclaimer.text
}
}
@ -64,7 +67,7 @@ struct PlaybackQualitySettingsView: View {
title: L10n.compatibility,
selection: $compatibilityMode
)
.animation(.none, value: compatibilityMode)
.focused($focusedItem, equals: .compatibility)
if compatibilityMode == .custom {
ChevronButton(L10n.profiles)
@ -74,8 +77,66 @@ struct PlaybackQualitySettingsView: View {
}
} header: {
L10n.deviceProfile.text
} footer: {
L10n.deviceProfileDescription.text
}
}
.navigationTitle(L10n.playbackQuality)
}
// MARK: - Description View Icon
private var descriptionView: some View {
ZStack {
Image(systemName: "play.rectangle.on.rectangle")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 400)
focusedDescription
.transition(.opacity.animation(.linear(duration: 0.2)))
}
}
// MARK: - Description View on Focus
@ViewBuilder
private var focusedDescription: some View {
switch focusedItem {
case .maximumBitrate:
LearnMoreModal {
TextPair(
title: L10n.auto,
subtitle: L10n.birateAutoDescription
)
TextPair(
title: L10n.bitrateMax,
subtitle: L10n.bitrateMaxDescription(PlaybackBitrate.max.rawValue.formatted(.bitRate))
)
}
case .compatibility:
LearnMoreModal {
TextPair(
title: L10n.auto,
subtitle: L10n.autoDescription
)
TextPair(
title: L10n.compatible,
subtitle: L10n.compatibleDescription
)
TextPair(
title: L10n.direct,
subtitle: L10n.directDescription
)
TextPair(
title: L10n.custom,
subtitle: L10n.customDescription
)
}
case nil:
EmptyView()
}
}
}

View File

@ -72,6 +72,7 @@
4E73E2A72C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */; };
4E762AAE2C3A1A95004D1579 /* PlaybackBitrate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */; };
4E762AAF2C3A1A95004D1579 /* PlaybackBitrate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */; };
4E884C652CEBB301004CF6AD /* LearnMoreModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E884C642CEBB2FF004CF6AD /* LearnMoreModal.swift */; };
4E8B34EA2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; };
4E8B34EB2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; };
4E8F74A22CE03C9000CC8969 /* ItemEditorCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8F74A02CE03C8B00CC8969 /* ItemEditorCoordinator.swift */; };
@ -1121,6 +1122,7 @@
4E71D6882C80910900A0174D /* EditCustomDeviceProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCustomDeviceProfileView.swift; sourceTree = "<group>"; };
4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackBitrateTestSize.swift; sourceTree = "<group>"; };
4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaybackBitrate.swift; sourceTree = "<group>"; };
4E884C642CEBB2FF004CF6AD /* LearnMoreModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearnMoreModal.swift; sourceTree = "<group>"; };
4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemFilter.swift; sourceTree = "<group>"; };
4E8F74A02CE03C8B00CC8969 /* ItemEditorCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemEditorCoordinator.swift; sourceTree = "<group>"; };
4E8F74A42CE03D3800CC8969 /* ItemEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemEditorView.swift; sourceTree = "<group>"; };
@ -3269,6 +3271,7 @@
E1A42E4D28CBD3B200A14DCB /* HomeView */,
E12376B22A33DFAC001F5B44 /* ItemOverviewView.swift */,
E193D54E271942C000900D82 /* ItemView */,
4E884C642CEBB2FF004CF6AD /* LearnMoreModal.swift */,
E158C8D02A31947500C527C5 /* MediaSourceInfoView.swift */,
E103DF932BCF31C5000229B2 /* MediaView */,
E10231572BCF8AF8009D71FC /* ProgramsView */,
@ -4831,6 +4834,7 @@
E14EDEC62B8FB64E000F00A4 /* AnyItemFilter.swift in Sources */,
E14EDEC92B8FB65F000F00A4 /* ItemFilterType.swift in Sources */,
E1D37F4C2B9CEA5C00343D2B /* ImageSource.swift in Sources */,
4E884C652CEBB301004CF6AD /* LearnMoreModal.swift in Sources */,
E1B4E4372CA7795200DC49DE /* OrderedDictionary.swift in Sources */,
E1AD104E26D96CE3003E4A08 /* BaseItemDto.swift in Sources */,
E118959E289312020042947B /* BaseItemPerson+Poster.swift in Sources */,

View File

@ -30,32 +30,38 @@ struct LearnMoreButton: View {
isPresented = true
}
.foregroundStyle(Color.accentColor)
.font(.subheadline)
.buttonStyle(.plain)
.frame(maxWidth: .infinity, alignment: .leading)
.sheet(isPresented: $isPresented) {
NavigationView {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
ForEach(items) { content in
VStack(alignment: .leading, spacing: 8) {
Text(content.title)
.font(.headline)
.foregroundStyle(.primary)
learnMoreView
}
}
Text(content.subtitle)
.font(.subheadline)
.foregroundStyle(.secondary)
}
// MARK: - Learn More View
Divider()
private var learnMoreView: some View {
NavigationView {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
ForEach(items) { content in
VStack(alignment: .leading, spacing: 8) {
Text(content.title)
.font(.headline)
.foregroundStyle(.primary)
Text(content.subtitle)
.font(.subheadline)
.foregroundStyle(.secondary)
}
Divider()
}
.edgePadding()
}
.navigationTitle(title)
.navigationBarTitleDisplayMode(.inline)
.navigationBarCloseButton {
isPresented = false
}
.edgePadding()
}
.navigationTitle(title)
.navigationBarTitleDisplayMode(.inline)
.navigationBarCloseButton {
isPresented = false
}
}
}

View File

@ -72,7 +72,7 @@ struct CustomDeviceProfileSettingsView: View {
.navigationTitle(L10n.profiles)
.topBarTrailing {
if customProfiles.isNotEmpty {
Button("Add") {
Button(L10n.add) {
UIDevice.impact(.light)
router.route(to: \.createCustomDeviceProfile)
}

View File

@ -150,7 +150,7 @@ struct CustomizeViewsSettings: View {
if libraryDisplayType == .list, UIDevice.isPad {
BasicStepper(
title: "Columns",
title: L10n.columns,
value: $listColumnCount,
range: 1 ... 4,
step: 1

View File

@ -31,7 +31,20 @@ struct PlaybackQualitySettingsView: View {
} header: {
L10n.bitrateDefault.text
} footer: {
L10n.bitrateDefaultDescription.text
VStack(alignment: .leading) {
Text(L10n.bitrateDefaultDescription)
LearnMoreButton(L10n.bitrateDefault) {
TextPair(
title: L10n.auto,
subtitle: L10n.birateAutoDescription
)
TextPair(
title: L10n.bitrateMax,
subtitle: L10n.bitrateMaxDescription(PlaybackBitrate.max.rawValue.formatted(.bitRate))
)
}
.foregroundStyle(.foreground, .primary)
}
}
.animation(.none, value: appMaximumBitrate)
@ -44,18 +57,12 @@ struct PlaybackQualitySettingsView: View {
} header: {
L10n.bitrateTest.text
} footer: {
VStack(alignment: .leading, spacing: 8) {
L10n.bitrateTestDescription.text
VStack(alignment: .leading) {
L10n.bitrateTestDisclaimer.text
}
}
}
// TODO: Have a small description and a "Learn More..."
// button that will open a page for longer descriptions
// of each option. See: iOS Settings/Accessibility/VoiceOver
// for reference
Section {
CaseIterablePicker(
L10n.compatibility,
@ -70,7 +77,30 @@ struct PlaybackQualitySettingsView: View {
}
}
} header: {
L10n.deviceProfile.text
Text(L10n.deviceProfile)
} footer: {
VStack(alignment: .leading) {
Text(L10n.deviceProfileDescription)
LearnMoreButton(L10n.deviceProfile) {
TextPair(
title: L10n.auto,
subtitle: L10n.autoDescription
)
TextPair(
title: L10n.compatible,
subtitle: L10n.compatibleDescription
)
TextPair(
title: L10n.direct,
subtitle: L10n.directDescription
)
TextPair(
title: L10n.custom,
subtitle: L10n.customDescription
)
}
.foregroundStyle(.foreground, .primary)
}
}
}
.animation(.linear, value: appMaximumBitrate)

View File

@ -364,11 +364,8 @@
/* Option to set the bitrate to 360p quality at 420 Kbps */
"bitrateKbps420" = "360p - 420 Kbps";
/* Description for bitrate test duration description */
"bitrateTestDescription" = "Determines the length of the 'Auto' bitrate test used to find the available internet bandwidth";
/* Description for bitrate test duration indicating longer tests provide more accurate bitrates but may delay playback */
"bitrateTestDisclaimer" = "Longer tests are more accurate but may result in a delayed playback";
"bitrateTestDisclaimer" = "Longer tests are more accurate but may result in a delayed playback.";
/* Select Server View */
"servers" = "Servers";
@ -464,10 +461,10 @@
"deviceProfile" = "Device Profile";
/* Custom profile is Added to the Existing Profiles */
"customDeviceProfileAdd" = "The custom device profiles will be added to the default Swiftfin device profiles";
"customDeviceProfileAdd" = "The custom device profiles will be added to the default Swiftfin device profiles.";
/* Custom profile will replace the Existing Profiles */
"customDeviceProfileReplace" = "The custom device profiles will replace the default Swiftfin device profiles";
"customDeviceProfileReplace" = "The custom device profiles will replace the default Swiftfin device profiles.";
/* Section for Playback Quality Settings */
"playbackQuality" = "Playback Quality";
@ -503,13 +500,13 @@
"bitrateDefault" = "Default Bitrate";
/* Default Bitrate Description */
"bitrateDefaultDescription" = "Limits the internet bandwidth used during video playback";
"bitrateDefaultDescription" = "Limits the internet bandwidth used during playback.";
/* Playback May Fail */
"mayResultInPlaybackFailure" = "This setting may result in media failing to start playback";
"mayResultInPlaybackFailure" = "This setting may result in media failing to start playback.";
/* Device Profile Section Description */
"customDeviceProfileDescription" = "Dictates back to the Jellyfin Server what this device hardware is capable of playing";
"customDeviceProfileDescription" = "Dictates back to the Jellyfin Server what this device hardware is capable of playing.";
/* Session Device Section Label */
"device" = "Device";
@ -1322,3 +1319,63 @@
/// Replace all metadata and images
/// Full refresh that replaces all unlocked metadata and images
"replaceAllDescription" = "Replace all unlocked metadata and images with new information.";
/// Device Profile - Description
/// Explains how device profiles control playback and transcoding behavior
/// Used in the device profile settings section
"deviceProfileDescription" = "Decide which media plays natively or requires server transcoding for compatibility.";
/// Auto - Description
/// Optimizes playback using default settings for most devices
/// Some formats may require server transcoding
"autoDescription" = "Optimizes playback using default settings for most devices. Some formats may require server transcoding for non-compatible media types.";
/// Compatible - Description
/// Converts media to H.264 video and AAC audio for compatibility
/// Requires server transcoding for all content
"compatibleDescription" = "Converts all media to H.264 video and AAC audio for maximum compatibility. May require server transcoding for non-compatible media types.";
/// Direct - Description
/// Plays content in its original format without transcoding
/// May cause playback issues on unsupported devices
"directDescription" = "Plays content in its original format. May cause playback issues on unsupported media types.";
/// Custom - Description
/// Allows customization of device profiles for native playback
/// Incorrect settings may result in playback issues
"customDescription" = "Allows advanced customization of device profiles for native playback. Incorrect settings may affect playback.";
/// Server Connection Test - Description
/// Tests the connection to the server to assess internet speed
/// Used to adjust bandwidth settings automatically
"birateAutoDescription" = "Tests your server connection to assess internet speed and adjust bandwidth automatically.";
/// Bandwidth Usage - Description
/// Indicates the maximum bandwidth used per playback stream
/// Helps to manage data usage during streaming
"bitrateMaxDescription" = "Maximizes bandwidth usage, up to %@, for each playback stream to ensure the highest quality.";
// Bits Per Second - Unit
// Represents a speed in bits per second
// Used for bandwidth display
"bitsPerSecond" = "bps";
// Kilobits Per Second - Unit
// Represents a speed in kilobits per second
// Used for bandwidth display
"kilobitsPerSecond" = "kbps";
// Megabits Per Second - Unit
// Represents a speed in megabits per second
// Used for bandwidth display
"megabitsPerSecond" = "Mbps";
// Gigabits Per Second - Unit
// Represents a speed in gigabits per second
// Used for bandwidth display
"gigabitsPerSecond" = "Gbps";
// Terabits Per Second - Unit
// Represents a speed in terabits per second
// Used for bandwidth display
"terabitsPerSecond" = "Tbps";