fix: calibrate duration of recorded audio (#15543)

Signed-off-by: Brian Sztamfater <brian@status.im>
This commit is contained in:
Brian Sztamfater 2023-06-26 11:02:13 -03:00 committed by GitHub
parent 60a8b944d9
commit 6d6e6b1eff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 265 additions and 193 deletions

View File

@ -1,7 +1,9 @@
(ns quo2.components.record-audio.record-audio.--tests--.record-audio-component-spec
(:require [quo2.components.record-audio.record-audio.view :as record-audio]
[react-native.audio-toolkit :as audio]
[test-helpers.component :as h]))
[test-helpers.component :as h]
[reagent.core :as reagent]
[utils.datetime :as datetime]))
(h/describe "record audio component"
(h/before-each
@ -34,12 +36,16 @@
(.toHaveBeenCalledTimes 1))))
(h/test "record-audio on-reviewing-audio works"
(let [event (js/jest.fn)]
(let [event (js/jest.fn)
on-meter (reagent/atom nil)]
(h/render [record-audio/record-audio
{:on-reviewing-audio event
:record-audio-permission-granted true}])
(with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))
(with-redefs [audio/new-recorder (fn [_ on-meter-fn _]
(reset! on-meter on-meter-fn))
audio/start-recording (fn [_ on-start _]
(on-start)
(js/setInterval #(@on-meter) 100))
audio/get-recorder-file-path (fn [] "file-path")]
(h/fire-event
:on-start-should-set-responder
@ -48,7 +54,8 @@
:locationY 70
:timestamp 0
:identifier 0}})
(h/advance-timers-by-time 500)
(with-redefs [datetime/timestamp (fn [] (+ (.now js/Date) 1000))]
(h/advance-timers-by-time 100)
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
@ -58,15 +65,19 @@
:identifier 0}})
(h/advance-timers-by-time 250)
(-> (h/expect event)
(.toHaveBeenCalledTimes 1)))))
(.toHaveBeenCalledTimes 1))))))
(h/test "record-audio on-send works after reviewing audio"
(let [event (js/jest.fn)]
(let [event (js/jest.fn)
on-meter (reagent/atom nil)]
(h/render [record-audio/record-audio
{:on-send event
:record-audio-permission-granted true}])
(with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))
(with-redefs [audio/new-recorder (fn [_ on-meter-fn _]
(reset! on-meter on-meter-fn))
audio/start-recording (fn [_ on-start _]
(on-start)
(js/setInterval #(@on-meter) 100))
audio/get-recorder-file-path (fn [] "audio-file-path")
audio/get-player-duration (fn [] 5000)]
(h/fire-event
@ -76,7 +87,8 @@
:locationY 70
:timestamp 0
:identifier 0}})
(h/advance-timers-by-time 500)
(with-redefs [datetime/timestamp (fn [] (+ (.now js/Date) 1000))]
(h/advance-timers-by-time 100)
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
@ -104,18 +116,28 @@
(.toHaveBeenCalledTimes 1))
(-> (js/expect event)
(.toHaveBeenCalledWith {:file-path "audio-file-path"
:duration 5000})))))
:duration 5000}))))))
(h/test "record-audio on-send works after sliding to the send button"
(let [event (js/jest.fn)]
(let [event (js/jest.fn)
on-meter (reagent/atom nil)
last-now-ms (atom nil)
duration-ms (atom nil)]
(h/render [record-audio/record-audio
{:on-send event
:record-audio-permission-granted true}])
(with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))
(with-redefs [audio/new-recorder (fn [_ on-meter-fn _]
(reset! on-meter on-meter-fn))
audio/start-recording (fn [_ on-start _]
(on-start)
(js/setInterval #(@on-meter) 100))
audio/stop-recording (fn [_ on-stop _]
(on-stop))
audio/get-recorder-file-path (fn [] "audio-file-path")]
audio/get-recorder-file-path (fn [] "audio-file-path")
datetime/timestamp (fn []
(let [now-ms (.now js/Date)]
(reset! last-now-ms now-ms)
now-ms))]
(h/fire-event
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
@ -123,7 +145,13 @@
:locationY 70
:timestamp 0
:identifier 0}})
(h/advance-timers-by-time 500)
(with-redefs [datetime/timestamp (fn []
(let [now-plus-ms (+ (.now js/Date) 1000)
time-diff-ms (- now-plus-ms @last-now-ms)]
(when-not @duration-ms
(reset! duration-ms time-diff-ms))
now-plus-ms))]
(h/advance-timers-by-time 100)
(h/fire-event
:on-responder-move
(h/get-by-test-id "record-audio")
@ -144,15 +172,19 @@
(.toHaveBeenCalledTimes 1))
(-> (js/expect event)
(.toHaveBeenCalledWith {:file-path "audio-file-path"
:duration 500})))))
:duration @duration-ms}))))))
(h/test "record-audio on-cancel works after reviewing audio"
(let [event (js/jest.fn)]
(let [event (js/jest.fn)
on-meter (reagent/atom nil)]
(h/render [record-audio/record-audio
{:on-cancel event
:record-audio-permission-granted true}])
(with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))]
(with-redefs [audio/new-recorder (fn [_ on-meter-fn _]
(reset! on-meter on-meter-fn))
audio/start-recording (fn [_ on-start _]
(on-start)
(js/setInterval #(@on-meter) 100))]
(h/fire-event
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
@ -160,6 +192,7 @@
:locationY 70
:timestamp 0
:identifier 0}})
(with-redefs [datetime/timestamp (fn [] (+ (.now js/Date) 1000))]
(h/advance-timers-by-time 500)
(h/fire-event
:on-responder-release
@ -177,15 +210,19 @@
:identifier 0}})
(h/advance-timers-by-time 250)
(-> (js/expect event)
(.toHaveBeenCalledTimes 1)))))
(.toHaveBeenCalledTimes 1))))))
(h/test "cord-audio on-cancel works after sliding to the cancel button"
(let [event (js/jest.fn)]
(h/test "record-audio on-cancel works after sliding to the cancel button"
(let [event (js/jest.fn)
on-meter (reagent/atom nil)]
(h/render [record-audio/record-audio
{:on-cancel event
:record-audio-permission-granted true}])
(with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))
(with-redefs [audio/new-recorder (fn [_ on-meter-fn _]
(reset! on-meter on-meter-fn))
audio/start-recording (fn [_ on-start _]
(on-start)
(js/setInterval #(@on-meter) 100))
audio/stop-recording (fn [_ on-stop _]
(on-stop))]
(h/fire-event
@ -195,6 +232,7 @@
:locationY 70
:timestamp 0
:identifier 0}})
(with-redefs [datetime/timestamp (fn [] (+ (.now js/Date) 1000))]
(h/advance-timers-by-time 500)
(h/fire-event
:on-responder-move
@ -213,4 +251,6 @@
:identifier 0}})
(h/advance-timers-by-time 250)
(-> (js/expect event)
(.toHaveBeenCalledTimes 1))))))
(.toHaveBeenCalledTimes 1)))))))

View File

@ -29,7 +29,7 @@
(defn f-record-button-big
[recording? ready-to-send? ready-to-lock? ready-to-delete? record-button-is-animating?
record-button-at-initial-position? locked? reviewing-audio? recording-timer recording-length-ms
record-button-at-initial-position? locked? reviewing-audio? recording-length-ms
clear-timeout touch-active? recorder-ref reload-recorder-fn idle? on-send on-cancel]
(let [scale (reanimated/use-shared-value 1)
opacity (reanimated/use-shared-value 0)
@ -82,7 +82,6 @@
(reset! ready-to-lock? false)
(reset! idle? true)
(js/setTimeout #(reset! idle? false) 1000)
(js/clearInterval @recording-timer)
(reset! recording-length-ms 0)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))))

View File

@ -15,11 +15,13 @@
[quo2.components.record-audio.record-audio.buttons.lock-button :as lock-button]
[quo2.components.record-audio.record-audio.buttons.delete-button :as delete-button]
[quo2.components.record-audio.record-audio.buttons.record-button :as record-button]
[clojure.string :as string]))
[clojure.string :as string]
[utils.datetime :as datetime]
[react-native.platform :as platform]))
(def ^:private min-audio-duration-ms 500)
(def ^:private max-audio-duration-ms 120000)
(def ^:private metering-interval 100)
(def ^:private min-audio-duration-ms 1000)
(def ^:private max-audio-duration-ms (if platform/ios? 120800 120500))
(def ^:private metering-interval 25)
(def ^:private base-filename "am")
(def ^:private default-format ".aac")
@ -108,7 +110,7 @@
time-str]]))
(defn- f-play-button
[playing-audio? player-ref playing-timer audio-current-time-ms seeking-audio?]
[playing-audio? player-ref playing-timer audio-current-time-ms seeking-audio? max-duration-ms]
(let [on-play (fn []
(reset! playing-audio? true)
(reset! playing-timer
@ -117,8 +119,22 @@
(let [current-time (audio/get-player-current-time @player-ref)
player-state (audio/get-state @player-ref)
playing? (= player-state audio/PLAYING)]
(when (and playing? (not @seeking-audio?) (> current-time 0))
(reset! audio-current-time-ms current-time))))
(when (and playing?
(not @seeking-audio?)
(> current-time 0)
(< current-time max-duration-ms))
(reset! audio-current-time-ms current-time))
(when (>= current-time max-duration-ms)
(audio/stop-playing
@player-ref
(fn []
(reset! playing-audio? false)
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil)
(reset! audio-current-time-ms 0)
(reset! seeking-audio? false)))
#(log/error "[record-audio] stop player - error: " %)))))
100)))
on-pause (fn []
(reset! playing-audio? false)
@ -143,7 +159,7 @@
[{:keys [on-init on-start-recording on-send on-cancel on-reviewing-audio
record-audio-permission-granted
on-request-record-audio-permission on-check-audio-permissions
audio-file on-lock]}]
audio-file on-lock max-duration-ms]}]
[:f>
;; TODO we need to refactor this, and use :f> with defined function, currenly state is reseted each
;; time parent component
@ -160,6 +176,7 @@
audio-current-time-ms (reagent/atom 0)
seeking-audio? (reagent/atom false)
force-show-controls? (reagent/atom (some? audio-file))
recording-start-ms (atom (datetime/timestamp))
clear-timeout (atom nil)
record-button-at-initial-position? (atom true)
record-button-is-animating? (atom false)
@ -167,7 +184,6 @@
recorder-ref (atom nil)
player-ref (atom nil)
touch-active? (atom false)
recording-timer (atom nil)
playing-timer (atom nil)
output-file (atom audio-file)
reached-max-duration? (atom false)
@ -178,7 +194,7 @@
rec-options
(merge
audio/default-recorder-options
{:filename (str base-filename (.now js/Date) default-format)
{:filename (str base-filename (datetime/timestamp) default-format)
:meteringInterval metering-interval})
destroy-player
(fn []
@ -209,13 +225,44 @@
(fn []
(audio/destroy-recorder @recorder-ref)
(reset! recorder-ref nil))
recorder-on-meter
(fn []
(when @recording-start-ms
(let [now-ms (datetime/timestamp)
recording-duration (- now-ms
@recording-start-ms)]
(reset! recording-length-ms recording-duration)
(when (>= recording-duration max-audio-duration-ms)
(reset! reached-max-duration? (not @locked?))
(reset! reviewing-audio? true)
(reset! idle? false)
(reset! locked? false)
(reset! recording? false)
(reset! ready-to-lock? false)
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(audio/stop-recording
@recorder-ref
(fn []
(reset! output-file (audio/get-recorder-file-path
@recorder-ref))
(reload-player nil)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000)
(reset! recording-length-ms 0)
(reset! recording-start-ms nil)
(when on-reviewing-audio
(on-reviewing-audio (audio/get-recorder-file-path
@recorder-ref))))
(log/debug "[record-audio] new recorder - on meter"))))
reload-recorder
(fn []
(when @recorder-ref
(destroy-recorder))
(reset! recorder-ref (audio/new-recorder
rec-options
#(log/debug "[record-audio] new recorder - on meter")
recorder-on-meter
#(log/debug "[record-audio] new recorder - on ended"))))
reset-recorder
(fn []
@ -238,9 +285,6 @@
(when @clear-timeout
(js/clearTimeout @clear-timeout)
(reset! clear-timeout nil))
(when @recording-timer
(js/clearInterval @recording-timer)
(reset! recording-timer nil))
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil))
@ -258,7 +302,7 @@
record-button-area)
new-recorder (audio/new-recorder
rec-options
#(log/debug "[record-audio] new recorder - on meter")
recorder-on-meter
#(log/debug "[record-audio] new recorder - on ended"))]
(reset! touch-timestamp (oops/oget e "nativeEvent.timestamp"))
(reset! touch-identifier (oops/oget e "nativeEvent.identifier"))
@ -269,44 +313,13 @@
(reset! recording? pressed-record-button?))
(when pressed-record-button?
(reset! playing-audio? false)
(when @recording-timer
(js/clearInterval @recording-timer))
(reset! output-file nil)
(reset! recorder-ref new-recorder)
(audio/start-recording
new-recorder
(fn []
(reset! recording-start-ms (datetime/timestamp))
(reset! audio-current-time-ms 0)
(reset! recording-timer
(js/setInterval
(fn []
(if (< @recording-length-ms max-audio-duration-ms)
(reset! recording-length-ms
(+ @recording-length-ms metering-interval))
(do
(reset! reached-max-duration? (not @locked?))
(reset! reviewing-audio? true)
(reset! idle? false)
(reset! locked? false)
(reset! recording? false)
(reset! ready-to-lock? false)
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(audio/stop-recording
new-recorder
(fn []
(reset! output-file (audio/get-recorder-file-path
new-recorder))
(reload-player nil)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000)
(js/clearInterval @recording-timer)
(reset! recording-length-ms 0)
(when on-reviewing-audio
(on-reviewing-audio (audio/get-recorder-file-path
new-recorder))))))
metering-interval))
(log/debug "[record-audio] start recording - success"))
#(log/error "[record-audio] start recording - error: " %))
(when on-start-recording
@ -424,7 +437,8 @@
(reset! force-show-controls? false)
(when on-send
(on-send {:file-path @output-file
:duration (int (audio/get-player-duration @player-ref))}))
:duration (min max-audio-duration-ms
(int (audio/get-player-duration @player-ref)))}))
(when @player-ref
(audio/stop-playing
@player-ref
@ -475,8 +489,8 @@
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000)
(js/clearInterval @recording-timer)
(reset! recording-length-ms 0)
(reset! recording-start-ms nil)
(reset! disabled? false))
(if (> touch-timestamp-diff min-touch-duration) 0 250)))
(and (not @locked?) (not @reviewing-audio?) (not @record-button-is-animating?))
@ -501,8 +515,8 @@
(reset! ready-to-lock? false)
(reset! idle? true)
(js/setTimeout #(reset! idle? false) 1000)
(js/clearInterval @recording-timer)
(reset! recording-length-ms 0)
(reset! recording-start-ms nil)
(reset! disabled? false)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %)))
@ -532,11 +546,12 @@
(when @reviewing-audio?
[:<>
[:f> f-play-button playing-audio? player-ref playing-timer audio-current-time-ms
seeking-audio?]
seeking-audio? max-duration-ms]
[:f> soundtrack/f-soundtrack
{:audio-current-time-ms audio-current-time-ms
:player-ref @player-ref
:seeking-audio? seeking-audio?}]])
:seeking-audio? seeking-audio?
:max-audio-duration-ms max-duration-ms}]])
(when (or @recording? @reviewing-audio?)
[:f> f-time-counter @recording? @recording-length-ms @ready-to-delete? @reviewing-audio?
@audio-current-time-ms])
@ -567,7 +582,6 @@
record-button-at-initial-position?
locked?
reviewing-audio?
recording-timer
recording-length-ms
clear-timeout
touch-active?

View File

@ -10,8 +10,8 @@
(def ^:private thumb-dark (js/require "../resources/images/icons2/12x12/thumb-dark.png"))
(defn f-soundtrack
[{:keys [audio-current-time-ms player-ref style seeking-audio?]}]
(let [audio-duration-ms (audio/get-player-duration player-ref)]
[{:keys [audio-current-time-ms player-ref style seeking-audio? max-audio-duration-ms]}]
(let [audio-duration-ms (min max-audio-duration-ms (audio/get-player-duration player-ref))]
[:<>
[slider/slider
{:test-ID "soundtrack"

View File

@ -334,3 +334,5 @@
(def ^:const auth-method-none "none")
(def ^:const image-description-in-lightbox? false)
(def ^:const audio-max-duration-ms 120000)

View File

@ -7,29 +7,30 @@
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[status-im2.common.alert.events :as alert]
[status-im2.contexts.chat.composer.constants :as constants]
[status-im2.contexts.chat.composer.constants :as comp-constants]
[status-im2.contexts.chat.messages.list.view :as messages.list]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[status-im2.contexts.chat.composer.actions.style :as style]))
[status-im2.contexts.chat.composer.actions.style :as style]
[status-im2.constants :as constants]))
(defn send-message
[{:keys [sending-images? sending-links?]}
{:keys [text-value focused? maximized?]}
{:keys [height saved-height last-height opacity background-y container-opacity]}
window-height]
(reanimated/animate height constants/input-height)
(reanimated/set-shared-value saved-height constants/input-height)
(reanimated/set-shared-value last-height constants/input-height)
(reanimated/animate height comp-constants/input-height)
(reanimated/set-shared-value saved-height comp-constants/input-height)
(reanimated/set-shared-value last-height comp-constants/input-height)
(reanimated/animate opacity 0)
(when-not @focused?
(js/setTimeout #(reanimated/animate container-opacity constants/empty-opacity) 300))
(js/setTimeout #(reanimated/animate container-opacity comp-constants/empty-opacity) 300))
(js/setTimeout #(reanimated/set-shared-value background-y
(- window-height))
300)
(rf/dispatch [:chat.ui/send-current-message])
(rf/dispatch [:chat.ui/set-input-maximized false])
(rf/dispatch [:chat.ui/set-input-content-height constants/input-height])
(rf/dispatch [:chat.ui/set-input-content-height comp-constants/input-height])
(rf/dispatch [:chat.ui/set-chat-input-text nil])
(reset! maximized? false)
(reset! text-value "")
@ -105,7 +106,7 @@
(rf/dispatch [:chat/send-audio file-path duration])
(if-not @focused?
(reanimated/animate container-opacity
constants/empty-opacity)
comp-constants/empty-opacity)
(js/setTimeout #(when @input-ref (.focus ^js @input-ref))
300))
(rf/dispatch [:chat.ui/set-input-audio nil]))
@ -116,7 +117,7 @@
(reset! gesture-enabled? true)
(if-not @focused?
(reanimated/animate container-opacity
constants/empty-opacity)
comp-constants/empty-opacity)
(js/setTimeout #(when @input-ref
(.focus ^js @input-ref))
300))
@ -143,7 +144,8 @@
{:text (i18n/label :t/settings)
:accessibility-label :settings-button
:onPress (fn [] (permissions/open-settings))}))
50)}]))}]]))
50)}]))
:max-duration-ms constants/audio-max-duration-ms}]]))
(defn camera-button

View File

@ -10,7 +10,8 @@
[quo2.core :as quo]
[react-native.core :as rn]
[utils.re-frame :as rf]
[utils.i18n :as i18n]))
[utils.i18n :as i18n]
[status-im2.constants :as constants]))
(def ^:const media-server-uri-prefix "https://localhost:")
(def ^:const audio-path "/messages/audio")
@ -104,8 +105,21 @@
(let [player (@active-players player-key)
current-time (audio/get-player-current-time player)
playing? (= @player-state :playing)]
(when (and playing? (not @seeking-audio?) (> current-time 0))
(reset! progress current-time))))
(when (and playing?
(not @seeking-audio?)
(> current-time 0)
(< current-time constants/audio-max-duration-ms))
(reset! progress current-time))
(when (>= current-time constants/audio-max-duration-ms)
(audio/stop-playing
player
(fn []
(update-state player-state :ready-to-play)
(reset! progress 0)
(when (and @progress-timer (= @current-player-key player-key))
(js/clearInterval @progress-timer)
(reset! progress-timer nil)))
#(log/error "[audio-message] stop player - error: " %)))))
100)))
(fn []
(update-state player-state :ready-to-play)
@ -135,9 +149,7 @@
{:keys [in-pinned-view?]}]
(let [player-key (get-player-key message-id in-pinned-view?)
player (@active-players player-key)
duration (if (and player (not (#{:preparing :not-loaded :error} @player-state)))
(audio/get-player-duration player)
audio-duration-ms)
duration (min constants/audio-max-duration-ms audio-duration-ms)
time-secs (quot
(if (or @seeking-audio? (#{:playing :seeking} @player-state))
(if (<= @progress 1) (* duration @progress) @progress)
@ -200,7 +212,8 @@
{:style style/slider-container
:audio-current-time-ms progress
:player-ref (@active-players player-key)
:seeking-audio? seeking-audio?}]
:seeking-audio? seeking-audio?
:max-audio-duration-ms constants/audio-max-duration-ms}]
[quo/text
{:style style/timestamp
:accessibility-label :audio-duration-label

View File

@ -6,7 +6,8 @@
[utils.re-frame :as rf]
[status-im2.common.alert.events :as alert]
[utils.i18n :as i18n]
[react-native.permissions :as permissions]))
[react-native.permissions :as permissions]
[status-im2.constants :as constants]))
(defonce record-audio-permission-granted (reagent/atom false))
@ -52,7 +53,8 @@
:on-reviewing-audio on-reviewing-audio
:on-cancel on-cancel
:on-check-audio-permissions on-check-audio-permissions
:on-request-record-audio-permission on-request-record-audio-permission}]]
:on-request-record-audio-permission on-request-record-audio-permission
:max-duration-ms constants/audio-max-duration-ms}]]
[quo/text {:style {:margin-horizontal 20}} @message]])))
(defn preview-record-audio