Why won't web audio api oscillator play in Safari Mobile? - javascript

I have been trying to get an oscillator to play in a mobile browser on IOS (won't work in Chrome or Safari) and I am struggling. From the research I have done I have found that you must create the oscillator (and maybe even the context) on a touch event. What I have working on desktop is an oscillator that connects to a gain node and plays a sound on a mouseenter event on a span element. Then on a mouseout event it disconnects from the gain node so that on the next mouseenter event it will connect again thus being able to create a new sound every time a character is hovered on.
$(".hover-letters span").on("touchend mouseenter", function(){
audioVariables($(this));
});
$(".hover-letters span").on("touchstart mouseout", function(){
oscillator.disconnect(gainNode);
});
function audioVariables(element){
resizeWindowWidth = parseInt($(window).width());
frequency = mouseX/(resizeWindowWidth/600);
type = element.parent().data("type");
sound();
}
var firstLetterInteraction = true;
function sound() {
if (firstLetterInteraction === true){
audioCtx = new(window.AudioContext || window.webkitAudioContext)();
oscillator = audioCtx.createOscillator();
gainNode = audioCtx.createGain();
oscillator.start();
}
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.frequency.value = frequency;
oscillator.type = type;
firstLetterInteraction = false;
};
For some reason the sound just won't play on IOS and is not showing any errors. I'm beginning to wonder if this is possible as even examples such as CaptureWiz example here:
How do I make Javascript beep?
and the example given on the Web Audio API site: http://webaudioapi.com/samples/oscillator/ do not work for me on mobile. Anybody have any ideas?

This works for the oscillator
<script>
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var oscillator = audioCtx.createOscillator();
var gainNode = audioCtx.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.type = 'sine';
oscillator.frequency.value = 300;
gainNode.gain.value = 7.5;
</script>
<button onclick="oscillator.start();">play</button>

Related

HTML5 Video - Pause when get to middle of video

Aim: video clip is in 2 parts: first half of content is the playing forwards, the second half is the first half in reverse. The video plays when mouseover and will then pause at the halfway stage. On mouseout #1 if paused at the halfway stage it unpauses and plays to end: #2 if you mouseout for before it pauses it will jump to the corresponding timestamp in the 2nd half and play - eg if 10sec video and you mouse at 3sec mark it will jump to 7sec and play from there to the end. See Fiddle for a rough example:
https://jsfiddle.net/cucocreative/1n3Lyqd5/422/
or see this site for what we are trying toreplicate:
https://www.polestar.com/uk/polestar-2/ (first section under hero banner)
Fiddle has latest version of code (not below).
var myVideo=document.getElementById("video");
var halfway=0;
document.getElementById("halfway").innerHTML=halfway;
function Rewind() {
var duration = video.duration;
var currentTime = video.currentTime;
var jumpto = duration - currentTime;
var pause = duration/2;
document.getElementById("jumpto").innerHTML=jumpto;
myVideo.currentTime = jumpto;
myVideo.play();
}
function Play() {
if (myVideo.paused)
var duration = video.duration;
var pause = duration/2;
document.getElementById("pause").innerHTML=pause;
myVideo.play();
this.on('timeupdate', function () {
console.log(this.currentTime());
});
}
$("#video").on(
"timeupdate",
function(event){
var duration = video.duration;
var pause = duration/2;
onTrackedVideoFrame(this.currentTime, this.duration);
// Pause when hit the halfway stage
// check to see if halfway yet
// can't get the below to play nice with the rewind function
if(this.currentTime >= pause) {
//this.pause();
//var halfway = 1;
//document.getElementById("halfway").innerHTML=halfway;
}
});
function onTrackedVideoFrame(currentTime, duration){
$("#current").text(currentTime); //Change #current to currentTime
$("#duration").text(duration)
}
After a lot of deadends, I have figured out the rewind effect and can get it to pause at the halfway stage BUT when both blocks of code are together they don't play nice together so have commented out the pause code for the time being (see lines 36-40).
I can't figure out the logic to get the Rewind function to know if the video has gotten as far as being paused yet.
So currently, as long as you mouseout before the 2sec mark it works, after that's I need help with.
update: I did think that putting an if statement at line 12 to check if the currentTime is >= than the Duration would work but it's not.
if(this.currentTime >= pause) {
myVideo.currentTime = pause;
} else {
myVideo.currentTime = jumpto;
}
I managed to get a rough solution working, it probably needs a little fine tuning tho.
HTML
<div style="text-align:center" onmouseenter="Play()" onmouseleave="Rewind()">
<video
id="video"
onended="onVideoEnd()"
class="video"
width="480">
<source src="https://www.cucostaging.co.uk/side-reversed.mp4" type="video/mp4" />
Your browser does not support HTML5 video.
</video>
</div>
Script
//var myVideo=document.getElementById("video");
const myVideo = document.getElementById('video')
var status = 'STOP' // STOP | PLAY | REWIND
function Rewind() {
var currentTime = myVideo.currentTime;
var duration = myVideo.duration;
myVideo.currentTime = duration - currentTime;
status = 'REWIND'
myVideo.play();
}
function Play() {
var currentTime = myVideo.currentTime;
var duration = myVideo.duration;
let tempCurrent = status === 'STOP' ? 0 : duration - currentTime;
myVideo.currentTime = tempCurrent;
status = 'PLAY'
myVideo.play();
setTimeout(() => {
if(status === 'PLAY')
myVideo.pause()},
(2 - tempCurrent) * 1000)
}
function onVideoEnd() {
status = 'STOP'
}

THREE.js - PositionalAudio only on file sourced HTMLMediaElement

I have problems running Three.js - PositionalAudio.
HTMLMediaElements with a file as source working fine but with a MediaStream Object i´m not able to hear it spatial (just global).
This code here runs perfectly:
<audio id="test_audio" src="/music/test.mp3"></audio>
const listener = new THREE.AudioListener();
player_local.object.add(listener);
var voice_src = game.document.getElementById("test_audio");
var voice = new THREE.PositionalAudio( listener );
voice.setMediaElementSource( voice_src);
voice.setRefDistance( 5 );
voice.setDirectionalCone( 180, 230, 1 );
player_remote.object.add(voice);
voice_src.play();
Within this snippet, i can hear the sound, but not spatial:
// MediaStreamObject coming from Peer.js connection
const audio = document.createElement('audio');
audio.srcObject = stream;
const listener = new THREE.AudioListener();
player_local.object.add(listener);
var voice_src = game.document.getElementById(idOfStreamAudioElement);
var voice = new THREE.PositionalAudio( listener );
voice.setMediaElementSource(voice_src);
voice.setRefDistance( 5 );
voice.setDirectionalCone( 180, 230, 1 );
player_remote.object.add(voice);
voice_src.play();
I have already tried .setMediaStreamSource(voice_src.srcObject).
Any ideas why this does not work?

Record Low Volume Input from Microphone using Web Audio API

I'm trying to figure out how to use the Web Audio API to record low volume input from a mircophone. So essentially I'm looking to record in low frequencies or decibels that start from 0Hz to around 100Hz.
Any help would be appreciated. Thanks.
So this is what I've got so far:
if (!navigator.getUserMedia) {
navigator.getUserMedia = navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia;
}
navigator.getUserMedia({
audio: true
}, function(stream) {
var ctx = new AudioContext();
var source = ctx.createMediaStreamSource(stream);
var gainNode = ctx.createGain();
source.connect(gainNode);
gainNode.connect(ctx.destination);
document.getElementById('volume').onchange = function() {
gainNode.gain.value = this.value;
};
gainNode.gain.value = document.getElementById('volume').value;
new Audio().play();
}, function(e) {
alert(e);
});
// For the demo only:
document.getElementById('volume').onchange = function() {
alert('Please provide access to the microhone before using this.');
}
This is HTML control:
Volume: <input type=range id=volume min=0 max=100 value=50 step=0.01/>
From what I can tell, all I am doing with this code is lowering the output volume level from the microphone.
As I said, I am trying to capture low volume input from 0Hz to 100Hz.
If you want record just the frequencies between 0 and 100 Hz, use one or more BiquadFilterNodes or an IIRFilterNode to implement a lowpass filter with a cutoff of 100 Hz or so.
Generally, it's up to you to figure out the right filter, but perhaps this filter design page will be helpful. Use at your own risk!

HTML5 / JavaScript camera blurry, only on front facing camera, only on my app

I have built a visitor management system and have recently swapped to a surface device as the driver. The html5 webcam stream is showing as blurry / out of focus on the front facing camera. If I swap to the rear camera however it is fine. And if i use the front facing camera on another public site that uses another webcam feature, it works absolutely fine.
Here is a capture of the camera element, it looks like some form of deliberate blurring as appose to the camera just being bad...
https://ibb.co/Dzf67nC/
I have tried scanning through the code and cannot find anything that scales the camera stream at all that may cause bluring or focus changing
Below is my photo.js file that provides the stream to my visitor sign in page and also handles the capturing of screenshots.
// References to all the element we will need.
var video = document.querySelector('#camera-stream'),
image = document.querySelector('#snap'),
my_photo = document.querySelector('#my-photo'),
container = document.querySelector('.camera-container'),
//start_camera = document.querySelector('#start-camera'),
controls = document.querySelector('.controls'),
take_photo_btn = document.querySelector('#take-photo'),
delete_photo_btn = document.querySelector('#delete-photo'),
download_photo_btn = document.querySelector('#download-photo'),
imgeurl = document.querySelector('#imagesource'),
open_camera = document.querySelector('#open-camera'),
error_message = document.querySelector('#error-message');
// The getUserMedia interface is used for handling camera input.
// Some browsers need a prefix so here we're covering all the options
navigator.getMedia = ( navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
// Mobile browsers cannot play video without user input,
// so here we're using a button to start it manually.
open_camera.addEventListener("click", function(e){
if($('#my-photo').attr('src') == '') {
e.preventDefault();
container.classList.add("visible");
// Start video playback manually.
//video.play();
//showVideo();
if(!navigator.getMedia){
displayErrorMessage("Your browser doesn't have support for the navigator.getUserMedia interface.");
}
else{
// Request the camera.
navigator.getMedia(
{
video: true
},
// Success Callback
function(stream){
// Create an object URL for the video stream and
// set it as src of our HTLM video element.
video.srcObject=stream;
// Play the video element to start the stream.
video.play();
video.onplay = function() {
showVideo();
};
},
// Error Callback
function(err){
displayErrorMessage("There was an error with accessing the camera stream: " + err.name, err);
}
);
}
}
});
open_camera.click();
take_photo_btn.addEventListener("click", function(e){
e.preventDefault();
var count=4;
var counter=setInterval(timer, 500); //1000 will run it every 1 second
$('.countdown-container').addClass('visible');
function timer(){
count=count-1;
if (count <= 0)
{
$('.countdown-number').html('<i class="far fa-smile"></i>');
clearInterval(counter);
//counter ended, do something here
return;
}
$('.countdown-number').text(count);
//Do code for showing the number of seconds here
}
setTimeout(function(){
$('.countdown-container').removeClass('visible');
$('.countdown-number').text('Get Ready');
},2500);
setTimeout(function(){
video.pause(snap);
var snap = takeSnapshot();
// Show image.
image.setAttribute('src', snap);
imgeurl.value = snap;
image.classList.add("visible");
//tumbnail image
my_photo.setAttribute('src', snap);
my_photo.value = snap;
// Enable delete and save buttons
delete_photo_btn.classList.remove("disabled");
download_photo_btn.classList.remove("disabled");
take_photo_btn.classList.add("disabled");
},3000);
// Set the href attribute of the download button to the snap url.
// Pause video playback of stream.
});
delete_photo_btn.addEventListener("click", function(e){
e.preventDefault();
// Hide image.
image.setAttribute('src', "");
image.classList.remove("visible");
my_photo.setAttribute('src', "");
// Disable delete and save buttons
delete_photo_btn.classList.add("disabled");
download_photo_btn.classList.add("disabled");
take_photo_btn.classList.remove("disabled");
// Resume playback of stream.
video.play();
});
function showVideo(){
// Display the video stream and the controls.
//hideUI();
video.classList.add("visible");
controls.classList.add("visible");
}
function takeSnapshot(){
// Here we're using a trick that involves a hidden canvas element.
var hidden_canvas = document.querySelector('canvas'),
context = hidden_canvas.getContext('2d');
var width = video.videoWidth,
height = video.videoHeight;
if (width && height) {
// Setup a canvas with the same dimensions as the video.
hidden_canvas.width = width;
hidden_canvas.height = height;
// Make a copy of the current frame in the video on the canvas.
context.drawImage(video, 0, 0, width, height);
// Turn the canvas image into a dataURL that can be used as a src for our photo.
return hidden_canvas.toDataURL('image/png');
}
}
download_photo_btn.addEventListener("click", function(e){
e.preventDefault();
container.classList.remove("visible");
my_photo.classList.add("visible");
});
function displayErrorMessage(error_msg, error){
error = error || "";
if(error){
console.log(error);
}
error_message.innerText = error_msg;
hideUI();
error_message.classList.add("visible");
}
function hideUI(){
// Helper function for clearing the app UI.
controls.classList.remove("visible");
//start_camera.classList.remove("visible");
video.classList.remove("visible");
snap.classList.remove("visible");
error_message.classList.remove("visible");
}
The camera should be a lot crisper than it is. No errors or anything in the console.

adding sound "volume" to a bit of javascript code (audiocontext)

I have this little javascript that I have working, it plays a tone for 30 seconds. I wanted to see if it was possible to modify it so that it would play at say, half volume instead (it's just too loud). I have researched and found "gain" node information, but I looked at the examples and honestly I'm a noob with javascrip and I don't fully understand the examples I saw.
Here is what I have so far (it works good, I just want to "volume" it down some)
function myFunction(frequency, duration, callback) {
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var oscillator = audioCtx.createOscillator();
duration = 30000 / 1000; // the 10000 used to be 'duration'
oscillator.type = 'square';
oscillator.frequency.value = 500; // value in hertz
oscillator.connect(audioCtx.destination);
oscillator.onended = callback;
oscillator.start(0);
oscillator.stop(audioCtx.currentTime + duration);
}
Can anyone help me modify this so I have a volume parameter I can tweak until it is right?
Documentation
The documentation for what you are trying to do can be found under BaseAudioContext, specifically the BaseAudioContext.createGain() method.
The MDN documentation is lacking a little in that it only provides snippets that will not work as is. As such, an overly simplified example is given below, bearing in mind that this may not be best practice.
Explanation
The oscillator and gain control are both audio nodes. As such, you should picture them in a signal chain like that given below.
The oscillator node passes through the gain node which passes through to the audio context.
Solution
Using your current format, as self contain snippet is given below (see jsfiddle here)
<!DOCTYPE html>
<html>
<head>
<script>
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var gainNode = audioCtx.createGain();
gainNode.connect(audioCtx.destination);
var gain = 0.1;
//---------------------------------------------------------------------
gainNode.gain.setValueAtTime(gain, audioCtx.currentTime);
</script>
</head>
<!-- ===================================================================== -->
<body>
<div>
<button onclick="myFunction()">
Click me
</button>
</div>
<script>
function myFunction()
{
//---------------------------------------------------------------------
var duration = 0.5; // in seconds
//---------------------------------------------------------------------
var oscillator = audioCtx.createOscillator();
oscillator.type = 'square';
oscillator.frequency.value = 500;
oscillator.connect(gainNode);
oscillator.start(audioCtx.currentTime);
oscillator.stop(audioCtx.currentTime + duration);
//---------------------------------------------------------------------
}
</script>
</body>
<!-- ===================================================================== -->
</html>
Best practice
For best practice, I would suggest you should avoid recreating nodes over and over again. Rather, I would simply turn the gain up for a short period, then turn it back down, an example of which is given below.
As far as I can tell, there is no envelope generator node for WebAudio, but you could use .linear​Ramp​ToValue​AtTime() if needed.
NOTE: This is not currently working in Safari.
(see jsfiddle here)
<!DOCTYPE html>
<html>
<head>
<script>
//---------------------------------------------------------------------
// Edit These
var gain = 0.1;
var duration = 1000;
//---------------------------------------------------------------------
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var gainNode = audioCtx.createGain();
//---------------------------------------------------------------------
var oscillator = audioCtx.createOscillator();
oscillator.type = 'square';
oscillator.frequency.value = 500;
//---------------------------------------------------------------------
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
//---------------------------------------------------------------------
gainNode.gain.setValueAtTime(0.0, audioCtx.currentTime); // turned off by default
oscillator.start(audioCtx.currentTime);
//---------------------------------------------------------------------
</script>
</head>
<!-- ===================================================================== -->
<body>
<div>
<button onclick="soundOn()">
Click me
</button>
</div>
<script>
function mute()
{
gainNode.gain.setValueAtTime(0.0, audioCtx.currentTime);
}
function soundOn()
{
gainNode.gain.setValueAtTime(gain, audioCtx.currentTime);
setTimeout(mute,duration);
}
</script>
</body>
<!-- ===================================================================== -->
</html>
Further Resources
If you are struggling with interacting with audio context in this was, I would suggest trying out a library such as p5.js and the p5.sound library.
Look at the https://p5js.org/reference/#/p5.Oscillator to see whether it is a more intuitive approach.

Categories