Skip to content

Commit

Permalink
implement scrubbing
Browse files Browse the repository at this point in the history
  • Loading branch information
Lee Jun Kit committed Jun 3, 2021
1 parent 81c4320 commit b315146
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 8 deletions.
10 changes: 10 additions & 0 deletions Spottie/Backend/SpotifyAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,14 @@ extension SpotifyAPI {
req.httpMethod = "POST"
return client.run(req).print().map(\.value).eraseToAnyPublisher()
}

static func seek(posMs: Int) -> AnyPublisher<Nothing?, Error> {
let queryItems = [URLQueryItem(name: "pos", value: "\(posMs)")]
var urlComponents = URLComponents(url: base.appendingPathComponent("/player/seek"), resolvingAgainstBaseURL: false)!
urlComponents.queryItems = queryItems

var req = URLRequest(url: urlComponents.url!)
req.httpMethod = "POST"
return client.run(req).print().map(\.value).eraseToAnyPublisher()
}
}
1 change: 1 addition & 0 deletions Spottie/ViewModels/FakePlayerViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ final class FakePlayerViewModel: PlayerStateProtocol {
func previousTrack() {}
func nextTrack() {}
func togglePlayPause() {}
func seek(toPercent: Double) {}
}
1 change: 1 addition & 0 deletions Spottie/ViewModels/PlayerStateProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ protocol PlayerStateProtocol: ObservableObject {
func togglePlayPause() -> Void
func nextTrack() -> Void
func previousTrack() -> Void
func seek(toPercent: Double) -> Void
}
13 changes: 12 additions & 1 deletion Spottie/ViewModels/PlayerViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class PlayerViewModel: PlayerStateProtocol {
case let .playbackResumed(playbackResumedEvent):
self.isPlaying = true
self.progressMs = playbackResumedEvent.trackTime
case let .trackSeeked(trackSeekedEvent):
self.progressMs = trackSeekedEvent.trackTime
case let .trackChanged(trackChangedEvent):
self.progressMs = 0
self.isPlaying = true
Expand Down Expand Up @@ -93,6 +95,15 @@ class PlayerViewModel: PlayerStateProtocol {
}

func previousTrack() {
SpotifyAPI.previousTrack().sink { _ in } receiveValue: { _ in }.store(in: &cancellables)
SpotifyAPI.previousTrack().sink { _ in
// manually reset the progress to 0
self.progressMs = 0
} receiveValue: { _ in }.store(in: &cancellables)
}

func seek(toPercent: Double) {
// calculate posMs
let posMs = Int(Double(self.durationMs) * toPercent)
SpotifyAPI.seek(posMs: posMs).sink { _ in } receiveValue: { _ in }.store(in: &cancellables)
}
}
2 changes: 1 addition & 1 deletion Spottie/Views/Components/PlayerControls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct PlayerControls<M: PlayerStateProtocol>: View {
)
RepeatButton()
}
TrackProgressSlider(viewModel: TrackProgressSlider.ViewModel(isPlaying: viewModel.isPlaying, progressMs: viewModel.progressMs, durationMs: viewModel.durationMs))
TrackProgressSlider(viewModel: TrackProgressSlider.ViewModel(isPlaying: viewModel.isPlaying, progressMs: viewModel.progressMs, durationMs: viewModel.durationMs, onScrubToNewProgressPercent: viewModel.seek))
.padding(.leading)
.padding(.trailing)
}
Expand Down
31 changes: 25 additions & 6 deletions Spottie/Views/Components/TrackProgressSlider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ struct TrackProgressSlider: View {

var prettyProgress: String {
get {
return TrackProgressSlider.DurationFormatter.shared().string(from: Double(self.viewModel.progressMs) / 1000.0)!
let formatter = TrackProgressSlider.DurationFormatter.shared()
if (self.viewModel.isScrubbing) {
let scrubbedProgress = self.viewModel.progressPercent * Double(self.viewModel.durationMs)
return formatter.string(from: scrubbedProgress / 1000.0)!
} else {
return formatter.string(from: Double(self.viewModel.progressMs) / 1000.0)!
}
}
}

Expand All @@ -27,10 +33,16 @@ struct TrackProgressSlider: View {
var body: some View {
Slider(
value: $viewModel.progressPercent,
onEditingChanged: { editing in
if (!editing) {
viewModel.onScrubToNewProgressPercent(viewModel.progressPercent)
}
viewModel.isScrubbing = editing
},
minimumValueLabel: Text(prettyProgress).foregroundColor(.secondary),
maximumValueLabel: Text(prettyDuration).foregroundColor(.secondary)
) {
Text("")
EmptyView()
}
.onReceive(timer) { _ in
viewModel.updateProgress()
Expand Down Expand Up @@ -58,12 +70,15 @@ extension TrackProgressSlider {
@Published var progressMs: Int
@Published var durationMs: Int
@Published var progressPercent: Double

init(isPlaying: Bool, progressMs: Int, durationMs: Int) {
var isScrubbing = false
var onScrubToNewProgressPercent: (_ progressPercent: Double) -> Void

init(isPlaying: Bool, progressMs: Int, durationMs: Int, onScrubToNewProgressPercent: @escaping (_ progressPercent: Double) -> Void) {
self.isPlaying = isPlaying
self.progressMs = progressMs
self.durationMs = durationMs
self.progressPercent = Double(progressMs) / Double(durationMs)
self.onScrubToNewProgressPercent = onScrubToNewProgressPercent
}

func calculateProgressPercent() {
Expand All @@ -74,15 +89,19 @@ extension TrackProgressSlider {
if (self.isPlaying) {
if (self.progressMs < self.durationMs) {
self.progressMs += 1000
self.calculateProgressPercent()
if (!self.isScrubbing) {
self.calculateProgressPercent()
}
}
}
}
}
}

struct TrackProgressSlider_Previews: PreviewProvider {
static let viewModel = TrackProgressSlider.ViewModel(isPlaying: true, progressMs: 20000, durationMs: 120000)
static let viewModel = TrackProgressSlider.ViewModel(isPlaying: true, progressMs: 20000, durationMs: 120000) { progressPercent in

}
static var previews: some View {
TrackProgressSlider(viewModel: viewModel)
}
Expand Down

0 comments on commit b315146

Please sign in to comment.