From 949b7f27a348ed0dd1fba0101b9a0af30f421d64 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Sat, 8 Oct 2022 19:04:04 +0200 Subject: [PATCH] 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;