Skip to content

Commit 518bcd5

Browse files
refactor(connectors): remove websocket URL audio playback
1 parent df61119 commit 518bcd5

File tree

5 files changed

+13
-106
lines changed

5 files changed

+13
-106
lines changed

open_wearable/docs/connectors/websocket-ipc-api.md

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Other event messages:
9393
| `list_connected` | `{}` | `WearableSummary[]` |
9494
| `disconnect` | `{"device_id":string}` | `{"disconnected":true}` |
9595
| `store_sound` | `{"sound_id":string,"audio_base64":string,"codec"?:string,"sample_rate"?:int,"num_channels"?:int,"interleaved"?:bool,"buffer_size"?:int}` | `{"sound_id":string,"stored":true,"bytes":int,"config":object}` |
96-
| `play_sound` | `{"sound_id"?:string,"url"?:string,"volume"?:number,"codec"?:string,"sample_rate"?:int,"num_channels"?:int}` | `{"source":"sound_id"\\|"url","playing":true,"config":object,...}` |
96+
| `play_sound` | `{"sound_id":string,"volume"?:number,"codec"?:string,"sample_rate"?:int,"num_channels"?:int}` | `{"source":"sound_id","sound_id":string,"playing":true,"config":object}` |
9797
| `subscribe` | `{"device_id":string,"stream":string,"args"?:object}` | `{"subscription_id":int,"stream":string,"device_id":string}` |
9898
| `unsubscribe` | `{"subscription_id":int}` | `{"subscription_id":int,"cancelled":bool}` |
9999
| `invoke_action` | `{"device_id":string,"action":string,"args"?:object}` | depends on action |
@@ -141,10 +141,7 @@ Note:
141141

142142
## Audio Playback Over WebSocket
143143

144-
The connector supports two audio modes:
145-
146-
1. Distinct preloaded sounds (store once, play many times)
147-
2. Chunked audio stream playback (headphone-like continuous feed)
144+
The connector supports distinct preloaded sounds (store once, play many times).
148145

149146
### 1) Distinct Preloaded Sounds
150147

@@ -174,23 +171,7 @@ Play a stored sound:
174171
}
175172
```
176173

177-
Play directly from a URL:
178-
179-
```json
180-
{
181-
"id": 22,
182-
"method": "play_sound",
183-
"params": {
184-
"url": "https://example.com/notification.wav",
185-
"volume": 1.0
186-
}
187-
}
188-
```
189-
190-
`play_sound` rules:
191-
192-
- Provide exactly one source: `sound_id` or `url`.
193-
- If both are set, the server returns an error.
174+
`play_sound` requires `sound_id`.
194175

195176
## Data Shapes
196177

@@ -268,4 +249,3 @@ Play directly from a URL:
268249

269250
1. `store_sound` with `sound_id` and `audio_base64`.
270251
2. `play_sound` with the same `sound_id`.
271-

open_wearable/lib/models/connectors/commands/play_sound_command.dart

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ class PlaySoundCommand extends RuntimeCommand {
99
name: 'play_sound',
1010
params: [
1111
CommandParam<String>(name: 'sound_id'),
12-
CommandParam<String>(name: 'url'),
1312
CommandParam<double>(name: 'volume'),
1413
CommandParam<String>(name: 'codec'),
1514
CommandParam<int>(name: 'sample_rate'),
@@ -20,16 +19,8 @@ class PlaySoundCommand extends RuntimeCommand {
2019
@override
2120
Future<Map<String, dynamic>> execute(List<CommandParam> params) {
2221
final soundId = readOptionalStringParam(params, 'sound_id');
23-
final url = readOptionalStringParam(params, 'url');
24-
25-
if ((soundId == null || soundId.isEmpty) && (url == null || url.isEmpty)) {
26-
throw ArgumentError('play_sound requires either "sound_id" or "url".');
27-
}
28-
if (soundId != null &&
29-
soundId.isNotEmpty &&
30-
url != null &&
31-
url.isNotEmpty) {
32-
throw ArgumentError('Provide either "sound_id" or "url", not both.');
22+
if (soundId == null || soundId.isEmpty) {
23+
throw ArgumentError('play_sound requires "sound_id".');
3324
}
3425

3526
final config = AudioPlaybackConfig.fromOptional(
@@ -40,7 +31,6 @@ class PlaySoundCommand extends RuntimeCommand {
4031

4132
return runtime.playSound(
4233
soundId: soundId,
43-
url: url,
4434
volume: readOptionalDoubleParam(params, 'volume'),
4535
config: config,
4636
);

open_wearable/lib/models/connectors/commands/runtime.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ abstract class CommandRuntime {
4141

4242
Future<Map<String, dynamic>> playSound({
4343
String? soundId,
44-
String? url,
4544
double? volume,
4645
AudioPlaybackConfig? config,
4746
});

open_wearable/lib/models/connectors/websocket_audio_playback_service.dart

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import 'dart:typed_data';
22

3+
import 'dart:io';
34
import 'package:audioplayers/audioplayers.dart';
45
import 'package:path_provider/path_provider.dart';
5-
import 'dart:io';
66

77
import '../logger.dart';
88
import 'audio_playback_config.dart';
@@ -68,49 +68,6 @@ class WebsocketAudioPlaybackService {
6868
return config;
6969
}
7070

71-
Future<AudioPlaybackConfig> playFromUrl({
72-
required String url,
73-
double? volume,
74-
AudioPlaybackConfig? config,
75-
}) async {
76-
final uri = Uri.tryParse(url);
77-
if (uri == null || !uri.hasScheme) {
78-
throw ArgumentError('Invalid URL: $url');
79-
}
80-
if (uri.scheme != 'http' && uri.scheme != 'https') {
81-
throw ArgumentError(
82-
'Only http/https URLs are supported. Got: ${uri.scheme}',
83-
);
84-
}
85-
if (uri.host == 'commons.wikimedia.org' &&
86-
uri.path.startsWith('/wiki/File:')) {
87-
throw ArgumentError(
88-
'URL points to a Wikimedia page, not a direct audio file. Use the raw media URL from upload.wikimedia.org.',
89-
);
90-
}
91-
92-
final playbackConfig = config ?? const AudioPlaybackConfig();
93-
94-
if (volume != null) {
95-
await _preloadedPlayer.setVolume(volume);
96-
}
97-
98-
await _preloadedPlayer.stop();
99-
100-
try {
101-
await _preloadedPlayer.play(UrlSource(url));
102-
} catch (error) {
103-
throw StateError(
104-
'Failed to play URL source. Ensure it is a direct audio file URL (not an HTML page). Original error: $error',
105-
);
106-
}
107-
108-
logger.i(
109-
'[connector.audio] playing url source=$url codec_hint=${playbackConfig.codec}',
110-
);
111-
return playbackConfig;
112-
}
113-
11471
Future<void> dispose() async {
11572
await _preloadedPlayer.dispose();
11673
}

open_wearable/lib/models/connectors/websocket_ipc_server.dart

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -336,41 +336,22 @@ class WebSocketIpcServer implements CommandRuntime {
336336
@override
337337
Future<Map<String, dynamic>> playSound({
338338
String? soundId,
339-
String? url,
340339
double? volume,
341340
AudioPlaybackConfig? config,
342341
}) async {
343342
final hasSoundId = soundId != null && soundId.trim().isNotEmpty;
344-
final hasUrl = url != null && url.trim().isNotEmpty;
345-
if (!hasSoundId && !hasUrl) {
346-
throw ArgumentError('play_sound requires either "sound_id" or "url".');
347-
}
348-
if (hasSoundId && hasUrl) {
349-
throw ArgumentError('Provide either "sound_id" or "url", not both.');
343+
if (!hasSoundId) {
344+
throw ArgumentError('play_sound requires "sound_id".');
350345
}
351346

352-
if (hasSoundId) {
353-
final usedConfig = await _audioPlaybackService.playStoredSound(
354-
soundId: soundId,
355-
volume: volume,
356-
overrideConfig: config,
357-
);
358-
return <String, dynamic>{
359-
'source': 'sound_id',
360-
'sound_id': soundId,
361-
'playing': true,
362-
'config': usedConfig.toJson(),
363-
};
364-
}
365-
366-
final usedConfig = await _audioPlaybackService.playFromUrl(
367-
url: url!,
347+
final usedConfig = await _audioPlaybackService.playStoredSound(
348+
soundId: soundId,
368349
volume: volume,
369-
config: config,
350+
overrideConfig: config,
370351
);
371352
return <String, dynamic>{
372-
'source': 'url',
373-
'url': url,
353+
'source': 'sound_id',
354+
'sound_id': soundId,
374355
'playing': true,
375356
'config': usedConfig.toJson(),
376357
};

0 commit comments

Comments
 (0)