Compare commits

...

66 Commits

Author SHA1 Message Date
Arkadiusz Fal
450a4b42f7 Fix property access 2023-05-21 19:37:22 +02:00
Arkadiusz Fal
db4b3115b1 Bump build number to 150 2023-05-21 19:27:30 +02:00
Arkadiusz Fal
fba465a22a Update CHANGELOG 2023-05-21 19:27:30 +02:00
Arkadiusz Fal
d996069a20 Rotation fixes 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
d7a2564617 Localization fix 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
d5f8e35430 Fix channel videos horizontal layout 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
8f9de6d1be Disable mac beta lane in release workflow 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
0fc6f7fdb7 Remove padding from search suggestions 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
d5f88a73f8 Fix Add to playlist actionable 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
97af5a6e0c Fix opening channels and playlists 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
4c5ef920b4 Fix drag gesture 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
a55683e6bf Fix orientation 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
739ca007e8 Fix tests 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
7d7bd40a89 Add option "Close player on end of video"
Fix #442
2023-05-21 19:13:42 +02:00
Arkadiusz Fal
c6798be167 Show stream opening status with AVPlayer 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
2b7ccc4b03 Orientation fixes 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
08ce572b9e Fix actions buttons text 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
1cc66fdc10 Fix various issues 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
34a05433d5 Fix issue with streams list duplicates 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
5383cf0e90 AVPlayer system controls on iOS 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
a4fdd50388 Update packages 2023-05-21 19:13:42 +02:00
Arkadiusz Fal
f67b1d4feb Improve orientation and safe area handling
Fix #369
Fix #382
2023-05-21 19:13:42 +02:00
Arkadiusz Fal
b53b5eac56 Localization fix 2023-05-21 19:13:19 +02:00
Arkadiusz Fal
6c5b8ef3ec Bump build number to 149 2023-05-21 19:13:18 +02:00
Arkadiusz Fal
226da4d2be Bump version number to 1.4.5 2023-05-21 19:13:18 +02:00
Arkadiusz Fal
49ffffae53 Fix crash 2023-05-21 19:13:18 +02:00
Arkadiusz Fal
848a43ce7f Merge pull request #464 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-21 19:12:56 +02:00
Arkadiusz Fal
6ca0e82feb Translated using Weblate (Polish)
Currently translated at 100.0% (512 of 512 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-05-21 19:12:46 +02:00
Anonymous
f7f53c6417 Translated using Weblate (English)
Currently translated at 100.0% (512 of 512 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-21 19:12:45 +02:00
Arkadiusz Fal
55a0b2dee6 Merge pull request #462 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-21 18:41:07 +02:00
Lionel Vallet
9f0700d2bf Translated using Weblate (French)
Currently translated at 99.0% (503 of 508 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-21 18:40:36 +02:00
Arkadiusz Fal
ab0c2e7b84 Translated using Weblate (Polish)
Currently translated at 100.0% (508 of 508 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-05-21 18:40:35 +02:00
Anonymous
d603ef7431 Translated using Weblate (English)
Currently translated at 100.0% (508 of 508 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-21 18:40:34 +02:00
mere
281e0510cd Translated using Weblate (Romanian)
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2023-05-21 11:04:56 +02:00
joaooliva
837c9a3f75 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-05-21 11:04:55 +02:00
Ophiushi
bcec9d09ab Translated using Weblate (French)
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-21 11:04:55 +02:00
Lionel Vallet
82a09d1584 Translated using Weblate (French)
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-21 11:04:54 +02:00
Bharathi
dae667fa8a Translated using Weblate (German)
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-05-21 11:04:54 +02:00
Arkadiusz Fal
0f802684f2 Translated using Weblate (Polish)
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-05-21 11:04:53 +02:00
Anonymous
59b49c2e2f Translated using Weblate (English)
Currently translated at 100.0% (503 of 503 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-21 11:04:53 +02:00
Arkadiusz Fal
3a8d6aed76 Bump build number to 148 2023-05-19 10:54:02 +02:00
Arkadiusz Fal
2952e10359 Update CHANGELOG 2023-05-19 10:54:02 +02:00
Arkadiusz Fal
59f84ec129 Merge pull request #460 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-19 10:53:07 +02:00
Dan
a76dae6656 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (500 of 500 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/uk/
2023-05-19 10:52:56 +02:00
Arkadiusz Fal
a208ef9147 Bump build number to 147 2023-05-19 09:59:31 +02:00
Arkadiusz Fal
7a998d2d69 Update CHANGELOG 2023-05-19 09:59:31 +02:00
Arkadiusz Fal
f3659904dc Fix keyboard issue with account/instance form on iOS 2023-05-19 09:55:25 +02:00
Arkadiusz Fal
72246448f1 Localizations fixes 2023-05-19 09:55:04 +02:00
Arkadiusz Fal
6617ad5fc6 Fix player width on macOS 2023-05-19 09:54:35 +02:00
Arkadiusz Fal
65b3eb60d9 Merge pull request #459 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-19 09:54:19 +02:00
Arkadiusz Fal
0174d2f8a0 Translated using Weblate (Polish)
Currently translated at 100.0% (500 of 500 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-05-19 09:54:00 +02:00
Anonymous
8f340586a6 Translated using Weblate (English)
Currently translated at 100.0% (500 of 500 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-19 09:54:00 +02:00
Anonymous
23e07baa7a Translated using Weblate (English)
Currently translated at 100.0% (499 of 499 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-19 09:48:46 +02:00
Arkadiusz Fal
e0e0352238 Bump build number to 146 2023-05-18 11:34:44 +02:00
Arkadiusz Fal
543a7c0da6 Update CHANGELOG 2023-05-18 11:34:44 +02:00
Arkadiusz Fal
c78a0dc8c3 Fix playback settings sheet height 2023-05-18 11:32:45 +02:00
Arkadiusz Fal
0051b3ab74 Minor change 2023-05-18 11:32:45 +02:00
Arkadiusz Fal
1f0a2d25e9 Localization fix 2023-05-18 11:32:45 +02:00
Arkadiusz Fal
5d8e8483d1 Minor performance improvement 2023-05-18 11:32:45 +02:00
Arkadiusz Fal
7972498f2c Merge pull request #454 from mikhailokarpenko/localization/ukrainian-translation
cover more ukrainian translation
2023-05-18 11:31:14 +02:00
Arkadiusz Fal
703ac90a33 Merge pull request #455 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-18 11:30:26 +02:00
maboroshin
265c4cd95c Translated using Weblate (Japanese)
Currently translated at 98.9% (491 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-05-15 00:47:45 +02:00
ButterflyOfFire
b7929465a7 Translated using Weblate (Arabic)
Currently translated at 86.8% (431 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ar/
2023-05-15 00:47:45 +02:00
Francesco
669f7d5aa6 Translated using Weblate (Italian)
Currently translated at 100.0% (496 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/it/
2023-05-15 00:47:44 +02:00
Ophiushi
3136d6328d Translated using Weblate (French)
Currently translated at 100.0% (496 of 496 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-15 00:47:44 +02:00
Mike
32b19d8cd5 cover more ukrainian translation 2023-05-14 12:12:42 -04:00
61 changed files with 1530 additions and 818 deletions

View File

@@ -23,7 +23,9 @@ jobs:
testflight:
strategy:
matrix:
lane: ['mac beta', 'ios beta', 'tvos beta']
# disabled mac beta lane
# lane: ['mac beta', 'ios beta', 'tvos beta']
lane: ['ios beta', 'tvos beta']
name: Releasing ${{ matrix.lane }} version to TestFlight
runs-on: macos-latest
steps:

View File

@@ -0,0 +1,21 @@
import SwiftUI
extension Backport where Content: View {
@ViewBuilder func toolbarBackground(_ color: Color) -> some View {
if #available(iOS 16, *) {
content
.toolbarBackground(color, for: .navigationBar)
} else {
content
}
}
@ViewBuilder func toolbarBackgroundVisibility(_ visible: Bool) -> some View {
if #available(iOS 16, *) {
content
.toolbarBackground(visible ? .visible : .hidden, for: .navigationBar)
} else {
content
}
}
}

View File

@@ -0,0 +1,12 @@
import SwiftUI
extension Backport where Content: View {
@ViewBuilder func toolbarColorScheme(_ colorScheme: ColorScheme) -> some View {
if #available(iOS 16, *) {
content
.toolbarColorScheme(colorScheme, for: .navigationBar)
} else {
content
}
}
}

View File

@@ -1,4 +1,16 @@
## Build 144
* Added button to scroll to top in comments and setting to toggle it
* Switch seek duration +/- buttons in controls settings
* Other minor fixes
## Build 150
* Added support for AVPlayer native system controls on iOS and macOS
- Use system features such as AirPlay, subtitles switching (Piped with HLS), text detection and copy and more
- Added Controls setting: "Use system controls with AVPlayer"
* Player rotates for landscape videos on entering full screen on iOS
- Player > Orientation setting: "Rotate when entering fullscreen on landscape video"
* Added Player > Playback setting: "Close video and player on end"
* Added reporting for opening stream in OSD for AVPlayer
* Fixed issue with opening channels and playlists links
* Fixed issues where controls/player layout could break (e.g., when going to background and back)
* Fixed issue where stream picker would show duplicate entries
* Fixed issue where search suggestions would show unnecessary bottom padding
* Fixed landscape channel sheet layout in player
* Fixed reported crashes
* Localization updates and fixes
* Other minor fixes and improvements

View File

@@ -0,0 +1,11 @@
import AVKit
extension AVPlayerViewController {
func enterFullScreen(animated: Bool) {
perform(NSSelectorFromString("enterFullScreenAnimated:completionHandler:"), with: animated, with: nil)
}
func exitFullScreen(animated: Bool) {
perform(NSSelectorFromString("exitFullScreenAnimated:completionHandler:"), with: animated, with: nil)
}
}

View File

@@ -1,4 +1,4 @@
import AVFoundation
import AVKit
import Defaults
import Foundation
import Logging
@@ -6,6 +6,7 @@ import MediaPlayer
#if !os(macOS)
import UIKit
#endif
import SwiftUI
final class AVPlayerBackend: PlayerBackend {
static let assetKeysToLoad = ["tracks", "playable", "duration"]
@@ -37,9 +38,7 @@ final class AVPlayerBackend: PlayerBackend {
!avPlayer.currentItem.isNil
}
var isLoadingVideo: Bool {
model.currentItem == nil || model.time == nil || !model.time!.isValid
}
var isLoadingVideo = false
var isPlaying: Bool {
avPlayer.timeControlStatus == .playing
@@ -84,6 +83,10 @@ final class AVPlayerBackend: PlayerBackend {
private(set) var playerLayer = AVPlayerLayer()
#if os(tvOS)
var controller: AppleAVPlayerViewController?
#elseif os(iOS)
var controller = AVPlayerViewController() { didSet {
controller.player = avPlayer
}}
#endif
var startPictureInPictureOnPlay = false
var startPictureInPictureOnSwitch = false
@@ -108,6 +111,9 @@ final class AVPlayerBackend: PlayerBackend {
addPlayerTimeControlStatusObserver()
playerLayer.player = avPlayer
#if os(iOS)
controller.player = avPlayer
#endif
}
func bestPlayable(_ streams: [Stream], maxResolution: ResolutionSetting) -> Stream? {
@@ -130,6 +136,8 @@ final class AVPlayerBackend: PlayerBackend {
preservingTime: Bool,
upgrading _: Bool
) {
isLoadingVideo = true
if let url = stream.singleAssetURL {
model.logger.info("playing stream with one asset\(stream.kind == .hls ? " (HLS)" : ""): \(url)")
@@ -467,12 +475,10 @@ final class AVPlayerBackend: PlayerBackend {
return
}
self.isLoadingVideo = false
switch playerItem.status {
case .readyToPlay:
if self.model.playingInPictureInPicture {
self.startPictureInPictureOnSwitch = false
self.startPictureInPictureOnPlay = false
}
if self.model.activeBackend == .appleAVPlayer,
self.isAutoplaying(playerItem)
{
@@ -487,17 +493,21 @@ final class AVPlayerBackend: PlayerBackend {
self.model.play()
}
} else if self.startPictureInPictureOnPlay {
self.startPictureInPictureOnPlay = false
self.model.stream = self.stream
self.model.streamSelection = self.stream
if self.model.activeBackend != .appleAVPlayer {
self.startPictureInPictureOnSwitch = true
let seconds = self.model.mpvBackend.currentTime?.seconds ?? 0
self.seek(to: seconds, seekType: .backendSync) { _ in
self.seek(to: seconds, seekType: .backendSync) { finished in
guard finished else { return }
DispatchQueue.main.async {
self.model.pause()
self.model.changeActiveBackend(from: .mpv, to: .appleAVPlayer, changingStream: false)
Delay.by(3) {
self.startPictureInPictureOnPlay = false
}
}
}
}
@@ -688,7 +698,6 @@ final class AVPlayerBackend: PlayerBackend {
func didChangeTo() {
if startPictureInPictureOnSwitch {
startPictureInPictureOnSwitch = false
tryStartingPictureInPicture()
} else if model.musicMode {
startMusicMode()
@@ -697,6 +706,10 @@ final class AVPlayerBackend: PlayerBackend {
}
}
var isStartingPiP: Bool {
startPictureInPictureOnPlay || startPictureInPictureOnSwitch
}
func tryStartingPictureInPicture() {
guard let controller = model.pipController else { return }
@@ -712,6 +725,32 @@ final class AVPlayerBackend: PlayerBackend {
}
}
}
Delay.by(5) {
self.startPictureInPictureOnSwitch = false
}
}
func setPlayerInLayer(_ playerIsPresented: Bool) {
if playerIsPresented {
bindPlayerToLayer()
} else {
removePlayerFromLayer()
}
}
func removePlayerFromLayer() {
playerLayer.player = nil
#if os(iOS)
controller.player = nil
#endif
}
func bindPlayerToLayer() {
playerLayer.player = avPlayer
#if os(iOS)
controller.player = avPlayer
#endif
}
func getTimeUpdates() {}

View File

@@ -222,7 +222,9 @@ final class MPVBackend: PlayerBackend {
func playStream(_ stream: Stream, of video: Video, preservingTime: Bool, upgrading: Bool) {
#if !os(macOS)
if model.presentingPlayer {
UIApplication.shared.isIdleTimerDisabled = true
DispatchQueue.main.async {
UIApplication.shared.isIdleTimerDisabled = true
}
}
#endif

View File

@@ -318,14 +318,14 @@ final class MPVClient: ObservableObject {
DispatchQueue.main.async { [weak self] in
guard let self else { return }
let model = self.backend.model
let aspectRatio = self.aspectRatio > 0 && self.aspectRatio < VideoPlayerView.defaultAspectRatio ? self.aspectRatio : VideoPlayerView.defaultAspectRatio
let height = [model.playerSize.height, model.playerSize.width / aspectRatio].min()!
var insets = 0.0
#if os(iOS)
insets = OrientationTracker.shared.currentInterfaceOrientation.isPortrait ? SafeAreaModel.shared.safeArea.bottom : 0
#endif
let offsetY = max(0, model.playingFullScreen ? ((model.playerSize.height / 2.0) - ((height + insets) / 2)) : 0)
UIView.animate(withDuration: 0.2, animations: {
let aspectRatio = self.aspectRatio > 0 && self.aspectRatio < VideoPlayerView.defaultAspectRatio ? self.aspectRatio : VideoPlayerView.defaultAspectRatio
let height = [model.playerSize.height, model.playerSize.width / aspectRatio].min()!
var insets = 0.0
#if os(iOS)
insets = OrientationTracker.shared.currentInterfaceOrientation.isPortrait ? SafeArea.insets.bottom : 0
#endif
let offsetY = model.playingFullScreen ? ((model.playerSize.height / 2.0) - ((height + insets) / 2)) : 0
self.glView?.frame = CGRect(x: 0, y: offsetY, width: roundedWidth, height: height)
}) { completion in
if completion {

View File

@@ -110,13 +110,15 @@ extension PlayerBackend {
model.prepareCurrentItemForHistory(finished: true)
if model.queue.isEmpty {
#if os(tvOS)
if model.activeBackend == .appleAVPlayer {
model.avPlayerBackend.controller?.dismiss(animated: false)
}
#endif
model.resetQueue()
model.hide()
if Defaults[.closeVideoOnEOF] {
#if os(tvOS)
if model.activeBackend == .appleAVPlayer {
model.avPlayerBackend.controller?.dismiss(animated: false)
}
#endif
model.resetQueue()
model.hide()
}
} else {
model.advanceToNextItem()
}

View File

@@ -4,7 +4,7 @@ import Foundation
import SwiftUI
final class PiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
var player: PlayerModel!
var player: PlayerModel { .shared }
func pictureInPictureController(
_: AVPictureInPictureController,
@@ -16,19 +16,17 @@ final class PiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
func pictureInPictureControllerWillStartPictureInPicture(_: AVPictureInPictureController) {}
func pictureInPictureControllerDidStartPictureInPicture(_: AVPictureInPictureController) {
guard let player else { return }
player.play()
player.playingInPictureInPicture = true
player.avPlayerBackend.startPictureInPictureOnPlay = false
player.avPlayerBackend.startPictureInPictureOnSwitch = false
player.controls.objectWillChange.send()
if Defaults[.closePlayerOnOpeningPiP] { Delay.by(0.1) { player.hide() } }
if Defaults[.closePlayerOnOpeningPiP] { Delay.by(0.1) { self.player.hide() } }
}
func pictureInPictureControllerDidStopPictureInPicture(_: AVPictureInPictureController) {
guard let player else { return }
player.playingInPictureInPicture = false
player.controls.objectWillChange.send()
}
@@ -39,6 +37,8 @@ final class PiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
_: AVPictureInPictureController,
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
) {
let wasPlaying = player.isPlaying
var delay = 0.0
#if os(iOS)
if !player.presentingPlayer {
@@ -50,7 +50,7 @@ final class PiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
#endif
if !player.currentItem.isNil, !player.musicMode {
player?.show()
player.show()
}
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
@@ -58,6 +58,11 @@ final class PiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
self?.player.playingInPictureInPicture = false
}
if wasPlaying {
Delay.by(1) {
self?.player.play()
}
}
completionHandler(true)
}
}

View File

@@ -49,7 +49,6 @@ final class PlayerModel: ObservableObject {
let logger = Logger(label: "stream.yattee.app")
var avPlayerView = AppleAVPlayerView()
var playerItem: AVPlayerItem?
var mpvPlayerView = MPVPlayerView()
@@ -129,6 +128,7 @@ final class PlayerModel: ObservableObject {
#if os(iOS)
@Published var lockedOrientation: UIInterfaceOrientationMask?
@Default(.rotateToPortraitOnExitFullScreen) private var rotateToPortraitOnExitFullScreen
@Default(.rotateToLandscapeOnEnterFullScreen) private var rotateToLandscapeOnEnterFullScreen
#endif
var accounts: AccountsModel { .shared }
@@ -152,6 +152,9 @@ final class PlayerModel: ObservableObject {
@Published var playingInPictureInPicture = false
var pipController: AVPictureInPictureController?
var pipDelegate = PiPDelegate()
#if !os(macOS)
var appleAVPlayerViewControllerDelegate = AppleAVPlayerViewControllerDelegate()
#endif
var playerError: Error? { didSet {
if let error = playerError {
@@ -163,6 +166,7 @@ final class PlayerModel: ObservableObject {
@Default(.saveLastPlayed) var saveLastPlayed
@Default(.lastPlayed) var lastPlayed
@Default(.qualityProfiles) var qualityProfiles
@Default(.avPlayerUsesSystemControls) var avPlayerUsesSystemControls
@Default(.forceAVPlayerForLiveStreams) var forceAVPlayerForLiveStreams
@Default(.pauseOnHidingPlayer) private var pauseOnHidingPlayer
@Default(.closePiPOnNavigation) var closePiPOnNavigation
@@ -186,16 +190,17 @@ final class PlayerModel: ObservableObject {
mpvBackend.client = mpvController.client
#endif
Defaults[.activeBackend] = .mpv
playbackMode = Defaults[.playbackMode]
guard pipController.isNil else { return }
pipController = .init(playerLayer: avPlayerBackend.playerLayer)
let pipDelegate = PiPDelegate()
pipDelegate.player = self
self.pipDelegate = pipDelegate
pipController = .init(playerLayer: avPlayerBackend.playerLayer)
pipController?.delegate = pipDelegate
#if os(iOS)
if #available(iOS 14.2, *) {
pipController?.canStartPictureInPictureAutomaticallyFromInline = true
}
#endif
currentRate = playerRate
}
@@ -459,18 +464,29 @@ final class PlayerModel: ObservableObject {
return
}
streamSelection = stream
playStream(
stream,
of: currentVideo,
preservingTime: !currentItem.playbackTime.isNil
)
DispatchQueue.global(qos: .userInitiated).async {
DispatchQueue.main.async {
self.streamSelection = stream
}
self.playStream(
stream,
of: currentVideo,
preservingTime: !self.currentItem.playbackTime.isNil
)
}
}
private func handlePresentationChange() {
backend.setNeedsDrawing(presentingPlayer)
#if os(iOS)
if presentingPlayer, activeBackend == .appleAVPlayer, avPlayerUsesSystemControls, Constants.isIPhone {
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
}
#endif
controls.hide()
controls.hideOverlays()
#if !os(macOS)
UIApplication.shared.isIdleTimerDisabled = presentingPlayer
@@ -487,6 +503,18 @@ final class PlayerModel: ObservableObject {
self?.pause()
}
}
if !presentingPlayer {
#if os(iOS)
if Defaults[.lockPortraitWhenBrowsing] {
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
} else {
Orientation.lockOrientation(.allButUpsideDown)
}
OrientationModel.shared.stopOrientationUpdates()
#endif
}
}
func changeActiveBackend(from: PlayerBackendType, to: PlayerBackendType, changingStream: Bool = true) {
@@ -537,10 +565,15 @@ final class PlayerModel: ObservableObject {
self.stream = stream
streamSelection = stream
self.upgradeToStream(stream, force: true)
return
}
if !backend.canPlay(stream) || (to == .mpv && !stream.hlsURL.isNil) {
if !backend.canPlay(stream) ||
(to == .mpv && stream.isHLS) ||
(to == .appleAVPlayer && !stream.isHLS)
{
guard let preferredStream = streamByQualityProfile else {
return
}
@@ -626,8 +659,8 @@ final class PlayerModel: ObservableObject {
if avPlayerBackend.video == video {
if activeBackend != .appleAVPlayer {
avPlayerBackend.startPictureInPictureOnSwitch = true
changeActiveBackend(from: activeBackend, to: .appleAVPlayer)
}
changeActiveBackend(from: activeBackend, to: .appleAVPlayer)
} else {
avPlayerBackend.startPictureInPictureOnPlay = true
playStream(stream, of: video, preservingTime: true, upgrading: true, withBackend: avPlayerBackend)
@@ -877,7 +910,7 @@ final class PlayerModel: ObservableObject {
#else
func handleEnterForeground() {
setNeedsDrawing(presentingPlayer)
avPlayerBackend.playerLayer.player = avPlayerBackend.avPlayer
avPlayerBackend.bindPlayerToLayer()
guard closePiPAndOpenPlayerOnEnteringForeground, playingInPictureInPicture else {
return
@@ -891,7 +924,7 @@ final class PlayerModel: ObservableObject {
if Defaults[.pauseOnEnteringBackground], !playingInPictureInPicture, !musicMode {
pause()
} else if !playingInPictureInPicture {
avPlayerBackend.playerLayer.player = nil
avPlayerBackend.removePlayerFromLayer()
}
}
#endif
@@ -914,6 +947,13 @@ final class PlayerModel: ObservableObject {
#if os(tvOS)
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
@@ -977,24 +1017,34 @@ final class PlayerModel: ObservableObject {
Windows.player.toggleFullScreen()
#endif
playingFullScreen = !isFullScreen
#if os(iOS)
if !playingFullScreen {
playingFullScreen = true
Orientation.lockOrientation(.allButUpsideDown)
if playingFullScreen {
if activeBackend == .appleAVPlayer, avPlayerUsesSystemControls {
avPlayerBackend.controller.enterFullScreen(animated: true)
return
}
guard rotateToLandscapeOnEnterFullScreen.isRotating else { return }
if currentVideoIsLandscape {
let delay = activeBackend == .appleAVPlayer && avPlayerUsesSystemControls ? 0.8 : 0
// not sure why but first rotation call is ignore so doing rotate to same orientation first
Delay.by(delay) {
let orientation = OrientationTracker.shared.currentDeviceOrientation.isLandscape ? OrientationTracker.shared.currentInterfaceOrientation : self.rotateToLandscapeOnEnterFullScreen.interaceOrientation
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: OrientationTracker.shared.currentInterfaceOrientation)
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: orientation)
}
}
} else {
if activeBackend == .appleAVPlayer, avPlayerUsesSystemControls {
avPlayerBackend.controller.exitFullScreen(animated: true)
avPlayerBackend.controller.dismiss(animated: true)
return
}
let rotationOrientation = rotateToPortraitOnExitFullScreen ? UIInterfaceOrientation.portrait : nil
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: rotationOrientation)
// TODO: rework to move view before rotating
if SafeArea.insets.left > 0 {
Delay.by(0.15) {
self.playingFullScreen = false
}
} else {
self.playingFullScreen = false
}
}
#else
playingFullScreen = !isFullScreen
#endif
}
@@ -1032,6 +1082,12 @@ final class PlayerModel: ObservableObject {
#endif
}
var currentVideoIsLandscape: Bool {
guard currentVideo != nil else { return false }
return aspectRatio > 1
}
var formattedSize: String {
guard let videoWidth = backend?.videoWidth, let videoHeight = backend?.videoHeight else { return "unknown" }
return "\(String(format: "%.2f", videoWidth))\u{d7}\(String(format: "%.2f", videoHeight))"

View File

@@ -88,7 +88,7 @@ extension PlayerModel {
guard let playerInstance = self.playerInstance else { return }
let streamsInstance = video.streams.compactMap(\.instance).first
if video.streams.isEmpty || streamsInstance != playerInstance {
if video.streams.isEmpty || streamsInstance.isNil || streamsInstance!.apiURLString != playerInstance.apiURLString {
self.loadAvailableStreams(video) { [weak self] _ in
self?.videoBeingOpened = nil
}

View File

@@ -32,6 +32,17 @@ extension PlayerModel {
}
}
var playerItemEndTimeWithSegments: CMTime? {
if let duration = playerItemDuration,
let segment = sponsorBlock.segments.last,
segment.endTime.seconds >= duration.seconds - 3
{
return segment.endTime
}
return playerItemDuration
}
private func skip(_ segment: Segment, at time: CMTime) {
if let duration = playerItemDuration, segment.endTime.seconds >= duration.seconds - 3 {
logger.error("segment end time is: \(segment.end) when player item duration is: \(duration.seconds)")
@@ -95,6 +106,8 @@ extension PlayerModel {
func resetSegments() {
resetLastSegment()
restoredSegments = []
DispatchQueue.main.async { [weak self] in
self?.restoredSegments = []
}
}
}

View File

@@ -41,7 +41,7 @@ extension PlayerModel {
self.logger.info("ignoring loaded streams from \(instance.description) as current video has changed")
return
}
self.availableStreams += self.streamsWithInstance(instance: instance, streams: video.streams)
self.availableStreams = self.streamsWithInstance(instance: instance, streams: video.streams)
} else {
self.logger.critical("no streams available from \(instance.description)")
}

View File

@@ -176,6 +176,10 @@ class Stream: Equatable, Hashable, Identifiable {
localURL != nil
}
var isHLS: Bool {
hlsURL != nil
}
var quality: String {
guard localURL.isNil else { return "Opened File" }
return kind == .hls ? "adaptive (HLS)" : "\(resolution.name)\(kind == .stream ? " (\(kind.rawValue))" : "")"
@@ -229,8 +233,14 @@ class Stream: Equatable, Hashable, Identifiable {
}
func hash(into hasher: inout Hasher) {
hasher.combine(videoAsset?.url)
hasher.combine(audioAsset?.url)
hasher.combine(hlsURL)
if let url = videoAsset?.url {
hasher.combine(url)
}
if let url = audioAsset?.url {
hasher.combine(url)
}
if let url = hlsURL {
hasher.combine(url)
}
}
}

View File

@@ -42,7 +42,7 @@ final class ShareViewController: SLComposeServiceViewController {
self.open(url: url)
}
self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil)
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
}
}
}

View File

@@ -85,7 +85,6 @@ struct ChannelPlaylistView: View {
}
}
#endif
#if os(macOS)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
if showCloseButton {
@@ -97,7 +96,9 @@ struct ChannelPlaylistView: View {
.buttonStyle(.plain)
}
}
}
#if os(macOS)
.toolbar {
ToolbarItem(placement: playlistButtonsPlacement) {
HStack {
ListingStyleButtons(listingStyle: $channelPlaylistListingStyle)

View File

@@ -66,7 +66,7 @@ struct ChannelVideosView: View {
.frame(maxWidth: .infinity)
#endif
VerticalCells(items: contentItems) {
VerticalCells(items: contentItems, edgesIgnoringSafeArea: .init()) {
if let description = presentedChannel?.description, !description.isEmpty {
Button {
withAnimation(.spring()) {

View File

@@ -132,6 +132,7 @@ extension Defaults.Keys {
static let playerControlsLayout = Key<PlayerControlsLayout>("playerControlsLayout", default: playerControlsLayoutDefault)
static let fullScreenPlayerControlsLayout = Key<PlayerControlsLayout>("fullScreenPlayerControlsLayout", default: fullScreenPlayerControlsLayoutDefault)
static let avPlayerUsesSystemControls = Key<Bool>("avPlayerUsesSystemControls", default: true)
static let horizontalPlayerGestureEnabled = Key<Bool>("horizontalPlayerGestureEnabled", default: true)
static let seekGestureSpeed = Key<Double>("seekGestureSpeed", default: 0.5)
static let seekGestureSensitivity = Key<Double>("seekGestureSensitivity", default: 30.0)
@@ -157,6 +158,7 @@ extension Defaults.Keys {
#if !os(macOS)
static let pauseOnEnteringBackground = Key<Bool>("pauseOnEnteringBackground", default: true)
#endif
static let closeVideoOnEOF = Key<Bool>("closeVideoOnEOF", default: false)
static let closePiPOnNavigation = Key<Bool>("closePiPOnNavigation", default: false)
static let closePiPOnOpeningPlayer = Key<Bool>("closePiPOnOpeningPlayer", default: false)
#if !os(macOS)
@@ -189,6 +191,10 @@ extension Defaults.Keys {
static let honorSystemOrientationLock = Key<Bool>("honorSystemOrientationLock", default: true)
static let enterFullscreenInLandscape = Key<Bool>("enterFullscreenInLandscape", default: UIDevice.current.userInterfaceIdiom == .phone)
static let rotateToPortraitOnExitFullScreen = Key<Bool>("rotateToPortraitOnExitFullScreen", default: UIDevice.current.userInterfaceIdiom == .phone)
static let rotateToLandscapeOnEnterFullScreen = Key<FullScreenRotationSetting>(
"rotateToLandscapeOnEnterFullScreen",
default: UIDevice.current.userInterfaceIdiom == .phone ? .landscapeRight : .disabled
)
#endif
static let showMPVPlaybackStats = Key<Bool>("showMPVPlaybackStats", default: false)
@@ -416,3 +422,26 @@ enum PlayerTapGestureAction: String, CaseIterable, Defaults.Serializable {
}
}
}
enum FullScreenRotationSetting: String, CaseIterable, Defaults.Serializable {
case disabled
case landscapeLeft
case landscapeRight
#if os(iOS)
var interaceOrientation: UIInterfaceOrientation {
switch self {
case .landscapeLeft:
return .landscapeLeft
case .landscapeRight:
return .landscapeRight
default:
return .portrait
}
}
#endif
var isRotating: Bool {
self != .disabled
}
}

View File

@@ -144,7 +144,7 @@ struct HomeView: View {
navigation.presentAlert(
Alert(
title: Text("Are you sure you want to clear history of watched videos?"),
message: Text("It cannot be reverted"),
message: Text("This cannot be reverted"),
primaryButton: .destructive(Text("Clear All")) {
PlayerModel.shared.removeHistory()
historyID = UUID()

View File

@@ -44,10 +44,10 @@ struct QueueView: View {
var label: String {
if items.count < 2 {
return "Next in Queue"
return "Next in Queue".localized()
}
return "Next in Queue (\(items.count))"
return "Next in Queue".localized() + " (\(items.count))"
}
var limitedItems: [ContentItem] {

View File

@@ -15,22 +15,36 @@ struct ContentView: View {
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
#endif
@Default(.avPlayerUsesSystemControls) private var avPlayerUsesSystemControls
var body: some View {
Group {
#if os(iOS)
if Constants.isIPhone {
AppTabNavigation()
} else {
if horizontalSizeClass == .compact {
AppTabNavigation()
} else {
AppSidebarNavigation()
GeometryReader { proxy in
Group {
#if os(iOS)
Group {
if Constants.isIPhone {
AppTabNavigation()
} else {
if horizontalSizeClass == .compact {
AppTabNavigation()
} else {
AppSidebarNavigation()
}
}
}
}
#elseif os(macOS)
AppSidebarNavigation()
#elseif os(tvOS)
TVNavigationView()
#elseif os(macOS)
AppSidebarNavigation()
#elseif os(tvOS)
TVNavigationView()
#endif
}
#if !os(macOS)
.onAppear {
SafeAreaModel.shared.safeArea = proxy.safeAreaInsets
}
.onChange(of: proxy.safeAreaInsets) { newValue in
SafeAreaModel.shared.safeArea = newValue
}
#endif
}
#if os(iOS)
@@ -88,7 +102,7 @@ struct ContentView: View {
}
.onOpenURL { url in
URLBookmarkModel.shared.saveBookmark(url)
OpenURLHandler.shared.handle(url)
OpenURLHandler(navigationStyle: navigationStyle).handle(url)
}
.background(
EmptyView().sheet(isPresented: $navigation.presentingAddToPlaylist) {
@@ -123,6 +137,9 @@ struct ContentView: View {
)
#endif
.alert(isPresented: $navigation.presentingAlert) { navigation.alert }
#if os(iOS)
.statusBarHidden(player.playingFullScreen)
#endif
}
var navigationStyle: NavigationStyle {
@@ -140,9 +157,11 @@ struct ContentView: View {
playerView
.transition(.asymmetric(insertion: .identity, removal: .opacity))
.zIndex(3)
} else if player.activeBackend == .appleAVPlayer {
} else if player.activeBackend == .appleAVPlayer,
avPlayerUsesSystemControls || player.avPlayerBackend.isStartingPiP
{
#if os(iOS)
playerView.offset(y: UIScreen.main.bounds.height)
AppleAVPlayerLayerView().offset(y: UIScreen.main.bounds.height)
#endif
}
}

View File

@@ -4,7 +4,6 @@ import Siesta
struct OpenURLHandler {
static var firstHandle = true
static var shared = Self()
static let yatteeProtocol = "yattee://"
var accounts: AccountsModel { .shared }
@@ -12,7 +11,7 @@ struct OpenURLHandler {
var recents: RecentsModel { .shared }
var player: PlayerModel { .shared }
var search: SearchModel { .shared }
var navigationStyle = NavigationStyle.sidebar
var navigationStyle: NavigationStyle
func handle(_ url: URL) {
if accounts.current.isNil {

View File

@@ -2,15 +2,133 @@ import AVKit
import Defaults
import SwiftUI
#if !os(macOS)
final class AppleAVPlayerViewControllerDelegate: NSObject, AVPlayerViewControllerDelegate {
#if os(iOS)
@Default(.rotateToLandscapeOnEnterFullScreen) private var rotateToLandscapeOnEnterFullScreen
@Default(.avPlayerUsesSystemControls) private var avPlayerUsesSystemControls
#endif
var player: PlayerModel { .shared }
func playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart(_: AVPlayerViewController) -> Bool {
false
}
func playerViewController(_: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator) {
#if os(iOS)
guard rotateToLandscapeOnEnterFullScreen.isRotating else { return }
if PlayerModel.shared.currentVideoIsLandscape {
let delay = PlayerModel.shared.activeBackend == .appleAVPlayer && avPlayerUsesSystemControls ? 0.8 : 0
// not sure why but first rotation call is ignore so doing rotate to same orientation first
Delay.by(delay) {
let orientation = OrientationTracker.shared.currentDeviceOrientation.isLandscape ? OrientationTracker.shared.currentInterfaceOrientation : self.rotateToLandscapeOnEnterFullScreen.interaceOrientation
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: OrientationTracker.shared.currentInterfaceOrientation)
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: orientation)
}
}
#endif
}
func playerViewController(_: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
let wasPlaying = player.isPlaying
coordinator.animate(alongsideTransition: nil) { context in
#if os(iOS)
if wasPlaying {
self.player.play()
}
#endif
if !context.isCancelled {
#if os(iOS)
self.player.lockedOrientation = nil
if Defaults[.rotateToPortraitOnExitFullScreen] {
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
}
if wasPlaying {
self.player.play()
}
self.player.playingFullScreen = false
#endif
}
}
}
func playerViewControllerWillStartPictureInPicture(_: AVPlayerViewController) {}
func playerViewControllerWillStopPictureInPicture(_: AVPlayerViewController) {}
func playerViewControllerDidStartPictureInPicture(_: AVPlayerViewController) {
player.playingInPictureInPicture = true
player.avPlayerBackend.startPictureInPictureOnPlay = false
player.avPlayerBackend.startPictureInPictureOnSwitch = false
player.controls.objectWillChange.send()
if Defaults[.closePlayerOnOpeningPiP] { Delay.by(0.1) { self.player.hide() } }
}
func playerViewControllerDidStopPictureInPicture(_: AVPlayerViewController) {
player.playingInPictureInPicture = false
player.controls.objectWillChange.send()
}
func playerViewController(_: AVPlayerViewController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
player.presentingPlayer = true
withAnimation(.linear(duration: 0.3)) {
self.player.playingInPictureInPicture = false
Delay.by(0.5) {
completionHandler(true)
Delay.by(0.2) {
self.player.play()
}
}
}
}
func playerViewController(_: AVPlayerViewController, restoreUserInterfaceForFullScreenExitWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
withAnimation(nil) {
player.presentingPlayer = true
}
completionHandler(true)
}
}
#endif
#if os(iOS)
struct AppleAVPlayerView: UIViewRepresentable {
struct AppleAVPlayerView: UIViewControllerRepresentable {
@State private var controller = AVPlayerViewController()
func makeUIViewController(context _: Context) -> AVPlayerViewController {
setupController()
return controller
}
func updateUIViewController(_: AVPlayerViewController, context _: Context) {
setupController()
}
func setupController() {
controller.delegate = PlayerModel.shared.appleAVPlayerViewControllerDelegate
controller.allowsPictureInPicturePlayback = true
if #available(iOS 14.2, *) {
controller.canStartPictureInPictureAutomaticallyFromInline = true
}
PlayerModel.shared.avPlayerBackend.controller = controller
}
}
struct AppleAVPlayerLayerView: UIViewRepresentable {
func makeUIView(context _: Context) -> some UIView {
PlayerLayerView(frame: .zero)
}
func updateUIView(_: UIViewType, context _: Context) {}
}
#else
#elseif os(tvOS)
struct AppleAVPlayerView: UIViewControllerRepresentable {
func makeUIViewController(context _: Context) -> AppleAVPlayerViewController {
let controller = AppleAVPlayerViewController()
@@ -23,4 +141,27 @@ import SwiftUI
PlayerModel.shared.rebuildTVMenu()
}
}
#else
struct AppleAVPlayerView: NSViewRepresentable {
@State private var pictureInPictureDelegate = MacOSPiPDelegate()
func makeNSView(context _: Context) -> some NSView {
let view = AVPlayerView()
view.player = PlayerModel.shared.avPlayerBackend.avPlayer
view.showsFullScreenToggleButton = true
view.allowsPictureInPicturePlayback = true
view.pictureInPictureDelegate = pictureInPictureDelegate
return view
}
func updateNSView(_: NSViewType, context _: Context) {}
}
struct AppleAVPlayerLayerView: NSViewRepresentable {
func makeNSView(context _: Context) -> some NSView {
PlayerLayerView(frame: .zero)
}
func updateNSView(_: NSViewType, context _: Context) {}
}
#endif

View File

@@ -24,7 +24,7 @@ struct OpeningStream: View {
if let selection = player.streamSelection {
if selection.isLocal {
return "Opening file..."
return "Opening file...".localized()
} else {
return String(format: "Opening %@ stream...".localized(), selection.shortQuality)
}

View File

@@ -13,6 +13,7 @@ struct PlayerControls: View {
#if os(iOS)
@Environment(\.verticalSizeClass) private var verticalSizeClass
@ObservedObject private var safeAreaModel = SafeAreaModel.shared
#elseif os(tvOS)
enum Field: Hashable {
case seekOSD
@@ -41,6 +42,8 @@ struct PlayerControls: View {
@Default(.playerControlsPlaybackModeEnabled) private var playerControlsPlaybackModeEnabled
@Default(.playerControlsMusicModeEnabled) private var playerControlsMusicModeEnabled
@Default(.avPlayerUsesSystemControls) private var avPlayerUsesSystemControls
private let controlsOverlayModel = ControlOverlaysModel.shared
private var navigation = NavigationModel.shared
@@ -48,22 +51,28 @@ struct PlayerControls: View {
player.playingFullScreen ? fullScreenPlayerControlsLayout : regularPlayerControlsLayout
}
var showControls: Bool {
player.activeBackend == .mpv || !avPlayerUsesSystemControls
}
var body: some View {
ZStack(alignment: .topLeading) {
Seek()
.zIndex(4)
.transition(.opacity)
.frame(maxWidth: .infinity, alignment: .topLeading)
#if os(tvOS)
.focused($focusedField, equals: .seekOSD)
.onChange(of: player.seek.lastSeekTime) { _ in
if !model.presentingControls {
focusedField = .seekOSD
if showControls {
Seek()
.zIndex(4)
.transition(.opacity)
.frame(maxWidth: .infinity, alignment: .topLeading)
#if os(tvOS)
.focused($focusedField, equals: .seekOSD)
.onChange(of: player.seek.lastSeekTime) { _ in
if !model.presentingControls {
focusedField = .seekOSD
}
}
}
#else
.offset(y: 2)
#endif
#else
.offset(y: 2)
#endif
}
VStack {
ZStack {
@@ -77,106 +86,108 @@ struct PlayerControls: View {
}
.offset(y: playerControlsLayout.osdVerticalOffset + 5)
Section {
#if !os(tvOS)
HStack {
seekBackwardButton
Spacer()
togglePlayButton
Spacer()
seekForwardButton
}
.font(.system(size: playerControlsLayout.bigButtonFontSize))
#endif
ZStack(alignment: .bottom) {
VStack(spacing: 4) {
#if !os(tvOS)
buttonsBar
HStack {
if !player.currentVideo.isNil, player.playingFullScreen {
Button {
withAnimation(Self.animation) {
model.presentingDetailsOverlay = true
}
} label: {
ControlsBar(fullScreen: $model.presentingDetailsOverlay, expansionState: .constant(.full), presentingControls: false, detailsTogglePlayer: false, detailsToggleFullScreen: false)
.clipShape(RoundedRectangle(cornerRadius: 4))
.frame(maxWidth: 300, alignment: .leading)
}
.buttonStyle(.plain)
}
Spacer()
}
#endif
Spacer()
if playerControlsLayout.displaysTitleLine {
VStack(alignment: .leading) {
Text(player.videoForDisplay?.displayTitle ?? "Not Playing")
.shadow(radius: 10)
.font(.system(size: playerControlsLayout.titleLineFontSize).bold())
.lineLimit(1)
Text(player.currentVideo?.displayAuthor ?? "")
.fontWeight(.semibold)
.shadow(radius: 10)
.foregroundColor(.init(white: 0.8))
.font(.system(size: playerControlsLayout.authorLineFontSize))
.lineLimit(1)
}
.foregroundColor(.white)
.frame(maxWidth: .infinity, alignment: .leading)
.offset(y: -40)
}
timeline
.padding(.bottom, 2)
}
.zIndex(1)
.padding(.top, 2)
.transition(.opacity)
HStack(spacing: playerControlsLayout.buttonsSpacing) {
#if os(tvOS)
togglePlayButton
if showControls {
Section {
#if !os(tvOS)
HStack {
seekBackwardButton
Spacer()
togglePlayButton
Spacer()
seekForwardButton
#endif
if playerControlsRestartEnabled {
restartVideoButton
}
if playerControlsAdvanceToNextEnabled {
advanceToNextItemButton
}
Spacer()
#if os(tvOS)
if playerControlsSettingsEnabled {
settingsButton
.font(.system(size: playerControlsLayout.bigButtonFontSize))
#endif
ZStack(alignment: .bottom) {
VStack(spacing: 4) {
#if !os(tvOS)
buttonsBar
HStack {
if !player.currentVideo.isNil, player.playingFullScreen {
Button {
withAnimation(Self.animation) {
model.presentingDetailsOverlay = true
}
} label: {
ControlsBar(fullScreen: $model.presentingDetailsOverlay, expansionState: .constant(.full), presentingControls: false, detailsTogglePlayer: false, detailsToggleFullScreen: false)
.clipShape(RoundedRectangle(cornerRadius: 4))
.frame(maxWidth: 300, alignment: .leading)
}
.buttonStyle(.plain)
}
Spacer()
}
#endif
Spacer()
if playerControlsLayout.displaysTitleLine {
VStack(alignment: .leading) {
Text(player.videoForDisplay?.displayTitle ?? "Not Playing")
.shadow(radius: 10)
.font(.system(size: playerControlsLayout.titleLineFontSize).bold())
.lineLimit(1)
Text(player.currentVideo?.displayAuthor ?? "")
.fontWeight(.semibold)
.shadow(radius: 10)
.foregroundColor(.init(white: 0.8))
.font(.system(size: playerControlsLayout.authorLineFontSize))
.lineLimit(1)
}
.foregroundColor(.white)
.frame(maxWidth: .infinity, alignment: .leading)
.offset(y: -40)
}
#endif
if playerControlsPlaybackModeEnabled {
playbackModeButton
timeline
.padding(.bottom, 2)
}
.zIndex(1)
.padding(.top, 2)
.transition(.opacity)
HStack(spacing: playerControlsLayout.buttonsSpacing) {
#if os(tvOS)
togglePlayButton
seekBackwardButton
seekForwardButton
#endif
if playerControlsRestartEnabled {
restartVideoButton
}
if playerControlsAdvanceToNextEnabled {
advanceToNextItemButton
}
Spacer()
#if os(tvOS)
if playerControlsSettingsEnabled {
settingsButton
}
#endif
if playerControlsPlaybackModeEnabled {
playbackModeButton
}
#if os(tvOS)
closeVideoButton
#else
if playerControlsMusicModeEnabled {
musicModeButton
}
#endif
}
.zIndex(0)
#if os(tvOS)
closeVideoButton
.offset(y: -playerControlsLayout.timelineHeight - 30)
#else
if playerControlsMusicModeEnabled {
musicModeButton
}
.offset(y: -playerControlsLayout.timelineHeight - 5)
#endif
}
.zIndex(0)
#if os(tvOS)
.offset(y: -playerControlsLayout.timelineHeight - 30)
#else
.offset(y: -playerControlsLayout.timelineHeight - 5)
#endif
}
.opacity(model.presentingControls && !player.availableStreams.isEmpty ? 1 : 0)
}
.opacity(model.presentingControls && !player.availableStreams.isEmpty ? 1 : 0)
}
}
.frame(maxWidth: .infinity)
@@ -229,7 +240,7 @@ struct PlayerControls: View {
guard player.playerSize.height.isFinite else { return 200 }
var inset = 0.0
#if os(iOS)
inset = SafeArea.insets.bottom
inset = safeAreaModel.safeArea.bottom
#endif
return [player.playerSize.height - inset, 500].min()!
}

View File

@@ -13,7 +13,8 @@ struct ProgressBar: View {
Rectangle().frame(width: min(Double(self.value) * geometry.size.width, geometry.size.width), height: geometry.size.height)
.foregroundColor(Color.accentColor)
.animation(.linear)
}.cornerRadius(45.0)
}
.cornerRadius(45.0)
}
}
}

View File

@@ -4,6 +4,7 @@ import SwiftUI
struct TVControls: UIViewRepresentable {
var model: PlayerControlsModel!
var player: PlayerModel { .shared }
var safeArea: SafeAreaModel { .shared }
var thumbnails: ThumbnailsModel { .shared }
@State private var direction = ""
@@ -32,10 +33,10 @@ struct TVControls: UIViewRepresentable {
let controls = UIHostingController(rootView: PlayerControls())
controls.view.frame = .init(
origin: .init(x: SafeArea.insets.left, y: SafeArea.insets.top),
origin: .init(x: safeArea.safeArea.leading, y: safeArea.safeArea.top),
size: .init(
width: UIScreen.main.bounds.width - SafeArea.horizontalInsets,
height: UIScreen.main.bounds.height - SafeArea.verticalInset
width: UIScreen.main.bounds.width - safeArea.horizontalInsets,
height: UIScreen.main.bounds.height - safeArea.verticalInsets
)
)

View File

@@ -5,7 +5,7 @@ extension Backport where Content: View {
@ViewBuilder func playbackSettingsPresentationDetents() -> some View {
if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) {
content
.presentationDetents([.height(350), .large])
.presentationDetents([.height(400), .large])
} else {
content
}

View File

@@ -1,11 +1,15 @@
import Defaults
import SwiftUI
struct PlayerBackendView: View {
#if os(iOS)
@Environment(\.verticalSizeClass) private var verticalSizeClass
@ObservedObject private var safeAreaModel = SafeAreaModel.shared
#endif
@ObservedObject private var player = PlayerModel.shared
@Default(.avPlayerUsesSystemControls) private var avPlayerUsesSystemControls
var body: some View {
ZStack(alignment: .top) {
Group {
@@ -15,13 +19,23 @@ struct PlayerBackendView: View {
case .mpv:
player.mpvPlayerView
case .appleAVPlayer:
player.avPlayerView
#if os(tvOS)
AppleAVPlayerView()
#else
if avPlayerUsesSystemControls,
!player.playingInPictureInPicture,
!player.avPlayerBackend.isStartingPiP
{
AppleAVPlayerView()
} else if !avPlayerUsesSystemControls ||
player.avPlayerBackend.isStartingPiP
{
AppleAVPlayerLayerView()
}
#endif
}
}
.zIndex(0)
ControlsGradientView()
.zIndex(1)
}
}
.overlay(GeometryReader { proxy in
@@ -30,12 +44,11 @@ struct PlayerBackendView: View {
.onChange(of: proxy.size) { _ in player.playerSize = proxy.size }
.onChange(of: player.controls.presentingOverlays) { _ in player.playerSize = proxy.size }
})
#if os(iOS)
.padding(.top, player.playingFullScreen && verticalSizeClass == .regular ? 20 : 0)
#endif
#if !os(tvOS)
PlayerGestures()
if player.activeBackend == .mpv || !avPlayerUsesSystemControls {
PlayerGestures()
}
PlayerControls()
#if os(iOS)
.padding(.top, controlsTopPadding)
@@ -55,19 +68,17 @@ struct PlayerBackendView: View {
guard player.playingFullScreen else { return 0 }
if UIDevice.current.userInterfaceIdiom != .pad {
return verticalSizeClass == .compact ? SafeArea.insets.top : 0
return verticalSizeClass == .compact ? safeAreaModel.safeArea.top : 0
} else {
return SafeArea.insets.top.isZero ? SafeArea.insets.bottom : SafeArea.insets.top
return safeAreaModel.safeArea.top.isZero ? safeAreaModel.safeArea.bottom : safeAreaModel.safeArea.top
}
}
var controlsBottomPadding: Double {
guard player.playingFullScreen else { return 0 }
if UIDevice.current.userInterfaceIdiom != .pad {
return player.playingFullScreen && verticalSizeClass == .compact ? SafeArea.insets.bottom : 0
return player.playingFullScreen || verticalSizeClass == .compact ? safeAreaModel.safeArea.bottom : 0
} else {
return player.playingFullScreen ? SafeArea.insets.bottom : 0
return player.playingFullScreen ? safeAreaModel.safeArea.bottom : 0
}
}
#endif

View File

@@ -3,7 +3,7 @@ import SwiftUI
extension VideoPlayerView {
var playerDragGesture: some Gesture {
DragGesture(minimumDistance: 0, coordinateSpace: .global)
DragGesture(minimumDistance: 30, coordinateSpace: .global)
#if os(iOS)
.updating($dragGestureOffset) { value, state, _ in
guard isVerticalDrag else { return }
@@ -17,7 +17,8 @@ extension VideoPlayerView {
}
.onChanged { value in
guard player.presentingPlayer,
!controlsOverlayModel.presenting else { return }
!controlsOverlayModel.presenting,
dragGestureState else { return }
if player.controls.presentingControls, !player.musicMode {
player.controls.presentingControls = false
@@ -36,7 +37,12 @@ extension VideoPlayerView {
}
#endif
if !isVerticalDrag, horizontalPlayerGestureEnabled, abs(horizontalDrag) > seekGestureSensitivity, !isHorizontalDrag {
if !isVerticalDrag,
horizontalPlayerGestureEnabled,
abs(horizontalDrag) > seekGestureSensitivity,
!isHorizontalDrag,
player.activeBackend == .mpv || !avPlayerUsesSystemControls
{
isHorizontalDrag = true
player.seek.onSeekGestureStart()
viewDragOffset = 0
@@ -80,6 +86,16 @@ 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,

View File

@@ -5,42 +5,50 @@ struct PlayerGestures: View {
private var player = PlayerModel.shared
@ObservedObject private var model = PlayerControlsModel.shared
@Default(.avPlayerUsesSystemControls) private var avPlayerUsesSystemControls
var showGestures: Bool {
player.activeBackend == .mpv || !avPlayerUsesSystemControls
}
var body: some View {
HStack(spacing: 0) {
gestureRectangle
.tapRecognizer(
tapSensitivity: 0.2,
doubleTapAction: {
model.presentingControls = false
let interval = TimeInterval(Defaults[.gestureBackwardSeekDuration]) ?? 10
player.backend.seek(relative: .secondsInDefaultTimescale(-interval), seekType: .userInteracted)
},
anyTapAction: {
singleTapAction()
model.update()
}
)
if showGestures {
gestureRectangle
.tapRecognizer(
tapSensitivity: 0.2,
doubleTapAction: {
model.presentingControls = false
let interval = TimeInterval(Defaults[.gestureBackwardSeekDuration]) ?? 10
player.backend.seek(relative: .secondsInDefaultTimescale(-interval), seekType: .userInteracted)
},
anyTapAction: {
singleTapAction()
model.update()
}
)
gestureRectangle
.tapRecognizer(
tapSensitivity: 0.2,
doubleTapAction: {
model.presentingControls = false
player.backend.togglePlay()
},
anyTapAction: singleTapAction
)
gestureRectangle
.tapRecognizer(
tapSensitivity: 0.2,
doubleTapAction: {
model.presentingControls = false
player.backend.togglePlay()
},
anyTapAction: singleTapAction
)
gestureRectangle
.tapRecognizer(
tapSensitivity: 0.2,
doubleTapAction: {
model.presentingControls = false
let interval = TimeInterval(Defaults[.gestureForwardSeekDuration]) ?? 10
player.backend.seek(relative: .secondsInDefaultTimescale(interval), seekType: .userInteracted)
},
anyTapAction: singleTapAction
)
gestureRectangle
.tapRecognizer(
tapSensitivity: 0.2,
doubleTapAction: {
model.presentingControls = false
let interval = TimeInterval(Defaults[.gestureForwardSeekDuration]) ?? 10
player.backend.seek(relative: .secondsInDefaultTimescale(interval), seekType: .userInteracted)
},
anyTapAction: singleTapAction
)
}
}
}

View File

@@ -15,7 +15,7 @@ struct InspectorView: View {
videoDetailRow("Format", value: player.mpvBackend.videoFormat)
videoDetailRow("Codec", value: player.mpvBackend.videoCodec)
videoDetailRow("Hardware Decoder", value: player.mpvBackend.hwDecoder)
videoDetailRow("Hardware decoder", value: player.mpvBackend.hwDecoder)
videoDetailRow("Driver", value: player.mpvBackend.currentVo)
videoDetailRow("Size", value: player.formattedSize)
videoDetailRow("FPS", value: player.mpvBackend.formattedOutputFps)

View File

@@ -91,7 +91,7 @@ struct VideoActions: View {
case .share:
return video?.isShareable ?? false
case .addToPlaylist:
return !(video?.isLocal ?? true)
return !(video?.isLocal ?? true) && accounts.signedIn
case .subscribe:
return !(video?.isLocal ?? true) && accounts.signedIn && accounts.app.supportsSubscriptions
case .settings:
@@ -206,6 +206,7 @@ struct VideoActions: View {
.foregroundColor(active ? Color("AppRedColor") : .secondary)
.font(.caption2)
.allowsTightening(true)
.lineLimit(1)
}
}
.padding(.horizontal, playerActionsButtonLabelStyle.text ? 6 : 12)

View File

@@ -4,8 +4,8 @@ import SwiftUI
struct VideoPlayerSizeModifier: ViewModifier {
let geometry: GeometryProxy
let aspectRatio: Double?
let minimumHeightLeft: Double
let fullScreen: Bool
var detailsHiddenInFullScreen = true
#if os(iOS)
@Environment(\.verticalSizeClass) private var verticalSizeClass
@@ -14,26 +14,31 @@ struct VideoPlayerSizeModifier: ViewModifier {
init(
geometry: GeometryProxy,
aspectRatio: Double? = nil,
minimumHeightLeft: Double? = nil,
fullScreen: Bool = false
fullScreen: Bool = false,
detailsHiddenInFullScreen: Bool = false
) {
self.geometry = geometry
self.aspectRatio = aspectRatio ?? VideoPlayerView.defaultAspectRatio
self.minimumHeightLeft = minimumHeightLeft ?? VideoPlayerView.defaultMinimumHeightLeft
self.fullScreen = fullScreen
self.detailsHiddenInFullScreen = detailsHiddenInFullScreen
}
func body(content: Content) -> some View {
content
.frame(width: geometry.size.width)
.frame(maxWidth: geometry.size.width)
.frame(maxHeight: maxHeight)
#if !os(macOS)
.aspectRatio(fullScreen ? nil : usedAspectRatio, contentMode: usedAspectRatioContentMode)
.aspectRatio(ratio, contentMode: usedAspectRatioContentMode)
#endif
}
var ratio: CGFloat? {
fullScreen ? detailsHiddenInFullScreen ? nil : usedAspectRatio : usedAspectRatio
}
var usedAspectRatio: Double {
guard let aspectRatio, aspectRatio > 0, aspectRatio < VideoPlayerView.defaultAspectRatio else {
guard let aspectRatio, aspectRatio > 0 else {
return VideoPlayerView.defaultAspectRatio
}
@@ -50,15 +55,13 @@ struct VideoPlayerSizeModifier: ViewModifier {
var maxHeight: Double {
guard !fullScreen else {
return .infinity
if detailsHiddenInFullScreen {
return geometry.size.height
} else {
return geometry.size.width / usedAspectRatio
}
}
#if os(iOS)
let height = verticalSizeClass == .regular ? geometry.size.height - minimumHeightLeft : .infinity
#else
let height = geometry.size.height - minimumHeightLeft
#endif
return [height, 0].max()!
return max(geometry.size.height - VideoPlayerView.defaultMinimumHeightLeft, 0)
}
}

View File

@@ -37,10 +37,8 @@ struct VideoPlayerView: View {
#if os(iOS)
@Environment(\.verticalSizeClass) private var verticalSizeClass
@State internal var orientation = UIInterfaceOrientation.portrait
@State internal var lastOrientation: UIInterfaceOrientation?
@State internal var orientationDebouncer = Debouncer(.milliseconds(300))
@ObservedObject private var safeAreaModel = SafeAreaModel.shared
private var orientationModel = OrientationModel.shared
#elseif os(macOS)
var hoverThrottle = Throttle(interval: 0.5)
var mouseLocation: CGPoint { NSEvent.mouseLocation }
@@ -52,7 +50,6 @@ struct VideoPlayerView: View {
@State internal var isHorizontalDrag = false
@State internal var isVerticalDrag = false
@State internal var viewDragOffset = Self.hiddenOffset
@State internal var orientationObserver: Any?
#endif
@ObservedObject internal var player = PlayerModel.shared
@@ -67,6 +64,7 @@ struct VideoPlayerView: View {
@Default(.playerSidebar) var playerSidebar
@Default(.gestureBackwardSeekDuration) private var gestureBackwardSeekDuration
@Default(.gestureForwardSeekDuration) private var gestureForwardSeekDuration
@Default(.avPlayerUsesSystemControls) internal var avPlayerUsesSystemControls
@ObservedObject internal var controlsOverlayModel = ControlOverlaysModel.shared
@@ -89,14 +87,6 @@ struct VideoPlayerView: View {
.onChange(of: playerSidebar) { _ in
updateSidebarQueue()
}
#if os(macOS)
.background(
EmptyView().sheet(isPresented: $navigation.presentingChannelSheet) {
ChannelVideosView(channel: navigation.channelPresentedInSheet, showCloseButton: true, inNavigationView: false)
.frame(minWidth: 1000, minHeight: 700)
}
)
#endif
}
var videoPlayer: some View {
@@ -113,69 +103,52 @@ struct VideoPlayerView: View {
playerSize = geometry.size
}
}
#if os(iOS)
.padding(.bottom, fullScreenPlayer ? 0.0001 : geometry.safeAreaInsets.bottom)
#endif
.onChange(of: geometry.size) { _ in
self.playerSize = geometry.size
}
.onChange(of: fullScreenDetails) { value in
player.backend.setNeedsDrawing(!value)
}
#if os(iOS)
.frame(width: playerWidth.isNil ? nil : Double(playerWidth!), height: playerHeight.isNil ? nil : Double(playerHeight!))
.ignoresSafeArea(.all, edges: .bottom)
.onChange(of: player.presentingPlayer) { newValue in
if newValue {
#if os(iOS)
.frame(height: playerHeight.isNil ? nil : Double(playerHeight!))
#endif
.onChange(of: geometry.size) { _ in
self.playerSize = geometry.size
}
.onChange(of: fullScreenDetails) { value in
player.backend.setNeedsDrawing(!value)
}
#if os(iOS)
.onChange(of: player.presentingPlayer) { newValue in
if newValue {
viewDragOffset = 0
}
}
.onAppear {
#if os(macOS)
if player.videoForDisplay.isNil {
player.hide()
}
#endif
viewDragOffset = 0
}
}
.onAppear {
#if os(macOS)
if player.videoForDisplay.isNil {
player.hide()
}
#endif
viewDragOffset = 0
Delay.by(0.2) {
configureOrientationUpdatesBasedOnAccelerometer()
Delay.by(0.2) {
orientationModel.configureOrientationUpdatesBasedOnAccelerometer()
if let orientationMask = player.lockedOrientation {
Orientation.lockOrientation(
orientationMask,
andRotateTo: orientationMask == .landscapeLeft ? .landscapeLeft : orientationMask == .landscapeRight ? .landscapeRight : .portrait
)
} else {
Orientation.lockOrientation(.allButUpsideDown)
if let orientationMask = player.lockedOrientation {
Orientation.lockOrientation(
orientationMask,
andRotateTo: orientationMask == .landscapeLeft ? .landscapeLeft : orientationMask == .landscapeRight ? .landscapeRight : .portrait
)
} else {
Orientation.lockOrientation(.allButUpsideDown)
}
}
}
}
.onDisappear {
if Defaults[.lockPortraitWhenBrowsing] {
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
} else {
Orientation.lockOrientation(.allButUpsideDown)
.onAnimationCompleted(for: viewDragOffset) {
guard !dragGestureState else { return }
if viewDragOffset == 0 {
player.onPresentPlayer.forEach { $0() }
player.onPresentPlayer = []
} else if viewDragOffset == Self.hiddenOffset {
player.hide(animate: false)
}
}
stopOrientationUpdates()
player.controls.hideOverlays()
}
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
guard player.lockedOrientation.isNil else {
return
}
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: OrientationTracker.shared.currentInterfaceOrientation)
}
.onAnimationCompleted(for: viewDragOffset) {
guard !dragGestureState else { return }
if viewDragOffset == 0 {
player.onPresentPlayer.forEach { $0() }
player.onPresentPlayer = []
} else if viewDragOffset == Self.hiddenOffset {
player.hide(animate: false)
}
}
#endif
}
#if os(iOS)
@@ -189,7 +162,7 @@ struct VideoPlayerView: View {
.persistentSystemOverlays(!fullScreenPlayer)
#endif
#if os(macOS)
.frame(minWidth: 1000, minHeight: 700)
.frame(minWidth: 1100, minHeight: 700)
#endif
}
@@ -263,16 +236,21 @@ struct VideoPlayerView: View {
}
var playerOffset: Double {
dragGestureState && !isHorizontalDrag ? dragGestureOffset.height : viewDragOffset
dragGestureState && !isHorizontalDrag ? dragGestureOffset.height : dragOffset
}
var playerWidth: Double? {
fullScreenPlayer ? (UIScreen.main.bounds.size.width - SafeArea.insets.left - SafeArea.insets.right) : nil
var dragOffset: Double {
if viewDragOffset.isZero || viewDragOffset == Self.hiddenOffset {
return viewDragOffset
}
return player.presentingPlayer ? 0 : Self.hiddenOffset
}
var playerHeight: Double? {
let lockedPortrait = player.lockedOrientation?.contains(.portrait) ?? false
return fullScreenPlayer ? UIScreen.main.bounds.size.height - (OrientationTracker.shared.currentInterfaceOrientation.isPortrait || lockedPortrait ? (SafeArea.insets.top + SafeArea.insets.bottom) : 0) : nil
let isPortrait = OrientationTracker.shared.currentInterfaceOrientation.isPortrait || lockedPortrait
return fullScreenPlayer ? UIScreen.main.bounds.size.height - (isPortrait ? safeAreaModel.safeArea.top + safeAreaModel.safeArea.bottom : 0) : nil
}
#endif
@@ -290,22 +268,21 @@ struct VideoPlayerView: View {
.ignoresSafeArea()
#else
GeometryReader { geometry in
ZStack {
player.playerBackendView
}
.modifier(
VideoPlayerSizeModifier(
geometry: geometry,
aspectRatio: player.aspectRatio,
fullScreen: fullScreenPlayer
player.playerBackendView
.modifier(
VideoPlayerSizeModifier(
geometry: geometry,
aspectRatio: player.aspectRatio,
fullScreen: fullScreenPlayer,
detailsHiddenInFullScreen: detailsHiddenInFullScreen
)
)
)
.frame(maxWidth: fullScreenPlayer ? .infinity : nil, maxHeight: fullScreenPlayer ? .infinity : nil)
.onHover { hovering in
hoveringPlayer = hovering
hovering ? player.controls.show() : player.controls.hide()
}
.gesture(player.controls.presentingOverlays ? nil : playerDragGesture)
.onHover { hovering in
hoveringPlayer = hovering
hovering ? player.controls.show() : player.controls.hide()
}
.gesture(player.controls.presentingOverlays ? nil : playerDragGesture)
#if os(macOS)
.onAppear(perform: {
NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) {
@@ -322,15 +299,12 @@ struct VideoPlayerView: View {
.background(Color.black)
if !fullScreenPlayer {
if !detailsHiddenInFullScreen {
VideoDetails(
video: player.videoForDisplay,
fullScreen: $fullScreenDetails,
sidebarQueue: $sidebarQueue
)
#if os(iOS)
.ignoresSafeArea(.all, edges: .bottom)
#endif
.modifier(VideoDetailsPaddingModifier(
playerSize: player.playerSize,
fullScreen: fullScreenDetails
@@ -346,6 +320,9 @@ struct VideoPlayerView: View {
}
#endif
}
#if os(iOS)
.background(BackgroundBlackView().edgesIgnoringSafeArea(.all))
#endif
.background(((colorScheme == .dark || fullScreenPlayer) ? Color.black : Color.white).edgesIgnoringSafeArea(.all))
#if os(macOS)
.frame(minWidth: 650)
@@ -387,7 +364,7 @@ struct VideoPlayerView: View {
}
}
#endif
if !fullScreenPlayer {
if !detailsHiddenInFullScreen {
#if os(iOS)
if sidebarQueue {
List {
@@ -422,6 +399,12 @@ struct VideoPlayerView: View {
}
#if os(iOS)
.statusBar(hidden: fullScreenPlayer)
.backport
.toolbarBackground(colorScheme == .light ? .white : .black)
.backport
.toolbarBackgroundVisibility(true)
.backport
.toolbarColorScheme(colorScheme)
#endif
#if os(macOS)
.background(
@@ -432,6 +415,20 @@ struct VideoPlayerView: View {
#endif
}
var detailsHiddenInFullScreen: Bool {
guard fullScreenPlayer else { return false }
if player.activeBackend == .mpv {
return true
}
#if os(iOS)
return !avPlayerUsesSystemControls || verticalSizeClass == .compact
#else
return !avPlayerUsesSystemControls
#endif
}
var fullScreenPlayer: Bool {
#if os(iOS)
player.playingFullScreen || verticalSizeClass == .compact
@@ -497,3 +494,18 @@ struct VideoPlayerView_Previews: PreviewProvider {
}
}
}
#if os(iOS)
struct BackgroundBlackView: UIViewRepresentable {
func makeUIView(context _: Context) -> UIView {
let view = UIView()
DispatchQueue.main.async {
view.superview?.superview?.backgroundColor = .black
view.superview?.superview?.layer.removeAllAnimations()
}
return view
}
func updateUIView(_: UIView, context _: Context) {}
}
#endif

View File

@@ -1,25 +0,0 @@
import Foundation
import UIKit
struct SafeArea {
static var insets: UIEdgeInsets {
let keyWindow = scene?.windows.first { $0.isKeyWindow }
return keyWindow?.safeAreaInsets ?? .init()
}
static var verticalInset: Double {
insets.top + insets.bottom
}
static var horizontalInsets: Double {
insets.left + insets.right
}
static var scene: UIWindowScene? {
UIApplication.shared.connectedScenes
.filter { $0.activationState == .foregroundActive }
.compactMap { $0 as? UIWindowScene }
.first
}
}

View File

@@ -59,9 +59,6 @@ struct SearchSuggestions: View {
#endif
}
}
#if os(iOS)
.padding(.bottom, 90)
#endif
#if os(macOS)
.buttonStyle(.link)
#endif

View File

@@ -94,8 +94,12 @@ struct AccountForm: View {
}
@ViewBuilder var validationStatus: some View {
if !username.isEmpty && !password.isEmpty {
Section {
Section {
if username.isEmpty || password.isEmpty {
Text("Enter account credentials to connect...")
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundColor(.secondary)
} else {
AccountValidationStatus(
app: .constant(instance.app),
isValid: $isValid,

View File

@@ -99,8 +99,12 @@ struct InstanceForm: View {
}
@ViewBuilder var validationStatus: some View {
if !url.isEmpty {
Section {
Section {
if url.isEmpty {
Text("Enter location address to connect...")
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundColor(.secondary)
} else {
AccountValidationStatus(
app: $app,
isValid: $isValid,

View File

@@ -2,6 +2,8 @@ import Defaults
import SwiftUI
struct PlayerControlsSettings: View {
@Default(.avPlayerUsesSystemControls) private var avPlayerUsesSystemControls
@Default(.systemControlsCommands) private var systemControlsCommands
@Default(.playerControlsLayout) private var playerControlsLayout
@Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout
@@ -61,6 +63,9 @@ struct PlayerControlsSettings: View {
@ViewBuilder var sections: some View {
#if !os(tvOS)
Section(header: SettingsHeader(text: "Controls".localized()), footer: controlsLayoutFooter) {
#if !os(tvOS)
avPlayerUsesSystemControlsToggle
#endif
horizontalPlayerGestureEnabledToggle
SettingsHeader(text: "Seek gesture sensitivity".localized(), secondary: true)
seekGestureSensitivityPicker
@@ -73,7 +78,7 @@ struct PlayerControlsSettings: View {
}
#endif
Section(header: SettingsHeader(text: "Seeking"), footer: seekingGestureSection) {
Section(header: SettingsHeader(text: "Seeking".localized()), footer: seekingGestureSection) {
systemControlsCommandsPicker
seekingSection
@@ -103,13 +108,13 @@ struct PlayerControlsSettings: View {
}
var controlsButtonsSection: some View {
Section(header: SettingsHeader(text: "Controls Buttons")) {
Section(header: SettingsHeader(text: "Controls Buttons".localized())) {
controlButtonToggles
}
}
@ViewBuilder var actionsButtonsSection: some View {
Section(header: SettingsHeader(text: "Actions Buttons")) {
Section(header: SettingsHeader(text: "Actions Buttons".localized())) {
actionButtonToggles
}
}
@@ -143,6 +148,10 @@ struct PlayerControlsSettings: View {
Toggle("Seek with horizontal swipe on video", isOn: $horizontalPlayerGestureEnabled)
}
private var avPlayerUsesSystemControlsToggle: some View {
Toggle("Use system controls with AVPlayer", isOn: $avPlayerUsesSystemControls)
}
private var seekGestureSensitivityPicker: some View {
Picker("Seek gesture sensitivity", selection: $seekGestureSensitivity) {
Text("Highest").tag(1.0)
@@ -206,7 +215,7 @@ struct PlayerControlsSettings: View {
private func seekingDurationSetting(_ name: String, _ value: Binding<String>) -> some View {
HStack {
Text(name)
Text(name.localized())
.frame(minWidth: 140, alignment: .leading)
Spacer()

View File

@@ -13,10 +13,12 @@ struct PlayerSettings: View {
#endif
@Default(.expandVideoDescription) private var expandVideoDescription
@Default(.pauseOnHidingPlayer) private var pauseOnHidingPlayer
@Default(.closeVideoOnEOF) private var closeVideoOnEOF
#if os(iOS)
@Default(.honorSystemOrientationLock) private var honorSystemOrientationLock
@Default(.enterFullscreenInLandscape) private var enterFullscreenInLandscape
@Default(.rotateToPortraitOnExitFullScreen) private var rotateToPortraitOnExitFullScreen
@Default(.rotateToLandscapeOnEnterFullScreen) private var rotateToLandscapeOnEnterFullScreen
#endif
@Default(.closePiPOnNavigation) private var closePiPOnNavigation
@Default(.closePiPOnOpeningPlayer) private var closePiPOnOpeningPlayer
@@ -65,6 +67,7 @@ struct PlayerSettings: View {
sourcePicker
}
pauseOnHidingPlayerToggle
closeVideoOnEOFToggle
#if !os(macOS)
pauseOnEnteringBackgroundToogle
#endif
@@ -118,8 +121,9 @@ struct PlayerSettings: View {
if idiom == .pad {
enterFullscreenInLandscapeToggle
}
rotateToPortraitOnExitFullScreenToggle
honorSystemOrientationLockToggle
rotateToPortraitOnExitFullScreenToggle
rotateToLandscapeOnEnterFullScreenPicker
}
#endif
@@ -190,6 +194,10 @@ struct PlayerSettings: View {
Toggle("Pause when player is closed", isOn: $pauseOnHidingPlayer)
}
private var closeVideoOnEOFToggle: some View {
Toggle("Close video and player on end", isOn: $closeVideoOnEOF)
}
#if !os(macOS)
private var pauseOnEnteringBackgroundToogle: some View {
Toggle("Pause when entering background", isOn: $pauseOnEnteringBackground)
@@ -209,6 +217,15 @@ struct PlayerSettings: View {
private var rotateToPortraitOnExitFullScreenToggle: some View {
Toggle("Rotate to portrait when exiting fullscreen", isOn: $rotateToPortraitOnExitFullScreen)
}
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("No rotation").tag(FullScreenRotationSetting.disabled)
}
.modifier(SettingsPickerModifier())
}
#endif
private var closePiPOnNavigationToggle: some View {

View File

@@ -245,7 +245,7 @@ struct SettingsView: View {
case .browsing:
return 880
case .player:
return 450
return 480
case .controls:
return 920
case .quality:

View File

@@ -12,11 +12,13 @@ struct VerticalCells<Header: View>: View {
var items = [ContentItem]()
var allowEmpty = false
var edgesIgnoringSafeArea = Edge.Set.horizontal
let header: Header?
init(items: [ContentItem], allowEmpty: Bool = false, @ViewBuilder header: @escaping () -> Header? = { nil }) {
init(items: [ContentItem], allowEmpty: Bool = false, edgesIgnoringSafeArea: Edge.Set = .horizontal, @ViewBuilder header: @escaping () -> Header? = { nil }) {
self.items = items
self.allowEmpty = allowEmpty
self.edgesIgnoringSafeArea = edgesIgnoringSafeArea
self.header = header()
}
@@ -40,7 +42,7 @@ struct VerticalCells<Header: View>: View {
#endif
}
.animation(nil)
.edgesIgnoringSafeArea(.horizontal)
.edgesIgnoringSafeArea(edgesIgnoringSafeArea)
#if os(macOS)
.background(Color.secondaryBackground)
.frame(minWidth: 360)

View File

@@ -457,7 +457,7 @@
"Video Details" = "تفاصيل الفيديو";
"Clear Queue before opening" = "مسح قائمة الانتظار قبل الفتح";
"Current: %@\n%@" = "الحالي: %@\n%@";
"Thumbnails" = "الصور المصغرة";
"Thumbnails" = "المعاينات";
"Show Open Videos toolbar button" = "إظهار زر الفيديوهات المفتوحة في شريط الأدوات";
"URL to Open" = "الرابط المراد فتحه";
"Enter link to open" = "أدخل الرابط للفتح";
@@ -502,3 +502,4 @@
"System controls show buttons for %@" = "تعرض ضوابط النظام أزرارًا لـ %@";
"Wiki" = "ويكي";
"Sample Rate" = "";
"Short videos: visible" = "مقاطع الفيديو القصيرة: مرئية";

View File

@@ -563,3 +563,10 @@
"Fullscreen" = "Vollbildmodus";
"Lock" = "Sperre";
"Description" = "Beschreibung";
"Seek" = "Suchen";
"Enter account credentials to connect..." = "Geben Sie die Kontozugangsdaten ein, um eine Verbindung herzustellen...";
"Show scroll to top button in comments" = "Schaltfläche Nach oben scrollen in Kommentaren anzeigen";
"Enter location address to connect..." = "Geben Sie die Internetadresse ein, um eine Verbindung herzustellen...";
"Opened File" = "Geöffnete Datei";
"File Extension" = "Dateierweiterung";
"Opening file..." = "Datei öffnen...";

View File

@@ -561,3 +561,19 @@
"Loop one" = "Loop one";
"Autoplay next" = "Autoplay next";
"Stream" = "Stream";
"Show scroll to top button in comments" = "Show scroll to top button in comments";
"Enter account credentials to connect..." = "Enter account credentials to connect...";
"Enter location address to connect..." = "Enter location address to connect...";
"Seek" = "Seek";
"Opened File" = "Opened File";
"File Extension" = "File Extension";
"Opening file..." = "Opening file...";
"Public account" = "Public account";
"Your Accounts" = "Your Accounts";
"Browse without account" = "Browse without account";
"Close video and player on end" = "Close video and player on end";
"Use system controls with AVPlayer" = "Use system controls with AVPlayer";
"Rotate when entering fullscreen on landscape video" = "Rotate when entering fullscreen on landscape video";
"Landscape left" = "Landscape left";
"Landscape right" = "Landscape right";
"No rotation" = "No rotation";

View File

@@ -1,30 +1,30 @@
" subscribers" = " abonnés";
"Add Location..." = "Ajouter un emplacement…";
"Add Location..." = "Ajouter une instance…";
"Add profile..." = "Ajouter un profil…";
"Add Quality Profile" = "Ajouter un profil de qualité";
"Delete" = "Supprimer";
"%@ Playlist" = "%@ liste de lecture";
"%@ Playlist" = "Liste de lecture %@";
"%@ subscribers" = "%@ abonnés";
"%@ Channel" = "%@ chaîne";
"10 seconds forwards/backwards" = "10 secondes en avant/en arrière";
"%@ Channel" = "Chaîne %@";
"10 seconds forwards/backwards" = "10 secondes en avant/arrière";
"Accounts" = "Comptes";
"%lld videos" = "%lld vidéos";
"Accounts are not supported for the application of this instance" = "Les comptes ne sont pas pris en charge pour l'application de cette instance";
"Add to Playlist" = "Ajouter à la liste de lecture";
"Add to Favorites" = "Ajouter aux Favoris";
"Add to Favorites" = "Ajouter aux favoris";
"Add Account" = "Ajouter un compte";
"Add to %@" = "Ajouter à %@";
"Are you sure you want to clear search history?" = "Êtes-vous sûr de vouloir effacer l'historique des recherches ?";
"Add Location" = "Ajouter un emplacement";
"Add Location" = "Ajouter une instance";
"Add to Playlist..." = "Ajouter à la liste de lecture…";
"Add Account..." = "Ajouter un compte…";
"Advanced" = "Avancé";
/* Trending category, section containing all kinds of videos */
"All" = "Tout";
"Are you sure you want to clear history of watched videos?" = "Êtes-vous sûr de vouloir effacer l'historique des vidéos regardées ?";
"Are you sure you want to clear history of watched videos?" = "Êtes-vous sûr de vouloir effacer l'historique des vidéos visionnées ?";
"Are you sure you want to delete playlist?" = "Êtes-vous sûr de vouloir supprimer la liste de lecture ?";
"Are you sure you want to restore default quality profiles?" = "Êtes-vous sûr de vouloir restaurer les profils de qualité par défaut ?";
"Are you sure you want to unsubscribe from %@?" = "Êtes-vous sûr de vouloir vous désabonner de %@ ?";
@@ -38,7 +38,7 @@
"Close Video" = "Fermer la vidéo";
"Disabled" = "Désactivé";
"Donations" = "Dons";
"Fullscreen size" = "Taille du plein écran";
"Fullscreen size" = "Taille en plein écran";
"Gaming" = "Jeux vidéo";
"Help" = "Aide";
"Hide sidebar" = "Masquer la barre latérale";
@@ -50,17 +50,17 @@
/* Video date filter in search */
"Hour" = "Heure";
"I am lost" = "Je suis perdu";
"I found a bug /" = "J'ai trouvé une erreur";
"I found a bug /" = "J'ai trouvé une erreur /";
"I have a feature request" = "J'ai une demande de fonctionnalité";
"I like this app!" = "J'aime bien cette appli !";
"I like this app!" = "J'aime cette application !";
"I want to ask a question" = "Je veux poser une question";
"Info" = "Infos";
"Info" = "Informations";
/* Selected video has just finished playing */
"Just watched" = "Regardée à l'instant";
"Just watched" = "Visionnée à l'instant";
/* Player controls layout size */
"Large" = "Grand";
"Large" = "Grands";
"LIVE" = "DIRECT";
"Loading..." = "Chargement…";
"Locations" = "Instances";
@@ -74,7 +74,7 @@
"No description" = "Aucune description";
"No Playlists" = "Aucune liste de lecture";
"No results" = "Aucun résultat";
"Normal" = "Normal";
"Normal" = "Normale";
"Not available" = "Indisponible";
"Not Playing" = "Pas en lecture";
"Nothing" = "Rien";
@@ -87,10 +87,10 @@
"Player" = "Lecteur";
"Playlist" = "Liste de lecture";
"Playlist \"%@\" will be deleted.\nIt cannot be reverted." = "La liste de lecture « %@ » sera supprimée.\nIl n'est pas possible de revenir en arrière.";
"Popular" = "Tendances";
"Popular" = "Populaire";
"Preferred Formats" = "Formats préférés";
"Profiles" = "Profils";
"Promoting a product or service that is directly related to the creator themselves. This usually includes merchandise or promotion of monetized platforms." = "La promotion d'un produit ou d'un service qui est directement lié au créateur lui-même. Il s'agit généralement de marchandises ou de la promotion de plateformes monétisées.";
"Promoting a product or service that is directly related to the creator themselves. This usually includes merchandise or promotion of monetized platforms." = "Promotion d'un produit ou d'un service directement lié au créateur lui-même. Il s'agit généralement de marchandises ou de la promotion de plateformes monétisées.";
"Quality" = "Qualité";
"Rate" = "Vitesse";
"Search history is empty" = "L'historique de recherche est vide";
@@ -108,16 +108,16 @@
"Unlisted" = "Non listée";
"Current Playlist" = "Liste de lecture actuelle";
"Now Playing" = "En lecture";
"Current Location" = "Emplacement actuel";
"Current Location" = "Instance actuelle";
"Private" = "Privée";
"Playback queue is empty" = "La file d'attente de lecture est vide";
"Playing Next" = "Lecture suivante";
"Playing Next" = "Lire la suivante";
"Visibility" = "Visibilité";
"Favorites" = "Favoris";
"Clear History" = "Effacer l'historique";
"Edit" = "Modifier";
"Low quality" = "Qualité basse";
"Mark watched videos with" = "Marquer les vidéos regardées avec";
"Mark watched videos with" = "Marquer les vidéos visionnées avec";
"Button" = "Bouton";
"Category" = "Catégorie";
"Chapters" = "Chapitres";
@@ -131,9 +131,9 @@
"Continue from %@" = "Continuer depuis %@";
"Close" = "Fermer";
"Clear Search History" = "Effacer l'historique de recherche";
"Clear Search History..." = "Effacer l'historique des recherches…";
"Clear Search History..." = "Effacer l'historique de recherche…";
"Clear the queue" = "Effacer la file d'attente";
"Connection failed" = "La connexion a échoué";
"Connection failed" = "Échec de la connexion";
"Comments" = "Commentaires";
"Continue" = "Continuer";
"Contributing" = "Contribuer";
@@ -154,7 +154,7 @@
"Lowest" = "La plus basse";
"Filter" = "Filtre";
"Find Other" = "En trouver un autre";
"Mark as watched" = "Marquer comme regardée";
"Mark as watched" = "Marquer comme visionnée";
"More info can be found in:" = "Vous trouverez plus d'informations dans :";
"Duration" = "Durée";
"Edit Playlist" = "Modifier la liste de lecture";
@@ -168,13 +168,13 @@
/* Subscriptions title */
"Subscriptions" = "Abonnements";
"Mark video as watched after playing" = "Marquer la vidéo comme regardée après la lecture";
"Mark video as watched after playing" = "Marquer la vidéo comme visionnée après la lecture";
/* Player controls layout size */
"Medium" = "Moyenne";
"Medium" = "Moyens";
"Music" = "Musique";
"Next" = "Suivant";
"Pause when entering background" = "Pause lors de l'entrée en arrière-plan";
"Pause when entering background" = "Pause lors du passage en arrière-plan";
"Play All" = "Tout lire";
"Quality Profile" = "Profil de qualité";
"Queue is empty" = "La file d'attente est vide";
@@ -184,18 +184,18 @@
"Pause" = "Pause";
"Picture in Picture" = "Image dans l'image";
"Play" = "Lecture";
"Play in PiP" = "Lire en image dans l'image";
"Play Last" = "Ajouter à la fin de la file dattente";
"Play in PiP" = "Lire en IDI";
"Play Last" = "Lire en dernier";
"Name" = "Nom";
"Password" = "Mot de passe";
"Playlists" = "Listes de lecture";
"New Playlist" = "Nouvelle liste de lecture";
"Pause when player is closed" = "Pause lorsque le lecteur est fermé";
"Pause when player is closed" = "Pause lors de la fermeture du lecteur";
"Recents" = "Récentes";
"Search" = "Recherche";
/* Selected video was played on given date */
"Watched %@" = "Regardée %@";
"Watched %@" = "Visionnée %@";
"Remove from the queue" = "Retirer de la file d'attente";
"Queue" = "File d'attente";
"Refresh" = "Actualiser";
@@ -206,8 +206,8 @@
"Replies" = "Réponses";
"Save" = "Enregistrer";
"Save history of played videos" = "Enregistrer l'historique des vidéos lues";
"Watched" = "Regardée";
"Save history of searches, channels and playlists" = "Enregistrer l'historique des recherches, des chaînes et des listes de lecture";
"Watched" = "Visionnée";
"Save history of searches, channels and playlists" = "Enregistrer l'historique des recherches, chaînes et listes de lecture";
"Sections" = "Sections";
/* Video date filter in search */
@@ -220,8 +220,8 @@
/* Selected video is being played */
"Watching now" = "En lecture";
"Unsubscribe" = "Se désabonner";
"Upload date" = "Date de mise en ligne";
"Thumbnails" = "Aperçus";
"Upload date" = "Date de publication";
"Thumbnails" = "Miniatures";
"URL" = "URL";
"Trending" = "Tendances";
@@ -230,9 +230,9 @@
"Sort: %@" = "Trier : %@";
"Subscribe" = "S'abonner";
"Sort" = "Trier";
"It can be changed later in settings. You can use your own locations too." = "Peut être modifié ultérieurement dans les paramètres. Vous pouvez également utiliser vos propres emplacements.";
"For videos which feature music as the primary content." = "Pour les vidéos qui n'utilisent que la musique comme contenu principal.";
"Formats will be selected in order as listed.\nHLS is an adaptive format (resolution setting does not apply)." = "Les formats seront sélectionnés dans l'ordre de la liste.\nL'HLS est un format adaptatif (le paramètre de résolution ne s'applique pas).";
"It can be changed later in settings. You can use your own locations too." = "Peut être modifié ultérieurement dans les paramètres. Vous pouvez également utiliser vos propres instances.";
"For videos which feature music as the primary content." = "Pour les vidéos dont le contenu principal est la musique.";
"Formats will be selected in order as listed.\nHLS is an adaptive format (resolution setting does not apply)." = "Les formats seront sélectionnés dans l'ordre de la liste.\nHLS est un format adaptatif (le paramètre de résolution ne s'applique pas).";
"MPV Documentation" = "Documentation de MPV";
/* SponsorBlock category name */
@@ -245,41 +245,41 @@
"Blue" = "Bleu";
"Browsing" = "Navigation";
"Buffering stream..." = "Mise en tampon du flux...";
"Bugs and great feature ideas can be sent to the GitHub issues tracker. " = "Les bugs ainsi que des idées de fonctionnalités peuvent être envoyés sur GitHub. ";
"Close PiP and open player when application enters foreground" = "Ferme l'IDI et ouvre le lecteur vidéo lorsque l'application passe au premier plan";
"Close PiP when player is opened" = "Ferme l'IDI can le lecteur vidéo est ouvert";
"Close PiP when starting playing other video" = "Ferme l'IDI quand une autre vidéo est commencée";
"Close player when closing video" = "Ferme le lecteur quand la vidéo est arrêtée";
"Close player when starting PiP" = "Ferme le lecteur lors que le mode PiP est lancé";
"Close video after playing last in the queue" = "Ferme la vidéo après avoir lu la dernière de la file d'attente";
"Bugs and great feature ideas can be sent to the GitHub issues tracker. " = "Les bugs et les idées de fonctionnalités peuvent être envoyés via GitHub. ";
"Close PiP and open player when application enters foreground" = "Fermer l'IDI et ouvrir le lecteur lorsque l'application passe au premier plan";
"Close PiP when player is opened" = "Fermer l'IDI lorsque le lecteur est ouvert";
"Close PiP when starting playing other video" = "Fermer l'IDI lors de la lecture d'une autre vidéo";
"Close player when closing video" = "Fermer le lecteur lors de la fermeture de la vidéo";
"Close player when starting PiP" = "Fermer le lecteur au lancement de l'IDI";
"Close video after playing last in the queue" = "Fermer la vidéo après la dernière lecture de la file d'attente";
"Controls" = "Contrôles";
"Could not load locations manifest" = "Impossible de charger le manifeste des instances";
"Custom Locations" = "Instances personalisées";
"Custom Locations" = "Instances personnalisées";
/* Video sort order in search */
"Date" = "Date";
"Decreased opacity" = "Opacité diminuée";
"Discord Server" = "Serveur Discord";
"Discussions take place in Discord and Matrix. It's a good spot for general questions." = "Les discussions ont lieu sur Discord et Matrix. Ce sont de bons endroits pour poser des questions généraliste.";
"Discussions take place in Discord and Matrix. It's a good spot for general questions." = "Les discussions ont lieu sur Discord et Matrix. Ce sont de bons endroits pour poser des questions génériques.";
"Don't use public locations" = "Ne pas utiliser d'instances publiques";
"Enable Return YouTube Dislike" = "Activer le retour du bouton dislike de Youtube";
"Enter fullscreen in landscape" = "Entrer en mode plein écran lors de la rotation 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)." = "Rappel d'aimer la vidéo, de s'abonner ou d'intéragir avec le créateur sur une plateforme gratuite ou payante.";
"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.";
"Frontend URL" = "URL frontale";
"Public Locations" = "Instances publiques";
"Public Manifest" = "Manifeste publique";
"Honor orientation lock" = "Respecter le verrouillage de l'orientation";
"If you are interested what's coming in future updates, you can track project Milestones." = "Si vous êtes intéressé par ce qui est prévu dans les futures mises à jour, vous pouvez suivre les étapes du projet.";
"If you are reporting a bug, include all relevant details (especially: app version, used device and system version, steps to reproduce)." = "Si vous signalez un bug, incluez tous les détails pertinents (notamment : la version de l'application, la version de l'appareil et du système utilisés, les étapes pour reproduire celui-ci).";
"If you are reporting a bug, include all relevant details (especially: app version, used device and system version, steps to reproduce)." = "Si vous signalez un bug, incluez tous les détails pertinents (notamment : version de l'application, appareil utilisé et version du système, étapes à suivre pour reproduire celui-ci).";
"Instance of current account" = "Instance du compte actif";
/* SponsorBlock category name */
"Interaction" = "Rappel dinteraction (abonnement)";
"Interaction" = "Rappel dinteraction";
"Interface" = "Interface";
/* SponsorBlock category name */
"Intro" = "Entracte/Animation d'intro";
"Large layout is not suitable for all devices and using it may cause controls not to fit on the screen." = "Le format large pour la disposition des boutons n'est pas adapté à tous les appareils et son utilisation peut empêcher les contrôles de s'afficher à l'écran.";
"Intro" = "Introduction";
"Large layout is not suitable for all devices and using it may cause controls not to fit on the screen." = "Le grand format n'est pas adapté à tous les appareils et son utilisation peut empêcher certains contrôles de s'afficher à l'écran.";
/* Loading stream OSD */
"Loading streams..." = "Chargement des flux...";
@@ -291,7 +291,7 @@
/* SponsorBlock category name */
"Outro" = "Générique de fin";
"Part of a video promoting a product or service not directly related to the creator. The creator will receive payment or compensation in the form of money or free products." = "Partie de la vidéo promotant un produit ou service qui n'est pas directement relié au créateur. le créateur recevra un paiement ou une compensation sous forme de monnaie ou de produits gratuits.";
"Part of a video promoting a product or service not directly related to the creator. The creator will receive payment or compensation in the form of money or free products." = "Partie d'une vidéo promouvant un produit ou un service sans lien direct avec le créateur. Le créateur recevra un paiement ou des produits gratuits.";
"Reset search filters" = "Réinitialiser les filtres de recherche";
"Resolution" = "Résolution";
"Restart" = "Redémarrer";
@@ -304,11 +304,11 @@
/* SponsorBlock category name */
"Sponsor" = "Sponsor";
"SponsorBlock" = "SponsorBlock";
"SponsorBlock API Instance" = "SponsorBlock exemple d'API";
"SponsorBlock API Instance" = "Instance de l'API SponsorBlock";
"This cannot be reverted" = "Vous ne pourrez pas revenir en arrière";
"Switch to public locations" = "Passer à une instance publique";
"Switch to public locations" = "Passer à des instances publiques";
"System controls buttons" = "Boutons de contrôle système";
"System controls show buttons for %@" = "Montrer les boutons du contrôle système pour %@";
"System controls show buttons for %@" = "Montrer les boutons de contrôle système pour %@";
"This cannot be reverted. You might need to switch between views or restart the app to see changes." = "Vous ne pourrez pas revenir en arrière. Vous devrez changer de vue ou redémarrer l'application pour voir les changements.";
"unknown" = "inconnu";
"This information will be processed only on your device and used to connect you to the server in the specified country." = "Ces informations sont traitées uniquement sur votre appareil et utilisées pour vous connecter au serveur dans un pays spécifique.";
@@ -317,42 +317,42 @@
"Reset watched status when playing again" = "Réinitialiser le status de visionnage lors d'une nouvelle lecture";
"Share %@ link" = "Partager le lien %@";
"Show anonymous accounts" = "Afficher les comptes anonymes";
"Show channel name" = "Afficher le nom de la chaine";
"Show channel name" = "Afficher le nom de la chaîne";
"Show history" = "Afficher l'historique";
"Show progress of watching on thumbnails" = "Afficher la progression de l'avancement dans les miniatures";
"Show progress of watching on thumbnails" = "Afficher la progression du visionnage sur les miniatures";
"Switch to other public location" = "Passer à une autre instance publique";
"Show keywords" = "Afficher le clavier";
"Show keywords" = "Afficher les mots-clés";
"Show playback statistics" = "Afficher les statistiques de lecture";
"Sidebar" = "Barre du côté";
"Sidebar" = "Barre latérale";
"Sign In Required" = "Authentification requise";
/* Player controls layout size */
"Small" = "Petit";
"Small" = "Petits";
/* Player controls layout size */
"Smaller" = "Le plus petit";
"Smaller" = "Très petits";
"Source" = "Source";
"Used to create links from videos, channels and playlists" = "Utilisé pour créer des liens depuis les vidéos, les chaînes et playlists";
"Used to create links from videos, channels and playlists" = "Utilisé pour créer des liens depuis les vidéos, chaînes et playlists";
"Restart/Play next" = "Redémarrer/Lire la suivante";
"Rotate to portrait when exiting fullscreen" = "Revenir au mode portrait quand le mode plein écran est quitté";
"Rotate to portrait when exiting fullscreen" = "Rotation en mode portrait à la sortie du plein écran";
"Round corners" = "Coins arrondis";
"Seek with horizontal swipe on video" = "Rechercher avec un glissement horizontal sur la vidéo";
"Settings" = "Paramètres";
"Share %@ link with time" = "Partager le lien %@ avec le temps";
"Show account username" = "Afficher le nom d'utilisateur";
"Show sidebar when space permits" = "Afficher la barre du côté quand l'espace le permet";
"Show sidebar when space permits" = "Afficher la barre latérale lorsque l'espace le permet";
"Show video length" = "Afficher la durée de la vidéo";
"Shuffle" = "Aléatoire";
"Shuffle All" = "Tout en aléatoire";
"Could not refresh Subscriptions" = "Impossible de rafraichir les abonnements";
"Could not refresh Subscriptions" = "Impossible d'actualiser Abonnements";
"Increase rate" = "Augmenter la vitesse";
"Issues Tracker" = "Suivi de problèmes";
"You need to select an account\nto access %@ section" = "Vous avez besoin de sélectionner un compte\npour accéder à la section %@";
"You can use automatic profile selection based on current device status or switch it in video playback settings controls." = "Vous pouvez utiliser une sélection automatique de profil basé sur le statut actuel de l'appareil ou le changer dans les paramètre de lecture vidéo.";
"You have no playlists\n\nTap on \"New Playlist\" to create one" = "Vous n'avez pas de liste de lecture\n\nTaper sur \"nouvelle liste de lecture\" pour en créer une";
"You need to create an instance and accounts\nto access %@ section" = "Vous avez besoin de créer une instance et un compte\npour accéder à la section %@";
"You need to select an account\nto access %@ section" = "Vous devez sélectionner un compte\npour accéder à la section %@";
"You can use automatic profile selection based on current device status or switch it in video playback settings controls." = "Vous pouvez utiliser la sélection automatique du profil en fonction de l'état actuel de l'appareil ou la modifier dans les commandes des paramètres de lecture vidéo.";
"You have no playlists\n\nTap on \"New Playlist\" to create one" = "Vous n'avez aucune liste de lecture\n\nAppuyez sur \"Nouvelle liste de lecture\" pour en créer une";
"You need to create an instance and accounts\nto access %@ section" = "Vous devez créer une instance et un compte\npour accéder à la section %@";
"Press and hold remote button to open captions and quality menus" = "Appuyez et maintenez le bouton télécommande pour ouvrir le menu des sous-titres et de la qualité vidéo";
"Keep last played video in the queue after restart" = "Garder la dernière vidéo jouée en file d'attente après le redémarrage";
"Keep last played video in the queue after restart" = "Conserver la dernière vidéo lue dans la file d'attente après le redémarrage";
"Proxy videos" = "Proxy vidéos";
/* Video sort order in search */
@@ -360,39 +360,39 @@
/* Video duration filter in search */
"Short" = "Courtes";
"This will remove all your custom profiles and return their default values. This cannot be reverted." = "Ceci va supprimer tous vos profils modifiés et les remettre à leur valeurs par défaut. Vous ne pourrez pas revenir en arrière.";
"This will remove all your custom profiles and return their default values. This cannot be reverted." = "Cette opération supprimera tous vos profils personnalisés et rétablira leurs valeurs par défaut. Il n'est pas possible de revenir en arrière.";
/* Player controls layout size */
"Very Large" = "Très Large";
"Stream & Player" = "Flux et Lecteur";
"Very Large" = "Très grands";
"Stream & Player" = "Flux & Lecteur";
"Statistics" = "Statistiques";
"%@ formats" = "%@ formats";
"Open logs in Finder" = "Ouvrir les lots dans le Finder";
"Open logs in Finder" = "Ouvrir les logs dans le Finder";
"Share Logs..." = "Partager les logs…";
"Could not load streams" = "Impossible de charger les flux";
"Could not open video" = "Impossible d'ouvrir la vidéo";
"Channel could not be found" = "Chaîne impossible à trouver";
"Channel could not be found" = "Impossible de trouver la chaîne";
"Could not extract channel information" = "Impossible d'extraire les informations de la chaîne";
"Could not extract SID from received cookies: %@" = "Impossible d'extraire le SID depuis les cookies reçus: %@";
"Could not update your token." = "Impossible de mettre à jour votre jeton.";
"Could not refresh Trending" = "Impossible de rafraîchir les tendances";
"For custom locations you can configure Frontend URL in Locations settings" = "Pour des instances personnalisées vous pouvez configurer l'URL frontale dans les paramètres d'instance";
"This URL could not be opened" = "Cette URL ne peut pas être ouverte";
"Could not open channel" = "Cette chaîne ne peut pas être ouverte";
"Could not refresh Popular" = "Impossible d'actualiser les tendances";
"Could not refresh Trending" = "Impossible d'actualiser Tendances";
"For custom locations you can configure Frontend URL in Locations settings" = "Pour des instances personnalisées, vous pouvez configurer l'URL frontale dans les paramètres d'instance";
"This URL could not be opened" = "Impossible d'ouvrir cette URL";
"Could not open channel" = "Impossible d'ouvrir cette chaîne";
"Could not refresh Popular" = "Impossible d'actualiser Populaire";
"Could not create share link" = "Impossible de créer un lien de partage";
"Could not load video" = "Impossible de charger la vidéo";
"If you want this app to be available in your language, join translation project." = "Si vous voulez que cette application soit disponible dans votre langue, rejoignez le projet de traduction.";
"Translations" = "Traductions";
"Decrease rate" = "Diminuer la vitesse";
"Finding something to play..." = "Trouver quelque chose à jouer…";
"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." = "Cela fait plaisir à entendre. C'est amusant de délivrer des apps que d'autres personnes veulent utiliser. Vous pouvez considérer faire un don au projet ou aider en contribuant au développement de nouvelles fonctionnalités.";
"Typically near or at the end of the video when the credits pop up and/or endcards are shown." = "Typiquement proche de la fin de la vidéo, quand les crédits ou les cartes sont montré.";
"When partially watched video is played" = "Quand une vidéo partiellement regardée est jouée";
"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." = "Cela fait plaisir à entendre. C'est amusant de délivrer des applications que d'autres personnes veulent utiliser. Vous pouvez considérer faire un don au projet ou aider en contribuant au développement de nouvelles fonctionnalités.";
"Typically near or at the end of the video when the credits pop up and/or endcards are shown." = "Généralement à la fin de la vidéo, lorsque le générique apparaît ou les images de fin sont affichées.";
"When partially watched video is played" = "Lorsqu'une vidéo partiellement visionnée est lue";
"Wi-Fi" = "Wi-Fi";
"Wiki" = "Wiki";
"Yattee" = "Yattee";
"Yattee %@ (build %@)" = "Yattee %@ (version %@)";
"Yattee %@ (build %@)" = "Yattee %@ (build %@)";
"You can switch between profiles in playback settings controls." = "Vous pouvez changer de profil dans les paramètres de lecture.";
"Make default" = "Mettre par défaut";
"Hardware decoder" = "Décodeur matériel";
@@ -400,85 +400,85 @@
"Cached time" = "Temps mis en cache";
"Dropped frames" = "Images perdus";
"Any format" = "Tout formats";
"Playlist is empty\n\nTap and hold on a video and then \n\"Add to Playlist\"" = "Liste de lecture vide\n\nAppuyez et maintenez sur une vidéo et faite\n\"Ajouter à la liste de lecture\"";
"Playlist is empty\n\nTap and hold on a video and then \n\"Add to Playlist\"" = "La liste de lecture est vide\n\nAppuyez longuement sur une vidéo, puis sur\n\"Ajouter à la liste de lecture\"";
"Comments are disabled" = "Les commentaires sont désactivés";
"No comments" = "Aucun commentaire";
"No chapters information available" = "Aucune information de chapitrage n'est disponible";
"Could not open playlist" = "Impossible d'ouvrir la liste de lecture";
"Could not extract video ID" = "Impossible d'extraire l'ID de la vidéo";
"This video could not be opened" = "Cette vidéo ne peut pas être ouverte";
"This video could not be opened" = "Impossible d'ouvrir cette vidéo";
"Could not extract playlist ID" = "Impossible d'extraire l'ID de la liste de lecture";
"No locations available at the moment" = "Aucune instances disponible pour le moment";
"Could not refresh Playlists" = "Impossible de rafraîchir les listes de lectures";
"No locations available at the moment" = "Aucune instance disponible pour le moment";
"Could not refresh Playlists" = "Impossible d'actualiser Listes de lectures";
/* Video date filter in search
Video duration filter in search */
"Any" = "Tous";
"Any" = "Toutes";
"Autoplaying Next" = "Lecture automatique de la suivante";
"Backend" = "Interne";
"You can find information about using Yattee in the Wiki pages." = "Vous pouvez trouver des informations expliquant comment utiliser Yattee sur les pages wiki.";
"You have no Playlists" = "Vous navez pas de listes de lecture";
"Add Channels, Playlists and Searches to Favorites using" = "Ajoutez des chaînes, des listes de lecture ou des recherches à vos Favoris en utilisant";
"Rate & Captions" = "Notation et Légendes";
"Backend" = "Lecteur";
"You can find information about using Yattee in the Wiki pages." = "Vous trouverez des informations sur l'utilisation de Yattee dans les pages Wiki.";
"You have no Playlists" = "Vous navez aucune liste de lecture";
"Add Channels, Playlists and Searches to Favorites using" = "Ajoutez des chaînes, listes de lecture et recherches à vos Favoris en utilisant";
"Rate & Captions" = "Vitesse & Sous-titres";
"Matrix Chat" = "Discussion Matrix";
"Milestones" = "Étapes";
/* Loading stream OSD */
"Opening %@ stream..." = "Ouverture du flux %@…";
"Regular size" = "Taille normale";
"Regular Size" = "Taille Normale";
"Regular Size" = "Taille normale";
"Related" = "En relation";
"Seek gesture sensitivity" = "Sensibilité du geste de recherche";
"Seek gesture speed" = "Vitesse du geste de recherche";
"Segments typically found at the start of a video that include an animation, still frame or clip which are also seen in other videos by the same creator." = "Portion que l'on trouve typiquement en début de vidéo incluant une animation, un cadre fixe ou un clip qui est aussi vu dans les autres vidéos du créateur.";
"Segments typically found at the start of a video that include an animation, still frame or clip which are also seen in other videos by the same creator." = "Segments généralement situés au début d'une vidéo et comprenant une animation, une image fixe ou un clip que l'on retrouve également dans d'autres vidéos du même créateur.";
"No documents" = "Aucun document";
"Recent Documents" = "Documents récents";
"Show Inspector" = "Afficher linspecteur";
"Files" = "Fichiers";
"Buttons labels" = "Étiquettes des boutons";
"Buttons labels" = "Libellés des boutons";
"Open" = "Ouvrir";
"Only for local files and URLs" = "Uniquement pour les fichiers locaux et les URLs";
"Share" = "Partager";
"Enter links to open, one per line" = "Entrer les liens à ouvrir, un par ligne";
"Enter links to open, one per line" = "Entrez les liens à ouvrir, un par ligne";
"Always" = "Toujours";
"Channels" = "Chaînes";
"Open Files" = "Ouvrir les fichiers";
"Open Files" = "Ouvrir Fichiers";
"Recent History" = "Historique récent";
"Show Favorites" = "Afficher les favoris";
"Show Home" = "Afficher l'accueil";
"Home" = "Accueil";
"Pages toolbar position" = "Position de la barre doutils sur les pages";
"Pages toolbar position" = "Position de la barre doutils des pages";
"Show Documents" = "Afficher les documents";
"URL to Open" = "URL à ouvrir";
"Enter link to open" = "Entrer le lien à ouvrir";
"Video Details" = "Details des vidéos";
"Enter link to open" = "Entrez le lien à ouvrir";
"Video Details" = "Informations sur la vidéo";
"Reload manifest" = "Recharger le manifest";
"Clear Queue before opening" = "Effacer la file dattente avant douvrir";
"Edit Favorites…" = "Éditer les favoris…";
"Edit Favorites…" = "Modifier les favoris…";
"Pages buttons" = "Boutons des pages";
"Could not open Files" = "Impossible douvrir les fichiers";
"Paste" = "Coller";
"Open Videos" = "Ouvrir les vidéos";
"Open Videos" = "Ouvrir des vidéos";
"Playback Mode" = "Mode de lecture";
"Add" = "Ajouter";
"Hide" = "Cacher";
"Hide" = "Masquer";
"Size" = "Taille";
"Address" = "Adresse";
"Could not delete document" = "Impossible de supprimer le document";
"Could not find any links to open in your clipboard" = "Aucun lien à ouvrir n'a pu être trouvé dans votre presse-papiers";
"Center" = "Centre";
"Locations Manifest" = "Manifest de l'emplacement";
"Locations Manifest" = "Manifeste de l'instance";
"Actions buttons" = "Boutons daction";
"Sample Rate" = "Taux d'échantillonnage";
"FPS" = "FPS";
"Remove…" = "Supprimer…";
"Live Streams" = "Diffusions en direct";
"\"%@\" will be irreversibly removed from this device." = "\"%@\" sera irréversiblement supprimé de cet appareil.";
"Remove Location" = "Supprimer lemplacement";
"Remove Location" = "Supprimer l'instance";
"Open Video" = "Ouvrir la vidéo";
"Video actions buttons" = "Boutons d'action vidéo";
"Documents" = "Documents";
"Show Open Videos toolbar button" = "Afficher le bouton de la barre d'outils Ouvrir les vidéos";
"Show Open Videos toolbar button" = "Afficher le bouton de la barre d'outils \"Ouvrir des vidéos\"";
"File" = "Fichier";
"Are you sure you want to remove this document?" = "Êtes-vous sûr de vouloir supprimer ce document ?";
"Left" = "Gauche";
@@ -487,10 +487,10 @@
"Verified" = "Vérifiée";
"Show sidebar" = "Afficher la barre latérale";
"Driver" = "Driver";
"Show Open Videos quick actions" = "Actions rapides Afficher les vidéos ouvertes";
"Show Open Videos quick actions" = "Afficher les actions rapides \"Ouvrir des vidéos\"";
"Format" = "Format";
"Are you sure you want to remove %@ location?" = "Êtes-vous sûr de vouloir supprimer l'emplacement %@ ?";
"Show only icons" = "Afficher seulement les icônes";
"Are you sure you want to remove %@ location?" = "Êtes-vous sûr de vouloir supprimer l'instance %@ ?";
"Show only icons" = "Afficher uniquement les icônes";
"Audio" = "Audio";
"Share files from Finder on a Mac\nor iTunes on Windows" = "Partager des fichiers depuis Finder sur Mac\nou iTunes sur Windows";
"Channel" = "Chaîne";
@@ -507,11 +507,11 @@
"Close video" = "Fermer la vidéo";
"Show cache status" = "Montrer l'état du cache";
"Cache" = "Cache";
"Maximum feed items" = "Nombres d'éléments maximum du flux";
"Open channels with description expanded" = "Ouvrir les chaînes avec la description complète";
"Are you sure you want to clear cache?" = "Voulez-vous vraiment vider le cache ?";
"Maximum feed items" = "Nombre maximal d'éléments du flux";
"Open channels with description expanded" = "Ouvrir les chaînes avec la description déployée";
"Are you sure you want to clear cache?" = "Êtes-vous sûr de vouloir vider le cache ?";
"Right click channel thumbnail to open context menu with more actions" = "Effectuez un clic droit sur la miniature d'une chaîne pour ouvrir un menu contextuel avec plus d'actions";
"Play next item" = "Jouer le prochain élément";
"Play next item" = "Lire l'élément suivant";
"Lock orientation" = "Verrouiller l'orientation";
"Subscribe/Unsubscribe" = "S'abonner/Se désabonner";
"Total size: %@" = "Taille totale : %@";
@@ -520,22 +520,22 @@
"Feed" = "Flux";
"Music Mode" = "Mode Musique";
"Gesture: fowards" = "Geste : en avant";
"Short videos: visible" = "Shorts : visible";
"Mark channel feed as watched" = "Marquer le flux de chaînes comme regardé";
"Short videos: hidden" = "Shorts : caché";
"Mark channel feed as unwatched" = "Marquer le flux de chaînes comme non regardé";
"Short videos: visible" = "Shorts : visibles";
"Mark channel feed as watched" = "Marquer le flux de chaîne comme visionné";
"Short videos: hidden" = "Shorts : masqués";
"Mark channel feed as unwatched" = "Marquer le flux de chaîne comme non visionné";
"Play all unwatched" = "Lire toutes les vidéos non visionnées";
"Player Bar" = "Barre de lecture";
"Double tap gesture" = "Geste de double pression";
"Always show controls buttons" = "Toujours afficher les boutons de contrôle";
"Always show controls buttons" = "Toujours afficher les boutons de commandes";
"Tap and hold channel thumbnail to open context menu with more actions" = "Touchez et maintenez la miniature de la chaîne pour ouvrir le menu contextuel avec plus d'actions";
"Gesture settings control skipping interval for double tap gesture on left/right side of the player. Changing system controls settings requires restart." = "Les paramètres des gestes contrôlent l'intervalle de saut pour les gestes de double pression sur le côté gauche/droit du lecteur. La modification des paramètres de contrôle du système nécessite un redémarrage.";
"Gesture settings control skipping interval for double click on left/right side of the player. Changing system controls settings requires restart." = "Les paramètres des gestes contrôlent l'intervalle de saut pour le double clic sur le côté gauche/droit du lecteur. La modification des paramètres de contrôle du système nécessite un redémarrage.";
"Gesture settings control skipping interval for remote arrow buttons (for 2nd generation Siri Remote or newer). Changing system controls settings requires restart." = "Les paramètres des gestes contrôlent l'intervalle de saut pour les boutons fléchés de la télécommande (pour la télécommande Siri de 2e génération ou plus récente). La modification des paramètres de contrôle du système nécessite un redémarrage.";
"Open expanded" = "Ouvrir en grand";
"Gesture settings control skipping interval for double tap gesture on left/right side of the player. Changing system controls settings requires restart." = "Les paramètres gestuels contrôlent l'intervalle de saut pour le geste de double pression sur le côté gauche/droit du lecteur. La modification des paramètres de commandes du système nécessite un redémarrage.";
"Gesture settings control skipping interval for double click on left/right side of the player. Changing system controls settings requires restart." = "Les paramètres gestuels contrôlent l'intervalle de saut pour le double clic sur le côté gauche/droit du lecteur. La modification des paramètres de commandes du système nécessite un redémarrage.";
"Gesture settings control skipping interval for remote arrow buttons (for 2nd generation Siri Remote or newer). Changing system controls settings requires restart." = "Les paramètres gestuels contrôlent l'intervalle de saut pour les boutons fléchés de la télécommande (pour la télécommande Siri de 2e génération ou plus récente). La modification des paramètres de commandes du système nécessite un redémarrage.";
"Open expanded" = "Ouvrir déployée";
"Single tap gesture" = "Geste de simple pression";
"Maximum width expanded" = "Largeur maximale étendue";
"Show unwatched feed badges" = "Afficher les badges de flux non regardés";
"Maximum width expanded" = "Largeur maximale déployée";
"Show unwatched feed badges" = "Afficher les badges de flux non visionnés";
"Seeking" = "Recherche";
"Controls Buttons" = "Boutons de contrôle";
"System controls" = "Contrôles système";
@@ -545,12 +545,12 @@
"Actions Buttons" = "Boutons daction";
"List" = "Liste";
"Cells" = "Tuiles";
"Show Next in Queue" = "Afficher la suivante dans la file";
"Next in Queue" = "Suivante dans la file";
"Show Next in Queue" = "Afficher \"Suivantes dans la file\"";
"Next in Queue" = "Suivantes dans la file";
"Do nothing" = "Ne rien faire";
"Open video description expanded" = "Ouvrir la description de la vidéo";
"Mark all as unwatched" = "Marquer toutes comme non regardées";
"Mark all as watched" = "Marquer toutes comme regardées";
"Open video description expanded" = "Ouvrir la description de la vidéo déployée";
"Mark all as unwatched" = "Marquer toutes comme non visionnées";
"Mark all as watched" = "Marquer toutes comme visionnées";
"Queue - shuffled" = "File d'attente - mélangée";
"Replay" = "Replay";
"Description" = "Description";
@@ -558,8 +558,15 @@
"Playback Settings" = "Paramètres de lecture";
"Autoplay next" = "Lecture automatique";
"Stream" = "Flux";
"Show toggle watch status button" = "Afficher le bouton d'état de visionnage";
"Show toggle watch status button" = "Afficher le bouton de changement d'état de visionnage";
"Toggle size" = "Taille de bouton";
"Toggle player" = "Activer le lecteur";
"Fullscreen" = "Plein écran";
"Lock" = "Verrouiller";
"Enter account credentials to connect..." = "Entrez les informations d'identification du compte pour vous connecter...";
"Seek" = "Recherche";
"Show scroll to top button in comments" = "Afficher le bouton de retour en haut de la page dans les commentaires";
"Opened File" = "Fichier ouvert";
"Opening file..." = "Ouverture du fichier...";
"Enter location address to connect..." = "Entrez l'adresse de l'instance pour se connecter...";
"File Extension" = "Extension de fichier";

View File

@@ -3,23 +3,23 @@
"Add to Playlist" = "Aggiungi a playlist";
"Always use AVPlayer for live videos" = "Usa sempre AVPlayer per i video dal vivo";
"Are you sure you want to clear history of watched videos?" = "Vuoi eliminare la cronologia dei video guardati?";
"Based on system color scheme" = "Sistema";
"Based on system color scheme" = "Adatta al sistema";
"Blue" = "Blu";
"Category" = "Categoria";
"Cancel" = "Annulla";
"Autoplaying Next" = "Riproduzione automatica del video successivo";
"Buffering stream..." = "Preparazione stream...";
"Autoplaying Next" = "Riproduzione automatica video successivo";
"Buffering stream..." = "Caricamento stream...";
"Are you sure you want to delete playlist?" = "Vuoi eliminare la playlist?";
"Badge" = "Indicatore";
"Are you sure you want to unsubscribe from %@?" = "Vuoi cancellare l'iscrizione a %@?";
"Backend" = "Backend";
"Are you sure you want to restore default quality profiles?" = "Vuoi ripristinare i profili di qualità predefiniti?";
"Are you sure you want to restore default quality profiles?" = "Vuoi ripristinare i profili qualità predefiniti?";
"Automatic" = "Automatico";
"Chapters" = "Capitoli";
"Badge & Decreased opacity" = "Indicatore e trasparenza";
"Badge color" = "Colore dell'indicatore";
"Badge color" = "Colore indicatore";
"Browsing" = "Navigazione";
"Bugs and great feature ideas can be sent to the GitHub issues tracker. " = "Errori e suggerimenti possono essere inviati su GitHub. ";
"Bugs and great feature ideas can be sent to the GitHub issues tracker. " = "Errori e idee per nuove funzioni possono essere inviati su GitHub. ";
"Captions" = "Sottotitoli";
"Clear All" = "Cancella tutto";
"Contributing" = "Contribuisci";
@@ -28,26 +28,26 @@
"Clear" = "Cancella";
"Cellular" = "Dati cellulare";
"Charging" = "In carica";
"Enable Return YouTube Dislike" = "Attiva Return YouTube Dislike";
"Enable Return YouTube Dislike" = "Abilita Return YouTube Dislike";
"Connected successfully (%@)" = "Connessione riuscita (%@)";
"Clear All Recents" = "Cancella tutti i recenti";
"Clear History" = "Cancella cronologia";
"Clear Search History" = "Cancella cronologia delle ricerche";
"Clear Search History..." = "Cancella cronologia delle ricerche...";
"Decrease rate" = "Diminuisci";
"Close PiP and open player when application enters foreground" = "Chiudi PiP e apri il video quando l'applicazione viene aperta";
"Close PiP and open player when application enters foreground" = "Chiudi PiP e apri il video quando l'applicazione viene portata in primo piano";
"Close PiP when player is opened" = "Chiudi PiP quando il riproduttore viene aperto";
"Contact" = "Contatto";
"Copy %@ link" = "Copia link %@";
"Copy %@ link with time" = "Copia %@ link con tempo";
"Could not load locations manifest" = "Impossibile caricare il manifesto della posizione";
"Copy %@ link with time" = "Copia link %@ con tempo";
"Could not load locations manifest" = "Impossibile caricare manifesto delle posizioni";
"Decreased opacity" = "Trasparenza";
"Favorites" = "Preferiti";
"Filter" = "Filtri";
"Close PiP when starting playing other video" = "Chiudi PiP quando viene aperto un altro video";
"Filter" = "Filtro";
"Close PiP when starting playing other video" = "Chiudi PiP quando viene riprodotto un altro video";
"Continue" = "Continua";
"Comments" = "Commenti";
"Country Name or Code" = "Nome o codice del Paese";
"Country Name or Code" = "Nome o codice del paese";
"Connection failed" = "Connessione fallita";
"Continue from %@" = "Continua da %@";
"Create Playlist" = "Crea playlist";
@@ -60,31 +60,31 @@
/* Video sort order in search */
"Date" = "Data";
"Delete" = "Elimina";
"Discussions take place in Discord and Matrix. It's a good spot for general questions." = "Le discussioni hanno luogo su Discord e su Matrix. Sono dei buoni posti per fare domande.";
"Discussions take place in Discord and Matrix. It's a good spot for general questions." = "Le discussioni hanno luogo su Discord e su Matrix. Sono posti adatti per domande generali.";
"Duration" = "Durata";
"Disabled" = "Disattivato";
"Discord Server" = "Server Discord";
"Enter fullscreen in landscape" = "Entra a schermo intero in orizzontale";
"Filter: active" = "Filtri: attivi";
"Enter fullscreen in landscape" = "Avvia schermo intero in orizzontale";
"Filter: active" = "Filtro: attivo";
"Edit" = "Modifica";
"Done" = "Fatto";
"Edit Quality Profile" = "Modifica profilo di qualità";
"Edit Quality Profile" = "Modifica profilo qualità";
"Error" = "Errore";
"Edit..." = "Modifica...";
"Enable logging" = "Attiva logging";
"Enable logging" = "Attiva resoconto";
"%@ Playlist" = "Playlist %@";
"%@ subscribers" = "%@ iscritti";
"10 seconds forwards/backwards" = "10 secondi avanti/indietro";
"%lld videos" = "Video di %lld";
"%lld videos" = "%lld video";
"Accounts" = "Account";
"Accounts are not supported for the application of this instance" = "Gli account non sono supportati in questa istanza";
"Accounts are not supported for the application of this instance" = "Gli account non sono supportati per l'applicazione di questa istanza";
"Add Account" = "Aggiungi account";
"Add Account..." = "Aggiungi account...";
"Add Location" = "Aggiungi posizione";
"Add Location..." = "Aggiungi posizione...";
"Add profile..." = "Aggiungi profilo...";
"Add to %@" = "Aggiungi a %@";
"Add Quality Profile" = "Aggiungi profilo di qualità";
"Add Quality Profile" = "Aggiungi profilo qualità";
"Add to Playlist..." = "Aggiungi a playlist...";
"Advanced" = "Avanzate";
@@ -100,14 +100,14 @@
"Button" = "Pulsante";
"Clear the queue" = "Cancella la coda";
"Close" = "Chiudi";
"Close player when starting PiP" = "Chiudi il riproduttore quando viene aperto PiP";
"Close Video" = "Chiudi il video";
"Close video after playing last in the queue" = "Chiudi il video dopo aver riprodotto l'ultimo elemento della coda";
"Error when accessing playlist" = "Impossibile accedere alla playlist";
"Explicit reminders to like, subscribe or interact with them on any paid or free platform(s) (e.g. click on a video)." = "Esortazioni a mettere like, iscriversi o interagire su qualsiasi piattaforma, a pagamento o gratuita (es. aprire un video).";
"Close player when starting PiP" = "Chiudi il riproduttore quando si avvia PiP";
"Close Video" = "Chiudi video";
"Close video after playing last in the queue" = "Chiudi il video dopo aver riprodotto l'ultimo elemento in coda";
"Error when accessing playlist" = "Errore nell'accesso alla playlist";
"Explicit reminders to like, subscribe or interact with them on any paid or free platform(s) (e.g. click on a video)." = "Esortazioni a mettere Mi piace, iscriversi o interagire con loro su qualsiasi piattaforma a pagamento o gratuita (es. aprire un video).";
" subscribers" = " iscritti";
"%@ Channel" = "Canale di %@";
"Add to Favorites" = "Aggiungi ai preferiti";
"%@ Channel" = "Canale %@";
"Add to Favorites" = "Aggiungi a Preferiti";
"Battery" = "Batteria";
"Categories to Skip" = "Categorie da saltare";
"Close player when closing video" = "Chiudi il riproduttore quando viene chiuso il video";
@@ -116,18 +116,18 @@
"Edit Playlist" = "Modifica playlist";
/* SponsorBlock category name */
"Intro" = "Introduzione";
"Issues Tracker" = "Issue tracker";
"Intro" = "Intro";
"Issues Tracker" = "Issue Tracker";
/* Video duration filter in search */
"Long" = "Lungo";
"Lowest" = "Bassissima";
"Lowest" = "Peggiore";
"Low" = "Bassa";
"Not available" = "Non disponibile";
"Not Playing" = "Nessun video aperto";
"Not Playing" = "Non in riproduzione";
"Music" = "Musica";
"Only when signed in" = "Solo se l'accesso è effettuato";
"If you are reporting a bug, include all relevant details (especially: app version, used device and system version, steps to reproduce)." = "Se stai segnalando un bug, includi tutti i dettagli rilevanti (in particolare: versione dell'app, versione del dispositivo e del sistema operativo, come riprodurre il bug).";
"If you are reporting a bug, include all relevant details (especially: app version, used device and system version, steps to reproduce)." = "Se stai segnalando un bug, includi tutti i dettagli rilevanti (in particolare: versione dell'app, dispositivo utilizzato, versione del sistema operativo e come riprodurre il bug).";
"Increase rate" = "Aumenta velocità";
"Instance of current account" = "Istanza dell'account attuale";
@@ -140,7 +140,7 @@
/* Player controls layout size */
"Large" = "Grandi";
"Lock portrait mode" = "Modalità blocco orientamento orizzontale";
"Lock portrait mode" = "Blocco orientamento verticale";
"Mark as watched" = "Segna come guardato";
"Mark video as watched after playing" = "Segna il video come guardato dopo la riproduzione";
"Mark watched videos with" = "Segna come guardati i video con";
@@ -149,17 +149,17 @@
"Medium quality" = "Qualità media";
"Milestones" = "Traguardi";
"Movies" = "Film";
"MPV Documentation" = "Documentazione di MPV";
"MPV Documentation" = "Documentazione MPV";
"Nothing" = "Niente";
/* SponsorBlock category name */
"Offtopic in Music Videos" = "Sezione non-musicale nei video musicali";
"Offtopic in Music Videos" = "Sezione non musicale nei video musicali";
"Open \"Playlists\" tab to create new one" = "Apri la sezione \"Playlist\" per crearne una nuova";
"Open Settings" = "Apri le impostazioni";
"Open Settings" = "Apri Impostazioni";
/* Loading stream OSD */
"Opening %@ stream..." = "Apertura del flusso %@...";
"Opening audio stream..." = "Apertura del flusso audio...";
"Opening %@ stream..." = "Apertura stream %@...";
"Opening audio stream..." = "Apertura stream audio...";
"Orientation" = "Orientamento";
/* SponsorBlock category name */
@@ -167,17 +167,17 @@
"Password" = "Password";
"Pause" = "Pausa";
"Info" = "Info";
"LIVE" = "DAL VIVO";
"LIVE" = "DIRETTA";
/* Loading stream OSD */
"Loading streams..." = "Caricamento flussi...";
"Loading streams..." = "Caricamento stream...";
"Loading..." = "Caricamento...";
"Locations" = "Posizioni";
"Low quality" = "Qualità bassa";
/* Video date filter in search */
"Month" = "Mese";
"More info can be found in:" = "Ulteriori informazioni possono essere trovate qui:";
"More info can be found in:" = "Per maggiori informazioni:";
"Next" = "Successivo";
"No description" = "Nessuna descrizione";
"No Playlists" = "Nessuna playlist";
@@ -185,42 +185,42 @@
"Normal" = "Normale";
"For videos which feature music as the primary content." = "Per video che hanno la musica come argomento principale.";
"Finding something to play..." = "Trova qualcosa da guardare...";
"Formats will be selected in order as listed.\nHLS is an adaptive format (resolution setting does not apply)." = "I formati saranno selezionati in ordine come elencato. \nHLS è un formato adattivo (le impostazioni di risoluzione non sono applicabili).";
"Formats will be selected in order as listed.\nHLS is an adaptive format (resolution setting does not apply)." = "I formati saranno selezionati in ordine come elencato. \nHLS è un formato adattivo (l'impostazione di risoluzione non si applica).";
"Name" = "Nome";
"I want to ask a question" = "Voglio fare una domanda";
"Large layout is not suitable for all devices and using it may cause controls not to fit on the screen." = "Il layout grande non è adatto a tutti i dispositivi e selezionarlo potrebbe rendere i controlli più grandi dello schermo.";
"I want to ask a question" = "Ho una domanda";
"Large layout is not suitable for all devices and using it may cause controls not to fit on the screen." = "Il layout grande non è adatto a tutti i dispositivi e selezionarlo potrebbe far uscire i controlli dallo schermo.";
"New Playlist" = "Nuova playlist";
/* Player controls layout size */
"Medium" = "Media";
"Part of a video promoting a product or service not directly related to the creator. The creator will receive payment or compensation in the form of money or free products." = "Parte del video che promuove un prodotto o servizio non direttamente collegato al canale. Il canale riceve soldi o prodotti gratuiti in compenso.";
"Part of a video promoting a product or service not directly related to the creator. The creator will receive payment or compensation in the form of money or free products." = "Parte del video che promuove un prodotto o servizio non direttamente collegato al canale. Il canale riceve soldi o prodotti gratuiti come compenso.";
"If you are interested what's coming in future updates, you can track project Milestones." = "Se vuoi sapere cosa arriverà negli aggiornamenti futuri, tieni d'occhio i traguardi del progetto.";
"Find Other" = "Trova altri";
"Frontend URL" = "URL frontend";
"Fullscreen size" = "Dimensione schermo intero";
"Gaming" = "Gaming";
"Gaming" = "Videogiochi";
"Help" = "Aiuto";
"Hide sidebar" = "Nascondi barra laterale";
"High" = "Alta";
"Highest" = "Massima";
"Highest quality" = "Qualità massima";
"History" = "Cronologia";
"Honor orientation lock" = "Priorità blocco orientamento";
"Honor orientation lock" = "Rispetta blocco rotazione";
/* Video date filter in search */
"Hour" = "Ora";
"I am lost" = "Sono perso";
"I found a bug /" = "Ho trovato un bug /";
"I have a feature request" = "Voglio richiedere una funzionalità";
"I have a feature request" = "Ho una funzionalità da proporre";
"I like this app!" = "Mi piace quest'app!";
"Remove" = "Elimina";
"Remove from Favorites" = "Elimina dai preferiti";
"Remove from history" = "Elimina dalla cronologia";
"Remove from Playlist" = "Elimina dalla playlist";
"Remove from the queue" = "Elimina dalla coda";
"Remove" = "Rimuovi";
"Remove from Favorites" = "Rimuovi da Preferiti";
"Remove from history" = "Rimuovi dalla cronologia";
"Remove from Playlist" = "Rimuovi dalla playlist";
"Remove from the queue" = "Rimuovi dalla coda";
"Replies" = "Risposte";
"Reset search filters" = "Azzera filtri di ricerca";
"For custom locations you can configure Frontend URL in Locations settings" = "Per posizioni personalizzate è possibile configurare l'URL del frontend nelle impostazioni delle posizioni";
"For custom locations you can configure Frontend URL in Locations settings" = "Per le posizioni personalizzate è possibile configurare l'URL del frontend nelle impostazioni delle posizioni";
"This video could not be opened" = "Impossibile aprire questo video";
"Playlist \"%@\" will be deleted.\nIt cannot be reverted." = "La playlist \"%@\" sarà eliminata.\nL'azione è irreversibile.";
"Search..." = "Cerca...";
@@ -233,47 +233,47 @@
"This cannot be reverted" = "L'azione è irreversibile";
"unknown" = "sconosciuto";
"You have no Playlists" = "Non hai alcuna playlist";
"You have no playlists\n\nTap on \"New Playlist\" to create one" = "Non hai alcuna playlist.\n\nUsare \"Nuova playlist\" per crearne una";
"Could not load streams" = "Impossibile caricare i flussi";
"You have no playlists\n\nTap on \"New Playlist\" to create one" = "Non hai alcuna playlist.\n\nUsa \"Nuova playlist\" per crearne una";
"Could not load streams" = "Impossibile caricare stream";
"Share Logs..." = "Condividi log…";
"Open logs in Finder" = "Apri log in file";
"Could not refresh Subscriptions" = "Impossibile aggiornare le iscrizioni";
"Could not open video" = "Impossibile aprire il video";
"Channel could not be found" = "Impossibile trovare il canale";
"Could not extract channel information" = "Impossibile estrarre le informazioni del canale";
"Could not extract SID from received cookies: %@" = "Impossibile estrarre l'SID dai cookie ricevuti: %@";
"Open logs in Finder" = "Apri log nel Finder";
"Could not refresh Subscriptions" = "Impossibile aggiornare Iscrizioni";
"Could not open video" = "Impossibile aprire video";
"Channel could not be found" = "Canale non trovato";
"Could not extract channel information" = "Impossibile estrarre informazioni del canale";
"Could not extract SID from received cookies: %@" = "Impossibile estrarre SID dai cookie ricevuti: %@";
"Could not update your token." = "Impossibile aggiornare il tuo token.";
"Could not refresh Trending" = "Impossibile aggiornare le tendenze";
"Could not refresh Trending" = "Impossibile aggiornare Tendenze";
"This URL could not be opened" = "Impossibile aprire questo URL";
"Could not open channel" = "Impossibile aprire il canale";
"Could not refresh Popular" = "Impossibile aggiornare i più popolari";
"Could not create share link" = "Impossibile creare il link";
"Could not extract video ID" = "Impossibile estrarre l'ID del video";
"Could not load video" = "Impossibile caricare il video";
"If you want this app to be available in your language, join translation project." = "Se vuoi che l'app sia disponibile nella tua lingua, unisciti al progetto di traduzione.";
"Could not open channel" = "Impossibile aprire canale";
"Could not refresh Popular" = "Impossibile aggiornare Popolari";
"Could not create share link" = "Impossibile creare link di condivisione";
"Could not extract video ID" = "Impossibile estrarre ID video";
"Could not load video" = "Impossibile caricare video";
"If you want this app to be available in your language, join translation project." = "Se vuoi che quest'app sia disponibile nella tua lingua, unisciti al progetto di traduzione.";
"Translations" = "Traduzioni";
"Pause when entering background" = "Metti in pausa quando si esce dall'app";
"Pause when player is closed" = "Metti in pausa quando si minimizza il riproduttore";
"Pause when entering background" = "Metti in pausa quando esci dall'app";
"Pause when player is closed" = "Metti in pausa quando il riproduttore viene chiuso";
"Picture in Picture" = "Picture in Picture";
"Play" = "Riproduci";
"Play All" = "Riproduci Tutti";
"Play All" = "Riproduci tutti";
"Play in PiP" = "Riproduci in PiP";
"Play Last" = "Aggiungi alla coda";
"Play Music" = "Riproduci Musica";
"Play Next" = "Aggiungi a seguire";
"Play Last" = "Riproduci per ultimo";
"Play Music" = "Riproduci musica";
"Play Next" = "Riproduci come successivo";
"Playback" = "Riproduzione";
"Player" = "Riproduttore";
"Playlist" = "Playlist";
"Playlists" = "Playlist";
"Popular" = "Più popolari";
"Preferred Formats" = "Format preferiti";
"Popular" = "Popolari";
"Preferred Formats" = "Formati preferiti";
"Profiles" = "Profili";
"Promoting a product or service that is directly related to the creator themselves. This usually includes merchandise or promotion of monetized platforms." = "Promozioni per un servizio direttamente collegato al canale. Ciò include sezioni riguardanti vendita di merce, donazioni o informazioni in merito a collaboratori.";
"Promoting a product or service that is directly related to the creator themselves. This usually includes merchandise or promotion of monetized platforms." = "Promozione di un prodotto o servizio direttamente collegato al canale. Include la vendita di prodotti o la promozione di piattaforme monetizzate.";
"Public Locations" = "Posizioni pubbliche";
"Public Manifest" = "Manifesto pubblico";
"Play Now" = "Riproduci adesso";
"Play Now" = "Riproduci ora";
"Quality" = "Qualità";
"Quality Profile" = "Profilo di qualità";
"Quality Profile" = "Profilo qualità";
"Queue" = "Coda";
"Queue is empty" = "La coda è vuota";
"Regular size" = "Dimensione normale";
@@ -282,51 +282,51 @@
/* Video sort order in search */
"Relevance" = "Rilevanza";
"Reset watched status when playing again" = "Azzera lo stato si visualizzazione quando si riapre il video";
"Resolution" = "Definizione";
"Reset watched status when playing again" = "Azzera lo stato di visualizzazione quando riproduci di nuovo";
"Resolution" = "Risoluzione";
"Restart" = "Riavvia";
"Restart the app to apply the settings above." = "Riavvia l'app per applicare l'impostazione.";
"Restart the app to apply the settings above." = "Riavvia l'app per applicare le impostazioni.";
"Restore default profiles..." = "Ripristina profili predefiniti...";
"Rotate to portrait when exiting fullscreen" = "Orientamento verticale quando si esce dallo schermo intero";
"Rotate to portrait when exiting fullscreen" = "Ruota in verticale all'uscita da schermo intero";
"Round corners" = "Angoli arrotondati";
"Save" = "Salva";
"Save history of played videos" = "Salva la cronologia dei video guardati";
"Save history of played videos" = "Salva la cronologia dei video riprodotti";
"Save history of searches, channels and playlists" = "Salva la cronologia delle ricerche, dei canali e delle playlist";
"Search" = "Cerca";
"Sections" = "Sezioni";
"Seek gesture sensitivity" = "Sensibilità dello scorrimento";
"Seek gesture speed" = "Velocità dello scorrimento";
"Seek gesture sensitivity" = "Sensibilità scorrimento";
"Seek gesture speed" = "Velocità scorrimento";
"Restart/Play next" = "Riavvia/riproduci successivo";
"Segments typically found at the start of a video that include an animation, still frame or clip which are also seen in other videos by the same creator." = "Segmenti trovati tipicamente all'inizio di un video che includono un'animazione, un'immagine o una clip presenti anche in altri video dello stesso canale.";
"Seek with horizontal swipe on video" = "Naviga scorrendo orizzontalmente sul video";
"Seek with horizontal swipe on video" = "Naviga con scorrimento orizzontale sul video";
"Share %@ link" = "Condividi link %@";
"Share..." = "Condividi...";
/* Video duration filter in search */
"Short" = "Breve";
"Show account username" = "Mostra il nome dell'account";
"Show account username" = "Mostra nome utente dell'account";
"Show anonymous accounts" = "Mostra account anonimi";
"Show channel name" = "Mostra il nome del canale";
"Show channel name" = "Mostra nome canale";
"Show history" = "Mostra cronologia";
"Show keywords" = "Mostra parole chiave";
"Show playback statistics" = "Mostra statistiche di riproduzione";
"Show progress of watching on thumbnails" = "Mostra l'avanzamento del video sulla copertina";
"Show sidebar when space permits" = "Mostra la barra laterale se c'è abbastanza spazio";
"Show video length" = "Mostra la lunghezza del video";
"Show progress of watching on thumbnails" = "Mostra avanzamento del video sulle copertine";
"Show sidebar when space permits" = "Mostra barra laterale quando c'è abbastanza spazio";
"Show video length" = "Mostra lunghezza video";
"Shuffle" = "Casuale";
"Shuffle All" = "Casuale tutto";
"Sidebar" = "Barra laterale";
"Sign In Required" = "Richiesto l'accesso";
"Sign In Required" = "Accesso richiesto";
/* Player controls layout size */
"Small" = "Piccoli";
/* Player controls layout size */
"Smaller" = "Piccolissimi";
"Smaller" = "Più piccoli";
"Sort: %@" = "Ordina: %@";
/* SponsorBlock category name */
"Sponsor" = "Sponsorizzazione";
"Sponsor" = "Sponsor";
"SponsorBlock" = "SponsorBlock";
"SponsorBlock API Instance" = "Istanza API SponsorBlock";
"Subscribe" = "Iscriviti";
@@ -335,11 +335,11 @@
"Subscriptions" = "Iscrizioni";
"Switch to other public location" = "Passa a un'altra posizione pubblica";
"Switch to public locations" = "Passa a posizioni pubbliche";
"System controls buttons" = "Pulsanti di controllo del sistema";
"System controls show buttons for %@" = "Pulsanti di controllo del sistema per %@";
"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." = "Mi fa piacere. È bello offrire un'app che le persone usano volentieri. Puoi valutare di donare al progetto o aiutare contribuendo a sviluppare nuove funzionalità.";
"System controls buttons" = "Pulsanti di controllo di sistema";
"System controls show buttons for %@" = "Pulsanti di controllo di sistema per %@";
"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." = "Mi fa piacere sentirlo. È bello offrire un'app che le persone usano volentieri. Puoi lasciare una donazione o contribuire allo sviluppo di nuove funzionalità, se vuoi.";
"This cannot be reverted. You might need to switch between views or restart the app to see changes." = "L'azione è irreversibile. Potresti dover cambiare sezione o riavviare l'app per applicare le modifiche.";
"This information will be processed only on your device and used to connect you to the server in the specified country." = "Queste informazioni saranno elaborate solo sul tuo dispositivo e saranno usate per connetterti con il server nella nazione specificata.";
"This information will be processed only on your device and used to connect you to the server in the specified country." = "Queste informazioni saranno elaborate solo sul tuo dispositivo e saranno usate per connetterti con il server nel paese specificato.";
"This will remove all your custom profiles and return their default values. This cannot be reverted." = "Saranno eliminati tutti i profili personalizzati e verranno riportati ai valori originali. L'azione è irreversibile.";
"Thumbnails" = "Copertine";
@@ -349,8 +349,8 @@
/* Player controls layout size for TV */
"TV" = "TV";
"Typically near or at the end of the video when the credits pop up and/or endcards are shown." = "Tipicamente alla fine del video quando appaiono i ringraziamenti.";
"Unsubscribe" = "Cancella iscrizione";
"Typically near or at the end of the video when the credits pop up and/or endcards are shown." = "Tipicamente alla fine del video quando appaiono i ringraziamenti o le anteprime finali.";
"Unsubscribe" = "Annulla iscrizione";
"Upload date" = "Data di caricamento";
"Used to create links from videos, channels and playlists" = "Usato per creare link da video, canali e playlist";
"Username" = "Nome utente";
@@ -370,14 +370,14 @@
"Watching now" = "In riproduzione";
/* Video date filter in search */
"Week" = "Questa Settimana";
"Week" = "Questa settimana";
"Welcome" = "Benvenuto";
"When partially watched video is played" = "Quando viene aperto un video parzialmente guardato";
"When partially watched video is played" = "Quando viene riprodotto un video guardato in parte";
"Wi-Fi" = "Wi-Fi";
"Wiki" = "Wiki";
"Yattee" = "Yattee";
"You can use automatic profile selection based on current device status or switch it in video playback settings controls." = "Puoi usare la selezione automatica del profilo in base allo stato del dispositivo o cambiarlo nei controlli della riproduzione.";
"You need to create an instance and accounts\nto access %@ section" = "Devi creare un'istanza e un'account\nper accede alla sezione %@";
"You can use automatic profile selection based on current device status or switch it in video playback settings controls." = "Puoi usare la selezione automatica del profilo in base allo stato del dispositivo o cambiarlo dai controlli di riproduzione.";
"You need to create an instance and accounts\nto access %@ section" = "Devi creare un'istanza e un account\nper accedere alla sezione %@";
"You need to select an account\nto access %@ section" = "Devi selezionare un account\nper accedere alla sezione %@";
@@ -387,85 +387,85 @@
"Current Location" = "Posizione attuale";
"Private" = "Privata";
"Playback queue is empty" = "La coda è vuota";
"Playing Next" = "Successivo";
"You can switch between profiles in playback settings controls." = "Puoi cambiare profilo nei controlli della riproduzione.";
"Add Channels, Playlists and Searches to Favorites using" = "Aggiungi canali, playlist e ricerche ai tuoi preferiti usando";
"Make default" = "Imposta predefinito";
"Playing Next" = "Prossimo in coda";
"You can switch between profiles in playback settings controls." = "Puoi cambiare profilo dai controlli di riproduzione.";
"Add Channels, Playlists and Searches to Favorites using" = "Aggiungi canali, playlist e ricerche a Preferiti usando";
"Make default" = "Imposta come predefinito";
"Visibility" = "Visibilità";
"Current Playlist" = "Playlist attuale";
"Stream & Player" = "Flusso e riproduttore";
"Stream & Player" = "Stream e riproduttore";
"Statistics" = "Statistiche";
"Hardware decoder" = "Decoder hardware";
"Stream FPS" = "FPS flusso";
"Cached time" = "Tempo nella cache";
"Hardware decoder" = "Decodificatore hardware";
"Stream FPS" = "FPS stream";
"Cached time" = "Tempo in cache";
"Rate & Captions" = "Velocità e sottotitoli";
"Dropped frames" = "Frame saltati";
"Dropped frames" = "Frame persi";
"Any format" = "Qualsiasi formato";
"%@ formats" = "Formati %@";
"Keep last played video in the queue after restart" = "Mantieni l'ultimo video guardato nella coda dopo la chiusura";
"Playlist is empty\n\nTap and hold on a video and then \n\"Add to Playlist\"" = "La playlist è vuota\n\nTieni premuto su un video e\n\"Aggiungi alla playlist\"";
"It can be changed later in settings. You can use your own locations too." = "Potrà essere cambiata nelle impostazioni. Puoi anche usare posizioni personalizzate.";
"%@ formats" = "%@ formati";
"Keep last played video in the queue after restart" = "Mantieni l'ultimo video guardato in coda dopo il riavvio";
"Playlist is empty\n\nTap and hold on a video and then \n\"Add to Playlist\"" = "La playlist è vuota\n\nTieni premuto su un video e fai\n\"Aggiungi a playlist\"";
"It can be changed later in settings. You can use your own locations too." = "P essere cambiata nelle impostazioni. Puoi anche usare posizioni personalizzate.";
"Press and hold remote button to open captions and quality menus" = "Tieni premuto il pulsante del telecomando per aprire il menu dei sottotitoli e della qualità";
"Comments are disabled" = "I commenti sono disattivati";
"No comments" = "Nessun commento";
"No chapters information available" = "Nessun capitolo disponibile";
"Proxy videos" = "Proxy";
"Proxy videos" = "Guarda video con proxy";
"Rate" = "Velocità";
/* Video sort order in search */
"Rating" = "Voto";
"Rating" = "Valutazione";
"Recents" = "Recenti";
"Red" = "Rosso";
"Refresh" = "Aggiorna";
"Reset" = "Reimposta";
"Settings" = "Impostazioni";
"Share %@ link with time" = "Condividi link %@ con minutaggio";
"Share %@ link with time" = "Condividi link %@ al minuto corrente";
"Sort" = "Ordina";
"URL" = "URL";
"Yattee %@ (build %@)" = "Yattee %@ (build %@)";
/* Video date filter in search */
"Year" = "Quest'anno";
"You can find information about using Yattee in the Wiki pages." = "Puoi trovare informazioni sull'utilizzo di Yattee nella Wiki.";
"Could not open playlist" = "Impossibile aprire la playlist";
"Could not extract playlist ID" = "Impossibile estrarre l'ID della playlist";
"You can find information about using Yattee in the Wiki pages." = "Puoi trovare informazioni sull'utilizzo di Yattee nelle pagine della Wiki.";
"Could not open playlist" = "Impossibile aprire playlist";
"Could not extract playlist ID" = "Impossibile estrarre ID playlist";
"No locations available at the moment" = "Nessuna posizione è attualmente disponibile";
"Could not refresh Playlists" = "Impossibile aggiornare le playlist";
"Could not refresh Playlists" = "Impossibile aggiornare Playlist";
"No documents" = "Nessun documento";
"Inspector visibility" = "Visualizza ispettore";
"Inspector visibility" = "Visibilità ispettore";
"Are you sure you want to remove this document?" = "Eliminare questo documento?";
"Are you sure you want to remove %@ location?" = "Eliminare la posizione %@?";
"Are you sure you want to remove %@ location?" = "Rimuovere la posizione %@?";
"Recent Documents" = "Documenti recenti";
"Recent History" = "Recenti dalla cronologia";
"Show Favorites" = "Mostra preferiti";
"Show Open Videos quick actions" = "Mostra i controlli rapidi per aprire video";
"Recent History" = "Cronologia recente";
"Show Favorites" = "Mostra Preferiti";
"Show Open Videos quick actions" = "Mostra controlli rapidi per aprire video";
"Home" = "Home";
"Share files from Finder on a Mac\nor iTunes on Windows" = "Condividi file dal Finder su Mac\no da iTunes su Windows";
"Show Home" = "Mostra la Home";
"Pages toolbar position" = "Posizione della barra delle pagine";
"Show Home" = "Mostra Home";
"Pages toolbar position" = "Posizione pagine in barra strumenti";
"URL to Open" = "URL da aprire";
"Enter link to open" = "Inserire un link da aprire";
"Enter link to open" = "Inserisci link da aprire";
"Show only icons" = "Mostra solo icone";
"Video" = "Video";
"Sample Rate" = "Frequenza di campionamento";
"Buttons labels" = "Etichette dei pulsanti";
"Buttons labels" = "Etichette pulsanti";
"Files" = "File";
"Show Documents" = "Mostra documenti";
"Show Documents" = "Mostra Documenti";
"Video Details" = "Dettagli video";
"Show Inspector" = "Mostra ispettore";
"Clear Queue before opening" = "Cancella la coda prima di aprire";
"Clear Queue before opening" = "Cancella coda prima di aprire";
"Open" = "Apri";
"Video actions buttons" = "Pulsanti delle azioni nel video";
"Pages buttons" = "Pulsanti delle pagine";
"Video actions buttons" = "Pulsanti azione nel video";
"Pages buttons" = "Pulsanti di pagine";
"Could not open Files" = "Impossibile aprire File";
"Enter links to open, one per line" = "Inserire i link da aprire, uno per riga";
"Enter links to open, one per line" = "Inserisci link da aprire, uno per riga";
"Playback Mode" = "Modalità riproduzione";
"Add" = "Aggiungi";
"Hide" = "Nascondi";
"Only for local files and URLs" = "Solo per file locali e URL";
"Right" = "Destra";
"Open Files" = "Apri File";
"Show icons and text when space permits" = "Mostra icone e testi quando c'è abbastanza spazio";
"Show icons and text when space permits" = "Mostra icone e testo quando c'è abbastanza spazio";
"Left" = "Sinistra";
"Format" = "Formato";
"Driver" = "Driver";
@@ -475,63 +475,91 @@
"File" = "File";
"Codec" = "Codec";
"Size" = "Dimensione";
"Could not find any links to open in your clipboard" = "Impossibile trovare link da aprire negli appunti";
"Remove…" = "Elimina…";
"Actions buttons" = "Pulsanti azioni";
"Could not find any links to open in your clipboard" = "Nessun link da aprire trovato negli appunti";
"Remove…" = "Rimuovi…";
"Actions buttons" = "Pulsanti azione";
"Show sidebar" = "Mostra barra laterale";
"Default Profile" = "Profilo predefinito";
"Playback history is empty" = "La cronologia di riproduzione è vuota";
"Address" = "Indirizzo";
"Locations Manifest" = "Manifesto della posizione";
"Remove Location" = "Elimina posizione";
"Locations Manifest" = "Manifesto posizioni";
"Remove Location" = "Rimuovi posizione";
"Open Video" = "Apri video";
"Copy%@link" = "Copia link%@";
"Share%@link" = "Condividi link%@";
"Edit Favorites…" = "Modifica preferiti…";
"Open Videos" = "Apri video";
"Edit Favorites…" = "Modifica Preferiti…";
"Open Videos" = "Apri Video";
"FPS" = "FPS";
"Show Open Videos toolbar button" = "Mostra il pulsante per aprire video nella barra superiore";
"Show Open Videos toolbar button" = "Mostra pulsante per aprire video nella barra strumenti";
"Reload manifest" = "Ricarica manifesto";
"Always" = "Sempre";
"Could not delete document" = "Impossibile eliminare il documento";
"Could not delete document" = "Impossibile eliminare documento";
"Paste" = "Incolla";
"Channels" = "Canali";
"Share" = "Condividi";
"\"%@\" will be irreversibly removed from this device." = "\"%@\" sarà eliminato permanentemente da questo dispositivo.";
"Live Streams" = "Trasmissioni dal vivo";
"\"%@\" will be irreversibly removed from this device." = "\"%@\" verrà eliminato definitivamente da questo dispositivo.";
"Live Streams" = "Trasmissioni in diretta";
"Shorts" = "Shorts";
"Verified" = "Verificato";
"Channel" = "Canale";
"Maximum width expanded" = "Larghezza massima espansa";
"Right click channel thumbnail to open context menu with more actions" = "Tasto destro sulla miniatura del canale per aprire il menù con più opzioni";
"Actions Buttons" = "Pulsanti di azione";
"Right click channel thumbnail to open context menu with more actions" = "Tasto destro sulla miniatura del canale per aprire il menu con più azioni";
"Actions Buttons" = "Pulsanti azione";
"Short videos: visible" = "Shorts: visibili";
"Short videos: hidden" = "Shorts: nascosti";
"Mark channel feed as unwatched" = "Contrassegna il feed del canale come non visto";
"Play all unwatched" = "Riproduci tutti i non visti";
"Double tap gesture" = "Gesto del doppio-tap";
"Mark channel feed as unwatched" = "Segna feed canale come non guardato";
"Play all unwatched" = "Riproduci tutti i non guardati";
"Double tap gesture" = "Doppio tocco";
"Clear all" = "Pulisci tutto";
"Close video" = "Chiudi video";
"Maximum feed items" = "Massimo numero di items nel feed";
"Maximum feed items" = "Massimo numero di elementi nel feed";
"Open channels with description expanded" = "Apri canali con descrizione espansa";
"Are you sure you want to clear cache?" = "Sei sicuro di voler cancellare la cache?";
"Open expanded" = "Apri espandendo";
"Mark channel feed as watched" = "Contrassegna il feed del canale come già visto";
"Player Bar" = "Barra di scorrimento";
"Tap and hold channel thumbnail to open context menu with more actions" = "Clicca e tieni premuto sulla miniatura del canale per aprire il menù con più opzioni";
"Always show controls buttons" = "Mostra sempre i pulsanti per il controllo";
"Single tap gesture" = "Gesto col singolo tocco";
"Gesture: fowards" = "Gesto: in avanti";
"Controls Buttons" = "Pulsanti di controllo";
"Are you sure you want to clear cache?" = "Cancellare la cache?";
"Open expanded" = "Apri espanso";
"Mark channel feed as watched" = "Segna feed canale come guardato";
"Player Bar" = "Barra riproduttore";
"Tap and hold channel thumbnail to open context menu with more actions" = "Tieni premuto sulla miniatura del canale per aprire il menu con più azioni";
"Always show controls buttons" = "Mostra sempre pulsanti controllo";
"Single tap gesture" = "Tocco singolo";
"Gesture: fowards" = "Gesto: avanti";
"Controls Buttons" = "Pulsanti controllo";
"System controls" = "Controlli di sistema";
"Controls button: backwards" = "Pulsanti di controllo: indietro";
"Controls button: forwards" = "Pulsanti di controllo: in avanti";
"Controls button: backwards" = "Pulsanti controllo: indietro";
"Controls button: forwards" = "Pulsanti controllo: avanti";
"Gesture: backwards" = "Gesto: indietro";
"Hide player" = "Nascondi player";
"Play next item" = "Riproduci il prossimo";
"Hide player" = "Nascondi riproduttore";
"Play next item" = "Riproduci prossimo";
"Lock orientation" = "Blocca orientamento";
"Music Mode" = "Modalità Musica";
"Total size: %@" = "Dimensione totale: %@";
"Cache" = "Cache";
"Subscribe/Unsubscribe" = "Iscriviti/Disiscriviti";
"Show cache status" = "Mostra stato della cache";
"Subscribe/Unsubscribe" = "Iscriviti/Annulla iscrizione";
"Show cache status" = "Mostra stato cache";
"Show toggle watch status button" = "Mostra pulsante per segnare come guardato/non guardato";
"Gesture settings control skipping interval for double click on left/right side of the player. Changing system controls settings requires restart." = "Le impostazioni dei gesti controllano gli intervalli dei salti tramite doppio tocco a destra/sinistra del riproduttore. Cambiare le impostazioni dei controlli di sistema richiede il riavvio.";
"Gesture settings control skipping interval for remote arrow buttons (for 2nd generation Siri Remote or newer). Changing system controls settings requires restart." = "Le impostazioni dei gesti controllano gli intervalli dei salti tramite pulsanti freccia (per Telecomando Siri di 2ª generazione o più recente). Cambiare le impostazioni dei controlli di sistema richiede il riavvio.";
"Open channel" = "Apri canale";
"Seeking" = "Scrubbing";
"Gesture settings control skipping interval for double tap gesture on left/right side of the player. Changing system controls settings requires restart." = "Le impostazioni dei gesti controllano gli intervalli dei salti tramite doppio tocco a destra/sinistra del riproduttore. Cambiare le impostazioni dei controlli di sistema richiede il riavvio.";
"List" = "Lista";
"Cells" = "Celle";
"Show Next in Queue" = "Mostra prossimo in coda";
"Next in Queue" = "Prossimo in coda";
"Open video description expanded" = "Apri descrizione video espansa";
"Mark all as unwatched" = "Segna tutti come non guardati";
"Mark all as watched" = "Segna tutti come guardati";
"Queue - shuffled" = "Coda - casuale";
"Playback Settings" = "Impostazioni di riproduzione";
"Replay" = "Riguarda";
"Fullscreen" = "Schermo intero";
"Lock" = "Blocca";
"Description" = "Descrizione";
"Loop one" = "Ripeti uno";
"Autoplay next" = "Riproduci prossimo in automatico";
"Stream" = "Stream";
"Toggle size" = "Cambia dimensione";
"Toggle player" = "Apri/Chiudi riproduttore";
"Do nothing" = "Nessuna azione";
"Feed" = "Feed";
"Inspector" = "Ispettore";
"Show unwatched feed badges" = "Mostra badge per video non guardati nel feed";

View File

@@ -50,7 +50,7 @@
"Decreased opacity" = "透明化";
"Delete" = "削除";
"Custom Locations" = "場所の指定";
"Decrease rate" = "評価を下げる";
"Decrease rate" = "速度を下げる";
"Done" = "完了";
"Discord Server" = "Discord のサーバー";
"Duration" = "長さ";
@@ -75,7 +75,7 @@
"Highest" = "最高";
"Highest quality" = "最高品質";
"Honor orientation lock" = "向きのロックに従う";
"Increase rate" = "評価を上げる";
"Increase rate" = "速度を上げる";
/* SponsorBlock category name */
"Intro" = "導入シーン";
@@ -87,7 +87,7 @@
"Large" = "大";
/* Loading stream OSD */
"Loading streams..." = "ストリームを読み込み中...";
"Loading streams..." = "ストリーム読込中...";
"Lock portrait mode" = "縦モードをロック";
"LIVE" = "ライブ";
"Locations" = "場所";
@@ -105,7 +105,7 @@
"Not available" = "利用できません";
/* SponsorBlock category name */
"Offtopic in Music Videos" = "音楽動画で脱線した内容";
"Offtopic in Music Videos" = "音楽動画で脱線した内容";
"Only when signed in" = "ログイン時のみ";
"Orientation" = "向き";
"Opening audio stream..." = "音声ストリームを開始中...";
@@ -125,7 +125,7 @@
"Queue" = "再生待ち";
"Red" = "赤";
"Recents" = "最近";
"Rate" = "評価";
"Rate" = "速度";
"Refresh" = "更新";
/* Video sort order in search */
@@ -154,7 +154,7 @@
"Show keywords" = "キーワードを表示";
/* SponsorBlock category name */
"Sponsor" = "広告";
"Sponsor" = "広告";
"SponsorBlock" = "SponsorBlock";
"SponsorBlock API Instance" = "SponsorBlock API インスタンス";
"Source" = "入力";
@@ -317,10 +317,10 @@
/* Player controls layout size */
"Small" = "小";
"URL" = "URL";
"Playing Next" = "次再生";
"Playing Next" = "次再生";
"Playback Mode" = "再生モード";
"Play Now" = "すぐ再生";
"Play Next" = "次再生";
"Play Now" = "すぐ再生";
"Play Next" = "次再生";
"Play Last" = "最後に再生";
"Play" = "再生";
"Playlist" = "プレイリスト";
@@ -382,7 +382,7 @@
"Lowest" = "最低";
"Low quality" = "低品質";
"Regular Size" = "通常時のサイズ";
"Large layout is not suitable for all devices and using it may cause controls not to fit on the screen." = "大きな表示はあらゆる端末では適さず、生魚ボタンが画面に収まらないことがあります。";
"Large layout is not suitable for all devices and using it may cause controls not to fit on the screen." = "大きな表示はあらゆる端末ではなく、制御ボタンが画面に収まらないことがあります。";
"Seeking" = "シーク";
"Always show controls buttons" = "常に制御ボタンを表示";
"Controls button: backwards" = "制御ボタン: 戻る";
@@ -487,7 +487,7 @@
"Category" = "分類";
/* SponsorBlock category name */
"Interaction" = "操作の呼びかけ";
"Interaction" = "操作の要求";
/* SponsorBlock category name */
"Self-promotion" = "自己宣伝";
@@ -530,3 +530,31 @@
"Public Locations" = "公開された場所";
"Switch to public locations" = "公開された場所に切り替え";
"Explicit reminders to like, subscribe or interact with them on any paid or free platform(s) (e.g. click on a video)." = "有料/無料のプラットフォームかを問わず、いいね、登録などを明示的に操作を促す(例: 動画をクリック)。";
"Proxy videos" = "動画閲覧にプロキシ使用";
"Sections" = "表示部分";
"System controls show buttons for %@" = "システム制御「%@」用のボタンを表示";
"You need to create an instance and accounts\nto access %@ section" = "%@ セクションの利用には\nインスタンスとアカウントの作成が必要";
"You need to select an account\nto access %@ section" = "%@ セクションの利用には\nアカウントの選択が必要";
"Visibility" = "表示";
"Cached time" = "キャッシュ済みの時間";
"Rate & Captions" = "速度と字幕";
"Fullscreen" = "全画面";
"Toggle player" = "プレイヤーを切り替え";
"Toggle size" = "大きさを切り替え";
"List" = "一覧";
"Cells" = "アイコン";
"Show toggle watch status button" = "視聴状態切り替えボタンを表示";
"Open channel" = "チャンネルを開く";
"Do nothing" = "何もしない";
"Open video description expanded" = "動画の説明を展開";
"Feed" = "フィード";
"Mark all as unwatched" = "すべて未視聴に";
"Mark all as watched" = "すべて視聴済みに";
"Queue - shuffled" = "再生キュー - シャッフル";
"Replay" = "もう一回再生";
"Lock" = "ロック";
"Loop one" = "ループ再生";
"Description" = "説明";
"Playback Settings" = "再生設定";
"Autoplay next" = "次を自動再生";
"Stream" = "ストリーム";

View File

@@ -564,3 +564,19 @@
"Autoplay next" = "Odtwarzaj automatycznie";
"Stream" = "Strumień";
"Show Next in Queue" = "Pokaż Następne w kolejce";
"Show scroll to top button in comments" = "Pokaż przycisk przewijania do góry w komentarzach";
"Seek" = "Przewijanie";
"Enter account credentials to connect..." = "Wprowadź dane logowania do konta, aby połączyć...";
"Enter location address to connect..." = "Wprowadź adres lokalizacji, aby połączyć...";
"Opened File" = "Otwarty plik";
"File Extension" = "Rozszerzenie pliku";
"Opening file..." = "Otwieranie pliku...";
"Public account" = "Konto publiczne";
"Your Accounts" = "Twoje konta";
"Browse without account" = "Przeglądanie bez konta";
"Use system controls with AVPlayer" = "Użyj systemowych kontrolek z AVPlayer";
"Close video and player on end" = "Zamknij wideo i odtwarzacz po zakończeniu";
"Rotate when entering fullscreen on landscape video" = "Obróć podczas przechodzenia do trybu pełnoekranowego w poziomym wideo";
"Landscape right" = "Obrót w prawo";
"No rotation" = "Brak rotacji";
"Landscape left" = "Obrót w lewo";

View File

@@ -191,9 +191,9 @@
"Round corners" = "Bordas arredondadas";
"Save" = "Salvar";
"Restart/Play next" = "Reiniciar/Tocar próximo";
"Seek gesture sensitivity" = "Sensitividade do gesto de busca";
"Seek gesture speed" = "Velocidade do gesto de busca";
"Seek with horizontal swipe on video" = "Buscar com deslize horizontal no vídeo";
"Seek gesture sensitivity" = "Sensitividade do gesto de vasculhar";
"Seek gesture speed" = "Velocidade do gesto de vasculhar";
"Seek with horizontal swipe on video" = "Vasculhar com deslize horizontal no vídeo";
"Proxy videos" = "Proxy de vídeos";
"Rate" = "Taxa";
@@ -531,7 +531,7 @@
"Player Bar" = "Barra do Player";
"Gesture settings control skipping interval for double tap gesture on left/right side of the player. Changing system controls settings requires restart." = "As configurações de gesto controlam o intervalo de pulo para o gesto de dois toques no lado esquerdo/direito do player. Mudar as configurações dos controles do sistema requer reinício.";
"Gesture settings control skipping interval for remote arrow buttons (for 2nd generation Siri Remote or newer). Changing system controls settings requires restart." = "As configurações de gesto controlam o intervalo de pulo para os botões de seta do controle remoto (para Siri Remote de 2ª geração ou posteriores). Mudar as configurações dos controles do sistema requer reinício.";
"Seeking" = "Busca";
"Seeking" = "Vasculhação";
"Show unwatched feed badges" = "Mostrar ícone de feed não visto";
"Clear all" = "Limpar tudo";
"Gesture: backwards" = "Gesto: para trás";
@@ -563,3 +563,10 @@
"Open video description expanded" = "Abrir descrição do vídeo expandida";
"Replay" = "Replay";
"Stream" = "Stream";
"Show scroll to top button in comments" = "Mostrar botão de voltar ao topo nos comentários";
"Enter location address to connect..." = "Insira o endereço da localização para conectar…";
"Seek" = "Vasculhar";
"Enter account credentials to connect..." = "Insira as credenciais da conta para conectar…";
"Opened File" = "Arquivo Aberto";
"File Extension" = "Extensão do Arquivo";
"Opening file..." = "Abrindo arquivo…";

View File

@@ -563,3 +563,10 @@
"Loop one" = "Buclă unu";
"Autoplay next" = "Urmează redarea automată";
"Stream" = "Flux";
"Show scroll to top button in comments" = "Afișați butonul de derulare până sus în comentarii";
"Seek" = "Căuta";
"Enter account credentials to connect..." = "Introduceți acreditările contului pentru a vă conecta...";
"Enter location address to connect..." = "Introdu adresa locației pentru a te conecta...";
"Opened File" = "Fișier deschis";
"Opening file..." = "Deschiderea fișierului...";
"File Extension" = "Extensie fișier";

View File

@@ -67,9 +67,9 @@
"I am lost" = "Я загубився";
"I found a bug /" = "Я знайшов баг /";
"I have a feature request" = "В мене є пропозиція";
"I like this app!" = "Мені подобається цей додаток!";
"I like this app!" = "Мені подобається цей застосунок!";
"I want to ask a question" = "Хочу задати питання";
"If you are interested what's coming in future updates, you can track project Milestones." = "Якщо вам цікаво, що буде в наступних оновленнях, ви можете відстежувати етапи проекту.";
"If you are interested what's coming in future updates, you can track project Milestones." = "Якщо вам цікаво, що буде в наступних оновленнях, ви можете відстежувати етапи проєкту.";
"Increase rate" = "Підвищити якість";
"Info" = "Інфо";
"Instance of current account" = "Экземпляр поточного акаунта";
@@ -197,7 +197,7 @@
"Help" = "Допомога";
"Related" = "Схожі";
"Honor orientation lock" = "Блокування орієнтації";
"If you are reporting a bug, include all relevant details (especially: app version, used device and system version, steps to reproduce)." = "Якщо ви повідомляєте про помилку, вказуйте всі відповідні деталі (особливо: версію програми, використовуваний пристрій, версію системи і кроки для відтворення).";
"If you are reporting a bug, include all relevant details (especially: app version, used device and system version, steps to reproduce)." = "Якщо ви повідомляєте про помилку, вказуйте всі відповідні деталі (особливо: версію застосунку, використовуваний пристрій, версію системи і кроки для відтворення).";
"Promoting a product or service that is directly related to the creator themselves. This usually includes merchandise or promotion of monetized platforms." = "Просування продукту або послуги, які безпосередньо пов'язані з самим автором. Зазвичай це стосується товарів або просування монетизованих платформ.";
"Red" = "Червоний";
"Refresh" = "Поновити";
@@ -229,7 +229,7 @@
"Clear Search History" = "Очистити історію пошуку";
"Clear Search History..." = "Очистити історію пошуку...";
"Clear the queue" = "Очистити чергу";
"Close PiP and open player when application enters foreground" = "Закрийте PiP і відкрийте плеєр, коли програма відкриється";
"Close PiP and open player when application enters foreground" = "Закрийте PiP і відкрийте плеєр, коли застосунок відкриється";
"Close player when closing video" = "Закрийте плеєр, коли зупините відео";
"Close player when starting PiP" = "Закрийте плеєр при старті PiP";
"Close Video" = "Закрити відео";
@@ -411,14 +411,14 @@
"Share %@ link with time" = "Поділитися посиланням %@ з часом";
"Reset watched status when playing again" = "Скинути статус перегляду при повторному відкритті";
"Show account username" = "Відображати імʼя акаунту";
"Restart the app to apply the settings above." = "Перезапустіть додаток, щоб застосувати нові налаштування.";
"Restart the app to apply the settings above." = "Перезапустіть застосунок, щоб застосувати нові налаштування.";
"Show anonymous accounts" = "Показувати анонімні акаунти";
"Show channel name" = "Показувати назву каналу";
"Sign In Required" = "Необхідно увійти в систему";
"It can be changed later in settings. You can use your own locations too." = "Пізніше це можна змінити в налаштуваннях. Ви також можете використовувати власні локації.";
"System controls buttons" = "Кнопки керування системою";
"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." = "Приємно це чути. Створювати додатки, якими хочуть користуватися інші люди, дуже приємно. Ви можете розглянути можливість пожертвувати кошти на проект або допомогти, взявши участь у розробці нових функцій.";
"This cannot be reverted. You might need to switch between views or restart the app to see changes." = "Це не можна буде скасувати. Можливо, вам доведеться переключитися між режимами перегляду або перезапустити програму, щоб побачити зміни.";
"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." = "Приємно це чути. Створювати застосунки, якими хочуть користуватися інші люди, дуже приємно. Ви можете розглянути можливість пожертвувати кошти на проєкт або допомогти, взявши участь у розробці нових функцій.";
"This cannot be reverted. You might need to switch between views or restart the app to see changes." = "Це не можна буде скасувати. Можливо, вам доведеться перемкнутися між режимами перегляду або перезапустити застосунок, щоб побачити зміни.";
"Keep last played video in the queue after restart" = "Залишати останнє проглянуте відео в черзі після перезапуску";
"Could not load streams" = "Не вдалося загрузити трансляції";
"Could not open video" = "Не вдалося відкрити відео";
@@ -430,7 +430,7 @@
"Could not load video" = "Не вдалося завантажити відео";
"No locations available at the moment" = "Зараз немає доступних API (локацій)";
"Could not refresh Playlists" = "Не вдалося поновити плейлисти";
"If you want this app to be available in your language, join translation project." = "Ви можете змінити або створити свій переклад на сторінці проекту.";
"If you want this app to be available in your language, join translation project." = "Ви можете змінити або створити свій переклад на сторінці проєкту.";
"No documents" = "Немає документів";
"Are you sure you want to remove this document?" = "Ви впевнені, що бажаєте видалити цей документ?";
"Recent Documents" = "Нещодавні документи";
@@ -502,3 +502,68 @@
"Shorts" = "Shorts";
"Verified" = "Перевірений";
"Channel" = "Канал";
"Open expanded" = "Відкрито розширено";
"Mark channel feed as unwatched" = "Позначити стрiчку каналу не переглянутою";
"Mark channel feed as watched" = "Позначити стрiчку каналу переглянутою";
"Short videos: visible" = "Короткi вiдео: виднi";
"Player Bar" = "Панель плеєра";
"Short videos: hidden" = "Короткi вiдео: схованi";
"Play all unwatched" = "Дивитися всi не переглянутi";
"Double tap gesture" = "Жест подвійного дотику";
"Tap and hold channel thumbnail to open context menu with more actions" = "Натисніть і утримуйте мініатюру каналу, щоб відкрити контекстне меню з додатковими діями";
"Always show controls buttons" = "Завжди показувати кнопки керування";
"Single tap gesture" = "Жест одним дотиком";
"Maximum width expanded" = "Максимальна ширина збільшена";
"Clear all" = "Зачистити все";
"Right click channel thumbnail to open context menu with more actions" = "Клацніть правою кнопкою миші на мініатюрі каналу, щоб відкрити контекстне меню з іншими діями";
"Show unwatched feed badges" = "Показати бейджики непроглянутих стрічок";
"Seeking" = "У пошуках";
"Gesture: fowards" = "Жест: вперед";
"Controls Buttons" = "Кнопки керування";
"System controls" = "Керування системою";
"Controls button: backwards" = "Кнопка керування: назад";
"Controls button: forwards" = "Кнопка керування: вперед";
"Gesture: backwards" = "Жест: назад";
"Gesture settings control skipping interval for double tap gesture on left/right side of the player. Changing system controls settings requires restart." = "Налаштування жестів визначають інтервал пропуску подвійного дотику до плеєра зліва/справа. Зміна налаштувань управління системою вимагає перезапуску.";
"Hide player" = "Приховати плеєр";
"Gesture settings control skipping interval for double click on left/right side of the player. Changing system controls settings requires restart." = "Налаштування жестів визначають інтервал пропуску подвійного клацання зліва/справа на плеєрі. Зміна налаштувань системних елементів керування потребує перезапуску.";
"Gesture settings control skipping interval for remote arrow buttons (for 2nd generation Siri Remote or newer). Changing system controls settings requires restart." = "Налаштування жестів керують інтервалом пропуску кнопок зі стрілками пульта дистанційного керування (для пульта дистанційного керування Siri Remote 2-го покоління або новіших). Зміна налаштувань системних елементів керування потребує перезапуску.";
"Actions Buttons" = "Кнопки дій";
"Play next item" = "Відтворити наступний елемент";
"Lock orientation" = "Зафіксувати орієнтацію";
"Music Mode" = "Музичний режим";
"Close video" = "Закрити відео";
"Total size: %@" = "Загальний розмір: %@";
"Open channels with description expanded" = "Відкрити канали з розширеним описом";
"Cache" = "Кеш";
"Subscribe/Unsubscribe" = "Підписатися/відписатися";
"Show cache status" = "Показати стан кешу";
"Maximum feed items" = "Максимальна кількість в стрічці";
"Are you sure you want to clear cache?" = "Ви впевнені, що хочете очистити кеш?";
"Show Next in Queue" = "Показати наступного в черзі";
"Show toggle watch status button" = "Показати перемикач переглянутого";
"Next in Queue" = "Наступний у черзі";
"List" = "Список";
"Cells" = "Комірки";
"Toggle size" = "Розмір перемикача";
"Toggle player" = "Переключити плеєр";
"Do nothing" = "Нічого не робити";
"Feed" = "Стрічка";
"Open channel" = "Відкрити канал";
"Inspector" = "Інспектор";
"Open video description expanded" = "Відкрити опис відео розширено";
"Mark all as unwatched" = "Позначити всі не переклянутими";
"Mark all as watched" = "Позначити всі переглянутими";
"Queue - shuffled" = "Черга - перемішана";
"Playback Settings" = "Налаштування відтворення";
"Replay" = "Повтор";
"Fullscreen" = "Повний екран";
"Lock" = "Замок";
"Description" = "Опис";
"Loop one" = "Цикл один";
"Autoplay next" = "Автовідтворення далі";
"Stream" = "Потік";
"Enter account credentials to connect..." = "Введіть облікові дані для з'єднання...";
"Seek" = "Шукати";
"Show scroll to top button in comments" = "Показати кнопку прокрутки вгору в коментарях";
"Enter location address to connect..." = "Введіть адресу розташування для з'єднання...";

View File

@@ -205,6 +205,7 @@
371B7E6A2759791900D21217 /* CommentsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E692759791900D21217 /* CommentsModel.swift */; };
371B7E6B2759791900D21217 /* CommentsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E692759791900D21217 /* CommentsModel.swift */; };
371B7E6C2759791900D21217 /* CommentsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E692759791900D21217 /* CommentsModel.swift */; };
371B88F82A1A310100D57683 /* String+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3782B9512755667600990149 /* String+Format.swift */; };
371CC76829466ED000979C1A /* AccountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371CC76729466ED000979C1A /* AccountsView.swift */; };
371CC76929466ED000979C1A /* AccountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371CC76729466ED000979C1A /* AccountsView.swift */; };
371CC76A29466ED000979C1A /* AccountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371CC76729466ED000979C1A /* AccountsView.swift */; };
@@ -253,7 +254,6 @@
37319F0627103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
37319F0727103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
3732BFD028B83763009F3F4D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 3732BFCF28B83763009F3F4D /* KeychainAccess */; };
3732C9FD28C012E600E7DCAF /* SafeArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37ECED55289FE166002BC2C9 /* SafeArea.swift */; };
3735C4E729A2D3D70051D251 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 3735C4E629A2D3D70051D251 /* Logging */; };
3736A1FE286BB72300C9E5EE /* libavdevice.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3736A1EF286BB72300C9E5EE /* libavdevice.xcframework */; };
3736A1FF286BB72300C9E5EE /* libavdevice.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3736A1EF286BB72300C9E5EE /* libavdevice.xcframework */; };
@@ -375,7 +375,6 @@
374D11E72943C56300CB4350 /* Cache in Frameworks */ = {isa = PBXBuildFile; productRef = 374D11E62943C56300CB4350 /* Cache */; };
374DE88028BB896C0062BBF2 /* PlayerDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374DE87F28BB896C0062BBF2 /* PlayerDragGesture.swift */; };
374DE88128BB896C0062BBF2 /* PlayerDragGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374DE87F28BB896C0062BBF2 /* PlayerDragGesture.swift */; };
374DE88328BB8A280062BBF2 /* PlayerOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374DE88228BB8A280062BBF2 /* PlayerOrientation.swift */; };
375168D62700FAFF008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
375168D72700FDB8008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
375168D82700FDB9008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
@@ -725,6 +724,11 @@
37B767DD2677C3CA0098BAA8 /* PlayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B767DA2677C3CA0098BAA8 /* PlayerModel.swift */; };
37B795902771DAE0001CF27B /* OpenURLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */; };
37B795912771DAE0001CF27B /* OpenURLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */; };
37B7CFE92A19603B001B0564 /* ToolbarBackground+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B7CFE82A19603B001B0564 /* ToolbarBackground+Backport.swift */; };
37B7CFEB2A1960EC001B0564 /* ToolbarColorScheme+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B7CFEA2A1960EC001B0564 /* ToolbarColorScheme+Backport.swift */; };
37B7CFEC2A197844001B0564 /* AppleAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */; };
37B7CFEE2A19789F001B0564 /* MacOSPiPDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B7CFED2A19789F001B0564 /* MacOSPiPDelegate.swift */; };
37B7CFEF2A197A08001B0564 /* SafeAreaModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3142A18F7630059A470 /* SafeAreaModel.swift */; };
37B81AF926D2C9A700675966 /* VideoPlayerSizeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */; };
37B81AFA26D2C9A700675966 /* VideoPlayerSizeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */; };
37B81AFC26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B81AFB26D2C9C900675966 /* VideoDetailsPaddingModifier.swift */; };
@@ -781,7 +785,6 @@
37BE0BD326A1D4780092E2DB /* AppleAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */; };
37BE0BD426A1D47D0092E2DB /* AppleAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */; };
37BE0BD726A1D4A90092E2DB /* AppleAVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD526A1D4A90092E2DB /* AppleAVPlayerViewController.swift */; };
37BE0BDC26A2367F0092E2DB /* AppleAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BDB26A2367F0092E2DB /* AppleAVPlayerView.swift */; };
37C069782725962F00F7F6CB /* ScreenSaverManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C069772725962F00F7F6CB /* ScreenSaverManager.swift */; };
37C0697A2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C069792725C09E00F7F6CB /* PlayerQueueItemBridge.swift */; };
37C0697B2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C069792725C09E00F7F6CB /* PlayerQueueItemBridge.swift */; };
@@ -885,6 +888,11 @@
37D9BA0629507F69002586BD /* PlayerControlsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */; };
37D9BA0729507F69002586BD /* PlayerControlsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */; };
37D9BA0829507F69002586BD /* PlayerControlsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */; };
37DCD3112A18E8150059A470 /* OrientationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3102A18E8150059A470 /* OrientationModel.swift */; };
37DCD3152A18F7630059A470 /* SafeAreaModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3142A18F7630059A470 /* SafeAreaModel.swift */; };
37DCD3172A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3162A191A180059A470 /* AVPlayerViewController+FullScreen.swift */; };
37DCD3192A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3162A191A180059A470 /* AVPlayerViewController+FullScreen.swift */; };
37DCD31A2A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DCD3162A191A180059A470 /* AVPlayerViewController+FullScreen.swift */; };
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
37DD87C8271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
@@ -941,7 +949,6 @@
37EBD8CA27AF26C200F1C24B /* MPVBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EBD8C927AF26C200F1C24B /* MPVBackend.swift */; };
37EBD8CB27AF26C200F1C24B /* MPVBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EBD8C927AF26C200F1C24B /* MPVBackend.swift */; };
37EBD8CC27AF26C200F1C24B /* MPVBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EBD8C927AF26C200F1C24B /* MPVBackend.swift */; };
37ECED56289FE166002BC2C9 /* SafeArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37ECED55289FE166002BC2C9 /* SafeArea.swift */; };
37EE6DC528A305AD00BFD632 /* Reachability in Frameworks */ = {isa = PBXBuildFile; productRef = 37EE6DC428A305AD00BFD632 /* Reachability */; };
37EF5C222739D37B00B03725 /* MenuModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EF5C212739D37B00B03725 /* MenuModel.swift */; };
37EF5C232739D37B00B03725 /* MenuModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EF5C212739D37B00B03725 /* MenuModel.swift */; };
@@ -1258,7 +1265,6 @@
374C0542272496E4009BDDBE /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = macOS/AppDelegate.swift; sourceTree = SOURCE_ROOT; };
374C0544272496FD009BDDBE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
374DE87F28BB896C0062BBF2 /* PlayerDragGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerDragGesture.swift; sourceTree = "<group>"; };
374DE88228BB8A280062BBF2 /* PlayerOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerOrientation.swift; sourceTree = "<group>"; };
375168D52700FAFF008F96A6 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = "<group>"; };
3751B4B127836902000B7DF4 /* SearchPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchPage.swift; sourceTree = "<group>"; };
3751BA7D27E63F1D007B1A60 /* MPVOGLView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPVOGLView.swift; sourceTree = "<group>"; };
@@ -1381,6 +1387,9 @@
37B4E804277D0AB4004BF56A /* Orientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Orientation.swift; sourceTree = "<group>"; };
37B767DA2677C3CA0098BAA8 /* PlayerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerModel.swift; sourceTree = "<group>"; };
37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenURLHandler.swift; sourceTree = "<group>"; };
37B7CFE82A19603B001B0564 /* ToolbarBackground+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ToolbarBackground+Backport.swift"; sourceTree = "<group>"; };
37B7CFEA2A1960EC001B0564 /* ToolbarColorScheme+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ToolbarColorScheme+Backport.swift"; sourceTree = "<group>"; };
37B7CFED2A19789F001B0564 /* MacOSPiPDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacOSPiPDelegate.swift; sourceTree = "<group>"; };
37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerSizeModifier.swift; sourceTree = "<group>"; };
37B81AFB26D2C9C900675966 /* VideoDetailsPaddingModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDetailsPaddingModifier.swift; sourceTree = "<group>"; };
37B81AFE26D2CA3700675966 /* VideoDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDetails.swift; sourceTree = "<group>"; };
@@ -1403,7 +1412,6 @@
37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = "<group>"; };
37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleAVPlayerView.swift; sourceTree = "<group>"; };
37BE0BD526A1D4A90092E2DB /* AppleAVPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleAVPlayerViewController.swift; sourceTree = "<group>"; };
37BE0BDB26A2367F0092E2DB /* AppleAVPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleAVPlayerView.swift; sourceTree = "<group>"; };
37C069772725962F00F7F6CB /* ScreenSaverManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenSaverManager.swift; sourceTree = "<group>"; };
37C069792725C09E00F7F6CB /* PlayerQueueItemBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerQueueItemBridge.swift; sourceTree = "<group>"; };
37C0697D2725C8D400F7F6CB /* CMTime+DefaultTimescale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CMTime+DefaultTimescale.swift"; sourceTree = "<group>"; };
@@ -1458,6 +1466,9 @@
37D836BB294927E700005E5E /* ChannelsCacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelsCacheModel.swift; sourceTree = "<group>"; };
37D9169A27388A81002B1BAA /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsSettings.swift; sourceTree = "<group>"; };
37DCD3102A18E8150059A470 /* OrientationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrientationModel.swift; sourceTree = "<group>"; };
37DCD3142A18F7630059A470 /* SafeAreaModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeAreaModel.swift; sourceTree = "<group>"; };
37DCD3162A191A180059A470 /* AVPlayerViewController+FullScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AVPlayerViewController+FullScreen.swift"; sourceTree = "<group>"; };
37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStreams.swift; sourceTree = "<group>"; };
37DD9DA22785BBC900539416 /* NoCommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCommentsView.swift; sourceTree = "<group>"; };
37DD9DAF2785D58D00539416 /* RefreshControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshControl.swift; sourceTree = "<group>"; };
@@ -1485,7 +1496,6 @@
37EBD8C327AF0DA800F1C24B /* PlayerBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerBackend.swift; sourceTree = "<group>"; };
37EBD8C527AF26B300F1C24B /* AVPlayerBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerBackend.swift; sourceTree = "<group>"; };
37EBD8C927AF26C200F1C24B /* MPVBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPVBackend.swift; sourceTree = "<group>"; };
37ECED55289FE166002BC2C9 /* SafeArea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeArea.swift; sourceTree = "<group>"; };
37EF5C212739D37B00B03725 /* MenuModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuModel.swift; sourceTree = "<group>"; };
37EF9A75275BEB8E0043B585 /* CommentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentView.swift; sourceTree = "<group>"; };
37EFAC0728C138CD00ED9B89 /* ControlsOverlayModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlsOverlayModel.swift; sourceTree = "<group>"; };
@@ -1812,7 +1822,6 @@
374DE87F28BB896C0062BBF2 /* PlayerDragGesture.swift */,
3703100127B0713600ECDDAA /* PlayerGestures.swift */,
373031F22838388A000CFD59 /* PlayerLayerView.swift */,
374DE88228BB8A280062BBF2 /* PlayerOrientation.swift */,
3743CA4D270EFE3400E4D32B /* PlayerQueueRow.swift */,
373197D82732015300EF734F /* RelatedView.swift */,
3795593527B08538007FF8F4 /* StreamControl.swift */,
@@ -1899,6 +1908,8 @@
37E80F3F287B472300561799 /* ScrollContentBackground+Backport.swift */,
376E331128AD3B320070E30C /* ScrollDismissesKeyboard+Backport.swift */,
3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */,
37B7CFE82A19603B001B0564 /* ToolbarBackground+Backport.swift */,
37B7CFEA2A1960EC001B0564 /* ToolbarColorScheme+Backport.swift */,
3727B74927872A920021C15E /* VisualEffectBlur-iOS.swift */,
3727B74727872A500021C15E /* VisualEffectBlur-macOS.swift */,
);
@@ -2180,6 +2191,8 @@
3749BF9227ADA142000480FF /* BridgingHeader.h */,
37992DC726CC50BC003D4C27 /* Info.plist */,
3782430A291E5AFA005DEC1C /* Yattee (iOS).entitlements */,
37DCD3102A18E8150059A470 /* OrientationModel.swift */,
37DCD3142A18F7630059A470 /* SafeAreaModel.swift */,
);
path = iOS;
sourceTree = "<group>";
@@ -2211,8 +2224,8 @@
isa = PBXGroup;
children = (
374C0542272496E4009BDDBE /* AppDelegate.swift */,
37BE0BDB26A2367F0092E2DB /* AppleAVPlayerView.swift */,
37FD43DB270470B70073EE42 /* InstancesSettings.swift */,
37B7CFED2A19789F001B0564 /* MacOSPiPDelegate.swift */,
3751BA7D27E63F1D007B1A60 /* MPVOGLView.swift */,
37F7AB5428A951B200FB46B5 /* Power.swift */,
37E04C0E275940FB00172673 /* VerticalScrollingFix.swift */,
@@ -2228,6 +2241,7 @@
isa = PBXGroup;
children = (
379775922689365600DD52A8 /* Array+Next.swift */,
37DCD3162A191A180059A470 /* AVPlayerViewController+FullScreen.swift */,
376578842685429C00D4EA09 /* CaseIterable+Next.swift */,
37C0697D2725C8D400F7F6CB /* CMTime+DefaultTimescale.swift */,
378AE942274EF00A006A4EE1 /* Color+Background.swift */,
@@ -2255,9 +2269,9 @@
37992DC826CC50CD003D4C27 /* iOS */,
37BE0BD826A214500092E2DB /* macOS */,
37D4B159267164AE00C925CA /* tvOS */,
37D4B1B72672CFE300C925CA /* Model */,
37D4B0C12671614700C925CA /* Shared */,
3722AEBA274DA312005EA4D6 /* Backports */,
37D4B1B72672CFE300C925CA /* Model */,
37C7A9022679058300E721B4 /* Extensions */,
37DD9DCC2785EE6F00539416 /* Vendor */,
3748186426A762300084E870 /* Fixtures */,
@@ -2298,7 +2312,6 @@
3729037D2739E47400EA99F6 /* MenuCommands.swift */,
37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */,
374924EC2921669B0017D862 /* PreferenceKeys.swift */,
37ECED55289FE166002BC2C9 /* SafeArea.swift */,
3700155E271B12DD0049C794 /* SiestaConfiguration.swift */,
37FFC43F272734C3009FFD26 /* Throttle.swift */,
378FFBC328660172009E3FBE /* URLParser.swift */,
@@ -3045,6 +3058,7 @@
378FFBC728660172009E3FBE /* URLParser.swift in Sources */,
37C0C0FF28665EAC007F6F78 /* VideosApp.swift in Sources */,
378FFBC92866018A009E3FBE /* URLParserTests.swift in Sources */,
371B88F82A1A310100D57683 /* String+Format.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3057,7 +3071,6 @@
374710052755291C00CE0F87 /* SearchTextField.swift in Sources */,
37494EA529200B14000DF176 /* DocumentsView.swift in Sources */,
374DE88028BB896C0062BBF2 /* PlayerDragGesture.swift in Sources */,
37ECED56289FE166002BC2C9 /* SafeArea.swift in Sources */,
37CEE4BD2677B670005A1EFE /* SingleAssetStream.swift in Sources */,
37C2211D27ADA33300305B41 /* MPVViewController.swift in Sources */,
37A362BE29537AAA00BDF328 /* PlaybackSettings.swift in Sources */,
@@ -3094,7 +3107,6 @@
377FF88F291A99580028EB0B /* HistoryView.swift in Sources */,
3782B94F27553A6700990149 /* SearchSuggestions.swift in Sources */,
378E50FF26FE8EEE00F49626 /* AccountViewButton.swift in Sources */,
374DE88328BB8A280062BBF2 /* PlayerOrientation.swift in Sources */,
374924F029216C630017D862 /* VideoActions.swift in Sources */,
37169AA62729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
37C7A1DA267CACF50010EAD6 /* TrendingCountry.swift in Sources */,
@@ -3176,6 +3188,7 @@
37FD77002932C4DA00D91A5F /* URL+ByReplacingYatteeProtocol.swift in Sources */,
373CFADB269663F1003CB2C6 /* Thumbnail.swift in Sources */,
3714166F267A8ACC006CA35D /* TrendingView.swift in Sources */,
37DCD3112A18E8150059A470 /* OrientationModel.swift in Sources */,
3782B9522755667600990149 /* String+Format.swift in Sources */,
37F9619F27BD90BB00058149 /* PlayerBackendType.swift in Sources */,
373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
@@ -3246,6 +3259,7 @@
37A9965A26D6F8CA006E3224 /* HorizontalCells.swift in Sources */,
37EBD8CA27AF26C200F1C24B /* MPVBackend.swift in Sources */,
37635FE4291EA6CF00C11E79 /* AccentButton.swift in Sources */,
37DCD3152A18F7630059A470 /* SafeAreaModel.swift in Sources */,
37D526DE2720AC4400ED2F5E /* VideosAPI.swift in Sources */,
37484C2526FC83E000287258 /* InstanceForm.swift in Sources */,
37DD9DBD2785D60300539416 /* ScrollViewMatcher.swift in Sources */,
@@ -3297,6 +3311,8 @@
37BE0BCF26A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
377692562946476F0055EC18 /* ChannelPlaylistsCacheModel.swift in Sources */,
3769C02E2779F18600DDB3EA /* PlaceholderProgressView.swift in Sources */,
37B7CFE92A19603B001B0564 /* ToolbarBackground+Backport.swift in Sources */,
37DCD3172A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */,
379EF9E029AA585F009FE6C6 /* HideShortsButtons.swift in Sources */,
37F5E8B6291BE9D0006C15F5 /* URLBookmarkModel.swift in Sources */,
37579D5D27864F5F00FD0B98 /* Help.swift in Sources */,
@@ -3306,6 +3322,7 @@
375EC972289F2ABF00751258 /* MultiselectRow.swift in Sources */,
37001563271B1F250049C794 /* AccountsModel.swift in Sources */,
3795593627B08538007FF8F4 /* StreamControl.swift in Sources */,
37B7CFEB2A1960EC001B0564 /* ToolbarColorScheme+Backport.swift in Sources */,
37A2B346294723850050933E /* CacheModel.swift in Sources */,
37CC3F50270D010D00608308 /* VideoBanner.swift in Sources */,
378E50FB26FE8B9F00F49626 /* Instance.swift in Sources */,
@@ -3335,11 +3352,11 @@
37D836BD294927E700005E5E /* ChannelsCacheModel.swift in Sources */,
3727B74B27872B880021C15E /* VisualEffectBlur-macOS.swift in Sources */,
374710062755291C00CE0F87 /* SearchTextField.swift in Sources */,
37B7CFEC2A197844001B0564 /* AppleAVPlayerView.swift in Sources */,
37F0F4EB286F397E00C06C2E /* SettingsModel.swift in Sources */,
378AE93F274EDFB5006A4EE1 /* Tint+Backport.swift in Sources */,
37C194C826F6A9C8005D3B96 /* RecentsModel.swift in Sources */,
37737786276F9858000521C1 /* Windows.swift in Sources */,
37BE0BDC26A2367F0092E2DB /* AppleAVPlayerView.swift in Sources */,
3786D05F294C737300D23E82 /* RequestErrorButton.swift in Sources */,
3743CA53270F284F00E4D32B /* View+Borders.swift in Sources */,
37599F39272B4D740087F250 /* FavoriteButton.swift in Sources */,
@@ -3404,6 +3421,7 @@
37B81B0026D2CA3700675966 /* VideoDetails.swift in Sources */,
3752069A285E8DD300CA655F /* Chapter.swift in Sources */,
373EBD69291F252D002ADB9C /* EditFavorites.swift in Sources */,
37B7CFEE2A19789F001B0564 /* MacOSPiPDelegate.swift in Sources */,
37484C1A26FC837400287258 /* PlayerSettings.swift in Sources */,
3776925329463C310055EC18 /* PlaylistsCacheModel.swift in Sources */,
37BD07C32698AD4F003EBB87 /* ContentView.swift in Sources */,
@@ -3616,6 +3634,7 @@
3774124D27387D2300423605 /* PlaylistsModel.swift in Sources */,
3774124B27387D2300423605 /* ThumbnailsModel.swift in Sources */,
3774125427387D2300423605 /* Store.swift in Sources */,
37DCD31A2A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */,
3774125027387D2300423605 /* Video.swift in Sources */,
37EF9A79275BEB8E0043B585 /* CommentView.swift in Sources */,
3774125327387D2300423605 /* Country.swift in Sources */,
@@ -3699,7 +3718,6 @@
376B2E0926F920D600B1D64D /* SignInRequiredView.swift in Sources */,
378FFBC628660172009E3FBE /* URLParser.swift in Sources */,
37141671267A8ACC006CA35D /* TrendingView.swift in Sources */,
3732C9FD28C012E600E7DCAF /* SafeArea.swift in Sources */,
37A2B348294723850050933E /* CacheModel.swift in Sources */,
37C3A24727235DA70087A57A /* ChannelPlaylist.swift in Sources */,
3788AC2926F6840700F6BAA9 /* FavoriteItemView.swift in Sources */,
@@ -3737,6 +3755,7 @@
3786D060294C737300D23E82 /* RequestErrorButton.swift in Sources */,
37A362C429537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift in Sources */,
37BDFF1929487B99000C6404 /* PlaylistVideosView.swift in Sources */,
37B7CFEF2A197A08001B0564 /* SafeAreaModel.swift in Sources */,
37C0698427260B2100F7F6CB /* ThumbnailsModel.swift in Sources */,
374924DC2921050B0017D862 /* LocationsSettings.swift in Sources */,
37D6025B28C17375009E8D98 /* PlaybackStatsView.swift in Sources */,
@@ -3815,6 +3834,7 @@
372D85E0283842EE00FF3C7D /* PlayerLayerView.swift in Sources */,
37CEE4C32677B697005A1EFE /* Stream.swift in Sources */,
37F64FE626FE70A60081B69E /* RedrawOnModifier.swift in Sources */,
37DCD3192A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */,
37B2631C2735EAAB00FE0D40 /* FavoriteResourceObserver.swift in Sources */,
37484C2B26FC83FF00287258 /* AccountForm.swift in Sources */,
37FB2860272225E800A57617 /* ContentItemView.swift in Sources */,
@@ -3962,7 +3982,7 @@
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Open in Yattee/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Open in Yattee";
@@ -3973,7 +3993,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = "stream.yattee.app.Open-in-Yattee";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -3993,7 +4013,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Open in Yattee/Info.plist";
@@ -4005,7 +4025,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = "stream.yattee.app.Open-in-Yattee";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -4024,11 +4044,11 @@
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = "net.arekf.Shared-Tests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
@@ -4044,11 +4064,11 @@
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = "net.arekf.Shared-Tests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
@@ -4204,7 +4224,7 @@
CODE_SIGN_ENTITLEMENTS = "iOS/Yattee (iOS).entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
@@ -4228,7 +4248,7 @@
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
OTHER_LDFLAGS = (
"-lstdc++",
"-Wl,-no_compact_unwind",
@@ -4257,7 +4277,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1";
@@ -4278,7 +4298,7 @@
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
OTHER_LDFLAGS = (
"-lstdc++",
"-Wl,-no_compact_unwind",
@@ -4309,7 +4329,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
DEAD_CODE_STRIPPING = YES;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
@@ -4328,7 +4348,7 @@
"$(PROJECT_DIR)/Vendor/mpv/macOS/lib",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
OTHER_LDFLAGS = "-Wl,-no_compact_unwind";
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
PRODUCT_NAME = Yattee;
@@ -4351,7 +4371,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
DEAD_CODE_STRIPPING = YES;
"DEVELOPMENT_TEAM[sdk=macosx*]" = 78Z5H3M6RJ;
ENABLE_APP_SANDBOX = YES;
@@ -4371,7 +4391,7 @@
"$(PROJECT_DIR)/Vendor/mpv/macOS/lib",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
OTHER_LDFLAGS = "-Wl,-no_compact_unwind";
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
PRODUCT_NAME = Yattee;
@@ -4389,7 +4409,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4397,7 +4417,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = "stream.yattee.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -4413,7 +4433,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4421,7 +4441,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = "stream.yattee.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -4439,7 +4459,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4448,7 +4468,7 @@
"@loader_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = "stream.yattee.Tests-macOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
@@ -4464,7 +4484,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
@@ -4473,7 +4493,7 @@
"@loader_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = "stream.yattee.Tests-macOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
@@ -4490,7 +4510,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
DEVELOPMENT_ASSET_PATHS = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -4506,7 +4526,7 @@
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
OTHER_LDFLAGS = (
"-Wl,-no_compact_unwind",
"-lstdc++",
@@ -4530,7 +4550,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
DEVELOPMENT_ASSET_PATHS = "";
"DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES;
@@ -4547,7 +4567,7 @@
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
OTHER_LDFLAGS = (
"-Wl,-no_compact_unwind",
"-lstdc++",
@@ -4571,14 +4591,14 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.YatteeUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
@@ -4595,14 +4615,14 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 145;
CURRENT_PROJECT_VERSION = 150;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.4.4;
MARKETING_VERSION = 1.4.5;
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.YatteeUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;

View File

@@ -14,8 +14,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Alamofire/Alamofire.git",
"state" : {
"revision" : "78424be314842833c04bc3bef5b72e85fff99204",
"version" : "5.6.4"
"revision" : "bc268c28fb170f494de9e9927c371b8342979ece",
"version" : "5.7.1"
}
},
{
@@ -24,7 +24,7 @@
"location" : "https://github.com/hyperoslo/Cache.git",
"state" : {
"branch" : "master",
"revision" : "eeaf771d8d2e8247fbd6da2e27c986d99803fb1f"
"revision" : "d048bf404a5c8362c6cf840c2096d5777975cd27"
}
},
{
@@ -96,7 +96,7 @@
"location" : "https://github.com/SDWebImage/SDWebImage",
"state" : {
"branch" : "master",
"revision" : "09f0fdd1890b7c9420109bc7b45dcee91b66b7de"
"revision" : "7f9fb5d43ecd4aa714c00746f54873f354403438"
}
},
{
@@ -158,8 +158,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/siteline/SwiftUI-Introspect.git",
"state" : {
"revision" : "c18951c747ab62af7c15e17a81bd37d4fd5a9979",
"version" : "0.2.3"
"revision" : "5b3f3996c7a2a84d5f4ba0e03cd7d584154778f2",
"version" : "0.3.1"
}
},
{

View File

@@ -31,7 +31,7 @@ struct Orientation {
logger.info("rotating to \(orientationString)")
if #available(iOS 16, *) {
guard let windowScene = SafeArea.scene else { return }
guard let windowScene = Self.scene else { return }
let rotateOrientationMask = rotateOrientation == .portrait ? UIInterfaceOrientationMask.portrait :
rotateOrientation == .landscapeLeft ? .landscapeLeft :
rotateOrientation == .landscapeRight ? .landscapeRight :
@@ -46,4 +46,11 @@ struct Orientation {
UINavigationController.attemptRotationToDeviceOrientation()
}
private static var scene: UIWindowScene? {
UIApplication.shared.connectedScenes
.filter { $0.activationState == .foregroundActive }
.compactMap { $0 as? UIWindowScene }
.first
}
}

View File

@@ -1,8 +1,18 @@
import Defaults
import Foundation
import Repeat
import SwiftUI
extension VideoPlayerView {
final class OrientationModel {
static var shared = OrientationModel()
var orientation = UIInterfaceOrientation.portrait
var lastOrientation: UIInterfaceOrientation?
var orientationDebouncer = Debouncer(.milliseconds(300))
internal var orientationObserver: Any?
private var player = PlayerModel.shared
func configureOrientationUpdatesBasedOnAccelerometer() {
let currentOrientation = OrientationTracker.shared.currentInterfaceOrientation
if currentOrientation.isLandscape,
@@ -15,8 +25,8 @@ extension VideoPlayerView {
player.presentingPlayer
{
DispatchQueue.main.async {
player.controls.presentingControls = false
player.enterFullScreen(showControls: false)
self.player.controls.presentingControls = false
self.player.enterFullScreen(showControls: false)
}
player.onPresentPlayer.append {
@@ -30,42 +40,42 @@ extension VideoPlayerView {
queue: .main
) { _ in
guard !Defaults[.honorSystemOrientationLock],
player.presentingPlayer,
!player.playingInPictureInPicture,
player.lockedOrientation.isNil
self.player.presentingPlayer,
!self.player.playingInPictureInPicture,
self.player.lockedOrientation.isNil
else {
return
}
let orientation = OrientationTracker.shared.currentInterfaceOrientation
guard lastOrientation != orientation else {
guard self.lastOrientation != orientation else {
return
}
lastOrientation = orientation
self.lastOrientation = orientation
DispatchQueue.main.async {
guard Defaults[.enterFullscreenInLandscape],
player.presentingPlayer
self.player.presentingPlayer
else {
return
}
orientationDebouncer.callback = {
self.orientationDebouncer.callback = {
DispatchQueue.main.async {
if orientation.isLandscape {
player.controls.presentingControls = false
player.enterFullScreen(showControls: false)
self.player.controls.presentingControls = false
self.player.enterFullScreen(showControls: false)
Orientation.lockOrientation(OrientationTracker.shared.currentInterfaceOrientationMask, andRotateTo: orientation)
} else {
player.exitFullScreen(showControls: false)
self.player.exitFullScreen(showControls: false)
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
}
}
}
orientationDebouncer.call()
self.orientationDebouncer.call()
}
}
}
@@ -74,4 +84,12 @@ extension VideoPlayerView {
guard let observer = orientationObserver else { return }
NotificationCenter.default.removeObserver(observer)
}
func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation: UIInterfaceOrientation? = nil) {
if let rotateOrientation {
self.orientation = rotateOrientation
lastOrientation = rotateOrientation
}
Orientation.lockOrientation(orientation, andRotateTo: rotateOrientation)
}
}

15
iOS/SafeAreaModel.swift Normal file
View File

@@ -0,0 +1,15 @@
import Foundation
import SwiftUI
final class SafeAreaModel: ObservableObject {
static var shared = SafeAreaModel()
@Published var safeArea = EdgeInsets()
var horizontalInsets: Double {
safeArea.leading + safeArea.trailing
}
var verticalInsets: Double {
safeArea.top + safeArea.bottom
}
}

View File

@@ -1,10 +0,0 @@
import Defaults
import SwiftUI
struct AppleAVPlayerView: NSViewRepresentable {
func makeNSView(context _: Context) -> some NSView {
PlayerLayerView(frame: .zero)
}
func updateNSView(_: NSViewType, context _: Context) {}
}

View File

@@ -0,0 +1,34 @@
import AVKit
import Foundation
final class MacOSPiPDelegate: NSObject, AVPlayerViewPictureInPictureDelegate {
var playerModel: PlayerModel { .shared }
func playerViewShouldAutomaticallyDismissAtPicture(inPictureStart _: AVPlayerView) -> Bool {
false
}
func playerViewWillStartPicture(inPicture _: AVPlayerView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
self?.playerModel.playingInPictureInPicture = true
self?.playerModel.hide()
}
}
func playerViewWillStopPicture(inPicture _: AVPlayerView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
self?.playerModel.playingInPictureInPicture = false
self?.playerModel.show()
}
}
func playerView(
_: AVPlayerView,
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: (Bool) -> Void
) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
self?.playerModel.show()
}
completionHandler(true)
}
}