<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Audio on Tarragon</title><link>https://tarrragon.github.io/blog/tags/audio/</link><description>Recent content in Audio on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 06 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/audio/index.xml" rel="self" type="application/rss+xml"/><item><title>Flutter 音量控制：App 自己的音量 vs 系統音量</title><link>https://tarrragon.github.io/blog/work-log/flutter_audio_volume_control/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/work-log/flutter_audio_volume_control/</guid><description>&lt;p>做 App 內的「音量設定」時，常會冒出一個問題：&lt;strong>App 到底能不能控制自己的音量？需不需要裝套件？&lt;/strong> 關鍵在於先分清楚「音量」其實有兩個不同層次。&lt;/p>
&lt;h2 id="兩種音量">兩種音量&lt;/h2>
&lt;h3 id="1-播放器自己的音量per-player-volume">1. 播放器自己的音量（per-player volume）&lt;/h3>
&lt;p>每個媒體播放套件都提供自己的音量 API，範圍 &lt;code>0.0–1.0&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">// video_player
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">controller&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">setVolume&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">0.5&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="o">//&lt;/span> &lt;span class="n">audioplayers&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">just_audio&lt;/span> &lt;span class="err">也都有對應的&lt;/span> &lt;span class="n">setVolume&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這是 &lt;strong>App 控制「自己播放的聲音」&lt;/strong> 的方式——不需要任何額外套件，播放套件本身就帶。想調哪個音源就對哪個播放器設音量，範圍精準、互不影響。&lt;/p>
&lt;h3 id="2-系統媒體音量device--stream-volume">2. 系統媒體音量（device / stream volume）&lt;/h3>
&lt;p>就是 OS 的音量滑桿、實體音量鍵所控制的那個值（Android 的 &lt;code>STREAM_MUSIC&lt;/code> 等音訊串流）。它是&lt;strong>整台裝置、所有 App 共用&lt;/strong>的。&lt;/p>
&lt;p>Flutter 核心&lt;strong>沒有&lt;/strong>暴露讀寫系統音量的 API，要從 App 動它得裝 plugin（例如 &lt;code>volume_controller&lt;/code>、&lt;code>flutter_volume_controller&lt;/code>），它們底層去戳平台的 &lt;code>AudioManager&lt;/code>。&lt;/p>
&lt;h2 id="沒有每個-app-的音量這種-os-概念">沒有「每個 App 的音量」這種 OS 概念&lt;/h2>
&lt;p>一個容易誤解的點：標準 Android &lt;strong>沒有&lt;/strong>「這個 App 的音量」這種 OS 層級的單一旋鈕。音量只有兩端：&lt;/p>
&lt;ul>
&lt;li>每個播放器自己的音量（App 說了算）。&lt;/li>
&lt;li>系統串流音量（全域，需 plugin 才能從 App 改）。&lt;/li>
&lt;/ul>
&lt;p>所以「App 主音量」的體驗，做法是&lt;strong>App 自己維護一個值、套用到它所有的播放器上&lt;/strong>——這個「主音量」是應用層自己合成出來的，不是 OS 給的。&lt;/p>
&lt;h2 id="兩者是相乘的">兩者是相乘的&lt;/h2>
&lt;p>實際聽到的響度大致是：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">實際響度 ≈ 播放器音量 (0–1) × 系統串流音量&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>兩個是獨立、可相乘、可共存的旋鈕。這也代表：就算 App 把播放器音量設成最大 &lt;code>1.0&lt;/code>，若裝置系統音量被轉到很小或靜音，還是不會大聲——App 的設定是相對衰減，不是絕對音量。&lt;/p>
&lt;p>相乘只是心智模型：系統音量刻度到實際增益是非線性的（多為對數 / dB 級距），也沒計入 audio focus、其他 App 搶焦點、輸出裝置增益等因素。但「兩個旋鈕獨立、App 只能相對衰減」這個結論不受影響。&lt;/p>
&lt;h2 id="為什麼多數情況不建議從-app-去改系統音量">為什麼多數情況不建議從 App 去改系統音量&lt;/h2>
&lt;p>如果需求只是「控制 App 自己發出的聲音」，用 per-player &lt;code>setVolume&lt;/code> 就夠了。去操作全域系統音量通常弊大於利：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>全域副作用&lt;/strong>：系統媒體音量是整台裝置共用的，動它會影響裝置上&lt;strong>所有&lt;/strong> App 的媒體輸出，範圍遠大於你想控制的目標。&lt;/li>
&lt;li>&lt;strong>跟使用者爭同一個控制點&lt;/strong>：實體音量鍵與 OS 滑桿是使用者的控制介面。App 也去寫系統音量，就變成同一個值有兩個主人——使用者調大、App 稍後又改回去，互相拉扯、行為難預測。&lt;/li>
&lt;li>&lt;strong>無法表達「只調自己、不動其他」&lt;/strong>：很多時候我們要的是「相對地」把某個音源調小或靜音（例如把廣告靜音、但其他媒體正常）。系統音量一動就是全部一起動，做不到這種區隔；per-player 音量剛好就是精準範圍。&lt;/li>
&lt;li>&lt;strong>為小需求引入平台依賴&lt;/strong>：per-player 音量本來就能完全解決，為此引入一個呼叫平台 API 的 plugin，只是多了依賴、權限與邊界情況。&lt;/li>
&lt;li>&lt;strong>相容性風險&lt;/strong>：系統音量 plugin 碰平台特定 API，跨 Android 版本／OEM（各手機廠牌客製 ROM）／模擬器行為常有差異；播放器音量由媒體框架處理，一致得多。&lt;/li>
&lt;li>&lt;strong>語意會被洗掉&lt;/strong>：你想存的是「某功能的音量」這個持久化設定；改成驅動系統音量，那個值本質是「裝置的音量」，OS 和其他 App 隨時會蓋掉它。&lt;/li>
&lt;/ul>
&lt;h2 id="什麼時候才真的需要動系統音量">什麼時候才真的需要動系統音量&lt;/h2>
&lt;ul>
&lt;li>需求是「控制&lt;strong>整台裝置&lt;/strong>的輸出」，包含非本 App 的聲音。&lt;/li>
&lt;li>部署在 kiosk／POS，&lt;strong>實體音量鍵被鎖死或無法操作&lt;/strong>，而店員需要一個 App 內入口去調整台裝置的音量。&lt;/li>
&lt;/ul>
&lt;p>即使是這些情境，也要清楚你是在動「裝置的音量」而非「App 自己的音量」，並接受它的全域性。&lt;/p>
&lt;h2 id="小結">小結&lt;/h2>
&lt;ul>
&lt;li>控制 App 自己的聲音 → 用播放器的 &lt;code>setVolume&lt;/code>，&lt;strong>免 plugin&lt;/strong>、範圍精準。&lt;/li>
&lt;li>想要「App 主音量」→ 自己維護一個值套到所有播放器，應用層合成。&lt;/li>
&lt;li>控制整台裝置的媒體音量 → 才需要 plugin，但那是全域副作用，先確認需求真的是「裝置音量」再用。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>做 App 內的「音量設定」時，常會冒出一個問題：<strong>App 到底能不能控制自己的音量？需不需要裝套件？</strong> 關鍵在於先分清楚「音量」其實有兩個不同層次。</p>
<h2 id="兩種音量">兩種音量</h2>
<h3 id="1-播放器自己的音量per-player-volume">1. 播放器自己的音量（per-player volume）</h3>
<p>每個媒體播放套件都提供自己的音量 API，範圍 <code>0.0–1.0</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// video_player
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="n">controller</span><span class="p">.</span><span class="n">setVolume</span><span class="p">(</span><span class="m">0.5</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="o">//</span> <span class="n">audioplayers</span> <span class="o">/</span> <span class="n">just_audio</span> <span class="err">也都有對應的</span> <span class="n">setVolume</span></span></span></code></pre></div><p>這是 <strong>App 控制「自己播放的聲音」</strong> 的方式——不需要任何額外套件，播放套件本身就帶。想調哪個音源就對哪個播放器設音量，範圍精準、互不影響。</p>
<h3 id="2-系統媒體音量device--stream-volume">2. 系統媒體音量（device / stream volume）</h3>
<p>就是 OS 的音量滑桿、實體音量鍵所控制的那個值（Android 的 <code>STREAM_MUSIC</code> 等音訊串流）。它是<strong>整台裝置、所有 App 共用</strong>的。</p>
<p>Flutter 核心<strong>沒有</strong>暴露讀寫系統音量的 API，要從 App 動它得裝 plugin（例如 <code>volume_controller</code>、<code>flutter_volume_controller</code>），它們底層去戳平台的 <code>AudioManager</code>。</p>
<h2 id="沒有每個-app-的音量這種-os-概念">沒有「每個 App 的音量」這種 OS 概念</h2>
<p>一個容易誤解的點：標準 Android <strong>沒有</strong>「這個 App 的音量」這種 OS 層級的單一旋鈕。音量只有兩端：</p>
<ul>
<li>每個播放器自己的音量（App 說了算）。</li>
<li>系統串流音量（全域，需 plugin 才能從 App 改）。</li>
</ul>
<p>所以「App 主音量」的體驗，做法是<strong>App 自己維護一個值、套用到它所有的播放器上</strong>——這個「主音量」是應用層自己合成出來的，不是 OS 給的。</p>
<h2 id="兩者是相乘的">兩者是相乘的</h2>
<p>實際聽到的響度大致是：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">實際響度 ≈ 播放器音量 (0–1)  ×  系統串流音量</span></span></code></pre></div><p>兩個是獨立、可相乘、可共存的旋鈕。這也代表：就算 App 把播放器音量設成最大 <code>1.0</code>，若裝置系統音量被轉到很小或靜音，還是不會大聲——App 的設定是相對衰減，不是絕對音量。</p>
<p>相乘只是心智模型：系統音量刻度到實際增益是非線性的（多為對數 / dB 級距），也沒計入 audio focus、其他 App 搶焦點、輸出裝置增益等因素。但「兩個旋鈕獨立、App 只能相對衰減」這個結論不受影響。</p>
<h2 id="為什麼多數情況不建議從-app-去改系統音量">為什麼多數情況不建議從 App 去改系統音量</h2>
<p>如果需求只是「控制 App 自己發出的聲音」，用 per-player <code>setVolume</code> 就夠了。去操作全域系統音量通常弊大於利：</p>
<ul>
<li><strong>全域副作用</strong>：系統媒體音量是整台裝置共用的，動它會影響裝置上<strong>所有</strong> App 的媒體輸出，範圍遠大於你想控制的目標。</li>
<li><strong>跟使用者爭同一個控制點</strong>：實體音量鍵與 OS 滑桿是使用者的控制介面。App 也去寫系統音量，就變成同一個值有兩個主人——使用者調大、App 稍後又改回去，互相拉扯、行為難預測。</li>
<li><strong>無法表達「只調自己、不動其他」</strong>：很多時候我們要的是「相對地」把某個音源調小或靜音（例如把廣告靜音、但其他媒體正常）。系統音量一動就是全部一起動，做不到這種區隔；per-player 音量剛好就是精準範圍。</li>
<li><strong>為小需求引入平台依賴</strong>：per-player 音量本來就能完全解決，為此引入一個呼叫平台 API 的 plugin，只是多了依賴、權限與邊界情況。</li>
<li><strong>相容性風險</strong>：系統音量 plugin 碰平台特定 API，跨 Android 版本／OEM（各手機廠牌客製 ROM）／模擬器行為常有差異；播放器音量由媒體框架處理，一致得多。</li>
<li><strong>語意會被洗掉</strong>：你想存的是「某功能的音量」這個持久化設定；改成驅動系統音量，那個值本質是「裝置的音量」，OS 和其他 App 隨時會蓋掉它。</li>
</ul>
<h2 id="什麼時候才真的需要動系統音量">什麼時候才真的需要動系統音量</h2>
<ul>
<li>需求是「控制<strong>整台裝置</strong>的輸出」，包含非本 App 的聲音。</li>
<li>部署在 kiosk／POS，<strong>實體音量鍵被鎖死或無法操作</strong>，而店員需要一個 App 內入口去調整台裝置的音量。</li>
</ul>
<p>即使是這些情境，也要清楚你是在動「裝置的音量」而非「App 自己的音量」，並接受它的全域性。</p>
<h2 id="小結">小結</h2>
<ul>
<li>控制 App 自己的聲音 → 用播放器的 <code>setVolume</code>，<strong>免 plugin</strong>、範圍精準。</li>
<li>想要「App 主音量」→ 自己維護一個值套到所有播放器，應用層合成。</li>
<li>控制整台裝置的媒體音量 → 才需要 plugin，但那是全域副作用，先確認需求真的是「裝置音量」再用。</li>
</ul>
]]></content:encoded></item></channel></rss>