mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Playback rate menu
This commit is contained in:
parent
320207e439
commit
0091af683f
@ -11,6 +11,7 @@ import SwiftUI
|
||||
import SwiftyJSON
|
||||
|
||||
final class PlayerModel: ObservableObject {
|
||||
static let availableRates: [Float] = [0.5, 0.67, 0.8, 1, 1.25, 1.5, 2]
|
||||
let logger = Logger(label: "net.arekf.Pearvidious.ps")
|
||||
|
||||
private(set) var player = AVPlayer()
|
||||
@ -23,7 +24,7 @@ final class PlayerModel: ObservableObject {
|
||||
@Published var presentingPlayer = false
|
||||
|
||||
@Published var stream: Stream?
|
||||
@Published var currentRate: Float?
|
||||
@Published var currentRate: Float = 1.0 { didSet { player.rate = currentRate } }
|
||||
|
||||
@Published var availableStreams = [Stream]() { didSet { rebuildTVMenu() } }
|
||||
@Published var streamSelection: Stream? { didSet { rebuildTVMenu() } }
|
||||
@ -418,6 +419,10 @@ final class PlayerModel: ObservableObject {
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
|
||||
if player.timeControlStatus == .playing, player.rate != self.currentRate {
|
||||
player.rate = self.currentRate
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
if player.timeControlStatus == .playing {
|
||||
ScreenSaverManager.shared.disable(reason: "Yattee is playing video")
|
||||
@ -469,4 +474,12 @@ final class PlayerModel: ObservableObject {
|
||||
|
||||
currentArtwork = MPMediaItemArtwork(boundsSize: image!.size) { _ in image! }
|
||||
}
|
||||
|
||||
func rateLabel(_ rate: Float) -> String {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.minimumFractionDigits = 0
|
||||
formatter.maximumFractionDigits = 2
|
||||
|
||||
return "\(formatter.string(from: NSNumber(value: rate))!)×"
|
||||
}
|
||||
}
|
||||
|
@ -42,12 +42,33 @@ extension PlayerModel {
|
||||
self.restoreLastSkippedSegment()
|
||||
}
|
||||
}
|
||||
|
||||
private var rateMenu: UIMenu {
|
||||
UIMenu(title: "Playback rate", image: UIImage(systemName: rateMenuSystemImage), children: rateMenuActions)
|
||||
}
|
||||
|
||||
private var rateMenuSystemImage: String {
|
||||
[0.0, 1.0].contains(currentRate) ? "speedometer" : (currentRate < 1.0 ? "tortoise.fill" : "hare.fill")
|
||||
}
|
||||
|
||||
private var rateMenuActions: [UIAction] {
|
||||
PlayerModel.availableRates.map { rate in
|
||||
let image = currentRate == Float(rate) ? UIImage(systemName: "checkmark") : nil
|
||||
|
||||
return UIAction(title: rateLabel(rate), image: image) { _ in
|
||||
DispatchQueue.main.async {
|
||||
self.currentRate = rate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
func rebuildTVMenu() {
|
||||
#if os(tvOS)
|
||||
avPlayerViewController?.transportBarCustomMenuItems = [
|
||||
restoreLastSkippedSegmentAction,
|
||||
rateMenu,
|
||||
streamsMenu
|
||||
].compactMap { $0 }
|
||||
#endif
|
||||
|
@ -14,9 +14,15 @@ struct PlaybackBar: View {
|
||||
closeButton
|
||||
|
||||
if player.currentItem != nil {
|
||||
Text(playbackStatus)
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption2)
|
||||
HStack {
|
||||
Text(playbackStatus)
|
||||
|
||||
Text("•")
|
||||
|
||||
rateMenu
|
||||
}
|
||||
.font(.caption2)
|
||||
.foregroundColor(.gray)
|
||||
|
||||
Spacer()
|
||||
|
||||
@ -44,7 +50,6 @@ struct PlaybackBar: View {
|
||||
.frame(maxWidth: 180)
|
||||
#endif
|
||||
}
|
||||
.environment(\.colorScheme, .dark)
|
||||
.transaction { t in t.animation = .none }
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption2)
|
||||
@ -52,6 +57,7 @@ struct PlaybackBar: View {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.environment(\.colorScheme, .dark)
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.padding(4)
|
||||
.background(.black)
|
||||
@ -82,7 +88,8 @@ struct PlaybackBar: View {
|
||||
return "loading..."
|
||||
}
|
||||
|
||||
let remainingSeconds = player.currentVideo!.length - player.time!.seconds
|
||||
let videoLengthAtRate = player.currentVideo!.length / Double(player.currentRate)
|
||||
let remainingSeconds = videoLengthAtRate - player.time!.seconds
|
||||
|
||||
if remainingSeconds < 60 {
|
||||
return "less than a minute"
|
||||
@ -94,6 +101,29 @@ struct PlaybackBar: View {
|
||||
return "ends at \(timeFinishAtString)"
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var restoreLastSkippedSegmentButton: some View {
|
||||
HStack(spacing: 4) {
|
||||
Button {
|
||||
|
Loading…
Reference in New Issue
Block a user