Working LIVE indicator

This commit is contained in:
Hariharan Mudaliar 2025-11-07 15:14:14 +05:30
commit fa1bd97d84
5 changed files with 255 additions and 58 deletions

View file

@ -89,49 +89,56 @@ struct MinimalisticMusicPlayerView: View {
private var progressBar: some View {
TimelineView(.animation(minimumInterval: musicManager.playbackRate > 0 ? 0.1 : nil)) { timeline in
let currentElapsed = currentSliderValue(timeline.date)
HStack(spacing: 8) {
// Elapsed time - left
Text(formatTime(dragging ? sliderValue : currentElapsed))
.font(.system(size: 11, weight: .medium, design: .monospaced))
.foregroundColor(.white.opacity(0.7))
.frame(width: 42, alignment: .leading)
// Progress bar
GeometryReader { geometry in
ZStack(alignment: .leading) {
// Background track
RoundedRectangle(cornerRadius: 3)
.fill(Color.white.opacity(0.2))
.frame(height: 6)
// Filled portion
RoundedRectangle(cornerRadius: 3)
.fill(sliderColor)
.frame(width: max(0, geometry.size.width * (currentSliderValue(timeline.date) / max(musicManager.songDuration, 1))), height: 6)
}
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
dragging = true
let newValue = min(max(0, Double(value.location.x / geometry.size.width) * musicManager.songDuration), musicManager.songDuration)
sliderValue = newValue
lastDragged = Date()
}
.onEnded { _ in
musicManager.seek(to: sliderValue)
dragging = false
}
)
if musicManager.isLiveStream {
HStack(spacing: 8) {
Spacer()
.frame(width: 42)
LiveStreamProgressIndicator(tint: sliderColor)
.frame(maxWidth: .infinity, minHeight: 6, maxHeight: 6)
Spacer()
.frame(width: 48)
}
.allowsHitTesting(false)
} else {
let currentElapsed = currentSliderValue(timeline.date)
HStack(spacing: 8) {
Text(formatTime(dragging ? sliderValue : currentElapsed))
.font(.system(size: 11, weight: .medium, design: .monospaced))
.foregroundColor(.white.opacity(0.7))
.frame(width: 42, alignment: .leading)
GeometryReader { geometry in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 3)
.fill(Color.white.opacity(0.2))
.frame(height: 6)
RoundedRectangle(cornerRadius: 3)
.fill(sliderColor)
.frame(width: max(0, geometry.size.width * (currentSliderValue(timeline.date) / max(musicManager.songDuration, 1))), height: 6)
}
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
dragging = true
let newValue = min(max(0, Double(value.location.x / geometry.size.width) * musicManager.songDuration), musicManager.songDuration)
sliderValue = newValue
lastDragged = Date()
}
.onEnded { _ in
musicManager.seek(to: sliderValue)
dragging = false
}
)
}
.frame(height: 6)
Text("-\(formatTime(musicManager.songDuration - (dragging ? sliderValue : currentElapsed)))")
.font(.system(size: 11, weight: .medium, design: .monospaced))
.foregroundColor(.white.opacity(0.7))
.frame(width: 48, alignment: .trailing)
}
.frame(height: 6)
// Time remaining - right
Text("-\(formatTime(musicManager.songDuration - (dragging ? sliderValue : currentElapsed)))")
.font(.system(size: 11, weight: .medium, design: .monospaced))
.foregroundColor(.white.opacity(0.7))
.frame(width: 48, alignment: .trailing)
}
}
.onAppear {

View file

@ -170,8 +170,10 @@ struct MusicControlsView: View {
timestampDate: musicManager.timestampDate,
elapsedTime: musicManager.elapsedTime,
playbackRate: musicManager.playbackRate,
isPlaying: musicManager.isPlaying
isPlaying: musicManager.isPlaying,
isLiveStream: musicManager.isLiveStream
) { newValue in
guard !musicManager.isLiveStream else { return }
MusicManager.shared.seek(to: newValue)
}
.padding(.top, 5)
@ -296,38 +298,71 @@ struct MusicSliderView: View {
let elapsedTime: Double
let playbackRate: Double
let isPlaying: Bool
let isLiveStream: Bool
var onValueChange: (Double) -> Void
var body: some View {
VStack {
sliderContent
.frame(height: 10, alignment: .center)
if !isLiveStream {
HStack {
Text(timeString(from: sliderValue))
Spacer()
Text(timeString(from: duration))
}
.fontWeight(.medium)
.foregroundColor(timeLabelColor)
.font(.caption)
}
}
.onChange(of: currentDate) { newDate in
guard !isLiveStream else { return }
guard !dragging, timestampDate.timeIntervalSince(lastDragged) > -1 else { return }
sliderValue = MusicManager.shared.estimatedPlaybackPosition(at: newDate)
}
.onChange(of: isLiveStream) { isLive in
if isLive {
sliderValue = 0
}
}
}
@ViewBuilder
private var sliderContent: some View {
if isLiveStream {
LiveStreamProgressIndicator(tint: sliderTint)
.frame(maxWidth: .infinity)
} else {
CustomSlider(
value: $sliderValue,
range: 0 ... duration,
color: Defaults[.sliderColor] == SliderColorEnum.albumArt ? Color(
nsColor: color
).ensureMinimumBrightness(factor: 0.8) : Defaults[.sliderColor] == SliderColorEnum.accent ? .accentColor : .white,
color: sliderTint,
dragging: $dragging,
lastDragged: $lastDragged,
onValueChange: onValueChange
)
.frame(height: 10, alignment: .center)
HStack {
Text(timeString(from: sliderValue))
Spacer()
Text(timeString(from: duration))
}
.fontWeight(.medium)
.foregroundColor(Defaults[.playerColorTinting] ? Color(nsColor: color)
.ensureMinimumBrightness(factor: 0.6) : .gray)
.font(.caption)
}
.onChange(of: currentDate) { newDate in
guard !dragging, timestampDate.timeIntervalSince(lastDragged) > -1 else { return }
sliderValue = MusicManager.shared.estimatedPlaybackPosition(at: newDate)
}
private var sliderTint: Color {
switch Defaults[.sliderColor] {
case .albumArt:
return Color(nsColor: color).ensureMinimumBrightness(factor: 0.8)
case .accent:
return .accentColor
case .white:
return .white
}
}
private var timeLabelColor: Color {
Defaults[.playerColorTinting]
? Color(nsColor: color).ensureMinimumBrightness(factor: 0.6)
: .gray
}
func timeString(from seconds: Double) -> String {
let totalMinutes = Int(seconds) / 60
let remainingSeconds = Int(seconds) % 60
@ -340,8 +375,10 @@ struct MusicSliderView: View {
return String(format: "%d:%02d", minutes, remainingSeconds)
}
}
}
struct CustomSlider: View {
@Binding var value: Double
var range: ClosedRange<Double>