I'm using hls.js with video Js and I was wondering how I could implement a custom loader that loads the content using fetch API instead of XMLHttpRequest.
The following is what I managed to achive:
hlsConfig: {
loader: function() {
this.load = function(url, responseType, onSuccess, onError, timeout, maxRetry, retryDelay) {
var onProgress = arguments.length <= 8 || arguments[8] === undefined ? null : arguments[8];
var frag = arguments.length <= 9 || arguments[9] === undefined ? null : arguments[9];
this.url = url;
if (frag && !isNaN(frag.byteRangeStartOffset) && !isNaN(frag.byteRangeEndOffset)) {
this.byteRange = frag.byteRangeStartOffset + '-' + (frag.byteRangeEndOffset - 1);
}
this.responseType = responseType;
this.onSuccess = onSuccess;
this.onProgress = onProgress;
this.onError = onError;
this.stats = {
trequest: performance.now(),
retry: 0
};
this.timeout = timeout;
this.maxRetry = maxRetry;
this.retryDelay = retryDelay;
if (self.fetch) {
// use fetch API
} else {
// fallback to XMLHttpRequest loader
}
return true;
}
}
hls.js only takes xmlHTTPrequest events. You have to modify hls.js to make it handle the input the way you like, search for 'loadsuccess' in unminified hls.js and change it to how you would handle the data yourself. Make sure the payload is the requested responseType; make sure payload is an ArrayBuffer if it asks for it, and just a plain string for empty responseType.
Personally I have coded the following shim in order to pass the data came from WebRTC or whatever else:
function CustomLoaderResponse(response)
{
this.currentTarget = {};
this.currentTarget.getResponseHeader = function() { return '' };
this.currentTarget.response = response;
this.currentTarget.responseText = typeof(response) == "string" ? response : '';
}
<...>
var response = new CustomLoaderResponse(/* pass string or arraybuffer here */);
I guess that the should propose handling the regular JavaScript dictionaries in the future releases of hls.js, as this can't be considered as a good practice either.
Related
I am building an Office Excel Add-in using the web add-in framework provided by Microsoft.
This add-in also includes a custom function. Currently, the custom function is working on Excel for Mac, Excel online (device agnostic), but not on Windows?
The add-in loads fine, and there are no obvious errors. But when the function is run (on Windows) it just says:
#BUSY and then resolves to #VALUE! and stays like that.
The code also works when using the Shared Runtime configuration, but that requires that we make all our Javascript compatible with IE, which is definitely a possibility - but I would like to know why the regular configuration is not working.
WISE is the Excel function.
function WISE(symbol, parameter, year, quarter) {
var param = parameter.replace(/\s/g, '').toLowerCase();
param = param.replace('&', 'and');
symbol = symbol.toUpperCase();
if (quarter == null) {
return getAnnualData(symbol, param, year);
}
}
function getVal(data, param) {
var apiResponseDataFormatted = {};
for (var key in data) {
apiResponseDataFormatted[key.replace(/ /g, '').toLowerCase()] = data[key];
}
var newValue = apiResponseDataFormatted[param];
if (newValue !== 0 && !newValue) {
newValue = 'Unavailable';
}
return newValue;
}
function getAnnualData(symbol, parameter, year) {
var apiPath = requestMap[parameter];
var response = "";
var url = URL_API + "/" + apiPath + "/" + symbol + "?apikey=" + api_key;
var request = new XMLHttpRequest();
request.open('GET', url, false); // `false` makes the request synchronous
request.send(null);
if (request.status === 200) {// That's HTTP for 'ok'
response = JSON.parse(request.responseText);
}else{
return "Request Error: " + request.status + " " + url;
}
var apiResponseData;
var currentYear = new Date().getFullYear();
if (year != null && year !== currentYear) {
apiResponseData = response[currentYear - year - 1];
} else {
apiResponseData = response[0];
}
result = getVal(apiResponseData, parameter)
return result;
}
sorry for the frustration. One of the main reason for this could be that currently on windows custom functions run in their own runtime, which is a seperate. While that runtime conserves memory, one of the limitations is it doesn't support Full CORS.
We have been recommending the use of the Shared Runtime as a way to get around this. This effectively will run the custom function in the same runtime as the taskpane, so I suspect will work for you. This also makes it easy to share state between taskpanes and functions.
Can you try that?
https://learn.microsoft.com/en-us/office/dev/add-ins/tutorials/share-data-and-events-between-custom-functions-and-the-task-pane-tutorial
(I realize the docs aren't super clear about this, so will follow up offline on this to get it corrected).
I am trying to change page but without any reloading
here's what I do:
AJAX:
app.ajax.client.request = function(headers, path, method, queryObj, payload, cb) {
// Set defaults
headers = typeof(headers) == 'object' && headers !== null ? headers : {};
path = typeof(path) == 'string' ? path : '/';
method = typeof(method) == 'string' && ['POST','PUT','DELETE','GET'].indexOf(method.toUpperCase()) > -1 ? method.toUpperCase() : 'GET';
queryObj = typeof(queryObj) == 'object' && queryObj !== null ? queryObj : {};
payload = typeof(payload) == 'object' && payload !== null ? payload : {};
cb = typeof(cb) == 'function' ? cb : false;
// For each query string parameter sent, add it to the path
let requestUrl = path + '?';
let counter = 0;
// Set the request url based on the query object
for (let i in queryObj) {
if (queryObj.hasOwnProperty(i)) {
counter++
if (counter > 1) {
requestUrl += '&';
}
requestUrl += i + '=' + queryObj[i];
}
}
// Form the http request as a JSON type
let xhr = new XMLHttpRequest();
xhr.open(method, requestUrl, true);
xhr.setRequestHeader('Content-Type', 'application/json');
// For each header sent, add it to the request
for (let i in headers) {
if (headers.hasOwnProperty(i)) {
xhr.setRequestHeader(i, headers[i]);
}
}
// When the request comes back, handle the response
xhr.onreadystatechange = function() {
// Set the parameters that will be called back
let readyState = xhr.readyState;
let statusCode = xhr.status;
let responseReturned = xhr.responseText;
// Parse the response
let parsedResponse = app.isJsonString(responseReturned);
if (parsedResponse) { // If the response text is a JSON, callback parsedResponse, if not, callback the not parsed response instead
cb(readyState, statusCode, parsedResponse);
} else {
cb(readyState, statusCode, responseReturned);
}
}
// Send the payload as JSON
let payloadString = JSON.stringify(payload);
xhr.send(payloadString);
}
Client Requests:
app.changeToIndexPage = function(e) {
e.preventDefault();
if (!app.mainContainer.hasClass('index')) {
app.closePage(); // show a loading screen
history.pushState({}, '', '/'); //Set the url to the index's url
setTimeout(function() {
app.ajax.client.request(undefined, 'public/request/index.txt', 'GET', undefined, undefined, function(readyState, statusCode, response) { // Get the request
console.log(readyState);
if (readyState < 3) {
app.preloader.addClass('loading');
} else if (readyState == 4 && statusCode == 200) {
app.navContainer.attr('class', app.navContainer.attr('class').replace(/index|project|about|contact/g, 'index'));
setTimeout(function() {
app.mainContainer.html(response);
}, 500);
}
});
}, 100);
}
}
So, for example:
If I am not on the index page, and wanted to go to the index page, i can run the changeToIndexPage function, and the ajax will request the needed file and change the html element based on the needed action. The only problem that I had is, are there any better solution??
If you're going to take the approach of fetching pages with AJAX and slapping them into the document, which I wouldn't recommend in the first place, you should have a generalized function to do so.
That function should have a signature like function navigate(path) { ... }. It should add the history entry, fetch the appropriate document and insert it onto the page.
Then, you'll need to attach an event listener to catch popState events, so when the user presses the back button you retrieve the path from the history entry that was popped and pass it to navigate().
Again, if you're looking to build an SPA I wouldn't recommend building it like this. One of the major benefits of SPAs are the performance gain from rendering your documents on the client, which this approach doesn't leverage. Consider using a component-based client-side rendering library like React or Angular.
I've been having a hard time writing websockets in PHP, so I decided to try to download a datastream.
It works, I can get content in different times, parse it, and use it, but the entire response is saved into memory...
Is there a way to reset the request's response every time in onreadystatechange function? (where that comment is)
Removing the comment, I get this error:
test.html:20 Uncaught TypeError: Cannot assign to read only property
'responseText' of object '#'
A working code for streaming:
class HTTPStream {
constructor(url, callback, error) {
this.request = new XMLHttpRequest();
var previous_text = '';
if (typeof error === "function")
this.request.onerror = error;
this.request.onreadystatechange = () => {
if (this.request.readyState > 2) {
var new_response = this.request.responseText.substring(previous_text.length);
if(new_response != "") {
var result = JSON.parse(new_response);
callback(result);
}
previous_text = this.request.responseText;
//this.request.responseText = "";
}
};
this.request.open("GET", url, true);
this.request.send();
}
cancel() {
this.request.abort();
}
}
new HTTPStream('test.php', (m) => console.log(m));
Here is a log:
EDIT:
By suggestions, I tried doing this, but unfortunately you can set it to writable only once, and that is it. I have lots of outputs, not just once.
I am building an add-on for Firefox that redirect request to a new URL if the URL match some conditions. I've tried this, and it does not work.
I register an observer on HTTP-on-modify-request to process the URL, if the URL match my condition, I will redirect to a new URL.
Here is my code:
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var newUrl = "https://google.com";
function isInBlacklist(url) {
// here will be somemore condition, I just use youtube.com to test
if (url.indexOf('youtube.com') != -1) {
return true;
}
return false;
}
exports.main = function(options,callbacks) {
// Create observer
httpRequestObserver =
{
observe: function (subject, topic, data) {
if (topic == "http-on-modify-request") {
var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
var uri = httpChannel.URI;
var domainLoc = uri.host;
if (isInBlacklist(domainLoc) === true) {
httpChannel.cancel(Cr.NS_BINDING_ABORTED);
var gBrowser = utils.getMostRecentBrowserWindow().gBrowser;
var domWin = channel.notificationCallbacks.getInterface(Ci.nsIDOMWindow);
var browser = gBrowser.getBrowserForDocument(domWin.top.document);
browser.loadURI(newUrl);
}
}
},
register: function () {
var observerService = Cc["#mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
observerService.addObserver(this, "http-on-modify-request", false);
},
unregister: function () {
var observerService = Cc["#mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
observerService.removeObserver(this, "http-on-modify-request");
}
};
//register observer
httpRequestObserver.register();
};
exports.onUnload = function(reason) {
httpRequestObserver.unregister();
};
I am new to Firefox add-on development.
You can redirect a channel by calling nsIHttpChannel.redirectTo.
This is not possible once the channel is opened, but in http-on-modify-request it will work.
So in your code, you can do something like:
Cu.import("resource://gre/modules/Services.jsm");
// ...
if (condition) {
httpChannel.redirectTo(
Services.io.newURI("http://example.org/", null, null));
}
It looks like you might be using the Add-on SDK. In that case, read up on Using Chrome Authority.
You could simply do a
httpChannel.URI.spec = newUrl;
instead of
httpChannel.cancel(Cr.NS_BINDING_ABORTED);
...
browser.loadURI(newUrl);
Not sure how 'safe' it would be in your case, since I'm not exactly sure how other headers in the request (e.g. Cookie) would be manipulated when you change the URL to point to an entirely different domain at this stage.
I have an ajax call where I used jQuery.ajax() to make a request to an mvc action. This all worked fine. However due to some forms having a file control I changed it from using jQuery.ajax() to using the XMLHttpRequest to send it using the HTML5 File API.
Since making this change the MVC action method no longer see's it as an ajax request. Using Fiddler2 I have noticed that it no longer adds the "X-Requested-With: XMLHttpRequest" to the request and I assume this is the problem.
The form I am trying to send does not have a file input in it, only normal textboxes etc, but I was trying to keep the method generic to deal with both. The following is the code I am using to send the ajax request:
// get the edit tender form
var $Form = $Button.closest('form');
var Url = $Form.attr('action');
var AjaxRequestObject = new XMLHttpRequest();
var FormDataToSend = new FormData();
$Form.find(':input').each(function () {
if ($(this).is('input[type="file"]')) {
var files = $(this)[0].files;
if (files.length > 0) {
FormDataToSend.append(this.name, files[0]);
}
} else {
FormDataToSend.append(this.name, $(this).val());
}
});
AjaxRequestObject.open('POST', Url, true);
AjaxRequestObject.onreadystatechange = function () {
if (AjaxRequestObject.readyState == 4) {
// handle response.
if (AjaxRequestObject.status == 200) {
if (!AjaxErrorExists(AjaxRequestObject.responseText, )) {
alert("success");
console.log(AjaxRequestObject.responseText);
}
else {
alert('failure');
}
}
else {
alert('failure');
}
}
};
AjaxRequestObject.send(FormDataToSend);
This code was provided following a problem I had which Darin Dimitrov provided the solution to, so I could send the file inputs by ajax.
Any ideas why this request would not send the header for an ajax call?
X-Requested-With is automatically added by jQuery. You can just as easily add it yourself with AjaxRequestObject.setRequestHeader(). Docs
I was having troubles with detecting if my request was ajax. So, maybe this sample will save someone a minute or two:
var xmlhttp = new XMLHttpRequest();
xmlhttp.open('GET', URL, true); // `true` for async call, `false` for sync.
// The header must be after `.open()`, but before `.send()`
xmlhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlhttp.onreadystatechange = function() {
// 4th state is the last:
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { ... }
};
xmlhttp.send();
Tested with Flask.
You can override natively all XMLHttpRequest.open method calls and add in it X-Requested-With header like:
(function () {
// #author https://github.com/stopsopa jfdsa78y453cq5hjfd7s877834h4h3
if (window.XMLHttpRequest.prototype.onOpen) {
return console.log('XMLHttpRequest.onOpen is already defined');
}
function over(method, on, off) {
var old = window.XMLHttpRequest.prototype[method];
if (!old.old) {
var stack = [];
window.XMLHttpRequest.prototype[on] = function (fn) {
if (typeof fn === 'function') {
stack.push(fn);
}
}
window.XMLHttpRequest.prototype[off] = function (fn) {
for (var i = 0, l = stack.length ; i < l ; i += 1 ) {
if (stack[i] === fn) {
stack.splice(i, 1);
break;
}
}
}
window.XMLHttpRequest.prototype[method] = function () {
var args = Array.prototype.slice.call(arguments);
var ret = old.apply(this, args);
for (var i = 0, l = stack.length ; i < l ; i += 1 ) {
stack[i].apply(this, args);
}
return ret;
}
window.XMLHttpRequest.prototype[method].old = old;
}
}
over('open', 'onOpen', 'offOpen')
XMLHttpRequest.prototype.onOpen(function () {
this.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
});
}());