I'm trying to use the following code to retrieve video files to play to a user:
public class VideoController : Controller
{
public VideoResult GetMP4Video(string videoID)
{
if (User.Identity.IsAuthenticated)
{
string clipLocation = string.Format("{0}\\Completed\\{1}.mp4", ConfigurationManager.AppSettings["VideoLocation"].ToString(), videoID);
using (FileStream stream = new FileStream(clipLocation, FileMode.Open))
{
FileStreamResult fsResult = new FileStreamResult(stream, "video/mp4");
VideoResult result = new VideoResult(ReadFully(fsResult.FileStream), "video/mp4");
return result;
}
}
else
{
return null;
}
}
private static byte[] ReadFully(Stream input)
{
byte[] buffer = new byte[32 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
}
For displaying to the client I'm using Media Element:
<!-- Video Player Here -->
<video width="640" height="360" poster="@Url.Content(string.Format("~/Videos/{0}_2.jpg", Model.VideoID))" controls="controls" preload="none">
<!-- MP4 for Safari, IE9, iPhone, iPad, Android, and Windows Phone 7 -->
<source type="video/mp4" src="@Url.Action("GetMP4Video", "Video", new { videoID = Model.VideoID })" />
<!-- Flash fallback for non-HTML5 browsers without JavaScript -->
<object width="320" height="240" type="application/x-shockwave-flash" data="@Url.Content("~/Scripts/ME/flashmediaelement.swf")">
<param name="movie" value="@Url.Content("~/Scripts/ME/flashmediaelement.swf")" />
<param name="flashvars" value="controls=true&[email protected]("GetMP4Video", "Video", new { videoID = Model.VideoID })" />
<!-- Image as a last resort -->
<img src="myvideo.jpg" width="320" height="240" title="No video playback capabilities" />
</object>
</video>
The problem is though that the file doesn't seem to play or at least not consistently. Also seeking in the video doesn not appear to work properly either. I guess my question is is this a acceptable way to serve a video to a user? If so what have I got wrong? I think its important that I'm very new to video and I'm very much learning as I go. Any help would be appreciated.
This works for me. Adapted from here:
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();
}
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