From 45bafed1e9e11b8ea0a58f3c23a1f58e10428c34 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Wed, 19 Jan 2022 00:18:24 -0700 Subject: [PATCH 1/4] add aspect fill and update packages --- Shared/Extensions/CGSizeExtensions.swift | 15 ++++ .../xcshareddata/swiftpm/Package.resolved | 16 ++-- .../Overlays/VLCPlayerOverlayView.swift | 38 ++++++++++ .../VideoPlayer/PlayerOverlayDelegate.swift | 6 ++ .../VideoPlayer/VLCPlayerViewController.swift | 74 ++++++++++++++++++- 5 files changed, 139 insertions(+), 10 deletions(-) diff --git a/Shared/Extensions/CGSizeExtensions.swift b/Shared/Extensions/CGSizeExtensions.swift index 08026424..664eb5d0 100644 --- a/Shared/Extensions/CGSizeExtensions.swift +++ b/Shared/Extensions/CGSizeExtensions.swift @@ -13,4 +13,19 @@ extension CGSize { static func Circle(radius: CGFloat) -> CGSize { CGSize(width: radius, height: radius) } + + // From https://gist.github.com/jkosoy/c835fea2c03e76720c77 + static func aspectFill(aspectRatio: CGSize, minimumSize: CGSize) -> CGSize { + var minimumSize = minimumSize + let mW = minimumSize.width / aspectRatio.width + let mH = minimumSize.height / aspectRatio.height + + if mH > mW { + minimumSize.width = minimumSize.height / aspectRatio.height * aspectRatio.width + } else if mW > mH { + minimumSize.height = minimumSize.width / aspectRatio.width * aspectRatio.height + } + + return minimumSize + } } diff --git a/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8e52e799..74055c43 100644 --- a/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/CombineCommunity/CombineExt", "state": { "branch": null, - "revision": "8ca006df5e3cc6bb176b70238e2b0014bbc3a235", - "version": "1.0.0" + "revision": "0880829102152185190064fd17847a7c681d2127", + "version": "1.5.1" } }, { @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/sindresorhus/Defaults", "state": { "branch": null, - "revision": "8a6e4a96fd38504a05903d136c85634b65fd7c4d", - "version": "6.0.0" + "revision": "55f3302c3ab30a8760f10042d0ebc0a6907f865a", + "version": "6.1.0" } }, { @@ -96,8 +96,8 @@ "repositoryURL": "https://github.com/sushichop/Puppy", "state": { "branch": null, - "revision": "dc82e65c749cee431ffbb8c0913680b61ccd7e08", - "version": "0.2.0" + "revision": "95ce04b0e778b8d7c351876bc98bbf68328dfc9b", + "version": "0.3.1" } }, { @@ -105,8 +105,8 @@ "repositoryURL": "https://github.com/rundfunk47/stinsen", "state": { "branch": null, - "revision": "5e6c714f6f308877c8a988523915f9eb592d7d82", - "version": "2.0.3" + "revision": "36d97964075dc770046ddef9346a29bfa8982d6d", + "version": "2.0.7" } }, { diff --git a/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift b/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift index ce9979f0..98d08f27 100644 --- a/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift +++ b/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift @@ -87,6 +87,8 @@ struct VLCPlayerOverlayView: View { HStack(spacing: 20) { + // MARK: Previous Item + if viewModel.shouldShowPlayPreviousItem { Button { viewModel.playerOverlayDelegate?.didSelectPlayPreviousItem() @@ -97,6 +99,8 @@ struct VLCPlayerOverlayView: View { .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) } + // MARK: Next Item + if viewModel.shouldShowPlayNextItem { Button { viewModel.playerOverlayDelegate?.didSelectPlayNextItem() @@ -107,6 +111,8 @@ struct VLCPlayerOverlayView: View { .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) } + // MARK: Autoplay + if viewModel.shouldShowAutoPlay { Button { viewModel.autoplayEnabled.toggle() @@ -119,6 +125,8 @@ struct VLCPlayerOverlayView: View { } } + // MARK: Subtitle Toggle + if !viewModel.subtitleStreams.isEmpty { Button { viewModel.subtitlesEnabled.toggle() @@ -133,10 +141,32 @@ struct VLCPlayerOverlayView: View { .foregroundColor(viewModel.selectedSubtitleStreamIndex == -1 ? .gray : .white) } + // MARK: Screen Fill + + Button { + viewModel.playerOverlayDelegate?.didSelectScreenFill() + } label: { + if viewModel.playerOverlayDelegate?.getScreenFilled() ?? true { + if viewModel.playerOverlayDelegate?.isVideoAspectRatioGreater() ?? true { + Image(systemName: "rectangle.arrowtriangle.2.inward") + } else { + Image(systemName: "rectangle.portrait.arrowtriangle.2.inward") + } + } else { + if viewModel.playerOverlayDelegate?.isVideoAspectRatioGreater() ?? true { + Image(systemName: "rectangle.arrowtriangle.2.outward") + } else { + Image(systemName: "rectangle.portrait.arrowtriangle.2.outward") + } + } + } + // MARK: Settings Menu Menu { + // MARK: Audio Streams + Menu { ForEach(viewModel.audioStreams, id: \.self) { audioStream in Button { @@ -156,6 +186,8 @@ struct VLCPlayerOverlayView: View { } } + // MARK: Subtitle Streams + Menu { ForEach(viewModel.subtitleStreams, id: \.self) { subtitleStream in Button { @@ -175,6 +207,8 @@ struct VLCPlayerOverlayView: View { } } + // MARK: Playback Speed + Menu { ForEach(PlaybackSpeed.allCases, id: \.self) { speed in Button { @@ -194,6 +228,8 @@ struct VLCPlayerOverlayView: View { } } + // MARK: Chapters + if !viewModel.chapters.isEmpty { Button { viewModel.playerOverlayDelegate?.didSelectChapters() @@ -205,6 +241,8 @@ struct VLCPlayerOverlayView: View { } } + // MARK: Jump Button Lengths + if viewModel.shouldShowJumpButtonsInOverlayMenu { Menu { ForEach(VideoPlayerJumpLength.allCases, id: \.self) { forwardLength in diff --git a/Swiftfin/Views/VideoPlayer/PlayerOverlayDelegate.swift b/Swiftfin/Views/VideoPlayer/PlayerOverlayDelegate.swift index f0e49c37..4977b3a4 100644 --- a/Swiftfin/Views/VideoPlayer/PlayerOverlayDelegate.swift +++ b/Swiftfin/Views/VideoPlayer/PlayerOverlayDelegate.swift @@ -32,4 +32,10 @@ protocol PlayerOverlayDelegate { func didSelectChapters() func didSelectChapter(_ chapter: ChapterInfo) + + func didSelectScreenFill() + func getScreenFilled() -> Bool + // Returns whether the aspect ratio of the video + // is greater than the aspect ratio of the screen + func isVideoAspectRatioGreater() -> Bool } diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index a22a5212..135c32ab 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -28,6 +28,7 @@ class VLCPlayerViewController: UIViewController { private var lastProgressReportTicks: Int64 = 0 private var viewModelListeners = Set() private var overlayDismissTimer: Timer? + private var isScreenFilled: Bool = false private var currentPlayerTicks: Int64 { Int64(vlcMediaPlayer.time.intValue) * 100_000 @@ -42,7 +43,7 @@ class VLCPlayerViewController: UIViewController { } private lazy var videoContentView = makeVideoContentView() - private lazy var mainGestureView = makeTapGestureView() + private lazy var mainGestureView = makeMainGestureView() private var currentOverlayHostingController: UIHostingController? private var currentChapterOverlayHostingController: UIHostingController? private var currentJumpBackwardOverlayView: UIImageView? @@ -152,7 +153,7 @@ class VLCPlayerViewController: UIViewController { return view } - private func makeTapGestureView() -> UIView { + private func makeMainGestureView() -> UIView { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false @@ -164,7 +165,10 @@ class VLCPlayerViewController: UIViewController { let leftSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(didLeftSwipe)) leftSwipeGesture.direction = .left + let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(didPinch(_:))) + view.addGestureRecognizer(singleTapGesture) + view.addGestureRecognizer(pinchGesture) if viewModel.jumpGesturesEnabled { view.addGestureRecognizer(rightSwipeGesture) @@ -189,6 +193,23 @@ class VLCPlayerViewController: UIViewController { self.didSelectBackward() } + private var pinchScale: CGFloat = 1 + + @objc + private func didPinch(_ gestureRecognizer: UIPinchGestureRecognizer) { + if gestureRecognizer.state == .began || gestureRecognizer.state == .changed { + pinchScale = gestureRecognizer.scale + } else { + isScreenFilled.toggle() + + if pinchScale > 1 { + fillScreen() + } else { + shrinkScreen() + } + } + } + // MARK: setupOverlayHostingController private func setupOverlayHostingController(viewModel: VideoPlayerViewModel) { @@ -814,4 +835,53 @@ extension VLCPlayerViewController: PlayerOverlayDelegate { viewModel.sendProgressReport() } + + func didSelectScreenFill() { + + isScreenFilled.toggle() + + if isScreenFilled { + fillScreen() + } else { + shrinkScreen() + } + } + + private func fillScreen() { + let screenSize = UIScreen.main.bounds.size + let videoSize = vlcMediaPlayer.videoSize + let fillSize = CGSize.aspectFill(aspectRatio: videoSize, minimumSize: screenSize) + + let scale: CGFloat + + if fillSize.height > screenSize.height { + scale = fillSize.height / screenSize.height + } else { + scale = fillSize.width / screenSize.width + } + + UIView.animate(withDuration: 0.2) { + self.videoContentView.transform = CGAffineTransform(scaleX: scale, y: scale) + } + } + + private func shrinkScreen() { + UIView.animate(withDuration: 0.2) { + self.videoContentView.transform = .identity + } + } + + func getScreenFilled() -> Bool { + isScreenFilled + } + + func isVideoAspectRatioGreater() -> Bool { + let screenSize = UIScreen.main.bounds.size + let videoSize = vlcMediaPlayer.videoSize + + let screenAspectRatio = screenSize.width / screenSize.height + let videoAspectRatio = videoSize.width / videoSize.height + + return videoAspectRatio > screenAspectRatio + } } From 6fa2b914d60097b81765a045caa973ad67fb718f Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Wed, 19 Jan 2022 00:23:48 -0700 Subject: [PATCH 2/4] update README --- README.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3d51fc32..42c86e76 100644 --- a/README.md +++ b/README.md @@ -38,13 +38,7 @@ Thank you for your interest in Swiftfin, please check out the [Contribution Guid ### Intended Behaviors Due to Technical Limitations -The following behaviors are intended due to technical limitations: +The following behaviors are intended due to technical limitations with VLCKit: -- Pausing playback when app is backgrounded - - Due to VLCKit pausing video output at the same moment - -- Audio delay after un-pausing - - Due to VLCKit, may be fixed in VLCKit v4 - -- No aspect fill - - VLCKit doesn't have the ability to aspect fill the view that the video output occupies +- Pausing playback when app is backgrounded as VLCKit pauses video output at the same time +- Audio delay when starting playback and un-pausing, may be fixed in VLCKit v4 From df73265df33ead9508330e0cc3a90ad729d33b97 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Wed, 19 Jan 2022 00:38:58 -0700 Subject: [PATCH 3/4] fill on rotate if necessary --- .../Views/VideoPlayer/VLCPlayerViewController.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index 135c32ab..f4fe2d83 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -29,6 +29,7 @@ class VLCPlayerViewController: UIViewController { private var viewModelListeners = Set() private var overlayDismissTimer: Timer? private var isScreenFilled: Bool = false + private var pinchScale: CGFloat = 1 private var currentPlayerTicks: Int64 { Int64(vlcMediaPlayer.time.intValue) * 100_000 @@ -143,6 +144,13 @@ class VLCPlayerViewController: UIViewController { startPlayback() } + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + if isScreenFilled { + fillScreen(screenSize: size) + } + super.viewWillTransition(to: size, with: coordinator) + } + // MARK: subviews private func makeVideoContentView() -> UIView { @@ -193,8 +201,6 @@ class VLCPlayerViewController: UIViewController { self.didSelectBackward() } - private var pinchScale: CGFloat = 1 - @objc private func didPinch(_ gestureRecognizer: UIPinchGestureRecognizer) { if gestureRecognizer.state == .began || gestureRecognizer.state == .changed { @@ -847,8 +853,7 @@ extension VLCPlayerViewController: PlayerOverlayDelegate { } } - private func fillScreen() { - let screenSize = UIScreen.main.bounds.size + private func fillScreen(screenSize: CGSize = UIScreen.main.bounds.size) { let videoSize = vlcMediaPlayer.videoSize let fillSize = CGSize.aspectFill(aspectRatio: videoSize, minimumSize: screenSize) From 433d4a97be4f696f5468ae2da4ef586ffa2b2f24 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Wed, 19 Jan 2022 14:33:18 -0700 Subject: [PATCH 4/4] finalize work and fix overlay --- .../Overlays/VLCPlayerOverlayView.swift | 22 +++++++++---------- .../VideoPlayer/VLCPlayerViewController.swift | 12 +++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift b/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift index 98d08f27..08133616 100644 --- a/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift +++ b/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerOverlayView.swift @@ -52,14 +52,14 @@ struct VLCPlayerOverlayView: View { // MARK: Top Bar - ZStack { + ZStack(alignment: .center) { if viewModel.overlayType == .compact { - LinearGradient(gradient: Gradient(colors: [.black.opacity(0.7), .clear]), + LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .clear]), startPoint: .top, endPoint: .bottom) .ignoresSafeArea() - .frame(height: 80) + .frame(height: 70) } VStack(alignment: .EpisodeSeriesAlignmentGuide) { @@ -78,6 +78,7 @@ struct VLCPlayerOverlayView: View { Text(viewModel.title) .font(.title3) .fontWeight(.bold) + .lineLimit(1) .alignmentGuide(.EpisodeSeriesAlignmentGuide) { context in context[.leading] } @@ -297,12 +298,11 @@ struct VLCPlayerOverlayView: View { .alignmentGuide(.EpisodeSeriesAlignmentGuide) { context in context[.leading] } - .offset(y: -20) + .offset(y: -18) } } + .padding(.horizontal, UIDevice.current.userInterfaceIdiom == .pad ? 30 : 0) } - .padding(.horizontal, UIDevice.current.userInterfaceIdiom == .pad ? 50 : 0) - .padding(.top, UIDevice.current.userInterfaceIdiom == .pad ? 10 : 0) // MARK: Center @@ -336,10 +336,10 @@ struct VLCPlayerOverlayView: View { // MARK: Bottom Bar - ZStack { + ZStack(alignment: .center) { if viewModel.overlayType == .compact { - LinearGradient(gradient: Gradient(colors: [.clear, .black.opacity(0.7)]), + LinearGradient(gradient: Gradient(colors: [.clear, .black.opacity(0.8)]), startPoint: .top, endPoint: .bottom) .ignoresSafeArea() @@ -401,12 +401,10 @@ struct VLCPlayerOverlayView: View { .accessibilityLabel(L10n.remainingTime) .accessibilityValue(viewModel.rightLabelText) } - .padding(.horizontal) - .frame(maxWidth: UIDevice.current.userInterfaceIdiom == .pad ? 800 : nil) + .padding(.horizontal, UIDevice.current.userInterfaceIdiom == .pad ? 30 : 0) + .padding(.bottom, UIDevice.current.userInterfaceIdiom == .pad ? 10 : 0) } - .frame(maxHeight: 50) } - .ignoresSafeArea(edges: .top) .tint(Color.white) .foregroundColor(Color.white) } diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index f4fe2d83..e46f8f78 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -151,7 +151,7 @@ class VLCPlayerViewController: UIViewController { super.viewWillTransition(to: size, with: coordinator) } - // MARK: subviews + // MARK: VideoContentView private func makeVideoContentView() -> UIView { let view = UIView() @@ -161,6 +161,8 @@ class VLCPlayerViewController: UIViewController { return view } + // MARK: MainGestureView + private func makeMainGestureView() -> UIView { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false @@ -206,11 +208,11 @@ class VLCPlayerViewController: UIViewController { if gestureRecognizer.state == .began || gestureRecognizer.state == .changed { pinchScale = gestureRecognizer.scale } else { - isScreenFilled.toggle() - - if pinchScale > 1 { + if pinchScale > 1 && !isScreenFilled { + isScreenFilled.toggle() fillScreen() - } else { + } else if pinchScale < 1 && isScreenFilled { + isScreenFilled.toggle() shrinkScreen() } }