I am trying to create a test page which will contain a HTML5 VIDEO tag which will allow converted videos to be played. I am successfully able to convert the videos and store these locally on the server but I'd like to be able to stream all videos through another .aspx page.
Assuming I have a player.aspx page which will contain the HTML code and getvideo.aspx page which will do nothing except provide the video binary, I thought that the following code would work fine in my player.aspx page:
<div style="text-align:center">
<video controls autoplay id="video1" width="920">
<source src="http://www.mywebsite.com/getvideo.aspx?getvideo=1" type="video/mp4">
Your browser does not support HTML5 video.
</video>
The getvideo.aspx page contains the following vb.net code:
Response.clearheaders
Response.AddHeader("Content-Type", "video/mp4")
Response.AddHeader("Content-Disposition", "inline;filename=""newvideo.mp4""")
dim Err as string = ""
Dim iStream As System.IO.Stream
' Buffer to read 10K bytes in chunk:
Dim buffer(buffersize) As Byte
' Length of the file:
Dim length As Integer
' Total bytes to read:
Dim dataToRead As Long
' Identify the file to download including its path.
Dim filepath As String = "./outout/videos/newvideo.mp4"
' Identify the file name.
Dim filename As String = System.IO.Path.GetFileName(filepath)
' Open the file.
try
iStream = New System.IO.FileStream(filepath, System.IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
catch ex as exception
throw new exception("Could not create FileStream for [" & filepath & "], error follows." & vbcrlf & ex.toString)
end try
Try
' Total bytes to read:
dataToRead = iStream.Length
' Read the bytes.
While dataToRead > 0
' Verify that the client is connected.
If system.web.httpcontext.current.Response.IsClientConnected Then
' Read the data in buffer
length = iStream.Read(buffer, 0, buffersize)
' Write the data to the current output stream.
system.web.httpcontext.current.Response.OutputStream.Write(buffer, 0, length)
' Flush the data to the HTML output.
system.web.httpcontext.current.Response.Flush()
ReDim buffer(buffersize) ' Clear the buffer
dataToRead = dataToRead - length
Else
'prevent infinite loop if user disconnects
dataToRead = -1
End If
End While
Catch ex As Exception
' Trap the error, if any.
err = "Error accessing " & filepath & " : " & ex.tostring
Finally
If IsNothing(iStream) = False Then
' Close the file.
iStream.Close()
End If
End Try
if err<>"" then throw new exception( err )
All I get on my page output is a HTML video player (chrome's basic player) which seems to time out and the PLAY button goes grey. The Network tool in the Chrome Developer tools shows that it's downloading 45mb and gets a 200 response code. This suggests to me that it is working fine. Although I get a second GET request with the status of "Cancelled"?
If I visit www.mywebsite.com/output/videos/myvideo.mp4 then this plays in the browser fine so I know IIS is configured to correctly stream video.
Also if I change the response content disposition to "attachment" then the browser correctly forces a download of the video when going to my ASPX page, but this also doesn't correctly play on the HTML player. Is there something 'clever' going on with the HTML5 VIDEO tag which is stopping a .aspx file from serving up video via .net? Or am I missing a response header?
Thanks!
FIXED!
The problem here is presumably something to do with the HTML5 video implementation of range-requests. Without supporting range-requests in the page being used to SERVE the video, they just won't work.
It was this very helpful post from Scott Mitchell which set me on the case: http://dotnetslackers.com/articles/aspnet/Range-Specific-Requests-in-ASP-NET.aspx
And another helpful post from Chris Coulson which provided the implementation in C# for the solution! http://blogs.visigo.com/chriscoulson/easy-handling-of-http-range-requests-in-asp-net/
My vb.net port of the code Chris found is below, to implement it simply put this in your page:
system.web.httpcontext.current.Response.ContentType = "video/" & convertFormat.toString
RangeDownload(convertedVideo, system.web.httpcontext.current)
VB.NET CODE:
private sub RangeDownload( fullpath as string, context as HttpContext)
dim size as long
dim start as long
dim theend as long
dim length as long
dim fp as long =0
using reader as new StreamReader(fullpath)
size = reader.BaseStream.Length
start = 0
theend = 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 (not String.IsNullOrEmpty(context.Request.ServerVariables("HTTP_RANGE"))) then
dim anotherStart as long = start
dim anotherEnd as long = theend
dim arr_split as string() = context.Request.ServerVariables("HTTP_RANGE").Split("=") 'new char[] { Convert.ToChar("=") })
dim range as string = arr_split(1)
'// Make sure the client hasn't sent us a multibyte range
if (range.IndexOf(",") > -1) then
'// (?) 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 & "-" & theend & "/" & size)
throw new HttpException(416, "Requested Range Not Satisfiable")
end if
'// 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("-")) then
'// The n-number of the last bytes is requested
anotherStart = size - Convert.ToInt64(range.Substring(1))
else
arr_split = range.Split("-")
anotherStart = Convert.ToInt64(arr_split(0))
dim temp as long = 0
if (arr_split.Length > 1 andalso Int64.TryParse(arr_split(1).ToString(), temp)) then
anotherEnd = Convert.ToInt64(arr_split(1))
else
anotherEnd = size
end if
end if
'/* 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.
if (anotherEnd > theend) then
anotherEnd = theend
else
anotherEnd = anotherEnd
end if
'// Validate the requested range and return an error if it's not correct.
if (anotherStart > anotherEnd or anotherStart > size - 1 or anotherEnd >= size) then
context.Response.AddHeader("Content-Range", "bytes " + start + "-" + theend + "/" + size)
throw new HttpException(416, "Requested Range Not Satisfiable")
end if
start = anotherStart
theend = anotherEnd
length = theend - start + 1 '// Calculate new content length
fp = reader.BaseStream.Seek(start, SeekOrigin.Begin)
context.Response.StatusCode = 206
end if
end using
'// Notify the client the byte range we'll be outputting
context.Response.AddHeader("Content-Range", "bytes " & start & "-" & theend & "/" & size)
context.Response.AddHeader("Content-Length", length.ToString())
'// Start buffered download
context.Response.WriteFile(fullpath, fp, length)
context.Response.End()
end sub
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With