Merge lp:~ted/indicator-sound/extreme-volume-warning into lp:indicator-sound/15.04

Proposed by Ted Gould
Status: Merged
Approved by: Charles Kerr
Approved revision: 485
Merged at revision: 461
Proposed branch: lp:~ted/indicator-sound/extreme-volume-warning
Merge into: lp:indicator-sound/15.04
Prerequisite: lp:~ted/indicator-sound/synchronous-notification
Diff against target: 263 lines (+139/-15)
4 files modified
src/service.vala (+16/-0)
src/sound-menu.vala (+39/-1)
src/volume-control.vala (+66/-14)
tests/manual (+18/-0)
To merge this branch: bzr merge lp:~ted/indicator-sound/extreme-volume-warning
Reviewer Review Type Date Requested Status
Charles Kerr (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+240201@code.launchpad.net

This proposal supersedes a proposal from 2014-10-13.

Commit message

Warn on high audio levels when using headphones

Description of the change

Both a notification change and a menu item for showing that there is high volume.

To post a comment you must log in.
Revision history for this message
Mirco Müller (macslow) wrote : Posted in a previous version of this proposal

Ted, I think you can drop this branch. For fixing LP: #1373404 I've the more fitting solution with a patch to lp:unity-settings-daemon, which is the one process triggering volume-notifications upon hardware multi-media-keys already.

My branch lp:~macslow/unity-settings-daemon/max-volume-notification-warning does also fully comply with the dedicated design for the hig-volume-warning.

Revision history for this message
Mirco Müller (macslow) wrote : Posted in a previous version of this proposal

Ignore my last comment. I've thought I was clever, but wasn't really :/ I'll take a look at your branch with my new sync.-notification-support in unity8's notification backend/frontend.

Revision history for this message
Ricardo Salveti (rsalveti) wrote : Posted in a previous version of this proposal

One easy way to find out if the active port is a headphone/headset:
http://paste.ubuntu.com/8556022/

Revision history for this message
Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

FYI, testing in rtm silo 6 yielded a few issues:
- sound played only once (I know Mirco explains this somewhere)
- I still had the sound when playing music, which got paused
- when the notification disappeared, the first subsequent volume button press resulted in an incorrect level notification displayed
- notification displayed and sound played when changing volume through the indicator menu slider
- no warning text displayed, just the bar went orange (when playing music with headphones plugged in)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote : Posted in a previous version of this proposal

New to this patch, indicator-sound is calling /org/freedesktop/Notifications's Notify() method on startup with an "audio-volume-muted" string. This appears to be a side-effect of constructing indicator-sound, as it shows muted regardless of whether sound is actually muted.

This is consistently reproducible by running "restart indicator-sound" from the shell after installing this patch.

review: Needs Fixing
Revision history for this message
Charles Kerr (charlesk) wrote : Posted in a previous version of this proposal

NVM that last, I see ted's already got lars' fix for that queued into 15.04 in https://code.launchpad.net/~ted/indicator-sound/lp1381871-no-initial-value-for-utopic

WTF, in testing the only issue I found was bug #1389008, which interfered with testing but doesn't seem to be new to this patch.

review: Approve
Revision history for this message
Charles Kerr (charlesk) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/service.vala'
2--- src/service.vala 2014-10-03 14:27:38 +0000
3+++ src/service.vala 2014-10-31 02:45:35 +0000
4@@ -49,6 +49,7 @@
5 this.actions.add_action (this.create_mute_action ());
6 this.actions.add_action (this.create_volume_action ());
7 this.actions.add_action (this.create_mic_volume_action ());
8+ this.actions.add_action (this.create_high_volume_actions ());
9
10 this.menus = new HashTable<string, SoundMenu> (str_hash, str_equal);
11 this.menus.insert ("desktop_greeter", new SoundMenu (null, SoundMenu.DisplayFlags.SHOW_MUTE | SoundMenu.DisplayFlags.HIDE_PLAYERS | SoundMenu.DisplayFlags.GREETER_PLAYERS));
12@@ -60,6 +61,10 @@
13 this.volume_control.bind_property ("active-mic", menu, "show-mic-volume", BindingFlags.SYNC_CREATE);
14 });
15
16+ this.menus.@foreach ( (profile, menu) => {
17+ this.volume_control.bind_property ("high-volume", menu, "show-high-volume-warning", BindingFlags.SYNC_CREATE);
18+ });
19+
20 this.sync_preferred_players ();
21 this.settings.changed["interested-media-players"].connect ( () => {
22 this.sync_preferred_players ();
23@@ -174,6 +179,8 @@
24 double v = this.volume_control.get_volume () + volume_step_percentage * delta;
25 this.volume_control.set_volume (v.clamp (0.0, this.max_volume));
26
27+ /* TODO: Don't want to mess up the desktop today, but we should remove this
28+ scrolling change and merge that into volume control's notification */
29 if (this.notification != null) {
30 string icon;
31 if (v <= 0.0)
32@@ -388,6 +395,15 @@
33 return volume_action;
34 }
35
36+ Action create_high_volume_actions () {
37+ var high_volume_action = new SimpleAction.stateful("high-volume", null, new Variant.boolean (this.volume_control.high_volume));
38+
39+ this.volume_control.notify["high-volume"].connect( () =>
40+ high_volume_action.set_state(new Variant.boolean (this.volume_control.high_volume)));
41+
42+ return high_volume_action;
43+ }
44+
45 void bus_acquired (DBusConnection connection, string name) {
46 try {
47 connection.export_action_group ("/com/canonical/indicator/sound", this.actions);
48
49=== modified file 'src/sound-menu.vala'
50--- src/sound-menu.vala 2014-10-08 01:57:56 +0000
51+++ src/sound-menu.vala 2014-10-31 02:45:35 +0000
52@@ -93,12 +93,49 @@
53 this.mic_volume_shown = true;
54 }
55 else if (!value && this.mic_volume_shown) {
56- this.volume_section.remove (this.volume_section.get_n_items () -1);
57+ int location = -1;
58+ while ((location = find_action(this.volume_section, "indicator.mic-volume")) != -1) {
59+ this.volume_section.remove (location);
60+ }
61 this.mic_volume_shown = false;
62 }
63 }
64 }
65
66+ public bool show_high_volume_warning {
67+ get {
68+ return this.high_volume_warning_shown;
69+ }
70+ set {
71+ if (value && !this.high_volume_warning_shown) {
72+ /* NOTE: Action doesn't really exist, just used to find below when removing */
73+ var item = new MenuItem(_("High volume can damage your hearing."), "indicator.high-volume-warning-item");
74+ volume_section.append_item (item);
75+ this.high_volume_warning_shown = true;
76+ }
77+ else if (!value && this.high_volume_warning_shown) {
78+ int location = -1;
79+ while ((location = find_action(this.volume_section, "indicator.high-volume-warning-item")) != -1) {
80+ this.volume_section.remove (location);
81+ }
82+ this.high_volume_warning_shown = false;
83+ }
84+ }
85+ }
86+
87+ int find_action (Menu menu, string in_action) {
88+ int n = menu.get_n_items ();
89+ for (int i = 0; i < n; i++) {
90+ string action;
91+ menu.get_item_attribute (i, "action", "s", out action);
92+ if (in_action == action)
93+ return i;
94+ }
95+
96+ return -1;
97+ }
98+
99+
100 public void add_player (MediaPlayer player) {
101 if (this.notify_handlers.contains (player))
102 return;
103@@ -141,6 +178,7 @@
104 Menu volume_section;
105 bool mic_volume_shown;
106 bool settings_shown = false;
107+ bool high_volume_warning_shown = false;
108 bool hide_inactive;
109 bool hide_players = false;
110 HashTable<MediaPlayer, ulong> notify_handlers;
111
112=== modified file 'src/volume-control.vala'
113--- src/volume-control.vala 2014-10-31 02:45:34 +0000
114+++ src/volume-control.vala 2014-10-31 02:45:35 +0000
115@@ -70,6 +70,8 @@
116 private double _account_service_volume = 0.0;
117 private Notify.Notification _notification;
118
119+ private bool _active_port_headphone = false;
120+
121 public signal void volume_changed (double v);
122 public signal void mic_volume_changed (double v);
123
124@@ -79,6 +81,9 @@
125 /** true when a microphone is active **/
126 public bool active_mic { get; private set; default = false; }
127
128+ /** true when high volume warnings should be shown */
129+ public bool high_volume { get; set; }
130+
131 public VolumeControl ()
132 {
133 if (loop == null)
134@@ -158,6 +163,8 @@
135
136 private void sink_info_cb_for_props (Context c, SinkInfo? i, int eol)
137 {
138+ bool old_active_port_headphone = this._active_port_headphone;
139+
140 if (i == null)
141 return;
142
143@@ -174,12 +181,27 @@
144 this.notify_property ("is-playing");
145 }
146
147+ /* Check if the current active port is headset/headphone */
148+ /* There is not easy way to check if the port is a headset/headphone besides
149+ * checking for the port name. On touch (with the pulseaudio droid element)
150+ * the headset/headphone port is called 'output-headset' and 'output-headphone'.
151+ * On the desktop this is usually called 'analog-output-headphones' */
152+ if (i.active_port.name == "output-wired_headset" ||
153+ i.active_port.name == "output-wired_headphone" ||
154+ i.active_port.name == "analog-output-headphones") {
155+ _active_port_headphone = true;
156+ } else {
157+ _active_port_headphone = false;
158+ }
159+
160 if (_pulse_use_stream_restore == false &&
161 _volume != volume_to_double (i.volume.max ()))
162 {
163 _volume = volume_to_double (i.volume.max ());
164 volume_changed (_volume);
165 start_local_volume_timer();
166+ } else if (this._active_port_headphone != old_active_port_headphone) {
167+ volume_changed (_volume);
168 }
169 }
170
171@@ -583,22 +605,52 @@
172 {
173 /* Using this to detect whether we're on the phone or not */
174 if (_pulse_use_stream_restore) {
175- if (volume == 0.0)
176- _notification.update (_("Volume"), "", "audio-volume-muted");
177- if (volume > 0.0 && volume <= 0.33)
178- _notification.update (_("Volume"), "", "audio-volume-low");
179- if (volume > 0.33 && volume <= 0.66)
180- _notification.update (_("Volume"), "", "audio-volume-medium");
181- if (volume > 0.66 && volume <= 1.0)
182- _notification.update (_("Volume"), "", "audio-volume-high");
183+ /* Watch for extreme */
184+ if (volume > 0.75 && _active_port_headphone)
185+ high_volume = true;
186+ else
187+ high_volume = false;
188+
189+ /* Determine Label */
190+ string volume_label = "";
191+ if (high_volume)
192+ volume_label = _("High volume");
193+
194+ /* Choose an icon */
195+ string icon = "audio-volume-muted";
196+ if (volume <= 0.0)
197+ icon = "audio-volume-muted";
198+ else if (volume <= 0.3)
199+ icon = "audio-volume-low";
200+ else if (volume <= 0.7)
201+ icon = "audio-volume-medium";
202+ else
203+ icon = "audio-volume-high";
204+
205+ /* Choose a sound */
206+ string? sound = null;
207+ if (!((_active_sink_input >= 0) && (_active_sink_input < _valid_roles.length)
208+ && (_valid_roles[_active_sink_input] == "multimedia")))
209+ sound = "/usr/share/sounds/ubuntu/stereo/message.ogg";
210+
211+ /* Check tint */
212+ string tint = "false";
213+ if (high_volume)
214+ tint = "true";
215+
216+ /* Put it all into the notification */
217+ _notification.clear_hints ();
218+ _notification.update (_("Volume"), volume_label, icon);
219 _notification.set_hint ("value", (int32)(volume * 100.0));
220- if (_active_sink_input == -1 || _valid_roles[_active_sink_input] != "multimedia") {
221- /* No audio ping if we're playing multimedia */
222- _notification.set_hint ("sound-file", "/usr/share/sounds/ubuntu/stereo/message.ogg");
223- } else {
224- _notification.set_hint ("sound-file", null);
225- }
226+ /* TODO: Removing sound until we can get all the roles cleaned up for
227+ when to play it. We expect this to come back, but in another landing.
228+ _notification.set_hint ("sound-file", sound);
229+ */
230+ _notification.set_hint ("x-canonical-value-bar-tint", tint);
231+ _notification.set_hint ("x-canonical-private-synchronous", "true");
232+ _notification.set_hint ("x-canonical-non-shaped-icon", "true");
233
234+ /* Show it */
235 try {
236 _notification.show ();
237 } catch (GLib.Error e) {
238
239=== modified file 'tests/manual'
240--- tests/manual 2014-10-31 02:45:34 +0000
241+++ tests/manual 2014-10-31 02:45:35 +0000
242@@ -39,3 +39,21 @@
243 <dd>A notification bubble should appear with the sound volume</dd>
244 <dd>No notification sound should be heard</dd>
245 </dl>
246+
247+Test-case indicator-sound/unity8-high-volume
248+<dl>
249+ <dt>Plug headphones into the headphone jack</dt>
250+ <dt>Adjust volume so that it is at the midpoint of volume range</dt>
251+ <dd>The slider should be in the middle of the scale</dd>
252+ <dt>Increase the volume once using HW keys if available</dt>
253+ <dd>A notification bubble should appear with the sound volume</dd>
254+ <dd>There should be no text on the notification</dd>
255+ <dt>Increase the volume using HW keys until it is roughly 90% of the range</dt>
256+ <dd>A notification bubble should appear with the sound volume</dd>
257+ <dd>The text on the notification should read "High volume"</dd>
258+ <dd>The range on the notification bubble should have a different color signifying the higher volume</dd>
259+ <dt>Decrease the volume using HW keys until it is roughly 50% of the range</dt>
260+ <dd>A notification bubble should appear with the sound volume</dd>
261+ <dd>There should be no text on the notification</dd>
262+ <dd>The range on the notification bubble should have a standard color</dd>
263+</dl>

Subscribers

People subscribed via source and target branches