Add seeking support to the audio record player

This commit is contained in:
Benoit Martins 2025-12-02 16:35:36 +01:00
parent 0f8df65dff
commit 36fa752ccf
5 changed files with 69 additions and 14 deletions

View file

@ -3051,6 +3051,21 @@ class VoiceRecordPlayerManager {
}
}
func seekVoiceRecordPlayer(percent: Double) {
guard !isPlayerClosed(),
let player = voiceRecordPlayer,
player.duration > 0 else { return }
let clamped = max(0, min(percent, 100))
let ratio = clamped / 100.0
let timeMs = Int(Double(player.duration) * ratio)
print("Seek voice record to \(clamped)% (\(timeMs) ms)")
try? player.seek(timeMs: timeMs)
}
func getSpeakerSoundCard(core: Core) -> String? {
var speakerCard: String? = nil
var earpieceCard: String? = nil

View file

@ -27,9 +27,14 @@ struct RecordingMediaPlayerFragment: View {
@State private var showShareSheet: Bool = false
@State private var showPicker: Bool = false
@State private var isSeeking: Bool = false
@State private var lastValue: Double = -1.0
@State private var value: Double = 40.0
@State private var timer: Timer?
init(recording: RecordingModel) {
_recordingMediaPlayerViewModel = StateObject(wrappedValue: RecordingMediaPlayerViewModel(recording: recording))
}
@ -133,19 +138,41 @@ struct RecordingMediaPlayerFragment: View {
.frame(width: 50)
let radius = geometry.size.height * 0.5
let barWidth = geometry.size.width - 120
ZStack(alignment: .leading) {
Rectangle()
.foregroundColor(Color.orangeMain100)
.frame(width: (geometry.size.width - 120), height: 5)
.frame(width: barWidth, height: 5)
.clipShape(RoundedRectangle(cornerRadius: radius))
Rectangle()
.foregroundColor(Color.orangeMain500)
.frame(width: self.value * (geometry.size.width - 120) / 100, height: 5)
.animation(self.value > 0 ? .linear(duration: 0.1) : nil, value: self.value)
.frame(width: (self.value / 100) * barWidth, height: isSeeking ? 10 : 5)
.clipShape(RoundedRectangle(cornerRadius: radius))
}
.clipShape(RoundedRectangle(cornerRadius: radius))
.frame(width: barWidth, height: 20)
.contentShape(Rectangle())
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { gesture in
isSeeking = true
let x = max(0, min(barWidth, gesture.location.x))
let percent = x / barWidth * 100
self.value = percent
}
.onEnded { gesture in
let x = max(0, min(barWidth, gesture.location.x))
let percent = x / barWidth * 100
self.value = percent
isSeeking = false
recordingMediaPlayerViewModel.seekTo(percent: percent)
lastValue = percent
}
)
Text(recordingMediaPlayerViewModel.recording.formattedDuration)
.default_text_style_white_600(styleSize: 18)
@ -153,6 +180,7 @@ struct RecordingMediaPlayerFragment: View {
.lineLimit(1)
.frame(width: 70)
}
.padding(.bottom, 20)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black)
@ -184,25 +212,29 @@ struct RecordingMediaPlayerFragment: View {
private func playProgress() {
timer?.invalidate()
var lastValue = -1.0
lastValue = -1.0
value = recordingMediaPlayerViewModel.getPositionVoiceRecordPlayer()
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
let current = recordingMediaPlayerViewModel.getPositionVoiceRecordPlayer()
if isSeeking {
return
}
if !recordingMediaPlayerViewModel.isPlaying {
self.value = current
lastValue = current
return
}
if current > lastValue {
self.value = current
lastValue = current
return
}
recordingMediaPlayerViewModel.stopVoiceRecordPlayer()
self.timer?.invalidate()
self.value = 0

View file

@ -101,4 +101,12 @@ class RecordingMediaPlayerViewModel: ObservableObject {
}
}
}
func seekTo(percent: Double) {
coreContext.doOnCoreQueue { _ in
if self.vrpManager != nil {
self.vrpManager!.seekVoiceRecordPlayer(percent: percent)
}
}
}
}

View file

@ -87,6 +87,7 @@
D719ABC92ABC6FD700B41C10 /* CoreContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D719ABC82ABC6FD700B41C10 /* CoreContext.swift */; };
D719ABCC2ABC769C00B41C10 /* AssistantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D719ABCB2ABC769C00B41C10 /* AssistantView.swift */; };
D719ABCF2ABC779A00B41C10 /* AccountLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D719ABCE2ABC779A00B41C10 /* AccountLoginViewModel.swift */; };
D719EF892EDF4AFA00509AAB /* GeneratedGit.swift in Sources */ = {isa = PBXBuildFile; fileRef = D719EF882EDF4AFA00509AAB /* GeneratedGit.swift */; };
D71A0E192B485ADF0002C6CD /* ViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71A0E182B485ADF0002C6CD /* ViewExtension.swift */; };
D71FCA812AE14CFC00D2E43E /* ContactsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71FCA802AE14CFC00D2E43E /* ContactsListFragment.swift */; };
D71FCA832AE14D6E00D2E43E /* ContactFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71FCA822AE14D6E00D2E43E /* ContactFragment.swift */; };
@ -180,7 +181,6 @@
D7D1698C2AE66FA500109A5C /* MagicSearchSingleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D1698B2AE66FA500109A5C /* MagicSearchSingleton.swift */; };
D7D1F5262EDD91B30034EEB0 /* RecordingMediaPlayerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D1F5252EDD91B10034EEB0 /* RecordingMediaPlayerFragment.swift */; };
D7D1F5282EDD939E0034EEB0 /* RecordingMediaPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D1F5272EDD939D0034EEB0 /* RecordingMediaPlayerViewModel.swift */; };
D7D1F5452EDDBBA70034EEB0 /* GeneratedGit.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D1F5442EDDBBA70034EEB0 /* GeneratedGit.swift */; };
D7D24D132AC1B4E800C6F35B /* NotoSans-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D7D24D0D2AC1B4E800C6F35B /* NotoSans-Medium.ttf */; };
D7D24D142AC1B4E800C6F35B /* NotoSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D7D24D0E2AC1B4E800C6F35B /* NotoSans-Regular.ttf */; };
D7D24D152AC1B4E800C6F35B /* NotoSans-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D7D24D0F2AC1B4E800C6F35B /* NotoSans-Light.ttf */; };
@ -324,6 +324,7 @@
D719ABC82ABC6FD700B41C10 /* CoreContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreContext.swift; sourceTree = "<group>"; };
D719ABCB2ABC769C00B41C10 /* AssistantView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssistantView.swift; sourceTree = "<group>"; };
D719ABCE2ABC779A00B41C10 /* AccountLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountLoginViewModel.swift; sourceTree = "<group>"; };
D719EF882EDF4AFA00509AAB /* GeneratedGit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratedGit.swift; sourceTree = "<group>"; };
D71A0E182B485ADF0002C6CD /* ViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtension.swift; sourceTree = "<group>"; };
D71C266F2E819A0D001A7F92 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = Localizable/sk.lproj/Localizable.strings; sourceTree = "<group>"; };
D71FCA802AE14CFC00D2E43E /* ContactsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsListFragment.swift; sourceTree = "<group>"; };
@ -419,7 +420,6 @@
D7D1698B2AE66FA500109A5C /* MagicSearchSingleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagicSearchSingleton.swift; sourceTree = "<group>"; };
D7D1F5252EDD91B10034EEB0 /* RecordingMediaPlayerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingMediaPlayerFragment.swift; sourceTree = "<group>"; };
D7D1F5272EDD939D0034EEB0 /* RecordingMediaPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingMediaPlayerViewModel.swift; sourceTree = "<group>"; };
D7D1F5442EDDBBA70034EEB0 /* GeneratedGit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratedGit.swift; sourceTree = "<group>"; };
D7D24D0D2AC1B4E800C6F35B /* NotoSans-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSans-Medium.ttf"; sourceTree = "<group>"; };
D7D24D0E2AC1B4E800C6F35B /* NotoSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSans-Regular.ttf"; sourceTree = "<group>"; };
D7D24D0F2AC1B4E800C6F35B /* NotoSans-Light.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSans-Light.ttf"; sourceTree = "<group>"; };
@ -674,7 +674,7 @@
D719ABBD2ABC67BF00B41C10 /* Preview Content */,
D7D24D0C2AC1B4C700C6F35B /* Fonts */,
D7ADF6012AFE5C7C00212231 /* Ressources */,
D7D1F5442EDDBBA70034EEB0 /* GeneratedGit.swift */,
D719EF882EDF4AFA00509AAB /* GeneratedGit.swift */,
);
path = Linphone;
sourceTree = "<group>";
@ -1371,6 +1371,7 @@
D7C2DA1D2CA44DE400A2441B /* EventModel.swift in Sources */,
D719ABC92ABC6FD700B41C10 /* CoreContext.swift in Sources */,
D70A26F22B7F5D95006CC8FC /* ConversationFragment.swift in Sources */,
D719EF892EDF4AFA00509AAB /* GeneratedGit.swift in Sources */,
D7D1F5282EDD939E0034EEB0 /* RecordingMediaPlayerViewModel.swift in Sources */,
D7DC096F2CFA1D7600A6D47C /* AccountProfileFragment.swift in Sources */,
D717A10E2CEB772300849D92 /* ShareSheetController.swift in Sources */,
@ -1419,7 +1420,6 @@
C628172E2C1C3A3600DBA646 /* AccountExtension.swift in Sources */,
66C491FF2B24D4AC00CEA16D /* FileUtils.swift in Sources */,
C62817322C1C400A00DBA646 /* StringExtension.swift in Sources */,
D7D1F5452EDDBBA70034EEB0 /* GeneratedGit.swift in Sources */,
D74C9D012ACB098C0021626A /* PermissionManager.swift in Sources */,
D7B99E992B29B39000BE7BF2 /* CallViewModel.swift in Sources */,
D7702EF22AC7205000557C00 /* WelcomeView.swift in Sources */,

View file

@ -124,7 +124,7 @@
"location" : "https://gitlab.linphone.org/BC/public/linphone-sdk-swift-ios.git",
"state" : {
"branch" : "alpha",
"revision" : "43ee1a062ef73808e27afe3c5341a27c1b82aae7"
"revision" : "4403eb00e8843352d9d4ca4e696c2824e53dd178"
}
},
{