63 lines
2.5 KiB
Dart
63 lines
2.5 KiB
Dart
// ignore_for_file: deprecated_member_use
|
|
import 'dart:html' as html;
|
|
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
import 'dart:math' as math;
|
|
|
|
class TeddyAudio {
|
|
final html.AudioElement _audio = html.AudioElement();
|
|
bool enabled = false;
|
|
String status = 'Audio disabled on this device';
|
|
String? _url;
|
|
int _generation = 0;
|
|
TeddyAudio() {
|
|
_audio.onEnded.listen((_) { status = 'Cue finished'; });
|
|
_audio.onError.listen((_) { status = 'Browser audio error ${_audio.error?.code}. Use Play current hint here to retry.'; });
|
|
}
|
|
Future<void> enable() async {
|
|
// Unlock this exact media element in the user's tap handler (required on phones).
|
|
final data = ByteData(16044);
|
|
void text(int offset, String value) { for (var i=0;i<value.length;i++) { data.setUint8(offset+i,value.codeUnitAt(i)); } }
|
|
text(0,'RIFF'); data.setUint32(4,16036,Endian.little); text(8,'WAVEfmt ');
|
|
data.setUint32(16,16,Endian.little); data.setUint16(20,1,Endian.little);
|
|
data.setUint16(22,1,Endian.little); data.setUint32(24,8000,Endian.little);
|
|
data.setUint32(28,16000,Endian.little); data.setUint16(32,2,Endian.little);
|
|
data.setUint16(34,16,Endian.little); text(36,'data'); data.setUint32(40,16000,Endian.little);
|
|
for (var i=0;i<8000;i++) { data.setInt16(44+i*2,(4000*math.sin(2*math.pi*440*i/8000)).round(),Endian.little); }
|
|
_audio.src = 'data:audio/wav;base64,${base64Encode(data.buffer.asUint8List())}';
|
|
try {
|
|
await _audio.play();
|
|
enabled = true;
|
|
_url = null;
|
|
status = 'Speaker test playing (one-second tone)';
|
|
} catch (_) {
|
|
enabled = false;
|
|
status = 'Browser blocked audio. Tap Enable audio again.';
|
|
}
|
|
}
|
|
void playNow(String url) { enabled = true; sync(url, force:true); }
|
|
void sync(String? url, {bool force=false}) {
|
|
if (!enabled || (!force && url == _url)) return;
|
|
_generation++;
|
|
final generation = _generation;
|
|
_audio.pause();
|
|
_url = url;
|
|
if (url == null) { status = 'Waiting for Teddy'; return; }
|
|
_audio.src = url;
|
|
status = 'Loading Teddy audio';
|
|
_audio.play().then((_) {
|
|
if (generation == _generation) status = 'Playing on this device';
|
|
}).catchError((Object error) {
|
|
if (generation == _generation) status = 'Playback blocked or unavailable. Tap Play current hint here.';
|
|
});
|
|
}
|
|
void disable() {
|
|
_generation++;
|
|
enabled = false;
|
|
_url = null;
|
|
_audio.pause();
|
|
status = 'Audio disabled on this device';
|
|
}
|
|
void dispose() { disable(); _audio.src = ''; }
|
|
}
|