Compare commits

..

32 Commits

Author SHA1 Message Date
Arkadiusz Fal
af75afa912 Bump build number to 187 2024-07-06 13:51:40 +02:00
Arkadiusz Fal
f32bcae5eb Update CHANGELOG 2024-07-06 13:51:04 +02:00
Arkadiusz Fal
c55161b6b6 Fix typo 2024-07-06 13:44:40 +02:00
Arkadiusz Fal
3fd99f464a Revert change to OSD positions 2024-07-06 13:44:40 +02:00
Arkadiusz Fal
43c8484514 Allow import of accounts to manually added (not imported) instances 2024-07-06 13:44:40 +02:00
Arkadiusz Fal
7755b392b7 Fix seek OSD layout on tvOS
Fix #707
2024-07-06 12:51:07 +02:00
Arkadiusz Fal
e0998638b1 Fix macOS settings windows
Fix #699
Fix #710
2024-07-06 12:45:44 +02:00
Arkadiusz Fal
511a528eb6 Add import export of missing settings 2024-07-06 12:32:31 +02:00
Arkadiusz Fal
89dfbdb5c7 Fix swiftformat offenses 2024-07-06 11:48:49 +02:00
Arkadiusz Fal
f88a1d17d6 Update dependecies 2024-07-06 11:45:15 +02:00
Toni Förster
9e05909659 fix duplicated share button
fixes #691

add `--initial-audio-sync=<yes|no>` to MPV settings. Might fix #690 but needs to be toggled by the user. We leave the standard settings.

Also added links to the icons on macOS.
2024-07-06 11:35:21 +02:00
Arkadiusz Fal
b966f4509a Merge pull request #704 from patelhiren/subscriptions-account-picker
tvOS: Allow account picker by long pressing channels button in subscriptions view
2024-07-06 11:35:15 +02:00
Arkadiusz Fal
dc7073dcb5 Merge pull request #701 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-07-06 11:28:49 +02:00
Hyunjae Chung
a258ee3be4 Translated using Weblate (Korean)
Currently translated at 11.2% (63 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ko/
2024-06-19 15:09:16 +02:00
Hiren Patel
b626f50adc - tvOS: Allow account picker by long pressing channels button in subscription view. 2024-06-16 23:21:49 -04:00
Pieter Janssens
78dc47dc24 Translated using Weblate (Dutch)
Currently translated at 80.7% (454 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/nl/
2024-06-14 19:09:21 +00:00
Arkadiusz Fal
46725bf4d9 Bump build number to 186 2024-06-13 19:12:19 +02:00
Arkadiusz Fal
8697ec8faf Update packages 2024-06-13 19:12:19 +02:00
Arkadiusz Fal
8a015d29c3 Update CHANGELOG 2024-06-13 19:12:19 +02:00
Arkadiusz Fal
4097d11b5e Fix build issues 2024-06-13 19:12:19 +02:00
Arkadiusz Fal
5323d53f9e Fix swiftformat offenses 2024-06-13 19:05:09 +02:00
Arkadiusz Fal
e3e0c4a92f Remove duplicated button 2024-06-13 19:04:46 +02:00
Arkadiusz Fal
9e5efc1aa6 Merge pull request #697 from patelhiren/main
tvOS: Refined Subscriptions View
2024-06-13 19:00:15 +02:00
Arkadiusz Fal
1ed4c20c3a Merge pull request #696 from stonerl/improved-conditional-proxying
improved conditional proxying
2024-06-13 18:59:42 +02:00
Arkadiusz Fal
ced9eb28d7 Merge pull request #695 from stonerl/more-async-work
more responsive UI when Favorites are used.
2024-06-13 18:59:30 +02:00
Arkadiusz Fal
ea49758ed2 Merge pull request #694 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2024-06-13 18:58:58 +02:00
Hiren Patel
65ec675859 - Improved Subscription View for tvOS
- Refined Subscriptions view on tvOS

- Refined Subscriptions view on tvOS

- Refined Subscriptions view on tvOS

- Refined Subscriptions view on tvOS
2024-05-31 19:06:15 -04:00
Hyunjae Chung
9a650799d3 Translated using Weblate (Korean)
Currently translated at 6.4% (36 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ko/
2024-05-25 08:09:13 +02:00
Ophiushi
ddd1f243f7 Translated using Weblate (French)
Currently translated at 100.0% (562 of 562 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2024-05-25 08:09:12 +02:00
Toni Förster
94f19d55c8 more responsive UI when Favorites are used.
- The reloading of items in the favorite widgets is now done async, so it does not block the UI when opening Home.
- Tasks that check for changes of the widget and favorites are now terminated when another view e.g. Subscriptions is opened.
- We only update the Favourites when the player is not in the foreground, to avoid unresponsiveness.
2024-05-24 19:46:42 +02:00
Toni Förster
30cdaf88e1 improved conditional proxying
We don't test every single stream anymore. If we get an 200, 206 or 403 we immediately stop testing streams. Unknown streams are also skipped. This speeds up starting playback, since we don'T have to wait for the network anymore.
2024-05-24 18:44:41 +02:00
Hyunjae Chung
8139bba31e Added translation using Weblate (Korean) 2024-05-24 07:37:13 +02:00
34 changed files with 986 additions and 274 deletions

View File

@@ -1,12 +1,10 @@
## Build 185
* 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
* Translations update from Hosted Weblate by @weblate in https://github.com/yattee/yattee/pull/683
## Build 187
* Allow import of accounts to manually added (not imported) instances
* Add import export of missing settings
* tvOS: Allow account picker by long pressing channels button in subscriptions view by @patelhiren in https://github.com/yattee/yattee/pull/704
* macOS: Fix settings windows layout
* Fix seek OSD layout on tvOS, revert OSD position
* Translations update from Hosted Weblate by @weblate in https://github.com/yattee/yattee/pull/694
## Previous builds
* Add skip, play/pause, and fullscreen shortcuts to macOS player (by @rickykresslein)
@@ -21,6 +19,9 @@
* 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)
* 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
@@ -44,6 +45,13 @@
* 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)

View File

@@ -1,6 +1,6 @@
source "https://rubygems.org"
gem 'fastlane', git: 'https://github.com/nekrich/fastlane.git', branch: 'fix/match-tvos-devices-fetch'
gem 'fastlane'
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)

View File

@@ -1,50 +1,3 @@
GIT
remote: https://github.com/nekrich/fastlane.git
revision: d2d51a9af37f9b04a157e78fd25d147cecc89980
branch: fix/match-tvos-devices-fetch
specs:
fastlane (2.219.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored (~> 1.2)
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-env (>= 1.6.0, < 2.0.0)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
http-cookie (~> 1.0.5)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (>= 0.1.1, < 1.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (~> 3)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
GEM
remote: https://rubygems.org/
specs:
@@ -52,24 +5,24 @@ GEM
base64
nkf
rexml
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.933.0)
aws-sdk-core (3.196.1)
aws-partitions (1.952.0)
aws-sdk-core (3.201.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.82.0)
aws-sdk-core (~> 3, >= 3.193.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.151.0)
aws-sdk-core (~> 3, >= 3.194.0)
aws-sdk-kms (1.88.0)
aws-sdk-core (~> 3, >= 3.201.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.156.0)
aws-sdk-core (~> 3, >= 3.201.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.8.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
@@ -115,6 +68,47 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.3.1)
fastlane (2.221.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored (~> 1.2)
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-env (>= 1.6.0, < 2.0.0)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
http-cookie (~> 1.0.5)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (>= 0.1.1, < 1.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.5)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (~> 3)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
@@ -153,14 +147,14 @@ GEM
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.5)
http-cookie (1.0.6)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.7.2)
jwt (2.8.1)
jwt (2.8.2)
base64
mini_magick (4.12.0)
mini_magick (4.13.1)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.4.1)
@@ -170,19 +164,19 @@ GEM
optparse (0.5.0)
os (1.1.4)
plist (3.7.1)
public_suffix (5.0.5)
public_suffix (6.0.0)
rake (13.2.1)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.8)
strscan (>= 3.0.9)
rexml (3.2.9)
strscan
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
security (0.1.5)
signet (0.19.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
@@ -222,7 +216,7 @@ PLATFORMS
x86_64-linux
DEPENDENCIES
fastlane!
fastlane
BUNDLED WITH
2.3.6

View File

@@ -415,6 +415,7 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
if let channel = extractChannel(from: content) {
return ContentItem(channel: channel)
}
default:
return nil
}

View File

@@ -9,7 +9,11 @@ final class AdvancedSettingsGroupExporter: SettingsGroupExporter {
"mpvEnableLogging": Defaults[.mpvEnableLogging],
"mpvCacheSecs": Defaults[.mpvCacheSecs],
"mpvCachePauseWait": Defaults[.mpvCachePauseWait],
"mpvCachePauseInital": Defaults[.mpvCachePauseInital],
"mpvDeinterlace": Defaults[.mpvDeinterlace],
"mpvHWdec": Defaults[.mpvHWdec],
"mpvDemuxerLavfProbeInfo": Defaults[.mpvDemuxerLavfProbeInfo],
"mpvInitialAudioSync": Defaults[.mpvInitialAudioSync],
"showCacheStatus": Defaults[.showCacheStatus],
"feedCacheSize": Defaults[.feedCacheSize]
]

View File

@@ -6,6 +6,9 @@ final class HistorySettingsGroupExporter: SettingsGroupExporter {
[
"saveRecents": Defaults[.saveRecents],
"saveHistory": Defaults[.saveHistory],
"showRecents": Defaults[.showRecents],
"limitRecents": Defaults[.limitRecents],
"limitRecentsAmount": Defaults[.limitRecentsAmount],
"showWatchingProgress": Defaults[.showWatchingProgress],
"saveLastPlayed": Defaults[.saveLastPlayed],
@@ -15,11 +18,7 @@ final class HistorySettingsGroupExporter: SettingsGroupExporter {
"watchedVideoStyle": Defaults[.watchedVideoStyle].rawValue,
"watchedVideoBadgeColor": Defaults[.watchedVideoBadgeColor].rawValue,
"showToggleWatchedStatusButton": Defaults[.showToggleWatchedStatusButton],
"showRecents": Defaults[.showRecents],
"limitRecents": Defaults[.limitRecents],
"limitRecentsAmount": Defaults[.limitRecentsAmount]
"showToggleWatchedStatusButton": Defaults[.showToggleWatchedStatusButton]
]
}
}

View File

@@ -10,6 +10,8 @@ final class PlayerSettingsGroupExporter: SettingsGroupExporter {
"expandVideoDescription": Defaults[.expandVideoDescription],
"collapsedLinesDescription": Defaults[.collapsedLinesDescription],
"showChapters": Defaults[.showChapters],
"showChapterThumbnails": Defaults[.showChapterThumbnails],
"showChapterThumbnailsOnlyWhenDifferent": Defaults[.showChapterThumbnailsOnlyWhenDifferent],
"expandChapters": Defaults[.expandChapters],
"showRelated": Defaults[.showRelated],
"showInspector": Defaults[.showInspector].rawValue,
@@ -18,7 +20,12 @@ final class PlayerSettingsGroupExporter: SettingsGroupExporter {
"enableReturnYouTubeDislike": Defaults[.enableReturnYouTubeDislike],
"closePiPOnNavigation": Defaults[.closePiPOnNavigation],
"closePiPOnOpeningPlayer": Defaults[.closePiPOnOpeningPlayer],
"closePlayerOnOpeningPiP": Defaults[.closePlayerOnOpeningPiP]
"closePlayerOnOpeningPiP": Defaults[.closePlayerOnOpeningPiP],
"captionsAutoShow": Defaults[.captionsAutoShow],
"captionsDefaultLanguageCode": Defaults[.captionsDefaultLanguageCode],
"captionsFallbackLanguageCode": Defaults[.captionsFallbackLanguageCode],
"captionsFontScaleSize": Defaults[.captionsFontScaleSize],
"captionsFontColor": Defaults[.captionsFontColor]
]
}

View File

@@ -5,7 +5,11 @@ final class SponsorBlockSettingsGroupExporter: SettingsGroupExporter {
override var globalJSON: JSON {
[
"sponsorBlockInstance": Defaults[.sponsorBlockInstance],
"sponsorBlockCategories": Array(Defaults[.sponsorBlockCategories])
"sponsorBlockCategories": Array(Defaults[.sponsorBlockCategories]),
"sponsorBlockColors": Defaults[.sponsorBlockColors],
"sponsorBlockShowTimeWithSkipsRemoved": Defaults[.sponsorBlockShowTimeWithSkipsRemoved],
"sponsorBlockShowCategoriesInTimeline": Defaults[.sponsorBlockShowCategoriesInTimeline],
"sponsorBlockShowNoticeAfterSkip": Defaults[.sponsorBlockShowNoticeAfterSkip]
]
}
}

View File

@@ -25,10 +25,26 @@ struct AdvancedSettingsGroupImporter {
Defaults[.mpvCachePauseWait] = mpvCachePauseWait
}
if let mpvCachePauseInital = json["mpvCachePauseInital"].bool {
Defaults[.mpvCachePauseInital] = mpvCachePauseInital
}
if let mpvDeinterlace = json["mpvDeinterlace"].bool {
Defaults[.mpvDeinterlace] = mpvDeinterlace
}
if let mpvHWdec = json["mpvHWdec"].string {
Defaults[.mpvHWdec] = mpvHWdec
}
if let mpvDemuxerLavfProbeInfo = json["mpvDemuxerLavfProbeInfo"].string {
Defaults[.mpvDemuxerLavfProbeInfo] = mpvDemuxerLavfProbeInfo
}
if let mpvInitialAudioSync = json["mpvInitialAudioSync"].bool {
Defaults[.mpvInitialAudioSync] = mpvInitialAudioSync
}
if let showCacheStatus = json["showCacheStatus"].bool {
Defaults[.showCacheStatus] = showCacheStatus
}

View File

@@ -13,6 +13,18 @@ struct HistorySettingsGroupImporter {
Defaults[.saveHistory] = saveHistory
}
if let showRecents = json["showRecents"].bool {
Defaults[.showRecents] = showRecents
}
if let limitRecents = json["limitRecents"].bool {
Defaults[.limitRecents] = limitRecents
}
if let limitRecentsAmount = json["limitRecentsAmount"].int {
Defaults[.limitRecentsAmount] = limitRecentsAmount
}
if let showWatchingProgress = json["showWatchingProgress"].bool {
Defaults[.showWatchingProgress] = showWatchingProgress
}
@@ -50,17 +62,5 @@ struct HistorySettingsGroupImporter {
if let showToggleWatchedStatusButton = json["showToggleWatchedStatusButton"].bool {
Defaults[.showToggleWatchedStatusButton] = showToggleWatchedStatusButton
}
if let showRecents = json["showRecents"].bool {
Defaults[.showRecents] = showRecents
}
if let limitRecents = json["limitRecents"].bool {
Defaults[.limitRecents] = limitRecents
}
if let limitRecentsAmount = json["limitRecentsAmount"].int {
Defaults[.limitRecentsAmount] = limitRecentsAmount
}
}
}

View File

@@ -68,7 +68,7 @@ struct LocationsSettingsGroupImporter {
if let password,
!password.isEmpty,
let instanceID = account.instanceID,
let instance = InstancesModel.shared.find(instanceID)
let instance = InstancesModel.shared.find(instanceID) ?? InstancesModel.shared.findByURLString(account.urlString)
{
if !instance.accounts.contains(where: { instanceAccount in
let (username, _) = instanceAccount.credentials

View File

@@ -29,6 +29,14 @@ struct PlayerSettingsGroupImporter {
Defaults[.showChapters] = showChapters
}
if let showChapterThumbnails = json["showChapterThumbnails"].bool {
Defaults[.showChapterThumbnails] = showChapterThumbnails
}
if let showChapterThumbnailsOnlyWhenDifferent = json["showChapterThumbnailsOnlyWhenDifferent"].bool {
Defaults[.showChapterThumbnailsOnlyWhenDifferent] = showChapterThumbnailsOnlyWhenDifferent
}
if let expandChapters = json["expandChapters"].bool {
Defaults[.expandChapters] = expandChapters
}
@@ -96,5 +104,25 @@ struct PlayerSettingsGroupImporter {
Defaults[.rotateToLandscapeOnEnterFullScreen] = rotateToLandscapeOnEnterFullScreen
}
#endif
if let captionsAutoShow = json["captionsAutoShow"].bool {
Defaults[.captionsAutoShow] = captionsAutoShow
}
if let captionsDefaultLanguageCode = json["captionsDefaultLanguageCode"].string {
Defaults[.captionsDefaultLanguageCode] = captionsDefaultLanguageCode
}
if let captionsFallbackLanguageCode = json["captionsFallbackLanguageCode"].string {
Defaults[.captionsFallbackLanguageCode] = captionsFallbackLanguageCode
}
if let captionsFontScaleSize = json["captionsFontScaleSize"].string {
Defaults[.captionsFontScaleSize] = captionsFontScaleSize
}
if let captionsFontColor = json["captionsFontColor"].string {
Defaults[.captionsFontColor] = captionsFontColor
}
}
}

View File

@@ -12,5 +12,22 @@ struct SponsorBlockSettingsGroupImporter {
if let sponsorBlockCategories = json["sponsorBlockCategories"].array {
Defaults[.sponsorBlockCategories] = Set(sponsorBlockCategories.compactMap { $0.string })
}
if let sponsorBlockColors = json["sponsorBlockColors"].dictionary {
let colors = sponsorBlockColors.mapValues { json in json.stringValue }
Defaults[.sponsorBlockColors] = colors
}
if let sponsorBlockShowTimeWithSkipsRemoved = json["sponsorBlockShowTimeWithSkipsRemoved"].bool {
Defaults[.sponsorBlockShowTimeWithSkipsRemoved] = sponsorBlockShowTimeWithSkipsRemoved
}
if let sponsorBlockShowCategoriesInTimeline = json["sponsorBlockShowCategoriesInTimeline"].bool {
Defaults[.sponsorBlockShowCategoriesInTimeline] = sponsorBlockShowCategoriesInTimeline
}
if let sponsorBlockShowNoticeAfterSkip = json["sponsorBlockShowNoticeAfterSkip"].bool {
Defaults[.sponsorBlockShowNoticeAfterSkip] = sponsorBlockShowNoticeAfterSkip
}
}
}

View File

@@ -542,6 +542,7 @@ final class AVPlayerBackend: PlayerBackend {
}
}
}
case .failed:
DispatchQueue.main.async {
self.model.playerError = item.error

View File

@@ -72,6 +72,7 @@ final class MPVClient: ObservableObject {
checkError(mpv_set_option_string(mpv, "sub-scale", Defaults[.captionsFontScaleSize]))
checkError(mpv_set_option_string(mpv, "sub-color", Defaults[.captionsFontColor]))
checkError(mpv_set_option_string(mpv, "user-agent", UserAgentManager.shared.userAgent))
checkError(mpv_set_option_string(mpv, "initial-audio-sync", Defaults[.mpvInitialAudioSync] ? "yes" : "no"))
// GPU //

View File

@@ -56,82 +56,101 @@ extension PlayerModel {
}
}
func streamsWithInstance(instance _: Instance, streams: [Stream], completion: @escaping ([Stream]) -> Void) {
func streamsWithInstance(instance: Instance, streams: [Stream], completion: @escaping ([Stream]) -> Void) {
// Queue for stream processing
let streamProcessingQueue = DispatchQueue(label: "stream.yattee.streamProcessing.Queue", qos: .userInitiated)
let streamProcessingQueue = DispatchQueue(label: "stream.yattee.streamProcessing.Queue")
// Queue for accessing the processedStreams array
let processedStreamsQueue = DispatchQueue(label: "stream.yattee.processedStreams.Queue")
// DispatchGroup for managing multiple tasks
let streamProcessingGroup = DispatchGroup()
var processedStreams = [Stream]()
let instance = instance
var hasForbiddenAsset = false
var hasAllowedAsset = false
for stream in streams {
streamProcessingQueue.async(group: streamProcessingGroup) {
let forbiddenAssetTestGroup = DispatchGroup()
var hasForbiddenAsset = false
if !hasAllowedAsset, !hasForbiddenAsset, !instance.proxiesVideos, stream.format != Stream.Format.unknown {
let (nonHLSAssets, hlsURLs) = self.getAssets(from: [stream])
if let firstStream = nonHLSAssets.first {
let asset = firstStream.0
let url = firstStream.1
let requestRange = firstStream.2
let (nonHLSAssets, hlsURLs) = self.getAssets(from: [stream])
if let randomStream = nonHLSAssets.randomElement() {
let instance = randomStream.0
let asset = randomStream.1
let url = randomStream.2
let requestRange = randomStream.3
// swiftlint:disable:next shorthand_optional_binding
if let asset = asset, let instance = instance, !instance.proxiesVideos {
if instance.app == .invidious {
self.testAsset(url: url, range: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
hasForbiddenAsset = isForbidden
self.testAsset(url: url, range: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { status in
switch status {
case HTTPStatus.Forbidden:
hasForbiddenAsset = true
case HTTPStatus.PartialContent:
hasAllowedAsset = true
case HTTPStatus.OK:
hasAllowedAsset = true
default:
break
}
}
} else if instance.app == .piped {
self.testPipedAssets(asset: asset, requestRange: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
hasForbiddenAsset = isForbidden
self.testPipedAssets(asset: asset!, requestRange: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { status in
switch status {
case HTTPStatus.Forbidden:
hasForbiddenAsset = true
case HTTPStatus.PartialContent:
hasAllowedAsset = true
case HTTPStatus.OK:
hasAllowedAsset = true
default:
break
}
}
}
}
} else if let randomHLS = hlsURLs.randomElement() {
let instance = randomHLS.0
let asset = AVURLAsset(url: randomHLS.1)
if instance?.app == .piped {
self.testPipedAssets(asset: asset, requestRange: nil, isHLS: true, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
hasForbiddenAsset = isForbidden
} else if let firstHLS = hlsURLs.first {
let asset = AVURLAsset(url: firstHLS)
if instance.app == .piped {
self.testPipedAssets(asset: asset, requestRange: nil, isHLS: true, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { status in
switch status {
case HTTPStatus.Forbidden:
hasForbiddenAsset = true
case HTTPStatus.PartialContent:
hasAllowedAsset = true
case HTTPStatus.OK:
hasAllowedAsset = true
default:
break
}
}
}
}
}
forbiddenAssetTestGroup.wait()
// Post-processing code
if let instance = stream.instance {
if instance.app == .invidious {
if hasForbiddenAsset || instance.proxiesVideos {
if let audio = stream.audioAsset {
stream.audioAsset = InvidiousAPI.proxiedAsset(instance: instance, asset: audio)
}
if let video = stream.videoAsset {
stream.videoAsset = InvidiousAPI.proxiedAsset(instance: instance, asset: video)
if instance.app == .invidious, hasForbiddenAsset || instance.proxiesVideos {
if let audio = stream.audioAsset {
stream.audioAsset = InvidiousAPI.proxiedAsset(instance: instance, asset: audio)
}
if let video = stream.videoAsset {
stream.videoAsset = InvidiousAPI.proxiedAsset(instance: instance, asset: video)
}
} else if instance.app == .piped, !instance.proxiesVideos, !hasForbiddenAsset {
if let hlsURL = stream.hlsURL {
PipedAPI.nonProxiedAsset(url: hlsURL) { possibleNonProxiedURL in
if let nonProxiedURL = possibleNonProxiedURL {
stream.hlsURL = nonProxiedURL.url
}
}
} else if instance.app == .piped, !instance.proxiesVideos, !hasForbiddenAsset {
if let hlsURL = stream.hlsURL {
PipedAPI.nonProxiedAsset(url: hlsURL) { possibleNonProxiedURL in
if let nonProxiedURL = possibleNonProxiedURL {
stream.hlsURL = nonProxiedURL.url
}
} else {
if let audio = stream.audioAsset {
PipedAPI.nonProxiedAsset(asset: audio) { nonProxiedAudioAsset in
stream.audioAsset = nonProxiedAudioAsset
}
} else {
if let audio = stream.audioAsset {
PipedAPI.nonProxiedAsset(asset: audio) { nonProxiedAudioAsset in
stream.audioAsset = nonProxiedAudioAsset
}
}
if let video = stream.videoAsset {
PipedAPI.nonProxiedAsset(asset: video) { nonProxiedVideoAsset in
stream.videoAsset = nonProxiedVideoAsset
}
}
if let video = stream.videoAsset {
PipedAPI.nonProxiedAsset(asset: video) { nonProxiedVideoAsset in
stream.videoAsset = nonProxiedVideoAsset
}
}
}
@@ -152,21 +171,21 @@ extension PlayerModel {
}
}
private func getAssets(from streams: [Stream]) -> (nonHLSAssets: [(Instance?, AVURLAsset?, URL, String?)], hlsURLs: [(Instance?, URL)]) {
var nonHLSAssets = [(Instance?, AVURLAsset?, URL, String?)]()
var hlsURLs = [(Instance?, URL)]()
private func getAssets(from streams: [Stream]) -> (nonHLSAssets: [(AVURLAsset?, URL, String?)], hlsURLs: [URL]) {
var nonHLSAssets = [(AVURLAsset?, URL, String?)]()
var hlsURLs = [URL]()
for stream in streams {
if stream.isHLS {
if let url = stream.hlsURL?.url {
hlsURLs.append((stream.instance, url))
hlsURLs.append(url)
}
} else {
if let asset = stream.audioAsset {
nonHLSAssets.append((stream.instance, asset, asset.url, stream.requestRange))
nonHLSAssets.append((asset, asset.url, stream.requestRange))
}
if let asset = stream.videoAsset {
nonHLSAssets.append((stream.instance, asset, asset.url, stream.requestRange))
nonHLSAssets.append((asset, asset.url, stream.requestRange))
}
}
}
@@ -174,24 +193,24 @@ extension PlayerModel {
return (nonHLSAssets, hlsURLs)
}
private func testAsset(url: URL, range: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) {
private func testAsset(url: URL, range: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Int) -> Void) {
// In case the range is nil, generate a random one.
let randomEnd = Int.random(in: 200 ... 800)
let requestRange = range ?? "0-\(randomEnd)"
forbiddenAssetTestGroup.enter()
URLTester.testURLResponse(url: url, range: requestRange, isHLS: isHLS) { statusCode in
completion(statusCode == HTTPStatus.Forbidden)
completion(statusCode)
forbiddenAssetTestGroup.leave()
}
}
private func testPipedAssets(asset: AVURLAsset, requestRange: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) {
private func testPipedAssets(asset: AVURLAsset, requestRange: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Int) -> Void) {
PipedAPI.nonProxiedAsset(asset: asset) { possibleNonProxiedAsset in
if let nonProxiedAsset = possibleNonProxiedAsset {
self.testAsset(url: nonProxiedAsset.url, range: requestRange, isHLS: isHLS, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: completion)
} else {
completion(false)
completion(0)
}
}
}

View File

@@ -106,6 +106,12 @@ extension Defaults.Keys {
static let closePiPAndOpenPlayerOnEnteringForeground = Key<Bool>("closePiPAndOpenPlayerOnEnteringForeground", default: false)
#endif
static let captionsAutoShow = Key<Bool>("captionsAutoShow", default: false)
static let captionsDefaultLanguageCode = Key<String>("captionsDefaultLanguageCode", default: LanguageCodes.English.rawValue)
static let captionsFallbackLanguageCode = Key<String>("captionsDefaultFallbackCode", default: LanguageCodes.English.rawValue)
static let captionsFontScaleSize = Key<String>("captionsFontScale", default: "1.0")
static let captionsFontColor = Key<String>("captionsFontColor", default: "#FFFFFF")
// MARK: GROUP - Controls
static let avPlayerUsesSystemControls = Key<Bool>("avPlayerUsesSystemControls", default: true)
@@ -270,6 +276,7 @@ extension Defaults.Keys {
static let mpvDeinterlace = Key<Bool>("mpvDeinterlace", default: false)
static let mpvHWdec = Key<String>("hwdec", default: "auto-safe")
static let mpvDemuxerLavfProbeInfo = Key<String>("mpvDemuxerLavfProbeInfo", default: "no")
static let mpvInitialAudioSync = Key<Bool>("mpvInitialAudioSync", default: true)
static let showCacheStatus = Key<Bool>("showCacheStatus", default: false)
static let feedCacheSize = Key<String>("feedCacheSize", default: "50")
@@ -303,13 +310,7 @@ extension Defaults.Keys {
static let lastPlayed = Key<PlayerQueueItem?>("lastPlayed")
static let activeBackend = Key<PlayerBackendType>("activeBackend", default: .mpv)
static let captionsAutoShow = Key<Bool>("captionsAutoShow", default: false)
static let captionsLanguageCode = Key<String?>("captionsLanguageCode")
static let captionsDefaultLanguageCode = Key<String>("captionsDefaultLanguageCode", default: LanguageCodes.English.rawValue)
static let captionsFallbackLanguageCode = Key<String>("captionsDefaultFallbackCode", default: LanguageCodes.English.rawValue)
static let captionsFontScaleSize = Key<String>("captionsFontScale", default: "1.0")
static let captionsFontColor = Key<String>("captionsFontColor", default: "#FFFFFF")
static let lastUsedPlaylistID = Key<Playlist.ID?>("lastPlaylistID")
static let lastAccountIsPublic = Key<Bool>("lastAccountIsPublic", default: false)

View File

@@ -88,26 +88,38 @@ struct FavoriteItemView: View {
reloadVisibleWatches()
} else {
resource?.addObserver(store)
loadCacheAndResource()
DispatchQueue.main.async {
self.loadCacheAndResource()
}
}
}
.onDisappear {
resource?.removeObservers(ownedBy: store)
}
.onChange(of: player.currentVideo) { _ in reloadVisibleWatches() }
.onChange(of: hideShorts) { _ in reloadVisibleWatches() }
.onChange(of: hideWatched) { _ in reloadVisibleWatches() }
.onChange(of: favoritesChanged) { _ in reloadVisibleWatches() }
.onChange(of: player.currentVideo) { _ in if !player.presentingPlayer { reloadVisibleWatches() } }
.onChange(of: hideShorts) { _ in if !player.presentingPlayer { 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() } } }
.onChange(of: player.presentingPlayer) { _ in
if player.presentingPlayer {
resource?.removeObservers(ownedBy: store)
} else {
resource?.addObserver(store)
}
}
}
}
.id(watchModel.historyToken)
.onChange(of: accounts.current) { _ in
resource?.removeObservers(ownedBy: store)
resource?.addObserver(store)
loadCacheAndResource(force: true)
DispatchQueue.main.async {
loadCacheAndResource(force: true)
}
}
.onChange(of: watchModel.historyToken) { _ in
reloadVisibleWatches()
if !player.presentingPlayer {
reloadVisibleWatches()
}
}
}
@@ -159,24 +171,26 @@ struct FavoriteItemView: View {
}
func reloadVisibleWatches() {
guard item.section == .history else { return }
DispatchQueue.main.async {
guard item.section == .history else { return }
visibleWatches = []
visibleWatches = []
let watches = Array(
watches
.filter { $0.videoID != player.currentVideo?.videoID && itemVisible(.init(video: $0.video)) }
.prefix(favoritesModel.limit(item))
)
let last = watches.last
let watches = Array(
watches
.filter { $0.videoID != player.currentVideo?.videoID && itemVisible(.init(video: $0.video)) }
.prefix(favoritesModel.limit(item))
)
let last = watches.last
for watch in watches {
player.loadHistoryVideoDetails(watch) {
guard let video = player.historyVideo(watch.videoID), itemVisible(.init(video: video)) else { return }
visibleWatches.append(watch)
for watch in watches {
player.loadHistoryVideoDetails(watch) {
guard let video = player.historyVideo(watch.videoID), itemVisible(.init(video: video)) else { return }
visibleWatches.append(watch)
if watch == last {
visibleWatches.sort { $0.watchedAt ?? Date() > $1.watchedAt ?? Date() }
if watch == last {
visibleWatches.sort { $0.watchedAt ?? Date() > $1.watchedAt ?? Date() }
}
}
}
}
@@ -227,6 +241,9 @@ struct FavoriteItemView: View {
onSuccess = { response in
if let videos: [Video] = response.typedContent() {
FeedCacheModel.shared.storeFeed(account: accounts.current, videos: videos)
DispatchQueue.main.async {
store.contentItems = contentItems
}
}
}
case let .channel(_, id, name):
@@ -239,19 +256,21 @@ struct FavoriteItemView: View {
}
onSuccess = { response in
if let channel: Channel = response.typedContent() {
ChannelsCacheModel.shared.store(channel)
store.contentItems = ContentItem.array(of: channel.videos)
} else if let videos: [Video] = response.typedContent() {
channel.videos = videos
ChannelsCacheModel.shared.store(channel)
store.contentItems = ContentItem.array(of: videos)
} else if let channelPage: ChannelPage = response.typedContent() {
if let channel = channelPage.channel {
DispatchQueue.main.async {
if let channel: Channel = response.typedContent() {
ChannelsCacheModel.shared.store(channel)
}
store.contentItems = ContentItem.array(of: channel.videos)
} else if let videos: [Video] = response.typedContent() {
channel.videos = videos
ChannelsCacheModel.shared.store(channel)
store.contentItems = ContentItem.array(of: videos)
} else if let channelPage: ChannelPage = response.typedContent() {
if let channel = channelPage.channel {
ChannelsCacheModel.shared.store(channel)
}
store.contentItems = channelPage.results
store.contentItems = channelPage.results
}
}
}
case let .channelPlaylist(_, id, title):
@@ -264,6 +283,9 @@ struct FavoriteItemView: View {
onSuccess = { response in
if let playlist: ChannelPlaylist = response.typedContent() {
ChannelPlaylistsCacheModel.shared.storePlaylist(playlist: playlist)
DispatchQueue.main.async {
store.contentItems = contentItems
}
}
}
case let .playlist(_, id):
@@ -272,12 +294,16 @@ struct FavoriteItemView: View {
if let playlist = playlists.first(where: { $0.id == id }) {
contentItems = ContentItem.array(of: playlist.videos)
}
DispatchQueue.main.async {
store.contentItems = contentItems
}
default:
contentItems = []
}
if !contentItems.isEmpty {
store.contentItems = contentItems
DispatchQueue.main.async {
store.contentItems = contentItems
}
}
if force {
@@ -443,6 +469,7 @@ struct FavoriteItemView: View {
switch item.section {
case .history:
return nil
case .subscriptions:
if accounts.app.supportsSubscriptions {
return accounts.api.feed(1)

View File

@@ -5,9 +5,11 @@ import UniformTypeIdentifiers
struct HomeView: View {
@ObservedObject private var accounts = AccountsModel.shared
@ObservedObject private var player = PlayerModel.shared
@State private var presentingHomeSettings = false
@State private var favoritesChanged = false
@State private var updateTask: Task<Void, Never>?
@FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)])
var watches: FetchedResults<Watch>
@@ -16,8 +18,6 @@ struct HomeView: View {
@State private var recentDocumentsID = UUID()
#endif
var favoritesObserver: Any?
#if !os(tvOS)
@Default(.favorites) private var favorites
@Default(.widgetsSettings) private var widgetsSettings
@@ -124,6 +124,24 @@ struct HomeView: View {
}
}
}
.onDisappear {
updateTask?.cancel()
}
.onChange(of: player.presentingPlayer) { _ in
if player.presentingPlayer {
updateTask?.cancel()
} else {
Task {
for await _ in Defaults.updates(.favorites) {
favoritesChanged.toggle()
}
for await _ in Defaults.updates(.widgetsSettings) {
favoritesChanged.toggle()
}
}
}
}
.redrawOn(change: favoritesChanged)

View File

@@ -75,20 +75,16 @@ struct PlayerControls: View {
}
VStack {
Spacer()
ZStack {
GeometryReader { geometry in
VStack(spacing: 0) {
ZStack {
OpeningStream()
NetworkState()
}
VStack(spacing: 0) {
ZStack {
OpeningStream()
NetworkState()
}
.position(
x: geometry.size.width / 2,
y: geometry.size.height / 2
)
Spacer()
}
.offset(y: playerControlsLayout.osdVerticalOffset + 5)
if showControls {
Section {

View File

@@ -30,7 +30,6 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable {
#else
return isIPad
#endif
case .large:
return true
case .medium:
@@ -264,7 +263,7 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable {
var seekOSDWidth: Double {
switch self {
case .tvRegular:
return 240
return 280
case .veryLarge:
return 240
case .large:
@@ -278,6 +277,10 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable {
}
}
var osdVerticalOffset: Double {
buttonSize
}
var osdProgressBarHeight: Double {
switch self {
case .tvRegular:

View File

@@ -164,7 +164,6 @@ struct VideoActions: View {
}
case .musicMode:
actionButton("Music", systemImage: "music.note", active: player.musicMode, action: player.toggleMusicMode)
case .settings:
actionButton("Settings", systemImage: "gear") {
withAnimation(ControlOverlaysModel.animation) {
@@ -179,7 +178,6 @@ struct VideoActions: View {
actionButton("Hide", systemImage: "chevron.down") {
player.hide(animate: true)
}
case .close:
actionButton("Close", systemImage: "xmark") {
player.closeCurrentItem()

View File

@@ -10,6 +10,7 @@ struct AdvancedSettings: View {
@Default(.mpvEnableLogging) private var mpvEnableLogging
@Default(.mpvHWdec) private var mpvHWdec
@Default(.mpvDemuxerLavfProbeInfo) private var mpvDemuxerLavfProbeInfo
@Default(.mpvInitialAudioSync) private var mpvInitialAudioSync
@Default(.showCacheStatus) private var showCacheStatus
@Default(.feedCacheSize) private var feedCacheSize
@Default(.showPlayNowInBackendContextMenu) private var showPlayNowInBackendContextMenu
@@ -83,6 +84,9 @@ struct AdvancedSettings: View {
UIApplication.shared.open(URL(string: "https://mpv.io/manual/stable/#options-cache-pause-initial")!)
}
#elseif os(macOS)
.onTapGesture {
NSWorkspace.shared.open(URL(string: "https://mpv.io/manual/stable/#options-cache-pause-initial")!)
}
.onHover(perform: onHover(_:))
#endif
#endif
@@ -100,6 +104,9 @@ struct AdvancedSettings: View {
UIApplication.shared.open(URL(string: "https://mpv.io/manual/stable/#options-cache-secs")!)
}
#elseif os(macOS)
.onTapGesture {
NSWorkspace.shared.open(URL(string: "https://mpv.io/manual/stable/#options-cache-secs")!)
}
.onHover(perform: onHover(_:))
#endif
@@ -123,6 +130,9 @@ struct AdvancedSettings: View {
UIApplication.shared.open(URL(string: "https://mpv.io/manual/stable/#options-cache-pause-wait")!)
}
#elseif os(macOS)
.onTapGesture {
NSWorkspace.shared.open(URL(string: "https://mpv.io/manual/stable/#options-cache-pause-wait")!)
}
.onHover(perform: onHover(_:))
#endif
#endif
@@ -147,6 +157,30 @@ struct AdvancedSettings: View {
UIApplication.shared.open(URL(string: "https://mpv.io/manual/stable/#options-deinterlace")!)
}
#elseif os(macOS)
.onTapGesture {
NSWorkspace.shared.open(URL(string: "https://mpv.io/manual/stable/#options-deinterlace")!)
}
.onHover(perform: onHover(_:))
#endif
#endif
}
}
Toggle(isOn: $mpvInitialAudioSync) {
HStack {
Text("initial-audio-sync")
#if !os(tvOS)
Image(systemName: "link")
.accessibilityAddTraits([.isButton, .isLink])
.font(.footnote)
#if os(iOS)
.onTapGesture {
UIApplication.shared.open(URL(string: "https://mpv.io/manual/stable/#options-initial-audio-sync")!)
}
#elseif os(macOS)
.onTapGesture {
NSWorkspace.shared.open(URL(string: "https://mpv.io/manual/stable/#options-initial-audio-sync")!)
}
.onHover(perform: onHover(_:))
#endif
#endif
@@ -165,6 +199,9 @@ struct AdvancedSettings: View {
UIApplication.shared.open(URL(string: "https://mpv.io/manual/stable/#options-hwdec")!)
}
#elseif os(macOS)
.onTapGesture {
NSWorkspace.shared.open(URL(string: "https://mpv.io/manual/stable/#options-hwdec")!)
}
.onHover(perform: onHover(_:))
#endif
#endif
@@ -179,10 +216,6 @@ struct AdvancedSettings: View {
#endif
}
if mpvEnableLogging {
logButton
}
HStack {
Text("demuxer-lavf-probe-info")
@@ -195,6 +228,9 @@ struct AdvancedSettings: View {
UIApplication.shared.open(URL(string: "https://mpv.io/manual/stable/#options-demuxer-lavf-probe-info")!)
}
#elseif os(macOS)
.onTapGesture {
NSWorkspace.shared.open(URL(string: "https://mpv.io/manual/stable/#options-demuxer-lavf-probe-info")!)
}
.onHover(perform: onHover(_:))
#endif
#endif

View File

@@ -62,7 +62,7 @@ struct ImportSettingsAccountRow: View {
}
} else {
Group {
if InstancesModel.shared.find(instanceID) != nil {
if InstancesModel.shared.find(instanceID) != nil || InstancesModel.shared.findByURLString(account.urlString) != nil {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)

View File

@@ -42,7 +42,7 @@ final class ImportSettingsSheetViewModel: ObservableObject {
return ((account.password != nil && !account.password!.isEmpty) ||
importableAccounts.contains(account.id)) && (
(InstancesModel.shared.find(instanceID) != nil) ||
(InstancesModel.shared.find(instanceID) != nil || InstancesModel.shared.findByURLString(account.urlString) != nil) ||
selectedInstances.contains(instanceID)
)
}

View File

@@ -57,7 +57,7 @@ struct QualityProfileForm: View {
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.background(scheme: colorScheme))
#else
.frame(width: 400, height: 400)
.frame(width: 400, height: 450)
.padding(.vertical, 10)
#endif
}

View File

@@ -316,19 +316,19 @@ struct SettingsView: View {
case .browsing:
return 800
case .player:
return 550
return 800
case .controls:
return 920
case .quality:
return 420
case .history:
return 500
return 600
case .sponsorBlock:
return 700
return 970
case .locations:
return 600
case .advanced:
return 500
return 550
case .importExport:
return 580
case .help:

View File

@@ -74,7 +74,7 @@ struct SponsorBlockSettings: View {
)
)
} label: {
Text("Restore Default Colors ")
Text("Restore Default Colors…")
.foregroundColor(.red)
}
#endif

View File

@@ -4,18 +4,183 @@ import SwiftUI
struct FeedView: View {
@ObservedObject private var feed = FeedModel.shared
@ObservedObject private var subscriptions = SubscribedChannelsModel.shared
@ObservedObject private var feedCount = UnwatchedFeedCountModel.shared
@Default(.showCacheStatus) private var showCacheStatus
#if os(tvOS)
@Default(.subscriptionsListingStyle) private var subscriptionsListingStyle
@StateObject private var accountsModel = AccountsViewModel()
#endif
var videos: [ContentItem] {
ContentItem.array(of: feed.videos)
guard let selectedChannel = selectedChannel else {
return ContentItem.array(of: feed.videos)
}
return ContentItem.array(of: feed.videos.filter {
$0.channel.id == selectedChannel.id
})
}
var channels: [Channel] {
feed.videos.map {
$0.channel
}.unique()
}
@State private var selectedChannel: Channel?
#if os(tvOS)
@FocusState private var focusedChannel: String?
#endif
@State private var feedChannelsViewVisible = false
private var navigation = NavigationModel.shared
private let dismiss_channel_list_id = "dismiss_channel_list_id"
var body: some View {
#if os(tvOS)
GeometryReader { geometry in
ZStack {
// selected channel feed view
HStack(spacing: 0) {
// sidebar - show channels
if feedChannelsViewVisible {
Spacer()
.frame(width: geometry.size.width * 0.3)
}
selectedFeedView
}
.disabled(feedChannelsViewVisible)
.frame(width: geometry.size.width, height: geometry.size.height)
if feedChannelsViewVisible {
HStack(spacing: 0) {
// sidebar - show channels
feedChannelsView
.padding(.all)
.frame(width: geometry.size.width * 0.3)
.background()
.clipShape(RoundedRectangle(cornerRadius: 16))
.contentShape(RoundedRectangle(cornerRadius: 16))
Rectangle()
.fill(.clear)
.id(dismiss_channel_list_id)
.focusable()
.focused(self.$focusedChannel, equals: dismiss_channel_list_id)
}
.frame(width: geometry.size.width, height: geometry.size.height)
.clipped()
}
}
}
#else
selectedFeedView
#endif
}
#if os(tvOS)
var accountsPicker: some View {
ForEach(accountsModel.sortedAccounts.filter { $0.anonymous == false }) { account in
Button(action: {
AccountsModel.shared.setCurrent(account)
}) {
HStack {
Text("\(account.description) (\(account.instance.app.rawValue))")
if account == accountsModel.currentAccount {
Image(systemName: "checkmark")
}
}
}
.buttonStyle(PlainButtonStyle())
}
}
var feedChannelsView: some View {
ScrollViewReader { proxy in
VStack {
Text("Channels")
.font(.subheadline)
if #available(tvOS 17.0, *) {
List(selection: $selectedChannel) {
Button(action: {
self.selectedChannel = nil
self.feedChannelsViewVisible = false
}) {
HStack(spacing: 16) {
Image(systemName: RecentsModel.symbolSystemImage("A"))
.imageScale(.large)
.foregroundColor(.accentColor)
.frame(width: 35, height: 35)
Text("All")
Spacer()
feedCount.unwatchedText
}
}
.padding(.all)
.background(RoundedRectangle(cornerRadius: 8.0)
.fill(self.selectedChannel == nil ? Color.secondary : Color.clear))
.font(.caption)
.buttonStyle(PlainButtonStyle())
.focused(self.$focusedChannel, equals: "all")
ForEach(channels, id: \.self) { channel in
Button(action: {
self.selectedChannel = channel
self.feedChannelsViewVisible = false
}) {
HStack(spacing: 16) {
ChannelAvatarView(channel: channel, subscribedBadge: false)
.frame(width: 50, height: 50)
Text(channel.name)
.lineLimit(1)
Spacer()
if let unwatchedCount = feedCount.unwatchedByChannelText(channel) {
unwatchedCount
}
}
}
.padding(.all)
.background(RoundedRectangle(cornerRadius: 8.0)
.fill(self.selectedChannel == channel ? Color.secondary : Color.clear))
.font(.caption)
.buttonStyle(PlainButtonStyle())
.focused(self.$focusedChannel, equals: channel.id)
}
}
.onChange(of: self.focusedChannel) {
if self.focusedChannel == "all" {
withAnimation {
self.selectedChannel = nil
}
} else if self.focusedChannel == dismiss_channel_list_id {
self.feedChannelsViewVisible = false
} else {
withAnimation {
self.selectedChannel = channels.first {
$0.id == self.focusedChannel
}
}
}
}
.onAppear {
guard let selectedChannel = self.selectedChannel else {
return
}
proxy.scrollTo(selectedChannel, anchor: .top)
}
.onExitCommand {
withAnimation {
self.feedChannelsViewVisible = false
}
}
}
}
}
}
#endif
var selectedFeedView: some View {
VerticalCells(items: videos) { if shouldDisplayHeader { header } }
.environment(\.loadMoreContentHandler) { feed.loadNextPage() }
.onAppear {
@@ -49,33 +214,55 @@ struct FeedView: View {
}
var header: some View {
HStack {
HStack(spacing: 16) {
#if os(tvOS)
SubscriptionsPageButton()
ListingStyleButtons(listingStyle: $subscriptionsListingStyle)
HideWatchedButtons()
HideShortsButtons()
#endif
if showCacheStatus {
Spacer()
CacheStatusHeader(
refreshTime: feed.formattedFeedTime,
isLoading: feed.isLoading
)
}
#if os(tvOS)
Button {
feed.loadResources(force: true)
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
.labelStyle(.iconOnly)
.imageScale(.small)
.font(.caption)
if #available(tvOS 17.0, *) {
Menu {
accountsPicker
} label: {
Label("Channels", systemImage: "filemenu.and.selection")
.labelStyle(.iconOnly)
.imageScale(.small)
.font(.caption)
} primaryAction: {
withAnimation {
self.feedChannelsViewVisible = true
self.focusedChannel = selectedChannel?.id ?? "all"
}
}
.opacity(feedChannelsViewVisible ? 0 : 1)
.frame(minWidth: feedChannelsViewVisible ? 0 : nil, maxWidth: feedChannelsViewVisible ? 0 : nil)
}
channelHeaderView
if selectedChannel == nil {
Spacer()
}
if feedChannelsViewVisible == false {
ListingStyleButtons(listingStyle: $subscriptionsListingStyle)
HideWatchedButtons()
HideShortsButtons()
}
#endif
if feedChannelsViewVisible == false {
if showCacheStatus {
CacheStatusHeader(
refreshTime: feed.formattedFeedTime,
isLoading: feed.isLoading
)
}
#if os(tvOS)
Button {
feed.loadResources(force: true)
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
.labelStyle(.iconOnly)
.imageScale(.small)
.font(.caption)
}
#endif
}
}
.padding(.leading, 30)
#if os(tvOS)
@@ -84,6 +271,46 @@ struct FeedView: View {
#endif
}
var channelHeaderView: some View {
guard let selectedChannel = selectedChannel else {
return AnyView(
Text("All Channels")
.font(.caption)
.frame(alignment: .leading)
.lineLimit(1)
.padding(0)
.padding(.leading, 16)
)
}
return AnyView(
HStack(spacing: 16) {
ChannelAvatarView(channel: selectedChannel, subscribedBadge: false)
.id("channel-avatar-\(selectedChannel.id)")
.frame(width: 80, height: 80)
Text("\(selectedChannel.name)")
.font(.caption)
.frame(alignment: .leading)
.lineLimit(1)
.fixedSize(horizontal: true, vertical: false)
Spacer()
if feedChannelsViewVisible == false {
Button(action: {
navigation.openChannel(selectedChannel, navigationStyle: .tab)
}) {
Text("Visit Channel")
.font(.caption)
.frame(alignment: .leading)
.lineLimit(1)
.fixedSize(horizontal: true, vertical: false)
}
}
}
.padding(0)
.padding(.leading, 16)
)
}
var shouldDisplayHeader: Bool {
#if os(tvOS)
true

View File

@@ -264,7 +264,7 @@
"Don't use public locations" = "Ne pas utiliser d'instances publiques";
"Enable Return YouTube Dislike" = "Activer Return YouTube Dislike";
"Enter fullscreen in landscape" = "Entrer en plein écran en mode paysage";
"Explicit reminders to like, subscribe or interact with them on any paid or free platform(s) (e.g. click on a video)." = "Rappels d'aimer la vidéo, de s'abonner ou d'interagir avec le créateur sur une plateforme gratuite ou payante.\n";
"Explicit reminders to like, subscribe or interact with them on any paid or free platform(s) (e.g. click on a video)." = "Rappels d'aimer la vidéo, de s'abonner ou d'interagir avec le créateur sur une plateforme gratuite ou payante.";
"Frontend URL" = "URL frontale";
"Public Locations" = "Instances publiques";
"Public Manifest" = "Manifeste publique";

View File

@@ -0,0 +1,70 @@
"%@ Playlist" = "%@ 플레이리스트";
"%@ subscribers" = "%@ 구독자";
"10 seconds forwards/backwards" = "10초 건너뛰기/뒤로 가기";
"Accounts" = "계정";
"Accounts are not supported for the application of this instance" = "해당 애플리케이션 인스턴스는 계정을 지원하지 않습니다";
"Add Location" = "주소 추가";
"Add Location..." = "주소 추가...";
"Add profile..." = "프로필 추가...";
"Add Quality Profile" = "품질 프로필 추가";
"Add to %@" = "%@에 추가";
"Add to Favorites" = "즐겨찾기에 추가";
"Add to Playlist" = "플레이리스트에 추가";
"Add to Playlist..." = "플레이리스트에 추가...";
"Advanced" = "고급";
/* Trending category, section containing all kinds of videos */
"All" = "전체";
"Always use AVPlayer for live videos" = "라이브 동영상에 항상 AVPlayer 사용";
"Anonymous" = "익명";
/* Video date filter in search
Video duration filter in search */
"Any" = "모두";
"Apply to all" = "모두 적용";
"Are you sure you want to clear search history?" = "검색 기록을 삭제하시겠습니까?";
"Are you sure you want to delete playlist?" = "플레이리스트를 삭제하시겠습니까?";
"Are you sure you want to restore default quality profiles?" = "기본 품질 프로필로 복구하시겠습니까?";
"Are you sure you want to unsubscribe from %@?" = "%@을/를 구독 해제하시겠습니까?";
"Autoplaying Next" = "다음으로 자동재생";
"Backend" = "백엔드";
"Badge" = "배지";
"Battery" = "배터리";
"Cellular" = "셀룰러";
" subscribers" = " 구독자";
"%@ Channel" = "%@ 채널";
"%lld videos" = "%lld 동영상";
"Are you sure you want to clear history of watched videos?" = "동영상 시청 기록을 삭제하시겠습니까?";
"Add Account" = "계정 추가";
"Add Account..." = "계정 추가...";
"Automatic" = "자동";
"Close" = "닫기";
"Badge color" = "배지 색상";
"Bugs and great feature ideas can be sent to the GitHub issues tracker. " = "깃허브 이슈 트래커를 통해 버그 제보 또는 개발 아이디어를 제공해주실 수 있습니다. ";
"Category" = "카테고리";
"Captions" = "자막";
"Close Video" = "동영상 닫기";
"Close player when closing video" = "동영상 종료 시 플레이어 닫기";
"Close player when starting PiP" = "PiP 시작 시 플레이어 닫기";
"Button" = "버튼";
"Cancel" = "취소";
"Based on system color scheme" = "시스템 색 구성표 기반";
"Blue" = "파란색";
"Categories to Skip" = "건너뛸 카테고리";
"Chapters" = "챕터";
"Charging" = "충전중";
"Badge & Decreased opacity" = "배지와 불투명도 감소";
"Browsing" = "탐색";
"Buffering stream..." = "스트림 버퍼링...";
"Clear" = "지우기";
"Clear All" = "모두 지우기";
"Clear All Recents" = "최근내역 모두 지우기";
"Clear History" = "기록 지우기";
"Clear Search History" = "검색기록 지우기";
"Clear Search History..." = "검색기록 지우기...";
"Clear the queue" = "대기열 지우기";
"Close PiP and open player when application enters foreground" = "애플리케이션이 포그라운드에 진입하면 PiP를 닫고 플레이어를 열기";
"Close PiP when player is opened" = "플레이어가 열리면 PiP 닫기";
"Close PiP when starting playing other video" = "다른 동영상 재생을 시작하면 PiP 닫기";

View File

@@ -294,3 +294,240 @@
"Show anonymous accounts" = "Anonieme accounts laten zien";
"Show channel name" = "Naam van kanaal laten zien";
"Show history" = "Geschiedenis laten zien";
"Show keywords" = "Toon sleutelwoorden";
"Show progress of watching on thumbnails" = "Toon vooruitgang bovenop voorvertoning";
"Show playback statistics" = "Toon afspeelstatistieken";
"Show sidebar when space permits" = "Toon zijbalk wanneer de ruimte dit toelaat";
"Show video length" = "Toon videoduur";
"Shuffle" = "Shuffle";
"Shuffle All" = "Shuffle alles";
"Sidebar" = "Zijbalk";
"Sign In Required" = "Aanmelden vereist";
/* Player controls layout size */
"Small" = "Klein";
/* Player controls layout size */
"Smaller" = "Kleiner";
"Subscribe" = "Abonneer";
/* Subscriptions title */
"Subscriptions" = "Abonnementen";
"Switch to other public location" = "Schakel naar andere publieke locatie";
"System controls show buttons for %@" = "Knoppen systeemcontrole voor %@";
"That's nice to hear. It is fun to deliver apps other people want to use. You can consider donating to the project or help by contributing to new features development." = "Dat is fijn om te horen. Het is leuk om apps aan te bieden die mensen willen gebruiken. Je kan een donatie overwegen of een bijdrage leveren in de ontwikkeling van nieuwe functionaliteiten.";
"This cannot be reverted" = "Dit kan niet ongedaan worden gemaakt";
"Switch to public locations" = "Schakel naar publieke locatie";
"System controls buttons" = "Knoppen voor systeemcontrole";
"This cannot be reverted. You might need to switch between views or restart the app to see changes." = "Dit kan niet ongedaan worden gemaakt. Je zou mogelijks tussen de weergave moeten schakelen of de app opnieuw te openen om de wijzigingen te zien.";
"This information will be processed only on your device and used to connect you to the server in the specified country." = "Deze informatie zal enkel verwerkt worden binnen het toestel zelf en gebruikt worden om te verbinden met de server in het gekozen land.";
"This will remove all your custom profiles and return their default values. This cannot be reverted." = "Dit zal alle profielen verwijderen en terug naar de initiële waarden brengen. Dit kan niet ongedaan worden gemaakt.";
"Thumbnails" = "Voorvertoningen";
/* Video date filter in search */
"Today" = "Vandaag";
"Trending" = "Trending";
/* Player controls layout size for TV */
"TV" = "TV";
"unknown" = "onbekend";
"Unsubscribe" = "Afmelden";
"Upload date" = "Datum van opladen";
"URL" = "URL";
"Videos" = "Videos";
/* Video sort order in search */
"Views" = "Views";
/* Selected video is being played */
"Watching now" = "Aan het bekijken";
/* Video date filter in search */
"Week" = "Week";
"Welcome" = "Welkom";
"When partially watched video is played" = "Wanneer een gedeeltelijk bekeken video wordt afgespeeld";
"Wi-Fi" = "Wi-Fi";
"Wiki" = "Wiki";
"Yattee" = "Yattee";
"Yattee %@ (build %@)" = "Yattee %@ (build %@)";
"You can use automatic profile selection based on current device status or switch it in video playback settings controls." = "Je kan gebruik maken van een automatische profielselectie gebaseerd op de status van het toestel of maak een keuze in de aspeelinstellingen.";
"You have no Playlists" = "Je hebt geen afspeellijsten";
"You have no playlists\n\nTap on \"New Playlist\" to create one" = "Je hebt geen afspeellijsten\n\nTik op \"Nieuwe afspeellijst\" om er een aan te maken";
"You need to create an instance and accounts\nto access %@ section" = "Je moet een instance en een account aanmaken\nom toegang te krijgen tot het onderdeel %@";
"You need to select an account\nto access %@ section" = "Je moet een account selecteren\nom toegang te krijgen tot het onderdeel %@";
"Public" = "Publiek";
"Unlisted" = "Onopgelijst";
"Now Playing" = "Wordt nu afgespeeld";
"Current Location" = "Huidige Locatie";
"Private" = "Privé";
"Playback queue is empty" = "Afspeellijst is leeg";
"Playing Next" = "Speelt Hierna";
"You can switch between profiles in playback settings controls." = "Je kan schakelen tussen profielen via de afspeelinstellingen.";
"Add Channels, Playlists and Searches to Favorites using" = "Voeg Kanalen, Afspeellijsten en Zoekopdrachten toe aan Favorieten gebruik makend van";
"Make default" = "Stel in als standaard";
"Visibility" = "Zichtbaarheid";
"Current Playlist" = "Huidige Afspeellijst";
"Stream & Player" = "Stream & Speler";
"Statistics" = "Statistieken";
"Hardware decoder" = "Hardware decoder";
"Stream FPS" = "Stream FPS";
"Cached time" = "Cache duurtijd";
"Rate & Captions" = "Beoordelen & Ondertiteling";
"Dropped frames" = "Verloren frames";
"Any format" = "Elk formaat";
"%@ formats" = "%@ formaten";
"Keep last played video in the queue after restart" = "Behoud de laatst gespeelde video in de afspeellijst na herstart";
"Playlist is empty\n\nTap and hold on a video and then \n\"Add to Playlist\"" = "Afspeellijst is leeg\n\nTik en houd de video vast en\nkies dan \"Voeg toe aan afspeellijst\"";
"It can be changed later in settings. You can use your own locations too." = "Dit kan later aangepast worden in de instellingen. Je kan tevens je eigen locatie gebruiken.";
"Press and hold remote button to open captions and quality menus" = "Tik en houd de knop van de afstandsbediening ingedrukt om het menu van de ondertitelingen en streamkwaliteit te openen";
"Comments are disabled" = "Commentaar is uitgeschakeld";
"No comments" = "Geen commentaren";
"No chapters information available" = "Geen hoofdstukinformatie beschikbaar";
"Share Logs..." = "Deel logboek…";
"Open logs in Finder" = "Open logboek in Finder";
"Channel could not be found" = "Kanaal kan niet gevonden worden";
"Could not extract channel information" = "Kan geen informatie van het kanaal vinden";
"Could not extract SID from received cookies: %@" = "Kon de SID niet bepalen van de ontvangen cookies: %@";
"Could not update your token." = "Kan jouw token niet updaten.";
"Could not refresh Trending" = "Kon Trending niet verversen";
"Could not create share link" = "Kon geen link aanmaken om te delen";
"Could not open playlist" = "Kon de afspeellijst niet openen";
"Could not extract video ID" = "Kon de video ID niet bepalen";
"This video could not be opened" = "Deze video kon niet worden geopend";
"Could not extract playlist ID" = "Kon de playlist ID niet bepalen";
"No locations available at the moment" = "Geen locaties beschikbaar op dit moment";
"Could not refresh Playlists" = "Kon de Afspeellijsten niet verversen";
"If you want this app to be available in your language, join translation project." = "Als je deze app in jouw taal wenst te gebruiken, neem deel aan het vertalingsproject.";
"Translations" = "Vertalingen";
"No documents" = "Geen documenten";
"Recent Documents" = "Recente Documenten";
"Home" = "Home";
"Pages buttons" = "Paginas knoppen";
"Only for local files and URLs" = "Enkel voor lokale bestanden en URLs";
"Right" = "Rechts";
"Playback Mode" = "Afspeelmodus";
"Add" = "Voeg toe";
"Hide" = "Verberg";
"Always" = "Altijd";
"Channels" = "Kanalen";
"Open Files" = "Open Bestanden";
"Share" = "Deel";
"Show icons and text when space permits" = "Toon iconen en tekst wanneer de ruimte dit toelaat";
"Left" = "Links";
"Format" = "Formaat";
"Driver" = "Driver";
"Show only icons" = "Toon enkel iconen";
"Audio" = "Audio";
"File" = "Bestand";
"Documents" = "Documenten";
"Video" = "Video";
"Codec" = "Codec";
"Size" = "Grootte";
"FPS" = "FPS";
"Sample Rate" = "Sample Rate";
"Could not find any links to open in your clipboard" = "Kon geen links vinden om te openen in het klembord";
"Address" = "Adres";
"Show sidebar" = "Toon zijbalk";
"Locations Manifest" = "Locatie Manifest";
"Remove Location" = "Verwijder Locatie";
"Could not delete document" = "Kon het document niet verwijderen";
"Verified" = "Geverifiëerd";
"Channel" = "Kanaal";
"Open expanded" = "Open uitgeklapt";
"Mark channel feed as unwatched" = "Markeer het kanaal als onbekeken";
"Mark channel feed as watched" = "Markeer het kanaal als bekeken";
"Short videos: visible" = "Korte videos: zichtbaar";
"Player Bar" = "Afspeelbalk";
"Short videos: hidden" = "Korte videos: verborgen";
"Play all unwatched" = "Speel alle onbekeken af";
"Seeking" = "Scrollen";
"System controls" = "Systeembediening";
"Controls button: backwards" = "Bedieningsknop: terug";
/* Loading stream OSD */
"Loading streams…" = "Streams laden…";
/* Loading stream OSD */
"Opening %@ stream…" = "Stream %@ openen…";
"Opening audio stream…" = "Audio stream openen…";
"Sort" = "Sorteer";
"Sort: %@" = "Sorteer: %@";
/* SponsorBlock category name */
"Sponsor" = "Sponsor";
"SponsorBlock" = "SponsorBlock";
"Source" = "Bron";
"SponsorBlock API Instance" = "SponsorBlock API Instance";
"Username" = "Gebruikersnaam";
/* Player controls layout size */
"Very Large" = "Heel Groot";
"Watched" = "Bekeken";
/* Selected video was played on given date */
"Watched %@" = "%@ Bekeken";
/* Video date filter in search */
"Year" = "Jaar";
"Typically near or at the end of the video when the credits pop up and/or endcards are shown." = "Typisch dicht bij of op het einde van een video wanneer de aftiteling getoond wordt.";
"Used to create links from videos, channels and playlists" = "Gebruikt om links te maken van videos, kanalen en afspeellijsten";
"You can find information about using Yattee in the Wiki pages." = "Je vindt informatie over het gebruik van Yattee in de Wiki.";
"Share files from Finder on a Mac\nor iTunes on Windows" = "Deel bestanden van Finder op Mac\nof iTunes op Windows";
"Show Favorites" = "Toon Favorieten";
"Inspector visibility" = "Inspector zichtbaarheid";
"Edit Favorites…" = "Pas Favorieten aan…";
"Show Open Videos toolbar button" = "Toon Open Videos werkbalkknop";
"Show Inspector" = "Toon Inspector";
"Open" = "Open";
"Open Videos" = "Open Videos";
"Enter links to open, one per line" = "Voeg links toe om te openen, één per lijn";
"Could not refresh Subscriptions" = "Kan Abonnementen niet verversen";
"Show Home" = "Toon Home";
"Recent History" = "Recente Geschiedenis";
"Enter link to open" = "Voeg de link in om te openen";
"Show Open Videos quick actions" = "Toon Open Videos snelle acties";
"Files" = "Bestanden";
"Could not open Files" = "Kon Bestanden niet openen";
"Could not load streams" = "Kan de streams niet laden";
"Could not open video" = "Kan de video niet openen";
"Buttons labels" = "Knoppen labels";
"Show Documents" = "Toon Documenten";
"Video Details" = "Video Eigenschappen";
"Clear Queue before opening" = "Maak de wachtrij leeg voor het openen";
"For custom locations you can configure Frontend URL in Locations settings" = "Voor aangepaste locaties kan je de URL instellen in de locatie instellingen";
"This URL could not be opened" = "Deze URL kon niet geopend worden";
"Could not load video" = "Kon de video niet laden";
"Pages toolbar position" = "Paginas werkbalk positie";
"Reload manifest" = "Ververs het manifest";
"Video actions buttons" = "Video actie knoppen";
"URL to Open" = "URL om te Openen";
"Paste" = "Plakken";
"Share%@link" = "Deel %@link";
"Could not open channel" = "Kan het Kanaal niet openen";
"Could not refresh Popular" = "Kon Populair niet verversen";
"Center" = "Midden";
"Open Video" = "Open Video";
"Default Profile" = "Standaard Profiel";
"Playback history is empty" = "Afspeelgeschiedenis is leeg";
"Shorts" = "Shorts";
"Double tap gesture" = "Dubbele tik gebaar";
"Tap and hold channel thumbnail to open context menu with more actions" = "Tik en houd voorvertoningsafbeelding vast om het contextmenu te openen met meer acties";
"Always show controls buttons" = "Toon altijd bedieningsknoppen";
"Single tap gesture" = "Enkel tikgebaar";
"Maximum width expanded" = "Maximale breedte uitgeklapt";
"Clear all" = "Verwijder alle";
"Show unwatched feed badges" = "Toon onbekeken feed badges";
"Controls button: forwards" = "Bedieningsknop: voorwaarts";
"Remove…" = "Verwijder…";
"Actions buttons" = "Actieknoppen";
"\"%@\" will be irreversibly removed from this device." = "\"%@\" zal onherroepelijk verwijderd worden van dit toestel.";
"Right click channel thumbnail to open context menu with more actions" = "Rechts klikken op voorvertoningsafbeelding om de contextmenu te openen met meer acties";
"Gesture: backwards" = "Gebaar: achteruit";
"Copy%@link" = "Kopieer %@link";
"Live Streams" = "Livestreams";
"Gesture: fowards" = "Gebaar: voorwaarts";
"Controls Buttons" = "Bedieningsknoppen";
"Are you sure you want to remove this document?" = "Ben je zeker om dit document te verwijderen?";
"Are you sure you want to remove %@ location?" = "Ben je zeker om locatie %a te verwijderen?";

View File

@@ -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 = 185;
CURRENT_PROJECT_VERSION = 187;
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 = 185;
CURRENT_PROJECT_VERSION = 187;
"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 = 185;
CURRENT_PROJECT_VERSION = 187;
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 = 185;
CURRENT_PROJECT_VERSION = 187;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 11.0;
@@ -4349,7 +4349,7 @@
CODE_SIGN_ENTITLEMENTS = "iOS/Yattee (iOS).entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 185;
CURRENT_PROJECT_VERSION = 187;
ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
@@ -4402,7 +4402,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 185;
CURRENT_PROJECT_VERSION = 187;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1";
@@ -4454,7 +4454,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 185;
CURRENT_PROJECT_VERSION = 187;
DEAD_CODE_STRIPPING = YES;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
@@ -4493,7 +4493,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 185;
CURRENT_PROJECT_VERSION = 187;
DEAD_CODE_STRIPPING = YES;
"DEVELOPMENT_TEAM[sdk=macosx*]" = 78Z5H3M6RJ;
ENABLE_APP_SANDBOX = YES;
@@ -4528,7 +4528,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 185;
CURRENT_PROJECT_VERSION = 187;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4552,7 +4552,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 185;
CURRENT_PROJECT_VERSION = 187;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4578,7 +4578,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 185;
CURRENT_PROJECT_VERSION = 187;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4603,7 +4603,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 185;
CURRENT_PROJECT_VERSION = 187;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4629,7 +4629,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 185;
CURRENT_PROJECT_VERSION = 187;
DEVELOPMENT_ASSET_PATHS = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -4669,7 +4669,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 185;
CURRENT_PROJECT_VERSION = 187;
DEVELOPMENT_ASSET_PATHS = "";
"DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES;
@@ -4710,7 +4710,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 185;
CURRENT_PROJECT_VERSION = 187;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -4734,7 +4734,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 185;
CURRENT_PROJECT_VERSION = 187;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",

View File

@@ -25,7 +25,7 @@
"location" : "https://github.com/hyperoslo/Cache.git",
"state" : {
"branch" : "master",
"revision" : "f44a8f6b5ec27730198725ccc542fef0d1cc6b3d"
"revision" : "dff9930c559aa2d1f7ed818d490d30c8852f57a6"
}
},
{
@@ -87,8 +87,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/ashleymills/Reachability.swift",
"state" : {
"revision" : "57da4b1270cab7c2228919eabc0e4e1bf93e48ea",
"version" : "5.2.2"
"revision" : "7cbd73f46a7dfaeca079e18df7324c6de6d1834a",
"version" : "5.2.3"
}
},
{
@@ -105,8 +105,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/SDWebImage/SDWebImage",
"state" : {
"revision" : "5642d1ffe3dbe628592443bd14154e31929727b4",
"version" : "5.19.2"
"revision" : "be0bcd7823ce56629948491f2eaeaa19979514f7",
"version" : "5.19.4"
}
},
{