video + webapi (一)

HTML5视频通过将metadata置于文件开头实现部分加载播放。Web服务器的范围请求(range request)允许按需下载视频片段,实现渐进式加载和快进。在ASP.NET中,IIS原生支持范围请求,但ASPX页面需手动处理。WebAPI的StreamContent可用于处理范围请求,返回状态码206表示部分内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引自: HTML5 video标签播放视频下载原理

HTML5 video does not work like streaming technologies or Flash. So how does the browser manage to play a long video without downloading the whole file before playing it? Part of the trick is that the video is encoded in such a way that the metadata is at the top of the file. This means once the first chunk of data is returned it is enough to determine whether the browser can play it all the way through. If you are encoding video for use with the video element, you will want to choose the Web optimized option in the encoding software. (See the section on video encoding above.)

video 的metadata 被放到了文件头部,这样可以不用加载整个文件就能判断出浏览器是否播放这个video。

The real trick though is how Web servers allow you to only download the a part of a file you request. The browser requests a chunk of the video at a time which allows HTML5 video to give the appearance of streaming. This behavior of mimicking streaming is called progressive download. This also allows fast seeking through the video by clicking the time rail on an unbuffered portion of the video. In general, requesting just a chunk of a file from a Web server is called a range request or “byte serving.

真正的原因应该是服务器允许每次请求下载文件的一部分。这种被称为“progressive download” 或者 “range request”


引自:mdn: HTTP range requests

If the Accept-Ranges is present in HTTP responses (and its value isn't "none"), the server supports range requests. You can check this by issuing a HEAD request and cURL, for example.
curl -I http://i.imgur.com/z4d4kWk.jpg

HTTP/1.1 200 OK
...
Accept-Ranges: bytes
Content-Length: 146515
In this response, Accept-Ranges: bytes indicates that bytes can be used as unit to define a range. Here the Content-Length header is also useful as it indicates the full size of the image to retrieve

"Accept-Ranges" 字段在返回头部出现,则说明服务器支持 range request ,这个字段的值是 bytes 。然后还需要的就是 "Content-Length" 标出文件字节长度。

其实,如果使用 webapi 的 StreamContent 可以返回整个视频文件,也可以实现播放,并且在浏览器端也是渐进式加载视频文件。

<video id="video" controls width="800" height="600">
    <source  src="/api/videoapi/PlayVideo/1" type="video/mp4"/>
</video>
        [HttpGet]
        public HttpResponseMessage PlayVideo(string id)
        {
            try
            {
                string filePath = ("F:\\c#\\WebApplication1\\WebApplication1\\Upload\\24854-376704-huipkn44.wae_H264_1500kbps_AAC_und_ch2_96kbps.mp4");
                HttpResponseMessage rsp = this.Request.CreateResponse(HttpStatusCode.OK);
                byte[] buffer = System.IO.File.ReadAllBytes(filePath);
                rsp.Content = new StreamContent(new MemoryStream(buffer));
                rsp.Headers.Add("ContentType", "video/mp4");
                return rsp;
            }
            catch (Exception e) {
                throw;
            }
        }

这样写可以播放视频,但是有个问题,不能拖动进度条。range request 可以解决进度条的问题。


引自:Easy Handling of Http Range Requests in ASP.NET

IIS 7 supports range requests natively, so if you are simply serving the video files directly from IIS you won’t have any problems. In this case, all file requests were being served by an ASPX page – it looked up information in a database as well as did some authorization checking before returning the file. Unfortunately range requests are not natively supported from aspx responses – they have to be handled manually

在上面的方法中,如果将网站host到本地iis后,查看浏览器的media请求,会发现video文件并非一次被下载到浏览器,可能就是因为 iis 10(win10)的自带功能,这个我并没有理解。

文章中的代码:

private void RangeDownload(string fullpath,HttpContext 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.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();

}

这段代码是可以完全应用到项目中,其中参数 HttpContext context ,可以使用 HttpContext.Current 。

还有一点需要注意,既然是 rang request ,那么返回的 http status code 是 206 , 即代表 partial content 。

最后:使用 webapi 中的对象,同样思路实现了一版

        [HttpGet]
        public HttpResponseMessage PlayVideoRange(string id) {
            try
            {
                string filePath = ("F:\\c#\\WebApplication1\\WebApplication1\\Upload\\24854-376704-huipkn44.wae_H264_1500kbps_AAC_und_ch2_96kbps.mp4");
                HttpResponseMessage rsp = this.Request.CreateResponse(HttpStatusCode.PartialContent);
                using (StreamReader sr = new StreamReader(filePath))
                {
                    long size =0,start =0, end = 0;
                    size = sr.BaseStream.Length;
                    end = size - 1;
                    //rsp.Headers.AcceptRanges.Add("0-" + length.ToString());
                    rsp.Headers.AcceptRanges.Add("bytes");
                    if (this.Request.Headers.Range != null)
                    {
                        RangeItemHeaderValue requestRange = this.Request.Headers.Range.Ranges.First();
                        start = requestRange.From.Value;
                        long count = 1024 * 1024 * 5;
                        long anotherEnd = (start + count) > end ? end : (start + count);
                        end = requestRange.To == null ? anotherEnd : requestRange.To.Value;
                    }
                    sr.BaseStream.Seek(start, SeekOrigin.Begin);
                    byte[] buffer = new byte[end - start + 1];
                    sr.BaseStream.Read(buffer, 0, buffer.Length);
                    MemoryStream ms = new MemoryStream(buffer);
                    rsp.Content = new StreamContent(ms);
                    rsp.Content.Headers.ContentRange = new ContentRangeHeaderValue(start, end, size);
                    rsp.Content.Headers.ContentType = new MediaTypeHeaderValue("video/mp4");
                    return rsp;
                }
            }
            catch (InvalidByteRangeException invalidByteRangeException)
            {
                return Request.CreateErrorResponse(invalidByteRangeException);
            }
            catch (Exception e) {
                throw;
            }
        }

<video id="video" controls width="800" height="600">
    <source src="/api/videoapi/PlayVideoRange/1" type="video/mp4" />
</video>


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值