ابتدا پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب آن را MVC + Web API انتخاب کنید.
ابتدا به فایل WebApiConfig.cs در پوشه App_Start مراجعه کنید و مسیر پیش فرض را حذف کنید. برای مسیریابی سرویسها از قابلیت جدید Attribute Routing استفاده خواهیم کرد. فایل مذکور باید مانند لیست زیر باشد.
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API configuration and services // Web API routes config.MapHttpAttributeRoutes(); } }
سپس در پوشه Models کلاس جدیدی بنام VideoStream ایجاد کنید. این کلاس مسئول نوشتن داده فایلهای ویدیویی در OutputStream خواهد بود. کد کامل این کلاس را در لیست زیر مشاهده میکنید.
public class VideoStream { private readonly string _filename; private long _contentLength; public long FileLength { get { return _contentLength; } } public VideoStream(string videoPath) { _filename = videoPath; using (var video = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { _contentLength = video.Length; } } public async void WriteToStream(Stream outputStream, HttpContent content, TransportContext context) { try { var buffer = new byte[65536]; using (var video = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { var length = (int)video.Length; var bytesRead = 1; while (length > 0 && bytesRead > 0) { bytesRead = video.Read(buffer, 0, Math.Min(length, buffer.Length)); await outputStream.WriteAsync(buffer, 0, bytesRead); length -= bytesRead; } } } catch (HttpException) { return; } finally { outputStream.Close(); } } }
شرح کلاس VideoStream
این کلاس ابتدا دو فیلد خصوصی تعریف میکند. یکی filename_ که فقط-خواندنی است و نام فایل ویدیو درخواستی را نگهداری میکند. و دیگری contentLength_ که سایز فایل ویدیو درخواستی را نگهداری میکند.
یک خاصیت عمومی بنام FileLength نیز تعریف شده که مقدار خاصیت contentLength_ را بر میگرداند.
متد سازنده این کلاس پارامتری از نوع رشته بنام videoPath را میپذیرد که مسیر کامل فایل ویدیوی مورد نظر است. در این متد، متغیرهای filename_ و contentLength_ مقدار دهی میشوند. نکتهی قابل توجه در این متد استفاده از پارامتر FileShare.Read است که باعث میشود فایل مورد نظر هنگام باز شدن قفل نشود و برای پروسههای دیگر قابل دسترسی باشد.
در آخر متد WriteToStream را داریم که مسئول نوشتن داده فایلها به OutputStream است. اول از همه دقت کنید که این متد از کلمه کلیدی async استفاده میکند بنابراین بصورت asynchronous اجرا خواهد شد. در بدنه این متد متغیری بنام buffer داریم که یک آرایه بایت با سایز 64KB را تعریف میکند. به بیان دیگر اطلاعات فایلها را در پکیجهای 64 کیلوبایتی برای کلاینت ارسال خواهیم کرد. در ادامه فایل مورد نظر را باز میکنیم (مجددا با استفاده از FileShare.Read) و شروع به خواندن اطلاعات آن میکنیم. هر 64 کیلوبایت خوانده شده بصورت async در جریان خروجی نوشته میشود و تا هنگامی که به آخر فایل نرسیده ایم این روند ادامه پیدا میکند.
while (length > 0 && bytesRead > 0) { bytesRead = video.Read(buffer, 0, Math.Min(length, buffer.Length)); await outputStream.WriteAsync(buffer, 0, bytesRead); length -= bytesRead; }
حال که کلاس VideoStream را در اختیار داریم میتوانیم پروژه را تکمیل کنیم. در پوشه کنترلرها کلاسی بنام VideoControllerبسازید. کد کامل این کلاس را در لیست زیر مشاهده میکنید.
public class VideoController : ApiController { [Route("api/video/{ext}/{fileName}")] public HttpResponseMessage Get(string ext, string fileName) { string videoPath = HostingEnvironment.MapPath(string.Format("~/Videos/{0}.{1}", fileName, ext)); if (File.Exists(videoPath)) { FileInfo fi = new FileInfo(videoPath); var video = new VideoStream(videoPath); var response = Request.CreateResponse(); response.Content = new PushStreamContent((Action<Stream, HttpContent, TransportContext>)video.WriteToStream, new MediaTypeHeaderValue("video/" + ext)); response.Content.Headers.Add("Content-Disposition", "attachment;filename=" + fi.Name.Replace(" ", "")); response.Content.Headers.Add("Content-Length", video.FileLength.ToString()); return response; } else { return Request.CreateResponse(HttpStatusCode.NotFound); } } }
شرح کلاس VideoController
همانطور که میبینید مسیر دستیابی به این کنترلر با استفاده از قابلیت Attribute Routing تعریف شده است.
[Route("api/video/{ext}/{fileName}")]
api/video/mp4/sample
متد Get این کنترلر دو پارامتر با نامهای ext و fileName را میپذیرد که همان فرمت و نام فایل هستند. سپس با استفاده از کلاس HostingEnvironment سعی میکنیم مسیر کامل فایل درخواست شده را بدست آوریم.
string videoPath = HostingEnvironment.MapPath(string.Format("~/Videos/{0}.{1}", fileName, ext));
var video = new VideoStream(videoPath);
var response = Request.CreateResponse(); response.Content = new PushStreamContent((Action<Stream, HttpContent, TransportContext>)video.WriteToStream, new MediaTypeHeaderValue("video/" + ext));
کلاس PushStreamContent در فضای نام System.Net.Http وجود دارد. همانطور که میبینید امضای Action پاس داده شده، با امضای متد WriteToStream در کلاس VideoStream مطابقت دارد.
در آخر دو Header به پاسخ ارسالی اضافه میکنیم تا نوع داده ارسالی و سایز آن را مشخص کنیم.
response.Content.Headers.Add("Content-Disposition", "attachment;filename=" + fileName); response.Content.Headers.Add("Content-Length", video.FileLength.ToString());
if(File.Exists(videoPath)) { // removed for bravity } else { return Request.CreateResponse(HttpStatusCode.NotFound); }
public class HomeController : Controller { // GET: Home public ActionResult Index() { return View(); } }
<div> <div> <video width="480" height="270" controls="controls" preload="auto"> <source src="/api/video/mp4/sample" type="video/mp4" /> Your browser does not support the video tag. </video> </div> </div>
اگر پروژه را اجرا کنید میبینید که ویدیو مورد نظر آماده پخش است. برای اینکه ببینید چطور دادههای ویدیو در قالب پکیجهای 64 کیلو بایتی دریافت میشوند از ابزار مرورگرتان استفاده کنید. مثلا در گوگل کروم F12 را بزنید و به قسمت Network بروید. صفحه را یکبار مجددا بارگذاری کنید تا ارتباطات شبکه مانیتور شود. اگر به المنت sample دقت کنید میبینید که با شروع پخش ویدیو پکیجهای اطلاعات یکی پس از دیگری دریافت میشوند و اطلاعات ریز آن را میتوانید مشاهده کنید.
پروژه نمونه به این مقاله ضمیمه شده است. قابلیت Package Restore فعال شده و برای صرفه جویی در حجم فایل، تمام پکیجها و محتویات پوشه bin حذف شده اند. برای تست بیشتر میتوانید فایل sample.mp4 را با فایلی حجیمتر جایگزین کنید تا نحوه دریافت اطلاعات را با روشی که در بالا بدان اشاره شد مشاهده کنید.
AsyncVideoStreaming.rar