Compare commits

..

35 Commits

Author SHA1 Message Date
Arkadiusz Fal
735e7d62b6 Update changelog for build 210 2025-11-20 18:00:31 +01:00
Arkadiusz Fal
320c16fcc7 Bump build number to 210 2025-11-20 17:56:24 +01:00
Arkadiusz Fal
8c5c503df2 Fix iPad iOS 18 keyboard dismissal issue in search
Removed auto-focus logic that was causing keyboard show/hide loop
on iPad with docked keyboard. The keyboard was repeatedly dismissing
immediately after appearing due to interaction between keyboard
notifications, focus state changes, and view updates.

Changes:
- Removed focused state and keyboard observer from SearchModel
- Removed iOS textField reference (kept macOS only)
- Removed auto-focus logic from FocusableSearchTextField on iOS
- Cleaned up unused focus-related code

The search field now works reliably when tapped manually on iPad.
Auto-focus still works on macOS where it doesn't cause issues.
2025-11-20 17:49:10 +01:00
Arkadiusz Fal
36738572da Fix SwiftFormat and SwiftLint issues
- Fix indentation in AppSidebarNavigation, VideoCell
- Replace && with comma in PlayerModel condition
- Add SwiftLint suppression for necessary tvOS 17.0 availability check
- Update SwiftLint config to use renamed rules and disable false positives
2025-11-20 17:05:22 +01:00
Arkadiusz Fal
9a8ccc366c Clean up trending settings when feature flag is disabled
Add startup cleanup to remove trending-related settings when the feature flag is disabled:
- Remove trending from visible sections
- Reset startup section to home if it was set to trending
- Remove all trending favorite items

This ensures users don't have invalid/broken settings referencing the disabled trending feature.
2025-11-20 13:21:56 +01:00
Arkadiusz Fal
e9ca36f1db Fix Trending menu command to hide instead of disable
Change the Trending menu command to be completely hidden when the feature flag is disabled, rather than just being disabled and still visible in the UI.
2025-11-20 13:18:03 +01:00
Arkadiusz Fal
5b607687d9 Add feature flag to disable Trending functionality
Introduces a feature flag to disable the Trending section across the app. When disabled, all trending-related UI elements, navigation links, and settings are hidden.

Changes:
- Add trendingEnabled feature flag to FeatureFlags.swift (currently disabled)
- Hide Trending tab in AppTabNavigation, Sidebar, and TVNavigationView
- Remove Trending option from visible sections settings
- Remove Trending option from startup section picker
- Disable Trending menu command and keyboard shortcut
- Prevent Trending URL navigation in OpenURLHandler
- Hide Trending in FavoriteItemView navigation
2025-11-20 13:14:31 +01:00
Arkadiusz Fal
e723bb9147 Change Trending icon to arrow.up.right.circle.fill
Replace chart.bar.fill icon with arrow.up.right.circle.fill for the Trending section across tab bar and sidebar navigation.
2025-11-20 13:08:55 +01:00
Arkadiusz Fal
a3747a0975 Change Popular icon to chart.bar.fill
Replace arrow.up.right.circle.fill icon with chart.bar.fill for the Popular section across all navigation contexts (tab bar, sidebar, and view header).
2025-11-20 13:07:32 +01:00
Arkadiusz Fal
bb2bd86c07 Add feature flag to disable hide shorts functionality
The hide shorts feature is no longer working due to API changes that prevent reliable detection of short videos. This commit introduces a feature flag system to disable the functionality while preserving the ability to easily restore it if the API issue is resolved.

Changes:
- Add FeatureFlags.swift with hideShortsEnabled flag (currently disabled)
- Hide all HideShortsButtons UI elements when flag is disabled
- Disable shorts filtering logic in ContentItemView, FavoriteItemView, and FeedModel
- Preserve hideShorts user preference for future restoration
2025-11-20 13:05:12 +01:00
Arkadiusz Fal
680ac9a8a0 Fix keyboard shortcut conflict for Show Player command
Changed Show Player shortcut from Cmd+O to Cmd+Shift+P to avoid
conflict with system Open command.
2025-11-20 00:23:27 +01:00
Arkadiusz Fal
c1b23d20f2 Fix tab selection timing to wait for account sign-in
Tab selection was being set immediately during app configuration, before
the user account had completed sign-in. This caused tabs that require
authentication (like Subscriptions and Playlists) to not be properly
selected on startup since they weren't visible yet.

Changes:
- Add notification system for account configuration completion
- Post notification after all account types finish configuration:
  * Accounts with existing tokens
  * Accounts requiring sign-in (after network request completes)
  * Anonymous/public accounts
  * Error cases (missing credentials, network failures)
- Set up observer before account configuration to ensure notification
  is received
- Set tab selection only when account is fully configured
2025-11-19 23:24:21 +01:00
Arkadiusz Fal
b8f6dabbc9 Update SwiftUI-Introspect to support iOS 26
Upgrade SwiftUI-Introspect dependency from 1.3.0 to 26.0.0 and add iOS 26 support to the introspect modifier in AppSidebarNavigation.
2025-11-19 23:09:18 +01:00
Arkadiusz Fal
1c168bd982 Fix thumbnail aspect ratio to prevent stretching and layout jumps
Fixed issues with thumbnails being stretched vertically and layout jumping during image loading:
- Simplified VideoCellThumbnail to always use 16:9 aspect ratio with .fill mode
- Added matching 16:9 aspect ratio to placeholder with .fill mode to prevent layout shifts
- Removed quality-based aspect ratio selection (4:3 vs 16:9) in favor of consistent 16:9
- Ensures thumbnails maintain proper proportions on both iOS and macOS

This provides consistent sizing across platforms and eliminates the jump when images finish loading.
2025-11-19 23:01:52 +01:00
Arkadiusz Fal
42d53c30db Fix thumbnail aspect ratio in video grid cells
Thumbnails were being stretched vertically due to incorrect aspect ratio handling. Fixed by:
- Using .scaledToFill() on thumbnails to fill the container width
- Constraining container to 16:9 aspect ratio with .fit mode
- Adding matching aspect ratio to placeholder to prevent layout shift during loading

This ensures thumbnails maintain proper proportions while filling the full cell width.
2025-11-19 22:37:05 +01:00
Arkadiusz Fal
a55adb2e65 Fix thumbnail loading for video details
Explicitly specify thumbnail quality order instead of using Thumbnail.Quality.allCases to ensure proper thumbnail URL generation and loading priority.
2025-11-19 22:05:55 +01:00
Arkadiusz Fal
cea296c4b7 Fix audio session interrupting other apps on launch
Previously, the audio session was initialized immediately when the app launched, causing audio from other apps (like Music) to stop even when no video was playing in Yattee.

Changes:
- Remove audio session initialization from AppDelegate launch
- Remove audio session setup from MPVClient initialization
- Update setAudioSessionActive() to configure audio session category before activation

The audio session is now lazily initialized only when playback actually starts:
- For MPV backend: triggered by FILE_LOADED, PLAYBACK_RESTART, AUDIO_RECONFIG events
- For AVPlayer backend: triggered when play() is called

This allows music from other apps to continue playing until a video is actually played in Yattee.
2025-11-19 21:45:56 +01:00
Arkadiusz Fal
ea0ea427e7 Bump build number to 209 2025-11-19 18:55:21 +01:00
Arkadiusz Fal
f685e180d0 Update CHANGELOG.md for Build 209 2025-11-19 18:55:07 +01:00
Arkadiusz Fal
a37f3e4a07 Adjust tvOS video cell dimensions for better layout
Reduced video cell and grid item sizes on tvOS to improve layout spacing
and visual consistency. Changed grid item size from 600 to 560 pixels,
and adjusted video cell frame dimensions accordingly.
2025-11-19 18:54:51 +01:00
Arkadiusz Fal
33377f7e0e Fix nil crash when accessing stream.format
This commit addresses crashes caused by accessing nil format values on streams:

- QualityProfile.swift: Add guard check for stream.format to prevent nil access crash
- MPVBackend.swift: Add nil check in canPlay method before comparing format
- PlayerStreams.swift: Add nil check before comparing format in asset processing

The crashes occurred when stream.format was nil and accessed as an implicitly unwrapped optional, causing "Unexpectedly found nil while implicitly unwrapping an Optional value" errors.
2025-11-19 18:03:43 +01:00
Arkadiusz Fal
4b577a296b Fix array index out of bounds crash in audio track handling
This commit addresses crashes caused by race conditions when accessing audio track arrays:

- MPVBackend.swift: Use safe index clamping to prevent array out of bounds crashes when selecting audio tracks
- PlayerModel.swift: Add selectedAudioTrack computed property for thread-safe audio track access
- ControlsOverlay.swift: Use safe accessor with "Original" fallback label
- PlaybackSettings.swift: Use safe accessor with "Original" fallback label

This fix resolves approximately 37% of crashes (23 out of 62 crash logs) that were caused by index out of range errors in MPVBackend.playStream at line 345.
2025-11-19 18:01:02 +01:00
Arkadiusz Fal
e882d0264b Fix thumbnail sizing and aspect ratio issues in video cells (#896)
Fixed improperly sized and positioned thumbnails by removing duplicate aspect ratio constraints and standardizing to 4:3 format with fill content mode for better display.
2025-11-19 17:50:39 +01:00
Arkadiusz Fal
45f72ce4a1 Fix playing videos from channel view in modal opened in video player
Handle case where player is already presenting by using delayed dispatch instead of appending to onPresentPlayer callback queue.
2025-11-19 01:32:09 +01:00
Arkadiusz Fal
3536370798 Refactor fullscreen details layout from VStack to ZStack
Switch from VStack to ZStack layout for better control over detail view positioning in fullscreen mode. Add z-index layering to ensure proper stacking order of player backend and video details.
2025-11-19 00:57:00 +01:00
Arkadiusz Fal
a5275fd800 Remove verbose logging statements
Removed logging for audio session activation and partial update operations to reduce log noise.
2025-11-18 18:19:45 +01:00
Arkadiusz Fal
49278e13cd Fix audio track label showing "Original" instead of "Unknown"
Changed the default audio track content type from "Unknown" to "Original"
when the content type is not specified. This provides a more accurate
description for the default audio track.
2025-11-18 18:16:38 +01:00
Arkadiusz Fal
13d7a8d0a6 Simplify fullscreen handling for iOS
Remove unnecessary edgesIgnoringSafeArea modifier and simplify status bar hiding logic by removing iPad-specific conditional checks, making the fullscreen behavior more consistent across iOS devices.
2025-11-18 18:11:48 +01:00
Arkadiusz Fal
50efe94839 Add macOS-specific entitlements for MPV backend
Enable Allow Unsigned Executable Memory and Disable Library Validation
entitlements for macOS target only to support MPV player backend.
2025-11-18 16:59:17 +01:00
Arkadiusz Fal
1e7656a9eb Revert MPVKit to track main branch
Switch MPVKit package dependency from pinned revision back to tracking the main branch for latest updates.
2025-11-18 16:46:30 +01:00
Arkadiusz Fal
4c5b801c45 Fix Now Playing controls when switching between MPV and AVPlayer backends
When switching from AVPlayer to MPV backend, Now Playing controls (play/pause/seek) were disabled because AVPlayer maintained control of the remote command center and audio session. This fix ensures MPV can properly reclaim control.

Key changes:
- Clear AVPlayer's current item when switching to MPV to release media control
- Clear Now Playing info and set playback state to stopped before MPV takes over
- Reset remote command center by removing all targets (including AVPlayer's internal handlers) and re-adding custom handlers
- Force audio session deactivation/reactivation with .notifyOthersOnDeactivation
- Add forceReactivate parameter to setupAudioSessionForNowPlaying() for backend switches
- Ensure stream loading continues after Now Playing setup (don't return early)

The fix properly handles the transition by:
1. Clearing AVPlayer's media session completely
2. Scheduling async Now Playing setup without blocking stream loading
3. Resetting remote command handlers to reclaim control from AVPlayer
4. Re-activating audio session to establish MPV as the active player
2025-11-18 16:43:17 +01:00
Arkadiusz Fal
e6b6778ba1 Fix iOS Now Playing integration for MPV backend
The MPV backend now properly displays Now Playing information in iOS Control Center. The fix addresses the issue where the AVAudioSession would become inactive during MPV's playback lifecycle.

Key changes:
- Added setupAudioSessionForNowPlaying() method to activate AVAudioSession with proper playback category and movie playback mode
- Re-activate audio session at critical MPV events: FILE_LOADED, PLAYBACK_RESTART, AUDIO_RECONFIG, and during periodic updates
- Initialize audio session immediately after mpv_initialize() in MPVClient

The audio session must be re-activated at multiple points during playback, not just at initialization, to ensure iOS recognizes the app as playing media.
2025-11-18 16:20:30 +01:00
Arkadiusz Fal
9c15393ab4 Pin MPVKit to specific commit revision
Pins MPVKit dependency to commit 8db0d19d03adaf824588de7f7cdbc05b8e2016bc
instead of tracking the main branch to ensure build stability.
2025-11-15 23:30:53 +01:00
Arkadiusz Fal
5cfdd36237 Fix SPM cache clearing in macOS notarized build workflow
Expands cache clearing to include DerivedData and .build directories
to prevent corrupted artifact issues during dependency resolution.

Renames workflow file and updates job title to include macOS and
Xcode versions for clarity.
2025-11-15 23:13:58 +01:00
Arkadiusz Fal
e0cf927ebb Add GitHub Action for macOS notarized builds
Creates a standalone workflow to build and notarize macOS-only builds
without creating a GitHub release. Uses macOS 15 and Xcode 16.4.
The notarized build is available as a workflow artifact.
2025-11-15 23:10:01 +01:00
44 changed files with 494 additions and 325 deletions

View File

@@ -0,0 +1,59 @@
name: Build and notarize macOS app (macOS 15, Xcode 16.4)
on:
workflow_dispatch:
env:
APP_NAME: Yattee
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
ITC_TEAM_ID: ${{ secrets.ITC_TEAM_ID }}
TEAM_ID: ${{ secrets.TEAM_ID }}
DEVELOPER_KEY_ID: ${{ secrets.DEVELOPER_KEY_ID }}
DEVELOPER_KEY_ISSUER_ID: ${{ secrets.DEVELOPER_KEY_ISSUER_ID }}
DEVELOPER_KEY_CONTENT: ${{ secrets.DEVELOPER_KEY_CONTENT }}
TEMP_KEYCHAIN_USER: ${{ secrets.TEMP_KEYCHAIN_USER }}
TEMP_KEYCHAIN_PASSWORD: ${{ secrets.TEMP_KEYCHAIN_PASSWORD }}
DEVELOPER_APP_IDENTIFIER: ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
GIT_AUTHORIZATION: ${{ secrets.GIT_AUTHORIZATION }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
CERTIFICATES_GIT_URL: ${{ secrets.CERTIFICATES_GIT_URL }}
jobs:
mac_notarized:
name: Build and notarize macOS app (macOS 15, Xcode 16.4)
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.1'
bundler-cache: true
cache-version: 1
- name: Replace signing certificate to Direct with Developer ID
run: |
sed -i '' 's/match AppStore/match Direct/' Yattee.xcodeproj/project.pbxproj
sed -i '' 's/3rd Party Mac Developer Application/Developer ID Application/' Yattee.xcodeproj/project.pbxproj
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.4'
- name: Clear SPM cache
run: |
rm -rf ~/Library/Caches/org.swift.swiftpm/artifacts
rm -rf ~/Library/Developer/Xcode/DerivedData
rm -rf .build
- uses: maierj/fastlane-action@v3.0.0
with:
lane: mac build_and_notarize
- run: |
echo "BUILD_NUMBER=$(cat Yattee.xcodeproj/project.pbxproj | grep -m 1 CURRENT_PROJECT_VERSION | cut -d' ' -f3 | sed 's/;//g')" >> $GITHUB_ENV
echo "VERSION_NUMBER=$(cat Yattee.xcodeproj/project.pbxproj | grep -m 1 MARKETING_VERSION | cut -d' ' -f3 | sed 's/;//g')" >> $GITHUB_ENV
- run: |
echo "APP_PATH=fastlane/builds/${{ env.VERSION_NUMBER }}-${{ env.BUILD_NUMBER }}/macOS/Yattee.app" >> $GITHUB_ENV
echo "ZIP_PATH=fastlane/builds/${{ env.VERSION_NUMBER }}-${{ env.BUILD_NUMBER }}/macOS/Yattee-${{ env.VERSION_NUMBER }}-macOS.zip" >> $GITHUB_ENV
- name: ZIP build
run: /usr/bin/ditto -c -k --keepParent ${{ env.APP_PATH }} ${{ env.ZIP_PATH }}
- uses: actions/upload-artifact@v4
with:
name: mac-notarized-build
path: ${{ env.ZIP_PATH }}
if-no-files-found: error

View File

@@ -8,6 +8,15 @@ disabled_rules:
- multiline_arguments
- implicit_return
- closure_end_indentation
- discarded_notification_center_observer # Observer intentionally lives for app lifetime
# Disable deprecated rules in favor of their renamed versions
- operator_whitespace # renamed to function_name_whitespace
- redundant_optional_initialization # renamed to implicit_optional_initialization
opt_in_rules:
- function_name_whitespace
- implicit_optional_initialization
excluded:
- Vendor
- Tests Apple TV

View File

@@ -1,180 +1,50 @@
## Build 208
## Build 210
## What's Changed
**iPad Enhancements:**
* Trending and Hide Shorts was disabled due to changes in the video apps API
* Fix iPad iOS 18 keyboard dismissal issue in search
* Fix audio session interrupting other apps on launch
* Fix thumbnail loading for video details
* Fix thumbnail aspect ratio to prevent stretching and layout jumps
* Fix keyboard shortcut conflict for Show Player command
## Previous builds
**Build 209:**
* Fix Now Playing controls for both MPV and AVPlayer backends
* Fix thumbnail sizing and aspect ratio issues in video cells (#896)
* Adjust tvOS video cell dimensions for better layout
* Fix playing videos from channel view in modal opened in video player
* Fix audio track label showing "Original" instead of "Unknown"
* Simplify fullscreen handling for iOS
* Add macOS-specific entitlements for MPV backend
**Build 208:**
* Enable resizable windows on iPad
* Improve iPad UI behavior and settings layout
* Fix horizontal content extending behind sidebar on iPad
* Add proper padding to player controls and video details in non-fullscreen iPad windows
* Hide orientation lock controls on iPad (not applicable for iPad)
**iOS Improvements:**
* Fix video player overlay to respect window fullscreen state
* Allow video player to extend into safe areas
* Fix iOS Now Playing Info Center integration for AVPlayer backend
* Fix button styling and safe area handling
**macOS Improvements:**
* Fix picker label visibility in settings
* Improve video layer rendering
* Add macOS 26 compatibility for search UI
* Improve playback settings UI controls
**Player & Playback:**
* Add retry mechanism for file load errors (both MPV and AVPlayer)
* Fix MPV player vertical positioning in fullscreen mode
* Improve player controls visibility and layout
* Add nil safety checks for stream resolution and playback time handling
* Refactor dirty region handling in MPV video rendering
* Remove verbose logging from MPV rendering
**UI/UX:**
* Improve layout stability and reduce unwanted animations
* Simplify stream description by removing instance info
* Update default visible sections from trending to popular
**Maintenance:**
* Update MPVKit dependency
* Update Ruby dependencies
* Fix SwiftLint and SwiftFormat violations
* Fix main actor isolation warnings
* Update GitHub Actions to latest macOS and Xcode versions
## Previous builds
**Build 204:**
* Fix Invidious companion API endpoint path
* Fix issues with audio tracks in Piped (crash)
* Improve MPV backend audio track handling
* Add Liquid Glass effect to player controls bar
* Use fullScreenCover for Settings and Accounts on tvOS
* Improve tvOS settings UI styling and navigation
* Improve fullscreen orientation handling for iOS player
* Fix published date handling in Piped API
* Optimize SwiftUI performance throughout the app
* Improve search field layout and responsiveness
* Add localization support for Finnish, Indonesian, Korean, Dutch, and Swedish
* Update dependencies
**Build 201:**
* MPV audio track switching and fix default audio language by @n3d1117 in https://github.com/yattee/yattee/pull/874
* Feat: Added caption support for Piped backend by @craftycorvid in https://github.com/yattee/yattee/pull/867
* Translations update from Hosted Weblate by @weblate in https://github.com/yattee/yattee/pull/877
* Add support for invidious companion by @lifo9 in https://github.com/yattee/yattee/pull/863
* Add skip, play/pause, and fullscreen shortcuts to macOS player (by @rickykresslein)
* Added Settings Import/Export
* Export all settings, instances and accounts
* Import selected elements from the file
* Include unencrypted passwords in the export or provide them during the import
* Import via URL for tvOS
* Added Controls setting "Action button labels" icon or icon and text
* Added Advanced setting for MPV: "deinterlace"
* Add help text to all header buttons (by @rickykresslein)
* History Setting: hide the recent activity in the sidebar or limit the number of items shown (by @rickykresslein)
* Fix issues with empty comments (by @stonerl)
* Improved Invidious comments (by @stonerl)
* Allow import of accounts to manually added (not imported) instances
* Add import export of missing settings
* macOS: Fix settings windows layout
* Fix seek OSD layout on tvOS, revert OSD position
* Allow users to disable fullscreen swipe gesture by @stonerl in https://github.com/yattee/yattee/pull/814
* Proper audio interrupt and route change handling by @stonerl in https://github.com/yattee/yattee/pull/815
* Improved subtitle handling by @stonerl in https://github.com/yattee/yattee/pull/817
* Improvements to MPVGLView by @stonerl in https://github.com/yattee/yattee/pull/818
* Add drag gestures to video details by @stonerl in https://github.com/yattee/yattee/pull/820
* Fix uneven playback when using MPV and not syncing refreshrate by @blennster in https://github.com/yattee/yattee/pull/833
* Norwegian Language by @mmaalo in https://github.com/yattee/yattee/pull/834
* Translations update from Hosted Weblate by @weblate in https://github.com/yattee/yattee/pull/836
* Update MPVKit to v0.39.0 by @stonerl in https://github.com/yattee/yattee/pull/824
* Update SwiftUI-Introspect by @stonerl in https://github.com/yattee/yattee/pull/813
* Orientation/Fullscreen fixes and cleanup by @stonerl in https://github.com/yattee/yattee/pull/806
* More robust resolution handling by @stonerl in https://github.com/yattee/yattee/pull/807
* MPV: improved A/V sync by @stonerl in https://github.com/yattee/yattee/pull/805
* Retry loading video before presenting error by @stonerl in https://github.com/yattee/yattee/pull/810
* Refactor Search by @stonerl in https://github.com/yattee/yattee/pull/809
* iOS: Simplified fullscreen and orientation by @stonerl in https://github.com/yattee/yattee/pull/786
* macOS: only apply player shortcuts when window is active by @stonerl in https://github.com/yattee/yattee/pull/802
* player controls: add background opacity selection by @stonerl in https://github.com/yattee/yattee/pull/799
* add missing Shorts resolutions by @stonerl in https://github.com/yattee/yattee/pull/797
* use -O1 on macOS by @stonerl in https://github.com/yattee/yattee/pull/801
* Gestures: swipe up toggles fullscreen by @stonerl in https://github.com/yattee/yattee/pull/778
* dont open video when dismissing context menu by @stonerl in https://github.com/yattee/yattee/pull/780
* mpv: remove video layer when entering background by @stonerl in https://github.com/yattee/yattee/pull/793
* hi-res invidious logos by @stonerl in https://github.com/yattee/yattee/pull/791
* enable -O3 by @stonerl in https://github.com/yattee/yattee/pull/794
* Better audio ducking by @stonerl in https://github.com/yattee/yattee/pull/779
* fix picture in picture by @stonerl in https://github.com/yattee/yattee/pull/789
* Invidious: propper HTTP basic auth support by @stonerl in https://github.com/yattee/yattee/pull/762
* Apply correct orientation by @stonerl in https://github.com/yattee/yattee/pull/770
* Circular Invidious logo by @stonerl in https://github.com/yattee/yattee/pull/769
* Video Thumbnails: retry 3 times fetching from URL by @stonerl in https://github.com/yattee/yattee/pull/768
* Make thumbnail fill the view in music mode by @stonerl in https://github.com/yattee/yattee/pull/766
* Changes to defaults by @stonerl in https://github.com/yattee/yattee/pull/767
* Fixed fullscreen handling for backgrounding by @stonerl in https://github.com/yattee/yattee/pull/772
* Update now playing info when using system controls Partial fix for 503 by @stonerl in https://github.com/yattee/yattee/pull/765
* Stop making videos with unknown length shorts. by @derspyy in https://github.com/yattee/yattee/pull/849
* Add Hungarian to locales list
* Fix crash on HLS live playback by @stonerl in https://github.com/yattee/yattee/pull/775
* Fix mpv crashing on macOS by @stonerl in https://github.com/yattee/yattee/pull/754
* Refreshed icons for iOS and macOS by @stonerl in https://github.com/yattee/yattee/pull/752
* Add new MPVKit repo by @stonerl in https://github.com/yattee/yattee/pull/753
* Add Chinese (Simplified) - zh-Hans to LanguageCodes by @stonerl in https://github.com/yattee/yattee/pull/757
* Color changes to VideoActions by @stonerl in https://github.com/yattee/yattee/pull/759
* Hide VideoActions Bar when no buttons is visible by @stonerl in https://github.com/yattee/yattee/pull/760
* Improved stream resolution handling by @stonerl in https://github.com/yattee/yattee/pull/747
* Fix some potential crashes by @stonerl in https://github.com/yattee/yattee/pull/748
* Fix regression and improve curentChapter handling by @stonerl in https://github.com/yattee/yattee/pull/749
* Refined chapter font scaling by @stonerl in https://github.com/yattee/yattee/pull/750
* Improved thumbnail handling by @stonerl in https://github.com/yattee/yattee/pull/740
* iOS: make timestamps in comments touchable by @stonerl in https://github.com/yattee/yattee/pull/741
* Improvements to opening channels from Videos by @stonerl in https://github.com/yattee/yattee/pull/742
* Allow hiding comments by @stonerl in https://github.com/yattee/yattee/pull/744
* Add option to exit fullscreen on end by @stonerl in https://github.com/yattee/yattee/pull/570
* Only updateWatch status while video is playing by @stonerl in https://github.com/yattee/yattee/pull/745
* Xcode 16 - update recommended settings by @stonerl in https://github.com/yattee/yattee/pull/737
* Translations update from Hosted Weblate by @weblate in https://github.com/yattee/yattee/pull/724
* tvOS: Allow account picker by long pressing channels button in subscriptions view by @patelhiren in https://github.com/yattee/yattee/pull/704
* tvOS: Refined Subscriptions View by @patelhiren in https://github.com/yattee/yattee/pull/697
* More responsive UI when Favorites are used. by @stonerl in https://github.com/yattee/yattee/pull/695
* Improved conditional proxying by @stonerl in https://github.com/yattee/yattee/pull/696
* Don't show related in sidebar when disabled in settings by @stonerl in https://github.com/yattee/yattee/pull/635
* Handle audio session interrupts by other media by @stonerl in https://github.com/yattee/yattee/pull/640
* Only show Queue header in sidebar view by @stonerl in https://github.com/yattee/yattee/pull/642
* SponsorBlock Improvements by @stonerl in https://github.com/yattee/yattee/pull/639
* Chapter title on jump by @stonerl in https://github.com/yattee/yattee/pull/655
* Restart finished video by @stonerl in https://github.com/yattee/yattee/pull/646
* SponsorBlock jump to end instead of pausing by @stonerl in https://github.com/yattee/yattee/pull/648
* Call correct class of SDImageAWebPCoder by @stonerl in https://github.com/yattee/yattee/pull/664
* Fix handling and displaying captions by @stonerl in https://github.com/yattee/yattee/pull/636
* Advanced settings: make number fields .numPad by @stonerl in https://github.com/yattee/yattee/pull/661
* Preserve time on stream change by @stonerl in https://github.com/yattee/yattee/pull/651
* Switch to previous backend when leaving PiP by @stonerl in https://github.com/yattee/yattee/pull/641
* Handle deep links by @timonus in https://github.com/yattee/yattee/pull/645
* Music Mode: don't bindPlayerToLayer when entering foreground by @stonerl in https://github.com/yattee/yattee/pull/644
* Allow user to disable thumbnails and jump to current chapter in horizontal view by @stonerl in https://github.com/yattee/yattee/pull/665
* Rework qualitiy settings by @stonerl in https://github.com/yattee/yattee/pull/650
* HLS: set target bitrate / AVPlayer: higher resolution by @stonerl in https://github.com/yattee/yattee/pull/667
* Fix #619: Remove ports from shared YouTube links by @0x000C in https://github.com/yattee/yattee/pull/627
* XCode enable IDEPreferLogStreaming by @stonerl in https://github.com/yattee/yattee/pull/638
* Conditional proxying by @stonerl in https://github.com/yattee/yattee/pull/662
* HomeView: Changes to Favourites and History Widget by @stonerl in https://github.com/yattee/yattee/pull/672
* Snappy UI - Offloading non UI task to background threads by @stonerl in https://github.com/yattee/yattee/pull/671
* Fix PiP Mode Not Working Using MPV by @stonerl in https://github.com/yattee/yattee/pull/676
* Fix thumbnails failing to load on tvOS by @patelhiren in https://github.com/yattee/yattee/pull/688
* speed up sorting for Stream by @stonerl in https://github.com/yattee/yattee/pull/681
* faster chapter extraction by @stonerl in https://github.com/yattee/yattee/pull/682
* Invidious: add images to chapters by @stonerl in https://github.com/yattee/yattee/pull/685
* Improved Captions handling by @stonerl in https://github.com/yattee/yattee/pull/684
* Add User-Agent to request by @stonerl in https://github.com/yattee/yattee/pull/680
* MPV: speed up playback start by @stonerl in https://github.com/yattee/yattee/pull/689
* Advanced Settings: cache-pause-initial by @stonerl in https://github.com/yattee/yattee/pull/679
* Changed description for Format reordering by @stonerl in https://github.com/yattee/yattee/pull/677
* Add Chinese (Traditional) localization (by @rexcsk)
* Localization fixes
* Updated localizations
* Upgraded dependencies
* Fixed reported crash
* Other minor changes and improvements
**Big thanks to the current, past and future project contributors!**

View File

@@ -0,0 +1,5 @@
import Foundation
extension Notification.Name {
static let accountConfigurationComplete = Notification.Name("accountConfigurationComplete")
}

View File

@@ -152,6 +152,10 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
FeedModel.shared.onAccountChange()
SubscribedChannelsModel.shared.onAccountChange()
PlaylistsModel.shared.onAccountChange()
DispatchQueue.main.async {
NotificationCenter.default.post(name: .accountConfigurationComplete, object: nil)
}
}
}
@@ -160,6 +164,9 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
guard !account.anonymous,
(account.token?.isEmpty ?? true) || force
else {
DispatchQueue.main.async {
NotificationCenter.default.post(name: .accountConfigurationComplete, object: nil)
}
return
}
@@ -172,6 +179,9 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
title: "Account Error",
message: "Remove and add your account again in Settings."
)
DispatchQueue.main.async {
NotificationCenter.default.post(name: .accountConfigurationComplete, object: nil)
}
return
}
@@ -212,6 +222,8 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
}
self.configure()
NotificationCenter.default.post(name: .accountConfigurationComplete, object: nil)
}
}

View File

@@ -174,6 +174,9 @@ final class PeerTubeAPI: Service, ObservableObject, VideosAPI {
guard !account.anonymous,
(account.token?.isEmpty ?? true) || force
else {
DispatchQueue.main.async {
NotificationCenter.default.post(name: .accountConfigurationComplete, object: nil)
}
return
}
@@ -186,6 +189,9 @@ final class PeerTubeAPI: Service, ObservableObject, VideosAPI {
title: "Account Error",
message: "Remove and add your account again in Settings."
)
DispatchQueue.main.async {
NotificationCenter.default.post(name: .accountConfigurationComplete, object: nil)
}
return
}
@@ -194,6 +200,7 @@ final class PeerTubeAPI: Service, ObservableObject, VideosAPI {
title: "Account Error",
message: message ?? "\(response?.response?.statusCode ?? -1) - \(response?.error?.errorDescription ?? "unknown")\nIf this issue persists, try removing and adding your account again in Settings."
)
NotificationCenter.default.post(name: .accountConfigurationComplete, object: nil)
}
AF
@@ -226,6 +233,8 @@ final class PeerTubeAPI: Service, ObservableObject, VideosAPI {
}
self.configure()
NotificationCenter.default.post(name: .accountConfigurationComplete, object: nil)
}
}

View File

@@ -135,6 +135,10 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
FeedModel.shared.onAccountChange()
SubscribedChannelsModel.shared.onAccountChange()
PlaylistsModel.shared.onAccountChange()
DispatchQueue.main.async {
NotificationCenter.default.post(name: .accountConfigurationComplete, object: nil)
}
}
}
@@ -149,6 +153,9 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
let username,
let password
else {
DispatchQueue.main.async {
NotificationCenter.default.post(name: .accountConfigurationComplete, object: nil)
}
return
}
@@ -184,11 +191,14 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
self.configure()
NotificationCenter.default.post(name: .accountConfigurationComplete, object: nil)
case let .failure(error):
NavigationModel.shared.presentAlert(
title: "Account Error",
message: error.localizedDescription
)
NotificationCenter.default.post(name: .accountConfigurationComplete, object: nil)
}
}
}
@@ -532,7 +542,10 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
let channelId = details["uploaderUrl"]?.string?.components(separatedBy: "/").last ?? "unknown"
let thumbnails: [Thumbnail] = Thumbnail.Quality.allCases.compactMap {
let qualities = [
Thumbnail.Quality.maxresdefault, .high, .medium, .default, .start, .middle, .end
]
let thumbnails: [Thumbnail] = qualities.compactMap {
if let url = buildThumbnailURL(from: content, quality: $0) {
return Thumbnail(url: url, quality: $0)
}

View File

@@ -235,7 +235,7 @@ final class FeedModel: ObservableObject, CacheModel {
let watches = watchFetchRequestResult(videos, context: backgroundContext)
let watchesIDs = watches.map(\.videoID)
let unwatched = videos.filter { video in
if Defaults[.hideShorts], video.short {
if FeatureFlags.hideShortsEnabled, Defaults[.hideShorts], video.short {
return false
}

View File

@@ -318,7 +318,7 @@ final class NavigationModel: ObservableObject {
func multipleTapHandler() {
switch tabSelection {
case .search:
search.focused = true
break
default:
print("not implemented")
}

View File

@@ -220,7 +220,7 @@ final class MPVBackend: PlayerBackend {
typealias AreInIncreasingOrder = (Stream, Stream) -> Bool
func canPlay(_ stream: Stream) -> Bool {
stream.format != .av1
stream.format != nil && stream.format != .av1
}
func playStream(_ stream: Stream, of video: Video, preservingTime: Bool, upgrading: Bool) {
@@ -338,11 +338,11 @@ final class MPVBackend: PlayerBackend {
// Handle streams with multiple audio tracks
if !stream.audioTracks.isEmpty {
if stream.selectedAudioTrackIndex >= stream.audioTracks.count {
stream.selectedAudioTrackIndex = 0
}
// Ensure the index is within bounds to prevent race conditions
let safeIndex = min(max(0, stream.selectedAudioTrackIndex), stream.audioTracks.count - 1)
stream.selectedAudioTrackIndex = safeIndex
stream.audioAsset = AVURLAsset(url: stream.audioTracks[stream.selectedAudioTrackIndex].url)
stream.audioAsset = AVURLAsset(url: stream.audioTracks[safeIndex].url)
let fileToLoad = self.model.musicMode ? stream.audioAsset.url : stream.videoAsset.url
let audioTrack = self.model.musicMode ? nil : stream.audioAsset.url
@@ -504,7 +504,10 @@ final class MPVBackend: PlayerBackend {
updateControls()
}
model.updateNowPlayingInfo()
#if !os(macOS)
model.setupAudioSessionForNowPlaying()
model.updateNowPlayingInfo()
#endif
handleSegmentsThrottle.execute {
model.handleSegments(at: currentTime)
@@ -594,6 +597,11 @@ final class MPVBackend: PlayerBackend {
onFileLoaded = nil
// Reset retry state on successful load
resetRetryState()
// Re-activate audio session for Now Playing
#if !os(macOS)
model.setupAudioSessionForNowPlaying()
model.updateNowPlayingInfo()
#endif
case MPV_EVENT_PROPERTY_CHANGE:
let dataOpaquePtr = OpaquePointer(event.pointee.data)
@@ -611,10 +619,22 @@ final class MPVBackend: PlayerBackend {
onFileLoaded = nil
// Reset retry state on successful playback restart
resetRetryState()
// Re-activate audio session for Now Playing
#if !os(macOS)
model.setupAudioSessionForNowPlaying()
model.updateNowPlayingInfo()
#endif
case MPV_EVENT_VIDEO_RECONFIG:
model.updateAspectRatio()
case MPV_EVENT_AUDIO_RECONFIG:
// Re-activate audio session when audio is reconfigured
#if !os(macOS)
model.setupAudioSessionForNowPlaying()
model.updateNowPlayingInfo()
#endif
case MPV_EVENT_SEEK:
isSeeking = true

View File

@@ -440,11 +440,19 @@ final class PlayerModel: ObservableObject {
#if os(iOS)
if !playingInPictureInPicture, showingPlayer {
onPresentPlayer.append { [weak self] in
changeBackendHandler?()
self?.playNow(video, at: time)
if presentingPlayer {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
guard let self else { return }
changeBackendHandler?()
self.playNow(video, at: time)
}
} else {
onPresentPlayer.append { [weak self] in
changeBackendHandler?()
self?.playNow(video, at: time)
}
show()
}
show()
return
}
#endif
@@ -640,8 +648,45 @@ final class PlayerModel: ObservableObject {
fromBackend.pause()
}
// Update Now Playing when switching backends to ensure the new backend takes control
updateNowPlayingInfo()
// When switching away from AVPlayer, clear its current item to release Now Playing control
#if !os(macOS)
if from == .appleAVPlayer, to == .mpv {
avPlayerBackend.avPlayer.replaceCurrentItem(with: nil)
// Clear Now Playing info entirely before MPV takes over
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
MPNowPlayingInfoCenter.default().playbackState = .stopped
logger.info("Cleared AVPlayer's Now Playing control")
// Schedule Now Playing setup after a brief delay, but don't block stream loading
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
guard let self else { return }
// Re-activate audio session when switching to MPV to ensure Now Playing controls work
// Force deactivate/reactivate to take control from AVPlayer
self.setupAudioSessionForNowPlaying(forceReactivate: true)
// Reset and re-enable remote commands to take control from AVPlayer
self.updateRemoteCommandCenter(reset: true)
// Set up Now Playing for MPV
self.updateNowPlayingInfo()
logger.info("Set up Now Playing for MPV backend")
}
// Continue to load the stream (don't return early)
} else if to == .mpv {
// Re-activate audio session when switching to MPV to ensure Now Playing controls work
// Force deactivate/reactivate to take control from AVPlayer
setupAudioSessionForNowPlaying(forceReactivate: true)
// Re-enable remote commands to take control from AVPlayer
updateRemoteCommandCenter()
updateNowPlayingInfo()
} else {
updateNowPlayingInfo()
}
#else
updateNowPlayingInfo()
#endif
guard var stream, changingStream else {
return
@@ -656,6 +701,12 @@ final class PlayerModel: ObservableObject {
toBackend.play()
// Update Now Playing after resuming playback on new backend
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
#if !os(macOS)
if to == .mpv {
self?.setupAudioSessionForNowPlaying(forceReactivate: true)
self?.updateRemoteCommandCenter(reset: true)
}
#endif
self?.updateNowPlayingInfo()
}
}
@@ -950,13 +1001,27 @@ final class PlayerModel: ObservableObject {
}
}
func updateRemoteCommandCenter() {
func updateRemoteCommandCenter(reset: Bool = false) {
let commandCenter = MPRemoteCommandCenter.shared()
let skipForwardCommand = commandCenter.skipForwardCommand
let skipBackwardCommand = commandCenter.skipBackwardCommand
let previousTrackCommand = commandCenter.previousTrackCommand
let nextTrackCommand = commandCenter.nextTrackCommand
// If resetting (e.g., after AVPlayer was active), remove all targets and re-add them
if reset {
logger.info("Resetting remote command center to reclaim control from AVPlayer")
commandCenter.playCommand.removeTarget(nil)
commandCenter.pauseCommand.removeTarget(nil)
commandCenter.togglePlayPauseCommand.removeTarget(nil)
commandCenter.changePlaybackPositionCommand.removeTarget(nil)
skipForwardCommand.removeTarget(nil)
skipBackwardCommand.removeTarget(nil)
previousTrackCommand.removeTarget(nil)
nextTrackCommand.removeTarget(nil)
remoteCommandCenterConfigured = false
}
if !remoteCommandCenterConfigured {
remoteCommandCenterConfigured = true
@@ -986,25 +1051,21 @@ final class PlayerModel: ObservableObject {
return .success
}
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { [weak self] _ in
self?.play()
return .success
}
commandCenter.pauseCommand.isEnabled = true
commandCenter.pauseCommand.addTarget { [weak self] _ in
self?.pause()
return .success
}
commandCenter.togglePlayPauseCommand.isEnabled = true
commandCenter.togglePlayPauseCommand.addTarget { [weak self] _ in
self?.togglePlay()
return .success
}
commandCenter.changePlaybackPositionCommand.isEnabled = true
commandCenter.changePlaybackPositionCommand.addTarget { [weak self] remoteEvent in
guard let event = remoteEvent as? MPChangePlaybackPositionCommandEvent else { return .commandFailed }
@@ -1014,6 +1075,12 @@ final class PlayerModel: ObservableObject {
}
}
// Always re-enable commands to ensure they work after backend switches
commandCenter.playCommand.isEnabled = true
commandCenter.pauseCommand.isEnabled = true
commandCenter.togglePlayPauseCommand.isEnabled = true
commandCenter.changePlaybackPositionCommand.isEnabled = true
switch Defaults[.systemControlsCommands] {
case .seek:
previousTrackCommand.isEnabled = false
@@ -1149,6 +1216,24 @@ final class PlayerModel: ObservableObject {
}
}
func setupAudioSessionForNowPlaying(forceReactivate: Bool = false) {
#if !os(macOS)
do {
let audioSession = AVAudioSession.sharedInstance()
// If forcing reactivation (e.g., after backend switch), deactivate first
if forceReactivate {
try? audioSession.setActive(false, options: .notifyOthersOnDeactivation)
}
try audioSession.setCategory(.playback, mode: .moviePlayback, options: [])
try audioSession.setActive(true, options: [])
} catch {
logger.error("Failed to set up audio session: \(error)")
}
#endif
}
func updateCurrentArtwork() {
guard let video = currentVideo,
let thumbnailURL = video.thumbnailURL(quality: Constants.isIPhone ? .medium : .maxres)
@@ -1309,7 +1394,11 @@ final class PlayerModel: ObservableObject {
func setAudioSessionActive(_ setActive: Bool) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
do {
try AVAudioSession.sharedInstance().setActive(setActive)
let audioSession = AVAudioSession.sharedInstance()
if setActive {
try audioSession.setCategory(.playback, mode: .moviePlayback)
}
try audioSession.setActive(setActive)
} catch {
self.logger.error("Error setting audio session to \(setActive): \(error)")
}
@@ -1490,4 +1579,11 @@ final class PlayerModel: ObservableObject {
var availableAudioTracks: [Stream.AudioTrack] {
(backend as? MPVBackend)?.availableAudioTracks ?? []
}
var selectedAudioTrack: Stream.AudioTrack? {
let tracks = availableAudioTracks
guard !tracks.isEmpty else { return nil }
let safeIndex = min(max(0, selectedAudioTrackIndex), tracks.count - 1)
return tracks[safeIndex]
}
}

View File

@@ -73,7 +73,7 @@ extension PlayerModel {
for stream in streams {
streamProcessingQueue.async(group: streamProcessingGroup) {
let forbiddenAssetTestGroup = DispatchGroup()
if !hasAllowedAsset, !hasForbiddenAsset, !instance.proxiesVideos, stream.format != Stream.Format.unknown {
if !hasAllowedAsset, !hasForbiddenAsset, !instance.proxiesVideos, stream.format != nil && stream.format != Stream.Format.unknown {
let (nonHLSAssets, hlsURLs) = self.getAssets(from: [stream])
if let firstStream = nonHLSAssets.first {
let asset = firstStream.0

View File

@@ -81,6 +81,11 @@ struct QualityProfile: Hashable, Identifiable, Defaults.Serializable {
return false
}
// Safety check: Ensure stream has a format
guard let streamFormat = stream.format else {
return false
}
let defaultResolution = Stream.Resolution.custom(height: 720, refreshRate: 30)
let resolutionMatch = resolution.value ?? defaultResolution >= streamResolution
@@ -88,7 +93,7 @@ struct QualityProfile: Hashable, Identifiable, Defaults.Serializable {
return true
}
let formatMatch = formats.compactMap(\.streamFormat).contains(stream.format)
let formatMatch = formats.compactMap(\.streamFormat).contains(streamFormat)
return resolutionMatch && formatMatch
}

View File

@@ -16,13 +16,9 @@ final class SearchModel: ObservableObject {
@Published var querySuggestions = [String]()
private var suggestionsDebouncer = Debouncer(.milliseconds(200))
@Published var focused = false
@Default(.showSearchSuggestions) private var showSearchSuggestions
#if os(iOS)
var textField: UITextField!
#elseif os(macOS)
#if os(macOS)
var textField: NSTextField!
#endif
@@ -30,15 +26,9 @@ final class SearchModel: ObservableObject {
private var resource: Resource!
init() {
#if os(iOS)
addKeyboardDidHideNotificationObserver()
#endif
}
deinit {
#if os(iOS)
removeKeyboardDidHideNotificationObserver()
#endif
}
var isLoading: Bool {
@@ -158,18 +148,4 @@ final class SearchModel: ObservableObject {
}
}
}
#if os(iOS)
private func addKeyboardDidHideNotificationObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardDidHide), name: UIResponder.keyboardDidHideNotification, object: nil)
}
@objc func onKeyboardDidHide() {
focused = false
}
private func removeKeyboardDidHideNotificationObserver() {
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidHideNotification, object: nil)
}
#endif
}

View File

@@ -203,7 +203,7 @@ class Stream: Equatable, Hashable, Identifiable {
}
var description: String {
"\(displayLanguage) (\(content ?? "Unknown"))"
"\(displayLanguage) (\(content ?? "Original"))"
}
var isDubbed: Bool {

12
Shared/FeatureFlags.swift Normal file
View File

@@ -0,0 +1,12 @@
import Foundation
/// Feature flags for enabling/disabling functionality across the app
enum FeatureFlags {
/// Controls whether the "Hide Shorts" functionality is available
/// Set to false when the API changes prevent reliable detection of short videos
static let hideShortsEnabled = false
/// Controls whether the "Trending" section is available
/// Set to false to disable trending functionality across the app
static let trendingEnabled = false
}

View File

@@ -50,9 +50,11 @@ struct FavoriteItemView: View {
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundColor(.secondary)
if hideShorts || hideWatched {
if (FeatureFlags.hideShortsEnabled && hideShorts) || hideWatched {
AccentButton(text: "Disable filters", maxWidth: nil, verticalPadding: 0, minHeight: 30) {
hideShorts = false
if FeatureFlags.hideShortsEnabled {
hideShorts = false
}
hideWatched = false
reloadVisibleWatches()
}
@@ -107,7 +109,7 @@ struct FavoriteItemView: View {
resource?.removeObservers(ownedBy: store)
}
.onChange(of: player.currentVideo) { _ in if !player.presentingPlayer { reloadVisibleWatches() } }
.onChange(of: hideShorts) { _ in if !player.presentingPlayer { reloadVisibleWatches() } }
.onChange(of: hideShorts) { _ in if !player.presentingPlayer && FeatureFlags.hideShortsEnabled { reloadVisibleWatches() } }
.onChange(of: hideWatched) { _ in if !player.presentingPlayer { reloadVisibleWatches() } }
// Delay is necessary to update the list with the new items.
.onChange(of: favoritesChanged) { _ in if !player.presentingPlayer { Delay.by(1.0) { reloadVisibleWatches() } } }
@@ -135,9 +137,9 @@ struct FavoriteItemView: View {
var emptyItemsText: String {
var filterText = ""
if hideShorts && hideWatched {
if FeatureFlags.hideShortsEnabled && hideShorts && hideWatched {
filterText = "(watched and shorts hidden)"
} else if hideShorts {
} else if FeatureFlags.hideShortsEnabled && hideShorts {
filterText = "(shorts hidden)"
} else if hideWatched {
filterText = "(watched hidden)"
@@ -227,7 +229,7 @@ struct FavoriteItemView: View {
return false
}
guard hideShorts, item.contentType == .video, let video = item.video else {
guard FeatureFlags.hideShortsEnabled, hideShorts, item.contentType == .video, let video = item.video else {
return true
}
@@ -351,7 +353,7 @@ struct FavoriteItemView: View {
case .history:
return false
case .trending:
return visibleSections.contains(.trending)
return FeatureFlags.trendingEnabled && visibleSections.contains(.trending)
case .subscriptions:
return visibleSections.contains(.subscriptions) && accounts.signedIn
case .popular:

View File

@@ -36,10 +36,12 @@ struct MenuCommands: Commands {
.disabled(!AccountsModel.shared.app.supportsPopular)
.keyboardShortcut("3")
Button("Trending") {
setTabSelection(.trending)
if FeatureFlags.trendingEnabled {
Button("Trending") {
setTabSelection(.trending)
}
.keyboardShortcut("4")
}
.keyboardShortcut("4")
Button("Search") {
setTabSelection(.search)
@@ -76,7 +78,7 @@ struct MenuCommands: Commands {
Button(togglePlayerLabel) {
PlayerModel.shared.togglePlayer()
}
.keyboardShortcut("o")
.keyboardShortcut("p", modifiers: [.command, .shift])
}
}

View File

@@ -15,7 +15,7 @@ struct AppSidebarNavigation: View {
var body: some View {
#if os(iOS)
content.introspect(.viewController, on: .iOS(.v15, .v16, .v17, .v18)) { viewController in
content.introspect(.viewController, on: .iOS(.v15, .v16, .v17, .v18, .v26)) { viewController in
// workaround for an empty supplementary view on launch
// the supplementary view is determined by the default selection inside the
// primary view, but the primary view is not loaded so its selection is not read

View File

@@ -37,7 +37,7 @@ struct AppTabNavigation: View {
popularNavigationView
}
if visibleSections.contains(.trending) {
if FeatureFlags.trendingEnabled && visibleSections.contains(.trending) {
trendingNavigationView
}
@@ -115,7 +115,7 @@ struct AppTabNavigation: View {
.toolbar { toolbarContent }
}
.tabItem {
Label("Popular", systemImage: "arrow.up.right.circle.fill")
Label("Popular", systemImage: "chart.bar.fill")
.accessibility(label: Text("Popular"))
}
.tag(TabSelection.popular)
@@ -126,7 +126,7 @@ struct AppTabNavigation: View {
LazyView(TrendingView())
}
.tabItem {
Label("Trending", systemImage: "chart.bar.fill")
Label("Trending", systemImage: "arrow.up.right.circle.fill")
.accessibility(label: Text("Trending"))
}
.tag(TabSelection.trending)

View File

@@ -48,7 +48,7 @@ struct ContentView: View {
#endif
}
#if os(iOS)
.overlay(videoPlayer.edgesIgnoringSafeArea(Constants.isWindowFullscreen ? .init() : .all))
.overlay(videoPlayer)
.sheet(isPresented: $navigation.presentingShareSheet) {
if let shareURL = navigation.shareURL {
ShareSheet(activityItems: [shareURL])

View File

@@ -95,15 +95,15 @@ struct Sidebar: View {
if visibleSections.contains(.popular), accounts.app.supportsPopular {
NavigationLink(destination: LazyView(PopularView()), tag: TabSelection.popular, selection: $navigation.tabSelection) {
Label("Popular", systemImage: "arrow.up.right.circle")
Label("Popular", systemImage: "chart.bar.fill")
.accessibility(label: Text("Popular"))
}
.id("popular")
}
if visibleSections.contains(.trending) {
if FeatureFlags.trendingEnabled && visibleSections.contains(.trending) {
NavigationLink(destination: LazyView(TrendingView()), tag: TabSelection.trending, selection: $navigation.tabSelection) {
Label("Trending", systemImage: "chart.bar")
Label("Trending", systemImage: "arrow.up.right.circle.fill")
.accessibility(label: Text("Trending"))
}
.id("trending")

View File

@@ -74,6 +74,7 @@ struct OpenURLHandler {
focusMainWindow()
#endif
case .trending:
guard FeatureFlags.trendingEnabled else { return }
navigation.hideViewsAboveBrowser()
navigation.tabSelection = .trending
#if os(macOS)

View File

@@ -458,7 +458,7 @@ struct ControlsOverlay: View {
Menu {
audioTrackPicker
} label: {
Text(player.availableAudioTracks[player.selectedAudioTrackIndex].displayLanguage)
Text(player.selectedAudioTrack?.displayLanguage ?? "Original")
.frame(maxWidth: 240, alignment: .trailing)
}
.transaction { t in t.animation = .none }
@@ -468,7 +468,7 @@ struct ControlsOverlay: View {
.frame(height: 40)
#else
ControlsOverlayButton(focusedField: $focusedField, field: .audioTrack) {
Text(player.availableAudioTracks[player.selectedAudioTrackIndex].displayLanguage)
Text(player.selectedAudioTrack?.displayLanguage ?? "Original")
.frame(maxWidth: 320)
}
.contextMenu {

View File

@@ -151,7 +151,6 @@ final class MPVOGLView: GLKView {
// Check if we need partial updates
if needsPartialUpdate() {
logger.info("Performing partial update with scissor test.")
glEnable(GLenum(GL_SCISSOR_TEST))
}

View File

@@ -503,7 +503,7 @@ struct PlaybackSettings: View {
Menu {
audioTrackPicker
} label: {
Text(player.availableAudioTracks[player.selectedAudioTrackIndex].displayLanguage)
Text(player.selectedAudioTrack?.displayLanguage ?? "Original")
.frame(maxWidth: 240, alignment: .trailing)
}
.transaction { t in t.animation = .none }
@@ -513,7 +513,7 @@ struct PlaybackSettings: View {
.frame(height: 40)
#else
ControlsOverlayButton(focusedField: $focusedField, field: .audioTrack) {
Text(player.availableAudioTracks[player.selectedAudioTrackIndex].displayLanguage)
Text(player.selectedAudioTrack?.displayLanguage ?? "Original")
.frame(maxWidth: 320)
}
.contextMenu {

View File

@@ -268,7 +268,7 @@ struct VideoPlayerView: View {
.ignoresSafeArea()
#else
GeometryReader { geometry in
VStack(spacing: 0) {
ZStack(alignment: .top) {
player.playerBackendView
.modifier(
VideoPlayerSizeModifier(
@@ -305,6 +305,7 @@ struct VideoPlayerView: View {
#endif
.background(Color.black)
.zIndex(1)
if !detailsHiddenInFullScreen {
VideoDetails(
@@ -313,6 +314,7 @@ struct VideoPlayerView: View {
sidebarQueue: $sidebarQueue
)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.frame(height: geometry.size.height)
#if os(macOS)
// TODO: Check whether this is needed on macOS.
.onDisappear {
@@ -323,8 +325,9 @@ struct VideoPlayerView: View {
#endif
.id(player.currentVideo?.cacheKey)
.transition(.opacity)
.offset(y: detailViewDragOffset)
.offset(y: (fullScreenDetails ? 0 : player.playerSize.height) + detailViewDragOffset)
.gesture(detailsDragGesture)
.zIndex(2) // Ensure details are on top
} else {
VStack {}
}
@@ -412,7 +415,7 @@ struct VideoPlayerView: View {
if !newValue { player.controls.hideOverlays() }
}
#if os(iOS)
.statusBar(hidden: Constants.isIPad ? Constants.isWindowFullscreen : fullScreenPlayer)
.statusBar(hidden: fullScreenPlayer)
.backport
.toolbarBackground(colorScheme == .light ? .white : .black)
.backport

View File

@@ -3,27 +3,15 @@ import SwiftUI
import SwiftUIIntrospect
struct FocusableSearchTextField: View {
@ObservedObject private var state = SearchModel.shared
var body: some View {
SearchTextField()
#if os(macOS)
.introspect(.textField, on: .macOS(.v12, .v13, .v14, .v15)) { textField in
state.textField = textField
SearchModel.shared.textField = textField
}
.onAppear {
DispatchQueue.main.async {
state.textField?.becomeFirstResponder()
}
}
#elseif os(iOS)
.introspect(.textField, on: .iOS(.v15, .v16, .v17, .v18)) { textField in
state.textField = textField
}
.onChange(of: state.focused) { newValue in
if newValue, let textField = state.textField, !textField.isFirstResponder {
textField.becomeFirstResponder()
textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.endOfDocument)
SearchModel.shared.textField?.becomeFirstResponder()
}
}
#endif

View File

@@ -55,9 +55,9 @@ struct SearchTextField: View {
TextField("Search...", text: $state.queryText) {
state.changeQuery { query in
query.query = state.queryText
navigation.hideKeyboard()
}
RecentsModel.shared.addQuery(state.queryText)
navigation.hideKeyboard()
}
.disableAutocorrection(true)
.textFieldStyle(.plain)

View File

@@ -49,10 +49,10 @@ struct SearchView: View {
.opacity(state.queryText.isEmpty ? 0 : 1)
} else {
results
.backport
.scrollDismissesKeyboardInteractively()
}
}
.backport
.scrollDismissesKeyboardInteractively()
}
.environment(\.listingStyle, searchListingStyle)
.toolbar {

View File

@@ -258,11 +258,13 @@ struct BrowsingSettings: View {
private var visibleSectionsSettings: some View {
Section(header: SettingsHeader(text: "Sections".localized())) {
ForEach(VisibleSection.allCases, id: \.self) { section in
MultiselectRow(
title: section.title,
selected: visibleSections.contains(section)
) { value in
toggleSection(section, value: value)
if section != .trending || FeatureFlags.trendingEnabled {
MultiselectRow(
title: section.title,
selected: visibleSections.contains(section)
) { value in
toggleSection(section, value: value)
}
}
}
}
@@ -279,7 +281,9 @@ struct BrowsingSettings: View {
Spacer()
Picker("Startup section", selection: $startupSection) {
ForEach(StartupSection.allCases, id: \.rawValue) { section in
Text(section.label).tag(section)
if section != .trending || FeatureFlags.trendingEnabled {
Text(section.label).tag(section)
}
}
}
.modifier(SettingsPickerModifier())
@@ -287,7 +291,9 @@ struct BrowsingSettings: View {
#else
Picker("Startup section", selection: $startupSection) {
ForEach(StartupSection.allCases, id: \.rawValue) { section in
Text(section.label).tag(section)
if section != .trending || FeatureFlags.trendingEnabled {
Text(section.label).tag(section)
}
}
}
.modifier(SettingsPickerModifier())

View File

@@ -223,6 +223,7 @@ struct FeedView: View {
var header: some View {
HStack(spacing: 16) {
#if os(tvOS)
// swiftlint:disable:next deployment_target
if #available(tvOS 17.0, *) {
Menu {
accountsPicker

View File

@@ -76,6 +76,8 @@ struct ThumbnailView: View {
}
var placeholder: some View {
Rectangle().fill(Color("PlaceholderColor"))
Rectangle()
.fill(Color("PlaceholderColor"))
.aspectRatio(Constants.aspectRatio16x9, contentMode: .fill)
}
}

View File

@@ -84,7 +84,7 @@ struct VerticalCells<Header: View>: View {
#if os(iOS)
return verticalSizeClass == .regular ? 320 : 800
#elseif os(tvOS)
return 600
return 560
#else
return 320
#endif
@@ -92,7 +92,7 @@ struct VerticalCells<Header: View>: View {
var adaptiveGridItemMaximumSize: Double {
#if os(tvOS)
return 600
return 560
#else
return .infinity
#endif

View File

@@ -39,7 +39,7 @@ struct VideoCell: View {
Button(action: playAction) {
content
#if os(tvOS)
.frame(width: 580, height: channelOnThumbnail ? 470 : 500)
.frame(width: 550, height: channelOnThumbnail ? 446 : 480)
#endif
}
.opacity(contentOpacity)
@@ -432,15 +432,11 @@ struct VideoCell: View {
}
private var thumbnailImage: some View {
Group {
VideoCellThumbnail(video: video)
#if os(tvOS)
.frame(minHeight: 320)
#endif
}
.mask(RoundedRectangle(cornerRadius: thumbnailRoundingCornerRadius))
.aspectRatio(Constants.aspectRatio16x9, contentMode: .fill)
VideoCellThumbnail(video: video)
#if os(tvOS)
.frame(minHeight: 320)
#endif
.mask(RoundedRectangle(cornerRadius: thumbnailRoundingCornerRadius))
}
private var time: String? {
@@ -478,11 +474,10 @@ struct VideoCellThumbnail: View {
private var thumbnails: ThumbnailsModel { .shared }
var body: some View {
let (url, quality) = thumbnails.best(video)
let aspectRatio = (quality == .default || quality == .high) ? Constants.aspectRatio4x3 : Constants.aspectRatio16x9
let (url, _) = thumbnails.best(video)
ThumbnailView(url: url)
.aspectRatio(aspectRatio, contentMode: .fill)
.aspectRatio(Constants.aspectRatio16x9, contentMode: .fill)
}
}

View File

@@ -54,7 +54,7 @@ struct ContentItemView: View {
return false
}
guard hideShorts, item.contentType == .video, let video = item.video else {
guard FeatureFlags.hideShortsEnabled, hideShorts, item.contentType == .video, let video = item.video else {
return true
}

View File

@@ -5,22 +5,24 @@ struct HideShortsButtons: View {
@Default(.hideShorts) private var hideShorts
var body: some View {
Button {
hideShorts.toggle()
} label: {
Group {
if hideShorts {
Label("Short videos: hidden", systemImage: "bolt.slash.fill")
.help("Short videos: hidden")
} else {
Label("Short videos: visible", systemImage: "bolt.fill")
.help("Short videos: visible")
if FeatureFlags.hideShortsEnabled {
Button {
hideShorts.toggle()
} label: {
Group {
if hideShorts {
Label("Short videos: hidden", systemImage: "bolt.slash.fill")
.help("Short videos: hidden")
} else {
Label("Short videos: visible", systemImage: "bolt.fill")
.help("Short videos: visible")
}
}
#if os(tvOS)
.font(.caption)
.imageScale(.small)
#endif
}
#if os(tvOS)
.font(.caption)
.imageScale(.small)
#endif
}
}
}

View File

@@ -104,7 +104,7 @@ struct PopularView: View {
} label: {
HStack(spacing: 12) {
HStack(spacing: 6) {
Image(systemName: "arrow.up.right.circle.fill")
Image(systemName: "chart.bar.fill")
.foregroundColor(.primary)
.imageScale(.small)

View File

@@ -162,6 +162,23 @@ struct YatteeApp: App {
SDWebImageManager.defaultImageCache = PINCache(name: "stream.yattee.app")
NotificationCenter.default.addObserver(
forName: .accountConfigurationComplete,
object: nil,
queue: .main
) { _ in
let startupSection = Defaults[.startupSection]
var section: TabSelection? = startupSection.tabSelection
#if os(macOS)
if section == .playlists {
section = .search
}
#endif
NavigationModel.shared.tabSelection = section ?? .search
}
if !Defaults[.lastAccountIsPublic] {
AccountsModel.shared.configureAccount()
}
@@ -180,17 +197,6 @@ struct YatteeApp: App {
}
}
let startupSection = Defaults[.startupSection]
var section: TabSelection? = startupSection.tabSelection
#if os(macOS)
if section == .playlists {
section = .search
}
#endif
NavigationModel.shared.tabSelection = section ?? .search
DispatchQueue.main.async {
playlists.load()
}
@@ -231,6 +237,10 @@ struct YatteeApp: App {
self.migrateQualityProfiles()
}
DispatchQueue.global(qos: .userInitiated).async {
self.cleanupDisabledFeatures()
}
#if os(iOS)
DispatchQueue.global(qos: .userInitiated).async {
self.migrateRotateToLandscapeOnEnterFullScreen()
@@ -285,6 +295,34 @@ struct YatteeApp: App {
}
#endif
func cleanupDisabledFeatures() {
// Remove trending from visible sections if feature flag is disabled
if !FeatureFlags.trendingEnabled {
var visibleSections = Defaults[.visibleSections]
if visibleSections.contains(.trending) {
visibleSections.remove(.trending)
Defaults[.visibleSections] = visibleSections
}
// Reset startup section if set to trending
if Defaults[.startupSection] == .trending {
Defaults[.startupSection] = .home
}
// Remove trending favorites
let trendingFavorites = favorites.all.filter { item in
if case .trending = item.section {
return true
}
return false
}
for favorite in trendingFavorites {
favorites.remove(favorite)
}
}
}
var navigationStyle: NavigationStyle {
#if os(iOS)
return horizontalSizeClass == .compact ? .tab : .sidebar

View File

@@ -43,6 +43,9 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
1B81344D4D2A0B0363850A9E /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D886FD1371688A42060DF82 /* FeatureFlags.swift */; };
2446210B2B03C320154634A5 /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D886FD1371688A42060DF82 /* FeatureFlags.swift */; };
3528A0FEB2B02A52B715041C /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D886FD1371688A42060DF82 /* FeatureFlags.swift */; };
3700155B271B0D4D0049C794 /* PipedAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3700155A271B0D4D0049C794 /* PipedAPI.swift */; };
3700155C271B0D4D0049C794 /* PipedAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3700155A271B0D4D0049C794 /* PipedAPI.swift */; };
3700155D271B0D4D0049C794 /* PipedAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3700155A271B0D4D0049C794 /* PipedAPI.swift */; };
@@ -207,6 +210,9 @@
37319F0627103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
37319F0727103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
3732BFD028B83763009F3F4D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 3732BFCF28B83763009F3F4D /* KeychainAccess */; };
3736882B2ECE7947006B1D1F /* Notification+Names.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3736882A2ECE7947006B1D1F /* Notification+Names.swift */; };
3736882C2ECE7947006B1D1F /* Notification+Names.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3736882A2ECE7947006B1D1F /* Notification+Names.swift */; };
3736882D2ECE7947006B1D1F /* Notification+Names.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3736882A2ECE7947006B1D1F /* Notification+Names.swift */; };
3738535429451DC800D2D0CB /* BookmarksCacheModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3738535329451DC800D2D0CB /* BookmarksCacheModel.swift */; };
3738535529451DC800D2D0CB /* BookmarksCacheModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3738535329451DC800D2D0CB /* BookmarksCacheModel.swift */; };
3738535629451DC800D2D0CB /* BookmarksCacheModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3738535329451DC800D2D0CB /* BookmarksCacheModel.swift */; };
@@ -1071,6 +1077,8 @@
37FFC440272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
37FFC441272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
37FFC442272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
4EDC5582D5232B58E0E6A3CD /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D886FD1371688A42060DF82 /* FeatureFlags.swift */; };
C61471C67790128B7638173B /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D886FD1371688A42060DF82 /* FeatureFlags.swift */; };
E24DC6582BFA124100BF6187 /* UserAgentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24DC6572BFA124100BF6187 /* UserAgentManager.swift */; };
E24DC6592BFA124100BF6187 /* UserAgentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24DC6572BFA124100BF6187 /* UserAgentManager.swift */; };
E24DC65A2BFA124100BF6187 /* UserAgentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24DC6572BFA124100BF6187 /* UserAgentManager.swift */; };
@@ -1086,6 +1094,9 @@
E27568B92BFAAC2000BDF0AF /* LanguageCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27568B82BFAAC2000BDF0AF /* LanguageCodes.swift */; };
E27568BA2BFAAC2000BDF0AF /* LanguageCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27568B82BFAAC2000BDF0AF /* LanguageCodes.swift */; };
E27568BB2BFAAC2000BDF0AF /* LanguageCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27568B82BFAAC2000BDF0AF /* LanguageCodes.swift */; };
E69D11698A85867A28CD6A5A /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D886FD1371688A42060DF82 /* FeatureFlags.swift */; };
F18DFC08B722DE4D5ACB791A /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D886FD1371688A42060DF82 /* FeatureFlags.swift */; };
F3BFD18BABAA233ADA094AC6 /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D886FD1371688A42060DF82 /* FeatureFlags.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -1215,6 +1226,7 @@
373197D82732015300EF734F /* RelatedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelatedView.swift; sourceTree = "<group>"; };
37319F0427103F94004ECCD0 /* PlayerQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerQueue.swift; sourceTree = "<group>"; };
37367E582B8F63C200436163 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
3736882A2ECE7947006B1D1F /* Notification+Names.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Names.swift"; sourceTree = "<group>"; };
3738535329451DC800D2D0CB /* BookmarksCacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksCacheModel.swift; sourceTree = "<group>"; };
373C8FE3275B955100CB5936 /* CommentsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentsPage.swift; sourceTree = "<group>"; };
373CFACA26966264003CB2C6 /* SearchQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchQuery.swift; sourceTree = "<group>"; };
@@ -1507,6 +1519,7 @@
37E21DC52CDE528A008DF47C /* ta */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ta; path = ta.lproj/Localizable.strings; sourceTree = "<group>"; };
37E32DD42EC0D63600A63F29 /* TVOSPlainToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVOSPlainToggleStyle.swift; sourceTree = "<group>"; };
37E64DD026D597EB00C71877 /* SubscribedChannelsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribedChannelsModel.swift; sourceTree = "<group>"; };
37E6AF002ECCCCD50001DB2B /* Yattee (macOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Yattee (macOS).entitlements"; sourceTree = "<group>"; };
37E6D79B2944AE1A00550C3D /* FeedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedModel.swift; sourceTree = "<group>"; };
37E6D79F2944CD3800550C3D /* CacheStatusHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheStatusHeader.swift; sourceTree = "<group>"; };
37E70922271CD43000D34DDE /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = "<group>"; };
@@ -1557,6 +1570,7 @@
3DA101AD287C30F50027D920 /* DEVELOPMENT_TEAM.template.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DEVELOPMENT_TEAM.template.xcconfig; sourceTree = "<group>"; };
3DA101AE287C30F50027D920 /* DEVELOPMENT_TEAM.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DEVELOPMENT_TEAM.xcconfig; sourceTree = "<group>"; };
3DA101AF287C30F50027D920 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = "<group>"; };
5D886FD1371688A42060DF82 /* FeatureFlags.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = FeatureFlags.swift; sourceTree = "<group>"; };
E24DC6572BFA124100BF6187 /* UserAgentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgentManager.swift; sourceTree = "<group>"; };
E25028AF2BF790F5002CB9FC /* HTTPStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPStatus.swift; sourceTree = "<group>"; };
E258F3892BF61BD2005B8C28 /* URLTester.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLTester.swift; sourceTree = "<group>"; };
@@ -2221,6 +2235,7 @@
37BE0BD826A214500092E2DB /* macOS */ = {
isa = PBXGroup;
children = (
37E6AF002ECCCCD50001DB2B /* Yattee (macOS).entitlements */,
374C0542272496E4009BDDBE /* AppDelegate.swift */,
37FD43DB270470B70073EE42 /* InstancesSettings.swift */,
37B7CFED2A19789F001B0564 /* MacOSPiPDelegate.swift */,
@@ -2238,6 +2253,7 @@
37C7A9022679058300E721B4 /* Extensions */ = {
isa = PBXGroup;
children = (
3736882A2ECE7947006B1D1F /* Notification+Names.swift */,
379775922689365600DD52A8 /* Array+Next.swift */,
37DCD3162A191A180059A470 /* AVPlayerViewController+FullScreen.swift */,
376578842685429C00D4EA09 /* CaseIterable+Next.swift */,
@@ -2323,6 +2339,7 @@
37D4B0C22671614700C925CA /* YatteeApp.swift */,
37D4B0C42671614800C925CA /* Assets.xcassets */,
37BD07C42698ADEE003EBB87 /* Yattee.entitlements */,
5D886FD1371688A42060DF82 /* FeatureFlags.swift */,
);
path = Shared;
sourceTree = "<group>";
@@ -2830,7 +2847,7 @@
3765917827237D07009F956E /* XCRemoteSwiftPackageReference "PINCache" */,
37CF8B8228535E4F00B71E37 /* XCRemoteSwiftPackageReference "SDWebImage" */,
372AA40E286D067B0000B1DC /* XCRemoteSwiftPackageReference "Repeat" */,
37EE6DC328A305AD00BFD632 /* XCRemoteSwiftPackageReference "Reachability" */,
37EE6DC328A305AD00BFD632 /* XCRemoteSwiftPackageReference "Reachability.swift" */,
3799AC0728B03CEC001376F9 /* XCRemoteSwiftPackageReference "ActiveLabel.swift" */,
375B8AAF28B57F4200397B31 /* XCRemoteSwiftPackageReference "KeychainAccess" */,
3797104728D3D10600D5F53C /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */,
@@ -3062,6 +3079,7 @@
3762C46D2BF66CDD008E50B8 /* EnvironmentValues.swift in Sources */,
37095E82291DC85400301883 /* ShareViewController.swift in Sources */,
3762C47A2BF66F04008E50B8 /* Strings.swift in Sources */,
C61471C67790128B7638173B /* FeatureFlags.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3074,6 +3092,7 @@
37C0C0FF28665EAC007F6F78 /* VideosApp.swift in Sources */,
378FFBC92866018A009E3FBE /* URLParserTests.swift in Sources */,
371B88F82A1A310100D57683 /* String+Format.swift in Sources */,
3528A0FEB2B02A52B715041C /* FeatureFlags.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3295,6 +3314,7 @@
37BA794F26DC3E0E002A0235 /* Int+Format.swift in Sources */,
378E9C3C2945565500B2D696 /* SubscriptionsView.swift in Sources */,
37D6025928C17375009E8D98 /* PlaybackStatsView.swift in Sources */,
3736882D2ECE7947006B1D1F /* Notification+Names.swift in Sources */,
37F49BA326CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */,
374C053B2724614F009BDDBE /* PlayerTVMenu.swift in Sources */,
37A9965A26D6F8CA006E3224 /* HorizontalCells.swift in Sources */,
@@ -3401,6 +3421,7 @@
37B795902771DAE0001CF27B /* OpenURLHandler.swift in Sources */,
37732FF02703A26300F04329 /* AccountValidationStatus.swift in Sources */,
37A7D72F2B681011009CB1ED /* OtherDataSettingsGroupImporter.swift in Sources */,
2446210B2B03C320154634A5 /* FeatureFlags.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3642,6 +3663,7 @@
37A81BFA294BD1440081D322 /* WatchView.swift in Sources */,
3700155C271B0D4D0049C794 /* PipedAPI.swift in Sources */,
376BE50C27349108009AD608 /* BrowsingSettings.swift in Sources */,
3736882C2ECE7947006B1D1F /* Notification+Names.swift in Sources */,
3710A55629488C7D006F8025 /* PlaceholderListItem.swift in Sources */,
37EBD8CB27AF26C200F1C24B /* MPVBackend.swift in Sources */,
37D4B19826717E1500C925CA /* Video.swift in Sources */,
@@ -3708,6 +3730,7 @@
3769C02F2779F18600DDB3EA /* PlaceholderProgressView.swift in Sources */,
37BA794426DBA973002A0235 /* PlaylistsModel.swift in Sources */,
37A362BF29537AAA00BDF328 /* PlaybackSettings.swift in Sources */,
4EDC5582D5232B58E0E6A3CD /* FeatureFlags.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3716,6 +3739,7 @@
buildActionMask = 2147483647;
files = (
37D4B0D92671614900C925CA /* Tests_iOS.swift in Sources */,
F3BFD18BABAA233ADA094AC6 /* FeatureFlags.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3769,6 +3793,7 @@
3766AFD2273DA97D00686348 /* Int+FormatTests.swift in Sources */,
3774124F27387D2300423605 /* SubscribedChannelsModel.swift in Sources */,
3774126127387D2D00423605 /* AccountsModel.swift in Sources */,
F18DFC08B722DE4D5ACB791A /* FeatureFlags.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3940,6 +3965,7 @@
37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
3756C2A82861131100E4B059 /* NetworkState.swift in Sources */,
376578932685490700D4EA09 /* PlaylistsView.swift in Sources */,
3736882B2ECE7947006B1D1F /* Notification+Names.swift in Sources */,
37E75CCD2B6AEB01003A6237 /* RecentlyOpenedExporter.swift in Sources */,
377FF891291A99580028EB0B /* HistoryView.swift in Sources */,
37CC3F47270CE30600608308 /* PlayerQueueItem.swift in Sources */,
@@ -4062,6 +4088,7 @@
3797758D2689345500DD52A8 /* Store.swift in Sources */,
37484C2F26FC844700287258 /* InstanceSettings.swift in Sources */,
37A7D7312B681011009CB1ED /* OtherDataSettingsGroupImporter.swift in Sources */,
1B81344D4D2A0B0363850A9E /* FeatureFlags.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -4070,6 +4097,7 @@
buildActionMask = 2147483647;
files = (
37D4B176267164B000C925CA /* YatteeUITests.swift in Sources */,
E69D11698A85867A28CD6A5A /* FeatureFlags.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -4140,7 +4168,7 @@
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Open in Yattee/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Open in Yattee";
@@ -4171,7 +4199,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Open in Yattee/Info.plist";
@@ -4202,7 +4230,7 @@
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 14.0;
@@ -4222,7 +4250,7 @@
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 14.0;
@@ -4386,7 +4414,7 @@
CODE_SIGN_ENTITLEMENTS = "iOS/Yattee (iOS).entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
@@ -4440,7 +4468,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1";
@@ -4490,11 +4518,11 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_ENTITLEMENTS = Shared/Yattee.entitlements;
CODE_SIGN_ENTITLEMENTS = "macOS/Yattee (macOS).entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
DEAD_CODE_STRIPPING = YES;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
@@ -4528,12 +4556,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_ENTITLEMENTS = Shared/Yattee.entitlements;
CODE_SIGN_ENTITLEMENTS = "macOS/Yattee (macOS).entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
DEAD_CODE_STRIPPING = YES;
"DEVELOPMENT_TEAM[sdk=macosx*]" = 78Z5H3M6RJ;
ENABLE_APP_SANDBOX = YES;
@@ -4568,7 +4596,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4591,7 +4619,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4616,7 +4644,7 @@
buildSettings = {
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4640,7 +4668,7 @@
buildSettings = {
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4666,7 +4694,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
DEVELOPMENT_ASSET_PATHS = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -4706,7 +4734,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
DEVELOPMENT_ASSET_PATHS = "";
"DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES;
@@ -4746,7 +4774,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -4769,7 +4797,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 208;
CURRENT_PROJECT_VERSION = 210;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -5032,7 +5060,7 @@
repositoryURL = "https://github.com/siteline/SwiftUI-Introspect.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.3.0;
minimumVersion = 26.0.0;
};
};
37CF8B8228535E4F00B71E37 /* XCRemoteSwiftPackageReference "SDWebImage" */ = {
@@ -5051,7 +5079,7 @@
minimumVersion = 5.0.2;
};
};
37EE6DC328A305AD00BFD632 /* XCRemoteSwiftPackageReference "Reachability" */ = {
37EE6DC328A305AD00BFD632 /* XCRemoteSwiftPackageReference "Reachability.swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ashleymills/Reachability.swift";
requirement = {
@@ -5333,7 +5361,7 @@
};
37EE6DC428A305AD00BFD632 /* Reachability */ = {
isa = XCSwiftPackageProductDependency;
package = 37EE6DC328A305AD00BFD632 /* XCRemoteSwiftPackageReference "Reachability" */;
package = 37EE6DC328A305AD00BFD632 /* XCRemoteSwiftPackageReference "Reachability.swift" */;
productName = Reachability;
};
37FB2848272207F000A57617 /* SDWebImageWebPCoder */ = {

View File

@@ -168,8 +168,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/siteline/SwiftUI-Introspect.git",
"state" : {
"revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336",
"version" : "1.3.0"
"revision" : "a08b87f96b41055577721a6e397562b21ad52454",
"version" : "26.0.0"
}
},
{

View File

@@ -22,13 +22,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
OrientationTracker.shared.startDeviceOrientationTracking()
OrientationModel.shared.startOrientationUpdates()
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
logger.error("Failed to set audio session category: \(error)")
}
UIApplication.shared.beginReceivingRemoteControlEvents()
#endif

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spki</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spks</string>
</array>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

View File

@@ -29,7 +29,7 @@ struct TVNavigationView: View {
.tag(TabSelection.popular)
}
if visibleSections.contains(.trending) {
if FeatureFlags.trendingEnabled && visibleSections.contains(.trending) {
LazyView(TrendingView())
.tabItem { Text("Trending") }
.tag(TabSelection.trending)