I am having trouble streaming video with my iOS app from URL of a PFFile uploaded in my database. I used Heroku and AWS and I still have the same issue. It used to work fine when the files were hosted in the old parse server.
the PFFile url works fine when I open it in a chrome web browser but not in safari nor in the iOS app.
the following is the link of the video:
http://shuuapp.herokuapp.com/parse/files/wnQeou0L4klDelSEtMOX6SxXRVKu1f3sKl6vg349/24092609eadcc049f711aafbd59c1a18_movie.mp4
Its exactly the same issue as the issue mentioned in the link below:
iOS - Can't stream video from Parse Backend
parse-server doesn't seem to be supporting streaming in Safari/iOS and the solution is the enable it using express & GridStore as follows,
parse-server-example\node_modules\parse-server\lib\Routers\FilesRouter
{
key: 'getHandler',
value: function getHandler(req, res, content) {
var config = new _Config2.default(req.params.appId);
var filesController = config.filesController;
var filename = req.params.filename;
var video = '.mp4'
var lastFourCharacters = video.substr(video.length - 4);
if (lastFourCharacters == '.mp4') {
filesController.handleVideoStream(req, res, filename).then(function (data) {
}).catch(function (err) {
console.log('404FilesRouter');
res.status(404);
res.set('Content-Type', 'text/plain');
res.end('File not found.');
});
}else{
filesController.getFileData(config, filename).then(function (data) {
res.status(200);
res.end(data);
}).catch(function (err) {
res.status(404);
res.set('Content-Type', 'text/plain');
res.end('File not found.');
});
}
}
} , ...
parse-server-example\node_modules\parse-server\lib\Controllers\FilesController
_createClass(FilesController, [{
key: 'getFileData',
value: function getFileData(config, filename) {
return this.adapter.getFileData(filename);
}
},{
key: 'handleVideoStream',
value: function handleVideoStream(req, res, filename) {
return this.adapter.handleVideoStream(req, res, filename);
}
}, ...
parse-server-example\node_modules\parse-server\lib\Adapters\Files\GridStoreAdapter
... , {
key: 'handleVideoStream',
value: function handleVideoStream(req, res, filename) {
return this._connect().then(function (database) {
return _mongodb.GridStore.exist(database, filename).then(function () {
var gridStore = new _mongodb.GridStore(database, filename, 'r');
gridStore.open(function(err, GridFile) {
if(!GridFile) {
res.send(404,'Not Found');
return;
}
console.log('filename');
StreamGridFile(GridFile, req, res);
});
});
})
}
}, ...
Bottom of GridStore Adapter
function StreamGridFile(GridFile, req, res) {
var buffer_size = 1024 * 1024;//1024Kb
if (req.get('Range') != null) { //was: if(req.headers['range'])
// Range request, partialle stream the file
console.log('Range Request');
var parts = req.get('Range').replace(/bytes=/, "").split("-");
var partialstart = parts[0];
var partialend = parts[1];
var start = partialstart ? parseInt(partialstart, 10) : 0;
var end = partialend ? parseInt(partialend, 10) : GridFile.length - 1;
var chunksize = (end - start) + 1;
if(chunksize == 1){
start = 0;
partialend = false;
}
if(!partialend){
if(((GridFile.length-1) - start) < (buffer_size) ){
end = GridFile.length - 1;
}else{
end = start + (buffer_size);
}
chunksize = (end - start) + 1;
}
if(start == 0 && end == 2){
chunksize = 1;
}
res.writeHead(206, {
'Cache-Control': 'no-cache',
'Content-Range': 'bytes ' + start + '-' + end + '/' + GridFile.length,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': 'video/mp4',
});
GridFile.seek(start, function () {
// get GridFile stream
var stream = GridFile.stream(true);
var ended = false;
var bufferIdx = 0;
var bufferAvail = 0;
var range = (end - start) + 1;
var totalbyteswanted = (end - start) + 1;
var totalbyteswritten = 0;
// write to response
stream.on('data', function (buff) {
bufferAvail += buff.length;
//Ok check if we have enough to cover our range
if(bufferAvail < range) {
//Not enough bytes to satisfy our full range
if(bufferAvail > 0)
{
//Write full buffer
res.write(buff);
totalbyteswritten += buff.length;
range -= buff.length;
bufferIdx += buff.length;
bufferAvail -= buff.length;
}
}
else{
//Enough bytes to satisfy our full range!
if(bufferAvail > 0) {
var buffer = buff.slice(0,range);
res.write(buffer);
totalbyteswritten += buffer.length;
bufferIdx += range;
bufferAvail -= range;
}
}
if(totalbyteswritten >= totalbyteswanted) {
// totalbytes = 0;
GridFile.close();
res.end();
this.destroy();
}
});
});
}else{
// res.end(GridFile);
// stream back whole file
res.header('Cache-Control', 'no-cache');
res.header('Connection', 'keep-alive');
res.header("Accept-Ranges", "bytes");
res.header('Content-Type', 'video/mp4');
res.header('Content-Length', GridFile.length);
var stream = GridFile.stream(true).pipe(res);
}
};
P.S
The original answer is given by #Bragegs here - https://github.com/ParsePlatform/parse-server/issues/1440#issuecomment-212815625 .
Related
I'm updating the totalPayable value in else if, but I'm not able to use the updated value anywhere in the code when I call getOrder(). Is it an API problem or what can anyone help me with the code?
else if (isOrderInitiated == false){
getCleintOrderFromApi();
debugPrint("ifelse" + totalPayable.toString());
}
getClientOrderFromApi() {
orders.clear();
totalPayable = 0.0;
api.getCleintOrder().then((list) {
list.forEach((order) {
if(order.is_placed) {
order.status = ORDER_STATUS[0];
} else if(!order.is_placed) {
order.status = ORDER_STATUS[2];
}
debugPrint("ORDER_STATUS from remote" + order.status);
for (var j = 0; j < order.items.length; j++) {
FoodItemOrder item = order.items[j];
totalPayable = totalPayable + item.unitPrice * item.quantity;
debugPrint(" total payable in order "+ totalPayable.toString());
}
orders.add(order);
debugPrint("itemsin order"+orders.last.items.length.toString());
});
currentOrderList.clear();
currentOrderList.addAll(orders);
orderItemsSink.add(orders);
});
}
}
The debugPrint in getClientOrderFromApi() is showing the updated result, but in else if debugPrint(" ifelse "+ totalPayable.toString()); it is not showing the updated value which is why wherever I use totalPayable it is not showing the desired value.
Future<List<Order>> getCleintOrder() async {
MakeOrder clientOrderRequest = MakeOrder(); //currentCafe;
clientOrderRequest.caffeID = currentCafe.caffeId;
clientOrderRequest.tableidtimestamp = currentUser.tableIdTimestamp;
// offset = 160;
String url = BASE_URL + BASE_URL_GET_CLIENT_ORDER;
debugPrint("requesting getCleintOrder --- \n" +
url +
"\n " +
clientOrderRequest.toClietnOrderJson().toString());
var response = await http
.post(
url,
headers: {
HttpHeaders.contentTypeHeader: 'application/json',
// HttpHeaders.authorizationHeader : ''
},
body: json.encode(clientOrderRequest.toClietnOrderJson()),
)
.catchError(
(error) {
////debugPrint(error.toString());
return false;
},
);
var jsonObj = json.decode(response.body);
////debugPrint(jsonObj.toString());
List<Order> orders = [];
try {
jsonObj.forEach((newJson) {
List<Order> orderList = (newJson["orders"] as List)
.map((neworderJson) => Order.fromJSON(neworderJson))
.toList().cast<Order>();
orders.addAll(orderList);
});
} catch (e) {
////debugPrint(e.toString());
}
// debugPrint("total client orders " + orders.length.toString());
return orders;
}
This is my api call for refernece.
I am working on telnet emulator in web using mvc with signalR, while sending and receiving telnet stream asynchronously while loop hangs the page if kept for long, does not finish. Tried many solution but no luck.
Below is code where it stuck while continuously looking for stream bytes.
/// <summary>
/// Wait for data from the telnet server and send it to the emulation.
/// </summary>
public async Task ReadLoop(string connectionId, BaseDecoder decoder, CancellationToken ct, string PanelId)
{
var client = Get(connectionId);
if (client == null) { return; }
string script = string.Empty;
if (string.IsNullOrWhiteSpace(panelScript))
{
panelScript = objAccn.ExecuteQueryPanelScript(Convert.ToInt32(PanelId)).ToString();
script = panelScript.Replace(#"\n", Environment.NewLine);
commands = Regex.Split(script, "\r\n");
}
string loginPrompt = null;
if (PanelId != "1")
loginPrompt = "login: ";
else
loginPrompt = "name?: ";
var login = commands[0];// WebConfigurationManager.AppSettings["login"];
if (!_panelCommands.ContainsKey(0))
_panelCommands.Add(0, true);
var passwordPrompt = WebConfigurationManager.AppSettings["passwordPrompt"];
var password = commands[1];//WebConfigurationManager.AppSettings["password"];
if (!_panelCommands.ContainsKey(1))
_panelCommands.Add(1, true);
var loginAuto = (!String.IsNullOrEmpty(loginPrompt) && !String.IsNullOrEmpty(login));
var passwordAuto = (!String.IsNullOrEmpty(passwordPrompt) && !String.IsNullOrEmpty(password));
var DefaultCommandsForm60 = false;
var DefaultCommandsForm50 = false;
var DefaultScreenm50 = false;
decoder.ScriptFunc = async (string str) =>
{
if (!String.IsNullOrEmpty(str))
{
if (loginAuto && str.EndsWith(loginPrompt, StringComparison.Ordinal))
{
await client.StreamWriter.WriteAsync(login + "\r\n");
loginAuto = false;
str = str.Remove(str.Length - loginPrompt.Length);
}
if (passwordAuto && str.EndsWith(passwordPrompt, StringComparison.Ordinal))
{
await client.StreamWriter.WriteAsync(password + "\r\n");
passwordAuto = false;
str = str.Remove(str.Length - passwordPrompt.Length);
if (PanelId != "1")
DefaultCommandsForm60 = true;
else
DefaultCommandsForm50 = true;
//System.Threading.Thread.Sleep(1500);
}
if (PanelId != "1")
{
if (DefaultCommandsForm60)
{
System.Threading.Thread.Sleep(1500);
await client.StreamWriter.WriteAsync(commands[2] + "\r\n");
if (commands.Length > 2)
{
System.Threading.Thread.Sleep(1500);
await client.StreamWriter.WriteAsync(commands[3] + "\r\n");
}
if (commands.Length > 3)
{
System.Threading.Thread.Sleep(1500);
await client.StreamWriter.WriteAsync(commands[4] + "\r\n");
}
DefaultCommandsForm60 = false;
}
}
else
{
if (DefaultCommandsForm50)
{
if (commands.Length > 1)
{
// System.Threading.Thread.Sleep(2500);
if (!_panelCommands.ContainsKey(3))
{
// System.Threading.Thread.Sleep(1500);
await client.StreamWriter.WriteAsync(commands[3] + "\r\n");
_panelCommands.Add(3, true);
}
else
{
if (commands.Length > 2)
{
if (!_panelCommands.ContainsKey(4))
{
// System.Threading.Thread.Sleep(1500);
await client.StreamWriter.WriteAsync(commands[3] + "\r\n");
_panelCommands.Add(4, true);
}
DefaultCommandsForm50 = false;
}
}
DefaultScreenm50 = true;
}
}
else
{
if (DefaultScreenm50)
if (str.EndsWith("$ "))
{
await client.StreamWriter.WriteAsync("Screen" + "\r\n");
str = str.Remove(str.Length - ("$ ").Length);
DefaultScreenm50 = false;
}
}
}
}
return str;
};
const int bufferSize = 4096;
//if (ns.CanRead)
//{
// byte[] readBuffer = new byte[1024];
// int numBytesRead = 0;
// do
// {
// numBytesRead = ns.Read(readBuffer, 0, readBuffer.Length);
// //var data = Encoding.UTF8.GetString(readBuffer);
// // ss= Encoding.GetEncoding(1252).GetString(readBuffer.ToArray());
// //sb.Append(readBuffer[0].ToString);
// sb.AppendFormat("{0}", Encoding.ASCII.GetString(readBuffer, 0, numBytesRead));
// sb.Replace(Convert.ToChar(24), ' ');
// sb.Replace(Convert.ToChar(255), ' ');
// sb.Replace('?', ' ');
// //sb.Replace(Environment.NewLine, "<br />").ToString();
// }
// while (ns.DataAvailable);
//}
//if (client._stream.CanRead)
//{
// do
// {
// var inBytes = await client.ReadAsync(bufferSize, ct);
// foreach (var b in inBytes)
// {
// await decoder.AddByte(b);
// }
// await decoder.Flush();
// } while (client.IsConnected );
////}
//Disconnect(connectionId);
//var readTask = client.ReadAsync(bufferSize, ct);
while (client.IsConnected && !ct.IsCancellationRequested)
{
if (client._stream.CanRead)
{
var inBytes = await client.ReadAsync(bufferSize, ct);
foreach (var b in inBytes)
{
await decoder.AddByte(b);
}
await decoder.Flush();
}
}
Disconnect(connectionId);
}
}
In above method here the part of code where it stuck and never comes to an end.
while (client.IsConnected && !ct.IsCancellationRequested)
{
if (client._stream.CanRead)
{
var inBytes = await client.ReadAsync(bufferSize, ct);
foreach (var b in inBytes)
{
await decoder.AddByte(b);
}
await decoder.Flush();
}
}
Disconnect(connectionId);
Any suggestion and help would be appreciable!
I'm trying to send a Revit file to my Bucket chunk by chunk. My Revit file is almost 13 MB. Here is my code:
function handleFileSelect(evt) {
var files = evt.target.files;
var file = files[0];
var segmentSize = 1024 * 1024 * 5; //5 MB
var startingByte = 0;
var endingByte = startingByte + segmentSize - 1;
var segments = Math.ceil(file.size / segmentSize);
var session = Math.floor(100000000 + Math.random() * -900000000);
for (var i = 0; i < segments; i ++)
{
var blob = file.slice(startingByte, endingByte);
var url = 'https://developer.api.autodesk.com/oss/v2/buckets/' + 'linked_model' + '/objects/' + file.name + '/resumable';
//console.log(url);
var contentRange = 'bytes ' + startingByte + '-' + endingByte + '/' + file.size;
$.ajax({
type: 'PUT',
url: url,
data: blob,
headers: {
'Authorization':'Bearer ' + token,
'Content-Type':'application/octet-stream',
'Content-Range': contentRange,
'Session-Id': session
},
crossDomain: true,
processData: false,
success: function (data) {
console.log(i);
startingByte = endingByte + 1;
endingByte = startingByte + segmentSize - 1;
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert("Status: " + textStatus); alert("Error: " + errorThrown);
console.log(startingByte);
console.log(endingByte);
console.log(file.size);
}
});
}
}
It gives me error: 416 (Requested Range Not Satisfiable)
Can anyone help?
I had the same 416 error but my issue was that I tried to upload chunks smaller than 2MB, which is not doable (except for the last chunk).
When I increased the chunks size to 5MB it started to work. I just wrote a blog article about it: https://forge.autodesk.com/blog/nailing-large-files-uploads-forge-resumable-api
Below is the core piece of code that handles chunking and uploading (in node.js).
By the way, I strongly discourage you to perform this kind of operation client-side as your snippet suggests, this means you have to pass a write-access token to the web page which compromises security of your app. You should first upload the file to your server and then securely upload it to Forge as described in the post and my sample.
/////////////////////////////////////////////////////////
// Uploads object to bucket using resumable endpoint
//
/////////////////////////////////////////////////////////
uploadObjectChunked (getToken, bucketKey, objectKey,
file, opts = {}) {
return new Promise((resolve, reject) => {
const chunkSize = opts.chunkSize || 5 * 1024 * 1024
const nbChunks = Math.ceil(file.size / chunkSize)
const chunksMap = Array.from({
length: nbChunks
}, (e, i) => i)
// generates uniques session ID
const sessionId = this.guid()
// prepare the upload tasks
const uploadTasks = chunksMap.map((chunkIdx) => {
const start = chunkIdx * chunkSize
const end = Math.min(
file.size, (chunkIdx + 1) * chunkSize) - 1
const range = `bytes ${start}-${end}/${file.size}`
const length = end - start + 1
const readStream =
fs.createReadStream(file.path, {
start, end: end
})
const run = async () => {
const token = await getToken()
return this._objectsAPI.uploadChunk(
bucketKey, objectKey,
length, range, sessionId,
readStream, {},
{autoRefresh: false}, token)
}
return {
chunkIndex: chunkIdx,
run
}
})
let progress = 0
// runs asynchronously in parallel the upload tasks
// number of simultaneous uploads is defined by
// opts.concurrentUploads
eachLimit(uploadTasks, opts.concurrentUploads || 3,
(task, callback) => {
task.run().then((res) => {
if (opts.onProgress) {
progress += 100.0 / nbChunks
opts.onProgress ({
progress: Math.round(progress * 100) / 100,
chunkIndex: task.chunkIndex
})
}
callback ()
}, (err) => {
console.log('error')
console.log(err)
callback(err)
})
}, (err) => {
if (err) {
return reject(err)
}
return resolve({
fileSize: file.size,
bucketKey,
objectKey,
nbChunks
})
})
})
}
This was the code, I was using but it has stopped working. Get anyone help me to solve this as I dont have much knowledge about this.
// JQuery Twitter Feed. Coded by www.tom-elliott.net (2012) and modified from https://twitter.com/javascripts/blogger.js
var p = jQuery.noConflict();
p(document).ready(function () {
var twitterprofile = "waateanews";
var hidereplies = true;
var showretweets = true;
var showtweetlinks = true;
var displayLimit = 3;
p.getJSON('https://api.twitter.com/1/statuses/user_timeline/'+twitterprofile+'.json?include_rts='+showretweets+'&exclude_replies='+hidereplies+'&count=30&callback=?',
function(feeds) {
var feedHTML = '';
var displayCounter = 1;
for (var i=0; i<feeds.length; i++) {
var username = feeds[i].user.screen_name;
var profileimage = feeds[i].user.profile_image_url_https;
var status = feeds[i].text;
if ((feeds[i].text.length > 1) && (displayCounter <= displayLimit)) {
if (showtweetlinks == true) {
status = addlinks(status);
}
if (displayCounter == 1) {
//feedHTML += '<h1>#'+twitterprofile+' on Twitter</h1>';
}
feedHTML += '<div class="twitter-article">';
feedHTML += '<div class="twitter-pic"><img src="'+profileimage+'"images/twitter-feed-icon.png" width="42" height="42" alt="twitter icon" /></div>';
feedHTML += '<div class="twitter-text"><p>'+status+'<br/><span class="tweet-time">'+relative_time(feeds[i].created_at)+'</span></p></div>';
feedHTML += '</div>';
displayCounter++;
}
}
p('#twitter-feed').html(feedHTML);
});
function addlinks(data) {
//Add link to all http:// links within tweets
data = data.replace(/((https?|s?ftp|ssh)\:\/\/[^"\s\<\>]*[^.,;'">\:\s\<\>\)\]\!])/g, function(url) {
return ''+url+'';
});
//Add link to #usernames used within tweets
data = data.replace(/\B#([_a-z0-9]+)/ig, function(reply) {
return ''+reply.charAt(0)+reply.substring(1)+'';
});
return data;
}
function relative_time(time_value) {
var values = time_value.split(" ");
time_value = values[1] + " " + values[2] + ", " + values[5] + " " + values[3];
var parsed_date = Date.parse(time_value);
var relative_to = (arguments.length > 1) ? arguments[1] : new Date();
var delta = parseInt((relative_to.getTime() - parsed_date) / 1000);
delta = delta + (relative_to.getTimezoneOffset() * 60);
if (delta < 60) {
return 'less than a minute ago';
} else if(delta < 120) {
return 'about a minute ago';
} else if(delta < (60*60)) {
return (parseInt(delta / 60)).toString() + ' minutes ago';
} else if(delta < (120*60)) {
return 'about an hour ago';
} else if(delta < (24*60*60)) {
return 'about ' + (parseInt(delta / 3600)).toString() + ' hours ago';
} else if(delta < (48*60*60)) {
return '1 day ago';
} else {
return (parseInt(delta / 86400)).toString() + ' days ago';
}
}
});
</script>
<div id="twitter-feed"></div>
<div id="twitter_footer">Follow us on twitter</div>
Twitter has retired the API v1. Details here. You will have to rewrite the code to use oAuth or use some ready made scripts. I will let you figure that out.
How can I add a timeout to the following script? I want it to display text as "Timed Out".
var bustcachevar = 1 //bust potential caching of external pages after initial request? (1=yes, 0=no)
var loadedobjects = ""
var rootdomain = "http://" + window.location.hostname
var bustcacheparameter = ""
function ajaxpage(url, containerid) {
var page_request = false
if (window.XMLHttpRequest) // if Mozilla, Safari etc
page_request = new XMLHttpRequest()
else if (window.ActiveXObject) { // if IE
try {
page_request = new ActiveXObject("Msxml2.XMLHTTP")
} catch (e) {
try {
page_request = new ActiveXObject("Microsoft.XMLHTTP")
} catch (e) {}
}
} else
return false
document.getElementById(containerid).innerHTML = '<img src="load.gif" border="0"><br><br><strong>Generating Link...</strong>'
page_request.onreadystatechange = function () {
loadpage(page_request, containerid)
}
if (bustcachevar) //if bust caching of external page
bustcacheparameter = (url.indexOf("?") != -1) ? "&" + new Date().getTime() : "?" + new Date().getTime()
page_request.open('GET', url + bustcacheparameter, true)
page_request.send(null)
}
function loadpage(page_request, containerid) {
if (page_request.readyState == 4 && (page_request.status == 200 || window.location.href.indexOf("http") == -1))
document.getElementById(containerid).innerHTML = page_request.responseText
else if (page_request.readyState == 4 && (page_request.status == 404 || window.location.href.indexOf("http") == -1))
document.getElementById(containerid).innerHTML = '<strong>Unable to load link</strong><br>Please try again in a few moments'
}
using the timeout properties of XMLHttpRequest object for example.
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
alert("ready state = 4");
}
};
xhr.open("POST", "http://www.service.org/myService.svc/Method", true);
xhr.setRequestHeader("Content-type", "application/json; charset=utf-8");
xhr.timeout = 4000; // Set timeout to 4 seconds (4000 milliseconds)
xhr.ontimeout = function () { alert("Timed out!!!"); }
xhr.send(json);
the above code works for me!
Cheers