Compare commits

..

77 Commits

Author SHA1 Message Date
Arkadiusz Fal
9bf3df1a29 Bump build number to 194 2024-09-04 09:38:15 +02:00
Arkadiusz Fal
34a805b986 Fix build issue 2024-09-04 09:37:38 +02:00
Arkadiusz Fal
36f680be62 Update CHANGELOG 2024-09-04 09:36:05 +02:00
Arkadiusz Fal
a27ab02433 Update dependencies 2024-09-04 09:33:23 +02:00
Arkadiusz Fal
59dd0785b3 Merge pull request #778 from stonerl/swipe-up-for-fullscreen
Gestures: swipe up toggles fullscreen
2024-09-04 09:16:23 +02:00
Arkadiusz Fal
d7be915e7e Merge pull request #779 from stonerl/better-audio-ducking
Better audio ducking
2024-09-04 09:15:35 +02:00
Arkadiusz Fal
3752f67630 Merge pull request #780 from stonerl/add-overlay-to-video-context-menu
don’t open video when dismissing context menu
2024-09-04 09:15:03 +02:00
Arkadiusz Fal
dfe7565138 Merge pull request #789 from stonerl/fix-picture-in-picture
fix picture in picture
2024-09-04 09:14:34 +02:00
Arkadiusz Fal
4d02538cb9 Merge pull request #793 from stonerl/mpv-remove-video-layer
mpv: remove video layer when entering background
2024-09-04 09:14:05 +02:00
Arkadiusz Fal
3229528a09 Merge pull request #794 from stonerl/enable-o3-optimization
enable -O3
2024-09-04 09:13:23 +02:00
Arkadiusz Fal
fffc4f4a5f Merge pull request #791 from stonerl/hi-res-invidious-logo
hi-res invidious logos
2024-09-04 09:13:01 +02:00
Toni Förster
e85bfe5007 enable -O3
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-03 21:40:48 +02:00
Toni Förster
b00b733fd5 don’t open video when dismissing context menu
fixes #510

fix tvOS build

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-03 21:21:34 +02:00
Toni Förster
119c663436 Gestures: swipe up toggles fullscreen 2024-09-03 21:20:56 +02:00
Toni Förster
e8fcee23ef make audio ducking and interruption more robust
Signed-off-by: Toni Förster <toni.foerster@gmail.com>

fix audio ducking and bluetooth play/pause

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-03 21:19:30 +02:00
Toni Förster
d56ef74a99 fix picture in picture
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-03 21:17:20 +02:00
Toni Förster
98f5b1a22b mpv: remove video layer when entering background
- fixes #792

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-03 14:58:18 +02:00
Toni Förster
f0b7bd3ab8 hi-res invidious logos
second try

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-09-03 01:01:52 +02:00
Arkadiusz Fal
2d7a101ce0 Bump build number to 193 2024-08-31 17:08:23 +02:00
Arkadiusz Fal
b2114174b4 Update CHANGELOG 2024-08-31 17:08:09 +02:00
Arkadiusz Fal
e9f502a486 Merge pull request #775 from stonerl/fix-crash-on-hls-live-playback 2024-08-31 14:54:08 +02:00
Toni Förster
6978e9437c fix crash on HLS live playback
fixes #774

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-31 14:22:28 +02:00
Arkadiusz Fal
2026201a5f Merge pull request #765 from stonerl/partial-fix-for-503
Update now playing info when using system controls – Partial fix for 503
2024-08-31 13:21:41 +02:00
Toni Förster
633af02577 don’t activate AVAudioSession on app start
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-31 13:19:54 +02:00
Arkadiusz Fal
1fd62f04aa Update dependencies 2024-08-31 13:08:46 +02:00
Arkadiusz Fal
e749307a0e Merge pull request #772 from stonerl/re-enter-fullscreen-on-background-return
fixed fullscreen handling for backgrounding
2024-08-31 13:08:40 +02:00
Toni Förster
d76ec881be fixed fullscreen handling for backgrounding
Currently when returning from background, the app is fullscreen but the video is in portrait mode.

Now when entering background we leave fullscreen and enter it again the the app is in foreground.

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-31 13:00:50 +02:00
Arkadiusz Fal
72a39a2c75 Merge pull request #767 from stonerl/new-defaults
Changes to defaults
2024-08-31 12:48:13 +02:00
Arkadiusz Fal
8a84db5a2d Merge pull request #766 from stonerl/music-mode-thumbnail
make thumbnail fill the view in music mode
2024-08-31 12:47:52 +02:00
Arkadiusz Fal
663c37e3d2 Merge pull request #768 from stonerl/improved-image-loading
Video Thumbnails: retry 3 times fetching from URL
2024-08-31 12:47:31 +02:00
Arkadiusz Fal
ea2b329df2 Merge pull request #769 from stonerl/new-invidious-logo
circular invidious logo
2024-08-31 12:46:55 +02:00
Arkadiusz Fal
bd79f56800 Merge pull request #770 from stonerl/correct-landscape-setting
apply correct orientation
2024-08-31 12:46:41 +02:00
Arkadiusz Fal
9a650b4ac0 Merge pull request #762 from stonerl/allow-username-and-password-in-url
Invidious: propper HTTP basic auth support
2024-08-31 12:46:33 +02:00
Toni Förster
13382270d5 Revert "fixed some potential crashes"
This reverts commit bde9aade11.
2024-08-31 02:50:56 +02:00
Toni Förster
24626c2299 apply correct orientation
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 23:20:47 +02:00
Toni Förster
18ac577c7f increase retry delay to 1 second
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 23:14:41 +02:00
Toni Förster
617af2cd20 format ordering adapted
- MP4 contains av1 encoded video which is not hardware accelerated.
- MP4 format removed from AVPlayer

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 22:04:31 +02:00
Toni Förster
1b778318dc circular invidious logo
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 20:58:42 +02:00
Toni Förster
c9ce574c7a VideoThumbnails: retry 3 times fetching from URL
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 20:01:25 +02:00
Toni Förster
9a1f0d7aaa Changes to defaults
- Don’t use System Controls with AVPlayer by default
- Changed default order of the formats
- no default AVPlayer profiles except for tvOS
- new constants isIOS, isTvOS, isMacOS
- ArtWork for nowPlaying is .medium on iPhone .maxres on all other platforms
- changes to the player bar defaults

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 16:03:35 +02:00
Toni Förster
1cb695848c make thumbnail fill the view in music mode
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-30 03:43:00 +02:00
Toni Förster
740a2f85ac updateNowPlayingInfo also with System controls
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-29 15:10:04 +02:00
Toni Förster
1a22ac71be move AVAudioSession configuration to AppDelegate
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-29 15:09:16 +02:00
Toni Förster
f0d581d512 remove autocorrect from location url TextField
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 18:05:40 +02:00
Toni Förster
049a42f2e8 allow basic auth with auth endpoint
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 17:40:16 +02:00
Toni Förster
cea2684a29 sanitise user and password in url
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 16:21:49 +02:00
Toni Förster
772e5016c4 make sure no log entries are lost
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 15:43:15 +02:00
Toni Förster
ed3d9a7d7c invidious support for basic auth urls
This adds user, password and port to the proxy and thumbnail url, if they exist.

fixes #614 & #731

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 15:42:35 +02:00
Toni Förster
bde9aade11 fixed some potential crashes
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 15:39:03 +02:00
Arkadiusz Fal
a194738bb6 Bump build number to 192 2024-08-28 13:40:12 +02:00
Arkadiusz Fal
45567254f2 Update CHANGELOG 2024-08-28 13:40:00 +02:00
Arkadiusz Fal
a3139ad059 Fix swiftformat offense 2024-08-28 13:34:36 +02:00
Arkadiusz Fal
598f17479f Merge pull request #760 from stonerl/hide-video-actions
Hide VideoActions Bar when no buttons is visible
2024-08-28 13:33:43 +02:00
Arkadiusz Fal
e888abfba9 Merge pull request #761 from stonerl/revert-new-appicons
Revert new AppIcons
2024-08-28 13:33:32 +02:00
Toni Förster
e1e53b2d36 Revert new AppIcons
This reverts commit 59da0e71b6.

Revert "Merge pull request #758 from stonerl/new-app-icons-second-try"

This reverts commit 7b26fdf400, reversing
changes made to 67b41e36d5.

Revert "Merge pull request #756 from stonerl/new-app-icons-second-try"

This reverts commit b51eadc7a9, reversing
changes made to 0c1fb02d50.
2024-08-28 12:42:57 +02:00
Toni Förster
9510d91d61 Hide VideoActions Bar when no buttons is visible
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-28 12:32:59 +02:00
Arkadiusz Fal
59da0e71b6 Fix bundle identifier 2024-08-27 22:46:07 +02:00
Arkadiusz Fal
6bdfb7368c Merge pull request #759 from stonerl/videoactions-color
Color changes to VideoActions
2024-08-27 22:26:56 +02:00
Arkadiusz Fal
7b26fdf400 Merge pull request #758 from stonerl/new-app-icons-second-try
change AppStore icon
2024-08-27 22:26:31 +02:00
Arkadiusz Fal
67b41e36d5 Merge pull request #757 from stonerl/zh-hans
Add Chinese (Simplified) - zh-Hans to LanguageCodes
2024-08-27 22:26:17 +02:00
Toni Förster
c9c60349df Color changes to VideoActions
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 22:02:00 +02:00
Toni Förster
6a70663f06 add more iOS icons
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 17:59:59 +02:00
Toni Förster
3d556d836f change the App Store Icon
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 16:26:56 +02:00
Toni Förster
8feeb33a55 distinguish between iOS and macOS AppIcons
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 15:45:54 +02:00
Toni Förster
497c3bfc12 Add Chinese (Simplified) - zh-Hans to LanguageCodes
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 15:33:06 +02:00
Arkadiusz Fal
b51eadc7a9 Merge pull request #756 from stonerl/new-app-icons-second-try
refreshed icons for iOS and macOS
2024-08-27 14:11:20 +02:00
Toni Förster
7d0c1180c4 refreshed icons for iOS and macOS
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 13:31:13 +02:00
Arkadiusz Fal
0c1fb02d50 Merge pull request #755 from yattee/revert-752-new-app-icons
Revert "refreshed icons for iOS and macOS"
2024-08-27 08:21:46 +02:00
Arkadiusz Fal
6f358fab56 Revert "refreshed icons for iOS and macOS" 2024-08-27 08:19:45 +02:00
Arkadiusz Fal
7631e2a8ed Bump build number to 191 2024-08-27 08:00:24 +02:00
Arkadiusz Fal
3a5f3fdfde Update CHANGELOG 2024-08-27 08:00:17 +02:00
Arkadiusz Fal
e3633bdaf7 Merge pull request #752 from stonerl/new-app-icons
refreshed icons for iOS and macOS
2024-08-27 07:56:31 +02:00
Arkadiusz Fal
e912d910bc Merge pull request #754 from stonerl/fix-mpv-crash-on-macos
fix mpv crashing on macOS
2024-08-27 07:56:03 +02:00
Arkadiusz Fal
5ccb0f90d5 Merge pull request #753 from stonerl/use-new-mpvkit-repo
add new MPVKit repo
2024-08-27 07:55:55 +02:00
Toni Förster
278bc343c2 fix mpv crashing on macOS
fixes #712

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-27 04:04:23 +02:00
Toni Förster
5dc197664d add new MPVKit repo
Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-26 22:59:24 +02:00
Toni Förster
192550ba7a refreshed icons for iOS and macOS
- added support for adaptive/tinted Icons on iOS.
- closes #700
- new macOS icon set, courtesy of @Kongolabongo (Discourse)

Signed-off-by: Toni Förster <toni.foerster@gmail.com>
2024-08-26 15:55:36 +02:00
34 changed files with 694 additions and 339 deletions

View File

@@ -1,8 +1,11 @@
## Build 190
* 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
## Build 194
* 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
## Previous builds
* Add skip, play/pause, and fullscreen shortcuts to macOS player (by @rickykresslein)
@@ -21,6 +24,25 @@
* Add import export of missing settings
* macOS: Fix settings windows layout
* Fix seek OSD layout on tvOS, revert OSD position
* 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
* 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

View File

@@ -10,17 +10,17 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.968.0)
aws-sdk-core (3.201.5)
aws-partitions (1.970.0)
aws-sdk-core (3.203.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.9)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.88.0)
aws-sdk-core (~> 3, >= 3.201.0)
aws-sdk-kms (1.89.0)
aws-sdk-core (~> 3, >= 3.203.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.159.0)
aws-sdk-core (~> 3, >= 3.201.0)
aws-sdk-s3 (1.160.0)
aws-sdk-core (~> 3, >= 3.203.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.9.1)
@@ -171,8 +171,7 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.3.6)
strscan
rexml (3.3.7)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
@@ -185,7 +184,6 @@ GEM
simctl (1.6.10)
CFPropertyList
naturally
strscan (3.1.0)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)

View File

@@ -10,11 +10,28 @@ struct AccountsBridge: Defaults.Bridge {
return nil
}
// Parse the urlString to check for embedded username and password
var sanitizedUrlString = value.urlString
if var urlComponents = URLComponents(string: value.urlString) {
if let user = urlComponents.user, let password = urlComponents.password {
// Sanitize the embedded username and password
let sanitizedUser = user.addingPercentEncoding(withAllowedCharacters: .urlUserAllowed) ?? user
let sanitizedPassword = password.addingPercentEncoding(withAllowedCharacters: .urlPasswordAllowed) ?? password
// Update the URL components with sanitized credentials
urlComponents.user = sanitizedUser
urlComponents.password = sanitizedPassword
// Reconstruct the sanitized URL
sanitizedUrlString = urlComponents.string ?? value.urlString
}
}
return [
"id": value.id,
"instanceID": value.instanceID ?? "",
"name": value.name,
"apiURL": value.urlString,
"apiURL": sanitizedUrlString,
"username": value.username,
"password": value.password ?? ""
]

View File

@@ -247,27 +247,27 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
}
func feed(_ page: Int?) -> Resource? {
resource(baseURL: account.url, path: "\(Self.basePath)/auth/feed")
resourceWithAuthCheck(baseURL: account.url, path: "\(Self.basePath)/auth/feed")
.withParam("page", String(page ?? 1))
}
var feed: Resource? {
resource(baseURL: account.url, path: basePathAppending("auth/feed"))
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/feed"))
}
var subscriptions: Resource? {
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
}
func subscribe(_ channelID: String, onCompletion: @escaping () -> Void = {}) {
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
.child(channelID)
.request(.post)
.onCompletion { _ in onCompletion() }
}
func unsubscribe(_ channelID: String, onCompletion: @escaping () -> Void) {
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
.child(channelID)
.request(.delete)
.onCompletion { _ in onCompletion() }
@@ -308,11 +308,11 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
return nil
}
return resource(baseURL: account.url, path: basePathAppending("auth/playlists"))
return resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/playlists"))
}
func playlist(_ id: String) -> Resource? {
resource(baseURL: account.url, path: basePathAppending("auth/playlists/\(id)"))
resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/playlists/\(id)"))
}
func playlistVideos(_ id: String) -> Resource? {
@@ -445,6 +445,9 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
urlComponents.scheme = instanceURLComponents.scheme
urlComponents.host = instanceURLComponents.host
urlComponents.user = instanceURLComponents.user
urlComponents.password = instanceURLComponents.password
urlComponents.port = instanceURLComponents.port
guard let url = urlComponents.url else {
return nil
@@ -553,6 +556,30 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
)
}
// Determines if the request requires Basic Auth credentials to be removed
private func needsBasicAuthRemoval(for path: String) -> Bool {
return path.hasPrefix("\(Self.basePath)/auth/")
}
// Creates a resource URL with consideration for removing Basic Auth credentials
private func createResourceURL(baseURL: URL, path: String) -> URL {
var resourceURL = baseURL
// Remove Basic Auth credentials if required
if needsBasicAuthRemoval(for: path), var urlComponents = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) {
urlComponents.user = nil
urlComponents.password = nil
resourceURL = urlComponents.url ?? baseURL
}
return resourceURL.appendingPathComponent(path)
}
func resourceWithAuthCheck(baseURL: URL, path: String) -> Resource {
let sanitizedURL = createResourceURL(baseURL: baseURL, path: path)
return super.resource(absoluteURL: sanitizedURL)
}
private func extractThumbnails(from details: JSON) -> [Thumbnail] {
details["videoThumbnails"].arrayValue.compactMap { json in
guard let url = json["url"].url,
@@ -563,13 +590,20 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
return nil
}
// some of instances are not configured properly and return thumbnails links
// with incorrect scheme
// Some instances are not configured properly and return thumbnail links
// with an incorrect scheme or a missing port.
components.scheme = accountUrlComponents.scheme
components.port = accountUrlComponents.port
// If basic HTTP authentication is used,
// the username and password need to be prepended to the URL.
components.user = accountUrlComponents.user
components.password = accountUrlComponents.password
guard let thumbnailUrl = components.url else {
return nil
}
print("Final thumbnail URL: \(thumbnailUrl)")
return Thumbnail(url: thumbnailUrl, quality: .init(rawValue: quality)!)
}

View File

@@ -102,7 +102,7 @@ final class AVPlayerBackend: PlayerBackend {
private var frequentTimeObserver: Any?
private var infrequentTimeObserver: Any?
private var playerTimeControlStatusObserver: Any?
private var playerTimeControlStatusObserver: NSKeyValueObservation?
private var statusObservation: NSKeyValueObservation?
@@ -119,10 +119,30 @@ final class AVPlayerBackend: PlayerBackend {
#if os(iOS)
controller.player = avPlayer
#endif
logger.info("AVPlayerBackend initialized.")
}
deinit {
// Invalidate any observers to avoid memory leaks
statusObservation?.invalidate()
playerTimeControlStatusObserver?.invalidate()
// Remove any time observers added to AVPlayer
if let frequentObserver = frequentTimeObserver {
avPlayer.removeTimeObserver(frequentObserver)
}
if let infrequentObserver = infrequentTimeObserver {
avPlayer.removeTimeObserver(infrequentObserver)
}
// Remove notification observers
removeItemDidPlayToEndTimeObserver()
logger.info("AVPlayerBackend deinitialized.")
}
func canPlay(_ stream: Stream) -> Bool {
stream.kind == .hls || stream.kind == .stream || (stream.kind == .adaptive && stream.format == .mp4)
stream.kind == .hls || stream.kind == .stream
}
func playStream(
@@ -344,7 +364,11 @@ final class AVPlayerBackend: PlayerBackend {
let startPlaying = {
#if !os(macOS)
try? AVAudioSession.sharedInstance().setActive(true)
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch {
self.logger.error("Error setting up audio session: \(error)")
}
#endif
self.setRate(self.model.currentRate)
@@ -779,7 +803,7 @@ final class AVPlayerBackend: PlayerBackend {
opened = true
controller.startPictureInPicture()
} else {
print("PiP not possible, waited \(delay) seconds")
self.logger.info("PiP not possible, waited \(delay) seconds")
}
}
}

View File

@@ -2,9 +2,9 @@ import AVFAudio
import CoreMedia
import Defaults
import Foundation
import Libmpv
import Logging
import MediaPlayer
import MPVKit
import Repeat
import SwiftUI
@@ -248,13 +248,6 @@ final class MPVBackend: PlayerBackend {
#if !os(macOS)
do {
try AVAudioSession.sharedInstance().setActive(true)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.handleAudioSessionInterruption(_:)),
name: AVAudioSession.interruptionNotification,
object: nil
)
} catch {
self.logger.error("Error setting up audio session: \(error)")
}
@@ -649,33 +642,4 @@ final class MPVBackend: PlayerBackend {
logger.info("MPV backend received unhandled property: \(name)")
}
}
#if !os(macOS)
@objc func handleAudioSessionInterruption(_ notification: Notification) {
logger.info("Audio session interruption received.")
guard let info = notification.userInfo,
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt
else {
logger.info("AVAudioSessionInterruptionTypeKey is missing or not a UInt in userInfo.")
return
}
let type = AVAudioSession.InterruptionType(rawValue: typeValue)
logger.info("Interruption type received: \(String(describing: type))")
switch type {
case .began:
pause()
logger.info("Audio session interrupted.")
default:
break
}
}
deinit {
NotificationCenter.default.removeObserver(self, name: AVAudioSession.interruptionNotification, object: nil)
}
#endif
}

View File

@@ -1,8 +1,8 @@
import CoreMedia
import Defaults
import Foundation
import Libmpv
import Logging
import MPVKit
#if !os(macOS)
import Siesta
import UIKit
@@ -99,6 +99,11 @@ final class MPVClient: ObservableObject {
checkError(mpv_set_option_string(mpv, "demuxer-lavf-analyzeduration", "1"))
checkError(mpv_set_option_string(mpv, "demuxer-lavf-probe-info", Defaults[.mpvDemuxerLavfProbeInfo]))
// Disable ytdl, since it causes crashes on macOS.
#if os(macOS)
checkError(mpv_set_option_string(mpv, "ytdl", "no"))
#endif
checkError(mpv_initialize(mpv))
let api = UnsafeMutableRawPointer(mutating: (MPV_RENDER_API_TYPE_OPENGL as NSString).utf8String)

View File

@@ -153,7 +153,8 @@ extension PlayerBackend {
// Filter out non-HLS streams and streams with resolution more than maxResolution
let nonHLSStreams = streams.filter {
let isHLS = $0.kind == .hls
let isWithinResolution = $0.resolution <= maxResolution.value
// Safely unwrap resolution and maxResolution.value to avoid crashes
let isWithinResolution = ($0.resolution != nil && maxResolution.value != nil) ? $0.resolution! <= maxResolution.value! : false
logger.info("Stream ID: \($0.id) - Kind: \(String(describing: $0.kind)) - Resolution: \(String(describing: $0.resolution)) - Bitrate: \($0.bitrate ?? 0)")
logger.info("Is HLS: \(isHLS), Is within resolution: \(isWithinResolution)")
return !isHLS && isWithinResolution
@@ -187,7 +188,8 @@ extension PlayerBackend {
}
let filteredStreams = adjustedStreams.filter { stream in
let isWithinResolution = stream.resolution <= maxResolution.value
// Safely unwrap resolution and maxResolution.value to avoid crashes
let isWithinResolution = (stream.resolution != nil && maxResolution.value != nil) ? stream.resolution! <= maxResolution.value! : false
logger.info("Filtered stream ID: \(stream.id) - Is within max resolution: \(isWithinResolution)")
return isWithinResolution
}

View File

@@ -47,7 +47,7 @@ final class PlayerModel: ObservableObject {
static var shared = PlayerModel()
let logger = Logger(label: "stream.yattee.app")
let logger = Logger(label: "stream.yattee.player.model")
var playerItem: AVPlayerItem?
@@ -56,6 +56,7 @@ final class PlayerModel: ObservableObject {
@Published var presentingPlayer = false { didSet { handlePresentationChange() } }
@Published var activeBackend = PlayerBackendType.mpv
@Published var forceBackendOnPlay: PlayerBackendType?
@Published var wasFullscreen = false
var avPlayerBackend = AVPlayerBackend()
var mpvBackend = MPVBackend()
@@ -203,6 +204,14 @@ final class PlayerModel: ObservableObject {
#if !os(macOS)
mpvBackend.controller = mpvController
mpvBackend.client = mpvController.client
// Register for audio session interruption notifications
NotificationCenter.default.addObserver(
self,
selector: #selector(handleAudioSessionInterruption(_:)),
name: AVAudioSession.interruptionNotification,
object: nil
)
#endif
playbackMode = Defaults[.playbackMode]
@@ -219,6 +228,12 @@ final class PlayerModel: ObservableObject {
currentRate = playerRate
}
#if !os(macOS)
deinit {
NotificationCenter.default.removeObserver(self, name: AVAudioSession.interruptionNotification, object: nil)
}
#endif
func show() {
#if os(macOS)
if presentingPlayer {
@@ -678,38 +693,24 @@ final class PlayerModel: ObservableObject {
avPlayerBackend.startPictureInPictureOnPlay = false
avPlayerBackend.startPictureInPictureOnSwitch = false
if activeBackend == .appleAVPlayer {
guard activeBackend != .appleAVPlayer else {
avPlayerBackend.tryStartingPictureInPicture()
return
}
// First, we need to create an array with supported formats.
let formatOrderPiP: [QualityProfile.Format] = [.hls, .stream, .mp4]
avPlayerBackend.startPictureInPictureOnSwitch = true
guard let video = currentVideo else { return }
guard let stream = avPlayerBackend.bestPlayable(availableStreams, maxResolution: .hd720p30, formatOrder: formatOrderPiP) else { return }
if avPlayerBackend.video == video {
if activeBackend != .appleAVPlayer {
avPlayerBackend.startPictureInPictureOnSwitch = true
}
changeActiveBackend(from: activeBackend, to: .appleAVPlayer)
} else {
avPlayerBackend.startPictureInPictureOnPlay = true
playStream(stream, of: video, preservingTime: true, upgrading: true, withBackend: avPlayerBackend)
}
var retryCount = 0
_ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
if let pipController = self?.pipController, pipController.isPictureInPictureActive, self?.avPlayerBackend.isPlaying == true {
self?.exitFullScreen()
self?.controls.objectWillChange.send()
timer.invalidate()
} else if retryCount < 3, self?.activeBackend == .appleAVPlayer, self?.avPlayerBackend.startPictureInPictureOnSwitch == false {
// If PiP didn't start, try starting it again up to 3 times,
self?.avPlayerBackend.startPictureInPictureOnSwitch = true
self?.avPlayerBackend.tryStartingPictureInPicture()
retryCount += 1
saveTime {
self.changeActiveBackend(from: .mpv, to: .appleAVPlayer)
_ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
if let pipController = self?.pipController, pipController.isPictureInPictureActive, self?.avPlayerBackend.isPlaying == true {
self?.exitFullScreen()
self?.controls.objectWillChange.send()
timer.invalidate()
} else if self?.activeBackend == .appleAVPlayer, self?.avPlayerBackend.startPictureInPictureOnSwitch == false {
self?.avPlayerBackend.startPictureInPictureOnSwitch = true
self?.avPlayerBackend.tryStartingPictureInPicture()
}
}
}
}
@@ -739,19 +740,27 @@ final class PlayerModel: ObservableObject {
show()
#endif
if previousActiveBackend == .mpv {
saveTime {
self.changeActiveBackend(from: self.activeBackend, to: .mpv, isInClosePip: true)
_ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
if self?.activeBackend == .mpv, self?.mpvBackend.isPlaying == true {
self?.backend.closePiP()
self?.controls.resetTimer()
timer.invalidate()
}
avPlayerBackend.closePiP()
_ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
if self?.activeBackend == .appleAVPlayer, self?.avPlayerBackend.isPlaying == true, self?.playingInPictureInPicture == false {
timer.invalidate()
}
}
guard previousActiveBackend == .mpv else { return }
saveTime {
self.changeActiveBackend(from: .appleAVPlayer, to: .mpv, isInClosePip: true)
_ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
if self?.activeBackend == .mpv, self?.mpvBackend.isPlaying == true {
timer.invalidate()
}
}
} else {
backend.closePiP()
}
// We need to remove the itme from the player, if not it will be displayed when next video goe to PiP.
Delay.by(1.0) {
self.avPlayerBackend.closeItem()
}
}
@@ -880,26 +889,29 @@ final class PlayerModel: ObservableObject {
}
func updateRemoteCommandCenter() {
let skipForwardCommand = MPRemoteCommandCenter.shared().skipForwardCommand
let skipBackwardCommand = MPRemoteCommandCenter.shared().skipBackwardCommand
let previousTrackCommand = MPRemoteCommandCenter.shared().previousTrackCommand
let nextTrackCommand = MPRemoteCommandCenter.shared().nextTrackCommand
let commandCenter = MPRemoteCommandCenter.shared()
let skipForwardCommand = commandCenter.skipForwardCommand
let skipBackwardCommand = commandCenter.skipBackwardCommand
let previousTrackCommand = commandCenter.previousTrackCommand
let nextTrackCommand = commandCenter.nextTrackCommand
if !remoteCommandCenterConfigured {
remoteCommandCenterConfigured = true
#if !os(macOS)
try? AVAudioSession.sharedInstance().setCategory(
.playback,
mode: .moviePlayback
)
UIApplication.shared.beginReceivingRemoteControlEvents()
#endif
let interval = TimeInterval(systemControlsSeekDuration) ?? 10
let preferredIntervals = [NSNumber(value: interval)]
// Remove existing targets to avoid duplicates
skipForwardCommand.removeTarget(nil)
skipBackwardCommand.removeTarget(nil)
previousTrackCommand.removeTarget(nil)
nextTrackCommand.removeTarget(nil)
commandCenter.playCommand.removeTarget(nil)
commandCenter.pauseCommand.removeTarget(nil)
commandCenter.togglePlayPauseCommand.removeTarget(nil)
commandCenter.changePlaybackPositionCommand.removeTarget(nil)
// Re-add targets for handling commands
skipForwardCommand.preferredIntervals = preferredIntervals
skipBackwardCommand.preferredIntervals = preferredIntervals
@@ -923,22 +935,22 @@ final class PlayerModel: ObservableObject {
return .success
}
MPRemoteCommandCenter.shared().playCommand.addTarget { [weak self] _ in
commandCenter.playCommand.addTarget { [weak self] _ in
self?.play()
return .success
}
MPRemoteCommandCenter.shared().pauseCommand.addTarget { [weak self] _ in
commandCenter.pauseCommand.addTarget { [weak self] _ in
self?.pause()
return .success
}
MPRemoteCommandCenter.shared().togglePlayPauseCommand.addTarget { [weak self] _ in
commandCenter.togglePlayPauseCommand.addTarget { [weak self] _ in
self?.togglePlay()
return .success
}
MPRemoteCommandCenter.shared().changePlaybackPositionCommand.addTarget { [weak self] remoteEvent in
commandCenter.changePlaybackPositionCommand.addTarget { [weak self] remoteEvent in
guard let event = remoteEvent as? MPChangePlaybackPositionCommandEvent else { return .commandFailed }
self?.backend.seek(to: event.positionTime, seekType: .userInteracted)
@@ -975,24 +987,53 @@ final class PlayerModel: ObservableObject {
func handleEnterForeground() {
setNeedsDrawing(presentingPlayer)
if !musicMode, activeBackend == .appleAVPlayer {
if !musicMode, activeBackend == .mpv {
mpvBackend.addVideoTrackFromStream()
mpvBackend.setVideoToAuto()
mpvBackend.controls.resetTimer()
} else if !musicMode, activeBackend == .appleAVPlayer {
avPlayerBackend.bindPlayerToLayer()
}
#if os(iOS)
if wasFullscreen {
wasFullscreen = false
DispatchQueue.main.async { [weak self] in
Delay.by(0.3) {
self?.enterFullScreen()
}
}
}
#endif
guard closePiPAndOpenPlayerOnEnteringForeground, playingInPictureInPicture else {
return
}
show()
closePiP()
// Needs to be delayed a bit, otherwise the PiP windows stays open
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
self?.closePiP()
}
}
func handleEnterBackground() {
if Defaults[.pauseOnEnteringBackground], !playingInPictureInPicture, !musicMode {
pause()
} else if !playingInPictureInPicture {
} else if !playingInPictureInPicture, activeBackend == .appleAVPlayer {
avPlayerBackend.removePlayerFromLayer()
} else if activeBackend == .mpv, !musicMode {
mpvBackend.setVideoToNo()
}
#if os(iOS)
guard playingFullScreen else { return }
wasFullscreen = playingFullScreen
DispatchQueue.main.async { [weak self] in
Delay.by(0.3) {
self?.exitFullScreen(showControls: false)
}
}
#endif
}
#endif
@@ -1017,18 +1058,22 @@ final class PlayerModel: ObservableObject {
guard activeBackend == .mpv else { return }
#endif
#if os(iOS)
if activeBackend == .appleAVPlayer, avPlayerUsesSystemControls {
return
}
#endif
guard let video = currentItem?.video else {
MPNowPlayingInfoCenter.default().nowPlayingInfo = .none
return
}
let currentTime = (backend.currentTime?.seconds.isFinite ?? false) ? backend.currentTime!.seconds : 0
// Determine the media type based on musicMode
let mediaType: NSNumber
if musicMode {
mediaType = MPMediaType.anyAudio.rawValue as NSNumber
} else {
mediaType = MPMediaType.anyVideo.rawValue as NSNumber
}
// Prepare the Now Playing info dictionary
var nowPlayingInfo: [String: AnyObject] = [
MPMediaItemPropertyTitle: video.displayTitle as AnyObject,
MPMediaItemPropertyArtist: video.displayAuthor as AnyObject,
@@ -1036,7 +1081,7 @@ final class PlayerModel: ObservableObject {
MPNowPlayingInfoPropertyElapsedPlaybackTime: currentTime as AnyObject,
MPNowPlayingInfoPropertyPlaybackQueueCount: queue.count as AnyObject,
MPNowPlayingInfoPropertyPlaybackQueueIndex: 1 as AnyObject,
MPMediaItemPropertyMediaType: MPMediaType.anyVideo.rawValue as AnyObject
MPMediaItemPropertyMediaType: mediaType
]
if !currentArtwork.isNil {
@@ -1057,7 +1102,7 @@ final class PlayerModel: ObservableObject {
func updateCurrentArtwork() {
guard let video = currentVideo,
let thumbnailURL = video.thumbnailURL(quality: .medium)
let thumbnailURL = video.thumbnailURL(quality: Constants.isIPhone ? .medium : .maxres)
else {
return
}
@@ -1203,6 +1248,42 @@ final class PlayerModel: ObservableObject {
return nil
}
#if !os(macOS)
@objc func handleAudioSessionInterruption(_ notification: Notification) {
logger.info("Audio session interruption received.")
logger.info("Notification received: \(notification)")
guard let info = notification.userInfo,
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue)
else {
logger.info("AVAudioSessionInterruptionTypeKey is missing or not a UInt in userInfo.")
return
}
logger.info("Interruption type received: \(type)")
switch type {
case .began:
logger.info("Audio session interrupted.")
// We need to call pause() to set all variables correctly, and play()
// directly afterwards, because the .began interrupt is sent after audio
// ducking ended and playback would pause. Audio ducking usually happens
// when using headphones.
pause()
play()
case .ended:
logger.info("Audio session interruption ended.")
// We need to call pause() to set all variables correctly.
// Otherwise, playback does not resume when the interruption ends.
pause()
play()
default:
break
}
}
#endif
#if os(macOS)
private func assignKeyPressMonitor() {
keyPressMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { keyEvent -> NSEvent? in
@@ -1240,7 +1321,7 @@ final class PlayerModel: ObservableObject {
}
private func destroyKeyPressMonitor() {
if let keyPressMonitor = keyPressMonitor {
if let keyPressMonitor {
NSEvent.removeMonitor(keyPressMonitor)
}
}

View File

@@ -6,12 +6,12 @@ struct QualityProfile: Hashable, Identifiable, Defaults.Serializable {
static var defaultProfile = Self(id: "default", backend: .mpv, resolution: .hd720p60, formats: [.stream], order: Array(Format.allCases.indices))
enum Format: String, CaseIterable, Identifiable, Defaults.Serializable {
case hls
case stream
case avc1
case stream
case webm
case mp4
case av1
case webm
case hls
var id: String {
rawValue
@@ -30,18 +30,18 @@ struct QualityProfile: Hashable, Identifiable, Defaults.Serializable {
var streamFormat: Stream.Format? {
switch self {
case .hls:
return nil
case .stream:
return nil
case .avc1:
return .avc1
case .stream:
return nil
case .webm:
return .webm
case .mp4:
return .mp4
case .av1:
return .av1
case .webm:
return .webm
case .hls:
return nil
}
}
}
@@ -59,14 +59,16 @@ struct QualityProfile: Hashable, Identifiable, Defaults.Serializable {
}
var formatsDescription: String {
if formats.count == Format.allCases.count {
switch formats.count {
case Format.allCases.count:
return "Any format".localized()
}
if formats.count <= 3 {
case 0:
return "No format selected".localized()
case 1 ... 3:
return formats.map(\.description).joined(separator: ", ")
default:
return String(format: "%@ formats".localized(), String(formats.count))
}
return String(format: "%@ formats".localized(), String(formats.count))
}
func isPreferred(_ stream: Stream) -> Bool {

View File

@@ -5,10 +5,27 @@ final class ThumbnailsModel: ObservableObject {
static var shared = ThumbnailsModel()
@Published var unloadable = Set<URL>()
private var retryCounts = [URL: Int]()
private let maxRetries = 3
private let retryDelay: TimeInterval = 1.0
func insertUnloadable(_ url: URL) {
DispatchQueue.main.async {
self.unloadable.insert(url)
let retries = (retryCounts[url] ?? 0) + 1
if retries >= maxRetries {
DispatchQueue.main.async {
self.unloadable.insert(url)
self.retryCounts.removeValue(forKey: url)
}
} else {
DispatchQueue.main.async {
self.retryCounts[url] = retries
}
DispatchQueue.global().asyncAfter(deadline: .now() + retryDelay) {
DispatchQueue.main.async {
self.retryCounts[url] = retries
}
}
}
}

View File

@@ -1,15 +1,17 @@
{
"images" : [
{
"filename" : "Invidious.svg",
"filename" : "Invidious_512x512@1x.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Invidious_512x512@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Invidious_512x512@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="512pt" height="512pt" version="1.0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><g><rect x="-.0072516" y=".00056299" width="512.01" height="512.02" fill="#575757" stroke-width=".063019"/><path d="m247.17 455.95c-19.792-0.78921-38.719-4.2564-57.154-10.47-60.968-20.55-108.68-68.579-127-127.86-7.8955-25.538-10.062-53.943-6.2586-82.067 3.7105-27.439 13.603-53.515 29.342-77.344 12.069-18.273 29.138-36.277 47.228-49.816 36.891-27.61 85.944-42.49 132.38-40.157 25.88 1.3001 49.939 6.765 73.106 16.606 8.1948 3.481 20.024 9.6845 27.696 14.525 14.15 8.9272 22.367 15.498 34.482 27.573 13.254 13.211 22.128 24.276 30.398 37.906 7.2081 11.879 14.099 27.15 18.229 40.397 1.5996 5.1305 4.442 16.456 5.6852 22.653 2.3908 11.917 2.6998 15.722 2.7049 33.312 6e-3 18.515-0.46256 24.413-2.9166 36.758-9.3274 46.92-35.58 88.167-74.872 117.64-22.814 17.112-50.027 29.535-78.547 35.858-16.714 3.7059-35.421 5.2453-54.498 4.4846zm-35.1-78.786c-5.3e-4 -0.52647-0.0741-2.0564-0.16311-3.3999l-0.16178-2.4427-4.7018-0.26271c-4.0477-0.22614-4.7968-0.33363-5.3847-0.77253-2.0235-1.5108-1.4679-6.0695 2.2494-18.457 0.8637-2.8781 3.3371-11.321 5.4966-18.762 2.1594-7.4409 5.2002-17.836 6.7573-23.101 1.5571-5.2648 4.1948-14.282 5.8615-20.038 1.6667-5.7562 3.6145-12.4 4.3284-14.764 0.71391-2.3641 3.2583-11.037 5.6542-19.272 4.9475-17.007 8.1626-27.723 8.9438-29.811 0.51852-1.3858 0.54785-1.4139 0.99761-0.95317 0.25486 0.26106 3.8462 7.3667 7.9807 15.79 4.1345 8.4236 13.089 26.573 19.898 40.331 17.188 34.73 37.849 76.578 43.261 87.622l4.5356 9.257 11.359-0.0895c6.2475-0.0492 11.615-0.19623 11.929-0.32672 0.5614-0.23385 0.54167-0.2959-1.3723-4.3176-1.068-2.2442-8.1436-16.601-15.724-31.904-48.687-98.293-61.22-123.86-67.889-138.48-4.7022-10.309-6.9031-14.807-7.7139-15.762-0.82931-0.97742-1.6319-1.0638-2.3704-0.25525-1.1993 1.313-4.1046 10.063-9.3869 28.27-2.0569 7.0899-6.5372 22.425-9.9562 34.077-6.6396 22.629-8.5182 29.037-14.33 48.883-2.0354 6.9495-4.7977 16.369-6.1385 20.931-1.3408 4.5628-4.033 13.81-5.9826 20.549-4.304 14.877-6.136 20.889-7.3886 24.25-2.1371 5.7334-2.5723 6.3292-4.9216 6.7384-0.88855 0.15472-2.4102 0.28196-3.3815 0.28275-2.1993 3e-3 -3.5494 0.36339-4.0558 1.0863-0.42176 0.60215-0.56421 4.8802-0.18251 5.4812 0.20573 0.32388 2.4672 0.37414 23.34 0.51873l8.6151 0.0597-7e-4 -0.95723zm36.751-205.59c4.3282-0.92335 8.4607-4.943 9.4374-9.1796 0.36569-1.5862 0.32543-4.9758-0.077-6.4799-0.85108-3.1813-3.2688-6.291-6.039-7.7675-3.8111-2.0313-9.456-2.0295-13.272 5e-3 -5.9828 3.1888-8.1556 11.089-4.7878 17.408 2.6995 5.0648 8.3611 7.3754 14.738 6.015z" fill="#f0f0f0" stroke-width=".025526"/></g><g transform="matrix(.069892 0 0 -.069892 44.236 474.48)"><path d="m2787 4669c-124-65-123-255 3-319 86-44 196-16 247 62 58 87 26 211-67 258-51 26-132 26-183-1z" fill="#00b6f0" stroke="#00b6f0" stroke-width="4.25"/><path d="m2882 4108c-12-16-63-166-102-303-30-104-101-350-165-565-20-69-58-199-85-290-26-91-64-221-85-290-20-69-58-199-85-290-26-91-64-221-85-290-20-69-57-195-81-280-59-207-93-299-115-310-10-6-35-10-56-10-73 0-84-8-81-54l3-41 228-3 228-2-3 47-3 48-73 3c-66 3-74 5-84 27-13 28 0 104 37 225 13 41 47 156 75 255s66 230 85 290c18 61 56 191 85 290 28 99 66 230 85 290 18 61 56 191 85 290 85 297 123 419 131 429 5 5 17-11 28-35 10-24 192-393 403-819s447-902 523-1058l139-282h168c92 0 168 4 168 8s-75 158-166 342c-588 1183-969 1958-1033 2100-29 63-69 151-89 195-44 95-58 110-80 83z" fill="#575757"/></g></svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View File

@@ -39,6 +39,30 @@ enum Constants {
#endif
}
static var isTvOS: Bool {
#if os(tvOS)
true
#else
false
#endif
}
static var isMacOS: Bool {
#if os(macOS)
true
#else
false
#endif
}
static var isIOS: Bool {
#if os(iOS)
true
#else
false
#endif
}
static var progressViewScale: Double {
#if os(macOS)
0.4

View File

@@ -20,14 +20,14 @@ extension Defaults.Keys {
static let showOpenActionsToolbarItem = Key<Bool>("showOpenActionsToolbarItem", default: false)
#if os(iOS)
static let showDocuments = Key<Bool>("showDocuments", default: false)
static let lockPortraitWhenBrowsing = Key<Bool>("lockPortraitWhenBrowsing", default: UIDevice.current.userInterfaceIdiom == .phone)
static let lockPortraitWhenBrowsing = Key<Bool>("lockPortraitWhenBrowsing", default: Constants.isIPhone)
#endif
#if !os(tvOS)
#if os(macOS)
static let accountPickerDisplaysUsernameDefault = true
#else
static let accountPickerDisplaysUsernameDefault = UIDevice.current.userInterfaceIdiom == .pad
static let accountPickerDisplaysUsernameDefault = Constants.isIPad
#endif
static let accountPickerDisplaysUsername = Key<Bool>("accountPickerDisplaysUsername", default: accountPickerDisplaysUsernameDefault)
#endif
@@ -41,9 +41,9 @@ extension Defaults.Keys {
static let showChannelAvatarInVideosListing = Key<Bool>("showChannelAvatarInVideosListing", default: true)
static let playerButtonSingleTapGesture = Key<PlayerTapGestureAction>("playerButtonSingleTapGesture", default: .togglePlayer)
static let playerButtonDoubleTapGesture = Key<PlayerTapGestureAction>("playerButtonDoubleTapGesture", default: .nothing)
static let playerButtonShowsControlButtonsWhenMinimized = Key<Bool>("playerButtonShowsControlButtonsWhenMinimized", default: false)
static let playerButtonIsExpanded = Key<Bool>("playerButtonIsExpanded", default: false)
static let playerButtonDoubleTapGesture = Key<PlayerTapGestureAction>("playerButtonDoubleTapGesture", default: .togglePlayerVisibility)
static let playerButtonShowsControlButtonsWhenMinimized = Key<Bool>("playerButtonShowsControlButtonsWhenMinimized", default: true)
static let playerButtonIsExpanded = Key<Bool>("playerButtonIsExpanded", default: true)
static let playerBarMaxWidth = Key<String>("playerBarMaxWidth", default: "600")
static let channelOnThumbnail = Key<Bool>("channelOnThumbnail", default: false)
static let timeOnThumbnail = Key<Bool>("timeOnThumbnail", default: true)
@@ -64,7 +64,7 @@ extension Defaults.Keys {
static let closeVideoOnEOF = Key<Bool>("closeVideoOnEOF", default: false)
#if !os(macOS)
static let pauseOnEnteringBackground = Key<Bool>("pauseOnEnteringBackground", default: true)
static let pauseOnEnteringBackground = Key<Bool>("pauseOnEnteringBackground", default: false)
#endif
#if os(iOS)
@@ -79,7 +79,7 @@ extension Defaults.Keys {
static let showChapters = Key<Bool>("showChapters", default: true)
static let showChapterThumbnails = Key<Bool>("showChapterThumbnails", default: true)
static let showChapterThumbnailsOnlyWhenDifferent = Key<Bool>("showChapterThumbnailsOnlyWhenDifferent", default: true)
static let showChapterThumbnailsOnlyWhenDifferent = Key<Bool>("showChapterThumbnailsOnlyWhenDifferent", default: false)
static let expandChapters = Key<Bool>("expandChapters", default: true)
static let showRelated = Key<Bool>("showRelated", default: true)
static let showInspector = Key<ShowInspectorSetting>("showInspector", default: .onlyLocal)
@@ -94,10 +94,10 @@ extension Defaults.Keys {
#if os(iOS)
static let honorSystemOrientationLock = Key<Bool>("honorSystemOrientationLock", default: true)
static let enterFullscreenInLandscape = Key<Bool>("enterFullscreenInLandscape", default: UIDevice.current.userInterfaceIdiom == .phone)
static let enterFullscreenInLandscape = Key<Bool>("enterFullscreenInLandscape", default: Constants.isIPhone)
static let rotateToLandscapeOnEnterFullScreen = Key<FullScreenRotationSetting>(
"rotateToLandscapeOnEnterFullScreen",
default: UIDevice.current.userInterfaceIdiom == .phone ? .landscapeRight : .disabled
default: Constants.isIPhone ? .landscapeRight : .disabled
)
#endif
@@ -116,14 +116,14 @@ extension Defaults.Keys {
// MARK: GROUP - Controls
static let avPlayerUsesSystemControls = Key<Bool>("avPlayerUsesSystemControls", default: true)
static let avPlayerUsesSystemControls = Key<Bool>("avPlayerUsesSystemControls", default: Constants.isTvOS)
static let horizontalPlayerGestureEnabled = Key<Bool>("horizontalPlayerGestureEnabled", default: true)
static let seekGestureSensitivity = Key<Double>("seekGestureSensitivity", default: 30.0)
static let seekGestureSpeed = Key<Double>("seekGestureSpeed", default: 0.5)
#if os(iOS)
static let playerControlsLayoutDefault = UIDevice.current.userInterfaceIdiom == .pad ? PlayerControlsLayout.medium : .small
static let fullScreenPlayerControlsLayoutDefault = UIDevice.current.userInterfaceIdiom == .pad ? PlayerControlsLayout.medium : .small
static let playerControlsLayoutDefault = Constants.isIPad ? PlayerControlsLayout.medium : .small
static let fullScreenPlayerControlsLayoutDefault = Constants.isIPad ? PlayerControlsLayout.medium : .small
#elseif os(tvOS)
static let playerControlsLayoutDefault = PlayerControlsLayout.tvRegular
static let fullScreenPlayerControlsLayoutDefault = PlayerControlsLayout.tvRegular
@@ -175,61 +175,152 @@ extension Defaults.Keys {
// MARK: GROUP - Quality
static let hd2160pMPVProfile = QualityProfile(id: "hd2160pMPVProfile", backend: .mpv, resolution: .hd2160p60, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices))
static let hd1080pMPVProfile = QualityProfile(id: "hd1080pMPVProfile", backend: .mpv, resolution: .hd1080p60, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices))
static let hd720pMPVProfile = QualityProfile(id: "hd720pMPVProfile", backend: .mpv, resolution: .hd720p60, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices))
static let hd720pAVPlayerProfile = QualityProfile(id: "hd720pAVPlayerProfile", backend: .appleAVPlayer, resolution: .hd720p30, formats: [.hls, .stream], order: Array(QualityProfile.Format.allCases.indices))
static let sd360pAVPlayerProfile = QualityProfile(id: "sd360pAVPlayerProfile", backend: .appleAVPlayer, resolution: .sd360p30, formats: [.hls, .stream], order: Array(QualityProfile.Format.allCases.indices))
static let hd2160p60MPVProfile = QualityProfile(id: "hd2160p60MPVProfile", backend: .mpv, resolution: .hd2160p60, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices))
static let hd1080p60MPVProfile = QualityProfile(id: "hd1080p60MPVProfile", backend: .mpv, resolution: .hd1080p60, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices))
static let hd1080pMPVProfile = QualityProfile(id: "hd1080pMPVProfile", backend: .mpv, resolution: .hd1080p30, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices))
static let hd720p60MPVProfile = QualityProfile(id: "hd720p60MPVProfile", backend: .mpv, resolution: .hd720p60, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices))
static let hd720pMPVProfile = QualityProfile(id: "hd720pMPVProfile", backend: .mpv, resolution: .hd720p30, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices))
static let sd360pMPVProfile = QualityProfile(id: "sd360pMPVProfile", backend: .mpv, resolution: .sd360p30, formats: QualityProfile.Format.allCases, order: Array(QualityProfile.Format.allCases.indices))
static let hd720pAVPlayerProfile = QualityProfile(id: "hd720pAVPlayerProfile", backend: .appleAVPlayer, resolution: .hd720p30, formats: [.stream, .hls], order: Array(QualityProfile.Format.allCases.indices))
static let sd360pAVPlayerProfile = QualityProfile(id: "sd360pAVPlayerProfile", backend: .appleAVPlayer, resolution: .sd360p30, formats: [.stream, .hls], order: Array(QualityProfile.Format.allCases.indices))
#if os(iOS)
static let qualityProfilesDefault = UIDevice.current.userInterfaceIdiom == .pad ? [
hd2160pMPVProfile,
hd1080pMPVProfile,
hd720pMPVProfile,
hd720pAVPlayerProfile,
sd360pAVPlayerProfile
] : [
hd1080pMPVProfile,
hd720pMPVProfile,
hd720pAVPlayerProfile,
sd360pAVPlayerProfile
]
enum QualityProfiles {
// iPad-specific settings
enum iPad {
static let qualityProfilesDefault = [
hd1080p60MPVProfile,
hd1080pMPVProfile,
hd720p60MPVProfile,
hd720pMPVProfile
]
static let batteryCellularProfileDefault = hd720pMPVProfile.id
static let batteryNonCellularProfileDefault = hd720p60MPVProfile.id
static let chargingCellularProfileDefault = hd1080pMPVProfile.id
static let chargingNonCellularProfileDefault = hd1080p60MPVProfile.id
}
// iPhone-specific settings
enum iPhone {
static let qualityProfilesDefault = [
hd1080p60MPVProfile,
hd1080pMPVProfile,
hd720p60MPVProfile,
hd720pMPVProfile,
sd360pMPVProfile
]
static let batteryCellularProfileDefault = sd360pMPVProfile.id
static let batteryNonCellularProfileDefault = hd720p60MPVProfile.id
static let chargingCellularProfileDefault = hd720pMPVProfile.id
static let chargingNonCellularProfileDefault = hd1080p60MPVProfile.id
}
// Access the correct profile based on device type
static var currentProfile: (qualityProfilesDefault: [QualityProfile], batteryCellularProfileDefault: String, batteryNonCellularProfileDefault: String, chargingCellularProfileDefault: String, chargingNonCellularProfileDefault: String) {
if Constants.isIPad {
return (
qualityProfilesDefault: iPad.qualityProfilesDefault,
batteryCellularProfileDefault: iPad.batteryCellularProfileDefault,
batteryNonCellularProfileDefault: iPad.batteryNonCellularProfileDefault,
chargingCellularProfileDefault: iPad.chargingCellularProfileDefault,
chargingNonCellularProfileDefault: iPad.chargingNonCellularProfileDefault
)
}
return (
qualityProfilesDefault: iPhone.qualityProfilesDefault,
batteryCellularProfileDefault: iPhone.batteryCellularProfileDefault,
batteryNonCellularProfileDefault: iPhone.batteryNonCellularProfileDefault,
chargingCellularProfileDefault: iPhone.chargingCellularProfileDefault,
chargingNonCellularProfileDefault: iPhone.chargingNonCellularProfileDefault
)
}
}
static let batteryCellularProfileDefault = hd720pAVPlayerProfile.id
static let batteryNonCellularProfileDefault = hd720pAVPlayerProfile.id
static let chargingCellularProfileDefault = hd720pAVPlayerProfile.id
static let chargingNonCellularProfileDefault = hd1080pMPVProfile.id
#elseif os(tvOS)
static let qualityProfilesDefault = [
hd2160pMPVProfile,
hd1080pMPVProfile,
hd720pMPVProfile,
hd720pAVPlayerProfile
]
static let batteryCellularProfileDefault = hd1080pMPVProfile.id
static let batteryNonCellularProfileDefault = hd1080pMPVProfile.id
static let chargingCellularProfileDefault = hd1080pMPVProfile.id
static let chargingNonCellularProfileDefault = hd1080pMPVProfile.id
enum QualityProfiles {
// tvOS-specific settings
enum tvOS {
static let qualityProfilesDefault = [
hd2160p60MPVProfile,
hd1080p60MPVProfile,
hd720p60MPVProfile,
hd720pAVPlayerProfile
]
static let batteryCellularProfileDefault = hd1080p60MPVProfile.id
static let batteryNonCellularProfileDefault = hd1080p60MPVProfile.id
static let chargingCellularProfileDefault = hd1080p60MPVProfile.id
static let chargingNonCellularProfileDefault = hd1080p60MPVProfile.id
}
// Access the correct profile based on device type
static var currentProfile: (qualityProfilesDefault: [QualityProfile], batteryCellularProfileDefault: String, batteryNonCellularProfileDefault: String, chargingCellularProfileDefault: String, chargingNonCellularProfileDefault: String) {
(
qualityProfilesDefault: tvOS.qualityProfilesDefault,
batteryCellularProfileDefault: tvOS.batteryCellularProfileDefault,
batteryNonCellularProfileDefault: tvOS.batteryNonCellularProfileDefault,
chargingCellularProfileDefault: tvOS.chargingCellularProfileDefault,
chargingNonCellularProfileDefault: tvOS.chargingNonCellularProfileDefault
)
}
}
#else
static let qualityProfilesDefault = [
hd2160pMPVProfile,
hd1080pMPVProfile,
hd720pMPVProfile,
hd720pAVPlayerProfile
]
static let batteryCellularProfileDefault = hd1080pMPVProfile.id
static let batteryNonCellularProfileDefault = hd1080pMPVProfile.id
static let chargingCellularProfileDefault = hd1080pMPVProfile.id
static let chargingNonCellularProfileDefault = hd1080pMPVProfile.id
enum QualityProfiles {
// macOS-specific settings
enum macOS {
static let qualityProfilesDefault = [
hd2160p60MPVProfile,
hd1080p60MPVProfile,
hd1080pMPVProfile,
hd720p60MPVProfile
]
static let batteryCellularProfileDefault = hd1080p60MPVProfile.id
static let batteryNonCellularProfileDefault = hd1080p60MPVProfile.id
static let chargingCellularProfileDefault = hd1080p60MPVProfile.id
static let chargingNonCellularProfileDefault = hd1080p60MPVProfile.id
}
// Access the correct profile for other platforms
static var currentProfile: (qualityProfilesDefault: [QualityProfile], batteryCellularProfileDefault: String, batteryNonCellularProfileDefault: String, chargingCellularProfileDefault: String, chargingNonCellularProfileDefault: String) {
(
qualityProfilesDefault: macOS.qualityProfilesDefault,
batteryCellularProfileDefault: macOS.batteryCellularProfileDefault,
batteryNonCellularProfileDefault: macOS.batteryNonCellularProfileDefault,
chargingCellularProfileDefault: macOS.chargingCellularProfileDefault,
chargingNonCellularProfileDefault: macOS.chargingNonCellularProfileDefault
)
}
}
#endif
static let batteryCellularProfile = Key<QualityProfile.ID>("batteryCellularProfile", default: batteryCellularProfileDefault)
static let batteryNonCellularProfile = Key<QualityProfile.ID>("batteryNonCellularProfile", default: batteryNonCellularProfileDefault)
static let chargingCellularProfile = Key<QualityProfile.ID>("chargingCellularProfile", default: chargingCellularProfileDefault)
static let chargingNonCellularProfile = Key<QualityProfile.ID>("chargingNonCellularProfile", default: chargingNonCellularProfileDefault)
static let forceAVPlayerForLiveStreams = Key<Bool>("forceAVPlayerForLiveStreams", default: true)
static let qualityProfiles = Key<[QualityProfile]>("qualityProfiles", default: qualityProfilesDefault)
static let batteryCellularProfile = Key<QualityProfile.ID>(
"batteryCellularProfile",
default: QualityProfiles.currentProfile.batteryCellularProfileDefault
)
static let batteryNonCellularProfile = Key<QualityProfile.ID>(
"batteryNonCellularProfile",
default: QualityProfiles.currentProfile.batteryNonCellularProfileDefault
)
static let chargingCellularProfile = Key<QualityProfile.ID>(
"chargingCellularProfile",
default: QualityProfiles.currentProfile.chargingCellularProfileDefault
)
static let chargingNonCellularProfile = Key<QualityProfile.ID>(
"chargingNonCellularProfile",
default: QualityProfiles.currentProfile.chargingNonCellularProfileDefault
)
static let forceAVPlayerForLiveStreams = Key<Bool>(
"forceAVPlayerForLiveStreams",
default: true
)
static let qualityProfiles = Key<[QualityProfile]>(
"qualityProfiles",
default: QualityProfiles.currentProfile.qualityProfilesDefault
)
// MARK: GROUP - History

View File

@@ -47,6 +47,7 @@ enum LanguageCodes: String, CaseIterable {
case Vietnamese = "vi"
case Xhosa = "xh"
case Chinese = "zh"
case Chinese_Hans = "zh-Hans"
case Zulu = "zu"
var description: String {
@@ -147,6 +148,8 @@ enum LanguageCodes: String, CaseIterable {
return "Xhosa"
case .Chinese:
return "Chinese"
case .Chinese_Hans:
return "Chinese (Simplified)"
case .Zulu:
return "Zulu"
}

View File

@@ -248,31 +248,33 @@ struct PlayerControls: View {
return [player.playerSize.height - inset, 500].min()!
}
@ViewBuilder var controlsBackground: some View {
ZStack {
if player.musicMode,
let url = controlsBackgroundURL
{
ThumbnailView(url: url)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(.opacity)
.animation(.default)
} else if player.videoForDisplay == nil {
Color.black
@ViewBuilder
var controlsBackground: some View {
GeometryReader { geometry in
ZStack {
if player.musicMode,
let video = player.videoForDisplay
{
let thumbnail = thumbnails.best(video)
if let url = thumbnail.url,
let quality = thumbnail.quality
{
let aspectRatio = (quality == .default || quality == .high) ? Constants.aspectRatio4x3 : Constants.aspectRatio16x9
ThumbnailView(url: url)
.aspectRatio(aspectRatio, contentMode: .fill)
.frame(width: geometry.size.width, height: geometry.size.height)
.transition(.opacity)
.animation(.default)
.clipped()
}
} else if player.videoForDisplay == nil {
Color.black
}
}
}
}
var controlsBackgroundURL: URL? {
if let video = player.videoForDisplay,
let url = thumbnails.best(video).url
{
return url
}
return nil
}
var timeline: some View {
TimelineView(context: .player).foregroundColor(.primary)
}
@@ -381,7 +383,7 @@ struct PlayerControls: View {
}
private var pipButton: some View {
button("PiP", systemImage: player.pipImage, action: player.togglePiPAction)
button("PiP", systemImage: player.pipImage, active: player.playingInPictureInPicture, action: player.togglePiPAction)
.disabled(!player.pipPossible)
}

View File

@@ -1,6 +1,6 @@
import GLKit
import Libmpv
import Logging
import MPVKit
import OpenGLES
final class MPVOGLView: GLKView {

View File

@@ -8,7 +8,7 @@ extension VideoPlayerView {
.updating($dragGestureOffset) { value, state, _ in
guard isVerticalDrag else { return }
var translation = value.translation
translation.height = max(0, translation.height)
translation.height = max(-translation.height, translation.height)
state = translation
}
#endif
@@ -18,7 +18,8 @@ extension VideoPlayerView {
.onChanged { value in
guard player.presentingPlayer,
!controlsOverlayModel.presenting,
dragGestureState else { return }
dragGestureState,
!disableToggleGesture else { return }
if player.controls.presentingControls, !player.musicMode {
player.controls.presentingControls = false
@@ -61,19 +62,22 @@ extension VideoPlayerView {
return
}
guard verticalDrag > 0 else { return }
viewDragOffset = verticalDrag
if verticalDrag > 60,
player.playingFullScreen
{
player.exitFullScreen(showControls: false)
#if os(iOS)
if Constants.isIPhone {
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
}
#endif
// Toggle fullscreen on upward drag only when not disabled
if verticalDrag < -50 {
if player.playingFullScreen {
player.exitFullScreen(showControls: false)
} else {
player.enterFullScreen()
}
disableGestureTemporarily()
return
}
// Ignore downward swipes when in fullscreen
guard verticalDrag > 0 && !player.playingFullScreen else {
return
}
viewDragOffset = verticalDrag
}
.onEnded { _ in
onPlayerDragGestureEnded()
@@ -86,16 +90,6 @@ extension VideoPlayerView {
player.seek.onSeekGestureEnd()
}
if viewDragOffset > 60,
player.playingFullScreen
{
#if os(iOS)
player.lockedOrientation = nil
#endif
player.exitFullScreen(showControls: false)
viewDragOffset = 0
return
}
isVerticalDrag = false
guard player.presentingPlayer,
@@ -117,4 +111,12 @@ extension VideoPlayerView {
}
}
}
// Function to temporarily disable the toggle gesture after a fullscreen change
private func disableGestureTemporarily() {
disableToggleGesture = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
disableToggleGesture = false
}
}
}

View File

@@ -86,6 +86,10 @@ struct VideoActions: View {
}
}
func isAnyActionVisible() -> Bool {
return Action.allCases.contains { isVisible($0) }
}
func isActionable(_ action: Action) -> Bool {
switch action {
case .share:
@@ -151,7 +155,7 @@ struct VideoActions: View {
case .fullScreen:
actionButton("Fullscreen", systemImage: player.fullscreenImage, action: player.toggleFullScreenAction)
case .pip:
actionButton("PiP", systemImage: player.pipImage, action: player.togglePiPAction)
actionButton("PiP", systemImage: player.pipImage, active: player.playingInPictureInPicture, action: player.togglePiPAction)
#if os(iOS)
case .lockOrientation:
actionButton("Lock", systemImage: player.lockOrientationImage, active: player.lockedOrientation != nil, action: player.lockOrientationAction)
@@ -198,10 +202,10 @@ struct VideoActions: View {
VStack(spacing: 3) {
Image(systemName: systemImage)
.frame(width: 20, height: 20)
.foregroundColor(active ? Color("AppRedColor") : .accentColor)
.foregroundColor(active ? Color("AppRedColor") : .primary)
if playerActionsButtonLabelStyle.text {
Text(name.localized())
.foregroundColor(active ? Color("AppRedColor") : .secondary)
.foregroundColor(active ? Color("AppRedColor") : .primary)
.font(.caption2)
.allowsTightening(true)
.lineLimit(1)

View File

@@ -235,15 +235,22 @@ struct VideoDetails: View {
)
#endif
// swiftlint:enable trailing_closure
VideoActions(video: player.videoForDisplay)
.padding(.vertical, 5)
.frame(maxHeight: 50)
.frame(maxWidth: .infinity)
.borderTop(height: 0.5, color: Color("ControlsBorderColor"))
.borderBottom(height: 0.5, color: Color("ControlsBorderColor"))
.animation(nil, value: player.currentItem)
.frame(minWidth: 0, maxWidth: .infinity)
if VideoActions().isAnyActionVisible() {
VideoActions(video: player.videoForDisplay)
.padding(.vertical, 5)
.frame(maxHeight: 50)
.frame(maxWidth: .infinity)
.borderTop(height: 0.5, color: Color("ControlsBorderColor"))
.borderBottom(height: 0.5, color: Color("ControlsBorderColor"))
.animation(nil, value: player.currentItem)
.frame(minWidth: 0, maxWidth: .infinity)
} else {
Rectangle()
.fill(Color.clear)
.frame(height: 0.5)
.frame(maxWidth: .infinity)
.background(Color("ControlsBorderColor"))
}
ScrollViewReader { proxy in
pageView

View File

@@ -47,11 +47,18 @@ struct VideoPlayerView: View {
#if !os(tvOS)
@GestureState var dragGestureState = false
@GestureState var dragGestureOffset = CGSize.zero
@State var isHorizontalDrag = false // swiftlint:disable:this swiftui_state_private
@State var isVerticalDrag = false // swiftlint:disable:this swiftui_state_private
@State var viewDragOffset = Self.hiddenOffset // swiftlint:disable:this swiftui_state_private
// swiftlint:disable private_swiftui_state
@State var isHorizontalDrag = false
@State var isVerticalDrag = false
@State var viewDragOffset = Self.hiddenOffset
// swiftlint:enable private_swiftui_state
#endif
// swiftlint:disable private_swiftui_state
@State var disableToggleGesture = false
// swiftlint:enable private_swiftui_state
@ObservedObject var player = PlayerModel.shared // swiftlint:disable:this swiftui_state_private
#if os(macOS)

View File

@@ -86,6 +86,7 @@ struct InstanceForm: View {
.autocapitalization(.none)
.keyboardType(.URL)
#endif
.disableAutocorrection(true)
#if os(tvOS)
VStack {

View File

@@ -329,8 +329,8 @@ struct PlayerSettings: View {
private var rotateToLandscapeOnEnterFullScreenPicker: some View {
Picker("Rotate when entering fullscreen on landscape video", selection: $rotateToLandscapeOnEnterFullScreen) {
Text("Landscape left").tag(FullScreenRotationSetting.landscapeRight)
Text("Landscape right").tag(FullScreenRotationSetting.landscapeLeft)
Text("Landscape left").tag(FullScreenRotationSetting.landscapeLeft)
Text("Landscape right").tag(FullScreenRotationSetting.landscapeRight)
Text("No rotation").tag(FullScreenRotationSetting.disabled)
}
.modifier(SettingsPickerModifier())

View File

@@ -301,7 +301,7 @@ struct QualityProfileForm: View {
func isFormatDisabled(_ format: QualityProfile.Format) -> Bool {
guard backend == .appleAVPlayer else { return false }
let avPlayerFormats = [QualityProfile.Format.hls, .stream, .mp4]
let avPlayerFormats = [.stream, QualityProfile.Format.hls]
return !avPlayerFormats.contains(format)
}

View File

@@ -24,14 +24,42 @@ struct VideoContextMenuView: View {
private var backgroundContext = PersistenceController.shared.container.newBackgroundContext()
@State private var isOverlayVisible = false
init(video: Video) {
self.video = video
_watchRequest = video.watchFetchRequest
}
var body: some View {
if video.videoID != Video.fixtureID {
contextMenu
ZStack {
// Conditional overlay to block taps on underlying views
if isOverlayVisible {
Color.clear
.contentShape(Rectangle())
#if !os(tvOS)
// This is not available on tvOS < 16 so we leave out.
// TODO: remove #if when setting the minimum deployment target to >= 16
.onTapGesture {
// Dismiss overlay without triggering other interactions
isOverlayVisible = false
}
#endif
.ignoresSafeArea() // Ensure overlay covers the entire screen
.accessibilityLabel("Dismiss context menu")
.accessibilityHint("Tap to close the context")
.accessibilityAddTraits(.isButton)
}
if video.videoID != Video.fixtureID {
contextMenu
.onAppear {
isOverlayVisible = true
}
.onDisappear {
isOverlayVisible = false
}
}
}
}

View File

@@ -619,9 +619,6 @@
3797104928D3D10600D5F53C /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3797104828D3D10600D5F53C /* SDWebImageSwiftUI */; };
3797104B28D3D18800D5F53C /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3797104A28D3D18800D5F53C /* SDWebImageSwiftUI */; };
3797104D28D3D19100D5F53C /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3797104C28D3D19100D5F53C /* SDWebImageSwiftUI */; };
3797665B2C79FA6900C10DBD /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3797665A2C79FA6900C10DBD /* MPVKit */; };
3797665D2C79FA7500C10DBD /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3797665C2C79FA7500C10DBD /* MPVKit */; };
3797665F2C79FA7D00C10DBD /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3797665E2C79FA7D00C10DBD /* MPVKit */; };
3797757D268922D100DD52A8 /* Siesta in Frameworks */ = {isa = PBXBuildFile; productRef = 3797757C268922D100DD52A8 /* Siesta */; };
37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37977582268922F600DD52A8 /* InvidiousAPI.swift */; };
37977584268922F600DD52A8 /* InvidiousAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37977582268922F600DD52A8 /* InvidiousAPI.swift */; };
@@ -1080,6 +1077,9 @@
E258F38A2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
E258F38B2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
E258F38C2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
E265D0C22C7D217000D2BB8E /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = E265D0C12C7D217000D2BB8E /* MPVKit */; };
E265D0C42C7D218A00D2BB8E /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = E265D0C32C7D218A00D2BB8E /* MPVKit */; };
E265D0C62C7D21A300D2BB8E /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = E265D0C52C7D21A300D2BB8E /* MPVKit */; };
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 */; };
@@ -1599,7 +1599,7 @@
3797104928D3D10600D5F53C /* SDWebImageSwiftUI in Frameworks */,
37BD07B92698AB2E003EBB87 /* Siesta in Frameworks */,
37FB284D2722099E00A57617 /* SDWebImageWebPCoder in Frameworks */,
3797665B2C79FA6900C10DBD /* MPVKit in Frameworks */,
E265D0C22C7D217000D2BB8E /* MPVKit in Frameworks */,
37CF8B8428535E4F00B71E37 /* SDWebImage in Frameworks */,
37C7367A2AC33010007630E1 /* SwiftUIIntrospect in Frameworks */,
);
@@ -1624,7 +1624,7 @@
375B8AB728B583BD00397B31 /* KeychainAccess in Frameworks */,
3703205E27D2BB12007A0CB8 /* SDWebImageWebPCoder in Frameworks */,
37CF8B8628535E5A00B71E37 /* SDWebImage in Frameworks */,
3797665D2C79FA7500C10DBD /* MPVKit in Frameworks */,
E265D0C42C7D218A00D2BB8E /* MPVKit in Frameworks */,
3703205C27D2BAF3007A0CB8 /* SwiftyJSON in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1670,7 +1670,7 @@
372915E42687E33E00F5A35B /* Defaults in Frameworks */,
3772003B27E8EEC800CB2475 /* libbz2.tbd in Frameworks */,
37BADCA9269A570B009BE4FB /* Alamofire in Frameworks */,
3797665F2C79FA7D00C10DBD /* MPVKit in Frameworks */,
E265D0C62C7D21A300D2BB8E /* MPVKit in Frameworks */,
37D4B19D2671817900C925CA /* SwiftyJSON in Frameworks */,
3797757D268922D100DD52A8 /* Siesta in Frameworks */,
);
@@ -2585,7 +2585,7 @@
371AC0AB294D1A490085989E /* CachedAsyncImage */,
379325D429A265A300181CF1 /* Logging */,
37C736792AC33010007630E1 /* SwiftUIIntrospect */,
3797665A2C79FA6900C10DBD /* MPVKit */,
E265D0C12C7D217000D2BB8E /* MPVKit */,
);
productName = "Yattee (iOS)";
productReference = 37D4B0C92671614900C925CA /* Yattee.app */;
@@ -2624,7 +2624,7 @@
371AC0B1294D1C230085989E /* CachedAsyncImage */,
379325D629A265AE00181CF1 /* Logging */,
37C736772AC32B28007630E1 /* SwiftUIIntrospect */,
3797665C2C79FA7500C10DBD /* MPVKit */,
E265D0C32C7D218A00D2BB8E /* MPVKit */,
);
productName = "Yattee (macOS)";
productReference = 37D4B0CF2671614900C925CA /* Yattee.app */;
@@ -2702,7 +2702,7 @@
377F9F75294403880043F856 /* Cache */,
371AC0B3294D1C290085989E /* CachedAsyncImage */,
379325D829A265B500181CF1 /* Logging */,
3797665E2C79FA7D00C10DBD /* MPVKit */,
E265D0C52C7D21A300D2BB8E /* MPVKit */,
);
productName = Yattee;
productReference = 37D4B158267164AE00C925CA /* Yattee.app */;
@@ -2822,7 +2822,7 @@
374D11E52943C56300CB4350 /* XCRemoteSwiftPackageReference "Cache" */,
371AC0AA294D1A490085989E /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */,
379325D329A265A300181CF1 /* XCRemoteSwiftPackageReference "swift-log" */,
379766592C79FA6900C10DBD /* XCRemoteSwiftPackageReference "MPVKit" */,
E265D0C02C7D217000D2BB8E /* XCRemoteSwiftPackageReference "MPVKit" */,
);
productRefGroup = 37D4B0CA2671614900C925CA /* Products */;
projectDirPath = "";
@@ -4103,7 +4103,7 @@
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Open in Yattee/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Open in Yattee";
@@ -4134,7 +4134,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Open in Yattee/Info.plist";
@@ -4165,7 +4165,7 @@
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 11.0;
@@ -4185,7 +4185,7 @@
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 11.0;
@@ -4326,6 +4326,7 @@
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 3;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
@@ -4348,7 +4349,7 @@
CODE_SIGN_ENTITLEMENTS = "iOS/Yattee (iOS).entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
@@ -4400,7 +4401,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1";
@@ -4452,7 +4453,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
DEAD_CODE_STRIPPING = YES;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
@@ -4491,7 +4492,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
DEAD_CODE_STRIPPING = YES;
"DEVELOPMENT_TEAM[sdk=macosx*]" = 78Z5H3M6RJ;
ENABLE_APP_SANDBOX = YES;
@@ -4525,7 +4526,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4548,7 +4549,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4573,7 +4574,7 @@
buildSettings = {
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4597,7 +4598,7 @@
buildSettings = {
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4623,7 +4624,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
DEVELOPMENT_ASSET_PATHS = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -4663,7 +4664,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
DEVELOPMENT_ASSET_PATHS = "";
"DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES;
@@ -4703,7 +4704,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -4726,7 +4727,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 194;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -4960,14 +4961,6 @@
minimumVersion = 2.1.0;
};
};
379766592C79FA6900C10DBD /* XCRemoteSwiftPackageReference "MPVKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/cxfksword/MPVKit";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.38.0;
};
};
3797757B268922D100DD52A8 /* XCRemoteSwiftPackageReference "siesta" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/bustoutsolutions/siesta";
@@ -5040,6 +5033,14 @@
minimumVersion = 0.3.0;
};
};
E265D0C02C7D217000D2BB8E /* XCRemoteSwiftPackageReference "MPVKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mpvkit/MPVKit.git";
requirement = {
kind = exactVersion;
version = "0.38.0-fix";
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@@ -5228,21 +5229,6 @@
package = 3797104728D3D10600D5F53C /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */;
productName = SDWebImageSwiftUI;
};
3797665A2C79FA6900C10DBD /* MPVKit */ = {
isa = XCSwiftPackageProductDependency;
package = 379766592C79FA6900C10DBD /* XCRemoteSwiftPackageReference "MPVKit" */;
productName = MPVKit;
};
3797665C2C79FA7500C10DBD /* MPVKit */ = {
isa = XCSwiftPackageProductDependency;
package = 379766592C79FA6900C10DBD /* XCRemoteSwiftPackageReference "MPVKit" */;
productName = MPVKit;
};
3797665E2C79FA7D00C10DBD /* MPVKit */ = {
isa = XCSwiftPackageProductDependency;
package = 379766592C79FA6900C10DBD /* XCRemoteSwiftPackageReference "MPVKit" */;
productName = MPVKit;
};
3797757C268922D100DD52A8 /* Siesta */ = {
isa = XCSwiftPackageProductDependency;
package = 3797757B268922D100DD52A8 /* XCRemoteSwiftPackageReference "siesta" */;
@@ -5328,6 +5314,21 @@
package = 37FB285227220D8400A57617 /* XCRemoteSwiftPackageReference "SDWebImagePINPlugin" */;
productName = SDWebImagePINPlugin;
};
E265D0C12C7D217000D2BB8E /* MPVKit */ = {
isa = XCSwiftPackageProductDependency;
package = E265D0C02C7D217000D2BB8E /* XCRemoteSwiftPackageReference "MPVKit" */;
productName = MPVKit;
};
E265D0C32C7D218A00D2BB8E /* MPVKit */ = {
isa = XCSwiftPackageProductDependency;
package = E265D0C02C7D217000D2BB8E /* XCRemoteSwiftPackageReference "MPVKit" */;
productName = MPVKit;
};
E265D0C52C7D21A300D2BB8E /* MPVKit */ = {
isa = XCSwiftPackageProductDependency;
package = E265D0C02C7D217000D2BB8E /* XCRemoteSwiftPackageReference "MPVKit" */;
productName = MPVKit;
};
/* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */

View File

@@ -1,5 +1,5 @@
{
"originHash" : "1f99971d9d21cffe56d0033bc5c38e9fcd5ff46ca5f7d19c76f5ba0a268ce4b6",
"originHash" : "515d8e68c4a31658288fb3f94789ee539399b042082c08c39f4c03c27fd8860c",
"pins" : [
{
"identity" : "activelabel.swift",
@@ -25,7 +25,7 @@
"location" : "https://github.com/hyperoslo/Cache.git",
"state" : {
"branch" : "master",
"revision" : "d2e8f5a53c601b43371fdc90277d7f64b0e89a25"
"revision" : "81a0277cbc6b63f4e0cd6f42c4abefa1011bbfa9"
}
},
{
@@ -58,10 +58,10 @@
{
"identity" : "mpvkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/cxfksword/MPVKit",
"location" : "https://github.com/mpvkit/MPVKit.git",
"state" : {
"revision" : "f646e4b625e9c8a2ff22a7e0bb5557306300be5d",
"version" : "0.38.0"
"revision" : "ee72059235566df8b455bff15e3f83a1c9053e78",
"version" : "0.38.0-fix"
}
},
{

View File

@@ -66,6 +66,11 @@
value = "Yes"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "IDELogRedirectionPolicy"
value = "oslogToStdio"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction

View File

@@ -1,9 +1,12 @@
import AVFoundation
import Foundation
import Logging
import UIKit
final class AppDelegate: UIResponder, UIApplicationDelegate {
var orientationLock = UIInterfaceOrientationMask.all
private var logger = Logger(label: "stream.yattee.app.delegalate")
private(set) static var instance: AppDelegate!
func application(_: UIApplication, supportedInterfaceOrientationsFor _: UIWindow?) -> UIInterfaceOrientationMask {
@@ -12,11 +15,22 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { // swiftlint:disable:this discouraged_optional_collection
Self.instance = self
#if os(iOS)
UIViewController.swizzleHomeIndicatorProperty()
#if !os(macOS)
UIViewController.swizzleHomeIndicatorProperty()
OrientationTracker.shared.startDeviceOrientationTracking()
// Configure the audio session for playback
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
} catch {
logger.error("Failed to set audio session category: \(error)")
}
// Begin receiving remote control events
UIApplication.shared.beginReceivingRemoteControlEvents()
#endif
return true
}

View File

@@ -1,5 +1,5 @@
import Cocoa
import MPVKit
import Libmpv
import OpenGL.GL
import OpenGL.GL3