我正在尝试将视频文件从ASP.NET MVC提供给iPhone客户端。视频格式正确,如果我在一个可公开访问的网络目录中,它可以正常工作。
我所读到的核心问题是,iPhone要求您拥有一个可随时使用的简历下载环境,该环境允许您通过HTTP标头过滤字节范围。我认为这是为了让用户可以跳过视频。
使用MVC提供文件时,这些标头不存在。我试图模仿它,但没有运气。我们这里有IIS6,我根本无法做很多标题操作。 ASP.NET会抱怨我说“ 此操作需要IIS集成管道模式。 ”
升级不是一种选择,我不允许将文件移动到公共网络共享。我觉得受到环境的限制,但我仍在寻找解决方案。
以下是我正在尝试做的一些示例代码......
public ActionResult Mobile(string guid = "x")
{
guid = Path.GetFileNameWithoutExtension(guid);
apMedia media = DB.apMedia_GetMediaByFilename(guid);
string mediaPath = Path.Combine(Transcode.Swap_MobileDirectory, guid + ".m4v");
if (!Directory.Exists(Transcode.Swap_MobileDirectory)) //Make sure it's there...
Directory.CreateDirectory(Transcode.Swap_MobileDirectory);
if(System.IO.File.Exists(mediaPath))
return base.File(mediaPath, "video/x-m4v");
return Redirect("~/Error/404");
}
我知道我需要做这样的事情,但是我无法在.NET MVC中做到这一点。 http://dotnetslackers.com/articles/aspnet/Range-Specific-Requests-in-ASP-NET.aspx
以下是一个有效的HTTP响应标头示例:
Date Mon, 08 Nov 2010 17:02:38 GMT
Server Apache
Last-Modified Mon, 08 Nov 2010 17:02:13 GMT
Etag "14e78b2-295eff-4cd82d15"
Accept-Ranges bytes
Content-Length 2711295
Content-Range bytes 0-2711294/2711295
Keep-Alive timeout=15, max=100
Connection Keep-Alive
Content-Type text/plain
这是一个没有的例子(这是来自.NET)
Server ASP.NET Development Server/10.0.0.0
Date Mon, 08 Nov 2010 18:26:17 GMT
X-AspNet-Version 4.0.30319
X-AspNetMvc-Version 2.0
Content-Range bytes 0-2711294/2711295
Cache-Control private
Content-Type video/x-m4v
Content-Length 2711295
Connection Close
有什么想法吗?谢谢。
答案 0 :(得分:21)
更新:现在是project on CodePlex。
好的,我让它在我当地的测试站工作,我可以将视频流式传输到我的iPad上。它有点脏,因为它比我预期的要困难一些,现在它正在工作,我现在没有时间清理它。关键部分:
动作过滤器:
public class ByteRangeRequest : FilterAttribute, IActionFilter
{
protected string RangeStart { get; set; }
protected string RangeEnd { get; set; }
public ByteRangeRequest(string RangeStartParameter, string RangeEndParameter)
{
RangeStart = RangeStartParameter;
RangeEnd = RangeEndParameter;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext == null)
throw new ArgumentNullException("filterContext");
if (!filterContext.ActionParameters.ContainsKey(RangeStart))
filterContext.ActionParameters.Add(RangeStart, null);
if (!filterContext.ActionParameters.ContainsKey(RangeEnd))
filterContext.ActionParameters.Add(RangeEnd, null);
var headerKeys = filterContext.RequestContext.HttpContext.Request.Headers.AllKeys.Where(key => key.Equals("Range", StringComparison.InvariantCultureIgnoreCase));
Regex rangeParser = new Regex(@"(\d+)-(\d+)", RegexOptions.Compiled);
foreach(string headerKey in headerKeys)
{
string value = filterContext.RequestContext.HttpContext.Request.Headers[headerKey];
if (!string.IsNullOrEmpty(value))
{
if (rangeParser.IsMatch(value))
{
Match match = rangeParser.Match(value);
filterContext.ActionParameters[RangeStart] = int.Parse(match.Groups[1].ToString());
filterContext.ActionParameters[RangeEnd] = int.Parse(match.Groups[2].ToString());
break;
}
}
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
基于FileStreamResult的自定义结果:
public class ContentRangeResult : FileStreamResult
{
public int StartIndex { get; set; }
public int EndIndex { get; set; }
public long TotalSize { get; set; }
public DateTime LastModified { get; set; }
public FileStreamResult(int startIndex, int endIndex, long totalSize, DateTime lastModified, string contentType, Stream fileStream)
: base(fileStream, contentType)
{
StartIndex = startIndex;
EndIndex = endIndex;
TotalSize = totalSize;
LastModified = lastModified;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = this.ContentType;
response.AddHeader(HttpWorkerRequest.GetKnownResponseHeaderName(HttpWorkerRequest.HeaderContentRange), string.Format("bytes {0}-{1}/{2}", StartIndex, EndIndex, TotalSize));
response.StatusCode = 206;
WriteFile(response);
}
protected override void WriteFile(HttpResponseBase response)
{
Stream outputStream = response.OutputStream;
using (this.FileStream)
{
byte[] buffer = new byte[0x1000];
int totalToSend = EndIndex - StartIndex;
int bytesRemaining = totalToSend;
int count = 0;
FileStream.Seek(StartIndex, SeekOrigin.Begin);
while (bytesRemaining > 0)
{
if (bytesRemaining <= buffer.Length)
count = FileStream.Read(buffer, 0, bytesRemaining);
else
count = FileStream.Read(buffer, 0, buffer.Length);
outputStream.Write(buffer, 0, count);
bytesRemaining -= count;
}
}
}
}
我的MVC行动:
[ByteRangeRequest("StartByte", "EndByte")]
public FileStreamResult NextSegment(int? StartByte, int? EndByte)
{
FileStream contentFileStream = System.IO.File.OpenRead(@"C:\temp\Gets.mp4");
var time = System.IO.File.GetLastWriteTime(@"C:\temp\Gets.mp4");
if (StartByte.HasValue && EndByte.HasValue)
return new ContentRangeResult(StartByte.Value, EndByte.Value, contentFileStream.Length, time, "video/x-m4v", contentFileStream);
return new ContentRangeResult(0, (int)contentFileStream.Length, contentFileStream.Length, time, "video/x-m4v", contentFileStream);
}
我真的希望这会有所帮助。我花了很多时间在这上面!您可能想要尝试的一件事是移除碎片直到它再次破裂。很高兴看到ETag的东西,修改日期等是否可以被删除。我现在没有时间。
快乐的编码!
答案 1 :(得分:2)
我尝试寻找现有的扩展程序,但我没有立即找到一个(也许我的搜索功能很弱。)
我的想法是你需要制作两个新课程。
首先,创建一个继承自ActionMethodSelectorAttribute
的类。这与HttpGet
,HttpPost
等基类相同。在此课程中,您将覆盖IsValidForRequest
。在该方法中,检查标头以查看是否请求了范围。您现在可以使用此属性来装饰控制器中的某个方法,当某人被请求成为流的一部分(iOS,Silverlight等)时,该方法将被调用。
其次,创建一个继承自ActionResult
或FileResult
的类,并覆盖ExecuteResult
方法,以添加为要返回的字节范围标识的标题。像JSON对象一样返回它,其中包含字节范围start,end,total size的参数,因此它可以正确生成响应头。
查看FileContentResult
的实现方式,了解如何访问上下文的HttpResponse
对象以更改标题。
查看HttpGet
,了解它如何实现对IsValidForRequest
的检查。 CodePlex上提供了源代码,或者您可以像我刚才那样使用Reflector。
您可以使用此信息进行更多搜索,看看是否有人已经创建了此自定义ActionResult
。
供参考,这是AcceptVerbs属性的样子:
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
string httpMethodOverride = controllerContext.HttpContext.Request.GetHttpMethodOverride();
return this.Verbs.Contains<string>(httpMethodOverride, StringComparer.OrdinalIgnoreCase);
}
这就是FileResult的样子。注意使用AddHeader:
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = this.ContentType;
if (!string.IsNullOrEmpty(this.FileDownloadName))
{
string headerValue = ContentDispositionUtil.GetHeaderValue(this.FileDownloadName);
context.HttpContext.Response.AddHeader("Content-Disposition", headerValue);
}
this.WriteFile(response);
}
我把它拼凑在一起。我不知道它是否适合你的需要(或有效)。
public class ContentRangeResult : FileStreamResult
{
public int StartIndex { get; set; }
public int EndIndex { get; set; }
public int TotalSize { get; set; }
public ContentRangeResult(int startIndex, int endIndex, string contentType, Stream fileStream)
:base(fileStream, contentType)
{
StartIndex = startIndex;
EndIndex = endIndex;
TotalSize = endIndex - startIndex;
}
public ContentRangeResult(int startIndex, int endIndex, string contentType, string fileDownloadName, Stream fileStream)
: base(fileStream, contentType)
{
StartIndex = startIndex;
EndIndex = endIndex;
TotalSize = endIndex - startIndex;
FileDownloadName = fileDownloadName;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
if (!string.IsNullOrEmpty(this.FileDownloadName))
{
System.Net.Mime.ContentDisposition cd = new System.Net.Mime.ContentDisposition() { FileName = FileDownloadName };
context.HttpContext.Response.AddHeader("Content-Disposition", cd.ToString());
}
context.HttpContext.Response.AddHeader("Accept-Ranges", "bytes");
context.HttpContext.Response.AddHeader("Content-Range", string.Format("bytes {0}-{1}/{2}", StartIndex, EndIndex, TotalSize));
//Any other headers?
this.WriteFile(response);
}
protected override void WriteFile(HttpResponseBase response)
{
Stream outputStream = response.OutputStream;
using (this.FileStream)
{
byte[] buffer = new byte[0x1000];
int totalToSend = EndIndex - StartIndex;
int bytesRemaining = totalToSend;
int count = 0;
while (bytesRemaining > 0)
{
if (bytesRemaining <= buffer.Length)
count = FileStream.Read(buffer, 0, bytesRemaining);
else
count = FileStream.Read(buffer, 0, buffer.Length);
outputStream.Write(buffer, 0, count);
bytesRemaining -= count;
}
}
}
}
像这样使用:
return new ContentRangeResult(50, 100, "video/x-m4v", "SomeOptionalFileName", contentFileStream);
答案 2 :(得分:0)
你可以搬出MVC吗?这是一个系统抽象射击你的脚的情况,但一个简单的jane IHttpHandler应该有更多的选择。
所有这一切,在您实施自己的流媒体服务器之前,您最好购买或租用一台。 。
答案 3 :(得分:-1)
有效内容类型设置为text / plain的标题是正确的还是拼写错误? 任何人,您可以尝试在Action上设置此标题:
Response.Headers.Add(...)