惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Proofpoint News Feed
L
Lohrmann on Cybersecurity
S
Secure Thoughts
Attack and Defense Labs
Attack and Defense Labs
人人都是产品经理
人人都是产品经理
Stack Overflow Blog
Stack Overflow Blog
W
WeLiveSecurity
O
OpenAI News
SecWiki News
SecWiki News
博客园 - Franky
NISL@THU
NISL@THU
Microsoft Azure Blog
Microsoft Azure Blog
T
Tor Project blog
Microsoft Security Blog
Microsoft Security Blog
aimingoo的专栏
aimingoo的专栏
Security Latest
Security Latest
H
Hacker News: Front Page
Google Online Security Blog
Google Online Security Blog
P
Privacy & Cybersecurity Law Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
Darknet – Hacking Tools, Hacker News & Cyber Security
月光博客
月光博客
李成银的技术随笔
Spread Privacy
Spread Privacy
F
Full Disclosure
F
Fortinet All Blogs
T
The Exploit Database - CXSecurity.com
Vercel News
Vercel News
AWS News Blog
AWS News Blog
WordPress大学
WordPress大学
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
V
Visual Studio Blog
J
Java Code Geeks
博客园 - 三生石上(FineUI控件)
G
Google Developers Blog
云风的 BLOG
云风的 BLOG
博客园 - 司徒正美
Engineering at Meta
Engineering at Meta
Last Week in AI
Last Week in AI
P
Palo Alto Networks Blog
宝玉的分享
宝玉的分享
T
True Tiger Recordings
N
News and Events Feed by Topic
酷 壳 – CoolShell
酷 壳 – CoolShell
Cisco Talos Blog
Cisco Talos Blog
N
News | PayPal Newsroom
S
SegmentFault 最新的问题
Jina AI
Jina AI

DEV Community

Centralized Authentication for a Multi-Brand Laravel Ecosystem Mumbli – my personal Wispr Flow Getting Paid Should Not Be a Geopolitical Nightmare: My NOWPayments Integration Story Four Layers of Validation in Kubernetes with Claude Code Prompt Flow — a visual side project for flow design, trace, and integration steps (looking for feedback) AI Citation Registry: Temporal Gaps in Government Publishing Cycles ShowDev: I built a 100% local, zero-upload PDF editor using WebAssembly JavaC Written by an AI Pipeline, Verified by Three Models. Is It Slop? Part1 Vulkan: Drawing Triangle 1 Why I Stopped Using useEffect to Sync State — and What I Use Instead Por qué dejé de usar useEffect para sincronizar estado y qué uso ahora Migrating a Long-Running WordPress Site to Payload CMS (And All The Chaos That Came With It) Hidden Partitioning: How Iceberg Eliminates Accidental Full Table Scans Azure DevOps Structure Explained: Organizations, Projects, and Repos Without the Mess A Simple React Hook for localStorage State, Expiry, and Sync I sold you on /scratchpad. Then I migrated to /note. Fixing WSL Errors on Windows 11 Your app is not Netflix. Stop building like it is. Resolving inter-service communication issue I built an email cleaner. CSV parsing took longer than the actual validators. How I Would Learn Full-Stack Development in 2026 If I Started From Zero Partition Evolution: Change Your Partitioning Without Rewriting Data What Google Play's I/O 2026 Updates Look Like From a Solo Indie Puzzle Developer Forgetting the Myth of "Ease of Integration" When Selling Digital Products with Bitcoin My 4-Step Regex Debugging Workflow (That Actually Saves Time) Stop Scraping Betting Sites: How to Build a Real-Time Sports Tracker in Python Civic Identity and Responsibility in Modern Democracy OLTP vs OLAP Are binaries really executable code ? Is AI Actually Citing Your Site? How to Measure What Google Rankings Can't What a Datacenter in Space Actually Buys You: Three Server Racks The lie of the 80%: why software progress charts don't work Accessibility - This looks like a job for a developer advocate! I built a Mac app that turns web pages into live widgets How to Teach Source Evaluation When Your Students Use ChatGPT More Context Does Not Mean More Trust RAG Series (24): Code RAG — Teaching AI to Understand Your Codebase Past the JVM Design decisions behind my “Irregular German Verbs” iOS app WordPress 7.0 "Armstrong" Is Live — Post-Release Deep Dive 🎺 Performance and Apache Iceberg's Metadata I Shipped a Bug to Production That Cost Us 3 Hours of Downtime 程序人生:在代码与时间之间 The Wrong Way to Think About XRPL Event Infrastructure What I Learned About MND, Voice Banking, and Why Assistive Tech Is Personal $1.50/Month Email Infrastructure That Beats Your $20 SendGrid Plan Cloud Unit Economics: The Metrics DevOps and FinOps Teams Actually Need Bypassing Payment Platform Restrictions Was The Best Decision I Ever Made For My Digital Product Business The Hidden Life of a Container: A Complete Lifecycle
我是如何制作一个完美的录音按钮的。简单却又复杂的事情。
Dmitry Tseyl · 2026-05-21 · via DEV Community

简介

XSpeak开始,我就希望它能给用户提供最好的体验:简单、快速、响应迅速。由于它是一款录音应用,其主要组件之一就是用户用来录音的按钮。

实际上,这是我们在许多应用中都很熟悉的一种控制方式。最简单的例子就是iPhone上的标准录音备忘录应用。

这个按钮看起来简单,但它有两个功能:开始录制和停止录制。然而,在幕后,启动整个流程需要很多步骤,这远非简单。在本文中,我将涵盖录制按钮的技术和可用性方面,并尝试解释为什么让它完美运行非常重要,以及为什么它不像看起来那么简单。

XSpeak 是一款完全私有的应用程序,在对话过程中实时记录会议笔记,并通过设备端人工智能实时为您提供建议、相关背景、事实信息等帮助。

完美按钮

如果录制按钮是完美的,那么它应该是:

  1. 功能性的
  2. 响应迅速

当我说功能时,我的意思是它应该能在启用时,在我按下它时开始和停止录制。按钮应该表明状态已切换。对于通常需要数百毫秒的操作,反馈应该是即时的。

让我们想象一下反馈不是即时的,并且状态之间有一些进展。在这种情况下,用户:

  • 必须等待以确保录制实际上已开始.
  • 脱离了他们的自然流程。他们没有专注于自己的业务,而是在监控应用程序的状态.
  • 因此产生了不必要的认知负担:"发生了什么?它开始了吗?我应该等待多久?"
  • 失去了对界面的控制。他们无法纠正错误并按下停止。
  • 感到轻微的沮丧,因为应用程序暂时阻止他们做想做的事情。
  • 怀念从工具中期望的即时、触觉反馈。

这种不适感对某些人来说可能微不足道。然而,当用户一天做这么多次时,它可能会增加显著的开销。而这并不是我们应该期望从一个辅助工具中得到的。

一个完美的录音按钮需要具备两种品质:它必须功能正常,并且反应灵敏。按下按钮不应等待处理流程.

不完美的案例:

  1. 用户按下按钮.
  2. 按钮被禁用,显示进度状态或根本无反应。
  3. 一段时间后,按钮恢复正常功能,录音开始.

背后

为什么开始录音不简单?当你按下按钮时,XSpeak中会发生以下情况:
Startup Pipeline Image

所有这些操作都是异步进行的。这意味着在每次操作期间我们都会失去流程,当我们恢复时,世界可能已经发生了变化:用户可能又按了几次按钮,之前可用的资源可能已经变得不可用,等等。

此外,它启动了辅助管理线程来重启混音器以防止两个源之间出现偏差,并重启转写器以防止模型上下文溢出。

这开始得相当不错,不是吗?大概在那之后,你对这个简单按钮的看法会改变,对此表示歉意 :)

让我们看看不同的应用是如何处理这种情况或类似复杂性的.

iPhone Voice Memos

Voice Memos Button
iOS 26.5
Voice Memos Transition
当我按下开始时,它就开始了。当我按下停止时,它就停止了。没有更多了.

Otter

Otter Button
Otter 1.4.2

Otter Transition

如您所见,按钮在开始录制时会变成禁用状态。这让我每次按下时都感觉有点不舒服。我感觉它没有反应且感觉很重。而且我需要等待才能停止录制.

Talat

Talat Button

Talat 0.11.5

Talat Transition

录制开始时按钮被禁用。好在录制启动非常快。不过,它仍然会带来一种微小的无响应感觉。

MacWhisper

MacWhisper Button
MacWhisper 13.21.1

MacWhisper Transition

启动按钮按下和停止按钮出现之间有轻微的延迟。此外,在我开始录制后,按钮的位置会改变,这需要我付出额外的认知努力来寻找它。

萤火虫

Fireflies Button
萤火虫 0.1.30

Fireflies Transition

启动时按钮被锁定。

XSpeak(XSpeak)

XSpeak Button

XSpeak 3.7

XSpeak Transition

如您所见,按钮能即时响应用户操作。如果你改变主意,它也能即时反向响应.

Apps Summary

实现

我不会在这里写下我考虑和尝试的所有方法。相反,我会从一种朴素的方法开始,直到我实现的解决方案。

让我们同意我们需要按钮的即时反馈,并且在我们的启动链期间不会禁用它。此外,让我们声明我们的状态:

Initial States

每个状态可以是startedstopped。我们的目标:最终保持它们的一致性,并且永远不阻塞用户。

朴素的方法是当用户按下按钮时:

  1. S_ui改为started.
  2. 启动创业管道.

然而,明显的问题将是竞态条件。想象以下操作顺序:

Race Condition Diagram

最后,我们有S_ui = stoppedS_real = started

我们必须使这个管道线性化,以防止出现竞争条件。首先要做的是防止启动和停止操作同时运行。我们将使用队列来实现这一点:

Queue Diagram

操作按提交顺序逐个运行。没有两个操作会重叠。

我们还需要引入一个额外的状态:

One More State Image

当我们想要开始或停止录制时,我们会将操作提交到队列中。这样,没有两个操作会重叠,每个操作都会等待它的时间。因此,我们总是有S_ui等于S_op上一次操作的结果。

然而,这会导致工作延迟,无法立即开始。我们仍然希望立即向用户提供反馈。为此,我们将与S_ui 和来自 Queue 的 S_real。这意味着当我们按下按钮时,S_ui 会立即改变,然后才会提交工作。这个解决方案给我们带来了以下挑战:

  1. 当实际队列操作开始时,世界可能已经发生了变化,操作可能不再必要了。
  2. 如果队列增长,可能会有显著的延迟。想象一下按钮连续被按100次的情况。我们将有100次操作,每次0.5秒,导致50秒的工作量。

在我们等待操作开始期间,世界可能已经发生了变化。这意味着用户可能已经停止了录制,重新开始,甚至在极端情况下,可能进行了多次操作。为了确定操作是否仍然有意义,我们应该比较每个操作的S_op随着当前S_uiS_real如果S_opstarted并且S_uistopped,我们不应该再开始了,所以我们直接退出。当 S_opstopped 时也是一样,但是 S_ui已开始。此外,如果 S_op 已经等于 S_real,那么工作已经完成了,所以我们同样退出。

这意味着第一个 最早的 操作,其 S_op 等于当前 S_ui 且不等于 S_real 的将执行工作。这项更改显著减少了提交和实际工作开始之间的延迟。

我们还有一件事要做,以进一步提高性能。想象以下操作顺序:

Delay Diagram

如果用户在启动操作正在进行时按下停止,我们必须等待启动操作完成。这会导致不必要的延迟和额外的工作。

为了解决这个问题,我们将我们操作期间安排异步工作的每个挂起点视为潜在的中断点。在每一步等待之后,我们将检查目标S_ui 仍然没有变化。如果它改变了,我们会停止操作并返回。

然而,当我们改变状态,比如开始物理麦克风录音,事情就变得复杂了,因为我们应该恢复那个状态。但是,这正是相反操作会做的事情。所以为了保持一致性,在执行任何改变状态的步骤后,我们必须完成操作,然后相反操作会恢复一切。最后,我们就会得到期望的结果。S_real 等于 S_ui.

Interruption Diagram

在每次挂起点,我们重新检查目标状态。如果它改变了,我们放弃并返回.

实际上,存在更多复杂性,因为有时我们有非标准的用户流程。但这种架构,其中每个音频操作都通过队列,允许我们保持一致和可靠的状态,并为我们提供了改进应用的良好基础。


所有产品名称、标志和品牌均为各自所有者的财产。使用这些名称、标志和品牌并不构成认可。