Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: subtitle syncing issues #112

Merged
merged 5 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 74 additions & 20 deletions Course/Course/Presentation/Video/SubtitlesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ public struct SubtitlesView: View {
@State var pause: Bool = false
@State var languages: [SubtitleUrl]

@State private var isAnimating = false
@State private var syncBlock: (() -> Void)?

@State private var autoScrollPublisher = PassthroughSubject<Void, Never>()

private enum Constants {
static let autoScrollInterval: TimeInterval = 3.0
static let animationDuration: TimeInterval = 0.3
static let animationSkipInterval: TimeInterval = animationDuration + 0.05
}

public init(languages: [SubtitleUrl],
currentTime: Binding<Double>,
viewModel: VideoPlayerViewModel,
Expand Down Expand Up @@ -76,39 +87,82 @@ public struct SubtitlesView: View {
.foregroundColor(subtitle.fromTo.contains(Date(milliseconds: currentTime))
? Theme.Colors.accentButtonColor
: Theme.Colors.textPrimary)

.onChange(of: currentTime, perform: { _ in
if subtitle.fromTo.contains(Date(milliseconds: currentTime)) {
if id != subtitle.id {
withAnimation {
if !pause {
scroll.scrollTo(subtitle.id, anchor: .top)
}
}
}
self.id = subtitle.id
}
})
})
}.id(subtitle.id)
}
}
}
}.simultaneousGesture(
}
.simultaneousGesture(
DragGesture().onChanged({ _ in
pauseScrolling()
}))
pause = true
autoScrollPublisher.send()
})
)
.onChange(of: currentTime) { _ in
refreshID()
}
.onChange(of: viewModel.isPlaying) { isPlaying in
if isPlaying {
scrollTo(id, in: scroll)
}
}
.onChange(of: id) { newID in
scrollTo(newID, in: scroll)
}
.onReceive(
autoScrollPublisher.debounce(
for: .seconds(Constants.autoScrollInterval),
scheduler: DispatchQueue.main
),
perform: { _ in
if pause {
refreshID()
pause = false
}
}
)
}
}.padding(.horizontal, 24)
.padding(.top, 16)
.padding(.bottom, isHorizontal ? 100 : 16)
}
}

private func pauseScrolling() {
pause = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.pause = false
private func refreshID() {
if let subtitle = viewModel.findSubtitle(at: Date(milliseconds: currentTime)) {
id = subtitle.id
}
}

private func scrollTo(_ viewID: Int, in scrollView: ScrollViewProxy) {
if !pause {
let scrollBlock = {
if viewModel.isPlaying {
isAnimating = true

withAnimation(.linear(duration: Constants.animationDuration)) {
scrollView.scrollTo(viewID, anchor: .top)
}

DispatchQueue.main.asyncAfter(deadline: .now() + Constants.animationSkipInterval) {
isAnimating = false

if let nextBlock = syncBlock {
syncBlock = nil
nextBlock()
}
}
} else {
scrollView.scrollTo(viewID, anchor: .top)
}
}

if isAnimating {
syncBlock = scrollBlock
} else {
scrollBlock()
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions Course/Course/Presentation/Video/VideoPlayerViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import _AVKit_SwiftUI
import Combine

public class VideoPlayerViewModel: ObservableObject {
@Published private(set) var isPlaying: Bool = false
@Published var pause: Bool = false
@Published var currentTime: Double = 0
@Published var isLoading: Bool = true
Expand Down Expand Up @@ -100,6 +101,8 @@ public class VideoPlayerViewModel: ObservableObject {

playerHolder.getRatePublisher()
.sink {[weak self] rate in
self?.isPlaying = rate != 0

guard self?.isLoading == false else { return }
self?.trackVideoSpeedChange(rate: rate)
}
Expand Down
Loading