Add Piped support

This commit is contained in:
Arkadiusz Fal
2021-10-17 00:48:58 +02:00
parent a68d89cb6f
commit 62e17d5a18
44 changed files with 919 additions and 327 deletions

View File

@@ -1,3 +1,4 @@
import Defaults
import Foundation
import SwiftUI
@@ -5,47 +6,71 @@ struct PlaybackBar: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.inNavigationView) private var inNavigationView
@EnvironmentObject<InstancesModel> private var instances
@EnvironmentObject<PlayerModel> private var player
var body: some View {
HStack {
closeButton
.frame(width: 80, alignment: .leading)
if player.currentItem != nil {
Text(playbackStatus)
.foregroundColor(.gray)
.font(.caption2)
.frame(minWidth: 130, maxWidth: .infinity)
VStack {
if player.stream != nil {
Text(currentStreamString)
} else {
if player.currentVideo!.live {
Image(systemName: "dot.radiowaves.left.and.right")
} else {
Image(systemName: "bolt.horizontal.fill")
}
Spacer()
HStack(spacing: 4) {
if player.currentVideo!.live {
Image(systemName: "dot.radiowaves.left.and.right")
} else if player.isLoadingAvailableStreams || player.isLoadingStream {
Image(systemName: "bolt.horizontal.fill")
}
streamControl
.disabled(player.isLoadingAvailableStreams)
.frame(alignment: .trailing)
.environment(\.colorScheme, .dark)
.onChange(of: player.streamSelection) { selection in
guard !selection.isNil else {
return
}
player.upgradeToStream(selection!)
}
#if os(macOS)
.frame(maxWidth: 180)
#endif
}
.transaction { t in t.animation = .none }
.foregroundColor(.gray)
.font(.caption2)
.frame(width: 80, alignment: .trailing)
.fixedSize(horizontal: true, vertical: true)
} else {
Spacer()
}
}
.frame(minWidth: 0, maxWidth: .infinity)
.padding(4)
.background(.black)
}
var currentStreamString: String {
"\(player.stream!.resolution.height)p"
private var closeButton: some View {
Button {
dismiss()
} label: {
Label(
"Close",
systemImage: inNavigationView ? "chevron.backward.circle.fill" : "chevron.down.circle.fill"
)
.labelStyle(.iconOnly)
}
.accessibilityLabel(Text("Close"))
.buttonStyle(.borderless)
.foregroundColor(.gray)
.keyboardShortcut(.cancelAction)
}
var playbackStatus: String {
private var playbackStatus: String {
if player.live {
return "LIVE"
}
@@ -66,17 +91,61 @@ struct PlaybackBar: View {
return "ends at \(timeFinishAtString)"
}
var closeButton: some View {
Button {
dismiss()
} label: {
Label("Close", systemImage: inNavigationView ? "chevron.backward.circle.fill" : "chevron.down.circle.fill")
.labelStyle(.iconOnly)
}
.accessibilityLabel(Text("Close"))
.buttonStyle(.borderless)
.foregroundColor(.gray)
.keyboardShortcut(.cancelAction)
private var streamControl: some View {
#if os(macOS)
Picker("", selection: $player.streamSelection) {
ForEach(instances.all) { instance in
let instanceStreams = availableStreamsForInstance(instance)
if !instanceStreams.values.isEmpty {
let kinds = Array(instanceStreams.keys).sorted { $0 < $1 }
Section(header: Text(instance.longDescription)) {
ForEach(kinds, id: \.self) { key in
ForEach(instanceStreams[key] ?? []) { stream in
Text(stream.quality).tag(Stream?.some(stream))
}
if kinds.count > 1 {
Divider()
}
}
}
}
}
}
#else
Menu {
ForEach(instances.all) { instance in
let instanceStreams = availableStreamsForInstance(instance)
if !instanceStreams.values.isEmpty {
let kinds = Array(instanceStreams.keys).sorted { $0 < $1 }
Picker("", selection: $player.streamSelection) {
ForEach(kinds, id: \.self) { key in
ForEach(instanceStreams[key] ?? []) { stream in
Text(stream.description).tag(Stream?.some(stream))
}
if kinds.count > 1 {
Divider()
}
}
}
}
}
} label: {
Text(player.streamSelection?.quality ?? "")
}
#endif
}
private func availableStreamsForInstance(_ instance: Instance) -> [Stream.Kind: [Stream]] {
let streams = player.availableStreams.filter { $0.instance == instance }.sorted(by: streamsSorter)
return Dictionary(grouping: streams, by: \.kind!)
}
private func streamsSorter(_ lhs: Stream, _ rhs: Stream) -> Bool {
lhs.kind == rhs.kind ? (lhs.resolution.height > rhs.resolution.height) : (lhs.kind < rhs.kind)
}
}

View File

@@ -2,7 +2,6 @@ import Defaults
import SwiftUI
struct Player: UIViewControllerRepresentable {
@EnvironmentObject<InvidiousAPI> private var api
@EnvironmentObject<PlayerModel> private var player
var controller: PlayerViewController?
@@ -18,11 +17,8 @@ struct Player: UIViewControllerRepresentable {
let controller = PlayerViewController()
player.controller = controller
controller.playerModel = player
controller.api = api
controller.resolution = Defaults[.quality]
player.controller = controller
return controller
}

View File

@@ -3,11 +3,9 @@ import Logging
import SwiftUI
final class PlayerViewController: UIViewController {
var api: InvidiousAPI!
var playerLoaded = false
var playerModel: PlayerModel!
var playerViewController = AVPlayerViewController()
var resolution: Stream.ResolutionSetting!
var shouldResume = false
override func viewWillAppear(_ animated: Bool) {
@@ -81,7 +79,7 @@ extension PlayerViewController: AVPlayerViewControllerDelegate {
func playerViewControllerDidEndDismissalTransition(_: AVPlayerViewController) {
if shouldResume {
playerModel.player.play()
playerModel.play()
}
dismiss(animated: false)

View File

@@ -4,9 +4,9 @@ import SwiftUI
struct VideoDetailsPaddingModifier: ViewModifier {
static var defaultAdditionalDetailsPadding: Double {
#if os(macOS)
20
30
#else
35
40
#endif
}

View File

@@ -57,10 +57,12 @@ struct VideoPlayerSizeModifier: ViewModifier {
var maxHeight: Double {
#if os(iOS)
verticalSizeClass == .regular ? geometry.size.height - minimumHeightLeft : .infinity
let height = verticalSizeClass == .regular ? geometry.size.height - minimumHeightLeft : .infinity
#else
geometry.size.height - minimumHeightLeft
let height = geometry.size.height - minimumHeightLeft
#endif
return [height, 0].max()!
}
var edgesIgnoringSafeArea: Edge.Set {