Improved thumbnail handling

- ThumbnailsModel optionally returns the quality
- Have constants for 4:3 and 16:9 aspect ratio
- Add high quality options for thumbnails
- Rename Highest quality to Best quality
- make 4:3 thumbnails fill the VideoCell
- use .maxes instead of .maxresdefault (the latter sometimes returns white images)

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
This commit is contained in:
Toni Förster 2024-08-19 16:35:29 +02:00
parent af75afa912
commit 35534bcbb1
No known key found for this signature in database
GPG Key ID: 292F3E5086C83FC7
8 changed files with 31 additions and 39 deletions

View File

@ -20,21 +20,23 @@ final class ThumbnailsModel: ObservableObject {
return unloadable.contains(url) return unloadable.contains(url)
} }
func best(_ video: Video) -> URL? { func best(_ video: Video) -> (url: URL?, quality: Thumbnail.Quality?) {
for quality in availableQualitites { for quality in availableQualitites {
let url = video.thumbnailURL(quality: quality) let url = video.thumbnailURL(quality: quality)
if !isUnloadable(url) { if !isUnloadable(url) {
return url return (url, quality)
} }
} }
return nil return (nil, nil)
} }
private var availableQualitites: [Thumbnail.Quality] { private var availableQualitites: [Thumbnail.Quality] {
switch Defaults[.thumbnailsQuality] { switch Defaults[.thumbnailsQuality] {
case .highest: case .highest:
return [.maxresdefault, .medium, .default] return [.maxres, .high, .medium, .default]
case .high:
return [.high, .medium, .default]
case .medium: case .medium:
return [.medium, .default] return [.medium, .default]
case .low: case .low:

View File

@ -4,6 +4,8 @@ import SwiftUI
enum Constants { enum Constants {
static let overlayAnimation = Animation.linear(duration: 0.2) static let overlayAnimation = Animation.linear(duration: 0.2)
static let aspectRatio16x9 = 16.0 / 9.0
static let aspectRatio4x3 = 4.0 / 3.0
static var isAppleTV: Bool { static var isAppleTV: Bool {
#if os(iOS) #if os(iOS)

View File

@ -462,12 +462,14 @@ enum ButtonLabelStyle: String, CaseIterable, Defaults.Serializable {
} }
enum ThumbnailsQuality: String, CaseIterable, Defaults.Serializable { enum ThumbnailsQuality: String, CaseIterable, Defaults.Serializable {
case highest, medium, low case highest, high, medium, low
var description: String { var description: String {
switch self { switch self {
case .highest: case .highest:
return "Highest quality".localized() return "Best quality".localized()
case .high:
return "High quality".localized()
case .medium: case .medium:
return "Medium quality".localized() return "Medium quality".localized()
case .low: case .low:

View File

@ -265,7 +265,7 @@ struct PlayerControls: View {
var controlsBackgroundURL: URL? { var controlsBackgroundURL: URL? {
if let video = player.videoForDisplay, if let video = player.videoForDisplay,
let url = thumbnails.best(video) let url = thumbnails.best(video).url
{ {
return url return url
} }

View File

@ -65,7 +65,7 @@ import SwiftUI
} }
static var thumbnailHeight: Double { static var thumbnailHeight: Double {
thumbnailWidth / (16 / 9) thumbnailWidth / Constants.aspectRatio16x9
} }
} }
@ -119,7 +119,7 @@ import SwiftUI
} }
static var thumbnailHeight: Double { static var thumbnailHeight: Double {
thumbnailWidth / 1.7777 thumbnailWidth / Constants.aspectRatio16x9
} }
} }
#endif #endif

View File

@ -19,7 +19,7 @@ struct VideoPlayerView: View {
static let hiddenOffset = 0.0 static let hiddenOffset = 0.0
#endif #endif
static let defaultAspectRatio = 16 / 9.0 static let defaultAspectRatio = Constants.aspectRatio16x9
static var defaultMinimumHeightLeft: Double { static var defaultMinimumHeightLeft: Double {
#if os(macOS) #if os(macOS)
335 335

View File

@ -219,22 +219,18 @@ struct VideoBanner: View {
return watch!.finished ? 0.5 : 1 return watch!.finished ? 0.5 : 1
} }
private var thumbnailWidth: Double {
#if os(tvOS)
356
#else
120
#endif
}
private var thumbnailHeight: Double { private var thumbnailHeight: Double {
#if os(tvOS) #if os(tvOS)
200 200
#else #else
72 75
#endif #endif
} }
private var thumbnailWidth: Double {
thumbnailHeight * Constants.aspectRatio16x9
}
private var videoDurationLabel: String? { private var videoDurationLabel: String? {
guard videoDuration != 0 else { return nil } guard videoDuration != 0 else { return nil }
return (videoDuration ?? video?.length)?.formattedAsPlaybackTime() return (videoDuration ?? video?.length)?.formattedAsPlaybackTime()

View File

@ -440,7 +440,7 @@ struct VideoCell: View {
#endif #endif
} }
.mask(RoundedRectangle(cornerRadius: thumbnailRoundingCornerRadius)) .mask(RoundedRectangle(cornerRadius: thumbnailRoundingCornerRadius))
.modifier(AspectRatioModifier()) .aspectRatio(Constants.aspectRatio16x9, contentMode: .fill)
} }
private var time: String? { private var time: String? {
@ -471,24 +471,6 @@ struct VideoCell: View {
.lineLimit(lineLimit) .lineLimit(lineLimit)
.truncationMode(.middle) .truncationMode(.middle)
} }
struct AspectRatioModifier: ViewModifier {
@Environment(\.horizontalCells) private var horizontalCells
func body(content: Content) -> some View {
Group {
if horizontalCells {
content
} else {
content
.aspectRatio(
VideoPlayerView.defaultAspectRatio,
contentMode: .fill
)
}
}
}
}
} }
struct VideoCellThumbnail: View { struct VideoCellThumbnail: View {
@ -496,7 +478,15 @@ struct VideoCellThumbnail: View {
@ObservedObject private var thumbnails = ThumbnailsModel.shared @ObservedObject private var thumbnails = ThumbnailsModel.shared
var body: some View { var body: some View {
ThumbnailView(url: thumbnails.best(video)) GeometryReader { geometry in
let (url, quality) = thumbnails.best(video)
let aspectRatio = (quality == .default || quality == .high) ? Constants.aspectRatio4x3 : Constants.aspectRatio16x9
ThumbnailView(url: url)
.aspectRatio(aspectRatio, contentMode: .fill)
.frame(width: geometry.size.width, height: geometry.size.height)
.clipped()
}
} }
} }