1
0
mirror of https://github.com/yattee/yattee.git synced 2024-12-14 22:30:32 +05:30
yattee/Shared/Player/PlaybackBar.swift

226 lines
7.4 KiB
Swift
Raw Normal View History

2021-10-17 04:18:58 +05:30
import Defaults
2021-08-23 00:43:33 +05:30
import Foundation
import SwiftUI
struct PlaybackBar: View {
2021-11-28 20:07:55 +05:30
@Environment(\.colorScheme) private var colorScheme
@Environment(\.presentationMode) private var presentationMode
@Environment(\.inNavigationView) private var inNavigationView
@EnvironmentObject<PlayerModel> private var player
2021-08-24 03:01:51 +05:30
2021-08-23 00:43:33 +05:30
var body: some View {
HStack {
#if !os(macOS)
closeButton
#endif
2021-08-23 00:43:33 +05:30
if player.currentItem != nil {
2021-11-02 22:54:59 +05:30
HStack {
Text(playbackStatus)
Text("")
rateMenu
}
.font(.caption2)
#if os(macOS)
.padding(.leading, 4)
#endif
2021-10-17 04:18:58 +05:30
Spacer()
HStack(spacing: 4) {
2021-10-23 22:19:45 +05:30
if !player.lastSkipped.isNil {
restoreLastSkippedSegmentButton
}
if player.live {
2021-10-17 04:18:58 +05:30
Image(systemName: "dot.radiowaves.left.and.right")
} else if player.isLoadingAvailableStreams || player.isLoadingStream {
Image(systemName: "bolt.horizontal.fill")
2021-11-07 22:22:42 +05:30
} else if !player.playerError.isNil {
Button {
player.presentingErrorDetails = true
} label: {
Image(systemName: "exclamationmark.circle.fill")
.foregroundColor(.red)
}
.buttonStyle(.plain)
2021-09-14 02:11:16 +05:30
}
2021-10-17 04:18:58 +05:30
streamControl
.disabled(player.isLoadingAvailableStreams)
.frame(alignment: .trailing)
.onChange(of: player.streamSelection) { selection in
guard !selection.isNil else {
return
}
player.upgradeToStream(selection!)
}
#if os(macOS)
.frame(maxWidth: 180)
#endif
2021-08-24 03:01:51 +05:30
}
2021-10-17 04:18:58 +05:30
.transaction { t in t.animation = .none }
.font(.caption2)
} else {
Spacer()
2021-08-24 03:01:51 +05:30
}
2021-08-23 00:43:33 +05:30
}
2021-12-01 04:29:24 +05:30
.foregroundColor(colorScheme == .dark ? .gray : .black)
2021-11-28 20:07:55 +05:30
.alert(isPresented: $player.presentingErrorDetails) {
Alert(
title: Text("Error"),
message: Text(player.playerError?.localizedDescription ?? "")
)
2021-11-07 22:22:42 +05:30
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 20)
2021-08-23 00:43:33 +05:30
.padding(4)
2021-11-28 20:07:55 +05:30
.background(colorScheme == .dark ? Color.black : Color.white)
2021-08-23 00:43:33 +05:30
}
2021-10-17 04:18:58 +05:30
private var closeButton: some View {
Button {
player.hide()
2021-10-17 04:18:58 +05:30
} label: {
Label(
"Close",
systemImage: inNavigationView ? "chevron.backward.circle.fill" : "chevron.down.circle.fill"
)
.labelStyle(.iconOnly)
}
.accessibilityLabel(Text("Close"))
.buttonStyle(.borderless)
.foregroundColor(.gray)
.keyboardShortcut(.cancelAction)
2021-08-23 00:43:33 +05:30
}
2021-10-17 04:18:58 +05:30
private var playbackStatus: String {
if player.live {
return "LIVE"
}
guard !player.isLoadingVideo else {
return "loading..."
2021-08-23 00:43:33 +05:30
}
2021-11-02 22:54:59 +05:30
let videoLengthAtRate = player.currentVideo!.length / Double(player.currentRate)
let remainingSeconds = videoLengthAtRate - player.time!.seconds
2021-08-23 00:43:33 +05:30
2021-08-24 03:01:51 +05:30
if remainingSeconds < 60 {
return "less than a minute"
}
2021-11-28 20:07:55 +05:30
let timeFinishAt = Date().addingTimeInterval(remainingSeconds)
return "ends at \(formattedTimeFinishAt(timeFinishAt))"
}
private func formattedTimeFinishAt(_ date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .short
2021-08-23 00:43:33 +05:30
2021-11-28 20:07:55 +05:30
return dateFormatter.string(from: date)
2021-08-23 00:43:33 +05:30
}
2021-11-02 22:54:59 +05:30
private var rateMenu: some View {
#if os(macOS)
ratePicker
.labelsHidden()
.frame(maxWidth: 70)
#else
Menu {
ratePicker
} label: {
Text(player.rateLabel(player.currentRate))
}
#endif
}
private var ratePicker: some View {
Picker("", selection: $player.currentRate) {
ForEach(PlayerModel.availableRates, id: \.self) { rate in
Text(player.rateLabel(rate)).tag(rate)
}
}
}
2021-10-23 22:19:45 +05:30
private var restoreLastSkippedSegmentButton: some View {
2021-10-24 18:01:10 +05:30
HStack(spacing: 4) {
Button {
player.restoreLastSkippedSegment()
} label: {
HStack(spacing: 4) {
Image(systemName: "arrow.uturn.left.circle")
Text(player.lastSkipped!.title())
}
2021-10-23 22:19:45 +05:30
}
2021-10-24 18:01:10 +05:30
.buttonStyle(.plain)
Text("")
2021-10-23 22:19:45 +05:30
}
}
2021-10-17 04:18:58 +05:30
private var streamControl: some View {
#if os(macOS)
Picker("", selection: $player.streamSelection) {
2021-11-09 04:44:28 +05:30
ForEach(InstancesModel.all) { instance in
2021-10-17 04:18:58 +05:30
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 {
2021-11-09 04:44:28 +05:30
ForEach(InstancesModel.all) { instance in
2021-10-17 04:18:58 +05:30
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]] {
2021-10-18 04:36:00 +05:30
let streams = player.availableStreamsSorted.filter { $0.instance == instance }
2021-10-17 04:18:58 +05:30
return Dictionary(grouping: streams, by: \.kind!)
}
2021-08-23 00:43:33 +05:30
}
2021-10-14 03:40:29 +05:30
struct PlaybackBar_Previews: PreviewProvider {
static var previews: some View {
PlaybackBar()
.injectFixtureEnvironmentObjects()
}
}