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.