From 8318e28ebef493453ebecb9be0304cf965df9e51 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 19 Jan 2026 19:53:40 -0700 Subject: [PATCH 1/3] style(ui): refine VideoPlayerView layout and SegmentsSection spacing --- ABPlayer/Sources/Views/Components/SegmentsSection.swift | 1 - ABPlayer/Sources/Views/VideoPlayerView.swift | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ABPlayer/Sources/Views/Components/SegmentsSection.swift b/ABPlayer/Sources/Views/Components/SegmentsSection.swift index d112a0f..4f0c12c 100644 --- a/ABPlayer/Sources/Views/Components/SegmentsSection.swift +++ b/ABPlayer/Sources/Views/Components/SegmentsSection.swift @@ -13,7 +13,6 @@ struct SegmentsSection: View { var body: some View { VStack(alignment: .leading, spacing: 8) { - HStack { // Loop Controls Group HStack { diff --git a/ABPlayer/Sources/Views/VideoPlayerView.swift b/ABPlayer/Sources/Views/VideoPlayerView.swift index 6f3bb22..fa6bc0f 100644 --- a/ABPlayer/Sources/Views/VideoPlayerView.swift +++ b/ABPlayer/Sources/Views/VideoPlayerView.swift @@ -150,8 +150,10 @@ struct VideoPlayerView: View { ) VideoControlsView(viewModel: viewModel) - + .padding(.horizontal) + Divider() + ContentPanelView(audioFile: audioFile) } } From f87d0125f247d6a82487bb7fbce6825a39dad51a Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 19 Jan 2026 22:14:30 -0700 Subject: [PATCH 2/3] feat(player): add HUD feedback, subtitle toggle, and track navigation controls --- .swift-format | 10 ++++ .../ClosedCaption.imageset/Contents.json | 12 +++++ .../ClosedCaption.imageset/closed_caption.png | Bin 0 -> 1476 bytes .../ViewModels/VideoPlayerViewModel.swift | 34 +++++++++++++ .../Views/Components/VideoControlsView.swift | 47 ++++++++++++++---- ABPlayer/Sources/Views/VideoPlayerView.swift | 35 +++++++++---- 6 files changed, 117 insertions(+), 21 deletions(-) create mode 100644 .swift-format create mode 100644 ABPlayer/Resources/Assets.xcassets/ClosedCaption.imageset/Contents.json create mode 100644 ABPlayer/Resources/Assets.xcassets/ClosedCaption.imageset/closed_caption.png diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..eb13c74 --- /dev/null +++ b/.swift-format @@ -0,0 +1,10 @@ +{ + "indentation" : { + "spaces" : 2 + }, + "lineLength" : 120, + "maximumBlankLines" : 1, + "respectsExistingLineBreaks" : true, + "tabWidth" : 2, + "version" : 1 +} diff --git a/ABPlayer/Resources/Assets.xcassets/ClosedCaption.imageset/Contents.json b/ABPlayer/Resources/Assets.xcassets/ClosedCaption.imageset/Contents.json new file mode 100644 index 0000000..e628b7b --- /dev/null +++ b/ABPlayer/Resources/Assets.xcassets/ClosedCaption.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "closed_caption.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ABPlayer/Resources/Assets.xcassets/ClosedCaption.imageset/closed_caption.png b/ABPlayer/Resources/Assets.xcassets/ClosedCaption.imageset/closed_caption.png new file mode 100644 index 0000000000000000000000000000000000000000..9112a6c05c27457d79f26bdca20dce3d03713540 GIT binary patch literal 1476 zcma)6dpOez82)}+HV;o*F4alIIpm>^30)Z0LYXHr=^{l7xs+Qnm&Qz`=8|VBBq5hW zwv*mV*9sPfv=bS&j_j}*({p0)VebcGCT$gI-X#l`dH;T*d z#m)LT>X=1rB^~JmP)l@k*|sME8Nb%!B_S_6r}YkX(N)w>i|53WiEc#7I?C>CG9aen zoUofIHq5gq2eZc7O|1zvbZ%qs*TMO$l>>c)(iRm{`xM7^YpZ7u4p?Q9l(@wY(N*ZOT!QV>O*u|-carZ8}g`2=|P}|zH7XP{0H}WhwoH_^Vz!7t3bC;-Wr4O zOO4Z#F_5(91e$A5ilX-+6d)%gBN;`E1PjCI=uEhFuMHl9x35Ty0E!WcshHw zrG3StnG*)Iy93H!4wOU9VB~(QCr0NR(X|eE42vM=aXZ^OuHYOA(9KpnK|YG99imFDBnLCh5|nij5e-*ZFY0g6lmTz`0F!dcOOKwd{yEF9>T*tZ?>K>ZNT#sJ@1R9 z$rVHhuWLM{$_aX?^lBFosmhfLw;O~e9zV>AB(zemPmp!wo78^Pri(2iE*a`;zAI#u z<5+*ixMz(3umb|QZb0TXXO_MB9bzMDibbQn-Ru!Tx{33YJb_#J&@snaKo~L!M&uUi z%5!!A=jL(;W#cgH{k69HlQewu_h1k2(d}^%n|9>$M}TOhxx@>MBz@fF7j1 zQ+=rdA>TNYF2X|kmJNGIpibW~IR{3Qm~#Rwl;TWDhF~J)Q|AT{s(7l$KGRvKC>q)* z6k7T5>;i?IIF?VFk4R^yA+SFLl_hqesC#=y`oom3W?m%Q)=$?aM@X*D=l>mya3W0< z6Z!!5h8goqAtPeGI$9GP#w$7&DvxW7+MgVYn%;6?Q{#9=STmz)=AZ#e#OEJfD z5Yq_+LS%a=F3gvE?4n*34p#11#u>_0Y6(srI-`B~BvU*&Xd9V5Trezk)j6JNU<$)! z9rE({=l2fqwk4)SGfReTQYW~MO5Ut2=Sm<3(nmP=hY;wV@ai)La^IIPmN@vvxICo} z96IBBCK~3~9Cbv2$qRq;0w4o{DxWb*ga?u@8gD1IX+RSt(}ih=J*`^O z#N#dGT{_=YrO!Sgj@bbD`T621mw=LM=p=DW=rj#{_(9fQDg0FWd)?&vCkh7*csUm9 zx3|~KbN1UR@5c)nO~InTS9)r&!n#nnMs`eP+~Uf%lRsW_UtGJ7jR8$xLcZmoj@7pU zOWeem)6ol|&5lNf6Ju_xYJT$b$z3D%-D4hS7pg7kB0a%P)xT6VK z8=ZDypgTs|!393W_}>k3gVyU#6&gUL+Ts+2EOs&GX9=UhG+yo4z1#xd#S6G?-{n&6 HL_7Ttyc?el literal 0 HcmV?d00001 diff --git a/ABPlayer/Sources/ViewModels/VideoPlayerViewModel.swift b/ABPlayer/Sources/ViewModels/VideoPlayerViewModel.swift index 69d00f5..24cc42d 100644 --- a/ABPlayer/Sources/ViewModels/VideoPlayerViewModel.swift +++ b/ABPlayer/Sources/ViewModels/VideoPlayerViewModel.swift @@ -13,6 +13,16 @@ final class VideoPlayerViewModel { var seekValue: Double = 0 var wasPlayingBeforeSeek: Bool = false + // MARK: - HUD + var hudMessage: String? + var hideHudTask: Task? + var showHudMessage: Bool { + guard let hudMessage else { + return false + } + return !hudMessage.isEmpty + } + // MARK: - Layout State var draggingWidth: Double? @@ -113,12 +123,14 @@ final class VideoPlayerViewModel { guard let manager = playerManager else { return } let targetTime = manager.currentTime - 5 manager.seek(to: targetTime) + showHUDMessage("- 5") } func seekForward() { guard let manager = playerManager else { return } let targetTime = manager.currentTime + 10 manager.seek(to: targetTime) + showHUDMessage("+ 10s") } func timeString(from value: Double) -> String { @@ -138,4 +150,26 @@ final class VideoPlayerViewModel { return String(format: "%d:%02d", minutes, seconds) } + + func showHUDMessage(_ message: String) { + hideHudTask?.cancel() + hudMessage = nil + + Task { @MainActor in + try? await Task.sleep(nanoseconds: 10_000_000) + withAnimation(.bouncy) { + hudMessage = message + } + + hideHudTask = Task { + try? await Task.sleep(nanoseconds: 2_000_000_000) + if Task.isCancelled { + return + } + withAnimation { + hudMessage = nil + } + } + } + } } diff --git a/ABPlayer/Sources/Views/Components/VideoControlsView.swift b/ABPlayer/Sources/Views/Components/VideoControlsView.swift index 8075c95..7b61d9a 100644 --- a/ABPlayer/Sources/Views/Components/VideoControlsView.swift +++ b/ABPlayer/Sources/Views/Components/VideoControlsView.swift @@ -4,19 +4,28 @@ import Observation struct VideoControlsView: View { @Bindable var viewModel: VideoPlayerViewModel @Environment(AudioPlayerManager.self) private var playerManager + @State private var showSubtitle = false var body: some View { ZStack(alignment: .center) { HStack { - // Loop Mode & Volume - HStack(spacing: 12) { + VideoTimeDisplay(isSeeking: viewModel.isSeeking, seekValue: viewModel.seekValue) + + Spacer() + + HStack { loopModeMenu + + Button { + showSubtitle.toggle() + } label: { + Image(.closedCaption).renderingMode(.template).resizable().aspectRatio(contentMode: .fit).frame(width: 24) + } + .buttonStyle(.plain) + .foregroundStyle(showSubtitle ? Color.accentColor : .primary) + VolumeControl(playerVolume: $viewModel.playerVolume) } - - Spacer() - - VideoTimeDisplay(isSeeking: viewModel.isSeeking, seekValue: viewModel.seekValue) } // Playback Controls @@ -39,11 +48,11 @@ struct VideoControlsView: View { } label: { Image( systemName: playerManager.loopMode != .none - ? "\(playerManager.loopMode.iconName).circle.fill" - : "repeat.circle" + ? "\(playerManager.loopMode.iconName)" + : "repeat" ) - .font(.title) - .foregroundStyle(playerManager.loopMode != .none ? .blue : .primary) + .font(.title2) + .foregroundStyle(playerManager.loopMode != .none ? Color.accentColor : .primary) } .buttonStyle(.plain) .help("Loop mode: \(playerManager.loopMode.displayName)") @@ -51,6 +60,15 @@ struct VideoControlsView: View { private var playbackControls: some View { HStack(spacing: 16) { + Button { + // switch to previous item + } label: { + Image(systemName: "backward.end") + .font(.title) + } + .buttonStyle(.plain) + .keyboardShortcut("f", modifiers: []) + Button { viewModel.seekBack() } label: { @@ -77,6 +95,15 @@ struct VideoControlsView: View { } .buttonStyle(.plain) .keyboardShortcut("g", modifiers: []) + + Button { + // switch to next item + } label: { + Image(systemName: "forward.end") + .font(.title) + } + .buttonStyle(.plain) + .keyboardShortcut("f", modifiers: []) } } } diff --git a/ABPlayer/Sources/Views/VideoPlayerView.swift b/ABPlayer/Sources/Views/VideoPlayerView.swift index fa6bc0f..462f9ef 100644 --- a/ABPlayer/Sources/Views/VideoPlayerView.swift +++ b/ABPlayer/Sources/Views/VideoPlayerView.swift @@ -126,20 +126,33 @@ struct VideoPlayerView: View { private var videoSection: some View { VStack(alignment: .leading, spacing: 0) { // 1. Video Player Area - Group { - if let player = playerManager.player { - NativeVideoPlayer(player: player) - } else { - ZStack { - Color.black - ProgressView() - .scaleEffect(1.5) - .tint(.white) + ZStack { + Group { + if let player = playerManager.player { + NativeVideoPlayer(player: player) + } else { + ZStack { + Color.black + ProgressView() + .scaleEffect(1.5) + .tint(.white) + } } } + .aspectRatio(16 / 9, contentMode: .fit) + .layoutPriority(1) + + if viewModel.showHudMessage { + Text(viewModel.hudMessage ?? "") + .font(.title) + .padding(.all, 16) + .background(.black.opacity(0.6)) + .cornerRadius(8) + .id(viewModel.hudMessage) + .transition(.scale.combined(with: .opacity)) + .animation(.bouncy, value: viewModel.hudMessage) + } } - .aspectRatio(16 / 9, contentMode: .fit) - .layoutPriority(1) // 2. Controls Area (Fixed height) VStack(spacing: 12) { From c3a7d3a765022f6666c3a3ac67d09a2bb87e9b2d Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 19 Jan 2026 22:15:01 -0700 Subject: [PATCH 3/3] ci(release_sh): 0.2.9-58 --- .release_state | 2 +- CHANGELOG.md | 9 +++++++++ Project.swift | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.release_state b/.release_state index 2f1a6ad..9d600a5 100644 --- a/.release_state +++ b/.release_state @@ -1 +1 @@ -2a7b09b293a17369def2bd4d479dadf1fc6bd955 +f87d0125f247d6a82487bb7fbce6825a39dad51a diff --git a/CHANGELOG.md b/CHANGELOG.md index 76ef8e6..424e659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.2.9.58] - 2026-01-19 + +### Features +- add HUD feedback, subtitle toggle, and track navigation controls + +### Chores +- refine VideoPlayerView layout and SegmentsSection spacing + + ## [0.2.9.57] - 2026-01-19 ### Bug Fixes diff --git a/Project.swift b/Project.swift index ebd545e..b8770fa 100644 --- a/Project.swift +++ b/Project.swift @@ -1,6 +1,6 @@ import ProjectDescription -let buildVersionString = "57" +let buildVersionString = "58" let shortVersionString = "0.2.9" let project = Project( name: "ABPlayer",