I am using web-share level 2 for my PWA app. Every media format is working fine except PDF. Web api is returning base64 string of PDF, At client side, I am creating blob object from it. but when I share it, Throws exception : Permission Denied
var file = new File(["/9j/4AAQSkZJRgABAQAAAQABAAD...."], 'filename.pdf', { type: 'application/pdf' });
var filesArray = [];
filesArray.push(file);
navigator['share']({files: filesArray})
.then(() => console.log('Share was successful.'))
.catch((error) => console.log('Sharing failed', error));
I don't have any clue whats going on.
For others who might encounter this problem, this was discussed on https://github.com/w3c/web-share/issues/141 and is a current limitation in Chrome tracked in https://crbug.com/1006055
Related
I'm working on a side project that is a music app. This requires me to play large files. I've figured out that there are a few solutions. One is to download the music to local storage and use that whenever it is available. I can also stream music over the network. I have been unsuccessful as it relates to the latter.
My question is, how do I play from a audio from a stream?
This is the code I have so far:
fetch(audio)
.then((res) => {
const reader = res.body.getReader()
const audioObj = new Audio()
reader.read().then(function processAudio({ done, value }) {
if (done) {
console.log("You got all the data")
return
}
/// DO SOMETHING WITH VALUE
return reader.read().then(processAudio)
})
})
.catch((err) => {
console.log(`Something went wrong ${err}`)
})
I have tried looking at other APIs within the browser, eg. MediaStream
Any help is much appreciated. Thank you.
There is a WebAudio API that might be useful. It appears to give you exactly what you need.
Web Audo API
Currently, as a requirement, if a user wishes to download a large zip file, the download is a streamed.
This is done by fetching an endpoint, then using Streamsaver.js to stream the download to their browser as shown below.
function download(id, fileName) {
const endpoint = `.../extract/downloads/zip_download/?id=${id}`;
return fetch(endpoint, requestOptions.get()).then(res => {
const downloadSize = res.headers.get("content-length");
const fileStream = createWriteStream(fileName, { size: downloadSize });
const writer = fileStream.getWriter();
if (res.body.pipeTo) {
writer.releaseLock();
return res.body.pipeTo(fileStream);
}
const reader = res.body.getReader();
const pump = () =>
reader
.read()
.then(({ value, done }) =>
done ? writer.close() : writer.write(value).then(pump)
);
return pump();
});
}
This works fine in Chrome, however I'm running into issues with Firefox and Safari. The issue I get is:
TypeError: undefined is not a constructor (evaluating 'new streamSaver.WritableStream')
What other methods are there of approaching this? Surely there must be a universal way to stream a download of a large that I'm missing?
I ran into the same issue and included the web-streams-polyfill package in my project to fix it. Currently, some non-chromium browsers appear to not support WritableStream.
For myself, I simply included this script tag in my index.html
<script src="https://unpkg.com/web-streams-polyfill/dist/polyfill.min.js"></script>
I'm having issues with storing blobs in IndexedDB on Safari version 10.1.2 (also facing the same issue on IOS).
I'm using the angular2-indexeddb module wrapper, however - i don't think it's a problem with the module as such. My code works fine in Chrome, however when attempting to put a blob object in the Safari indexedDb, the record always displays as 'null' (see FileData field):
I have tried a variety of different blob files (audio, video, html) and they always display as 'null'. No (visible) errors are returned from the IndexedDb when inserting this record.
From what i've read - blobs should be supported in Safari. I'm thinking the problem could be associated with the way the blob is created? i.e. perhaps Safari does not like the blob data?
Below is a sample of my code (i haven't included too much here, so please let me know if more information is required):
// create blob:
const aFileParts = ['<a id="a"><b id="b">foo!</b></a>'];
const oMyBlob = new Blob(aFileParts, {type : 'text/html'});
console.log('blob type' + oMyBlob.type); // outputs as 'text/html'
// initialize my indexeddb store:
return this.initializeStores().then(() => {
// add 'oMyBlob' to the FileData data store:
return this.db.add('FileData',
{ FileName: 'foo', FileData: oMyBlob, FileType: 'audio' }).then(() => {
// Success
console.log('added ' + 'foo' + ' to FileData store.');
// Get the file from the FileData store
return this.db.getByIndex('FileData', 'FileName', 'foo').then((record) => {
return Promise.resolve();
});
}, (error) => {
console.log(error);
this.handleError(error)
return Promise.reject(error);
});
}, (error) => {
this.handleError(error);
return Promise.reject(error);
});
as a side note - i can store this data as an ArrayBuffer in Safari IndexedDB without any issues. The problem is that i then have to convert this back to a blob when i retrieve it from the db (the extra processing power required is not ideal).
Any help is much appreciated.
So i managed to find the cause of the issue. When creating my store i was referencing an incorrect index (mainly because i was following an online tutorial)
const objectDataStore = evt.currentTarget.result.createObjectStore(
'FileData', { keyPath: 'id', autoIncrement: true });
The keypath 'id' does not exist within my store. This was causing the issues when saving the blobs (strangely with no reported error...and it didn't appear to cause issues on chrome).
The correct code:
const objectDataStore = evt.currentTarget.result.createObjectStore(
'FileData', { keyPath: 'FileName', autoIncrement: true });
'FileName' is the name of a property within my store object. This now fixes the issue on desktop safari. So the lesson here is to make sure the KeyPath is correct.
However, i now face a new issue. On IOS Safari the blob fails to persist to the indexedDb. I get the following error:
error: DOMError {name: "UnknownError", message: "An unknown error occurred within Indexed Database."}
So it appears that blobs are not supported for indexedDb on IOS Safari (i'm assuming this is a bug). For now i will just store ArrayBuffers instead of blobs.
Yes, I can confirm that on iOS Safari version 11.2.6, IndexedDB cannot store Blobs as values.
I wrote a small piece of code to store a String value and it works. If I comment out the String and replace it with a Blob, using the exact same code, Safari gives an "Unknown Error"
{name: "UnknownError", message: "An unknown error occurred within Indexed Database."}
Perhaps try to convert the Blob to an ArrayBuffer before storing it.
If You still have a problem with indexedDb on iOS/OSX just dont use auto-incrementation, becaues it's a bug in Safari.
While trying to determine a solution to How to use Web Speech API at chromium? found that
var voices = window.speechSynthesis.getVoices();
returns an empty array for voices identifier.
Not certain if lack of support at chromium browser is related to this issue Not OK, Google: Chromium voice extension pulled after spying concerns?
Questions:
1) Are there any workarounds which can implement the requirement of creating or converting audio from text at chromium browser?
2) How can we, the developer community, create an open source database of audio files reflecting both common and uncommon words; served with appropriate CORS headers?
There are several possible workarounds that have found which provide the ability to create audio from text; two of which require requesting an external resource, the other uses meSpeak.js by #masswerk.
Using approach described at Download the Audio Pronunciation of Words from Google, which suffers from not being able to pre-determine which words actually exist as a file at the resource without writing a shell script or performing a HEAD request to check if a network error occurs. For example, the word "do" is not available at the resource used below.
window.addEventListener("load", () => {
const textarea = document.querySelector("textarea");
const audio = document.createElement("audio");
const mimecodec = "audio/webm; codecs=opus";
audio.controls = "controls";
document.body.appendChild(audio);
audio.addEventListener("canplay", e => {
audio.play();
});
let words = textarea.value.trim().match(/\w+/g);
const url = "https://ssl.gstatic.com/dictionary/static/sounds/de/0/";
const mediatype = ".mp3";
Promise.all(
words.map(word =>
fetch(`https://query.yahooapis.com/v1/public/yql?q=select * from data.uri where url="${url}${word}${mediatype}"&format=json&callback=`)
.then(response => response.json())
.then(({query: {results: {url}}}) =>
fetch(url).then(response => response.blob())
.then(blob => blob)
)
)
)
.then(blobs => {
// const a = document.createElement("a");
audio.src = URL.createObjectURL(new Blob(blobs, {
type: mimecodec
}));
// a.download = words.join("-") + ".webm";
// a.click()
})
.catch(err => console.log(err));
});
<textarea>what it does my ninja?</textarea>
Resources at Wikimedia Commons Category:Public domain are not necessary served from same directory, see How to retrieve Wiktionary word content?, wikionary API - meaning of words.
If the precise location of the resource is known, the audio can be requested, though the URL may include prefixes other than the word itself.
fetch("https://upload.wikimedia.org/wikipedia/commons/c/c5/En-uk-hello-1.ogg")
.then(response => response.blob())
.then(blob => new Audio(URL.createObjectURL(blob)).play());
Not entirely sure how to use the Wikipedia API, How to get Wikipedia content using Wikipedia's API?, Is there a clean wikipedia API just for retrieve content summary? to get only the audio file. The JSON response would need to be parsed for text ending in .ogg, then a second request would need to be made for the resource itself.
fetch("https://en.wiktionary.org/w/api.php?action=parse&format=json&prop=text&callback=?&page=hello")
.then(response => response.text())
.then(data => {
new Audio(location.protocol + data.match(/\/\/upload\.wikimedia\.org\/wikipedia\/commons\/[\d-/]+[\w-]+\.ogg/).pop()).play()
})
// "//upload.wikimedia.org/wikipedia/commons/5/52/En-us-hello.ogg\"
which logs
Fetch API cannot load https://en.wiktionary.org/w/api.php?action=parse&format=json&prop=text&callback=?&page=hello. No 'Access-Control-Allow-Origin' header is present on the requested resource
when not requested from same origin. We would need to try to use YQL again, though not certain how to formulate the query to avoid errors.
The third approach uses a slightly modified version of meSpeak.js to generate the audio without making an external request. The modification was to create a proper callback for .loadConfig() method
fetch("https://gist.githubusercontent.com/guest271314/f48ee0658bc9b948766c67126ba9104c/raw/958dd72d317a6087df6b7297d4fee91173e0844d/mespeak.js")
.then(response => response.text())
.then(text => {
const script = document.createElement("script");
script.textContent = text;
document.body.appendChild(script);
return Promise.all([
new Promise(resolve => {
meSpeak.loadConfig("https://gist.githubusercontent.com/guest271314/8421b50dfa0e5e7e5012da132567776a/raw/501fece4fd1fbb4e73f3f0dc133b64be86dae068/mespeak_config.json", resolve)
}),
new Promise(resolve => {
meSpeak.loadVoice("https://gist.githubusercontent.com/guest271314/fa0650d0e0159ac96b21beaf60766bcc/raw/82414d646a7a7ef11bb04ddffe4091f78ef121d3/en.json", resolve)
})
])
})
.then(() => {
// takes approximately 14 seconds to get here
console.log(meSpeak.isConfigLoaded());
meSpeak.speak("what it do my ninja", {
amplitude: 100,
pitch: 5,
speed: 150,
wordgap: 1,
variant: "m7"
});
})
.catch(err => console.log(err));
one caveat of the above approach being that it takes approximately 14 and a half seconds for the three files to load before the audio is played back. However, avoids external requests.
It would be a positive to either or both 1) create a FOSS, developer maintained database or directory of sounds for both common and uncommon words; 2) perform further development of meSpeak.js to reduce load time of the three necessary files; and use Promise based approaches to provide notifications of the progress of of the loading of the files and readiness of the application.
In this users' estimation, it would be a useful resource if developers themselves created and contributed to an online database of files which responded with an audio file of the specific word. Not entirely sure if github is the appropriate venue to host audio files? Will have to consider the possible options if interest in such a project is shown.
I was wondering if it was possible to stream data from javascript to the browser's downloads manager.
Using webrtc, I stream data (from files > 1Gb) from a browser to the other. On the receiver side, I store into memory all this data (as arraybuffer ... so the data is essentially still chunks), and I would like the user to be able to download it.
Problem : Blob objects have a maximum size of about 600 Mb (depending on the browser) so I can't re-create the file from the chunks. Is there a way to stream these chunks so that the browser downloads them directly ?
if you want to fetch a large file blob from an api or url, you can use streamsaver.
npm install streamsaver
then you can do something like this
import { createWriteStream } from 'streamsaver';
export const downloadFile = (url, fileName) => {
return fetch(url).then(res => {
const fileStream = createWriteStream(fileName);
const writer = fileStream.getWriter();
if (res.body.pipeTo) {
writer.releaseLock();
return res.body.pipeTo(fileStream);
}
const reader = res.body.getReader();
const pump = () =>
reader
.read()
.then(({ value, done }) => (done ? writer.close() : writer.write(value).then(pump)));
return pump();
});
};
and you can use it like this:
const url = "http://urltobigfile";
const fileName = "bigfile.zip";
downloadFile(url, fileName).then(() => { alert('done'); });
Following #guest271314's advice, I added StreamSaver.js to my project, and I successfully received files bigger than 1GB on Chrome. According to the documentation, it should work for files up to 15GB but my browser crashed before that (maximum file size was about 4GB for me).
Note I: to avoid the Blob max size limitation, I also tried to manually append data to the href field of a <a></a> but it failed with files of about 600MB ...
Note II: as amazing as it might seem, the basic technique using createObjectURL works perfectly fine on Firefox for files up to 4GB !!