diff --git a/src/Builtins.jl b/src/Builtins.jl
index e663d591..4204614f 100644
--- a/src/Builtins.jl
+++ b/src/Builtins.jl
@@ -1,7 +1,7 @@
import Random: randstring
import Dates
-export Slider, NumberField, Button, CheckBox, TextField, PasswordField, Select, MultiSelect, Radio, FilePicker, DateField, TimeField, ColorStringPicker
+export Slider, NumberField, Button, CheckBox, TextField, PasswordField, Select, MultiSelect, Radio, FilePicker, DateField, TimeField, ColorStringPicker, Microphone
struct Slider
range::AbstractRange
@@ -149,7 +149,6 @@ end
get(textfield::TextField) = textfield.default
-
"""A password input (``) - the user can type text, the text is returned as `String` via `@bind`.
This does not provide any special security measures, it just renders black dots (•••) instead of the typed characters.
@@ -240,6 +239,13 @@ function show(io::IO, ::MIME"text/html", select::MultiSelect)
show(io, MIME"text/html"(), o.second)
else
print(io, o.second)
+ print(io, """")
end
@@ -253,6 +259,7 @@ get(select::MultiSelect) = ismissing(select.default) ? Any[] : select.default
The optional `accept` argument can be an array of `MIME`s. The user can only select files with these MIME. If only `image/*` MIMEs are allowed, then smartphone browsers will open the camera instead of a file browser.
## Examples
+get(select::Select) = ismissing(select.default) ? first(select.options).first : select.default
`@bind file_data FilePicker()`
@@ -336,6 +343,74 @@ end
get(radio::Radio) = radio.default
+struct Microphone end
+
+function show(io::IO, ::MIME"text/html", microphone::Microphone)
+ mic_id = randstring('a':'z')
+ mic_btn_id = randstring('a':'z')
+ microphone
+ withtag(() -> (), io, :audio, :id => mic_id)
+ print(io, """""")
+ withtag(io, :script) do
+ print(io, """
+ const player = document.getElementById('$mic_id');
+ const stop = document.getElementById('$mic_btn_id');
+
+ const handleSuccess = function(stream) {
+ const context = new AudioContext({ sampleRate: 44100 });
+ const analyser = context.createAnalyser();
+ const source = context.createMediaStreamSource(stream);
+
+ source.connect(analyser);
+
+ const bufferLength = analyser.frequencyBinCount;
+
+ let dataArray = new Float32Array(bufferLength);
+ let animFrame;
+
+ const streamAudio = () => {
+ animFrame = requestAnimationFrame(streamAudio);
+ analyser.getFloatTimeDomainData(dataArray);
+ player.value = dataArray;
+ player.dispatchEvent(new CustomEvent("input"));
+ }
+
+ streamAudio();
+
+ stop.onclick = e => {
+ source.disconnect(analyser);
+ cancelAnimationFrame(animFrame);
+ }
+ }
+
+ navigator.mediaDevices.getUserMedia({ audio: true, video: false })
+ .then(handleSuccess)
+ """
+ )
+ end
+ withtag(io, :style) do
+ print(io, """
+ .mic-button {
+ background-color: darkred;
+ border: none;
+ border-radius: 6px;
+ color: white;
+ padding: 15px 32px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ font-family: "Alegreya Sans", sans-serif;
+ margin: 4px 2px;
+ cursor: pointer;
+ }
+ """
+ )
+ end
+end
+
+get(microphone::Microphone) = microphone
+
"""A date input (``) - the user can pick a date, the date is returned as `Dates.DateTime` via `@bind`.
Use `default` to set the initial value.