1
0
mirror of https://github.com/yattee/yattee.git synced 2024-12-13 22:00:31 +05:30

Minor player controls improvements (fix #94)

This commit is contained in:
Arkadiusz Fal 2022-05-28 00:59:35 +02:00
parent 45d591537c
commit e3cf77e928
5 changed files with 109 additions and 59 deletions

View File

@ -241,7 +241,11 @@ final class MPVBackend: PlayerBackend {
client?.setDoubleAsync("speed", Double(rate)) client?.setDoubleAsync("speed", Double(rate))
} }
func closeItem() {} func closeItem() {
handleEOF = false
client?.pause()
client?.stop()
}
func enterFullScreen() {} func enterFullScreen() {}

View File

@ -371,7 +371,11 @@ final class PlayerModel: ObservableObject {
} }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
self?.upgradeToStream(stream, force: true) guard let self = self else {
return
}
self.upgradeToStream(stream, force: true)
self.setNeedsDrawing(self.presentingPlayer)
} }
} }
@ -448,6 +452,10 @@ final class PlayerModel: ObservableObject {
logger.info("exiting fullscreen") logger.info("exiting fullscreen")
if controls.playingFullscreen {
toggleFullscreen(true)
}
backend.exitFullScreen() backend.exitFullScreen()
} }
#endif #endif

View File

@ -53,18 +53,21 @@ struct PlayerControls: View {
Spacer() Spacer()
timeline Group {
.offset(y: 10) timeline
.zIndex(1) .offset(y: 10)
.zIndex(1)
bottomBar bottomBar
#if os(macOS) #if os(macOS)
.background(VisualEffectBlur(material: .hudWindow)) .background(VisualEffectBlur(material: .hudWindow))
#elseif os(iOS) #elseif os(iOS)
.background(VisualEffectBlur(blurStyle: .systemThinMaterial)) .background(VisualEffectBlur(blurStyle: .systemThinMaterial))
#endif #endif
.mask(RoundedRectangle(cornerRadius: 3)) .mask(RoundedRectangle(cornerRadius: 3))
}
.padding(.horizontal, 16)
} }
} }
.opacity(model.presentingControls ? 1 : 0) .opacity(model.presentingControls ? 1 : 0)
@ -104,9 +107,6 @@ struct PlayerControls: View {
var statusBar: some View { var statusBar: some View {
HStack(spacing: 4) { HStack(spacing: 4) {
#if os(iOS)
hidePlayerButton
#endif
Text(playbackStatus) Text(playbackStatus)
Text("") Text("")
@ -129,11 +129,10 @@ struct PlayerControls: View {
} }
private var hidePlayerButton: some View { private var hidePlayerButton: some View {
Button { button("Hide", systemImage: "chevron.down") {
player.hide() player.hide()
} label: {
Image(systemName: "chevron.down.circle.fill")
} }
#if !os(tvOS) #if !os(tvOS)
.keyboardShortcut(.cancelAction) .keyboardShortcut(.cancelAction)
#endif #endif
@ -170,14 +169,18 @@ struct PlayerControls: View {
} }
var buttonsBar: some View { var buttonsBar: some View {
HStack { HStack(spacing: 20) {
#if !os(tvOS) #if !os(tvOS)
hidePlayerButton
fullscreenButton fullscreenButton
#if os(iOS) #if os(iOS)
pipButton pipButton
#endif #endif
rateButton rateButton
closeVideoButton
Spacer() Spacer()
#endif #endif
// button("Music Mode", systemImage: "music.note") // button("Music Mode", systemImage: "music.note")
@ -225,6 +228,19 @@ struct PlayerControls: View {
#endif #endif
} }
private var closeVideoButton: some View {
button("Close", systemImage: "xmark") {
player.pause()
player.hide()
player.closePiP()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
player.closeCurrentItem()
}
}
}
var ratePicker: some View { var ratePicker: some View {
Picker("Rate", selection: rateBinding) { Picker("Rate", selection: rateBinding) {
ForEach(PlayerModel.availableRates, id: \.self) { rate in ForEach(PlayerModel.availableRates, id: \.self) { rate in
@ -244,6 +260,8 @@ struct PlayerControls: View {
player.avPlayerBackend.switchToMPVOnPipClose = true player.avPlayerBackend.switchToMPVOnPipClose = true
} }
player.exitFullScreen()
if player.activeBackend != PlayerBackendType.appleAVPlayer { if player.activeBackend != PlayerBackendType.appleAVPlayer {
player.saveTime { player.saveTime {
player.changeActiveBackend(from: .mpv, to: .appleAVPlayer) player.changeActiveBackend(from: .mpv, to: .appleAVPlayer)
@ -261,7 +279,7 @@ struct PlayerControls: View {
var mediumButtonsBar: some View { var mediumButtonsBar: some View {
HStack { HStack {
#if !os(tvOS) #if !os(tvOS)
button("Seek Backward", systemImage: "gobackward.10", size: 50, cornerRadius: 10) { button("Seek Backward", systemImage: "gobackward.10", size: 30, cornerRadius: 5) {
player.backend.seek(relative: .secondsInDefaultTimescale(-10)) player.backend.seek(relative: .secondsInDefaultTimescale(-10))
} }
@ -279,8 +297,7 @@ struct PlayerControls: View {
button( button(
model.isPlaying ? "Pause" : "Play", model.isPlaying ? "Pause" : "Play",
systemImage: model.isPlaying ? "pause.fill" : "play.fill", systemImage: model.isPlaying ? "pause.fill" : "play.fill",
size: 50, size: 30, cornerRadius: 5
cornerRadius: 10
) { ) {
player.backend.togglePlay() player.backend.togglePlay()
} }
@ -295,7 +312,7 @@ struct PlayerControls: View {
Spacer() Spacer()
#if !os(tvOS) #if !os(tvOS)
button("Seek Forward", systemImage: "goforward.10", size: 50, cornerRadius: 10) { button("Seek Forward", systemImage: "goforward.10", size: 30, cornerRadius: 5) {
player.backend.seek(relative: .secondsInDefaultTimescale(10)) player.backend.seek(relative: .secondsInDefaultTimescale(10))
} }
#if os(tvOS) #if os(tvOS)
@ -306,7 +323,7 @@ struct PlayerControls: View {
#endif #endif
#endif #endif
} }
.font(.system(size: 30)) .font(.system(size: 20))
.padding(.horizontal, 4) .padding(.horizontal, 4)
} }
@ -361,6 +378,25 @@ struct PlayerControls: View {
struct PlayerControls_Previews: PreviewProvider { struct PlayerControls_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
PlayerControls(player: PlayerModel()) let model = PlayerControlsModel()
model.presentingControls = true
model.currentTime = .secondsInDefaultTimescale(0)
model.duration = .secondsInDefaultTimescale(120)
let view = ZStack {
Color.red
PlayerControls(player: PlayerModel())
.injectFixtureEnvironmentObjects()
.environmentObject(model)
}
return Group {
if #available(iOS 15.0, *) {
view.previewInterfaceOrientation(.landscapeLeft)
} else {
view
}
}
} }
} }

View File

@ -10,7 +10,7 @@ struct TimelineView: View {
@State private var draggedFrom: Double = 0 @State private var draggedFrom: Double = 0
private var start: Double = 0.0 private var start: Double = 0.0
private var height = 10.0 private var height = 8.0
var cornerRadius: Double var cornerRadius: Double
var thumbTooltipWidth: Double = 100 var thumbTooltipWidth: Double = 100
@ -26,26 +26,26 @@ struct TimelineView: View {
var body: some View { var body: some View {
ZStack(alignment: .leading) { ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: cornerRadius) Group {
.foregroundColor(.blue) RoundedRectangle(cornerRadius: cornerRadius)
.frame(maxHeight: height) .foregroundColor(.blue)
.frame(maxHeight: height)
RoundedRectangle(cornerRadius: cornerRadius) RoundedRectangle(cornerRadius: cornerRadius)
.fill( .fill(Color.green)
Color.green .frame(maxHeight: height)
) .frame(width: current * oneUnitWidth)
.frame(maxHeight: height)
.frame(width: current * oneUnitWidth)
segmentsLayers segmentsLayers
}
.mask(RoundedRectangle(cornerRadius: 3))
Circle() Circle()
.strokeBorder(.gray, lineWidth: 1) .strokeBorder(.gray, lineWidth: 1)
.background(Circle().fill(dragging ? .gray : .white)) .background(Circle().fill(dragging ? .gray : .white))
.offset(x: thumbOffset) .offset(x: thumbOffset)
.foregroundColor(.red.opacity(0.6)) .foregroundColor(.red.opacity(0.6))
.frame(maxHeight: height * 4)
.frame(maxHeight: height * 2)
#if !os(tvOS) #if !os(tvOS)
.gesture( .gesture(
@ -114,7 +114,7 @@ struct TimelineView: View {
var projectedValue: Double { var projectedValue: Double {
let change = (dragOffset / size.width) * units let change = (dragOffset / size.width) * units
let projected = draggedFrom + change let projected = draggedFrom + change
return projected.isFinite ? projected : start return projected.isFinite ? (duration - projected < (0.03 * duration) ? duration : projected) : start
} }
var thumbOffset: Double { var thumbOffset: Double {
@ -192,6 +192,7 @@ struct TimelineView_Previews: PreviewProvider {
TimelineView(duration: .constant(100), current: .constant(90)) TimelineView(duration: .constant(100), current: .constant(90))
TimelineView(duration: .constant(100), current: .constant(100)) TimelineView(duration: .constant(100), current: .constant(100))
} }
.environmentObject(PlayerModel())
.padding() .padding()
} }
} }

View File

@ -138,17 +138,7 @@ struct VideoPlayerView: View {
hoveringPlayer = hovering hoveringPlayer = hovering
hovering ? playerControls.show() : playerControls.hide() hovering ? playerControls.show() : playerControls.hide()
} }
#if os(iOS) #if os(macOS)
.onSwipeGesture(
up: {
withAnimation {
fullScreenDetails = true
}
},
down: { player.hide() }
)
#elseif os(macOS)
.onAppear(perform: { .onAppear(perform: {
NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) { NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) {
if hoveringPlayer { if hoveringPlayer {
@ -269,20 +259,31 @@ struct VideoPlayerView: View {
} }
func playerPlaceholder(geometry: GeometryProxy) -> some View { func playerPlaceholder(geometry: GeometryProxy) -> some View {
HStack { ZStack(alignment: .topLeading) {
Spacer() HStack {
VStack {
Spacer() Spacer()
VStack(spacing: 10) { VStack {
#if !os(tvOS) Spacer()
Image(systemName: "ticket") VStack(spacing: 10) {
.font(.system(size: 120)) #if !os(tvOS)
#endif Image(systemName: "ticket")
.font(.system(size: 120))
#endif
}
Spacer()
} }
.foregroundColor(.gray)
Spacer() Spacer()
} }
Button {
player.hide()
} label: {
Image(systemName: "xmark")
.font(.system(size: 40))
}
.padding(10)
.foregroundColor(.gray) .foregroundColor(.gray)
Spacer()
} }
.contentShape(Rectangle()) .contentShape(Rectangle())
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: geometry.size.width / Self.defaultAspectRatio) .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: geometry.size.width / Self.defaultAspectRatio)