Issue with preloading assets to browser - javascript

I have a Nodejs App running on background and I have this code in frontend ( here it's simplified for you ) to preload videos , audios , etc ... to blobs like this:
function preloadOne(url, done) {
const xhr = new XMLHttpRequest();
xhrs.push(xhr);
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onprogress = event => {
if (!event.lengthComputable) return false;
let item = this.getItemByUrl(event.target.responseURL);
item.completion = parseInt((event.loaded / event.total) * 100);
item.downloaded = event.loaded;
item.total = event.total;
this.updateProgressBar(item);
};
xhr.onload = event => {
let type = event.target.response.type;
let blob = new Blob([event.target.response], {
type: type
});
let url = URL.createObjectURL(blob);
let responseURL = event.target.responseURL;
let item = this.getItemByUrl(responseURL);
item.blobUrl = url;
item.fileName = responseURL.substring(responseURL.lastIndexOf('/') + 1);
item.type = type;
item.size = blob.size;
done(item);
};
xhr.onerror = event => {}
xhr.send();
}
Everything is going fine until I reach 32mb transferred then the transfer gets really slow and for the next 10 mb to load I need more time than what I needed to reach 30MBs!!??
Sometimes and not all the times I get this error for loading a video too:
Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR
Here you can see the end of the fast part of the preloading where suddenly everything gets slow:
Any suggestions why this is happening and how to fix this is a good point to start and debug this and it's really appreciated ...
For me it seems something get's full after 30 MBs :)
UPDATE: it a more complete version of the code to have a look if you need it:
async function Preloading() {
preloadedPercent = 0;
setTimeout(() => startBlink(), 1500);
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof definer === 'function' && definer.amd ? definer(factory) :
(global.Preload = factory());
}(this, (function () {
'use strict';
function preloadOne(url, done) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onprogress = event => {
if(!event.lengthComputable) return false
let item = this.getItemByUrl(event.target.responseURL);
item.completion = parseInt((event.loaded / event.total) * 100);
item.downloaded = event.loaded;
item.total = event.total;
this.updateProgressBar(item);
};
xhr.onload = event => {
let type = event.target.response.type;
let blob = new Blob([event.target.response], { type: type });
let url = URL.createObjectURL(blob);
let responseURL = event.target.responseURL;
let item = this.getItemByUrl(responseURL);
item.blobUrl = url;
item.fileName = responseURL.substring(responseURL.lastIndexOf('/') + 1);
item.type = type;
item.size = blob.size;
done(item);
};
xhr.onerror = event => {
console.log('Error Happend in Preloading');
reload();
};
xhr.send();
}
function updateProgressBar(item) {
var sumCompletion = 0;
var maxCompletion = this.status.length * 100;
for(var itemStatus of this.status) {
if(itemStatus.completion) {
sumCompletion += itemStatus.completion;
}
}
var totalCompletion = parseInt((sumCompletion / maxCompletion) * 100);
if(!isNaN(totalCompletion)) {
this.onprogress({
progress: totalCompletion,
item: item
});
}
}
function getItemByUrl(rawUrl) {
for (var item of this.status) {
if (item.url == rawUrl) return item
}
}
function fetch(list) {
return new Promise((resolve, reject) => {
this.loaded = list.length;
for (let item of list) {
this.status.push({ url: item });
this.preloadOne(item, item => {
this.onfetched(item);
this.loaded--;
if (this.loaded == 0) {
this.oncomplete(this.status);
resolve(this.status);
}
});
}
})
}
function Preload() {
return {
status: [],
loaded: false,
onprogress: () => {},
oncomplete: () => {},
onfetched: () => {},
fetch,
updateProgressBar,
preloadOne,
getItemByUrl
}
}
return Preload;
})));
const preload = Preload();
preload.fetch(preloadingData.preloadURLs).then(items => {});
preload.oncomplete = items => {
Media = generateMedia(items);
Blobs = generateBlobs(items);
sfx = getSfx();
preloadProgress(100, true);
preloadingData = null;
}
preload.onprogress = event => { preloadProgress(event.progress); }
preload.onfetched = item => {}
}
function preloadProgress(value, done) {
if(value > preloadedPercent || done) {
preloadedPercent = value;
checkRestart = 0;
checkLatency(value);
progressBar(value, done);
}
}

Related

How to lower the workload of Nodejs app server

Using function below I preload the videos, voices, pictures data as blobs to users browser:
Note that this is a simple XMLHttpRequest to get the files and store the blob in the browser of the user and if you rather not to look at the code it's ok please continue reading the question itself:
async function Preloading() {
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof definer === 'function' && definer.amd ? definer(factory) :
(global.Preload = factory());
}(this, (function () {
'use strict';
function preloadOne(url, done) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onprogress = event => {
if(!event.lengthComputable) return false
let item = this.getItemByUrl(event.target.responseURL);
item.completion = parseInt((event.loaded / event.total) * 100);
item.downloaded = event.loaded;
item.total = event.total;
this.updateProgressBar(item);
};
xhr.onload = event => {
let type = event.target.response.type;
let blob = new Blob([event.target.response], { type: type });
let url = URL.createObjectURL(blob);
let responseURL = event.target.responseURL;
let item = this.getItemByUrl(responseURL);
item.blobUrl = url;
item.fileName = responseURL.substring(responseURL.lastIndexOf('/') + 1);
item.type = type;
item.size = blob.size;
done(item);
};
xhr.onerror = event => {
console.log('Error Happend in Preloading');
reload();
};
xhr.send();
}
function updateProgressBar(item) {
var sumCompletion = 0;
var maxCompletion = this.status.length * 100;
for(var itemStatus of this.status) {
if(itemStatus.completion) {
sumCompletion += itemStatus.completion;
}
}
var totalCompletion = parseInt((sumCompletion / maxCompletion) * 100);
if(!isNaN(totalCompletion)) {
this.onprogress({
progress: totalCompletion,
item: item
});
}
}
function getItemByUrl(rawUrl) {
for (var item of this.status) {
if (item.url == rawUrl) return item
}
}
function fetch(list) {
return new Promise((resolve, reject) => {
this.loaded = list.length;
for (let item of list) {
this.status.push({ url: item });
this.preloadOne(item, item => {
this.onfetched(item);
this.loaded--;
if (this.loaded == 0) {
this.oncomplete(this.status);
resolve(this.status);
}
});
}
})
}
function Preload() {
return {
status: [],
loaded: false,
onprogress: () => {},
oncomplete: () => {},
onfetched: () => {},
fetch,
updateProgressBar,
preloadOne,
getItemByUrl
}
}
return Preload;
})));
const preload = Preload();
preload.fetch(preloadingData.preloadURLs).then(items => {});
preload.oncomplete = items => {
Blobs = generateBlobs(items); // HERE WE SAVE BLOBS FOR USERS
}
preload.onprogress = event => { preloadProgress(event.progress); }
preload.onfetched = item => {}
}
The issue is when multiple users try to download their data using the above function, sometimes the function doesn't respond to them and user will miss some of the blobs ( some blobs will not be created if server is busy )
Now I have a straight question:
What if I place my files ( video, voices, pictures, etc..) in a download server, and my Nodejs app running in another so that downloading the files does not impact the application server ? does this fix the issue and lowers the workload of the application's server ?

React Native Fetch video file by chunk / YouTube Data API chunk(multipart) upload

I made a YouTube API upload app. It works great with small video file sizes but with larger sizes my app crashes. The exception happens when I try to get the video file with Fetch().
Question: Is there a way I can fetch a large file in React Native and feed it into the YouTube API in smaller chunks?
Here is my fetch code:
const fetchResponse = await fetch(videoUri);
const blob = await fetchResponse.blob();
var file = new File([blob], "video.mp4", {type: "video/mp4"});
My upload YouTube code is taken from the following git repos - supposedly supports multipart upload as well:
https://github.com/youtube/api-samples/blob/master/javascript/cors_upload.js and
https://github.com/youtube/api-samples/blob/master/javascript/upload_video.js
Here is my full upload code:
uploadVideo = async function() {
var match = this.state.match.value;
var video = match.mergedVideo;
var players = match.players;
var scoreboard = this.state.match.value.scoreboard;
var points = match.points;
var title = players[0].name + " vs. " + players[1].name + " " + scoreboard;
var description = this.descriptionBuilder(points, match.videos);
/*const fetchResponse = await fetch(video);
const blob = await fetchResponse.blob();
var file = new File([blob], "video.mp4", {type: "video/mp4"});
console.log(file);*/
const file = await DocumentPicker.pick({
type: [DocumentPicker.types.video],
});
var metadata = {
snippet: {
title: title,
description: description,
tags: ['youtube-cors-upload'],
categoryId: 22
},
status: {
privacyStatus: 'unlisted'
}
};
var uploader = new MediaUploader({
baseUrl: 'https://www.googleapis.com/upload/youtube/v3/videos',
file: file,
token: this.state.user.auth.accessToken,
metadata: metadata,
chunkSize: 1024 * 1024,
params: {
part: Object.keys(metadata).join(',')
},
onError: function(data) {
console.log(data);
var message = data;
try {
var errorResponse = JSON.parse(data);
message = errorResponse.error.message;
} finally {
alert(message);
}
}.bind(this),
onProgress: function(data) {
var currentTime = Date.now();
var bytesUploaded = data.loaded;
var totalBytes = data.total;
var bytesPerSecond = bytesUploaded / ((currentTime - window.uploadStartTime) / 1000);
var estimatedSecondsRemaining = (totalBytes - bytesUploaded) / bytesPerSecond;
var percentageComplete = (bytesUploaded * 100) / totalBytes;
this.setState({ youtubeUploadProgress: percentageComplete / 100});
console.log("Uploaded: " + bytesUploaded + " | Total: " + totalBytes + " | Percentage: " + percentageComplete + " | Esitmated seconds remaining: " + estimatedSecondsRemaining);
}.bind(this),
onComplete: function(data) {
console.log("Complete");
alert("Upload successful!");
this.setState({ youtubeUploadProgress: 0});
}.bind(this)
});
window.uploadStartTime = Date.now();
uploader.upload();
}
and this is my cors_upload.js in React Native class module:
import React, { Component } from 'react';
export default class MediaUploader extends Component {
constructor(props) {
super(props);
const obj = this;
const DRIVE_UPLOAD_URL = 'https://www.googleapis.com/upload/drive/v2/files/';
var options = props;
var noop = function() {};
this.file = options.file;
this.contentType = options.contentType || this.file.type || 'application/octet-stream';
this.metadata = options.metadata || {
'title': this.file.name,
'mimeType': this.contentType
};
this.token = options.token;
this.onComplete = options.onComplete || noop;
this.onProgress = options.onProgress || noop;
this.onError = options.onError || noop;
this.offset = options.offset || 0;
this.chunkSize = options.chunkSize || 0;
//this.retryHandler = new RetryHandler();
//this.retryHandler = new obj.RetryHandler();
this.interval = 1000; // Start at one second
this.maxInterval = 60 * 1000;
this.url = options.url;
if (!this.url) {
var params = options.params || {};
params.uploadType = 'resumable';
//this.url = this.buildUrl_(options.fileId, params, options.baseUrl);
this.url = obj.buildUrl_(options.fileId, params, options.baseUrl);
}
this.httpMethod = options.fileId ? 'PUT' : 'POST';
}
retry = function(fn) {
setTimeout(fn, this.interval);
this.interval = this.nextInterval_();
};
reset = function() {
this.interval = 1000;
};
nextInterval_ = function() {
var interval = this.interval * 2 + this.getRandomInt_(0, 1000);
return Math.min(interval, this.maxInterval);
};
getRandomInt_ = function(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
};
buildQuery_ = function(params) {
params = params || {};
return Object.keys(params).map(function(key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
}).join('&');
};
buildUrl_ = function(id, params, baseUrl) {
var url = baseUrl || DRIVE_UPLOAD_URL;
if (id) {
url += id;
}
var query = this.buildQuery_(params);
if (query) {
url += '?' + query;
}
return url;
};
upload = function() {
//var self = this;
console.log("UPLOAD called", this.file.size);
var xhr = new XMLHttpRequest();
xhr.open(this.httpMethod, this.url, true);
xhr.setRequestHeader('Authorization', 'Bearer ' + this.token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('X-Upload-Content-Length', this.file.size);
xhr.setRequestHeader('X-Upload-Content-Type', this.contentType);
xhr.onload = function(e) {
console.log("ON LOAD CALLED");
if (e.target.status < 400) {
var location = e.target.getResponseHeader('Location');
this.url = location;
this.sendFile_();
} else {
this.onUploadError_(e);
}
}.bind(this);
xhr.onerror = this.onUploadError_.bind(this);
xhr.send(JSON.stringify(this.metadata));
};
sendFile_ = function() {
console.log("SEND FILE CALLED");
var content = this.file;
var end = this.file.size;
if (this.offset || this.chunkSize) {
// Only bother to slice the file if we're either resuming or uploading in chunks
if (this.chunkSize) {
end = Math.min(this.offset + this.chunkSize, this.file.size);
}
content = content.slice(this.offset, end);
}
var xhr = new XMLHttpRequest();
xhr.open('PUT', this.url, true);
xhr.setRequestHeader('Content-Type', this.contentType);
xhr.setRequestHeader('Content-Range', 'bytes ' + this.offset + '-' + (end - 1) + '/' + this.file.size);
xhr.setRequestHeader('X-Upload-Content-Type', this.file.type);
if (xhr.upload) {
xhr.upload.addEventListener('progress', this.onProgress);
}
xhr.onload = this.onContentUploadSuccess_.bind(this);
xhr.onerror = this.onContentUploadError_.bind(this);
xhr.send(content);
};
resume_ = function() {
var xhr = new XMLHttpRequest();
xhr.open('PUT', this.url, true);
xhr.setRequestHeader('Content-Range', 'bytes */' + this.file.size);
xhr.setRequestHeader('X-Upload-Content-Type', this.file.type);
if (xhr.upload) {
xhr.upload.addEventListener('progress', this.onProgress);
}
xhr.onload = this.onContentUploadSuccess_.bind(this);
xhr.onerror = this.onContentUploadError_.bind(this);
xhr.send();
};
extractRange_ = function(xhr) {
var range = xhr.getResponseHeader('Range');
if (range) {
this.offset = parseInt(range.match(/\d+/g).pop(), 10) + 1;
}
};
onContentUploadSuccess_ = function(e) {
if (e.target.status == 200 || e.target.status == 201) {
this.onComplete(e.target.response);
} else if (e.target.status == 308) {
this.extractRange_(e.target);
this.reset();
this.sendFile_();
}
};
onContentUploadError_ = function(e) {
if (e.target.status && e.target.status < 500) {
this.onError(e.target.response);
} else {
this.retry(this.resume_.bind(this));
}
};
onUploadError_ = function(e) {
this.onError(e.target.response); // TODO - Retries for initial upload
};
}
UPDATE 1:
To avoid using Fetch() I decided to use React Native Document Picker. Now I can select the video file and pass it to the MediaUploader following this guide: https://alishavineeth.medium.com/upload-a-video-from-a-mobile-device-to-youtube-using-react-native-eb2fa54a7445
Now if I set the chunkSize option I will receive a .slice array exception because the object structure doesn't match. If I pass the file without the chunkSize option the metadata uploads to YouTube but the video status will be stuck on processing without any other errors. The video upload process never begins.
DocumentPicker responds with the following object after I select my file:
[{"fileCopyUri": "content://com.android.providers.media.documents/document/video%3A7853", "name": "video_1629795128339.mp4", "size": 192660773, "type": "video/mp4", "uri": "content://com.android.providers.media.documents/document/video%3A7853"}]
UPDATE 2:
Managed to fix my DocumentPicker file issue(from my Update 1) with changing React Native Document Picker to Expo Document Picker.
Now I am able to select large files and call the upload function - the metadata uploads, the video file begins to upload as well but the app crashes during the upload. If I set the chunkSize option on the MediaUploader object I get [TypeError: content.slice is not a function. (In 'content.slice(this.offset, end)', 'content.slice' is undefined)]
Expo Document Picker responds with the following object after I select my video file:
{"name": "video_1629801588164.mp4", "size": 5799179, "type": "video/mp4", "uri": "file:///data/user/0/com.tennis.rec/cache/DocumentPicker/8b350fbf-1b66-4a78-a10f-b61eb2ed3032.mp4"}
UPDATE 3 - RESOLVED!!!
The chunk upload is working now!!! I modified my cors_upload.js file where the chunkSize is being evaluated and sliced with the following code:
if (this.offset || this.chunkSize) {
// Only bother to slice the file if we're either resuming or uploading in chunks
if (this.chunkSize) {
end = Math.min(this.offset + this.chunkSize, this.file.size);
}
console.log("CONTENT SLICE", this.offset, end, this.file.size);
//content = content.slice(this.offset, end);
var base64 = await RNFS.read(this.file.uri, this.chunkSize, this.offset, 'base64');
content = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
}
I added React Native File System and I am using its read() function to load the chunk as base64 and convert it back to a byte array.

What in this IndexedDB code is causing the device storage to fill up to 1.6GB?

Our application that utilizes IndexedDB to track tallies of data over periods of time. In using the app for a number of months, it seems that there is some sort of storage leak causing a relatively small amount of data to require 1.6 GB of storage on the device?
The app has a feature to download its full data as a ZIP file, and that file is under 50KB. So there must be a long-term leak somewhere. What we're trying now is to download the data, clear storage, and re-import to track down where this leak might be.
However, looking at the following code that interacts with IndexedDB, it's not obvious to me where that leak may be:
// Initializing the Database
if(!dbengine){
dbengine = this;
var request = window.indexedDB.open("DB", 1);
request.onerror = function(event) {
throw new Error("Error opening indexedDb")
};
// This creates the schema the first time around
request.onupgradeneeded = (event) => {
event.target.result.createObjectStore("db", { keyPath: 'id' });
};
request.onsuccess = (event) => {
dbengine.db = event.target.result;
var dbReadyEvent = new CustomEvent('db-ready', {});
document.dispatchEvent(dbReadyEvent);
dbengine.db.onerror = function(event) {
throw new Error("Database error: " + event.target.errorCode);
};
dbengine.db.onabort = function(event) {
throw new Error("Database aborted: " + event.target.error);
// This is the error that is being called for us - QuotaExceededError
};
};
}// if !dbengine
var initialTransaction = dbengine.db.transaction(['db'], 'readwrite');
initialTransaction.onabort = (event) => {
var error = event.target.error; // DOMError
alert(error.name);
} // onabort
initialTransaction.objectStore('db').openCursor().onsuccess = (event) => {
var cursor = event.target.result;
if(cursor) {
this.encryption.loadIdentity((success) => {
if(success) {
this.loadDb(() => {
document.dispatchEvent(new CustomEvent('api-ready', {}));
setInterval(this.syncDb.bind(this), this.syncInterval);
this.syncDb();
});
} else {
document.dispatchEvent(new CustomEvent('api-fail', {}));
}
});
} else {
initialTransaction.objectStore('db').add({
/* [...] */
});
document.dispatchEvent(new CustomEvent('db-created', {}));
} // if/else cursor
}; // objectStore
loadDb(callback) {
this._tallies = [];
this._collections = [];
var dbStore = dbengine.db.transaction(['db'], 'readwrite').objectStore('db');
dbStore.get(1).onsuccess = (event) => {
var result = event.target.result;
var tallies = result.tallies;
if(result.tallies.length !== 0) {
tallies = this._encryption.decrypt(result.tallies);
}
tallies.forEach(((t) => {
/* [...] */
}));
var collections = result.collections;
if(collections.length !== 0) {
collections = this._encryption.decrypt(result.collections);
}
collections.forEach(((c) => {
/* [...] */
}));
callback()
}
} // loadDb
logout() {
var dbStore = dbengine.db.transaction(['db'], 'readwrite').objectStore('db');
var request = dbStore.get(1);
request.onsuccess = function(e) {
delete e.currentTarget.result.identity;
dbStore.put(e.currentTarget.result);
redirectTo('welcome');
};
} // logout
syncDb() {
var tallyDataString = JSON.stringify(this.tallies);
var collectionDataString = JSON.stringify(this.collections);
var encryptedTallyDataString = this._encryption.encrypt(tallyDataString);
var encryptedCollectionDataString = this._encryption.encrypt(collectionDataString);
var dbStore = dbengine.db.transaction(['db'], 'readwrite').objectStore('db');
dbStore.get(1).onsuccess = (event) => {
var data = event.target.result;
if(data.tallies !== encryptedTallyDataString) {
data.tallies = encryptedTallyDataString;
dbStore.put(data);
}
if(data.collections !== encryptedCollectionDataString) {
data.collections = encryptedCollectionDataString;
dbStore.put(data);
}
var syncEvent = new CustomEvent('sync', {});
document.dispatchEvent(syncEvent);
} // onsuccess
dbStore.get(1).onabort = (event) => {
var error = event.target.error; // DOMError
// if (error.name == 'QuotaExceededError') {
// alert('Sync failed. Quota exceeded.')
// }
alert(error.name);
} // onabort
} // syncDb
loadIdentity(callback) {
var dbStore = dbengine.db.transaction(['db'], 'readwrite').objectStore('db');
dbStore.get(1).onsuccess = (event) => {
var result = event.target.result;
if(!result.identity || !result.identity.publicKey || !result.identity.privateKey) {
if(window.location.pathname !== "/app/welcome/") {
redirectTo('welcome');
}
}
this.identity = {
/* [...] */
};
var solid = false;
if(result.identity.publicKey && result.identity.privateKey) {
solid = true;
}
callback(solid);
};
} // loadIdentity
setIdentity(identity, username) {
var dbStore = dbengine.db.transaction(['db'], 'readwrite').objectStore('db');
var getRequest = dbStore.get(1);
getRequest.onsuccess = (event) => {
var result = event.target.result;
result.identity = {
/* [...] */
};
this.identity = identity;
var request = dbStore.put(result);
} // getRequest
} // setIdentity

How to decode only part of the mp3 for use with WebAudio API?

In my web application, I have a requirement to play part of mp3 file. This is a local web app, so I don't care about downloads etc, everything is stored locally.
My use case is as follows:
determine file to play
determine start and stop of the sound
load the file [I use BufferLoader]
play
Quite simple.
Right now I just grab the mp3 file, decode it in memory for use with WebAudio API, and play it.
Unfortunately, because the mp3 files can get quite long [30minutes of audio for example] the decoded file in memory can take up to 900MB. That's a bit too much to handle.
Is there any option, where I could decode only part of the file? How could I detect where to start and how far to go?
I cannot anticipate the bitrate, it can be constant, but I would expect variable as well.
Here's an example of what I did:
http://tinyurl.com/z9vjy34
The code [I've tried to make it as compact as possible]:
var MediaPlayerAudioContext = window.AudioContext || window.webkitAudioContext;
var MediaPlayer = function () {
this.mediaPlayerAudioContext = new MediaPlayerAudioContext();
this.currentTextItem = 0;
this.playing = false;
this.active = false;
this.currentPage = null;
this.currentAudioTrack = 0;
};
MediaPlayer.prototype.setPageNumber = function (page_number) {
this.pageTotalNumber = page_number
};
MediaPlayer.prototype.generateAudioTracks = function () {
var audioTracks = [];
var currentBegin;
var currentEnd;
var currentPath;
audioTracks[0] = {
begin: 4.300,
end: 10.000,
path: "example.mp3"
};
this.currentPageAudioTracks = audioTracks;
};
MediaPlayer.prototype.show = function () {
this.mediaPlayerAudioContext = new MediaPlayerAudioContext();
};
MediaPlayer.prototype.hide = function () {
if (this.playing) {
this.stop();
}
this.mediaPlayerAudioContext = null;
this.active = false;
};
MediaPlayer.prototype.play = function () {
this.stopped = false;
console.trace();
this.playMediaPlayer();
};
MediaPlayer.prototype.playbackStarted = function() {
this.playing = true;
};
MediaPlayer.prototype.playMediaPlayer = function () {
var instance = this;
var audioTrack = this.currentPageAudioTracks[this.currentAudioTrack];
var newBufferPath = audioTrack.path;
if (this.mediaPlayerBufferPath && this.mediaPlayerBufferPath === newBufferPath) {
this.currentBufferSource = this.mediaPlayerAudioContext.createBufferSource();
this.currentBufferSource.buffer = this.mediaPlayerBuffer;
this.currentBufferSource.connect(this.mediaPlayerAudioContext.destination);
this.currentBufferSource.onended = function () {
instance.currentBufferSource.disconnect(0);
instance.audioTrackFinishedPlaying()
};
this.playing = true;
this.currentBufferSource.start(0, audioTrack.begin, audioTrack.end - audioTrack.begin);
this.currentAudioStartTimeInAudioContext = this.mediaPlayerAudioContext.currentTime;
this.currentAudioStartTimeOffset = audioTrack.begin;
this.currentTrackStartTime = this.mediaPlayerAudioContext.currentTime - (this.currentTrackResumeOffset || 0);
this.currentTrackResumeOffset = null;
}
else {
function finishedLoading(bufferList) {
instance.mediaPlayerBuffer = bufferList[0];
instance.playMediaPlayer();
}
if (this.currentBufferSource){
this.currentBufferSource.disconnect(0);
this.currentBufferSource.stop(0);
this.currentBufferSource = null;
}
this.mediaPlayerBuffer = null;
this.mediaPlayerBufferPath = newBufferPath;
this.bufferLoader = new BufferLoader(this.mediaPlayerAudioContext, [this.mediaPlayerBufferPath], finishedLoading);
this.bufferLoader.load();
}
};
MediaPlayer.prototype.stop = function () {
this.stopped = true;
if (this.currentBufferSource) {
this.currentBufferSource.onended = null;
this.currentBufferSource.disconnect(0);
this.currentBufferSource.stop(0);
this.currentBufferSource = null;
}
this.bufferLoader = null;
this.mediaPlayerBuffer = null;
this.mediaPlayerBufferPath = null;
this.currentTrackStartTime = null;
this.currentTrackResumeOffset = null;
this.currentAudioTrack = 0;
if (this.currentTextTimeout) {
clearTimeout(this.currentTextTimeout);
this.textHighlightFinished();
this.currentTextTimeout = null;
this.currentTextItem = null;
}
this.playing = false;
};
MediaPlayer.prototype.getNumberOfPages = function () {
return this.pageTotalNumber;
};
MediaPlayer.prototype.playbackFinished = function () {
this.currentAudioTrack = 0;
this.playing = false;
};
MediaPlayer.prototype.audioTrackFinishedPlaying = function () {
this.currentAudioTrack++;
if (this.currentAudioTrack >= this.currentPageAudioTracks.length) {
this.playbackFinished();
} else {
this.playMediaPlayer();
}
};
//
//
// Buffered Loader
//
// Class used to get the sound files
//
function BufferLoader(context, urlList, callback) {
this.context = context;
this.urlList = urlList;
this.onload = callback;
this.bufferList = [];
this.loadCount = 0;
}
// this allows us to handle media files with embedded artwork/id3 tags
function syncStream(node) { // should be done by api itself. and hopefully will.
var buf8 = new Uint8Array(node.buf);
buf8.indexOf = Array.prototype.indexOf;
var i = node.sync, b = buf8;
while (1) {
node.retry++;
i = b.indexOf(0xFF, i);
if (i == -1 || (b[i + 1] & 0xE0 == 0xE0 )) break;
i++;
}
if (i != -1) {
var tmp = node.buf.slice(i); //carefull there it returns copy
delete(node.buf);
node.buf = null;
node.buf = tmp;
node.sync = i;
return true;
}
return false;
}
BufferLoader.prototype.loadBuffer = function (url, index) {
// Load buffer asynchronously
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.responseType = "arraybuffer";
var loader = this;
function decode(sound) {
loader.context.decodeAudioData(
sound.buf,
function (buffer) {
if (!buffer) {
alert('error decoding file data');
return
}
loader.bufferList[index] = buffer;
if (++loader.loadCount == loader.urlList.length)
loader.onload(loader.bufferList);
},
function (error) {
if (syncStream(sound)) {
decode(sound);
} else {
console.error('decodeAudioData error', error);
}
}
);
}
request.onload = function () {
// Asynchronously decode the audio file data in request.response
var sound = {};
sound.buf = request.response;
sound.sync = 0;
sound.retry = 0;
decode(sound);
};
request.onerror = function () {
alert('BufferLoader: XHR error');
};
request.send();
};
BufferLoader.prototype.load = function () {
for (var i = 0; i < this.urlList.length; ++i)
this.loadBuffer(this.urlList[i], i);
};
There is no way of streaming with decodeAudioData(), you need to use MediaElement with createMediaStreamSource and run your stuff then. decodeAudioData() cannot stream on a part.#zre00ne And mp3 will be decoded big!!! Verybig!!!

Downloading binary data using XMLHttpRequest, without overrideMimeType

I am trying to retrieve the data of an image in Javascript using XMLHttpRequest.
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://www.celticfc.net/images/doc/celticcrest.png");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
var resp = xhr.responseText;
console.log(resp.charCodeAt(0) & 0xff);
}
};
xhr.send();
The first byte of this data should be 0x89, however any high-value bytes return as 0xfffd (0xfffd & 0xff being 0xfd).
Questions such as this one offer solutions using the overrideMimeType() function, however this is not supported on the platform I am using (Qt/QML).
How can I download the data correctly?
Google I/O 2011: HTML5 Showcase for Web Developers: The Wow and the How
Fetch binary file: new hotness
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.celticfc.net/images/doc/celticcrest.png', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
if (this.status == 200) {
var uInt8Array = new Uint8Array(this.response); // Note:not xhr.responseText
for (var i = 0, len = uInt8Array.length; i < len; ++i) {
uInt8Array[i] = this.response[i];
}
var byte3 = uInt8Array[4]; // byte at offset 4
}
}
xhr.send();
I'm not familiarized with Qt but i found this in their documentation
string Qt::btoa ( data )
Binary to ASCII - this function returns a base64 encoding of data.
So, if your image is a png you can try:
resp = "data:image/png;base64," + btoa(resp);
document.write("<img src=\""+resp+"\">");
Qt version 5.13.1(support native Promise in qml env), prue qml.
function fetch, like node-fetch
function hexdump, dump the data as hex, just use the linux command hexdump to check.
function createResponse(xhr) {
let res = {};
function headersParser() {
let headersRaw = {};
let lowerCaseHeaders = {};
let rawHeaderArray = xhr.getAllResponseHeaders().split("\n");
for(let i in rawHeaderArray) {
let rawHeader = rawHeaderArray[i];
let headerItem = rawHeader.split(":");
let name = headerItem[0].trim();
let value = headerItem[1].trim();
let lowerName = name.toLowerCase();
headersRaw[name] = value;
lowerCaseHeaders [lowerName] = value;
}
return {
"headersRaw": headersRaw,
"lowerCaseHeaders": lowerCaseHeaders
};
}
res.headers = {
__alreayParse : false,
raw: function() {
if (!res.headers.__alreayParse) {
let {headersRaw, lowerCaseHeaders} = headersParser();
res.headers.__alreayParse = true;
res.headers.__headersRaw = headersRaw;
res.headers.__lowerCaseHeaders = lowerCaseHeaders;
}
return res.headers.__headersRaw;
},
get: function(headerName) {
if (!res.headers.__alreayParse) {
let {headersRaw, lowerCaseHeaders} = headersParser();
res.headers.__alreayParse = true;
res.headers.__headersRaw = headersRaw;
res.headers.__lowerCaseHeaders = lowerCaseHeaders;
}
return res.headers.__lowerCaseHeaders[headerName.toLowerCase()];
}
};
res.json = function() {
if(res.__json) {
return res.__json;
}
return res.__json = JSON.parse(xhr.responseText);
}
res.text = function() {
if (res.__text) {
return res.__text;
}
return res.__text = xhr.responseText;
}
res.arrayBuffer = function() {
if (res.__arrayBuffer) {
return res.__arrayBuffer;
}
return res.__arrayBuffer = new Uint8Array(xhr.response);
}
res.ok = (xhr.status >= 200 && xhr.status < 300);
res.status = xhr.status;
res.statusText = xhr.statusText;
return res;
}
function fetch(url, options) {
return new Promise(function(resolve, reject) {
let requestUrl = "";
let method = "";
let headers = {};
let body;
let timeout;
if (typeof url === 'string') {
requestUrl = url;
method = "GET";
if (options) {
requestUrl = options['url'];
method = options['method'];
headers = options['headers'];
body = options['body'];
timeout = options['timeout'];
}
} else {
let optionsObj = url;
requestUrl = optionsObj['url'];
method = optionsObj['method'];
headers = optionsObj['headers'];
body = optionsObj['body'];
timeout = optionsObj['timeout'];
}
let xhr = new XMLHttpRequest;
if (timeout) {
xhr.timeout = timeout;
}
// must set responseType to arraybuffer, then the xhr.response type will be ArrayBuffer
// but responseType not effect the responseText
xhr.responseType = 'arraybuffer';
xhr.onreadystatechange = function() {
// readyState as follow: UNSENT, OPENED, HEADERS_RECEIVED, LOADING, DONE
if(xhr.readyState === XMLHttpRequest.DONE) {
resolve(createResponse(xhr));
}
};
xhr.open(method, requestUrl);
// todo check headers
for(var iter in headers) {
xhr.setRequestHeader(iter, headers[iter]);
}
if("GET" === method || "HEAD" === method) {
xhr.send();
} else {
xhr.send(body);
}
});
}
function hexdump(uint8array) {
let count = 0;
let line = "";
let lineCount = 0;
let content = "";
for(let i=0; i<uint8array.byteLength; i++) {
let c = uint8array[i];
let hex = c.toString(16).padStart (2, "0");
line += hex + " ";
count++;
if (count === 16) {
let lineCountHex = (lineCount).toString (16).padStart (7, "0") + "0";
content += lineCountHex + " " + line + "\n";
line = "";
count = 0;
lineCount++;
}
}
if(line) {
let lineCountHex = (lineCount).toString (16).padStart (7, "0") + "0";
content += lineCountHex + " " + line + "\n";
line = "";
// count = 0;
lineCount++;
}
content+= (lineCount).toString (16).padStart (7, "0") + count.toString(16) +"\n";
return content;
}
fetch("https://avatars2.githubusercontent.com/u/6630355")
.then((res)=>{
try {
let headers = res.headers.raw();
console.info(`headers:`, JSON.stringify(headers));
let uint8array = res.arrayBuffer();
let hex = hexdump(uint8array);
console.info(hex)
}catch(error) {
console.trace(error);
}
})
.catch((error)=>{
});

Categories

Resources