mirror of
https://github.com/yattee/yattee.git
synced 2025-01-08 22:07:10 +00:00
Add related videos
This commit is contained in:
parent
f49453e871
commit
f8e6560698
@ -314,8 +314,8 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
likes: json["likeCount"].int,
|
likes: json["likeCount"].int,
|
||||||
dislikes: json["dislikeCount"].int,
|
dislikes: json["dislikeCount"].int,
|
||||||
keywords: json["keywords"].arrayValue.map { $0.stringValue },
|
keywords: json["keywords"].arrayValue.map { $0.stringValue },
|
||||||
streams: extractFormatStreams(from: json["formatStreams"].arrayValue) +
|
streams: extractStreams(from: json),
|
||||||
extractAdaptiveFormats(from: json["adaptiveFormats"].arrayValue)
|
related: extractRelated(from: json)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,6 +349,11 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func extractStreams(from json: JSON) -> [Stream] {
|
||||||
|
extractFormatStreams(from: json["formatStreams"].arrayValue) +
|
||||||
|
extractAdaptiveFormats(from: json["adaptiveFormats"].arrayValue)
|
||||||
|
}
|
||||||
|
|
||||||
private static func extractFormatStreams(from streams: [JSON]) -> [Stream] {
|
private static func extractFormatStreams(from streams: [JSON]) -> [Stream] {
|
||||||
streams.map {
|
streams.map {
|
||||||
SingleAssetStream(
|
SingleAssetStream(
|
||||||
@ -378,4 +383,11 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func extractRelated(from content: JSON) -> [Video] {
|
||||||
|
content
|
||||||
|
.dictionaryValue["recommendedVideos"]?
|
||||||
|
.arrayValue
|
||||||
|
.compactMap(extractVideo(from:)) ?? []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,7 +224,8 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
|||||||
thumbnails: thumbnails,
|
thumbnails: thumbnails,
|
||||||
likes: details["likes"]?.int,
|
likes: details["likes"]?.int,
|
||||||
dislikes: details["dislikes"]?.int,
|
dislikes: details["dislikes"]?.int,
|
||||||
streams: extractStreams(from: content)
|
streams: extractStreams(from: content),
|
||||||
|
related: extractRelated(from: content)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,6 +311,13 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
|||||||
return streams
|
return streams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func extractRelated(from content: JSON) -> [Video] {
|
||||||
|
content
|
||||||
|
.dictionaryValue["relatedStreams"]?
|
||||||
|
.arrayValue
|
||||||
|
.compactMap(extractVideo(from:)) ?? []
|
||||||
|
}
|
||||||
|
|
||||||
private static func compatibleAudioStreams(from content: JSON) -> [JSON] {
|
private static func compatibleAudioStreams(from content: JSON) -> [JSON] {
|
||||||
content
|
content
|
||||||
.dictionaryValue["audioStreams"]?
|
.dictionaryValue["audioStreams"]?
|
||||||
|
@ -30,6 +30,8 @@ struct Video: Identifiable, Equatable, Hashable {
|
|||||||
|
|
||||||
var channel: Channel
|
var channel: Channel
|
||||||
|
|
||||||
|
var related = [Video]()
|
||||||
|
|
||||||
init(
|
init(
|
||||||
id: String? = nil,
|
id: String? = nil,
|
||||||
videoID: String,
|
videoID: String,
|
||||||
@ -49,7 +51,8 @@ struct Video: Identifiable, Equatable, Hashable {
|
|||||||
likes: Int? = nil,
|
likes: Int? = nil,
|
||||||
dislikes: Int? = nil,
|
dislikes: Int? = nil,
|
||||||
keywords: [String] = [],
|
keywords: [String] = [],
|
||||||
streams: [Stream] = []
|
streams: [Stream] = [],
|
||||||
|
related: [Video] = []
|
||||||
) {
|
) {
|
||||||
self.id = id ?? UUID().uuidString
|
self.id = id ?? UUID().uuidString
|
||||||
self.videoID = videoID
|
self.videoID = videoID
|
||||||
@ -70,6 +73,7 @@ struct Video: Identifiable, Equatable, Hashable {
|
|||||||
self.dislikes = dislikes
|
self.dislikes = dislikes
|
||||||
self.keywords = keywords
|
self.keywords = keywords
|
||||||
self.streams = streams
|
self.streams = streams
|
||||||
|
self.related = related
|
||||||
}
|
}
|
||||||
|
|
||||||
var publishedDate: String? {
|
var publishedDate: String? {
|
||||||
|
@ -82,6 +82,8 @@
|
|||||||
372915E72687E3B900F5A35B /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E52687E3B900F5A35B /* Defaults.swift */; };
|
372915E72687E3B900F5A35B /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E52687E3B900F5A35B /* Defaults.swift */; };
|
||||||
372915E82687E3B900F5A35B /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E52687E3B900F5A35B /* Defaults.swift */; };
|
372915E82687E3B900F5A35B /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372915E52687E3B900F5A35B /* Defaults.swift */; };
|
||||||
3730D8A02712E2B70020ED53 /* NowPlayingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3730D89F2712E2B70020ED53 /* NowPlayingView.swift */; };
|
3730D8A02712E2B70020ED53 /* NowPlayingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3730D89F2712E2B70020ED53 /* NowPlayingView.swift */; };
|
||||||
|
373197D92732015300EF734F /* RelatedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373197D82732015300EF734F /* RelatedView.swift */; };
|
||||||
|
373197DA2732060100EF734F /* RelatedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373197D82732015300EF734F /* RelatedView.swift */; };
|
||||||
37319F0527103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
|
37319F0527103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
|
||||||
37319F0627103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
|
37319F0627103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
|
||||||
37319F0727103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
|
37319F0727103F94004ECCD0 /* PlayerQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37319F0427103F94004ECCD0 /* PlayerQueue.swift */; };
|
||||||
@ -525,6 +527,7 @@
|
|||||||
371F2F19269B43D300E4A7AB /* NavigationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModel.swift; sourceTree = "<group>"; };
|
371F2F19269B43D300E4A7AB /* NavigationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModel.swift; sourceTree = "<group>"; };
|
||||||
372915E52687E3B900F5A35B /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = "<group>"; };
|
372915E52687E3B900F5A35B /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = "<group>"; };
|
||||||
3730D89F2712E2B70020ED53 /* NowPlayingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingView.swift; sourceTree = "<group>"; };
|
3730D89F2712E2B70020ED53 /* NowPlayingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingView.swift; sourceTree = "<group>"; };
|
||||||
|
373197D82732015300EF734F /* RelatedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelatedView.swift; sourceTree = "<group>"; };
|
||||||
37319F0427103F94004ECCD0 /* PlayerQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerQueue.swift; sourceTree = "<group>"; };
|
37319F0427103F94004ECCD0 /* PlayerQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerQueue.swift; sourceTree = "<group>"; };
|
||||||
373CFACA26966264003CB2C6 /* SearchQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchQuery.swift; sourceTree = "<group>"; };
|
373CFACA26966264003CB2C6 /* SearchQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchQuery.swift; sourceTree = "<group>"; };
|
||||||
373CFADA269663F1003CB2C6 /* Thumbnail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbnail.swift; sourceTree = "<group>"; };
|
373CFADA269663F1003CB2C6 /* Thumbnail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbnail.swift; sourceTree = "<group>"; };
|
||||||
@ -783,6 +786,7 @@
|
|||||||
3743CA4D270EFE3400E4D32B /* PlayerQueueRow.swift */,
|
3743CA4D270EFE3400E4D32B /* PlayerQueueRow.swift */,
|
||||||
37CC3F4B270CFE1700608308 /* PlayerQueueView.swift */,
|
37CC3F4B270CFE1700608308 /* PlayerQueueView.swift */,
|
||||||
37BE0BD526A1D4A90092E2DB /* PlayerViewController.swift */,
|
37BE0BD526A1D4A90092E2DB /* PlayerViewController.swift */,
|
||||||
|
373197D82732015300EF734F /* RelatedView.swift */,
|
||||||
37B81AFE26D2CA3700675966 /* VideoDetails.swift */,
|
37B81AFE26D2CA3700675966 /* VideoDetails.swift */,
|
||||||
37B81AFB26D2C9C900675966 /* VideoDetailsPaddingModifier.swift */,
|
37B81AFB26D2C9C900675966 /* VideoDetailsPaddingModifier.swift */,
|
||||||
37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */,
|
37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */,
|
||||||
@ -1743,6 +1747,7 @@
|
|||||||
3748186E26A769D60084E870 /* DetailBadge.swift in Sources */,
|
3748186E26A769D60084E870 /* DetailBadge.swift in Sources */,
|
||||||
37AAF2A026741C97007FC770 /* SubscriptionsView.swift in Sources */,
|
37AAF2A026741C97007FC770 /* SubscriptionsView.swift in Sources */,
|
||||||
37599F30272B42810087F250 /* FavoriteItem.swift in Sources */,
|
37599F30272B42810087F250 /* FavoriteItem.swift in Sources */,
|
||||||
|
373197D92732015300EF734F /* RelatedView.swift in Sources */,
|
||||||
373CFAEB26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */,
|
373CFAEB26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */,
|
||||||
37B81B0226D2CAE700675966 /* PlaybackBar.swift in Sources */,
|
37B81B0226D2CAE700675966 /* PlaybackBar.swift in Sources */,
|
||||||
372915E62687E3B900F5A35B /* Defaults.swift in Sources */,
|
372915E62687E3B900F5A35B /* Defaults.swift in Sources */,
|
||||||
@ -2011,6 +2016,7 @@
|
|||||||
37152EEC26EFEB95004FB96D /* LazyView.swift in Sources */,
|
37152EEC26EFEB95004FB96D /* LazyView.swift in Sources */,
|
||||||
37484C2726FC83E000287258 /* InstanceForm.swift in Sources */,
|
37484C2726FC83E000287258 /* InstanceForm.swift in Sources */,
|
||||||
37F49BA826CB0FCE00304AC0 /* PlaylistFormView.swift in Sources */,
|
37F49BA826CB0FCE00304AC0 /* PlaylistFormView.swift in Sources */,
|
||||||
|
373197DA2732060100EF734F /* RelatedView.swift in Sources */,
|
||||||
37D4B19926717E1500C925CA /* Video.swift in Sources */,
|
37D4B19926717E1500C925CA /* Video.swift in Sources */,
|
||||||
378E50FD26FE8B9F00F49626 /* Instance.swift in Sources */,
|
378E50FD26FE8B9F00F49626 /* Instance.swift in Sources */,
|
||||||
37169AA82729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
|
37169AA82729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
|
||||||
|
@ -10,12 +10,12 @@ struct PlayerQueueView: View {
|
|||||||
List {
|
List {
|
||||||
Group {
|
Group {
|
||||||
playingNext
|
playingNext
|
||||||
|
related
|
||||||
playedPreviously
|
playedPreviously
|
||||||
}
|
}
|
||||||
|
#if !os(iOS)
|
||||||
.padding(.vertical, 5)
|
.padding(.vertical, 5)
|
||||||
.listRowInsets(EdgeInsets())
|
.listRowInsets(EdgeInsets())
|
||||||
#if os(iOS)
|
|
||||||
.padding(.horizontal, 10)
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +71,27 @@ struct PlayerQueueView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeButton(_ item: PlayerQueueItem, history: Bool) -> some View {
|
private var related: some View {
|
||||||
|
Group {
|
||||||
|
if !player.currentVideo.isNil, !player.currentVideo!.related.isEmpty {
|
||||||
|
Section(header: Text("Related")) {
|
||||||
|
ForEach(player.currentVideo!.related) { video in
|
||||||
|
PlayerQueueRow(item: PlayerQueueItem(video), fullScreen: $fullScreen)
|
||||||
|
.contextMenu {
|
||||||
|
Button("Play Next") {
|
||||||
|
player.playNext(video)
|
||||||
|
}
|
||||||
|
Button("Play Last") {
|
||||||
|
player.enqueueVideo(video)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func removeButton(_ item: PlayerQueueItem, history: Bool) -> some View {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
if history {
|
if history {
|
||||||
player.removeHistory(item)
|
player.removeHistory(item)
|
||||||
@ -83,7 +103,7 @@ struct PlayerQueueView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeAllButton(history: Bool) -> some View {
|
private func removeAllButton(history: Bool) -> some View {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
if history {
|
if history {
|
||||||
player.removeHistoryItems()
|
player.removeHistoryItems()
|
||||||
|
@ -32,23 +32,29 @@ final class PlayerViewController: UIViewController {
|
|||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
playerModel.avPlayerViewController = playerViewController
|
playerModel.avPlayerViewController = playerViewController
|
||||||
playerViewController.customInfoViewControllers = [playerQueueInfoViewController]
|
playerViewController.customInfoViewControllers = [
|
||||||
|
infoViewController([.related], title: "Related"),
|
||||||
|
infoViewController([.playingNext, .playedPreviously], title: "Playing Next")
|
||||||
|
]
|
||||||
#else
|
#else
|
||||||
embedViewController()
|
embedViewController()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
var playerQueueInfoViewController: UIHostingController<AnyView> {
|
func infoViewController(
|
||||||
|
_ sections: [NowPlayingView.ViewSection],
|
||||||
|
title: String
|
||||||
|
) -> UIHostingController<AnyView> {
|
||||||
let controller = UIHostingController(rootView:
|
let controller = UIHostingController(rootView:
|
||||||
AnyView(
|
AnyView(
|
||||||
NowPlayingView(inInfoViewController: true)
|
NowPlayingView(sections: sections, inInfoViewController: true)
|
||||||
.frame(maxHeight: 600)
|
.frame(maxHeight: 600)
|
||||||
.environmentObject(playerModel)
|
.environmentObject(playerModel)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
controller.title = "Playing Next"
|
controller.title = title
|
||||||
|
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
30
Shared/Player/RelatedView.swift
Normal file
30
Shared/Player/RelatedView.swift
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct RelatedView: View {
|
||||||
|
@EnvironmentObject<PlayerModel> private var player
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
List {
|
||||||
|
if !player.currentVideo.isNil, !player.currentVideo!.related.isEmpty {
|
||||||
|
Section(header: Text("Related")) {
|
||||||
|
ForEach(player.currentVideo!.related) { video in
|
||||||
|
PlayerQueueRow(item: PlayerQueueItem(video), fullScreen: .constant(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if os(macOS)
|
||||||
|
.listStyle(.inset)
|
||||||
|
#elseif os(iOS)
|
||||||
|
.listStyle(.grouped)
|
||||||
|
#else
|
||||||
|
.listStyle(.plain)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RelatedView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
RelatedView()
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct VideoDetails: View {
|
struct VideoDetails: View {
|
||||||
enum Page {
|
enum Page {
|
||||||
case details, queue
|
case details, queue, related
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binding var sidebarQueue: Bool
|
@Binding var sidebarQueue: Bool
|
||||||
@ -89,6 +89,14 @@ struct VideoDetails: View {
|
|||||||
case .queue:
|
case .queue:
|
||||||
PlayerQueueView(fullScreen: $fullScreen)
|
PlayerQueueView(fullScreen: $fullScreen)
|
||||||
.edgesIgnoringSafeArea(.horizontal)
|
.edgesIgnoringSafeArea(.horizontal)
|
||||||
|
|
||||||
|
case .related:
|
||||||
|
#if os(macOS)
|
||||||
|
EmptyView()
|
||||||
|
#else
|
||||||
|
RelatedView()
|
||||||
|
.edgesIgnoringSafeArea(.horizontal)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, inNavigationView && fullScreen ? 10 : 0)
|
.padding(.top, inNavigationView && fullScreen ? 10 : 0)
|
||||||
@ -109,7 +117,9 @@ struct VideoDetails: View {
|
|||||||
.onChange(of: sidebarQueue) { queue in
|
.onChange(of: sidebarQueue) { queue in
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
if queue {
|
if queue {
|
||||||
|
if currentPage == .queue {
|
||||||
currentPage = .details
|
currentPage = .details
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
currentPage = .queue
|
currentPage = .queue
|
||||||
}
|
}
|
||||||
@ -216,6 +226,7 @@ struct VideoDetails: View {
|
|||||||
var pagePicker: some View {
|
var pagePicker: some View {
|
||||||
Picker("Page", selection: $currentPage) {
|
Picker("Page", selection: $currentPage) {
|
||||||
Text("Details").tag(Page.details)
|
Text("Details").tag(Page.details)
|
||||||
|
Text("Related").tag(Page.related)
|
||||||
Text("Queue").tag(Page.queue)
|
Text("Queue").tag(Page.queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,8 +62,14 @@ struct VideoPlayerView: View {
|
|||||||
if player.currentItem.isNil {
|
if player.currentItem.isNil {
|
||||||
playerPlaceholder(geometry: geometry)
|
playerPlaceholder(geometry: geometry)
|
||||||
} else {
|
} else {
|
||||||
|
#if os(macOS)
|
||||||
|
Player()
|
||||||
|
.modifier(VideoPlayerSizeModifier(geometry: geometry))
|
||||||
|
|
||||||
|
#else
|
||||||
player.playerView
|
player.playerView
|
||||||
.modifier(VideoPlayerSizeModifier(geometry: geometry))
|
.modifier(VideoPlayerSizeModifier(geometry: geometry))
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -107,6 +113,11 @@ struct VideoPlayerView: View {
|
|||||||
.frame(minWidth: 250)
|
.frame(minWidth: 250)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
.onDisappear {
|
||||||
|
if !player.playingInPictureInPicture {
|
||||||
|
player.pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func playerPlaceholder(geometry: GeometryProxy) -> some View {
|
func playerPlaceholder(geometry: GeometryProxy) -> some View {
|
||||||
|
@ -21,6 +21,7 @@ struct ShareButton: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
Menu {
|
Menu {
|
||||||
instanceActions
|
instanceActions
|
||||||
|
Divider()
|
||||||
youtubeActions
|
youtubeActions
|
||||||
} label: {
|
} label: {
|
||||||
Label("Share", systemImage: "square.and.arrow.up")
|
Label("Share", systemImage: "square.and.arrow.up")
|
||||||
|
@ -9,20 +9,26 @@ final class PictureInPictureDelegate: NSObject, AVPlayerViewPictureInPictureDele
|
|||||||
}
|
}
|
||||||
|
|
||||||
func playerViewWillStartPicture(inPicture _: AVPlayerView) {
|
func playerViewWillStartPicture(inPicture _: AVPlayerView) {
|
||||||
playerModel.playingInPictureInPicture = true
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||||
playerModel.presentingPlayer = false
|
self?.playerModel.playingInPictureInPicture = true
|
||||||
|
self?.playerModel.presentingPlayer = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func playerViewWillStopPicture(inPicture _: AVPlayerView) {
|
func playerViewWillStopPicture(inPicture _: AVPlayerView) {
|
||||||
playerModel.playingInPictureInPicture = false
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||||
playerModel.presentPlayer()
|
self?.playerModel.playingInPictureInPicture = false
|
||||||
|
self?.playerModel.presentPlayer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func playerView(
|
func playerView(
|
||||||
_: AVPlayerView,
|
_: AVPlayerView,
|
||||||
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: (Bool) -> Void
|
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: (Bool) -> Void
|
||||||
) {
|
) {
|
||||||
playerModel.presentingPlayer = true
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||||
|
self?.playerModel.presentingPlayer = true
|
||||||
|
}
|
||||||
completionHandler(true)
|
completionHandler(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,6 @@ final class PlayerViewController: NSViewController {
|
|||||||
var pictureInPictureDelegate = PictureInPictureDelegate()
|
var pictureInPictureDelegate = PictureInPictureDelegate()
|
||||||
|
|
||||||
override func viewDidDisappear() {
|
override func viewDidDisappear() {
|
||||||
if !playerModel.playingInPictureInPicture {
|
|
||||||
playerModel.pause()
|
|
||||||
}
|
|
||||||
super.viewDidDisappear()
|
super.viewDidDisappear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct NowPlayingView: View {
|
struct NowPlayingView: View {
|
||||||
|
enum ViewSection: CaseIterable {
|
||||||
|
case nowPlaying, playingNext, playedPreviously, related
|
||||||
|
}
|
||||||
|
|
||||||
|
var sections = ViewSection.allCases
|
||||||
var inInfoViewController = false
|
var inInfoViewController = false
|
||||||
|
|
||||||
@EnvironmentObject<PlayerModel> private var player
|
@EnvironmentObject<PlayerModel> private var player
|
||||||
@ -18,7 +23,7 @@ struct NowPlayingView: View {
|
|||||||
var content: some View {
|
var content: some View {
|
||||||
List {
|
List {
|
||||||
Group {
|
Group {
|
||||||
if !inInfoViewController, let item = player.currentItem {
|
if sections.contains(.nowPlaying), let item = player.currentItem {
|
||||||
Section(header: Text("Now Playing")) {
|
Section(header: Text("Now Playing")) {
|
||||||
Button {
|
Button {
|
||||||
player.presentPlayer()
|
player.presentPlayer()
|
||||||
@ -29,6 +34,7 @@ struct NowPlayingView: View {
|
|||||||
.onPlayPauseCommand(perform: player.togglePlay)
|
.onPlayPauseCommand(perform: player.togglePlay)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sections.contains(.playingNext) {
|
||||||
Section(header: Text("Playing Next")) {
|
Section(header: Text("Playing Next")) {
|
||||||
if player.queue.isEmpty {
|
if player.queue.isEmpty {
|
||||||
Text("Playback queue is empty")
|
Text("Playback queue is empty")
|
||||||
@ -50,8 +56,31 @@ struct NowPlayingView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !player.history.isEmpty {
|
if sections.contains(.related), !player.currentVideo.isNil, !player.currentVideo!.related.isEmpty {
|
||||||
|
Section(header: inInfoViewController ? AnyView(EmptyView()) : AnyView(Text("Related"))) {
|
||||||
|
ForEach(player.currentVideo!.related) { video in
|
||||||
|
Button {
|
||||||
|
player.playNow(video)
|
||||||
|
player.presentPlayer()
|
||||||
|
} label: {
|
||||||
|
VideoBanner(video: video)
|
||||||
|
}
|
||||||
|
.contextMenu {
|
||||||
|
Button("Play Next") {
|
||||||
|
player.playNext(video)
|
||||||
|
}
|
||||||
|
Button("Play Last") {
|
||||||
|
player.enqueueVideo(video)
|
||||||
|
}
|
||||||
|
Button("Cancel", role: .cancel) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sections.contains(.playedPreviously), !player.history.isEmpty {
|
||||||
Section(header: Text("Played Previously")) {
|
Section(header: Text("Played Previously")) {
|
||||||
ForEach(player.history) { item in
|
ForEach(player.history) { item in
|
||||||
Button {
|
Button {
|
||||||
@ -75,7 +104,6 @@ struct NowPlayingView: View {
|
|||||||
}
|
}
|
||||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 20))
|
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 20))
|
||||||
.padding(.vertical, 20)
|
.padding(.vertical, 20)
|
||||||
// .padding(.horizontal, 40)
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal, inInfoViewController ? 40 : 0)
|
.padding(.horizontal, inInfoViewController ? 40 : 0)
|
||||||
.listStyle(.grouped)
|
.listStyle(.grouped)
|
||||||
@ -86,7 +114,6 @@ struct NowPlayingView: View {
|
|||||||
Text(text)
|
Text(text)
|
||||||
.font((inInfoViewController ? Font.system(size: 40) : .title3).bold())
|
.font((inInfoViewController ? Font.system(size: 40) : .title3).bold())
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
// .padding(.leading, 40)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user