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:
parent
45d591537c
commit
e3cf77e928
@ -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() {}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user