From e35f8b78921b61a119bc32075d966f819bff10c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20F=C3=B6rster?= Date: Fri, 17 May 2024 16:16:48 +0200 Subject: [PATCH] Snappy UI - Offloading non UI task to background threads This gives a huge increase in perceived performance. The UI is now much more responsive since some tasks are run in the background and don't block the UI anymore. --- Model/Player/Backends/MPVBackend.swift | 2 +- Shared/Home/FavoriteItemView.swift | 15 ++- Shared/Home/FavoriteResourceObserver.swift | 46 +++++++-- Shared/YatteeApp.swift | 105 ++++++++++++--------- 4 files changed, 110 insertions(+), 58 deletions(-) diff --git a/Model/Player/Backends/MPVBackend.swift b/Model/Player/Backends/MPVBackend.swift index 2ec93a1c..4b2938cc 100644 --- a/Model/Player/Backends/MPVBackend.swift +++ b/Model/Player/Backends/MPVBackend.swift @@ -10,7 +10,7 @@ import SwiftUI final class MPVBackend: PlayerBackend { static var timeUpdateInterval = 0.5 - static var networkStateUpdateInterval = 1.0 + static var networkStateUpdateInterval = 0.1 private var logger = Logger(label: "mpv-backend") diff --git a/Shared/Home/FavoriteItemView.swift b/Shared/Home/FavoriteItemView.swift index 0154854b..9f1b71c9 100644 --- a/Shared/Home/FavoriteItemView.swift +++ b/Shared/Home/FavoriteItemView.swift @@ -89,6 +89,9 @@ struct FavoriteItemView: View { loadCacheAndResource() } } + .onDisappear { + resource?.removeObservers(ownedBy: store) + } .onChange(of: player.currentVideo) { _ in reloadVisibleWatches() } .onChange(of: hideShorts) { _ in reloadVisibleWatches() } .onChange(of: hideWatched) { _ in reloadVisibleWatches() } @@ -96,13 +99,12 @@ struct FavoriteItemView: View { } .id(watchModel.historyToken) .onChange(of: accounts.current) { _ in + resource?.removeObservers(ownedBy: store) resource?.addObserver(store) loadCacheAndResource(force: true) } .onChange(of: watchModel.historyToken) { _ in - Delay.by(0.5) { - reloadVisibleWatches() - } + reloadVisibleWatches() } } @@ -164,12 +166,15 @@ struct FavoriteItemView: View { .prefix(favoritesModel.limit(item)) ) let last = watches.last + for watch in watches { player.loadHistoryVideoDetails(watch) { guard let video = player.historyVideo(watch.videoID), itemVisible(.init(video: video)) else { return } visibleWatches.append(watch) - guard watch == last else { return } - visibleWatches.sort { $0.watchedAt ?? Date() > $1.watchedAt ?? Date() } + + if watch == last { + visibleWatches.sort { $0.watchedAt ?? Date() > $1.watchedAt ?? Date() } + } } } } diff --git a/Shared/Home/FavoriteResourceObserver.swift b/Shared/Home/FavoriteResourceObserver.swift index 48cb4f97..fc6e19a2 100644 --- a/Shared/Home/FavoriteResourceObserver.swift +++ b/Shared/Home/FavoriteResourceObserver.swift @@ -5,18 +5,52 @@ final class FavoriteResourceObserver: ObservableObject, ResourceObserver { @Published var contentItems = [ContentItem]() func resourceChanged(_ resource: Resource, event _: ResourceEvent) { + // swiftlint:disable discouraged_optional_collection + var newVideos: [Video]? + var newItems: [ContentItem]? + // swiftlint:enable discouraged_optional_collection + + var newChannel: Channel? + var newChannelPlaylist: ChannelPlaylist? + var newPlaylist: Playlist? + var newPage: SearchPage? + if let videos: [Video] = resource.typedContent() { - contentItems = videos.map { ContentItem(video: $0) } + newVideos = videos } else if let channel: Channel = resource.typedContent() { - contentItems = channel.videos.map { ContentItem(video: $0) } + newChannel = channel } else if let playlist: ChannelPlaylist = resource.typedContent() { - contentItems = playlist.videos.map { ContentItem(video: $0) } + newChannelPlaylist = playlist } else if let playlist: Playlist = resource.typedContent() { - contentItems = playlist.videos.map { ContentItem(video: $0) } + newPlaylist = playlist } else if let page: SearchPage = resource.typedContent() { - contentItems = page.results + newPage = page } else if let items: [ContentItem] = resource.typedContent() { - contentItems = items + newItems = items + } + + DispatchQueue.global(qos: .userInitiated).async { + var newContentItems: [ContentItem] = [] + + if let videos = newVideos { + newContentItems = videos.map { ContentItem(video: $0) } + } else if let channel = newChannel { + newContentItems = channel.videos.map { ContentItem(video: $0) } + } else if let playlist = newChannelPlaylist { + newContentItems = playlist.videos.map { ContentItem(video: $0) } + } else if let playlist = newPlaylist { + newContentItems = playlist.videos.map { ContentItem(video: $0) } + } else if let page = newPage { + newContentItems = page.results + } else if let items = newItems { + newContentItems = items + } + + DispatchQueue.main.async { + if !newContentItems.isEmpty { + self.contentItems = newContentItems + } + } } } } diff --git a/Shared/YatteeApp.swift b/Shared/YatteeApp.swift index 1975706b..4adfbd16 100644 --- a/Shared/YatteeApp.swift +++ b/Shared/YatteeApp.swift @@ -150,61 +150,74 @@ struct YatteeApp: App { } configured = true - #if DEBUG - SiestaLog.Category.enabled = .common - #endif - SDImageCodersManager.shared.addCoder(SDImageAWebPCoder.shared) - SDWebImageManager.defaultImageCache = PINCache(name: "stream.yattee.app") + DispatchQueue.main.async { + #if DEBUG + SiestaLog.Category.enabled = .common + #endif + SDImageCodersManager.shared.addCoder(SDImageAWebPCoder.shared) + SDWebImageManager.defaultImageCache = PINCache(name: "stream.yattee.app") - if !Defaults[.lastAccountIsPublic] { - AccountsModel.shared.configureAccount() - } - - if let countryOfPublicInstances = Defaults[.countryOfPublicInstances] { - InstancesManifest.shared.setPublicAccount(countryOfPublicInstances, asCurrent: AccountsModel.shared.current.isNil) - } - - if !AccountsModel.shared.current.isNil { - player.restoreQueue() - } - - if !Defaults[.saveRecents] { - recents.clear() - } - - let startupSection = Defaults[.startupSection] - var section: TabSelection? = startupSection.tabSelection - - #if os(macOS) - if section == .playlists { - section = .search + if !Defaults[.lastAccountIsPublic] { + AccountsModel.shared.configureAccount() } - #endif - NavigationModel.shared.tabSelection = section ?? .search + if let countryOfPublicInstances = Defaults[.countryOfPublicInstances] { + InstancesManifest.shared.setPublicAccount(countryOfPublicInstances, asCurrent: AccountsModel.shared.current.isNil) + } - playlists.load() + if !AccountsModel.shared.current.isNil { + player.restoreQueue() + } - #if !os(macOS) - player.updateRemoteCommandCenter() - #endif - - if player.presentingPlayer { - player.presentingPlayer = false - } - - #if os(iOS) - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - if Defaults[.lockPortraitWhenBrowsing] { - Orientation.lockOrientation(.portrait, andRotateTo: .portrait) + DispatchQueue.global(qos: .userInitiated).async { + if !Defaults[.saveRecents] { + recents.clear() } } - #endif - URLBookmarkModel.shared.refreshAll() + let startupSection = Defaults[.startupSection] + var section: TabSelection? = startupSection.tabSelection - migrateHomeHistoryItems() - migrateQualityProfiles() + #if os(macOS) + if section == .playlists { + section = .search + } + #endif + + NavigationModel.shared.tabSelection = section ?? .search + + DispatchQueue.global(qos: .userInitiated).async { + playlists.load() + } + + #if !os(macOS) + player.updateRemoteCommandCenter() + #endif + + if player.presentingPlayer { + player.presentingPlayer = false + } + + #if os(iOS) + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + if Defaults[.lockPortraitWhenBrowsing] { + Orientation.lockOrientation(.all, andRotateTo: .portrait) + } + } + #endif + + DispatchQueue.global(qos: .userInitiated).async { + URLBookmarkModel.shared.refreshAll() + } + + DispatchQueue.global(qos: .userInitiated).async { + self.migrateHomeHistoryItems() + } + + DispatchQueue.global(qos: .userInitiated).async { + self.migrateQualityProfiles() + } + } } func migrateHomeHistoryItems() {