mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-26 03:20:50 +02:00
Add scrubbing
This commit is contained in:
@@ -121,8 +121,9 @@ class VoiceMessageAudioPlayer: NSObject {
|
||||
audioPlayer?.seek(to: .zero)
|
||||
}
|
||||
|
||||
func seekToTime(_ time: TimeInterval) {
|
||||
audioPlayer?.seek(to: CMTime(seconds: time, preferredTimescale: 60000))
|
||||
func seekToTime(_ time: TimeInterval, completionHandler:@escaping (Bool) -> Void = { _ in }) {
|
||||
isStopped = false
|
||||
audioPlayer?.seek(to: CMTime(seconds: time, preferredTimescale: 60000), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
func registerDelegate(_ delegate: VoiceMessageAudioPlayerDelegate) {
|
||||
|
||||
@@ -48,6 +48,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
private var audioSamples: [Float] = []
|
||||
private var isInLockedMode: Bool = false
|
||||
private var notifiedRemainingTime = false
|
||||
private var recordDuration: TimeInterval?
|
||||
|
||||
private static let elapsedTimeFormatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
@@ -156,6 +157,28 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
}
|
||||
}
|
||||
|
||||
func voiceMessageToolbarViewRequestedFormattedTimestamp(for progress: CGFloat) -> String? {
|
||||
guard let duration = recordDuration else {
|
||||
return nil
|
||||
}
|
||||
return VoiceMessageController.elapsedTimeFormatter.string(from: Date(timeIntervalSinceReferenceDate: duration * progress))
|
||||
}
|
||||
|
||||
func voiceMessageToolbarViewDidRequestSeek(to progress: CGFloat) {
|
||||
guard let audioPlayer = audioPlayer,
|
||||
let duration = recordDuration else {
|
||||
return
|
||||
}
|
||||
|
||||
if audioPlayer.url == nil {
|
||||
audioPlayer.loadContentFromURL(temporaryFileURL)
|
||||
}
|
||||
|
||||
audioPlayer.seekToTime(duration * progress) { [weak self] _ in
|
||||
self?.updateUI()
|
||||
}
|
||||
}
|
||||
|
||||
func voiceMessageToolbarViewDidRequestSend(_ toolbarView: VoiceMessageToolbarView) {
|
||||
audioPlayer?.stop()
|
||||
audioRecorder?.stopRecording()
|
||||
@@ -213,6 +236,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
|
||||
private func finishRecording() {
|
||||
let recordDuration = audioRecorder?.currentTime
|
||||
self.recordDuration = recordDuration
|
||||
audioRecorder?.stopRecording()
|
||||
|
||||
guard isInLockedMode else {
|
||||
@@ -406,10 +430,12 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
|
||||
var details = VoiceMessageToolbarViewDetails()
|
||||
details.state = (audioRecorder?.isRecording ?? false ? (isInLockedMode ? .lockedModeRecord : .record) : (isInLockedMode ? .lockedModePlayback : .idle))
|
||||
details.elapsedTime = VoiceMessageController.elapsedTimeFormatter.string(from: Date(timeIntervalSinceReferenceDate: (audioPlayer.isPlaying ? audioPlayer.currentTime : audioPlayer.duration)))
|
||||
// Show the current time if the player is paused or playing but not when stopped.
|
||||
details.elapsedTime = VoiceMessageController.elapsedTimeFormatter.string(from: Date(timeIntervalSinceReferenceDate: (!audioPlayer.isStopped ? audioPlayer.currentTime : audioPlayer.duration)))
|
||||
details.audioSamples = audioSamples
|
||||
details.isPlaying = audioPlayer.isPlaying
|
||||
details.progress = (audioPlayer.isPlaying ? (audioPlayer.duration > 0.0 ? audioPlayer.currentTime / audioPlayer.duration : 0.0) : 0.0)
|
||||
// Set progress if the player is paused or playing but not when stopped.
|
||||
details.progress = (!audioPlayer.isStopped ? (audioPlayer.duration > 0.0 ? audioPlayer.currentTime / audioPlayer.duration : 0.0) : 0.0)
|
||||
_voiceMessageToolbarView.configureWithDetails(details)
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +97,29 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
|
||||
}
|
||||
}
|
||||
|
||||
func voiceMessagePlaybackViewRequestedFormattedTimestamp(for progress: CGFloat) -> String? {
|
||||
return VoiceMessagePlaybackController.timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: self.duration * progress))
|
||||
}
|
||||
|
||||
func voiceMessagePlaybackViewDidRequestSeek(to progress: CGFloat) {
|
||||
guard let audioPlayer = audioPlayer else {
|
||||
return
|
||||
}
|
||||
|
||||
if state == .stopped {
|
||||
state = .paused
|
||||
}
|
||||
|
||||
if audioPlayer.url == nil,
|
||||
let url = urlToLoad {
|
||||
audioPlayer.loadContentFromURL(url, displayName: attachment?.originalFileName)
|
||||
}
|
||||
|
||||
audioPlayer.seekToTime(self.duration * progress) { [weak self] _ in
|
||||
self?.updateUI()
|
||||
}
|
||||
}
|
||||
|
||||
func voiceMessagePlaybackViewDidChangeWidth() {
|
||||
loadAttachmentData()
|
||||
}
|
||||
|
||||
@@ -16,9 +16,13 @@
|
||||
|
||||
import Foundation
|
||||
import Reusable
|
||||
import UIKit
|
||||
import MatrixSDK
|
||||
|
||||
protocol VoiceMessagePlaybackViewDelegate: AnyObject {
|
||||
func voiceMessagePlaybackViewDidRequestPlaybackToggle()
|
||||
func voiceMessagePlaybackViewRequestedFormattedTimestamp(for progress: CGFloat) -> String?
|
||||
func voiceMessagePlaybackViewDidRequestSeek(to progress: CGFloat)
|
||||
func voiceMessagePlaybackViewDidChangeWidth()
|
||||
}
|
||||
|
||||
@@ -40,12 +44,19 @@ class VoiceMessagePlaybackView: UIView, NibLoadable, Themable {
|
||||
|
||||
private var _waveformView: VoiceMessageWaveformView!
|
||||
private var currentTheme: Theme?
|
||||
private var scrubProgress: CGFloat? {
|
||||
didSet {
|
||||
MXLog.debug("change progress \(scrubProgress)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@IBOutlet private var backgroundView: UIView!
|
||||
@IBOutlet private var recordingIcon: UIView!
|
||||
@IBOutlet private var playButton: UIButton!
|
||||
@IBOutlet private var elapsedTimeLabel: UILabel!
|
||||
@IBOutlet private var waveformContainerView: UIView!
|
||||
@IBOutlet private var panGestureRecognizer: UIPanGestureRecognizer!
|
||||
|
||||
weak var delegate: VoiceMessagePlaybackViewDelegate?
|
||||
|
||||
@@ -94,19 +105,17 @@ class VoiceMessagePlaybackView: UIView, NibLoadable, Themable {
|
||||
}
|
||||
|
||||
if details.loading {
|
||||
elapsedTimeLabel.text = "--:--"
|
||||
_waveformView.progress = 0
|
||||
_waveformView.samples = []
|
||||
_waveformView.alpha = 0.3
|
||||
} else {
|
||||
elapsedTimeLabel.text = details.currentTime
|
||||
_waveformView.progress = details.progress
|
||||
_waveformView.samples = details.samples
|
||||
_waveformView.alpha = 1.0
|
||||
}
|
||||
|
||||
self.details = details
|
||||
|
||||
updateElapsedTime()
|
||||
guard let theme = currentTheme else {
|
||||
return
|
||||
}
|
||||
@@ -120,6 +129,20 @@ class VoiceMessagePlaybackView: UIView, NibLoadable, Themable {
|
||||
elapsedTimeLabel.textColor = theme.colors.tertiaryContent
|
||||
}
|
||||
|
||||
private func updateElapsedTime() {
|
||||
guard let details = details,
|
||||
!details.loading else {
|
||||
elapsedTimeLabel.text = "--:--"
|
||||
return
|
||||
}
|
||||
if let scrubProgress = scrubProgress,
|
||||
let scrubTime = delegate?.voiceMessagePlaybackViewRequestedFormattedTimestamp(for: scrubProgress) {
|
||||
elapsedTimeLabel.text = scrubTime
|
||||
} else {
|
||||
elapsedTimeLabel.text = details.currentTime
|
||||
}
|
||||
}
|
||||
|
||||
func getRequiredNumberOfSamples() -> Int {
|
||||
_waveformView.setNeedsLayout()
|
||||
_waveformView.layoutIfNeeded()
|
||||
@@ -138,4 +161,31 @@ class VoiceMessagePlaybackView: UIView, NibLoadable, Themable {
|
||||
@IBAction private func onPlayButtonTap() {
|
||||
delegate?.voiceMessagePlaybackViewDidRequestPlaybackToggle()
|
||||
}
|
||||
|
||||
@IBAction private func tap(gestureRecognizer: UITapGestureRecognizer) {
|
||||
let x = gestureRecognizer.location(in: waveformContainerView).x.clamped(to: 0...waveformContainerView.bounds.width)
|
||||
let progress = x / waveformContainerView.bounds.width
|
||||
let seekPoint = progress == 1 ? 0 : progress
|
||||
delegate?.voiceMessagePlaybackViewDidRequestSeek(to: seekPoint)
|
||||
}
|
||||
|
||||
@IBAction private func pan(gestureRecognizer: UIPanGestureRecognizer) {
|
||||
switch gestureRecognizer.state {
|
||||
|
||||
case .possible, .cancelled, .failed:
|
||||
scrubProgress = nil
|
||||
case .began, .changed:
|
||||
let x = gestureRecognizer.location(in: waveformContainerView).x.clamped(to: 0...waveformContainerView.bounds.width)
|
||||
scrubProgress = x / waveformContainerView.bounds.width
|
||||
case .ended:
|
||||
let seekPoint = scrubProgress == 1 ? 0 : scrubProgress ?? 0
|
||||
MXLog.debug("progress end seekPoint --->\(seekPoint)")
|
||||
delegate?.voiceMessagePlaybackViewDidRequestSeek(to: seekPoint)
|
||||
scrubProgress = nil
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
updateElapsedTime()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19162" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19144"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -50,6 +50,11 @@
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7Fl-yZ-dZB">
|
||||
<rect key="frame" x="94" y="7" width="317" height="30"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<gestureRecognizers/>
|
||||
<connections>
|
||||
<outletCollection property="gestureRecognizers" destination="ZQp-nu-e9K" appends="YES" id="98b-lQ-pW3"/>
|
||||
<outletCollection property="gestureRecognizers" destination="WD8-Zc-Vke" appends="YES" id="6pf-Ca-Npj"/>
|
||||
</connections>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
@@ -75,15 +80,26 @@
|
||||
<connections>
|
||||
<outlet property="backgroundView" destination="LPc-i8-8UC" id="mfD-md-nTj"/>
|
||||
<outlet property="elapsedTimeLabel" destination="eAi-HM-Wvj" id="z70-aJ-O90"/>
|
||||
<outlet property="panGestureRecognizer" destination="ZQp-nu-e9K" id="h67-Te-8U3"/>
|
||||
<outlet property="playButton" destination="GL1-b8-dZK" id="5u7-CG-d99"/>
|
||||
<outlet property="recordingIcon" destination="REB-gl-h0h" id="uL1-nI-bhF"/>
|
||||
<outlet property="waveformContainerView" destination="7Fl-yZ-dZB" id="f9u-wS-jvG"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-1742.753623188406" y="-299.33035714285711"/>
|
||||
</view>
|
||||
<panGestureRecognizer minimumNumberOfTouches="1" id="ZQp-nu-e9K">
|
||||
<connections>
|
||||
<action selector="panWithGestureRecognizer:" destination="cGR-49-HWB" id="McO-Pq-HFs"/>
|
||||
</connections>
|
||||
</panGestureRecognizer>
|
||||
<tapGestureRecognizer id="WD8-Zc-Vke">
|
||||
<connections>
|
||||
<action selector="tapWithGestureRecognizer:" destination="cGR-49-HWB" id="6qM-N2-zmN"/>
|
||||
</connections>
|
||||
</tapGestureRecognizer>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="voice_message_play_button" width="12.5" height="15"/>
|
||||
<image name="voice_message_play_button" width="15.5" height="15"/>
|
||||
<image name="voice_message_record_icon" width="10" height="10"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -23,6 +23,8 @@ protocol VoiceMessageToolbarViewDelegate: AnyObject {
|
||||
func voiceMessageToolbarViewDidRequestRecordingFinish(_ toolbarView: VoiceMessageToolbarView)
|
||||
func voiceMessageToolbarViewDidRequestLockedModeRecording(_ toolbarView: VoiceMessageToolbarView)
|
||||
func voiceMessageToolbarViewDidRequestPlaybackToggle(_ toolbarView: VoiceMessageToolbarView)
|
||||
func voiceMessageToolbarViewRequestedFormattedTimestamp(for progress: CGFloat) -> String?
|
||||
func voiceMessageToolbarViewDidRequestSeek(to progress: CGFloat)
|
||||
func voiceMessageToolbarViewDidRequestSend(_ toolbarView: VoiceMessageToolbarView)
|
||||
}
|
||||
|
||||
@@ -93,6 +95,7 @@ class VoiceMessageToolbarView: PassthroughView, NibLoadable, Themable, UIGesture
|
||||
private var lockChevronToRecordButtonDistance: CGFloat = 0.0
|
||||
private var lockChevronToLockButtonDistance: CGFloat = 0.0
|
||||
private var panDirection: UISwipeGestureRecognizer.Direction?
|
||||
private var tapGesture: UITapGestureRecognizer!
|
||||
|
||||
private var details: VoiceMessageToolbarViewDetails?
|
||||
|
||||
@@ -126,6 +129,8 @@ class VoiceMessageToolbarView: PassthroughView, NibLoadable, Themable, UIGesture
|
||||
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleWaveformTap))
|
||||
playbackView.waveformView.addGestureRecognizer(tapGesture)
|
||||
tapGesture.delegate = self
|
||||
self.tapGesture = tapGesture
|
||||
|
||||
updateUIWithDetails(VoiceMessageToolbarViewDetails(), animated: false)
|
||||
}
|
||||
@@ -183,12 +188,28 @@ class VoiceMessageToolbarView: PassthroughView, NibLoadable, Themable, UIGesture
|
||||
return true
|
||||
}
|
||||
|
||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if gestureRecognizer == self.tapGesture, self.lastUIState != .lockedModeRecord {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: - VoiceMessagePlaybackViewDelegate
|
||||
|
||||
func voiceMessagePlaybackViewDidRequestPlaybackToggle() {
|
||||
delegate?.voiceMessageToolbarViewDidRequestPlaybackToggle(self)
|
||||
}
|
||||
|
||||
func voiceMessagePlaybackViewRequestedFormattedTimestamp(for progress: CGFloat) -> String? {
|
||||
return delegate?.voiceMessageToolbarViewRequestedFormattedTimestamp(for: progress)
|
||||
}
|
||||
|
||||
func voiceMessagePlaybackViewDidRequestSeek(to progress: CGFloat) {
|
||||
delegate?.voiceMessageToolbarViewDidRequestSeek(to: progress)
|
||||
}
|
||||
|
||||
|
||||
func voiceMessagePlaybackViewDidChangeWidth() {
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user