mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Bring AVPlayer back to tvOS
This commit is contained in:
parent
48e616b301
commit
ae9b23b9e7
@ -55,7 +55,19 @@ struct FixtureEnvironmentObjectsModifier: ViewModifier {
|
|||||||
channel: .init(id: "", name: "Channel Name"),
|
channel: .init(id: "", name: "Channel Name"),
|
||||||
likes: 2332,
|
likes: 2332,
|
||||||
dislikes: 30,
|
dislikes: 30,
|
||||||
keywords: ["Video", "Computer", "Long Long Keyword"]
|
keywords: ["Video", "Computer", "Long Long Keyword"],
|
||||||
|
chapters: [
|
||||||
|
.init(
|
||||||
|
title: "Abc",
|
||||||
|
image: URL(string: "https://pipedproxy.kavin.rocks/vi/rr2XfL_df3o/hqdefault_29633.jpg?sqp=-oaymwEcCNACELwBSFXyq4qpAw4IARUAAIhCGAFwAcABBg%3D%3D&rs=AOn4CLDFDm9D5SvsIA7D3v5n5KZahLs_UA&host=i.ytimg.com")!,
|
||||||
|
start: 3
|
||||||
|
),
|
||||||
|
.init(
|
||||||
|
title: "Def",
|
||||||
|
image: URL(string: "https://pipedproxy.kavin.rocks/vi/rr2XfL_df3o/hqdefault_98900.jpg?sqp=-oaymwEcCNACELwBSFXyq4qpAw4IARUAAIhCGAFwAcABBg%3D%3D&rs=AOn4CLCfjXJBJb2O2q0jT0RHIi7hARVahw&host=i.ytimg.com")!,
|
||||||
|
start: 33
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
@ -142,7 +142,9 @@ final class MPVClient: ObservableObject {
|
|||||||
|
|
||||||
options.append("force-seekable=yes")
|
options.append("force-seekable=yes")
|
||||||
|
|
||||||
args.append(options.joined(separator: ","))
|
if !options.isEmpty {
|
||||||
|
args.append(options.joined(separator: ","))
|
||||||
|
}
|
||||||
|
|
||||||
command("loadfile", args: args, returnValueCallback: completionHandler)
|
command("loadfile", args: args, returnValueCallback: completionHandler)
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ extension PlayerModel {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
return availableStreamsSorted.map { stream in
|
return availableStreamsSorted.filter { backend.canPlay($0) }.map { stream in
|
||||||
let state = stream == streamSelection ? UIAction.State.on : .off
|
let state = stream == streamSelection ? UIAction.State.on : .off
|
||||||
|
|
||||||
return UIAction(title: stream.description, state: state) { _ in
|
return UIAction(title: stream.description, state: state) { _ in
|
||||||
@ -43,6 +43,13 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var switchToMPVAction: UIAction? {
|
||||||
|
UIAction(title: "Switch to MPV", image: UIImage(systemName: "m.circle")) { _ in
|
||||||
|
self.avPlayerBackend.controller?.dismiss(animated: false)
|
||||||
|
self.changeActiveBackend(from: .appleAVPlayer, to: .mpv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var rateMenu: UIMenu {
|
private var rateMenu: UIMenu {
|
||||||
UIMenu(title: "Playback rate", image: UIImage(systemName: rateMenuSystemImage), children: rateMenuActions)
|
UIMenu(title: "Playback rate", image: UIImage(systemName: rateMenuSystemImage), children: rateMenuActions)
|
||||||
}
|
}
|
||||||
@ -69,7 +76,8 @@ extension PlayerModel {
|
|||||||
avPlayerBackend.controller?.playerView.transportBarCustomMenuItems = [
|
avPlayerBackend.controller?.playerView.transportBarCustomMenuItems = [
|
||||||
restoreLastSkippedSegmentAction,
|
restoreLastSkippedSegmentAction,
|
||||||
rateMenu,
|
rateMenu,
|
||||||
streamsMenu
|
streamsMenu,
|
||||||
|
switchToMPVAction
|
||||||
].compactMap { $0 }
|
].compactMap { $0 }
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -136,8 +136,8 @@ class Stream: Equatable, Hashable, Identifiable {
|
|||||||
var kind: Kind!
|
var kind: Kind!
|
||||||
var format: Format!
|
var format: Format!
|
||||||
|
|
||||||
var encoding: String!
|
var encoding: String?
|
||||||
var videoFormat: String!
|
var videoFormat: String?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
instance: Instance? = nil,
|
instance: Instance? = nil,
|
||||||
|
@ -65,7 +65,7 @@ extension Defaults.Keys {
|
|||||||
static let qualityProfilesDefault = [
|
static let qualityProfilesDefault = [
|
||||||
hd2160pMPVProfile,
|
hd2160pMPVProfile,
|
||||||
hd1080pMPVProfile,
|
hd1080pMPVProfile,
|
||||||
hd720pMPVProfile
|
hd720pAVPlayerProfile
|
||||||
]
|
]
|
||||||
static let batteryCellularProfileDefault = hd1080pMPVProfile.id
|
static let batteryCellularProfileDefault = hd1080pMPVProfile.id
|
||||||
static let batteryNonCellularProfileDefault = hd1080pMPVProfile.id
|
static let batteryNonCellularProfileDefault = hd1080pMPVProfile.id
|
||||||
|
@ -2,14 +2,44 @@ import AVKit
|
|||||||
import Defaults
|
import Defaults
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct AppleAVPlayerView: UIViewRepresentable {
|
#if os(iOS)
|
||||||
@EnvironmentObject<PlayerModel> private var player
|
struct AppleAVPlayerView: UIViewRepresentable {
|
||||||
|
@EnvironmentObject<PlayerModel> private var player
|
||||||
|
|
||||||
func makeUIView(context _: Context) -> some UIView {
|
func makeUIView(context _: Context) -> some UIView {
|
||||||
let playerLayerView = PlayerLayerView(frame: .zero)
|
let playerLayerView = PlayerLayerView(frame: .zero)
|
||||||
playerLayerView.player = player
|
playerLayerView.player = player
|
||||||
return playerLayerView
|
return playerLayerView
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_: UIViewType, context _: Context) {}
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
struct AppleAVPlayerView: UIViewControllerRepresentable {
|
||||||
|
@EnvironmentObject<AccountsModel> private var accounts
|
||||||
|
@EnvironmentObject<CommentsModel> private var comments
|
||||||
|
@EnvironmentObject<NavigationModel> private var navigation
|
||||||
|
@EnvironmentObject<PlayerModel> private var player
|
||||||
|
@EnvironmentObject<PlaylistsModel> private var playlists
|
||||||
|
@EnvironmentObject<SubscriptionsModel> private var subscriptions
|
||||||
|
|
||||||
func updateUIView(_: UIViewType, context _: Context) {}
|
func makeUIViewController(context _: Context) -> AppleAVPlayerViewController {
|
||||||
}
|
let controller = AppleAVPlayerViewController()
|
||||||
|
|
||||||
|
controller.accountsModel = accounts
|
||||||
|
controller.commentsModel = comments
|
||||||
|
controller.navigationModel = navigation
|
||||||
|
controller.playerModel = player
|
||||||
|
controller.playlistsModel = playlists
|
||||||
|
controller.subscriptionsModel = subscriptions
|
||||||
|
|
||||||
|
player.avPlayerBackend.controller = controller
|
||||||
|
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_: AppleAVPlayerViewController, context _: Context) {
|
||||||
|
player.rebuildTVMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
@ -4,125 +4,93 @@ import SwiftUI
|
|||||||
|
|
||||||
final class AppleAVPlayerViewController: UIViewController {
|
final class AppleAVPlayerViewController: UIViewController {
|
||||||
var playerLoaded = false
|
var playerLoaded = false
|
||||||
|
var accountsModel: AccountsModel!
|
||||||
var commentsModel: CommentsModel!
|
var commentsModel: CommentsModel!
|
||||||
var navigationModel: NavigationModel!
|
var navigationModel: NavigationModel!
|
||||||
var playerModel: PlayerModel!
|
var playerModel: PlayerModel!
|
||||||
|
var playlistsModel: PlaylistsModel!
|
||||||
var subscriptionsModel: SubscriptionsModel!
|
var subscriptionsModel: SubscriptionsModel!
|
||||||
var playerView = AVPlayerViewController()
|
var playerView = AVPlayerViewController()
|
||||||
|
|
||||||
let persistenceController = PersistenceController.shared
|
let persistenceController = PersistenceController.shared
|
||||||
|
|
||||||
#if os(iOS)
|
|
||||||
var aspectRatio: Double? {
|
|
||||||
let ratio = Double(playerView.videoBounds.width) / Double(playerView.videoBounds.height)
|
|
||||||
|
|
||||||
guard ratio.isFinite else {
|
|
||||||
return VideoPlayerView.defaultAspectRatio // swiftlint:disable:this implicit_return
|
|
||||||
}
|
|
||||||
|
|
||||||
return [ratio, 1.0].max()!
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
loadPlayer()
|
loadPlayer()
|
||||||
|
|
||||||
#if os(tvOS)
|
if playerModel.presentingPlayer, !playerView.isBeingPresented, !playerView.isBeingDismissed {
|
||||||
if !playerView.isBeingPresented, !playerView.isBeingDismissed {
|
present(playerView, animated: false)
|
||||||
present(playerView, animated: false)
|
}
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(tvOS)
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
override func viewDidDisappear(_ animated: Bool) {
|
super.viewDidDisappear(animated)
|
||||||
super.viewDidDisappear(animated)
|
|
||||||
|
|
||||||
if !playerModel.presentingPlayer, !Defaults[.pauseOnHidingPlayer], !playerModel.isPlaying {
|
if !playerModel.presentingPlayer, !Defaults[.pauseOnHidingPlayer], !playerModel.isPlaying {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
||||||
self?.playerModel.play()
|
self?.playerModel.play()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
}
|
||||||
|
|
||||||
func loadPlayer() {
|
func loadPlayer() {
|
||||||
guard !playerLoaded else {
|
guard !playerLoaded else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
playerModel.avPlayerBackend.controller = self
|
|
||||||
playerView.player = playerModel.avPlayerBackend.avPlayer
|
playerView.player = playerModel.avPlayerBackend.avPlayer
|
||||||
playerView.allowsPictureInPicturePlayback = true
|
playerView.allowsPictureInPicturePlayback = true
|
||||||
playerView.showsPlaybackControls = false
|
playerView.showsPlaybackControls = true
|
||||||
#if os(iOS)
|
|
||||||
if #available(iOS 14.2, *) {
|
|
||||||
playerView.canStartPictureInPictureAutomaticallyFromInline = true
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
playerView.delegate = self
|
playerView.delegate = self
|
||||||
|
|
||||||
#if os(tvOS)
|
var infoViewControllers = [UIHostingController<AnyView>]()
|
||||||
var infoViewControllers = [UIHostingController<AnyView>]()
|
infoViewControllers.append(infoViewController([.chapters], title: "Chapters"))
|
||||||
infoViewControllers.append(infoViewController([.comments], title: "Comments"))
|
infoViewControllers.append(infoViewController([.comments], title: "Comments"))
|
||||||
|
|
||||||
var queueSections = [NowPlayingView.ViewSection.playingNext]
|
var queueSections = [NowPlayingView.ViewSection.playingNext]
|
||||||
if Defaults[.showHistoryInPlayer] {
|
if Defaults[.showHistoryInPlayer] {
|
||||||
queueSections.append(.playedPreviously)
|
queueSections.append(.playedPreviously)
|
||||||
}
|
}
|
||||||
|
|
||||||
infoViewControllers.append(contentsOf: [
|
infoViewControllers.append(contentsOf: [
|
||||||
infoViewController([.related], title: "Related"),
|
infoViewController([.related], title: "Related"),
|
||||||
infoViewController(queueSections, title: "Queue")
|
infoViewController(queueSections, title: "Queue")
|
||||||
])
|
])
|
||||||
|
|
||||||
playerView.customInfoViewControllers = infoViewControllers
|
playerView.customInfoViewControllers = infoViewControllers
|
||||||
#else
|
|
||||||
embedViewController()
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(tvOS)
|
func infoViewController(
|
||||||
func infoViewController(
|
_ sections: [NowPlayingView.ViewSection],
|
||||||
_ sections: [NowPlayingView.ViewSection],
|
title: String
|
||||||
title: String
|
) -> UIHostingController<AnyView> {
|
||||||
) -> UIHostingController<AnyView> {
|
let controller = UIHostingController(rootView:
|
||||||
let controller = UIHostingController(rootView:
|
AnyView(
|
||||||
AnyView(
|
NowPlayingView(sections: sections, inInfoViewController: true)
|
||||||
NowPlayingView(sections: sections, inInfoViewController: true)
|
.frame(maxHeight: 600)
|
||||||
.frame(maxHeight: 600)
|
.environmentObject(accountsModel)
|
||||||
.environmentObject(commentsModel)
|
.environmentObject(commentsModel)
|
||||||
.environmentObject(playerModel)
|
.environmentObject(playerModel)
|
||||||
.environmentObject(subscriptionsModel)
|
.environmentObject(playlistsModel)
|
||||||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
.environmentObject(subscriptionsModel)
|
||||||
)
|
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
controller.title = title
|
controller.title = title
|
||||||
|
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
func embedViewController() {
|
|
||||||
playerView.view.frame = view.bounds
|
|
||||||
|
|
||||||
addChild(playerView)
|
|
||||||
view.addSubview(playerView.view)
|
|
||||||
|
|
||||||
playerView.didMove(toParent: self)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppleAVPlayerViewController: AVPlayerViewControllerDelegate {
|
extension AppleAVPlayerViewController: AVPlayerViewControllerDelegate {
|
||||||
func playerViewControllerShouldDismiss(_: AVPlayerViewController) -> Bool {
|
func playerViewControllerShouldDismiss(_: AVPlayerViewController) -> Bool {
|
||||||
false
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
func playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart(_: AVPlayerViewController) -> Bool {
|
func playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart(_: AVPlayerViewController) -> Bool {
|
||||||
false
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
func playerViewControllerWillBeginDismissalTransition(_: AVPlayerViewController) {
|
func playerViewControllerWillBeginDismissalTransition(_: AVPlayerViewController) {
|
||||||
|
63
Shared/Player/ChapterView.swift
Normal file
63
Shared/Player/ChapterView.swift
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import Foundation
|
||||||
|
import SDWebImageSwiftUI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ChapterView: View {
|
||||||
|
var chapter: Chapter
|
||||||
|
|
||||||
|
@EnvironmentObject<PlayerModel> private var player
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button {
|
||||||
|
player.backend.seek(to: chapter.start)
|
||||||
|
} label: {
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
if !chapter.image.isNil {
|
||||||
|
smallImage(chapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text(chapter.title)
|
||||||
|
.font(.headline)
|
||||||
|
Text(chapter.start.formattedAsPlaybackTime(allowZero: true) ?? "")
|
||||||
|
.font(.system(.subheadline).monospacedDigit())
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder func smallImage(_ chapter: Chapter) -> some View {
|
||||||
|
WebImage(url: chapter.image)
|
||||||
|
.resizable()
|
||||||
|
.placeholder {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
.indicator(.activity)
|
||||||
|
#if os(tvOS)
|
||||||
|
.frame(width: thumbnailWidth, height: 140)
|
||||||
|
.mask(RoundedRectangle(cornerRadius: 12))
|
||||||
|
#else
|
||||||
|
.frame(width: thumbnailWidth, height: 60)
|
||||||
|
.mask(RoundedRectangle(cornerRadius: 6))
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private var thumbnailWidth: Double {
|
||||||
|
#if os(tvOS)
|
||||||
|
250
|
||||||
|
#else
|
||||||
|
100
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ChapterView_Preview: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
ChapterView(chapter: .init(title: "Chapter", start: 30))
|
||||||
|
.injectFixtureEnvironmentObjects()
|
||||||
|
}
|
||||||
|
}
|
@ -10,12 +10,7 @@ struct ChaptersView: View {
|
|||||||
List {
|
List {
|
||||||
Section(header: Text("Chapters")) {
|
Section(header: Text("Chapters")) {
|
||||||
ForEach(chapters) { chapter in
|
ForEach(chapters) { chapter in
|
||||||
Button {
|
ChapterView(chapter: chapter)
|
||||||
player.backend.seek(to: chapter.start)
|
|
||||||
} label: {
|
|
||||||
chapterButtonLabel(chapter)
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listRowBackground(Color.clear)
|
.listRowBackground(Color.clear)
|
||||||
@ -33,51 +28,9 @@ struct ChaptersView: View {
|
|||||||
NoCommentsView(text: "No chapters information available", systemImage: "xmark.circle.fill")
|
NoCommentsView(text: "No chapters information available", systemImage: "xmark.circle.fill")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func chapterButtonLabel(_ chapter: Chapter) -> some View {
|
|
||||||
HStack(spacing: 12) {
|
|
||||||
if !chapter.image.isNil {
|
|
||||||
smallImage(chapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
|
||||||
Text(chapter.title)
|
|
||||||
.font(.headline)
|
|
||||||
Text(chapter.start.formattedAsPlaybackTime(allowZero: true) ?? "")
|
|
||||||
.font(.system(.subheadline).monospacedDigit())
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder func smallImage(_ chapter: Chapter) -> some View {
|
|
||||||
WebImage(url: chapter.image)
|
|
||||||
.resizable()
|
|
||||||
.placeholder {
|
|
||||||
ProgressView()
|
|
||||||
}
|
|
||||||
.indicator(.activity)
|
|
||||||
#if os(tvOS)
|
|
||||||
.frame(width: thumbnailWidth, height: 140)
|
|
||||||
.mask(RoundedRectangle(cornerRadius: 12))
|
|
||||||
#else
|
|
||||||
.frame(width: thumbnailWidth, height: 60)
|
|
||||||
.mask(RoundedRectangle(cornerRadius: 6))
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private var thumbnailWidth: Double {
|
|
||||||
#if os(tvOS)
|
|
||||||
250
|
|
||||||
#else
|
|
||||||
100
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ChaptersView_Preview: PreviewProvider {
|
struct ChaptersView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ChaptersView()
|
ChaptersView()
|
||||||
.injectFixtureEnvironmentObjects()
|
.injectFixtureEnvironmentObjects()
|
||||||
|
@ -58,23 +58,15 @@ struct ControlsOverlay: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(tvOS)
|
Section(header: controlsHeader("Stream & Player")) {
|
||||||
let streamAndPlayerHeaderText = "Stream"
|
|
||||||
#else
|
|
||||||
let streamAndPlayerHeaderText = "Stream & Player"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Section(header: controlsHeader(streamAndPlayerHeaderText)) {
|
|
||||||
qualityButton
|
qualityButton
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
.focused($focusedField, equals: .stream)
|
.focused($focusedField, equals: .stream)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !os(tvOS)
|
HStack(spacing: 8) {
|
||||||
HStack {
|
backendButtons
|
||||||
backendButtons
|
}
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if player.activeBackend == .mpv,
|
if player.activeBackend == .mpv,
|
||||||
@ -129,11 +121,13 @@ struct ControlsOverlay: View {
|
|||||||
private var backendButtons: some View {
|
private var backendButtons: some View {
|
||||||
ForEach(PlayerBackendType.allCases, id: \.self) { backend in
|
ForEach(PlayerBackendType.allCases, id: \.self) { backend in
|
||||||
backendButton(backend)
|
backendButton(backend)
|
||||||
|
#if !os(tvOS)
|
||||||
.frame(height: 40)
|
.frame(height: 40)
|
||||||
|
#endif
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.frame(maxWidth: 115)
|
.frame(maxWidth: 115)
|
||||||
.modifier(ControlBackgroundModifier())
|
.modifier(ControlBackgroundModifier())
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,9 +144,6 @@ struct ControlsOverlay: View {
|
|||||||
}
|
}
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
#elseif os(tvOS)
|
|
||||||
.modifier(ControlBackgroundModifier())
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,14 +89,15 @@ struct PlayerControls: View {
|
|||||||
}
|
}
|
||||||
.frame(maxHeight: .infinity)
|
.frame(maxHeight: .infinity)
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
.onChange(of: model.presentingControls) { newValue in
|
.onChange(of: model.presentingControls) { newValue in
|
||||||
if newValue { focusedField = .play }
|
if newValue { focusedField = .play }
|
||||||
}
|
}
|
||||||
.onChange(of: focusedField) { _ in model.resetTimer() }
|
.onChange(of: focusedField) { _ in model.resetTimer() }
|
||||||
#else
|
#else
|
||||||
.background(PlayerGestures())
|
.background(PlayerGestures())
|
||||||
.background(controlsBackground)
|
.background(controlsBackground)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if model.presentingDetailsOverlay {
|
if model.presentingDetailsOverlay {
|
||||||
@ -176,6 +177,7 @@ struct PlayerControls: View {
|
|||||||
}
|
}
|
||||||
.retryOnAppear(true)
|
.retryOnAppear(true)
|
||||||
.indicator(.activity)
|
.indicator(.activity)
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,7 +275,6 @@ struct PlayerControls: View {
|
|||||||
|
|
||||||
private var musicModeButton: some View {
|
private var musicModeButton: some View {
|
||||||
button("Music Mode", systemImage: "music.note", background: false, active: player.musicMode, action: player.toggleMusicMode)
|
button("Music Mode", systemImage: "music.note", background: false, active: player.musicMode, action: player.toggleMusicMode)
|
||||||
.disabled(player.activeBackend == .appleAVPlayer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var pipButton: some View {
|
private var pipButton: some View {
|
||||||
|
@ -60,7 +60,7 @@ struct StreamControl: View {
|
|||||||
.frame(maxWidth: 320)
|
.frame(maxWidth: 320)
|
||||||
}
|
}
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
ForEach(player.availableStreamsSorted) { stream in
|
ForEach(streams) { stream in
|
||||||
Button(stream.description) { player.streamSelection = stream }
|
Button(stream.description) { player.streamSelection = stream }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,10 +79,14 @@ struct StreamControl: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func availableStreamsForInstance(_ instance: Instance) -> [Stream.Kind: [Stream]] {
|
private func availableStreamsForInstance(_ instance: Instance) -> [Stream.Kind: [Stream]] {
|
||||||
let streams = player.availableStreamsSorted.filter { $0.instance == instance }.filter { player.backend.canPlay($0) }
|
let streams = streams.filter { $0.instance == instance }.filter { player.backend.canPlay($0) }
|
||||||
|
|
||||||
return Dictionary(grouping: streams, by: \.kind!)
|
return Dictionary(grouping: streams, by: \.kind!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var streams: [Stream] {
|
||||||
|
player.availableStreamsSorted.filter { player.backend.canPlay($0) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StreamControl_Previews: PreviewProvider {
|
struct StreamControl_Previews: PreviewProvider {
|
||||||
|
@ -252,7 +252,9 @@ struct VideoPlayerView: View {
|
|||||||
ZStack {
|
ZStack {
|
||||||
player.playerBackendView
|
player.playerBackendView
|
||||||
|
|
||||||
tvControls
|
if player.activeBackend == .mpv {
|
||||||
|
tvControls
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
#else
|
#else
|
||||||
|
@ -99,6 +99,9 @@ struct QualityProfileForm: View {
|
|||||||
Section(header: Text("Resolution")) {
|
Section(header: Text("Resolution")) {
|
||||||
qualityButton
|
qualityButton
|
||||||
}
|
}
|
||||||
|
Section(header: Text("Backend")) {
|
||||||
|
backendPicker
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
backendPicker
|
backendPicker
|
||||||
qualityPicker
|
qualityPicker
|
||||||
@ -147,7 +150,7 @@ struct QualityProfileForm: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
return picker
|
picker
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +194,7 @@ struct QualityProfileForm: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
return picker
|
picker
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +162,9 @@
|
|||||||
37169AA72729E2CC0011DE61 /* AccountsBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37169AA52729E2CC0011DE61 /* AccountsBridge.swift */; };
|
37169AA72729E2CC0011DE61 /* AccountsBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37169AA52729E2CC0011DE61 /* AccountsBridge.swift */; };
|
||||||
37169AA82729E2CC0011DE61 /* AccountsBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37169AA52729E2CC0011DE61 /* AccountsBridge.swift */; };
|
37169AA82729E2CC0011DE61 /* AccountsBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37169AA52729E2CC0011DE61 /* AccountsBridge.swift */; };
|
||||||
37192D5528B0D5D60012EEDD /* PlayerLayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373031F22838388A000CFD59 /* PlayerLayerView.swift */; };
|
37192D5528B0D5D60012EEDD /* PlayerLayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373031F22838388A000CFD59 /* PlayerLayerView.swift */; };
|
||||||
|
37192D5728B179D60012EEDD /* ChaptersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37192D5628B179D60012EEDD /* ChaptersView.swift */; };
|
||||||
|
37192D5828B179D60012EEDD /* ChaptersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37192D5628B179D60012EEDD /* ChaptersView.swift */; };
|
||||||
|
37192D5928B179D60012EEDD /* ChaptersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37192D5628B179D60012EEDD /* ChaptersView.swift */; };
|
||||||
371B7E5C27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; };
|
371B7E5C27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; };
|
||||||
371B7E5D27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; };
|
371B7E5D27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; };
|
||||||
371B7E5E27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; };
|
371B7E5E27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; };
|
||||||
@ -312,9 +315,9 @@
|
|||||||
37520699285E8DD300CA655F /* Chapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37520698285E8DD300CA655F /* Chapter.swift */; };
|
37520699285E8DD300CA655F /* Chapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37520698285E8DD300CA655F /* Chapter.swift */; };
|
||||||
3752069A285E8DD300CA655F /* Chapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37520698285E8DD300CA655F /* Chapter.swift */; };
|
3752069A285E8DD300CA655F /* Chapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37520698285E8DD300CA655F /* Chapter.swift */; };
|
||||||
3752069B285E8DD300CA655F /* Chapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37520698285E8DD300CA655F /* Chapter.swift */; };
|
3752069B285E8DD300CA655F /* Chapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37520698285E8DD300CA655F /* Chapter.swift */; };
|
||||||
3752069D285E910600CA655F /* ChaptersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3752069C285E910600CA655F /* ChaptersView.swift */; };
|
3752069D285E910600CA655F /* ChapterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3752069C285E910600CA655F /* ChapterView.swift */; };
|
||||||
3752069E285E910600CA655F /* ChaptersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3752069C285E910600CA655F /* ChaptersView.swift */; };
|
3752069E285E910600CA655F /* ChapterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3752069C285E910600CA655F /* ChapterView.swift */; };
|
||||||
3752069F285E910600CA655F /* ChaptersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3752069C285E910600CA655F /* ChaptersView.swift */; };
|
3752069F285E910600CA655F /* ChapterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3752069C285E910600CA655F /* ChapterView.swift */; };
|
||||||
3756C2A62861131100E4B059 /* NetworkState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3756C2A52861131100E4B059 /* NetworkState.swift */; };
|
3756C2A62861131100E4B059 /* NetworkState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3756C2A52861131100E4B059 /* NetworkState.swift */; };
|
||||||
3756C2A72861131100E4B059 /* NetworkState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3756C2A52861131100E4B059 /* NetworkState.swift */; };
|
3756C2A72861131100E4B059 /* NetworkState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3756C2A52861131100E4B059 /* NetworkState.swift */; };
|
||||||
3756C2A82861131100E4B059 /* NetworkState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3756C2A52861131100E4B059 /* NetworkState.swift */; };
|
3756C2A82861131100E4B059 /* NetworkState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3756C2A52861131100E4B059 /* NetworkState.swift */; };
|
||||||
@ -622,7 +625,6 @@
|
|||||||
37BE0BD126A0E2D50092E2DB /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */; };
|
37BE0BD126A0E2D50092E2DB /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */; };
|
||||||
37BE0BD326A1D4780092E2DB /* AppleAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */; };
|
37BE0BD326A1D4780092E2DB /* AppleAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */; };
|
||||||
37BE0BD426A1D47D0092E2DB /* AppleAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */; };
|
37BE0BD426A1D47D0092E2DB /* AppleAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */; };
|
||||||
37BE0BD626A1D4A90092E2DB /* AppleAVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD526A1D4A90092E2DB /* AppleAVPlayerViewController.swift */; };
|
|
||||||
37BE0BD726A1D4A90092E2DB /* AppleAVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD526A1D4A90092E2DB /* AppleAVPlayerViewController.swift */; };
|
37BE0BD726A1D4A90092E2DB /* AppleAVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD526A1D4A90092E2DB /* AppleAVPlayerViewController.swift */; };
|
||||||
37BE0BDC26A2367F0092E2DB /* AppleAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BDB26A2367F0092E2DB /* AppleAVPlayerView.swift */; };
|
37BE0BDC26A2367F0092E2DB /* AppleAVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BDB26A2367F0092E2DB /* AppleAVPlayerView.swift */; };
|
||||||
37BF661C27308859008CCFB0 /* DropFavorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BF661B27308859008CCFB0 /* DropFavorite.swift */; };
|
37BF661C27308859008CCFB0 /* DropFavorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BF661B27308859008CCFB0 /* DropFavorite.swift */; };
|
||||||
@ -987,6 +989,7 @@
|
|||||||
37152EE926EFEB95004FB96D /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = "<group>"; };
|
37152EE926EFEB95004FB96D /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = "<group>"; };
|
||||||
37169AA12729D98A0011DE61 /* InstancesBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesBridge.swift; sourceTree = "<group>"; };
|
37169AA12729D98A0011DE61 /* InstancesBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesBridge.swift; sourceTree = "<group>"; };
|
||||||
37169AA52729E2CC0011DE61 /* AccountsBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsBridge.swift; sourceTree = "<group>"; };
|
37169AA52729E2CC0011DE61 /* AccountsBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsBridge.swift; sourceTree = "<group>"; };
|
||||||
|
37192D5628B179D60012EEDD /* ChaptersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChaptersView.swift; sourceTree = "<group>"; };
|
||||||
371B7E5B27596B8400D21217 /* Comment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comment.swift; sourceTree = "<group>"; };
|
371B7E5B27596B8400D21217 /* Comment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comment.swift; sourceTree = "<group>"; };
|
||||||
371B7E602759706A00D21217 /* CommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentsView.swift; sourceTree = "<group>"; };
|
371B7E602759706A00D21217 /* CommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentsView.swift; sourceTree = "<group>"; };
|
||||||
371B7E652759786B00D21217 /* Comment+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Comment+Fixtures.swift"; sourceTree = "<group>"; };
|
371B7E652759786B00D21217 /* Comment+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Comment+Fixtures.swift"; sourceTree = "<group>"; };
|
||||||
@ -1053,7 +1056,7 @@
|
|||||||
3751BA7F27E64244007B1A60 /* VideoLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoLayer.swift; sourceTree = "<group>"; };
|
3751BA7F27E64244007B1A60 /* VideoLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoLayer.swift; sourceTree = "<group>"; };
|
||||||
3751BA8227E6914F007B1A60 /* ReturnYouTubeDislikeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReturnYouTubeDislikeAPI.swift; sourceTree = "<group>"; };
|
3751BA8227E6914F007B1A60 /* ReturnYouTubeDislikeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReturnYouTubeDislikeAPI.swift; sourceTree = "<group>"; };
|
||||||
37520698285E8DD300CA655F /* Chapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chapter.swift; sourceTree = "<group>"; };
|
37520698285E8DD300CA655F /* Chapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chapter.swift; sourceTree = "<group>"; };
|
||||||
3752069C285E910600CA655F /* ChaptersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChaptersView.swift; sourceTree = "<group>"; };
|
3752069C285E910600CA655F /* ChapterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterView.swift; sourceTree = "<group>"; };
|
||||||
3756C2A52861131100E4B059 /* NetworkState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkState.swift; sourceTree = "<group>"; };
|
3756C2A52861131100E4B059 /* NetworkState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkState.swift; sourceTree = "<group>"; };
|
||||||
3756C2A92861151C00E4B059 /* NetworkStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkStateModel.swift; sourceTree = "<group>"; };
|
3756C2A92861151C00E4B059 /* NetworkStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkStateModel.swift; sourceTree = "<group>"; };
|
||||||
37579D5C27864F5F00FD0B98 /* Help.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Help.swift; sourceTree = "<group>"; };
|
37579D5C27864F5F00FD0B98 /* Help.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Help.swift; sourceTree = "<group>"; };
|
||||||
@ -1531,7 +1534,8 @@
|
|||||||
375E45F327B1973400BA7902 /* MPV */,
|
375E45F327B1973400BA7902 /* MPV */,
|
||||||
37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */,
|
37BE0BD226A1D4780092E2DB /* AppleAVPlayerView.swift */,
|
||||||
37BE0BD526A1D4A90092E2DB /* AppleAVPlayerViewController.swift */,
|
37BE0BD526A1D4A90092E2DB /* AppleAVPlayerViewController.swift */,
|
||||||
3752069C285E910600CA655F /* ChaptersView.swift */,
|
3752069C285E910600CA655F /* ChapterView.swift */,
|
||||||
|
37192D5628B179D60012EEDD /* ChaptersView.swift */,
|
||||||
371B7E602759706A00D21217 /* CommentsView.swift */,
|
371B7E602759706A00D21217 /* CommentsView.swift */,
|
||||||
37EF9A75275BEB8E0043B585 /* CommentView.swift */,
|
37EF9A75275BEB8E0043B585 /* CommentView.swift */,
|
||||||
37DD9DA22785BBC900539416 /* NoCommentsView.swift */,
|
37DD9DA22785BBC900539416 /* NoCommentsView.swift */,
|
||||||
@ -2760,7 +2764,6 @@
|
|||||||
37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */,
|
37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */,
|
||||||
37130A5F277657300033018A /* PersistenceController.swift in Sources */,
|
37130A5F277657300033018A /* PersistenceController.swift in Sources */,
|
||||||
37FD43E32704847C0073EE42 /* View+Fixtures.swift in Sources */,
|
37FD43E32704847C0073EE42 /* View+Fixtures.swift in Sources */,
|
||||||
37BE0BD626A1D4A90092E2DB /* AppleAVPlayerViewController.swift in Sources */,
|
|
||||||
3776ADD6287381240078EBC4 /* Captions.swift in Sources */,
|
3776ADD6287381240078EBC4 /* Captions.swift in Sources */,
|
||||||
37BA793F26DB8F97002A0235 /* ChannelVideosView.swift in Sources */,
|
37BA793F26DB8F97002A0235 /* ChannelVideosView.swift in Sources */,
|
||||||
37C194C726F6A9C8005D3B96 /* RecentsModel.swift in Sources */,
|
37C194C726F6A9C8005D3B96 /* RecentsModel.swift in Sources */,
|
||||||
@ -2770,6 +2773,7 @@
|
|||||||
37F64FE426FE70A60081B69E /* RedrawOnModifier.swift in Sources */,
|
37F64FE426FE70A60081B69E /* RedrawOnModifier.swift in Sources */,
|
||||||
37EBD8C427AF0DA800F1C24B /* PlayerBackend.swift in Sources */,
|
37EBD8C427AF0DA800F1C24B /* PlayerBackend.swift in Sources */,
|
||||||
376A33E02720CAD6000C1D6B /* VideosApp.swift in Sources */,
|
376A33E02720CAD6000C1D6B /* VideosApp.swift in Sources */,
|
||||||
|
37192D5728B179D60012EEDD /* ChaptersView.swift in Sources */,
|
||||||
37B81AF926D2C9A700675966 /* VideoPlayerSizeModifier.swift in Sources */,
|
37B81AF926D2C9A700675966 /* VideoPlayerSizeModifier.swift in Sources */,
|
||||||
37C0698227260B2100F7F6CB /* ThumbnailsModel.swift in Sources */,
|
37C0698227260B2100F7F6CB /* ThumbnailsModel.swift in Sources */,
|
||||||
37BC50A82778A84700510953 /* HistorySettings.swift in Sources */,
|
37BC50A82778A84700510953 /* HistorySettings.swift in Sources */,
|
||||||
@ -2792,7 +2796,7 @@
|
|||||||
37030FF727B0347C00ECDDAA /* MPVPlayerView.swift in Sources */,
|
37030FF727B0347C00ECDDAA /* MPVPlayerView.swift in Sources */,
|
||||||
37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
|
37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
|
||||||
377FC7DC267A081800A6BBAF /* PopularView.swift in Sources */,
|
377FC7DC267A081800A6BBAF /* PopularView.swift in Sources */,
|
||||||
3752069D285E910600CA655F /* ChaptersView.swift in Sources */,
|
3752069D285E910600CA655F /* ChapterView.swift in Sources */,
|
||||||
375EC96A289F232600751258 /* QualityProfilesModel.swift in Sources */,
|
375EC96A289F232600751258 /* QualityProfilesModel.swift in Sources */,
|
||||||
3751B4B227836902000B7DF4 /* SearchPage.swift in Sources */,
|
3751B4B227836902000B7DF4 /* SearchPage.swift in Sources */,
|
||||||
37CC3F4C270CFE1700608308 /* PlayerQueueView.swift in Sources */,
|
37CC3F4C270CFE1700608308 /* PlayerQueueView.swift in Sources */,
|
||||||
@ -2981,6 +2985,7 @@
|
|||||||
3743CA4F270EFE3400E4D32B /* PlayerQueueRow.swift in Sources */,
|
3743CA4F270EFE3400E4D32B /* PlayerQueueRow.swift in Sources */,
|
||||||
374C053C2724614F009BDDBE /* PlayerTVMenu.swift in Sources */,
|
374C053C2724614F009BDDBE /* PlayerTVMenu.swift in Sources */,
|
||||||
377FC7DD267A081A00A6BBAF /* PopularView.swift in Sources */,
|
377FC7DD267A081A00A6BBAF /* PopularView.swift in Sources */,
|
||||||
|
37192D5828B179D60012EEDD /* ChaptersView.swift in Sources */,
|
||||||
3784CDE327772EE40055BBF2 /* Watch.swift in Sources */,
|
3784CDE327772EE40055BBF2 /* Watch.swift in Sources */,
|
||||||
37E80F3D287B107F00561799 /* VideoDetailsOverlay.swift in Sources */,
|
37E80F3D287B107F00561799 /* VideoDetailsOverlay.swift in Sources */,
|
||||||
37DD9DBB2785D60300539416 /* FramePreferenceKey.swift in Sources */,
|
37DD9DBB2785D60300539416 /* FramePreferenceKey.swift in Sources */,
|
||||||
@ -3025,7 +3030,7 @@
|
|||||||
37CC3F51270D010D00608308 /* VideoBanner.swift in Sources */,
|
37CC3F51270D010D00608308 /* VideoBanner.swift in Sources */,
|
||||||
37F961A027BD90BB00058149 /* PlayerBackendType.swift in Sources */,
|
37F961A027BD90BB00058149 /* PlayerBackendType.swift in Sources */,
|
||||||
37BC50AD2778BCBA00510953 /* HistoryModel.swift in Sources */,
|
37BC50AD2778BCBA00510953 /* HistoryModel.swift in Sources */,
|
||||||
3752069E285E910600CA655F /* ChaptersView.swift in Sources */,
|
3752069E285E910600CA655F /* ChapterView.swift in Sources */,
|
||||||
37030FF827B0347C00ECDDAA /* MPVPlayerView.swift in Sources */,
|
37030FF827B0347C00ECDDAA /* MPVPlayerView.swift in Sources */,
|
||||||
378E50FC26FE8B9F00F49626 /* Instance.swift in Sources */,
|
378E50FC26FE8B9F00F49626 /* Instance.swift in Sources */,
|
||||||
37169AA32729D98A0011DE61 /* InstancesBridge.swift in Sources */,
|
37169AA32729D98A0011DE61 /* InstancesBridge.swift in Sources */,
|
||||||
@ -3278,6 +3283,7 @@
|
|||||||
37E80F44287B7AB400561799 /* VideoDetails.swift in Sources */,
|
37E80F44287B7AB400561799 /* VideoDetails.swift in Sources */,
|
||||||
3751BA8527E6914F007B1A60 /* ReturnYouTubeDislikeAPI.swift in Sources */,
|
3751BA8527E6914F007B1A60 /* ReturnYouTubeDislikeAPI.swift in Sources */,
|
||||||
37030FFD27B0398000ECDDAA /* MPVClient.swift in Sources */,
|
37030FFD27B0398000ECDDAA /* MPVClient.swift in Sources */,
|
||||||
|
37192D5928B179D60012EEDD /* ChaptersView.swift in Sources */,
|
||||||
37B767DD2677C3CA0098BAA8 /* PlayerModel.swift in Sources */,
|
37B767DD2677C3CA0098BAA8 /* PlayerModel.swift in Sources */,
|
||||||
373CFAF12697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
373CFAF12697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
||||||
3784CDE427772EE40055BBF2 /* Watch.swift in Sources */,
|
3784CDE427772EE40055BBF2 /* Watch.swift in Sources */,
|
||||||
@ -3341,7 +3347,7 @@
|
|||||||
37FEF11527EFD8580033912F /* PlaceholderCell.swift in Sources */,
|
37FEF11527EFD8580033912F /* PlaceholderCell.swift in Sources */,
|
||||||
37FD43F02704A9C00073EE42 /* RecentsModel.swift in Sources */,
|
37FD43F02704A9C00073EE42 /* RecentsModel.swift in Sources */,
|
||||||
379775952689365600DD52A8 /* Array+Next.swift in Sources */,
|
379775952689365600DD52A8 /* Array+Next.swift in Sources */,
|
||||||
3752069F285E910600CA655F /* ChaptersView.swift in Sources */,
|
3752069F285E910600CA655F /* ChapterView.swift in Sources */,
|
||||||
37F4AD1D28612B23004D0F66 /* OpeningStream.swift in Sources */,
|
37F4AD1D28612B23004D0F66 /* OpeningStream.swift in Sources */,
|
||||||
3705B180267B4DFB00704544 /* TrendingCountry.swift in Sources */,
|
3705B180267B4DFB00704544 /* TrendingCountry.swift in Sources */,
|
||||||
373CFACD26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
373CFACD26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
||||||
|
@ -4,7 +4,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct NowPlayingView: View {
|
struct NowPlayingView: View {
|
||||||
enum ViewSection: CaseIterable {
|
enum ViewSection: CaseIterable {
|
||||||
case nowPlaying, playingNext, playedPreviously, related, comments
|
case nowPlaying, playingNext, playedPreviously, related, comments, chapters
|
||||||
}
|
}
|
||||||
|
|
||||||
var sections = [ViewSection.nowPlaying, .playingNext, .playedPreviously, .related]
|
var sections = [ViewSection.nowPlaying, .playingNext, .playedPreviously, .related]
|
||||||
@ -84,23 +84,16 @@ struct NowPlayingView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if sections.contains(.related), !player.currentVideo.isNil, !player.currentVideo!.related.isEmpty {
|
if sections.contains(.related), let video = player.currentVideo, !video.related.isEmpty {
|
||||||
Section(header: inInfoViewController ? AnyView(EmptyView()) : AnyView(Text("Related"))) {
|
Section(header: Text("Related")) {
|
||||||
ForEach(player.currentVideo!.related) { video in
|
ForEach(video.related) { video in
|
||||||
Button {
|
Button {
|
||||||
player.playNow(video)
|
player.play(video)
|
||||||
player.show()
|
|
||||||
} label: {
|
} label: {
|
||||||
VideoBanner(video: video)
|
VideoBanner(video: video)
|
||||||
}
|
}
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
Button("Play Next") {
|
VideoContextMenuView(video: video)
|
||||||
player.playNext(video)
|
|
||||||
}
|
|
||||||
Button("Play Last") {
|
|
||||||
player.enqueueVideo(video)
|
|
||||||
}
|
|
||||||
Button("Cancel", role: .cancel) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,9 +118,7 @@ struct NowPlayingView: View {
|
|||||||
player.loadHistoryVideoDetails(watch.videoID)
|
player.loadHistoryVideoDetails(watch.videoID)
|
||||||
}
|
}
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
Button("Remove", role: .destructive) {
|
VideoContextMenuView(video: watch.video)
|
||||||
player.removeWatch(watch)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,6 +152,20 @@ struct NowPlayingView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sections.contains(.chapters) {
|
||||||
|
if let video = player.currentVideo {
|
||||||
|
if video.chapters.isEmpty {
|
||||||
|
NoCommentsView(text: "No chapters information available", systemImage: "xmark.circle.fill")
|
||||||
|
} else {
|
||||||
|
Section(header: Text("Chapters")) {
|
||||||
|
ForEach(video.chapters) { chapter in
|
||||||
|
ChapterView(chapter: chapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.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)
|
||||||
@ -177,7 +182,7 @@ struct NowPlayingView: View {
|
|||||||
|
|
||||||
struct NowPlayingView_Previews: PreviewProvider {
|
struct NowPlayingView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NowPlayingView()
|
NowPlayingView(sections: [.chapters])
|
||||||
.injectFixtureEnvironmentObjects()
|
.injectFixtureEnvironmentObjects()
|
||||||
|
|
||||||
NowPlayingView(inInfoViewController: true)
|
NowPlayingView(inInfoViewController: true)
|
||||||
|
Loading…
Reference in New Issue
Block a user