Video Streaming for a IIS hosted MVC web application using HTML5 video - asp.net-mvc

I was following article on http://blogs.visigo.com/chriscoulson/easy-handling-of-http-range-requests-in-asp-net and wrote simple MVC application to stream large video files.
Here is my code with slight modifications to the code in that tutorial,
internal static void StreamVideo(string fullpath, HttpContextBase context)
{
long size, start, end, length, fp = 0;
using (StreamReader reader = new StreamReader(fullpath))
{
size = reader.BaseStream.Length;
start = 0;
end = size - 1;
length = size;
// Now that we've gotten so far without errors we send the accept range header
/* At the moment we only support single ranges.
* Multiple ranges requires some more work to ensure it works correctly
* and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
*
* Multirange support annouces itself with:
* header('Accept-Ranges: bytes');
*
* Multirange content must be sent with multipart/byteranges mediatype,
* (mediatype = mimetype)
* as well as a boundry header to indicate the various chunks of data.
*/
context.Response.AddHeader("Accept-Ranges", "0-" + size);
// header('Accept-Ranges: bytes');
// multipart/byteranges
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
if (!String.IsNullOrEmpty(context.Request.ServerVariables["HTTP_RANGE"]))
{
long anotherStart = start;
long anotherEnd = end;
string[] arr_split = context.Request.ServerVariables["HTTP_RANGE"].Split(new char[] { Convert.ToChar("=") });
string range = arr_split[1];
// Make sure the client hasn't sent us a multibyte range
if (range.IndexOf(",") > -1)
{
// (?) Shoud this be issued here, or should the first
// range be used? Or should the header be ignored and
// we output the whole content?
context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
throw new HttpException(416, "Requested Range Not Satisfiable");
}
// If the range starts with an '-' we start from the beginning
// If not, we forward the file pointer
// And make sure to get the end byte if spesified
if (range.StartsWith("-"))
{
// The n-number of the last bytes is requested
anotherStart = size - Convert.ToInt64(range.Substring(1));
}
else
{
arr_split = range.Split(new char[] { Convert.ToChar("-") });
anotherStart = Convert.ToInt64(arr_split[0]);
long temp = 0;
anotherEnd = (arr_split.Length > 1 && Int64.TryParse(arr_split[1].ToString(), out temp)) ? Convert.ToInt64(arr_split[1]) : size;
}
/* Check the range and make sure it's treated according to the specs.
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
// End bytes can not be larger than $end.
anotherEnd = (anotherEnd > end) ? end : anotherEnd;
// Validate the requested range and return an error if it's not correct.
if (anotherStart > anotherEnd || anotherStart > size - 1 || anotherEnd >= size)
{
context.Response.ContentType = MimeMapping.GetMimeMapping(fullpath);
context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
throw new HttpException(416, "Requested Range Not Satisfiable");
}
start = anotherStart;
end = anotherEnd;
length = end - start + 1; // Calculate new content length
fp = reader.BaseStream.Seek(start, SeekOrigin.Begin);
context.Response.StatusCode = 206;
}
}
// Notify the client the byte range we'll be outputting
context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
context.Response.AddHeader("Content-Length", length.ToString());
// Start buffered download
context.Response.WriteFile(fullpath, fp, length);
context.Response.End();
}
When I streaming large capacity(around 700MB) video in a network using above code the starting of video is very slow (around 1-2 minutes). In this stage I checked the network requests and it seems browser ask for video and waiting for a response from server. This is really annoying for the user.
Then once it started video is playing smoothly (It is a 720P resolution video and as my network connection is good video is playing very smoothly).
But when I do a seek with controls on html video player, then same issue happens and I have to wait another 1-2 minutes till response completed.
I am using IIS7 (MVC4). If I play the same video which located inside of IIS dir then I can play it without mentioned delay. Also if the video is located outside of IIS folder but if it within the same machine that hosted IIS then also no issues.
I am having this issue when I have video in a network location which is a different machine that IIS hosted.
So conclusion is,
This is not because of user browser trying to load large video in to browser. It is something between video share machine vs IIS.
Any idea about resolving this?
Regards,
-Lasith

Related

How do I determine an expired access token?

I am interfacing with the Microsoft Health Cloud API and have successfully requested an access token and refresh token. Communication with the RESTful API works as intended, although I am having a hard time figuring out, how to reliably determine an expired access token.
I have the following code in place:
fire_and_forget read_profile()
{
HttpClient httpClient{};
httpClient.DefaultRequestHeaders().Authorization({ L"bearer", access_token_ });
try
{
auto const response{ co_await httpClient.GetStringAsync({ L"https://api.microsofthealth.net/v1/me/Profile" }) };
// Raise event passing the response along.
// Code left out for brevity.
co_return;
}
catch (hresult_error const& e)
{
if (e.code() != 0x80190191) // Magic value for "unauthorized access (401)"
{
throw;
}
// This is an "unauthorized access (401)" error. Continue with requesting a new
// access token from the refresh token.
// Code left out for brevity.
}
Although it appears to work, it feels wrong for so many reasons. It's not just the magic value, but also the fact, that this particular error code may be used for other error modes.
Is there a more robust way of determining, whether an access token has expired?
Note: I understand, that I could use the expiration interval, and check against the system time. I'd rather not go down that route, as it isn't entirely reliable either, and introduces additional complexity for roaming that information across devices.
I understand, that I could use the expiration interval, and check against the system time.
Microsoft Health Cloud API has provided expires_in field to verify the token is valid. In general, we could check against the system time, and if the system time was artificially modified, it isn't entirely reliable. So we could use NTP server time, rather than use system time.
public async static Task<DateTime> GetNetworkTime()
{
//default Windows time server
const string ntpServer = "time.windows.com";
// NTP message size - 16 bytes of the digest (RFC 2030)
var ntpData = new byte[48];
//Setting the Leap Indicator, Version Number and Mode values
ntpData[0] = 0x1B; //LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)
var addresses = await Dns.GetHostAddressesAsync(ntpServer);
//The UDP port number assigned to NTP is 123
var ipEndPoint = new IPEndPoint(addresses[0], 123);
//NTP uses UDP
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
{
socket.Connect(ipEndPoint);
//Stops code hang if NTP is blocked
socket.ReceiveTimeout = 3000;
socket.Send(ntpData);
socket.Receive(ntpData);
socket.Dispose();
}
//Offset to get to the "Transmit Timestamp" field (time at which the reply
//departed the server for the client, in 64-bit timestamp format."
const byte serverReplyTime = 40;
//Get the seconds part
ulong intPart = BitConverter.ToUInt32(ntpData, serverReplyTime);
//Get the seconds fraction
ulong fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4);
//Convert From big-endian to little-endian
intPart = SwapEndianness(intPart);
fractPart = SwapEndianness(fractPart);
var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
//**UTC** time
var networkDateTime = (new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds((long)milliseconds);
return networkDateTime.ToLocalTime();
}
// stackoverflow.com/a/3294698/162671
static uint SwapEndianness(ulong x)
{
return (uint)(((x & 0x000000ff) << 24) +
((x & 0x0000ff00) << 8) +
((x & 0x00ff0000) >> 8) +
((x & 0xff000000) >> 24));
}

How to calculate the throughput in Cooja simulator

Hi All,
I work with example/ipv6/rpl-udp files in cooja simulator. How do I measure the amount of throughput in the network?
With the command "powertrace_start(CLOCK_SECOND * 60); " in the client.c code, I get the Powertrace output.
Can I use this method?
throughput= packet received / Simulation time
Simulation time = (ENERGEST_TYPE_TRANSMIT + ENERGEST_TYPE_LISTEN) / 32768
Is the method correct?
Thanks in advance,
Nasrin
No, that is not correct as ENERGEST_TYPE_* are constants.
One way to do it is with Cooja simulator scripts.
For example, let's say you have a C program that prints "Message transmitted" every time the node sends a message to another node and "Message received" every time it receives as message.
A Cooja script can automatically run the simulation for a specific time and count the messages. This code works for me:
TIMEOUT(100000); // simulation duration in milliseconds
num_messages_tx = 0;
num_messages_rx = 0;
timeout_function = function () {
log.log("Script timed out.\n");
log.log("Messages transmitted: " + num_messages_tx + " \n");
log.log("Messages received: " + num_messages_rx + " \n");
log.testOK();
}
while (true) {
if (msg) {
if(msg.startsWith("Message transmitted")) {
num_messages_tx += 1;
}
if(msg.startsWith("Message received")) {
num_messages_rx += 1;
}
}
YIELD();
}
To start using it, save the code (its JavaScript) in a file test.js and add this to your .csc Cooja configuration file:
<plugin>
org.contikios.cooja.plugins.ScriptRunner
<plugin_config>
<scriptfile>[CONFIG_DIR]/test.js</scriptfile>
<active>true</active>
</plugin_config>
<width>457</width>
<z>4</z>
<height>427</height>
<location_x>3</location_x>
<location_y>404</location_y>
</plugin>

Xamarin - WCF Upload Large Files Report progress vis UIProgressView

I have create a WCF Service that allows uploading large files via BasicHttpBinding using streaming and it is working great! I would like to extended this to show a progress bar (UIProgressView) so that when a large file is being uploaded in 65k chunks, the user can see that it is actively working.
The client code calling the WCF Service is:
BasicHttpBinding binding = CreateBasicHttp ();
BTSMobileWcfClient _client = new BTSMobileWcfClient (binding, endPoint);
_client.UploadFileCompleted += ClientUploadFileCompleted;
byte[] b = File.ReadAllBytes (zipFileName);
using (new OperationContextScope(_client.InnerChannel)) {
OperationContext.Current.OutgoingMessageHeaders.Add(System.ServiceModel.Channels.MessageHeader.CreateHeader("SalvageId","",iBTSSalvageId.ToString()));
OperationContext.Current.OutgoingMessageHeaders.Add(System.ServiceModel.Channels.MessageHeader.CreateHeader("FileName","",Path.GetFileName(zipFileName)));
OperationContext.Current.OutgoingMessageHeaders.Add(System.ServiceModel.Channels.MessageHeader.CreateHeader("Length","",b.LongLength));
_client.UploadFileAsync(b);
}
On the server side, I read the file stream in 65k chuncks and do report back to the calling routine "bytes read", etc. A snippet of code for that is:
using (FileStream targetStream = new FileStream(filePath, FileMode.CreateNew,FileAccess.Write)) {
//read from the input stream in 65000 byte chunks
const int chunkSize = 65536;
byte[] buffer = new byte[chunkSize];
do {
// read bytes from input stream
int bytesRead = request.FileData.Read(buffer, 0, chunkSize);
if (bytesRead == 0) break;
// write bytes to output stream
targetStream.Write(buffer, 0, bytesRead);
} while (true);
targetStream.Close();
}
But I don't know how to hook into the callback on the Xamarin side to receive the "bytes read" versus "total bytes to send" so I can update the UIProgressView.
Has anyone tried this or is this even possible?
Thanks In Advance,
Bo

Video streaming to ipad does not work with Tapestry5

I want to stream a video to my IPad via the HTML5 video tag with tapestry5 (5.3.5) on the backend. Usually the serverside framework shouldn't even play a role in this but somehow it does.
Anyway, hopefully someone here can help me out. Please keep in mind that my project is very much a prototype and that what I describe is simplified / reduced to the relevant parts. I would very much appreciate it if people didn't respond with the obligatory "you want to do the wrong thing" or security/performance nitpicks that aren't relevant to the problem.
So here it goes:
Setup
I have a video taken from the Apple HTML5 showcase so I know that format isn't an issue. I have a simple tml page "Play" that just contains a "video" tag.
Problem
I started by implementing a RequestFilter that handles the request from the video control by opening the referenced video file and streaming it to client. That's basic "if path starts with 'file' then copy file inputstream to response outputstream". This works very well with Chrome but not with the Ipad. Fine, I though, must be some headers I'm missing so I looked at the Apple Showcase again and included the same headers and content type but no joy.
Next, I though, well, let's see what happens if I let t5 serve the file. I copied the video to the webapp context, disabled my request filter and put the simple filename in the video's src attribute. This works in Chrome AND IPad.
That surprised me and prompted me to look at how T5 handles static files / context request. Thus far I've only gotten so far as to feel like there are two different paths which I've confirmed by switching out the hardwired "video src" to an Asset with a #Path("context:"). This, again, works on Chrome but not on IPad.
So I'm really lost here. What's this secret juice in the "simple context" requests that allow it to work on the IPad? There is nothing special going on and yet it's the only way this works. Problem is, I can't really serve those vids from my webapp context ...
Solution
So, it turns out that there is this http header called "Range" and that the IPad, unlike Chrome uses it with video. The "secret sauce" then is that the servlet handler for static resource request know how to deal with range requests while T5's doesn't. Here is my custom implementation:
OutputStream os = response.getOutputStream("video/mp4");
InputStream is = new BufferedInputStream( new FileInputStream(f));
try {
String range = request.getHeader("Range");
if( range != null && !range.equals("bytes=0-")) {
logger.info("Range response _______________________");
String[] ranges = range.split("=")[1].split("-");
int from = Integer.parseInt(ranges[0]);
int to = Integer.parseInt(ranges[1]);
int len = to - from + 1 ;
response.setStatus(206);
response.setHeader("Accept-Ranges", "bytes");
String responseRange = String.format("bytes %d-%d/%d", from, to, f.length());
logger.info("Content-Range:" + responseRange);
response.setHeader("Connection", "close");
response.setHeader("Content-Range", responseRange);
response.setDateHeader("Last-Modified", new Date().getTime());
response.setContentLength(len);
logger.info("length:" + len);
byte[] buf = new byte[4096];
is.skip(from);
while( len != 0) {
int read = is.read(buf, 0, len >= buf.length ? buf.length : len);
if( read != -1) {
os.write(buf, 0, read);
len -= read;
}
}
} else {
response.setStatus(200);
IOUtils.copy(is, os);
}
} finally {
os.close();
is.close();
}
I want to post my refined solution from above. Hopefully this will be useful to someone.
So basically the problem seemed to be that I was disregarding the "Range" http request header which the IPad didn't like. In a nutshell this header means that the client only wants a certain part (in this case a byte range) of the response.
This is what an iPad html video request looks like::
[INFO] RequestLogger Accept:*/*
[INFO] RequestLogger Accept-Encoding:identity
[INFO] RequestLogger Connection:keep-alive
[INFO] RequestLogger Host:mars:8080
[INFO] RequestLogger If-Modified-Since:Wed, 10 Oct 2012 22:27:38 GMT
[INFO] RequestLogger Range:bytes=0-1
[INFO] RequestLogger User-Agent:AppleCoreMedia/1.0.0.9B176 (iPad; U; CPU OS 5_1 like Mac OS X; en_us)
[INFO] RequestLogger X-Playback-Session-Id:BC3B397D-D57D-411F-B596-931F5AD9879F
It means that the iPad only wants the first byte. If you disregard this header and simply send a 200 response with the full body then the video won't play. So, you need send a 206 response (partial response) and set the following response headers:
[INFO] RequestLogger Content-Range:bytes 0-1/357772702
[INFO] RequestLogger Content-Length:2
This means "I'm sending you byte 0 through 1 of 357772702 total bytes available".
When you actually start playing the video, the next request will look like this (everything except the range header ommited):
[INFO] RequestLogger Range:bytes=0-357772701
So my refined solution looks like this:
OutputStream os = response.getOutputStream("video/mp4");
try {
String range = request.getHeader("Range");
/** if there is no range requested we will just send everything **/
if( range == null) {
InputStream is = new BufferedInputStream( new FileInputStream(f));
try {
IOUtils.copy(is, os);
response.setStatus(200);
} finally {
is.close();
}
return true;
}
requestLogger.info("Range response _______________________");
String[] ranges = range.split("=")[1].split("-");
int from = Integer.parseInt(ranges[0]);
/**
* some clients, like chrome will send a range header but won't actually specify the upper bound.
* For them we want to send out our large video in chunks.
*/
int to = HTTP_DEFAULT_CHUNK_SIZE + from;
if( to >= f.length()) {
to = (int) (f.length() - 1);
}
if( ranges.length == 2) {
to = Integer.parseInt(ranges[1]);
}
int len = to - from + 1 ;
response.setStatus(206);
response.setHeader("Accept-Ranges", "bytes");
String responseRange = String.format("bytes %d-%d/%d", from, to, f.length());
response.setHeader("Content-Range", responseRange);
response.setDateHeader("Last-Modified", new Date().getTime());
response.setContentLength(len);
requestLogger.info("Content-Range:" + responseRange);
requestLogger.info("length:" + len);
long start = System.currentTimeMillis();
RandomAccessFile raf = new RandomAccessFile(f, "r");
raf.seek(from);
byte[] buf = new byte[IO_BUFFER_SIZE];
try {
while( len != 0) {
int read = raf.read(buf, 0, buf.length > len ? len : buf.length);
os.write(buf, 0, read);
len -= read;
}
} finally {
raf.close();
}
logger.info("r/w took:" + (System.currentTimeMillis() - start));
} finally {
os.close();
}
This solution is better then my first one because it handles all cases for "Range" requests which seems to be a prereq for clients like Chrome to be able to support skipping within the video ( at which point they'll issue a range request for that point in the video).
It's still not perfect though. Further improvments would be setting the "Last-Modified" header correctly and doing proper handling of clients requests an invalid range or a range of something else then bytes.
I suspect this is more about iPad than about Tapestry.
I might invoke Response.disableCompression() before writing the stream to the response; Tapestry may be trying to GZIP your stream, and the iPad may not be prepared for that, as video and image formats are usually already compressed.
Also, I don't see a content type header being set; again the iPad may simply be more sensitive to that than Chrome.

How to properly stream big data from MVC3 without using too much RAM?

I'd like to use HttpResponse.OutputStream together with ContentResult so that I can Flush from time to time to avoid using too much RAM by .Net.
But all examples with MVC FileStreamResult, EmptyResult, FileResult, ActionResult, ContentResult show code that gets all the data into memory and passes to one of those. Also one post suggest that returning EmptyResult together with using HttpResponse.OutputStream is bad idea. How else can I do that in MVC ?
What is the right way to organize flushable output of big data (html or binary) from MVC server ?
Why is returning EmptyResult or ContentResult or FileStreamResult a bad idea ?
You would want to use FileStreamResult if you already had a stream to work with. A lot of times you may only have access to the file, need to build a stream and then output that to the client.
System.IO.Stream iStream = null;
// Buffer to read 10K bytes in chunk:
byte[] buffer = new Byte[10000];
// Length of the file:
int length;
// Total bytes to read:
long dataToRead;
// Identify the file to download including its path.
string filepath = "DownloadFileName";
// Identify the file name.
string filename = System.IO.Path.GetFileName(filepath);
try
{
// Open the file.
iStream = new System.IO.FileStream(filepath, System.IO.FileMode.Open,
System.IO.FileAccess.Read,System.IO.FileShare.Read);
// Total bytes to read:
dataToRead = iStream.Length;
Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition", "attachment; filename=" + filename);
// Read the bytes.
while (dataToRead > 0)
{
// Verify that the client is connected.
if (Response.IsClientConnected)
{
// Read the data in buffer.
length = iStream.Read(buffer, 0, 10000);
// Write the data to the current output stream.
Response.OutputStream.Write(buffer, 0, length);
// Flush the data to the HTML output.
Response.Flush();
buffer= new Byte[10000];
dataToRead = dataToRead - length;
}
else
{
//prevent infinite loop if user disconnects
dataToRead = -1;
}
}
}
catch (Exception ex)
{
// Trap the error, if any.
Response.Write("Error : " + ex.Message);
}
finally
{
if (iStream != null)
{
//Close the file.
iStream.Close();
}
Response.Close();
}
Here is the microsoft article explaining the above code.

Resources