From 949b7f27a348ed0dd1fba0101b9a0af30f421d64 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Sat, 8 Oct 2022 19:04:04 +0200 Subject: [PATCH 1/6] feat(web): add setBalance (#58) --- feature_parity_table.md | 2 +- .../audioplayers_web/lib/web_audio_js.dart | 57 +++++++++++++++++++ .../audioplayers_web/lib/wrapped_player.dart | 14 ++++- 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 packages/audioplayers_web/lib/web_audio_js.dart diff --git a/feature_parity_table.md b/feature_parity_table.md index c74213e40..7f01ecc92 100644 --- a/feature_parity_table.md +++ b/feature_parity_table.md @@ -59,7 +59,7 @@ Note: LLM means Low Latency Mode. stay awakeyes (except LLM)yesnononono recording activenot yetyesnononono playing routeyes (except LLM)yesnononono - balancenonononoyesyes + balancenononoyesyesyes Streams duration eventyesyesyesyesyesyes position eventyesyesyesyesyesyes diff --git a/packages/audioplayers_web/lib/web_audio_js.dart b/packages/audioplayers_web/lib/web_audio_js.dart new file mode 100644 index 000000000..b5589f121 --- /dev/null +++ b/packages/audioplayers_web/lib/web_audio_js.dart @@ -0,0 +1,57 @@ +import 'dart:html'; + +import 'package:js/js.dart'; + +@JS('AudioContext') +@staticInterop +class JsAudioContext { + external JsAudioContext(); +} + +extension JsAudioContextExtension on JsAudioContext { + external MediaElementAudioSourceNode createMediaElementSource( + AudioElement element,); + + external StereoPannerNode createStereoPanner(); + + external AudioNode get destination; +} + +@JS() +@staticInterop +abstract class AudioNode { + external AudioNode(); + +} + +extension AudioNodeExtension on AudioNode { + external AudioNode connect(AudioNode audioNode); +} + +@JS() +@staticInterop +class AudioParam { + external AudioParam(); +} + +extension AudioParamExtension on AudioParam { + external num value; +} + +@JS() +@staticInterop +class StereoPannerNode + implements AudioNode { + external StereoPannerNode(); +} + +extension StereoPannerNodeExtension on StereoPannerNode { + external AudioParam get pan; +} + +@JS() +@staticInterop +class MediaElementAudioSourceNode + implements AudioNode { + external MediaElementAudioSourceNode(); +} diff --git a/packages/audioplayers_web/lib/wrapped_player.dart b/packages/audioplayers_web/lib/wrapped_player.dart index 2bbe632cf..caf552b7f 100644 --- a/packages/audioplayers_web/lib/wrapped_player.dart +++ b/packages/audioplayers_web/lib/wrapped_player.dart @@ -4,6 +4,7 @@ import 'dart:html'; import 'package:audioplayers_platform_interface/api/release_mode.dart'; import 'package:audioplayers_platform_interface/streams_interface.dart'; import 'package:audioplayers_web/num_extension.dart'; +import 'package:audioplayers_web/web_audio_js.dart'; class WrappedPlayer { final String playerId; @@ -17,6 +18,7 @@ class WrappedPlayer { bool isPlaying = false; AudioElement? player; + StereoPannerNode? stereoPanner; StreamSubscription? playerTimeUpdateSubscription; StreamSubscription? playerEndedSubscription; StreamSubscription? playerLoadedDataSubscription; @@ -44,7 +46,7 @@ class WrappedPlayer { } void setBalance(double balance) { - throw UnimplementedError('setBalance is not currently implemented on Web'); + stereoPanner?.pan.value = balance; } void setPlaybackRate(double rate) { @@ -58,9 +60,18 @@ class WrappedPlayer { } final p = player = AudioElement(currentUrl); + p.crossOrigin = 'anonymous'; // need for stereo panning to work p.loop = shouldLoop(); p.volume = currentVolume; p.playbackRate = currentPlaybackRate; + + // setup stereo panning + final audioContext = JsAudioContext(); + final source = audioContext.createMediaElementSource(player!); + stereoPanner = audioContext.createStereoPanner(); + source.connect(stereoPanner!); + stereoPanner?.connect(audioContext.destination); + playerPlaySubscription = p.onPlay.listen((_) { streamsInterface.emitDuration( playerId, @@ -99,6 +110,7 @@ class WrappedPlayer { void release() { _cancel(); player = null; + stereoPanner = null; playerLoadedDataSubscription?.cancel(); playerLoadedDataSubscription = null; From a0281c3c992f28e4590b90aa163e4b9dfab3ed35 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Sat, 8 Oct 2022 19:06:44 +0200 Subject: [PATCH 2/6] docs: update parity table --- feature_parity_table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature_parity_table.md b/feature_parity_table.md index 7f01ecc92..3a2413c0b 100644 --- a/feature_parity_table.md +++ b/feature_parity_table.md @@ -59,7 +59,7 @@ Note: LLM means Low Latency Mode. stay awakeyes (except LLM)yesnononono recording activenot yetyesnononono playing routeyes (except LLM)yesnononono - balancenononoyesyesyes + balancenot yetnot yetnot yetyesyesyes Streams duration eventyesyesyesyesyesyes position eventyesyesyesyesyesyes From 911656b423bbb43d364d661f9b2c3ef4db6327de Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Sat, 8 Oct 2022 19:49:14 +0200 Subject: [PATCH 3/6] docs(web): explain cors value --- packages/audioplayers_web/lib/wrapped_player.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/audioplayers_web/lib/wrapped_player.dart b/packages/audioplayers_web/lib/wrapped_player.dart index caf552b7f..4905da4d4 100644 --- a/packages/audioplayers_web/lib/wrapped_player.dart +++ b/packages/audioplayers_web/lib/wrapped_player.dart @@ -60,7 +60,10 @@ class WrappedPlayer { } final p = player = AudioElement(currentUrl); - p.crossOrigin = 'anonymous'; // need for stereo panning to work + // As the AudioElement is created dynamically via script, + // features like 'stereo panning' need the CORS header to be enabled. + // See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS + p.crossOrigin = 'anonymous'; p.loop = shouldLoop(); p.volume = currentVolume; p.playbackRate = currentPlaybackRate; From b8990eed29bc56036dff815e676027d8958e9f17 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Sat, 8 Oct 2022 19:50:54 +0200 Subject: [PATCH 4/6] flutter format --- packages/audioplayers_web/lib/web_audio_js.dart | 10 ++++------ packages/audioplayers_web/lib/wrapped_player.dart | 6 +++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/audioplayers_web/lib/web_audio_js.dart b/packages/audioplayers_web/lib/web_audio_js.dart index b5589f121..c327d343c 100644 --- a/packages/audioplayers_web/lib/web_audio_js.dart +++ b/packages/audioplayers_web/lib/web_audio_js.dart @@ -10,7 +10,8 @@ class JsAudioContext { extension JsAudioContextExtension on JsAudioContext { external MediaElementAudioSourceNode createMediaElementSource( - AudioElement element,); + AudioElement element, + ); external StereoPannerNode createStereoPanner(); @@ -21,7 +22,6 @@ extension JsAudioContextExtension on JsAudioContext { @staticInterop abstract class AudioNode { external AudioNode(); - } extension AudioNodeExtension on AudioNode { @@ -40,8 +40,7 @@ extension AudioParamExtension on AudioParam { @JS() @staticInterop -class StereoPannerNode - implements AudioNode { +class StereoPannerNode implements AudioNode { external StereoPannerNode(); } @@ -51,7 +50,6 @@ extension StereoPannerNodeExtension on StereoPannerNode { @JS() @staticInterop -class MediaElementAudioSourceNode - implements AudioNode { +class MediaElementAudioSourceNode implements AudioNode { external MediaElementAudioSourceNode(); } diff --git a/packages/audioplayers_web/lib/wrapped_player.dart b/packages/audioplayers_web/lib/wrapped_player.dart index 4905da4d4..bbacad711 100644 --- a/packages/audioplayers_web/lib/wrapped_player.dart +++ b/packages/audioplayers_web/lib/wrapped_player.dart @@ -60,14 +60,14 @@ class WrappedPlayer { } final p = player = AudioElement(currentUrl); - // As the AudioElement is created dynamically via script, - // features like 'stereo panning' need the CORS header to be enabled. + // As the AudioElement is created dynamically via script, + // features like 'stereo panning' need the CORS header to be enabled. // See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS p.crossOrigin = 'anonymous'; p.loop = shouldLoop(); p.volume = currentVolume; p.playbackRate = currentPlaybackRate; - + // setup stereo panning final audioContext = JsAudioContext(); final source = audioContext.createMediaElementSource(player!); From a1a843ab330db9a3319f43a006640a1ff4b212be Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Sat, 8 Oct 2022 19:53:49 +0200 Subject: [PATCH 5/6] test(web): enable balance tests for web --- .../audioplayers/example/integration_test/platform_features.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/audioplayers/example/integration_test/platform_features.dart b/packages/audioplayers/example/integration_test/platform_features.dart index f9cdb717c..17b17fe36 100644 --- a/packages/audioplayers/example/integration_test/platform_features.dart +++ b/packages/audioplayers/example/integration_test/platform_features.dart @@ -14,7 +14,6 @@ class PlatformFeatures { hasRecordingActive: false, hasPlayingRoute: false, hasErrorEvent: false, - hasBalance: false, ); static const androidPlatformFeatures = PlatformFeatures( From 5f3ebbd994b70dd392296ab670f61048be07fa87 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Sat, 8 Oct 2022 20:16:05 +0200 Subject: [PATCH 6/6] fix(web): explicitly add js as dependency --- packages/audioplayers_web/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/audioplayers_web/pubspec.yaml b/packages/audioplayers_web/pubspec.yaml index 5290ccc56..7693ce9dc 100644 --- a/packages/audioplayers_web/pubspec.yaml +++ b/packages/audioplayers_web/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter + js: ^0.6.4 dev_dependencies: dartdoc: ^5.0.1