diff --git a/Model/Player/PlayerControlsModel.swift b/Model/Player/PlayerControlsModel.swift index 72ba9c91..7ac8e616 100644 --- a/Model/Player/PlayerControlsModel.swift +++ b/Model/Player/PlayerControlsModel.swift @@ -17,7 +17,7 @@ final class PlayerControlsModel: ObservableObject { private(set) var reporter = PassthroughSubject() #endif - private var player: PlayerModel! { .shared } + var player: PlayerModel! { .shared } private var controlsOverlayModel = ControlOverlaysModel.shared init( diff --git a/Model/Player/PlayerModel.swift b/Model/Player/PlayerModel.swift index 6d4e5012..60376156 100644 --- a/Model/Player/PlayerModel.swift +++ b/Model/Player/PlayerModel.swift @@ -108,6 +108,7 @@ final class PlayerModel: ObservableObject { @Published var autoplayItem: PlayerQueueItem? @Published var autoplayItemSource: Video? @Published var advancing = false + @Published var closing = false @Published var returnYouTubeDislike = ReturnYouTubeDislikeAPI() @@ -321,6 +322,7 @@ final class PlayerModel: ObservableObject { func play(_ video: Video, at time: CMTime? = nil, showingPlayer: Bool = true) { pause() + videoBeingOpened = video WatchNextViewModel.shared.presentingOutro = false @@ -571,25 +573,29 @@ final class PlayerModel: ObservableObject { } func closeCurrentItem(finished: Bool = false) { - controls.presentingControls = false pause() - closePiP() - prepareCurrentItemForHistory(finished: finished) - withAnimation { - currentItem = nil + closing = true + controls.presentingControls = false + + self.hide() + + Delay.by(0.8) { [weak self] in + guard let self else { return } + self.closePiP() + + self.prepareCurrentItemForHistory(finished: finished) + withAnimation { + self.currentItem = nil + } + self.updateNowPlayingInfo() + + self.backend.closeItem() + self.aspectRatio = VideoPlayerView.defaultAspectRatio + self.resetAutoplay() + self.closing = false + self.playingFullScreen = false } - updateNowPlayingInfo() - - backend.closeItem() - aspectRatio = VideoPlayerView.defaultAspectRatio - resetAutoplay() - - exitFullScreen() - - #if !os(macOS) - if closePlayerOnItemClose { Delay.by(0.2) { self.hide() } } - #endif } func startPiP() { diff --git a/Model/Player/PlayerQueue.swift b/Model/Player/PlayerQueue.swift index a3683d26..aa2e2b88 100644 --- a/Model/Player/PlayerQueue.swift +++ b/Model/Player/PlayerQueue.swift @@ -9,6 +9,10 @@ extension PlayerModel { currentItem?.video } + var videoForDisplay: Video? { + videoBeingOpened ?? (closing ? nil : currentVideo) + } + func play(_ videos: [Video], shuffling: Bool = false) { WatchNextViewModel.shared.presentingOutro = false playbackMode = shuffling ? .shuffle : .queue @@ -33,6 +37,8 @@ extension PlayerModel { closePiP() } + videoBeingOpened = video + prepareCurrentItemForHistory() enqueueVideo(video, play: true, atTime: time, prepending: true) { _, item in @@ -70,9 +76,8 @@ extension PlayerModel { return } - self.videoBeingOpened = nil - if video.isLocal { + self.videoBeingOpened = nil self.availableStreams = video.streams return } @@ -81,8 +86,11 @@ extension PlayerModel { let streamsInstance = video.streams.compactMap(\.instance).first if video.streams.isEmpty || streamsInstance != playerInstance { - self.loadAvailableStreams(video) + self.loadAvailableStreams(video) { [weak self] _ in + self?.videoBeingOpened = nil + } } else { + self.videoBeingOpened = nil self.availableStreams = self.streamsWithInstance(instance: playerInstance, streams: video.streams) } } diff --git a/Model/Player/PlayerStreams.swift b/Model/Player/PlayerStreams.swift index bd556860..5e50e204 100644 --- a/Model/Player/PlayerStreams.swift +++ b/Model/Player/PlayerStreams.swift @@ -15,13 +15,13 @@ extension PlayerModel { availableStreams.sorted(by: streamsSorter) } - func loadAvailableStreams(_ video: Video) { + func loadAvailableStreams(_ video: Video, onCompletion: @escaping (ResponseInfo) -> Void = { _ in }) { availableStreams = [] guard let playerInstance else { return } logger.info("loading streams from \(playerInstance.description)") - fetchStreams(playerAPI(video).video(video.videoID), instance: playerInstance, video: video) + fetchStreams(playerAPI(video).video(video.videoID), instance: playerInstance, video: video, onCompletion: onCompletion) } private func fetchStreams( diff --git a/Shared/Constants.swift b/Shared/Constants.swift index 026a2448..62c8b3ef 100644 --- a/Shared/Constants.swift +++ b/Shared/Constants.swift @@ -35,4 +35,12 @@ struct Constants { 6 #endif } + + static var descriptionVisibility: Bool { + #if os(iOS) + false + #else + true + #endif + } } diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index 8b200342..01defd22 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -106,7 +106,6 @@ extension Defaults.Keys { static let chargingCellularProfile = Key("chargingCellularProfile", default: chargingCellularProfileDefault) static let chargingNonCellularProfile = Key("chargingNonCellularProfile", default: chargingNonCellularProfileDefault) static let forceAVPlayerForLiveStreams = Key("forceAVPlayerForLiveStreams", default: true) - static let videoDetailsPage = Key("videoDetailsPage", default: .queue) static let playerSidebar = Key("playerSidebar", default: .defaultValue) static let showInspector = Key("showInspector", default: .onlyLocal) static let detailsToolbarPosition = Key("detailsToolbarPosition", default: .center) diff --git a/Shared/Modifiers/PlayerOverlayModifier.swift b/Shared/Modifiers/PlayerOverlayModifier.swift index f8ef95ba..e0ac5ea5 100644 --- a/Shared/Modifiers/PlayerOverlayModifier.swift +++ b/Shared/Modifiers/PlayerOverlayModifier.swift @@ -19,7 +19,7 @@ struct PlayerOverlayModifier: ViewModifier { @ViewBuilder var overlay: some View { Group { - if player.currentItem != nil { + if player.videoForDisplay != nil { ControlsBar(fullScreen: .constant(false), expansionState: $expansionState, playerBar: true) .offset(x: expansionState == .mini && !controlsWhenMinimized ? 10 : 0, y: 0) .transition(.opacity) diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index e5ad9fda..bd94a6df 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -138,7 +138,7 @@ struct ContentView: View { @ViewBuilder var videoPlayer: some View { if player.presentingPlayer { playerView - .transition(.asymmetric(insertion: .identity, removal: .move(edge: .bottom))) + .transition(.asymmetric(insertion: .identity, removal: .opacity)) .zIndex(3) } else if player.activeBackend == .appleAVPlayer { #if os(iOS) diff --git a/Shared/Player/Controls/PlayerControls.swift b/Shared/Player/Controls/PlayerControls.swift index 4fc6aabd..065bdfac 100644 --- a/Shared/Player/Controls/PlayerControls.swift +++ b/Shared/Player/Controls/PlayerControls.swift @@ -106,7 +106,7 @@ struct PlayerControls: View { if playerControlsLayout.displaysTitleLine { VStack(alignment: .leading) { - Text(player.currentVideo?.displayTitle ?? "Not Playing") + Text(player.videoForDisplay?.displayTitle ?? "Not Playing") .shadow(radius: 10) .font(.system(size: playerControlsLayout.titleLineFontSize).bold()) .lineLimit(1) @@ -216,19 +216,23 @@ struct PlayerControls: View { @ViewBuilder var controlsBackground: some View { if player.musicMode, - let item = self.player.currentItem, - let video = item.video, + let url = controlsBackgroundURL + { + ThumbnailView(url: url) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .transition(.opacity) + .animation(.default) + } + } + + var controlsBackgroundURL: URL? { + if let video = player.videoForDisplay, let url = thumbnails.best(video) { - WebImage(url: url, options: [.lowPriority]) - .resizable() - .placeholder { - Rectangle().fill(Color("PlaceholderColor")) - } - .retryOnAppear(true) - .indicator(.activity) - .frame(maxWidth: .infinity, maxHeight: .infinity) + return url } + + return nil } var timeline: some View { @@ -296,7 +300,7 @@ struct PlayerControls: View { "Fullscreen", systemImage: player.playingFullScreen ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right" ) { - player.toggleFullscreen(player.playingFullScreen) + player.toggleFullscreen(player.playingFullScreen, showControls: false) } #if !os(tvOS) .keyboardShortcut(player.playingFullScreen ? .cancelAction : .defaultAction) diff --git a/Shared/Player/Controls/VideoDetailsOverlay.swift b/Shared/Player/Controls/VideoDetailsOverlay.swift index 0243b3c8..33e12abf 100644 --- a/Shared/Player/Controls/VideoDetailsOverlay.swift +++ b/Shared/Player/Controls/VideoDetailsOverlay.swift @@ -4,10 +4,8 @@ import SwiftUI struct VideoDetailsOverlay: View { @ObservedObject private var controls = PlayerControlsModel.shared - @State private var detailsPage = VideoDetails.DetailsPage.info - var body: some View { - VideoDetails(video: PlayerModel.shared.currentVideo, page: $detailsPage, sidebarQueue: .constant(false), fullScreen: fullScreenBinding) + VideoDetails(video: PlayerModel.shared.currentVideo, fullScreen: fullScreenBinding) .clipShape(RoundedRectangle(cornerRadius: 4)) } diff --git a/Shared/Player/Video Details/VideoActions.swift b/Shared/Player/Video Details/VideoActions.swift index 35bf4402..45681147 100644 --- a/Shared/Player/Video Details/VideoActions.swift +++ b/Shared/Player/Video Details/VideoActions.swift @@ -52,24 +52,19 @@ struct VideoActions: View { } } - if player.currentItem == nil { - Spacer() - } - actionButton("Hide", systemImage: "chevron.down") { player.hide(animate: true) } - if player.currentItem != nil { - Spacer() - actionButton("Close", systemImage: "xmark") { + Spacer() + actionButton("Close", systemImage: "xmark") { // TODO: setting // player.pause() // WatchNextViewModel.shared.prepareForEmptyPlayerPlaceholder(player.currentItem) // WatchNextViewModel.shared.open() - player.closeCurrentItem() - } + player.closeCurrentItem() } + .disabled(player.currentItem == nil) } .padding(.horizontal) .multilineTextAlignment(.center) diff --git a/Shared/Player/Video Details/VideoDetails.swift b/Shared/Player/Video Details/VideoDetails.swift index 9b8f1c5d..6ee5db45 100644 --- a/Shared/Player/Video Details/VideoDetails.swift +++ b/Shared/Player/Video Details/VideoDetails.swift @@ -10,11 +10,11 @@ struct VideoDetails: View { var video: Video? - @Binding var page: DetailsPage - @Binding var sidebarQueue: Bool @Binding var fullScreen: Bool var bottomPadding = false + @State private var detailsSize = CGSize.zero + @State private var descriptionVisibility = Constants.descriptionVisibility @State private var subscribed = false @State private var subscriptionToggleButtonDisabled = false @@ -49,50 +49,10 @@ struct VideoDetails: View { VideoActions(video: video) .animation(nil, value: player.currentItem) - ZStack(alignment: .bottom) { - currentPage - .frame(maxWidth: detailsSize.width) - .animation(nil, value: player.currentItem) - - HStack { - if detailsToolbarPosition.needsLeftSpacer { Spacer() } - - VideoDetailsToolbar(video: video, page: $page, sidebarQueue: sidebarQueue) - - if detailsToolbarPosition.needsRightSpacer { Spacer() } - } - .padding(.leading, detailsToolbarPosition == .left ? 10 : 0) - .padding(.trailing, detailsToolbarPosition == .right ? 10 : 0) - - #if os(iOS) - .offset(y: bottomPadding ? -SafeArea.insets.bottom : 0) - #endif - } - .onChange(of: player.currentItem) { _ in - page = .info - } - } - .onAppear { - if video.isNil || - !VideoDetailsTool.find(for: page)!.isAvailable(for: video!, sidebarQueue: sidebarQueue) - { - guard let video, video.isLocal else { return } - page = .info - } - - guard video != nil, accounts.app.supportsSubscriptions else { - subscribed = false - return - } - } - .onChange(of: sidebarQueue) { queue in - if queue { - if page == .related || page == .queue { - page = video.isNil || video!.isLocal ? .inspector : .info - } - } else if video.isNil { - page = .inspector - } + detailsPage + #if os(iOS) + .frame(maxWidth: maxWidth) + #endif } .overlay(GeometryReader { proxy in Color.clear @@ -100,67 +60,53 @@ struct VideoDetails: View { detailsSize = proxy.size } .onChange(of: proxy.size) { newSize in + guard !player.playingFullScreen else { return } detailsSize = newSize } }) .background(colorScheme == .dark ? Color.black : .white) } + #if os(iOS) + private var maxWidth: Double { + let width = min(detailsSize.width, player.playerSize.width) + if width.isNormal, width > 0 { + return width + } + + return 0 + } + #endif + private var contentItem: ContentItem { ContentItem(video: player.currentVideo) } - var currentPage: some View { - VStack { - switch page { - case .info: - detailsPage - - case .inspector: - InspectorView(video: video) - - case .chapters: - ChaptersView() - - case .comments: - CommentsView(embedInScrollView: true) - .onAppear { - Delay.by(0.3) { comments.loadIfNeeded() } - } - - case .related: - RelatedView() - - case .queue: - PlayerQueueView(sidebarQueue: sidebarQueue, fullScreen: $fullScreen) - } - } - .contentShape(Rectangle()) - .frame(maxWidth: .infinity, maxHeight: .infinity) - } - - @State private var detailsSize = CGSize.zero - var detailsPage: some View { ScrollView(.vertical, showsIndicators: false) { - if let video, player.videoBeingOpened == nil { + if let video { VStack(alignment: .leading, spacing: 10) { videoProperties + #if os(iOS) + .opacity(descriptionVisibility ? 1 : 0) + #endif if !player.videoBeingOpened.isNil && (video.description.isNil || video.description!.isEmpty) { - VStack(alignment: .leading, spacing: 0) { - ForEach(1 ... Int.random(in: 2 ... 5), id: \.self) { _ in - Text(String(repeating: Video.fixture.description ?? "", count: Int.random(in: 1 ... 4))) - } + VStack { + ProgressView() + .progressViewStyle(.circular) } - .redacted(reason: .placeholder) + .frame(maxWidth: .infinity) + .opacity(descriptionVisibility ? 1 : 0) } else if video.description != nil, !video.description!.isEmpty { VideoDescription(video: video, detailsSize: detailsSize) #if os(iOS) + .opacity(descriptionVisibility ? 1 : 0) .padding(.bottom, player.playingFullScreen ? 10 : SafeArea.insets.bottom) #endif } else if !video.isLocal { Text("No description") + .font(.caption) .foregroundColor(.secondary) } } @@ -168,6 +114,17 @@ struct VideoDetails: View { .padding(.bottom, 60) } } + #if os(iOS) + .onAppear { + if fullScreen { + descriptionVisibility = true + return + } + Delay.by(0.3) { withAnimation(.easeOut(duration: 0.2)) { self.descriptionVisibility = true } } + } + #endif + .transition(.opacity) + .animation(nil, value: player.currentItem) .padding(.horizontal) } @@ -180,7 +137,7 @@ struct VideoDetails: View { HStack(spacing: 4) { Image(systemName: "eye") - if let views = video?.viewsCount, player.videoBeingOpened.isNil { + if let views = video?.viewsCount { Text(views) } else { if player.videoBeingOpened == nil { @@ -192,7 +149,7 @@ struct VideoDetails: View { Image(systemName: "hand.thumbsup") - if let likes = video?.likesCount, player.videoBeingOpened.isNil { + if let likes = video?.likesCount { Text(likes) } else { if player.videoBeingOpened == nil { @@ -205,7 +162,7 @@ struct VideoDetails: View { if enableReturnYouTubeDislike { Image(systemName: "hand.thumbsdown") - if let dislikes = video?.dislikesCount, player.videoBeingOpened.isNil { + if let dislikes = video?.dislikesCount { Text(dislikes) } else { if player.videoBeingOpened == nil { @@ -238,7 +195,6 @@ struct VideoDetails: View { struct VideoDetails_Previews: PreviewProvider { static var previews: some View { - VideoDetails(video: .fixture, page: .constant(.info), sidebarQueue: .constant(true), fullScreen: .constant(false)) - .injectFixtureEnvironmentObjects() + VideoDetails(video: .fixture, fullScreen: .constant(false)) } } diff --git a/Shared/Player/VideoPlayerView.swift b/Shared/Player/VideoPlayerView.swift index 629bf653..5492aa5d 100644 --- a/Shared/Player/VideoPlayerView.swift +++ b/Shared/Player/VideoPlayerView.swift @@ -66,7 +66,6 @@ struct VideoPlayerView: View { @Default(.seekGestureSpeed) var seekGestureSpeed @Default(.seekGestureSensitivity) var seekGestureSensitivity @Default(.playerSidebar) var playerSidebar - @Default(.videoDetailsPage) var detailsPage @ObservedObject internal var controlsOverlayModel = ControlOverlaysModel.shared @@ -107,6 +106,9 @@ 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 } @@ -114,7 +116,6 @@ struct VideoPlayerView: View { player.backend.setNeedsDrawing(!value) } #if os(iOS) - .padding(.bottom, playerEdgesIgnoringSafeArea == [.bottom] ? 0 : geometry.safeAreaInsets.bottom) .frame(width: playerWidth.isNil ? nil : Double(playerWidth!), height: playerHeight.isNil ? nil : Double(playerHeight!)) .ignoresSafeArea(.all, edges: playerEdgesIgnoringSafeArea) .onChange(of: player.presentingPlayer) { newValue in @@ -321,20 +322,24 @@ struct VideoPlayerView: View { .background(Color.black) if !fullScreenPlayer { - VideoDetails(video: player.currentVideo, page: $detailsPage, sidebarQueue: $sidebarQueue, fullScreen: $fullScreenDetails, bottomPadding: detailsNeedBottomPadding) + VideoDetails( + video: player.videoForDisplay, + fullScreen: $fullScreenDetails, + bottomPadding: detailsNeedBottomPadding + ) #if os(iOS) - .ignoresSafeArea(.all, edges: .bottom) + .ignoresSafeArea(.all, edges: .bottom) #endif - .modifier(VideoDetailsPaddingModifier( - playerSize: player.playerSize, - fullScreen: fullScreenDetails - )) - .onDisappear { - if player.presentingPlayer { - player.setNeedsDrawing(true) - } + .modifier(VideoDetailsPaddingModifier( + playerSize: player.playerSize, + fullScreen: fullScreenDetails + )) + .onDisappear { + if player.presentingPlayer { + player.setNeedsDrawing(true) } - .transition(.opacity) + } + .transition(.opacity) } } #endif @@ -389,7 +394,7 @@ struct VideoPlayerView: View { #elseif os(macOS) if Defaults[.playerSidebar] != .never { PlayerQueueView(sidebarQueue: true, fullScreen: $fullScreenDetails) - .frame(minWidth: 300) + .frame(width: 350) .background(colorScheme == .dark ? Color.black : Color.white) } #endif @@ -471,8 +476,5 @@ struct VideoPlayerView: View { struct VideoPlayerView_Previews: PreviewProvider { static var previews: some View { VideoPlayerView() - .onAppear { - WatchNextViewModel.shared.prepareForEmptyPlayerPlaceholder(.init(.fixture)) - } } } diff --git a/Shared/Player/WatchNextView.swift b/Shared/Player/WatchNextView.swift index e9ce2f6c..f7a4a8bb 100644 --- a/Shared/Player/WatchNextView.swift +++ b/Shared/Player/WatchNextView.swift @@ -60,7 +60,9 @@ struct WatchNextView: View { } .padding(.horizontal) } + #if os(iOS) .navigationBarTitleDisplayMode(.inline) + #endif .navigationTitle("Watch Next") #if !os(macOS) .toolbar { @@ -78,7 +80,7 @@ struct WatchNextView: View { var closeButton: some View { Button { player.closeCurrentItem() - player.hide(animate: true) + player.hide() Delay.by(0.8) { model.presentingOutro = false } diff --git a/Shared/Views/ControlsBar.swift b/Shared/Views/ControlsBar.swift index 64fe6516..a7a66318 100644 --- a/Shared/Views/ControlsBar.swift +++ b/Shared/Views/ControlsBar.swift @@ -60,7 +60,7 @@ struct ControlsBar: View { .clipShape(RoundedRectangle(cornerRadius: expansionState == .full || !playerBar ? 0 : 6)) .overlay( RoundedRectangle(cornerRadius: expansionState == .full || !playerBar ? 0 : 6) - .stroke(Color("ControlsBorderColor"), lineWidth: playerBar ? 0 : 0.5) + .stroke(Color("ControlsBorderColor"), lineWidth: 0.5) ) #if os(iOS) .background( @@ -156,7 +156,7 @@ struct ControlsBar: View { HStack(spacing: 8) { if !playerBar { Button { - if let video = model.currentVideo, !video.isLocal { + if let video = model.videoForDisplay, !video.isLocal { navigation.openChannel( video.channel, navigationStyle: navigationStyle @@ -164,8 +164,8 @@ struct ControlsBar: View { } } label: { ChannelAvatarView( - channel: model.currentVideo?.channel, - video: model.currentVideo + channel: model.videoForDisplay?.channel, + video: model.videoForDisplay ) .frame(width: barHeight - 10, height: barHeight - 10) } @@ -173,8 +173,8 @@ struct ControlsBar: View { .zIndex(3) } else { ChannelAvatarView( - channel: model.currentVideo?.channel, - video: model.currentVideo + channel: model.videoForDisplay?.channel, + video: model.videoForDisplay ) #if !os(tvOS) .highPriorityGesture(playerButtonDoubleTapGesture != .nothing ? doubleTapGesture : nil) @@ -187,15 +187,15 @@ struct ControlsBar: View { if expansionState == .full { VStack(alignment: .leading, spacing: 0) { let notPlaying = "Not Playing".localized() - Text(model.currentVideo?.displayTitle ?? notPlaying) + Text(model.videoForDisplay?.displayTitle ?? notPlaying) .font(.system(size: 14)) .fontWeight(.semibold) - .foregroundColor(model.currentVideo.isNil ? .secondary : .accentColor) + .foregroundColor(model.videoForDisplay.isNil ? .secondary : .accentColor) .fixedSize(horizontal: false, vertical: true) .lineLimit(titleLineLimit) .multilineTextAlignment(.leading) - if let video = model.currentVideo, !video.localStreamIsFile { + if let video = model.videoForDisplay, !video.localStreamIsFile { HStack(spacing: 2) { Text(video.displayAuthor) .font(.system(size: 12)) @@ -204,7 +204,7 @@ struct ControlsBar: View { HStack(spacing: 2) { Image(systemName: "person.2.fill") - if let channel = model.currentVideo?.channel { + if let channel = model.videoForDisplay?.channel { if let subscriptions = channel.subscriptionsString { Text(subscriptions) } else { @@ -248,7 +248,7 @@ struct ControlsBar: View { case .togglePlayer: model.togglePlayer() case .openChannel: - guard let channel = model.currentVideo?.channel else { return } + guard let channel = model.videoForDisplay?.channel else { return } navigation.openChannel(channel, navigationStyle: navigationStyle) case .togglePlayerVisibility: withAnimation(.spring(response: 0.25)) { @@ -260,7 +260,7 @@ struct ControlsBar: View { } #endif @ViewBuilder var contextMenu: some View { - if let video = model.currentVideo { + if let video = model.videoForDisplay { Group { Section { if accounts.app.supportsUserPlaylists && accounts.signedIn, !video.isLocal { @@ -271,7 +271,7 @@ struct ControlsBar: View { Label("Add to Playlist...", systemImage: "text.badge.plus") } - if let playlist = playlists.lastUsed, let video = model.currentVideo { + if let playlist = playlists.lastUsed, let video = model.videoForDisplay { Button { playlists.addVideo(playlistID: playlist.id, videoID: video.videoID) } label: { @@ -282,7 +282,7 @@ struct ControlsBar: View { } #if !os(tvOS) - ShareButton(contentItem: .init(video: model.currentVideo)) + ShareButton(contentItem: .init(video: model.videoForDisplay)) #endif Section {