Connecting a simple gain node to Web Audio API file buffers - web-audio-api

I can't seem to understand why this isn't working. I have no errors.I have done this using oscillators and it works fine. FYI this is a continuation of this thread:
Using Web Audio API to assign two sounds to two Divs and play each independently via a click event with javascript
Code:
<div id="divElement"></div>
<style>
#divElement{
background-color: orange;
width:70px;
height:100px;
left:143px;
}
</style>
<script>
var context = new webkitAudioContext(),
savedBuffer;
var playAudioFile = function () {
var source = context.createBufferSource();
var gainNode = context.createGainNode();
source.buffer = savedBuffer;
// START OF PROBLEM AREA: Gain not working but file still plays with no errors.
source.connect(gainNode);
gainNode.connect(context.destination);
gainNode.gain = 0;
// END OF PROBLEM AREA
source.noteOn(0); // Play sound immediately
};
var request = new XMLHttpRequest();
request.open('get', 'audio/A.mp3', true);
request.responseType = 'arraybuffer';
request.onload = function () {
context.decodeAudioData(request.response,
function(incomingBuffer) {
savedBuffer = incomingBuffer;
var divElement = document.getElementById("divElement");
divElement.addEventListener("click", playAudioFile , false);
}
);
};
request.send();
</script>

Try gainNode.gain.value = 0 instead. gainNode.gain is an AudioGain object, which has the attribute value.
http://www.w3.org/TR/webaudio/#AudioGainNode

Related

DOMContentLoaded is not firing

I am trying to create a chrome extension but having problems with DOMContentLoaded as it is not firing.
Note: my code was taken from a different website.
Basically, I have create an HTML file with a button:
<head>
<title>GTmetrix Analyzer</title>
<script src="popup.js"></script>
</head>
<body>
<h1>GTmetrix Analyzer</h1>
<button id="checkPage">Check this page
now!</button>
</body>
And this is the JS file (popup.js):
document.addEventListener
('DOMContentLoaded',
function() {
console.log("f")
var checkPageButton =
document.getElementById('checkPage');
checkPageButton.addEventListener('click',
function() {
chrome.tabs.getSelected(null,
function(tab) {
d = document;
var f = d.createElement('form');
f.action = 'http://gtmetrix.com/analyze.html?bm';
f.method = 'post';
var i = d.createElement('input');
i.type = 'hidden';
i.name = 'url';
i.value = tab.url;
f.appendChild(i);
d.body.appendChild(f);
f.submit();
});
}, false);
}, false);
I added the console.log event in order to check if the event is executed, so this is how I verified that it isn't working.
I also added run_at": "document_start
but then I got
Uncaught TypeError: Cannot read property 'addEventListener' of null
For the "click" event, so I guess that the event was triggered before the button was created.
Help, please!

Upload File Angular 2

Currently playing around with Angular 2 RC.
Is there any tutorial/article that would help me understand uploading a file to the back-end via a REST call?
I've been through this, but it feels like there should be a more convenient way of doing it.
If you wait for RC2, you will have the ability to use other payloads than text ones.
For example Blob ones:
var headers = new Headers({'Content-Type': 'text/css'});
var body = new Blob(['body { color: red; }']);
return this.http.post('/url', body, { headers });
Arraybuffer ones:
var headers = new Headers({'Content-Type': 'text/css'});
var body = new ArrayBuffer(512);
var longInt8View = new Uint8Array(body);
for (var i = 0; i < longInt8View.length; i++) {
longInt8View[i] = i % 255;
}
return this.http.post('/url', body, { headers });
FormData ones:
var body = new FormData();
body.append('test1', 'val1');
body.append('test2', 123456);
var blob = new Blob(['body { color: red; }'], {type: 'text/css'});
body.append("userfile", blob);
return this.http.post('/url', body, { headers });
It would much easier to handle binary content for HTTP requests. Right now, it's difficult (and hacky) since Angular2 only accepts text payloads as input.

Chrome App FileReader

I'm trying to make use of the file system API in a Chrome App. I've tried all the sample code I can find and can't get a simple text file to read. I'm logging almost every step, and what seems to happen (or not happen) is everything stops the first time I reference a file reader object. It creates just fine, because I can log the .readyState, but after that I can't seem to even set an onload()event or execute a .readAsText().
Here's what I'm calling from a button:
function clickButton(){
chrome.fileSystem.chooseEntry({type: 'openFile', acceptsMultiple: false}, function(FileEntry){
if(chrome.runtime.lastError) {console.warn("Warning: " + chrome.runtime.lastError.message);}
else{
console.log(FileEntry);
var thing = new FileReader();
console.log(thing.readyState);
thing.onloadstart(function(){
console.log("Started loading " & FileEntry);
});
console.log("added onloadstart");
console.log(thing.readyState);
console.log(thing);
thing.readAsText(FileEntry);
console.log(thing.readyState);
console.log(thing.result);
}
});
document.getElementById("status").innerHTML = "I did something";
}
I did read somewhere that Chrome doesn't allow access to local files, but the chrome apps seem to be different. At least, the documentation seems to suggest that.
The only thing I end up with in my console is the FileEntry object.
https://developer.chrome.com/apps/app_storage#filesystem
I've used the example code right from the above link and still can't get it right. Anyone else have this issue or know what I'm doing wrong?
There is a difference between a FileEntry and a File. You need to call FileEntry's .file() method. So, replace
thing.readAsText(FileEntry);
with
FileEntry.file(function(File) {
thing.readAsText(File)
})
https://developer.mozilla.org/en-US/docs/Web/API/FileEntry#File
Try this code...
<!doctype html>
<html>
<script>
function handle_files(files) {
for (i = 0; i < files.length; i++) {
file = files[i]
console.log(file)
var reader = new FileReader()
ret = []
reader.onload = function(e) {
console.log(e.target.result)
}
reader.onerror = function(stuff) {
console.log("error", stuff)
console.log (stuff.getMessage())
}
reader.readAsText(file) //readAsdataURL
}
}
</script>
<body>
FileReader that works!
<input type="file" multiple onchange="handle_files(this.files)">
</body>
</html>
I've written a function to extract text from a file.
function getFileEntryText(fileEntry) {
return new Promise(function (resolve, reject) {
fileEntry.file(function (file) {
var fileReader = new FileReader();
fileReader.onload = function (text) {
resolve(fileReader.result);
};
fileReader.onerror = function () {
reject(fileReader.error);
};
fileReader.readAsText(file);
});
});
}
You can invoke this method like so:
getFileEntryText(fileEntry).then(function(text) {
// Process the file text here
}, function(error) {
// Handle the file error here
});
One thing I'm grappling with when working with the FileSystem is that every call is asynchronous. Having multiple levels of nested callbacks can make for code that's hard to read. I'm currently working around this by converting everything I can to a Promise.
for anyone who is interested, here's my final (working) code, complete with all the console.log()'s I needed to follow all those callbacks.
var chosenEntry = null;
function clickButton(){
console.log("Button clicked");
var accepts = [{
mimeTypes: ['text/*'],
extensions: ['js', 'css', 'txt', 'html', 'xml', 'tsv', 'csv', 'rtf']
}];
chrome.fileSystem.chooseEntry({type: 'openFile', accepts: accepts}, function(theEntry) {
if (!theEntry) {
output.textContent = 'No file selected.';
return;
}
// use local storage to retain access to this file
chrome.storage.local.set({'chosenFile': chrome.fileSystem.retainEntry(theEntry)});
console.log("local data set. calling loadFileEntry");
loadFileEntry(theEntry);
console.log("loadFileEntry called, returned to clickButton()");
});
}
function loadFileEntry(_chosenEntry) {
console.log("entered loadFileEntry()");
chosenEntry = _chosenEntry;
chosenEntry.file(function(file) {
readAsText(chosenEntry, function(result) {
console.log("running callback in readAsText");
document.getElementById('text').innerHTML = result;
console.log("I just tried to update textarea.innerHTML");
});
});
console.log("added function to chosenEntry.file()");
}
function readAsText(fileEntry, callback) {
console.log("readAsText called");
fileEntry.file(function(file) {
var reader = new FileReader();
console.log("Created reader as FileReader");
reader.onload = function(e) {
console.log("called reader.onload function");
callback(e.target.result);
};
console.log("calling reader.readAsText");
reader.readAsText(file);
});
}

HTML5 Video - currentTime not setting properly on iPhone

I have a basic HTML5 video set up from which I load one of four videos. The problem I'm having is that when I load the next video, it continues playing from the previous time position. Efforts to set the currentTime property seem to be either short lived or ignored entirely.
I have added listeners to a collection of events and have something like this in each one;
myPlayer.addEventListener("loadeddata", function() {
console.log(" loadeddata: before = " + myPlayer.currentTime);
myPlayer.currentTime = 0.1;
console.log(" loadeddata: after = " + myPlayer.currentTime);
}, false);
Sometimes I see the time change for one event but not persist correctly;
durationchange: before = 19.773332595825195
durationchange: after = 0.10000000149011612
loadedmetadata: before = 0.10000000149011612
loadedmetadata: after = 19.773332595825195
loadeddata: before = 19.773332595825195
loadeddata: after = 0.10000000149011612
canplay: before = 0.10000000149011612
canplay: after = 19.773332595825195
And sometimes it never even seems to set at all;
durationchange: before = 50.66666793823242
durationchange: after = 50.66666793823242
loadedmetadata: before = 50.66666793823242
loadedmetadata: after = 50.66666793823242
loadeddata: before = 50.66666793823242
loadeddata: after = 50.66666793823242
canplay: before = 50.66666793823242
canplay: after = 50.66666793823242
This seems similar to the issue here but there didn't seem to be any resolution. Has anyone encountered this issue on iPhone before?
TL;DR: change currentTime on the loadeddata event. This works for audio too.
It looks like Safari (and the problem is still appearing for me on Safari 11.1) is Safari will not allow currentTime to be changed when a video is first loaded IF it hasn't loaded a frame for that currentTime yet. The bigger problem: the wrong solution can break Chrome (and likely other browsers too).
Fortunately, we have a lot of events we can listen for while media is loading:
During the loading process of an audio/video, the following events occur, in this order:
loadstart
durationchange
loadedmetadata
loadeddata
progress
canplay
canplaythrough
-W3Schools (I know it's not a preferred source, but I couldn't find the same info on MDN)
I tried adjusting currentTime on different events, but on the first 3 events, Safari would move the time back to 0; and on 5 and 6 it seemed to prevent the video from playing in Chrome, because it would get stuck at currentTime (which I could've worked around, but I thought there was a better solution).
(I didn't want to have to load the whole file to go to the right spot, because I want to support hefty videos. So canplaythrough wasn't an option for me.)
The description for the loadeddata event reads:
The loadeddata event is fired when the first frame of the media has finished loading.
-MDN
When I changed currentTime on loadeddata, Safari could tell that the frame was loaded and available, and would update correctly. It also wouldn't cause Chrome to freeze in a single spot while playing.
The problem and solution are identical for audio.
From my findings the issue seems to be that on iPhone only (iPad works fine) the currentTime property will not be set correctly until the "canplaythrough" event, however changing the currentTime at that point will cause a noticeable hiccup. The solution for that would be to intentionally pause the video after calling load...
myVideo.load();
myVideo.pause();
...and then call play in the event when the time has reset.
The second problem however is when the duration of the new movie is shorter then the currentTime position. In this case not only does currentTime fail to set but "canplaythrough" is never called, and QT just sits at the end of the video doing nothing.
I discovered the solution to both problems was to force a secondary load if the currentTime was not reset in the event BEFORE "canplaythrough". Its a bit round about with the timer callback but it seems to do the trick;
var myVideo = document.getElementById("video1");
myVideo.addEventListener("canplay", function() {
console.log(" canplay: before = " + myVideo.currentTime);
myVideo.currentTime = 0.1;
console.log(" canplay: after = " + myVideo.currentTime);
if( myVideo.currentTime < 1 ) {
myVideo.play();
}
else {
myVideo.load();
myVideo.pause();
setTimeout(checkStarted, 500);
}
}, false);
function checkStarted()
{
console.log(" checkStarted called");
myVideo.play();
}
I had to set the preload attribute to metadata (on the HTML of the main video element) and set the currentTime of the video element within the loadedmetadata event listener.
<video id="myVideo" preload="metadata">
<source src="/path/to/video" type="video/mp4">
</video>
JS
VideoEl.addEventListener('loadedmetadata', VideoMetaDataLoaded);
function VideoMetaDataLoaded() {
VideoEl.currentTime = newTime;
}
Below is my Angular/Ionic solution to restore the video position. It works on IOS, Android, Chrome and Safari.
HTML:
<video preload="metadata"
poster="{{ resource.thumbnail_file }}"
playsinline webkit-playsinline
#videos>
...
</video>
Typescript:
#ViewChildren('videos') videos: QueryList<any>;
videoCache: Map<number, number> = new Map<number, number>();
private restoreVideoPositions() {
var context = this;
setTimeout(() => {
this.videos.forEach(function (item, idx) {
var video = item.nativeElement;
var currentTime = context.videoCache.get(video.id);
if (currentTime != null && currentTime > 0) {
console.log('add listener', currentTime);
video.addEventListener("loadeddata", function () {
if (video.readyState >= 3) {
video.currentTime = currentTime;
// video.play();
}
});
video.load();
}
});
}, 0);
}
private storeVideoPositions() {
var context = this;
this.videoCache.clear()
this.videos.forEach(function (item, idx) {
var video = item.nativeElement;
video.pause();
context.videoCache.set(video.id, video.currentTime)
});
}
I used a combination of the last two answers here, but had to reduce the ready state limit to 2 (HTMLVideoElement.prototype.HAVE_CURRENT_DATA) as the loadeddata event on iOS would often never come in higher than 2.
With help from the other answers here I came up with the next solution:
HTMLVideoElement.prototype.playFromTime = function(currentTime){
let that = this;
that.load();
that.pause();
that.currentTime = currentTime;
let loadedMetadata;
loadedMetadata = function(event){
that.currentTime = currentTime;
that.removeEventListener("loadedmetadata", loadedMetadata);
}
if(that.currentTime !== currentTime){
that.addEventListener("loadedmetadata", loadedMetadata);
}
that.play();
}
//usage example:
myVidElement.playFromTime(20);
Which in my situation works on an iPhone on Safari, Safari on the desktop, Firefox on macOS, Chrome on macOS. That's all I have tested for now.
Had the same problem with a play video on scroll with React. The following solved it for me.
useEffect(() => {
videoRef.current.load();
}, []);
const { render } = ReactDOM;
const { useState, useEffect, useRef } = React;
const Video = ({ src, height, length }) => {
const [currentTime, setCurrentTime] = useState(0);
const videoRef = useRef(null);
useEffect(() => {
// This is needed for IOS
videoRef.current.load();
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const getCurrentTime = () => {
const percentScrolled = window.scrollY / (height - window.innerHeight);
return length * percentScrolled;
};
const handleScroll = (e) => {
const time = getCurrentTime();
videoRef.current.currentTime = time;
setCurrentTime(time);
};
return (
<div style={{ height }}>
<p>Time: {currentTime.toFixed(2)}s</p>
<video ref={videoRef} muted playsInline>
<source src={src} type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
);
};
const App = () => {
return (
<Video
length={5}
height={3000}
src="https://lqez.github.io/js/airpodsvf/video.mp4"
/>
);
};
render(<App />, document.getElementById("root"));
body {
background: black;
}
p {
color: slategrey;
top: 0;
position: fixed;
z-index: 1;
padding: 20px;
}
video {
top: 60px;
position: fixed;
width: 100%;
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
this is a problem with chrome on local assets. happened with me. when i was serving video from local assets and setting time it resets the video to 0.
so the solution that worked for me was serving the video from s3 bucket.

Clickhandler on a feature in OpenLayers

I am trying to have a click event on a pop-up in openlayers. Right now I'm doing it with a hardcoded onclick in the feature:
var vector = new OpenLayers.Layer.Vector("Points",{
eventListeners:{
'featureselected':function(evt){
var feature = evt.feature;
var popup = new OpenLayers.Popup.Anchored("popup",
OpenLayers.LonLat.fromString(feature.geometry.toShortString()),
new OpenLayers.Size(275,71),
'<div id="pincontent" onclick="pindetails()"><h3>' + feature.attributes.title +'</h3><div style="display: none;" id="pindescription">'+ feature.attributes.content +'</div></div>',
null,
false
);
popup.imageSrc = 'img/popup.png';
popup.autoSize = false;
popup.backgroundColor = 'transparent';
var offset = {'size':new OpenLayers.Size(0,0),'offset':new OpenLayers.Pixel(-74,-10)};
popup.anchor = offset;
popup.panMapIfOutOfView = true;
popup.imageSize = new OpenLayers.Size(275,71);
popup.relativePosition = "br";
popup.calculateRelativePosition = function () {
return 'tr';
};
feature.popup = popup;
map.addPopup(popup);
//adding event listener
map.events.register('mousedown', popup, function(evt){alert('help')}, false);
},
'featureunselected':function(evt){
var feature = evt.feature;
map.removePopup(feature.popup);
feature.popup.destroy();
feature.popup = null;
}
}
});
But the main OpenLayers div is intercepting the click so I have to click twice.. I'm not sure if there's a way to disable this. I've looked at the openLayers documentation and I'm not sure how to use their API to add an event listener for a click on a feature.
Perhaps you should add return false; after your function call in onclick to stop event propagation (which is the default).
Also have a look at the register method:
this.map.events.register('click', this.map, function handleMapClick(e) { ... }
http://dev.openlayers.org/releases/OpenLayers-2.6/doc/apidocs/files/OpenLayers/Events-js.html#OpenLayers.Events.register
It seems that evt.stopPropagation(); does not work. But using return true; in the singleclick event handler can stop the Event-Propagation from penetrating down to cascaded layers.