diff --git a/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj b/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj index 990ccce11..5a1c349de 100644 --- a/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj +++ b/src/Orchard.Azure/Orchard.Azure.Web/Orchard.Azure.Web.csproj @@ -249,6 +249,10 @@ {66FCCD76-2761-47E3-8D11-B45D0001DDAA} Orchard.Autoroute + + {14a96b1a-9dc9-44c8-a675-206329e15263} + Orchard.Azure.MediaServices + {cbc7993c-57d8-4a6c-992c-19e849dfe71d} Orchard.Azure diff --git a/src/Orchard.Azure/Orchard.Azure.sln b/src/Orchard.Azure/Orchard.Azure.sln index 12937bc1a..6a0a74a5e 100644 --- a/src/Orchard.Azure/Orchard.Azure.sln +++ b/src/Orchard.Azure/Orchard.Azure.sln @@ -143,6 +143,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.SecureSocketsLayer" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Templates", "..\Orchard.Web\Modules\Orchard.Templates\Orchard.Templates.csproj", "{10AB3CE2-A720-467F-9EC8-EBB4BAC9A1C9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Azure.MediaServices", "..\Orchard.Web\Modules\Orchard.Azure.MediaServices\Orchard.Azure.MediaServices.csproj", "{14A96B1A-9DC9-44C8-A675-206329E15263}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -409,6 +411,10 @@ Global {10AB3CE2-A720-467F-9EC8-EBB4BAC9A1C9}.Debug|Any CPU.Build.0 = Debug|Any CPU {10AB3CE2-A720-467F-9EC8-EBB4BAC9A1C9}.Release|Any CPU.ActiveCfg = Release|Any CPU {10AB3CE2-A720-467F-9EC8-EBB4BAC9A1C9}.Release|Any CPU.Build.0 = Release|Any CPU + {14A96B1A-9DC9-44C8-A675-206329E15263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14A96B1A-9DC9-44C8-A675-206329E15263}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14A96B1A-9DC9-44C8-A675-206329E15263}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14A96B1A-9DC9-44C8-A675-206329E15263}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -468,6 +474,7 @@ Global {7528BF74-25C7-4ABE-883A-443B4EEC4776} = {8E3DE014-9B28-4B32-8AC1-B2BE404E9B2D} {36B82383-D69E-4897-A24A-648BABDF80EC} = {8E3DE014-9B28-4B32-8AC1-B2BE404E9B2D} {10AB3CE2-A720-467F-9EC8-EBB4BAC9A1C9} = {8E3DE014-9B28-4B32-8AC1-B2BE404E9B2D} + {14A96B1A-9DC9-44C8-A675-206329E15263} = {8E3DE014-9B28-4B32-8AC1-B2BE404E9B2D} {33B1BC8D-E292-4972-A363-22056B207156} = {75E7476C-C05B-4C41-8E38-081D3EB55659} {CB70A642-8CEC-4DDE-8C9F-AD08900EC98D} = {84650275-884D-4CBB-9CC0-67553996E211} {9916839C-39FC-4CEB-A5AF-89CA7E87119F} = {F2AB7512-139A-420F-AE3A-9ED22CA52CE1} diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/MSAdaptiveStreamingPlugin.swf b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/MSAdaptiveStreamingPlugin.swf new file mode 100644 index 000000000..551a09e15 Binary files /dev/null and b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/MSAdaptiveStreamingPlugin.swf differ diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/Strobe Media Playback Notice.docx b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/Strobe Media Playback Notice.docx new file mode 100644 index 000000000..9e4ce1f6e Binary files /dev/null and b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/Strobe Media Playback Notice.docx differ diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/StrobeMediaPlayback.swf b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/StrobeMediaPlayback.swf new file mode 100644 index 000000000..487d91c1f Binary files /dev/null and b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/StrobeMediaPlayback.swf differ diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/Web.config b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/Web.config new file mode 100644 index 000000000..817198995 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/Web.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/expressInstall.swf b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/expressInstall.swf new file mode 100644 index 000000000..0fbf8fca9 Binary files /dev/null and b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Content/expressInstall.swf differ diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Controllers/AssetController.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Controllers/AssetController.cs new file mode 100644 index 000000000..803ff9d6a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Controllers/AssetController.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Web.Mvc; +using Orchard.Azure.MediaServices.Helpers; +using Orchard.Azure.MediaServices.Infrastructure.Assets; +using Orchard.Azure.MediaServices.Models.Assets; +using Orchard.Azure.MediaServices.Services.Assets; +using Orchard.Azure.MediaServices.Services.Wams; +using Orchard.Azure.MediaServices.ViewModels.Media; +using Orchard.ContentManagement; +using Orchard.Data; +using Orchard.Localization; +using Orchard.Logging; +using Orchard.Mvc; +using Orchard.Mvc.Html; +using Orchard.Security; +using Orchard.UI.Admin; +using Orchard.UI.Notify; +using System.Net; + +namespace Orchard.Azure.MediaServices.Controllers { + + [Admin] + public class AssetController : Controller, IUpdateModel { + + private readonly ITransactionManager _transactionManager; + private readonly IOrchardServices _services; + private readonly IAssetManager _assetManager; + private readonly INotifier _notifier; + private readonly IAuthorizer _authorizer; + private readonly IWamsClient _wamsClient; + private readonly Lazy> _assetDrivers; + + public AssetController( + ITransactionManager transactionManager, + IOrchardServices services, + IAssetManager assetManager, + IWamsClient wamsClient, + Lazy> assetDrivers) { + + _transactionManager = transactionManager; + _services = services; + _assetManager = assetManager; + _wamsClient = wamsClient; + _assetDrivers = assetDrivers; + _notifier = services.Notifier; + _authorizer = services.Authorizer; + + T = NullLocalizer.Instance; + Logger = NullLogger.Instance; + } + + public Localizer T { get; set; } + public ILogger Logger { get; set; } + + [HttpPost] + public async Task GenerateWamsAsset(string fileName) { + var asset = await _wamsClient.CreateAssetAsync(fileName).ConfigureAwait(continueOnCapturedContext: false); + + return Json(new { + sasLocator = asset.SasLocator, + assetId = asset.AssetId + }); + } + + [HttpDelete] + public async Task DeleteWamsAsset(string id) { + var asset = _wamsClient.GetAssetById(id); + await _wamsClient.DeleteAssetAsync(asset).ConfigureAwait(continueOnCapturedContext: false); + return new EmptyResult(); + } + + public JsonResult State(int id) { + var asset = _assetManager.GetAssetById(id); + return Json(new { + uploadState = new { + status = asset.UploadState.Status.ToString(), + percentComplete = (int?)asset.UploadState.PercentComplete, + }, + published = asset.VideoPart != null && asset.VideoPart.IsPublished() // VideoPart can potentially be null here if the user deleted the media item, and an AJAX request was still issued. + }, JsonRequestBehavior.AllowGet); + } + + public ActionResult Edit(int id) { + return Validate(id, asset => { + var viewModel = BuildAssetViewModel(asset, driver => driver.BuildEditor(asset, _services.New)); + return View(viewModel); + }); + + } + + [HttpPost, ActionName("Edit"), FormValueRequired("button.Save")] + public ActionResult EditSave(int id) { + return Validate(id, asset => { + var viewModel = BuildAssetViewModel(asset, driver => driver.UpdateEditor(asset, this, _services.New)); + + if (TryUpdateModel(viewModel, null, null, new[] { "Asset", "SpecializedSettingsShapes" })) { + asset.Name = viewModel.Name.TrimSafe(); + asset.Description = viewModel.Description.TrimSafe(); + asset.IncludeInPlayer = viewModel.IncludeInPlayer; + asset.MediaQuery = viewModel.MediaQuery.TrimSafe(); + } + + if (!ModelState.IsValid) { + _transactionManager.Cancel(); + return View(viewModel); + } + + _notifier.Information(T("The Asset has been saved.")); + return RedirectToAction("Edit", new { id = id }); + }); + } + + [HttpPost, ActionName("Edit"), FormValueRequired("button.Delete")] + public ActionResult EditDelete(int id) { + return Delete(id); + } + + public ActionResult Delete(int id) { + if (!_authorizer.Authorize(Permissions.ManageCloudMediaContent, T("You are not authorized to manage Windows Azure Media content."))) + return new HttpUnauthorizedResult(); + + Logger.Debug("User requested to delete asset with ID {0}.", id); + + var asset = _assetManager.GetAssetById(id); + if (asset == null) { + Logger.Warning("User requested to delete asset with ID {0} but no such asset record exists.", id); + return HttpNotFound(String.Format("No asset with ID {0} was found.", id)); + } + + var cloudVideoPart = asset.VideoPart; + + if (cloudVideoPart.MezzanineAsset.Record.Id == asset.Record.Id) { + Logger.Warning("User requested to delete asset with ID {0} but it is the mezzanine asset and cannot be deleted.", id); + return new HttpStatusCodeResult(HttpStatusCode.Forbidden, String.Format("Asset with ID {0} is the mezzanine asset and cannot be deleted.", id)); + } + + try { + _assetManager.DeleteAsset(asset); + + Logger.Information("Asset with ID {0} was deleted.", id); + _notifier.Information(T("The asset '{0}' was successfully deleted.", asset.Name)); + } + catch (Exception ex) { + _transactionManager.Cancel(); + + Logger.Error(ex, "Error while deleting asset with ID {0}.", id); + _notifier.Error(T("Ar error occurred while deleting the asset '{0}':\n{1}", asset.Name, ex.Message)); + } + + return Redirect(Url.ItemEditUrl(cloudVideoPart)); + } + + private ActionResult Validate(int id, Func validationSucceeded) { + if (!_authorizer.Authorize(Permissions.ManageCloudMediaContent, T("You are not authorized to manage Windows Azure Media content."))) + return new HttpUnauthorizedResult(); + + var asset = _assetManager.GetAssetById(id); + return asset == null ? new HttpNotFoundResult() : validationSucceeded(asset); + } + + private AssetViewModel BuildAssetViewModel(Asset asset, Func> driverAction) { + var specializedSettings = _assetDrivers.Value.SelectMany(driverAction); + + return new AssetViewModel { + Name = asset.Name, + Description = asset.Description, + IncludeInPlayer = asset.IncludeInPlayer, + MediaQuery = asset.MediaQuery, + Asset = asset, + SpecializedSettings = specializedSettings.ToArray() + }; + } + + bool IUpdateModel.TryUpdateModel(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) { + return TryUpdateModel(model, prefix, includeProperties, excludeProperties); + } + + void IUpdateModel.AddModelError(string key, LocalizedString errorMessage) { + ModelState.AddModelError(key, errorMessage.ToString()); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Controllers/JobController.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Controllers/JobController.cs new file mode 100644 index 000000000..10a360ab7 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Controllers/JobController.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using Orchard.Azure.MediaServices.Helpers; +using Orchard.Azure.MediaServices.Models; +using Orchard.Azure.MediaServices.Models.Jobs; +using Orchard.Azure.MediaServices.Services.Jobs; +using Orchard.Azure.MediaServices.Services.Tasks; +using Orchard.Azure.MediaServices.Services.Wams; +using Orchard.Azure.MediaServices.ViewModels.Jobs; +using Orchard; +using Orchard.ContentManagement; +using Orchard.Data; +using Orchard.Localization; +using Orchard.Logging; +using Orchard.Mvc; +using Orchard.Mvc.Extensions; +using Orchard.Mvc.Html; +using Orchard.Security; +using Orchard.Services; +using Orchard.Themes; +using Orchard.UI.Admin; +using Orchard.UI.Notify; + +namespace Orchard.Azure.MediaServices.Controllers { + + [Admin] + public class JobController : Controller, IUpdateModel { + private readonly ITransactionManager _transactionManager; + private readonly IContentManager _contentManager; + private readonly INotifier _notifier; + private readonly IJobManager _jobManager; + private readonly IEnumerable _taskProviders; + private readonly IWamsClient _wamsClient; + private readonly IClock _clock; + private readonly IAuthorizer _authorizer; + + public JobController( + ITransactionManager transactionManager, + IOrchardServices services, + IJobManager jobManager, + IEnumerable taskProviders, + IWamsClient wamsClient, + IClock clock) { + + _transactionManager = transactionManager; + _contentManager = services.ContentManager; + _notifier = services.Notifier; + _jobManager = jobManager; + _taskProviders = taskProviders; + _wamsClient = wamsClient; + _clock = clock; + _authorizer = services.Authorizer; + + T = NullLocalizer.Instance; + Logger = NullLogger.Instance; + + New = services.New; + } + + public Localizer T { get; set; } + public ILogger Logger { get; set; } + + private dynamic New { get; set; } + + public ActionResult Index() { + if (!_authorizer.Authorize(Permissions.ManageCloudMediaJobs, T("You are not authorized to manage cloud jobs."))) + return new HttpUnauthorizedResult(); + + var jobsShape = GetOpenJobsTableShape(); + return View(jobsShape); + } + + [Themed(false)] + public ActionResult OpenJobsTable() { + return new ShapeResult(this, GetOpenJobsTableShape()); + } + + public ActionResult SelectTask(int id) { + var taskProviders = _taskProviders.OrderBy(x => x.Name).ToArray(); + + // Short-circuit in case there's just one task provider, saving the user from another mouse-click. + if (taskProviders.Length == 1) + return RedirectToAction("Create", new {id = id, task = taskProviders.First().Name}); + + var viewModel = New.ViewModel(CloudVideoPartId: id, TaskProviders: taskProviders); + return View(viewModel); + } + + public ActionResult Create(int id, string task) { + if (!_authorizer.Authorize(Permissions.ManageCloudMediaJobs, T("You are not authorized to manage cloud jobs."))) + return new HttpUnauthorizedResult(); + + if (String.IsNullOrWhiteSpace(task)) + return RedirectToAction("SelectTask", new { id }); + + var cloudVideoPart = _contentManager.Get(id, VersionOptions.Latest); + var taskProvider = _taskProviders.Single(x => x.Name == task); + var taskConfig = taskProvider.Editor(New); + var jobViewModel = new JobViewModel { TaskEditorShape = taskConfig.EditorShape }; + var viewModel = New.ViewModel( + TaskProvider: taskProvider, + CloudVideoPart: cloudVideoPart, + JobViewModel: jobViewModel); + + return View(viewModel); + } + + [HttpPost] + public ActionResult Create(int id, string task, JobViewModel jobViewModel) { + if (!_authorizer.Authorize(Permissions.ManageCloudMediaJobs, T("You are not authorized to manage cloud jobs."))) + return new HttpUnauthorizedResult(); + + Logger.Debug("User requested to create job with task of type {0} on cloud video item with ID {1}.", task, id); + + var cloudVideoPart = _contentManager.Get(id, VersionOptions.Latest); + if (cloudVideoPart == null) { + Logger.Warning("User requested to create job on cloud video item with ID {0} but no such cloud video item exists.", id); + return HttpNotFound(String.Format("No cloud video item with ID {0} was found.", id)); + } + + var taskProvider = _taskProviders.Single(x => x.Name == task); + var inputAsset = cloudVideoPart.Assets.Single(x => x.Record.Id == jobViewModel.SelectedInputAssetId); + var videoName = _contentManager.GetItemMetadata(cloudVideoPart).DisplayText; + var taskConfig = (TaskConfiguration)taskProvider.Editor(New, this); + var taskConnections = taskProvider.GetConnections(taskConfig); + var taskDisplayText = taskProvider.GetDisplayText(taskConfig); + var jobName = !String.IsNullOrWhiteSpace(jobViewModel.Name) ? jobViewModel.Name.TrimSafe() : !String.IsNullOrWhiteSpace(taskDisplayText) ? taskDisplayText : String.Format("{0} ({1})", videoName, taskProvider.Name); + var jobDescription = jobViewModel.Description.TrimSafe(); + + if (ModelState.IsValid) { + try { + var wamsJob = _wamsClient.CreateNewJob(jobName); + var wamsInputAsset = _wamsClient.GetAssetById(inputAsset.WamsAssetId); + var wamsTask = taskProvider.CreateTask(taskConfig, wamsJob.Tasks, new[] { wamsInputAsset }); + wamsJob.Submit(); // Needs to be done here for job and tasks to get their WAMS ID values. + + var job = _jobManager.CreateJobFor(cloudVideoPart, j => { + j.WamsJobId = wamsJob.Id; + j.Name = jobName; + j.Description = jobDescription; + j.Status = JobStatus.Pending; + j.CreatedUtc = _clock.UtcNow; + j.OutputAssetName = jobViewModel.OutputAssetName.TrimSafe(); + j.OutputAssetDescription = jobViewModel.OutputAssetDescription.TrimSafe(); + }); + + _jobManager.CreateTaskFor(job, t => { + t.HarvestAssetType = taskConnections.Outputs.First().AssetType; + t.HarvestAssetName = taskConnections.Outputs.First().AssetName; + t.Settings = taskProvider.Serialize(taskConfig.Settings); + t.Index = 0; + t.TaskProviderName = taskProvider.Name; + t.WamsTaskId = wamsTask.Id; + }); + + Logger.Information("Job was created with task of type {0} on cloud video item with ID {1}.", task, id); + _notifier.Information(T("The job '{0}' was successfully created.", job.Name)); + + return Redirect(Url.ItemEditUrl(cloudVideoPart)); + } + catch (Exception ex) { + _transactionManager.Cancel(); + + Logger.Error(ex, "Error while creating job with task of type {0} on cloud video item with ID {1}.", task, id); + _notifier.Error(T("Ar error occurred while creating the job:\n{1}", ex.Message)); + } + } + + return View(jobViewModel); + } + + [HttpPost] + public ActionResult Archive(int id, string returnUrl = null) { + if (!_authorizer.Authorize(Permissions.ManageCloudMediaJobs, T("You are not authorized to manage cloud jobs."))) + return new HttpUnauthorizedResult(); + + Logger.Debug("User requested to archive job with ID {0}.", id); + + var job = _jobManager.GetJobById(id); + if (job == null) { + Logger.Warning("User requested to archive job with ID {0} but no such job exists.", id); + return HttpNotFound(String.Format("No job with ID {0} was found.", id)); + } + + job.Status = JobStatus.Archived; + + Logger.Information("Job with ID {0} was archived.", id); + _notifier.Information(T("The job '{0}' was successfully archived.", job.Name)); + + return RedirectToReturnUrl(returnUrl, Url.ItemEditUrl(job.CloudVideoPart)); + } + + [HttpPost] + public ActionResult Cancel(int id, string returnUrl = null) { + if (!_authorizer.Authorize(Permissions.ManageCloudMediaJobs, T("You are not authorized to manage cloud jobs."))) + return new HttpUnauthorizedResult(); + + Logger.Debug("User requested to cancel job with ID {0}.", id); + + var job = _jobManager.GetJobById(id); + if (job == null) { + Logger.Warning("User requested to cancel job with ID {0} but no such job exists.", id); + return HttpNotFound(String.Format("No job with ID {0} was found.", id)); + } + + job.Status = JobStatus.Canceling; // Set status to reflect in UI immediately - may be reset by JobProcessor later. + + try { + var wamsJob = _wamsClient.GetJobById(job.WamsJobId); + wamsJob.Cancel(); + + Logger.Information("Job with ID {0} was canceled.", id); + _notifier.Information(T("The job '{0}' was successfully canceled.", job.Name)); + } + catch (Exception ex) { + _transactionManager.Cancel(); + + Logger.Error(ex, "Error while canceling the job with ID {0}.", id); + _notifier.Error(T("An error occurred while canceling the job:\n{0}", ex.Message)); + } + + return RedirectToReturnUrl(returnUrl, Url.ItemEditUrl(job.CloudVideoPart)); + } + + [Themed(false)] + public ActionResult AssetsTable(int id) { + var videoPart = _contentManager.Get(id, VersionOptions.Latest); + return new ShapeResult(this, New.CloudVideo_Edit_Assets(CloudVideoPart: videoPart)); + } + + [Themed(false)] + public ActionResult JobsTable(int id) { + var videoPart = _contentManager.Get(id, VersionOptions.Latest); + return new ShapeResult(this, New.CloudVideo_Edit_Jobs(CloudVideoPart: videoPart)); + } + + private dynamic GetOpenJobsTableShape() { + var jobs = _jobManager.GetOpenJobs().ToArray(); + return New.OpenJobsTable(Jobs: jobs); + } + + private ActionResult RedirectToReturnUrl(string returnUrl, string defaultUrl) { + return !String.IsNullOrEmpty(returnUrl) ? this.RedirectLocal(returnUrl) : Redirect(defaultUrl); + } + + bool IUpdateModel.TryUpdateModel(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) { + return TryUpdateModel(model, prefix, includeProperties, excludeProperties); + } + + void IUpdateModel.AddModelError(string key, LocalizedString errorMessage) { + ModelState.AddModelError(key, errorMessage.ToString()); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Controllers/MediaController.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Controllers/MediaController.cs new file mode 100644 index 000000000..90b1d3172 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Controllers/MediaController.cs @@ -0,0 +1,171 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Web.Mvc; +using Orchard.Azure.MediaServices.Models; +using Orchard.Azure.MediaServices.Models.Assets; +using Orchard.Azure.MediaServices.Services.Assets; +using Orchard; +using Orchard.ContentManagement; +using Orchard.Data; +using Orchard.Localization; +using Orchard.Logging; +using Orchard.MediaLibrary.Models; +using Orchard.Mvc; +using Orchard.Security; +using Orchard.UI.Admin; +using Orchard.UI.Notify; + +namespace Orchard.Azure.MediaServices.Controllers { + + [Admin] + public class MediaController : Controller, IUpdateModel { + + private readonly IContentManager _contentManager; + private readonly IAssetManager _assetManager; + private readonly INotifier _notifier; + private readonly ITransactionManager _transactionManager; + private readonly IAuthorizer _authorizer; + + public MediaController( + IOrchardServices services, + IAssetManager assetManager, + ITransactionManager transactionManager) { + + _contentManager = services.ContentManager; + _assetManager = assetManager; + _transactionManager = transactionManager; + _authorizer = services.Authorizer; + _notifier = services.Notifier; + + T = NullLocalizer.Instance; + Logger = NullLogger.Instance; + + New = services.New; + } + + public Localizer T { get; set; } + public ILogger Logger { get; set; } + + private dynamic New { get; set; } + + public ActionResult Import(string folderPath) { + var part = _contentManager.New("CloudVideo"); + return EditImplementation(part, folderPath); + } + + [HttpPost, ActionName("Import")] + [FormValueRequired("submit.Save")] + public ActionResult ImportSave(string folderPath) { + var part = _contentManager.Create("CloudVideo", VersionOptions.Draft); + return UpdateImplementation(part, folderPath, T("The cloud video item was successfully created."), publish: false); + } + + [HttpPost, ActionName("Import")] + [FormValueRequired("submit.Publish")] + public ActionResult ImportPublish(string folderPath) { + var part = _contentManager.Create("CloudVideo", VersionOptions.Draft); + return UpdateImplementation(part, folderPath, T("The cloud video item was successfully created."), publish: true); + } + + public ActionResult Edit(int id) { + var part = _contentManager.Get(id, VersionOptions.Latest); + return EditImplementation(part, null); + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("submit.Save")] + public ActionResult EditSave(int id) { + var part = _contentManager.Get(id, VersionOptions.Latest); + return UpdateImplementation(part, null, T("The cloud video item was successfully updated."), publish: false); + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("submit.Publish")] + public ActionResult EditPublish(int id) { + var part = _contentManager.Get(id, VersionOptions.Latest); + return UpdateImplementation(part, null, T("The cloud video item was successfully updated."), publish: true); + } + + [HttpPost] + public ActionResult Upload() { + if (!_authorizer.Authorize(Permissions.ManageCloudMediaContent, T("You are not authorized to manage Windows Azure Media content."))) + return new HttpUnauthorizedResult(); + + if (HttpContext.Request.Files.Count < 1) + return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "At least one file must be provided in the upload request."); + + var postedFile = HttpContext.Request.Files[0]; + Logger.Debug("User requested asynchronous upload of file with name '{0}' and size {1} bytes to temporary storage.", postedFile.FileName, postedFile.ContentLength); + + var fileName = _assetManager.SaveTemporaryFile(postedFile); + Logger.Information("File with name '{0}' and size {1} bytes was uploaded to temporary storage.", postedFile.FileName, postedFile.ContentLength); + return Json(new { + originalFileName = Path.GetFileName(postedFile.FileName), + temporaryFileName = fileName, + fileSize = postedFile.ContentLength + }); + } + + private ActionResult EditImplementation(IContent content, string folderPath) { + if (!_authorizer.Authorize(Permissions.ManageCloudMediaContent, T("You are not authorized to manage Windows Azure Media content."))) + return new HttpUnauthorizedResult(); + + var editorShape = _contentManager.BuildEditor(content); + var model = New.ViewModel(Editor: editorShape, FolderPath: folderPath); + return View(model); + } + + private ActionResult UpdateImplementation(CloudVideoPart part, string folderPath, LocalizedString notification, bool publish) { + if (!_authorizer.Authorize(Permissions.ManageCloudMediaContent, T("You are not authorized to manage Windows Azure Media content."))) + return new HttpUnauthorizedResult(); + + Logger.Debug("User requested to save cloud video item with ID {0}.", part.Id); + + var editorShape = _contentManager.UpdateEditor(part, this); + + if (!ModelState.IsValid) { + _transactionManager.Cancel(); + + var viewModel = New.ViewModel(FolderPath: folderPath, Editor: editorShape); + return View(viewModel); + } + + var mediaPart = part.As(); + mediaPart.LogicalType = "CloudVideo"; + + if (String.IsNullOrWhiteSpace(mediaPart.MimeType)) { + var mezzanineAsset = _assetManager.LoadAssetsFor(part).Single(); + mediaPart.MimeType = mezzanineAsset.MimeType; + } + + if (!String.IsNullOrWhiteSpace(folderPath)) + mediaPart.FolderPath = folderPath; + + try { + if (publish) + _contentManager.Publish(mediaPart.ContentItem); + + Logger.Information("Cloud video item with ID {0} was saved.", part.Id); + _notifier.Information(notification); + } + catch (Exception ex) { + _transactionManager.Cancel(); + + Logger.Error(ex, "Error while saving cloud video item with ID {0}.", part.Id); + _notifier.Error(T("Ar error occurred while saving the cloud video item:\n{1}", ex.Message)); + } + + return RedirectToAction("Edit", new { id = part.Id }); + } + + bool IUpdateModel.TryUpdateModel(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) { + return TryUpdateModel(model, prefix, includeProperties, excludeProperties); + } + + void IUpdateModel.AddModelError(string key, LocalizedString errorMessage) { + ModelState.AddModelError(key, errorMessage.ToString()); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Controllers/SettingsController.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Controllers/SettingsController.cs new file mode 100644 index 000000000..5c9f46dd1 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Controllers/SettingsController.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Web.Mvc; +using Orchard.Azure.MediaServices.Helpers; +using Orchard.Azure.MediaServices.Models; +using Orchard.Azure.MediaServices.Services.Wams; +using Orchard.Azure.MediaServices.ViewModels.Settings; +using Microsoft.WindowsAzure.MediaServices.Client; +using Microsoft.WindowsAzure.Storage; +using Microsoft.WindowsAzure.Storage.Auth; +using Orchard; +using Orchard.ContentManagement; +using Orchard.Localization; +using Orchard.Logging; +using Orchard.UI.Admin; +using Orchard.UI.Notify; + +namespace Orchard.Azure.MediaServices.Controllers { + + [Admin] + public class SettingsController : Controller { + private readonly IOrchardServices _services; + private readonly IWamsClient _wamsClient; + + public SettingsController(IOrchardServices services, IWamsClient wamsClient) { + _services = services; + _wamsClient = wamsClient; + + T = NullLocalizer.Instance; + Logger = NullLogger.Instance; + } + + public Localizer T { get; set; } + public ILogger Logger { get; set; } + + public ActionResult Index() { + if (!_services.Authorizer.Authorize(Permissions.ManageCloudMediaSettings, T("You are not authorized to manage Windows Azure Media settings."))) + return new HttpUnauthorizedResult(); + + var settings = _services.WorkContext.CurrentSite.As(); + var viewModel = new SettingsViewModel { + General = new GeneralSettingsViewModel { + WamsAccountName = settings.WamsAccountName, + WamsAccountKey = settings.WamsAccountKey, + StorageAccountKey = settings.StorageAccountKey, + EnableDynamicPackaging = settings.EnableDynamicPackaging, + AccessPolicyDuration = settings.AccessPolicyDuration, + AllowedVideoFilenameExtensions = String.Join(";", settings.AllowedVideoFilenameExtensions) + }, + EncodingSettings = new EncodingSettingsViewModel { + WamsEncodingPresets = settings.WamsEncodingPresets, + DefaultWamsEncodingPresetIndex = settings.DefaultWamsEncodingPresetIndex + }, + EncryptionSettings = new EncryptionSettingsViewModel { + KeySeedValue = settings.EncryptionKeySeedValue, + LicenseAcquisitionUrl = settings.EncryptionLicenseAcquisitionUrl, + }, + SubtitleLanguages = new SubtitleLanguagesSettingsViewModel { + Languages = settings.SubtitleLanguages + } + }; + + return View(viewModel); + } + + [HttpPost] + public ActionResult Save(SettingsViewModel viewModel) { + if (!_services.Authorizer.Authorize(Permissions.ManageCloudMediaSettings, T("You are not authorized to manage Windows Azure Media settings."))) + return new HttpUnauthorizedResult(); + + if (!ModelState.IsValid) { + return View("Index", viewModel); + } + + var presetPattern = new Regex(@"^[\w\s]*$"); + foreach (var preset in viewModel.EncodingSettings.WamsEncodingPresets) { + if (!presetPattern.IsMatch(preset)) { + _services.Notifier.Error(T("The encoding preset '{0}' is invalid. Encoding presets can only contain letters, numbers and spaces.", preset)); + return View("Index", viewModel); + } + } + + Logger.Debug("User requested to save module settings."); + + var settings = _services.WorkContext.CurrentSite.As(); + + if (!String.IsNullOrWhiteSpace(viewModel.General.WamsAccountName) && !String.IsNullOrEmpty(viewModel.General.WamsAccountKey)) { + // Test WAMS credentials if they were changed. + if (viewModel.General.WamsAccountName != settings.WamsAccountName || viewModel.General.WamsAccountKey != settings.WamsAccountKey || viewModel.General.StorageAccountKey != settings.StorageAccountKey) { + if (!TestCredentialsInternal(viewModel.General.WamsAccountName, viewModel.General.WamsAccountKey, viewModel.General.StorageAccountKey)) { + _services.Notifier.Error(T("The account credentials verification failed. The settings were not saved.")); + return View("Index", viewModel); + } + else { + _services.Notifier.Information(T("The new account credentials were successfully verified.")); + } + } + } + + var previousStorageAccountKey = settings.StorageAccountKey; + + settings.WamsAccountName = viewModel.General.WamsAccountName.TrimSafe(); + settings.WamsAccountKey = viewModel.General.WamsAccountKey.TrimSafe(); + settings.StorageAccountKey = viewModel.General.StorageAccountKey.TrimSafe(); + settings.EnableDynamicPackaging = viewModel.General.EnableDynamicPackaging; + settings.AccessPolicyDuration = viewModel.General.AccessPolicyDuration; + settings.AllowedVideoFilenameExtensions = viewModel.General.AllowedVideoFilenameExtensions.Split(';'); + settings.WamsEncodingPresets = viewModel.EncodingSettings.WamsEncodingPresets; + settings.DefaultWamsEncodingPresetIndex = viewModel.EncodingSettings.DefaultWamsEncodingPresetIndex; + settings.SubtitleLanguages = viewModel.SubtitleLanguages != null ? viewModel.SubtitleLanguages.Languages : null; + + // TODO: Encryption is disabled for now. Uncomment when we need it again. + //settings.EncryptionKeySeedValue = viewModel.EncryptionSettings.KeySeedValue.TrimSafe(); + //settings.EncryptionLicenseAcquisitionUrl = viewModel.EncryptionSettings.LicenseAcquisitionUrl.TrimSafe(); + + // Configure storage account for CORS if account key was specified and changed. + if (settings.IsValid() && !String.IsNullOrWhiteSpace(settings.StorageAccountKey)) { + if (settings.StorageAccountKey != previousStorageAccountKey) { + try { + Logger.Debug("Ensuring CORS support for the configured base URL and the current request URL."); + var originsToAdd = new List(); + var baseUrlOrigin = new Uri(_services.WorkContext.CurrentSite.BaseUrl).GetLeftPart(UriPartial.Authority); + originsToAdd.Add(baseUrlOrigin); + + var currentUrlOrigin = _services.WorkContext.HttpContext.Request.Url.GetLeftPart(UriPartial.Authority); + if (!originsToAdd.Contains(currentUrlOrigin)) + originsToAdd.Add(currentUrlOrigin); + + var addedOrigins = _wamsClient.EnsureCorsIsEnabledAsync(originsToAdd.ToArray()).Result; + + if (addedOrigins.Any()) { + Logger.Information("CORS rules were added to the configured storage account for the following URLs: {0}.", String.Join("; ", addedOrigins)); + _services.Notifier.Information(T("CORS rules have been configured on your storage account for the following URLs: {0}.", String.Join("; ", addedOrigins))); + } + } + catch (Exception ex) { + Logger.Error(ex, "Error while ensuring CORS support."); + _services.Notifier.Warning(T("Failed to check or configure CORS support on your storage account.")); + } + } + } + + Logger.Information("Module settings were saved."); + _services.Notifier.Information(T("The settings were saved successfully.")); + + return RedirectToAction("Index"); + } + + [HttpPost] + public ActionResult TestCredentials(SettingsViewModel viewModel) { + if (!_services.Authorizer.Authorize(Permissions.ManageCloudMediaSettings, T("You are not authorized to manage Windows Azure Media settings."))) + return new HttpUnauthorizedResult(); + + Logger.Debug("User requested to verify WAMS account credentials."); + + if (TestCredentialsInternal(viewModel.General.WamsAccountName, viewModel.General.WamsAccountKey, viewModel.General.StorageAccountKey)) { + _services.Notifier.Information(T("The account credentials were successfully verified.")); + } + else { + _services.Notifier.Error(T("The account credentials verification failed.")); + } + + return View("Index", viewModel); + } + + private bool TestCredentialsInternal(string wamsAccountName, string wamsAccountKey, string storageAccountKey) { + try { + // This will trigger an authentication call to WAMS. + var ctx = new CloudMediaContext(wamsAccountName, wamsAccountKey); + + if (!String.IsNullOrWhiteSpace(storageAccountKey)) { + // This will trigger an authentication call to Windows Azure Storage. + var storageAccount = new CloudStorageAccount(new StorageCredentials(ctx.DefaultStorageAccount.Name, storageAccountKey), false); + storageAccount.CreateCloudBlobClient().GetServiceProperties(); + Logger.Information("Storage account credentials were verified."); + } + + Logger.Information("WAMS account credentials were verified."); + return true; + } + catch (Exception ex) { + Logger.Error(ex, "Error while verifying WAMS and storage account credentials."); + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Drivers/CloudVideoPartDriver.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Drivers/CloudVideoPartDriver.cs new file mode 100644 index 000000000..b1b7dd83f --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Drivers/CloudVideoPartDriver.cs @@ -0,0 +1,221 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Orchard.Azure.MediaServices.Models; +using Orchard.Azure.MediaServices.Models.Assets; +using Orchard.Azure.MediaServices.Services.Assets; +using Orchard.Azure.MediaServices.Services.Jobs; +using Orchard.Azure.MediaServices.Services.Wams; +using Orchard.Azure.MediaServices.ViewModels.Media; +using Orchard; +using Orchard.ContentManagement; +using Orchard.ContentManagement.Drivers; +using Orchard.Core.Contents.Settings; +using Orchard.Localization; +using Orchard.Mvc; +using Orchard.UI.Notify; + +namespace Orchard.Azure.MediaServices.Drivers { + public class CloudVideoPartDriver : ContentPartDriver { + + private readonly IOrchardServices _services; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IAssetManager _assetManager; + private readonly IJobManager _jobManager; + private readonly IWamsClient _wamsClient; + + public CloudVideoPartDriver( + IOrchardServices services, + IHttpContextAccessor httpContextAccessor, + IAssetManager assetManager, + IJobManager jobManager, + IWamsClient wamsClient) { + + _services = services; + _httpContextAccessor = httpContextAccessor; + _assetManager = assetManager; + _jobManager = jobManager; + _wamsClient = wamsClient; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + protected override DriverResult Display(CloudVideoPart part, string displayType, dynamic shapeHelper) { + return Combined( + ContentShape("Parts_CloudVideo_Metadata", () => shapeHelper.Parts_CloudVideo_Metadata(ActiveJobCount: _jobManager.GetActiveJobs().Count(job => job.Record.CloudVideoPartId == part.Id))), + ContentShape("Parts_CloudVideo_SummaryAdmin", () => shapeHelper.Parts_CloudVideo_SummaryAdmin()), + ContentShape("Parts_CloudVideo_Summary", () => shapeHelper.Parts_CloudVideo_Summary()), + ContentShape("Parts_CloudVideo_Raw", () => shapeHelper.Parts_CloudVideo_Raw()), + ContentShape("Parts_CloudVideo", () => shapeHelper.Parts_CloudVideo())); + } + + protected override DriverResult Editor(CloudVideoPart part, dynamic shapeHelper) { + return Editor(part, null, shapeHelper); + } + + protected override DriverResult Editor(CloudVideoPart part, IUpdateModel updater, dynamic shapeHelper) { + var results = new List(); + results.Add(ContentShape("Parts_CloudVideo_Edit", () => { + var settings = _services.WorkContext.CurrentSite.As(); + var httpContext = _httpContextAccessor.Current(); + + var occupiedSubtitleLanguagesQuery = + from asset in part.Assets + where asset is SubtitleAsset + select ((SubtitleAsset)asset).Language; + var availableSubtitleLanguagesQuery = + from language in settings.SubtitleLanguages + where !occupiedSubtitleLanguagesQuery.Contains(language) + select language; + + var viewModel = new CloudVideoPartViewModel(availableSubtitleLanguagesQuery.ToArray()) { + Id = part.Id, + Part = part, + AllowedVideoFilenameExtensions = settings.AllowedVideoFilenameExtensions, + TemporaryVideoFile = new TemporaryFileViewModel { + OriginalFileName = part.MezzanineAsset != null ? part.MezzanineAsset.OriginalFileName : "", + FileSize = 0, + TemporaryFileName = "" + }, + AddedSubtitleLanguage = settings.SubtitleLanguages.FirstOrDefault(), + WamsVideo = new WamsAssetViewModel(), + WamsThumbnail = new WamsAssetViewModel(), + WamsSubtitle = new WamsAssetViewModel() + }; + + if (updater != null) { + + if (updater.TryUpdateModel(viewModel, Prefix, null, null) && AVideoWasUploaded(part, updater, viewModel)) { + + ProcessCreatedWamsAssets(part, viewModel); + ProcessUploadedFiles(part, viewModel); + + var unpublish = httpContext.Request.Form["submit.Save"] == "submit.Unpublish"; + if (unpublish) { + _services.ContentManager.Unpublish(part.ContentItem); + _services.Notifier.Information(T("Your {0} has been unpublished.", part.ContentItem.TypeDefinition.DisplayName)); + } + + if (part.IsPublished()) + _assetManager.PublishAssetsFor(part); + } + } + + return shapeHelper.EditorTemplate(TemplateName: "Parts/CloudVideo", Model: viewModel, Prefix: Prefix); + })); + + if (part.TypeDefinition.Settings.GetModel().Draftable) { + if (part.IsPublished()) { + results.Add(ContentShape("CloudVideo_Edit_UnpublishButton", actions => actions)); + } + } + + return Combined(results.ToArray()); + } + + private bool AVideoWasUploaded(CloudVideoPart part, IUpdateModel updater, CloudVideoPartViewModel viewModel) { + var isValid = viewModel.WamsVideo.WamsAssetId != null || viewModel.TemporaryVideoFile.FileSize > 0 || part.MezzanineAsset != null; + + if (!isValid) + updater.AddModelError(Prefix + ".WamsVideo.WamsAssetId", T("You need to upload a video.")); + + return isValid; + } + + private void ProcessCreatedWamsAssets(CloudVideoPart part, CloudVideoPartViewModel viewModel) { + if (viewModel.WamsVideo.AssetId == null && !String.IsNullOrWhiteSpace(viewModel.WamsVideo.WamsAssetId)) { + var asset = _assetManager.CreateAssetFor(part, a => { + a.Name = "Mezzanine"; + a.IncludeInPlayer = false; + a.OriginalFileName = viewModel.WamsVideo.FileName; + a.WamsAssetId = viewModel.WamsVideo.WamsAssetId; + a.UploadState.Status = AssetUploadStatus.Uploaded; + CreatePrivateLocatorFor(a); + }); + viewModel.WamsVideo.AssetId = asset.Record.Id; + } + + if (viewModel.WamsThumbnail.AssetId == null && !String.IsNullOrWhiteSpace(viewModel.WamsThumbnail.WamsAssetId)) { + var asset = _assetManager.CreateAssetFor(part, a => { + a.Name = viewModel.WamsThumbnail.FileName; + a.IncludeInPlayer = true; + a.OriginalFileName = viewModel.WamsThumbnail.FileName; + a.WamsAssetId = viewModel.WamsThumbnail.WamsAssetId; + a.UploadState.Status = AssetUploadStatus.Uploaded; + CreatePrivateLocatorFor(a); + }); + viewModel.WamsThumbnail.AssetId = asset.Record.Id; + } + + if (viewModel.WamsSubtitle.AssetId == null && !String.IsNullOrWhiteSpace(viewModel.WamsSubtitle.WamsAssetId)) { + var asset = _assetManager.CreateAssetFor(part, a => { + a.Name = viewModel.AddedSubtitleLanguage; + a.IncludeInPlayer = true; + a.OriginalFileName = viewModel.WamsSubtitle.FileName; + a.Language = viewModel.AddedSubtitleLanguage; + a.WamsAssetId = viewModel.WamsSubtitle.WamsAssetId; + a.UploadState.Status = AssetUploadStatus.Uploaded; + CreatePrivateLocatorFor(a); + }); + viewModel.WamsThumbnail.AssetId = asset.Record.Id; + } + } + + private void ProcessUploadedFiles(CloudVideoPart part, CloudVideoPartViewModel viewModel) { + var httpContext = _httpContextAccessor.Current(); + var files = httpContext.Request.Files; + var postedThumbnailFile = files["ThumbnailFile.Proxied"]; + var postedSubtitleFile = files["SubtitleFile.Proxied"]; + + if (viewModel.TemporaryVideoFile.FileSize > 0) { + _assetManager.CreateAssetFor(part, a => { + a.Name = "Mezzanine"; + a.IncludeInPlayer = false; + a.OriginalFileName = Path.GetFileName(viewModel.TemporaryVideoFile.OriginalFileName); + a.LocalTempFileName = viewModel.TemporaryVideoFile.TemporaryFileName; + a.LocalTempFileSize = viewModel.TemporaryVideoFile.FileSize; + }); + } + if (postedThumbnailFile != null && postedThumbnailFile.ContentLength > 0) { + var thumbnailTempFilePath = _assetManager.SaveTemporaryFile(postedThumbnailFile); + _assetManager.CreateAssetFor(part, a => { + a.Name = Path.GetFileName(postedThumbnailFile.FileName); + a.IncludeInPlayer = true; + a.OriginalFileName = Path.GetFileName(postedThumbnailFile.FileName); + a.LocalTempFileName = thumbnailTempFilePath; + a.LocalTempFileSize = postedThumbnailFile.ContentLength; + }); + } + if (postedSubtitleFile != null && postedSubtitleFile.ContentLength > 0) { + var subtitleTempFilePath = _assetManager.SaveTemporaryFile(postedSubtitleFile); + _assetManager.CreateAssetFor(part, a => { + a.Name = Path.GetFileName(postedSubtitleFile.FileName); + a.IncludeInPlayer = true; + a.OriginalFileName = Path.GetFileName(postedSubtitleFile.FileName); + a.LocalTempFileName = subtitleTempFilePath; + a.LocalTempFileSize = postedSubtitleFile.ContentLength; + a.Language = viewModel.AddedSubtitleLanguage; + }); + } + } + + private void DeleteExistingThumbnails(CloudVideoPart part) { + var thumbnailAssets = part.Assets.Where(x => x is ThumbnailAsset); + + foreach (var asset in thumbnailAssets) { + _assetManager.DeleteAsset(asset); + } + } + + public void CreatePrivateLocatorFor(Asset asset) { + var wamsAsset = _wamsClient.GetAssetById(asset.WamsAssetId); + var wamsLocators = _wamsClient.CreateLocatorsAsync(wamsAsset, WamsLocatorCategory.Private).Result; + + asset.WamsPrivateLocatorId = wamsLocators.SasLocator.Id; + asset.WamsPrivateLocatorUrl = wamsLocators.SasLocator.Url; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Drivers/ThumbnailAssetDriver.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Drivers/ThumbnailAssetDriver.cs new file mode 100644 index 000000000..77620ee0b --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Drivers/ThumbnailAssetDriver.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Orchard.Azure.MediaServices.Infrastructure.Assets; +using Orchard.Azure.MediaServices.Models.Assets; +using Orchard.ContentManagement; + +namespace Orchard.Azure.MediaServices.Drivers { + public class ThumbnailAssetDriver : AssetDriver { + protected override IEnumerable Editor(ThumbnailAsset asset, dynamic shapeFactory) { + return Editor(asset, null, shapeFactory); + } + + protected override IEnumerable Editor(ThumbnailAsset asset, IUpdateModel updater, dynamic shapeFactory) { + yield return new AssetDriverResult { + TabTitle = T("Thumbnail"), + EditorShape = shapeFactory.EditorTemplate(Model: asset, TemplateName: "Assets/Thumbnail.Preview", Prefix: Prefix) + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Drivers/VideoAssetDriver.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Drivers/VideoAssetDriver.cs new file mode 100644 index 000000000..fa0f18e1d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Drivers/VideoAssetDriver.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Orchard.Azure.MediaServices.Infrastructure.Assets; +using Orchard.Azure.MediaServices.Models.Assets; +using Orchard.ContentManagement; + +namespace Orchard.Azure.MediaServices.Drivers { + public class VideoAssetDriver : AssetDriver { + protected override IEnumerable Editor(VideoAsset asset, dynamic shapeFactory) { + return Editor(asset, null, shapeFactory); + } + + protected override IEnumerable Editor(VideoAsset asset, IUpdateModel updater, dynamic shapeFactory) { + yield return new AssetDriverResult { + TabTitle = T("Files"), + EditorShape = shapeFactory.EditorTemplate(Model: asset, TemplateName: "Assets/Video.Files", Prefix: Prefix) + }; + yield return new AssetDriverResult { + TabTitle = T("Preview"), + EditorShape = shapeFactory.EditorTemplate(Model: asset, TemplateName: "Assets/Video.Preview", Prefix: Prefix) + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Events/FeatureEventHandler.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Events/FeatureEventHandler.cs new file mode 100644 index 000000000..71fac50d4 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Events/FeatureEventHandler.cs @@ -0,0 +1,115 @@ +using Orchard.Azure.MediaServices.Models; +using Orchard; +using Orchard.ContentManagement; +using Orchard.Environment; +using Orchard.Environment.Extensions.Models; + +namespace Orchard.Azure.MediaServices.Events { + public class FeatureEventHandler : IFeatureEventHandler { + + private readonly IOrchardServices _orchardServices; + + public FeatureEventHandler(IOrchardServices orchardServices) { + _orchardServices = orchardServices; + } + + public void Installing(Feature feature) { + + } + + public void Installed(Feature feature) { + if (feature.Descriptor.Id != "Orchard.Azure.MediaServices") + return; + + var settings = _orchardServices.WorkContext.CurrentSite.As(); + settings.AllowedVideoFilenameExtensions = "asf;avi;m2ts;m2v;mp4;mpeg;mpg;mts;ts;wmv;3gp;3g2;3gp2;mod;dv;vob;ismv;m4a".Split(';'); + settings.WamsEncodingPresets = new[] { + "VC1 Broadband 1080p", + "VC1 Broadband 720p", + "VC1 Broadband SD 16x9", + "VC1 Broadband SD 4x3", + "VC1 Smooth Streaming 1080p", + "VC1 Smooth Streaming 720p", + "VC1 Smooth Streaming SD 16x9", + "VC1 Smooth Streaming SD 4x3", + "VC1 Smooth Streaming 1080p Xbox Live ADK", + "VC1 Smooth Streaming 720p Xbox Live ADK", + "H264 Broadband 1080p", + "H264 Broadband 720p", + "H264 Broadband SD 16x9", + "H264 Broadband SD 4x3", + "H264 Smooth Streaming 1080p", + "H264 Smooth Streaming 720p", + "H264 Smooth Streaming 720p for 3G or 4G", + "H264 Smooth Streaming SD 16x9", + "H264 Smooth Streaming SD 4x3", + "H264 Adaptive Bitrate MP4 Set 1080p", + "H264 Adaptive Bitrate MP4 Set 720p", + "H264 Adaptive Bitrate MP4 Set SD 16x9", + "H264 Adaptive Bitrate MP4 Set SD 4x3", + "H264 Adaptive Bitrate MP4 Set 1080p for iOS Cellular Only", + "H264 Adaptive Bitrate MP4 Set 720p for iOS Cellular Only", + "H264 Adaptive Bitrate MP4 Set SD 16x9 for iOS Cellular Only", + "H264 Adaptive Bitrate MP4 Set SD 4x3 for iOS Cellular Only", + "H264 Smooth Streaming 720p Xbox Live ADK", + "H264 Smooth Streaming Windows Phone 7 Series" + }; + settings.SubtitleLanguages = new[] { + "da-DK", + "nl-BE", + "nl-NL", + "en-AU", + "en-CA", + "en-IE", + "en-NZ", + "en-GB", + "en-US", + "fr-BE", + "fr-CA", + "fr-FR", + "fr-CH", + "de-AT", + "de-DE", + "de-CH", + "ga-IE", + "it-IT", + "it-CH", + "nb-NO", + "nn-NO", + "fa-IR", + "pl-PL", + "pt-BR", + "pt-PT", + "ru-RU", + "es-CO", + "es-MX", + "es-ES", + "sv-SE" + }; + } + + public void Enabling(Feature feature) { + + } + + public void Enabled(Feature feature) { + + } + + public void Disabling(Feature feature) { + + } + + public void Disabled(Feature feature) { + + } + + public void Uninstalling(Feature feature) { + + } + + public void Uninstalled(Feature feature) { + + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Handlers/CloudMediaSettingsPartHandler.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Handlers/CloudMediaSettingsPartHandler.cs new file mode 100644 index 000000000..d8346fdb6 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Handlers/CloudMediaSettingsPartHandler.cs @@ -0,0 +1,10 @@ +using Orchard.Azure.MediaServices.Models; +using Orchard.ContentManagement.Handlers; + +namespace Orchard.Azure.MediaServices.Handlers { + public class CloudMediaSettingsPartHandler : ContentHandler { + public CloudMediaSettingsPartHandler() { + Filters.Add(new ActivatingFilter("Site")); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Handlers/CloudVideoPartHandler.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Handlers/CloudVideoPartHandler.cs new file mode 100644 index 000000000..cc0ab3530 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Handlers/CloudVideoPartHandler.cs @@ -0,0 +1,57 @@ +using Orchard.Azure.MediaServices.Models; +using Orchard.Azure.MediaServices.Models.Assets; +using Orchard.Azure.MediaServices.Services.Assets; +using Orchard.Azure.MediaServices.Services.Jobs; +using Orchard.ContentManagement.Handlers; +using Orchard.Localization; +using Orchard.UI.Notify; + +namespace Orchard.Azure.MediaServices.Handlers { + public class CloudVideoPartHandler : ContentHandler { + private readonly IAssetManager _assetManager; + private readonly IJobManager _jobManager; + private readonly INotifier _notifier; + + public CloudVideoPartHandler( + IAssetManager assetManager, + IJobManager jobManager, + INotifier notifier) { + + _assetManager = assetManager; + _jobManager = jobManager; + _notifier = notifier; + T = NullLocalizer.Instance; + OnActivated(SetupFields); + OnPublishing(DeferOrPublishAssets); + OnUnpublished(CancelAndUnpublishAssets); + OnRemoved(RemoveAssets); + } + + public Localizer T { get; set; } + + private void SetupFields(ActivatedContentContext context, CloudVideoPart part) { + part._assetManager = _assetManager; + part._jobManager = _jobManager; + } + + private void DeferOrPublishAssets(PublishContentContext context, CloudVideoPart part) { + if (part.MezzanineAsset != null && part.MezzanineAsset.UploadState.Status != AssetUploadStatus.Uploaded) { + part.PublishOnUpload = true; + _notifier.Warning(T("The cloud video item was saved, but will not be published until the primary video asset has finished uploading to Windows Azure Media Services.")); + context.Cancel = true; + } + else + _assetManager.PublishAssetsFor(part); + } + + private void CancelAndUnpublishAssets(PublishContentContext context, CloudVideoPart part) { + part.PublishOnUpload = false; + _assetManager.UnpublishAssetsFor(part); + } + + private void RemoveAssets(RemoveContentContext context, CloudVideoPart part) { + _assetManager.DeleteAssetsFor(part); + _jobManager.CloseJobsFor(part); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/AssetExtensions.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/AssetExtensions.cs new file mode 100644 index 000000000..2aaf54720 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/AssetExtensions.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Linq; +using Orchard.Azure.MediaServices.Models.Assets; + +namespace Orchard.Azure.MediaServices.Helpers { + public static class AssetExtensions { + public static IEnumerable Filter(this IEnumerable assets) where T:Asset { + return assets.Where(x => x is T).Cast(); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/BitExtensions.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/BitExtensions.cs new file mode 100644 index 000000000..1edbfdf8f --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/BitExtensions.cs @@ -0,0 +1,7 @@ +namespace Orchard.Azure.MediaServices.Helpers { + public static class BitExtensions { + public static int ToKiloBits(this int bits) { + return bits/1000; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/BooleanExtensions.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/BooleanExtensions.cs new file mode 100644 index 000000000..a34456266 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/BooleanExtensions.cs @@ -0,0 +1,7 @@ +namespace Orchard.Azure.MediaServices.Helpers { + public static class BooleanExtensions { + public static string ToYesNo(this bool value) { + return value ? "Yes" : "No"; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/EnumExtensions.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/EnumExtensions.cs new file mode 100644 index 000000000..3ca875df1 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/EnumExtensions.cs @@ -0,0 +1,13 @@ +using System.Linq; + +namespace Orchard.Azure.MediaServices.Helpers { + public static class EnumExtensions { + public static bool IsAny(this T value, params T[] values) where T:struct { + return values.Any(x => value.Equals(x)); + } + + public static bool IsNotAny(this T value, params T[] values) where T:struct { + return values.All(x => !value.Equals(x)); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/FileSizeFormatProvider.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/FileSizeFormatProvider.cs new file mode 100644 index 000000000..42b06b790 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/FileSizeFormatProvider.cs @@ -0,0 +1,64 @@ +using System; + +namespace Orchard.Azure.MediaServices.Helpers { + // Credits: http://blog.flimflan.com/FileSizeFormatProvider.html. + public class FileSizeFormatProvider : IFormatProvider, ICustomFormatter { + public object GetFormat(Type formatType) { + return formatType == typeof(ICustomFormatter) ? this : null; + } + + private const string FileSizeFormat = "fs"; + private const Decimal OneKiloByte = 1024M; + private const Decimal OneMegaByte = OneKiloByte * 1024M; + private const Decimal OneGigaByte = OneMegaByte * 1024M; + + public string Format(string format, object arg, IFormatProvider formatProvider) { + if (format == null || !format.StartsWith(FileSizeFormat)) { + return DefaultFormat(format, arg, formatProvider); + } + + if (arg is string) { + return DefaultFormat(format, arg, formatProvider); + } + + Decimal size; + + try { + size = Convert.ToDecimal(arg); + } + catch (InvalidCastException) { + return DefaultFormat(format, arg, formatProvider); + } + + string suffix; + if (size > OneGigaByte) { + size /= OneGigaByte; + suffix = "GB"; + } + else if (size > OneMegaByte) { + size /= OneMegaByte; + suffix = "MB"; + } + else if (size > OneKiloByte) { + size /= OneKiloByte; + suffix = "kB"; + } + else { + suffix = " B"; + } + + var precision = format.Substring(2); + if (String.IsNullOrEmpty(precision)) precision = "2"; + return String.Format("{0:N" + precision + "} {1}", size, suffix); + + } + + private static string DefaultFormat(string format, object arg, IFormatProvider formatProvider) { + var formattableArg = arg as IFormattable; + if (formattableArg != null) { + return formattableArg.ToString(format, formatProvider); + } + return arg.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/NamespaceHelper.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/NamespaceHelper.cs new file mode 100644 index 000000000..1aa122c91 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/NamespaceHelper.cs @@ -0,0 +1,27 @@ +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; + +namespace Orchard.Azure.MediaServices.Helpers { + + public class NamespaceHelper { + + public static XmlNamespaceManager CreateNamespaceManager(XElement xml) { + var nav = xml.CreateNavigator(); + var nsm = new XmlNamespaceManager(nav.NameTable); + nsm.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance"); + nsm.AddNamespace("me", "http://schemas.microsoft.com/windowsazure/mediaservices/2013/05/mediaencoder/metadata"); + + return nsm; + } + + public static XmlNamespaceManager CreateNamespaceManager(XDocument xml) { + var reader = xml.CreateReader(); + var nsm = new XmlNamespaceManager(reader.NameTable); + nsm.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance"); + nsm.AddNamespace("me", "http://schemas.microsoft.com/windowsazure/mediaservices/2013/05/mediaencoder/metadata"); + + return nsm; + } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/StringExtensions.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/StringExtensions.cs new file mode 100644 index 000000000..828b27515 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Helpers/StringExtensions.cs @@ -0,0 +1,7 @@ +namespace Orchard.Azure.MediaServices.Helpers { + public static class StringExtensions { + public static string TrimSafe(this string value) { + return value != null ? value.Trim() : null; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Loader1.GIF b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Loader1.GIF new file mode 100644 index 000000000..a54810a89 Binary files /dev/null and b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Loader1.GIF differ diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Thumbnail-Placeholder.psd b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Thumbnail-Placeholder.psd new file mode 100644 index 000000000..329aa1903 Binary files /dev/null and b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Thumbnail-Placeholder.psd differ diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Thumbnail-Placeholder1.png b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Thumbnail-Placeholder1.png new file mode 100644 index 000000000..8f2f01e4b Binary files /dev/null and b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Thumbnail-Placeholder1.png differ diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Thumbnail-Placeholder2.png b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Thumbnail-Placeholder2.png new file mode 100644 index 000000000..3bd02ecc9 Binary files /dev/null and b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Thumbnail-Placeholder2.png differ diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Web.config b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Web.config new file mode 100644 index 000000000..817198995 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Images/Web.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Infrastructure/Assets/AssetDriver.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Infrastructure/Assets/AssetDriver.cs new file mode 100644 index 000000000..7f37289a1 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Infrastructure/Assets/AssetDriver.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Orchard.Azure.MediaServices.Models.Assets; +using Orchard.ContentManagement; +using Orchard.Localization; + +namespace Orchard.Azure.MediaServices.Infrastructure.Assets { + public abstract class AssetDriver : IAssetDriver where TAsset : Asset { + + protected AssetDriver() { + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + public virtual string Prefix { + get { return GetType().Name; } + } + + IEnumerable IAssetDriver.BuildEditor(Asset asset, dynamic shapeFactory) { + return BuildEditorInternal(asset, a => Editor(a, shapeFactory)); + } + + IEnumerable IAssetDriver.UpdateEditor(Asset asset, IUpdateModel updater, dynamic shapeFactory) { + return BuildEditorInternal(asset, a => Editor(a, updater, shapeFactory)); + } + + protected virtual IEnumerable Editor(TAsset asset, dynamic shapeFactory) { + return Enumerable.Empty(); + } + + protected virtual IEnumerable Editor(TAsset asset, IUpdateModel updater, dynamic shapeFactory) { + return Enumerable.Empty(); + } + + private static IEnumerable BuildEditorInternal(Asset asset, Func> harvestResults) { + var a = asset as TAsset; + + if (a == null) + return Enumerable.Empty(); + + var results = harvestResults(a).ToArray(); + + foreach (var result in results) { + if (result.EditorShape != null) + result.EditorShape.Asset = a; + } + + return results; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Infrastructure/Assets/AssetDriverResult.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Infrastructure/Assets/AssetDriverResult.cs new file mode 100644 index 000000000..cd432254c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Infrastructure/Assets/AssetDriverResult.cs @@ -0,0 +1,8 @@ +using Orchard.Localization; + +namespace Orchard.Azure.MediaServices.Infrastructure.Assets { + public class AssetDriverResult { + public LocalizedString TabTitle { get; set; } + public dynamic EditorShape { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Infrastructure/Assets/IAssetDriver.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Infrastructure/Assets/IAssetDriver.cs new file mode 100644 index 000000000..3c72e8021 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Infrastructure/Assets/IAssetDriver.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Orchard.Azure.MediaServices.Models.Assets; +using Orchard; +using Orchard.ContentManagement; + +namespace Orchard.Azure.MediaServices.Infrastructure.Assets { + public interface IAssetDriver : IDependency { + IEnumerable BuildEditor(Asset asset, dynamic shapeFactory); + IEnumerable UpdateEditor(Asset asset, IUpdateModel updater, dynamic shapeFactory); + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Infrastructure/Mappings/PersistenceConfiguration.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Infrastructure/Mappings/PersistenceConfiguration.cs new file mode 100644 index 000000000..f24372f4c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Infrastructure/Mappings/PersistenceConfiguration.cs @@ -0,0 +1,25 @@ +using FluentNHibernate.Automapping; +using FluentNHibernate.Cfg; +using Orchard.Azure.MediaServices.Models.Records; +using NHibernate.Cfg; +using Orchard.Data; +using Orchard.Utility; + +namespace Orchard.Azure.MediaServices.Infrastructure.Mappings { + public class PersistenceConfiguration : ISessionConfigurationEvents { + public void Created(FluentConfiguration cfg, AutoPersistenceModel defaultModel) { + defaultModel.Override(mapping => mapping.IgnoreProperty(x => x.Infoset)); + defaultModel.Override(mapping => mapping.References(x => x.Job, "JobId")); + defaultModel.Override(mapping => mapping.HasMany(x => x.Tasks).KeyColumn("JobId")); + } + public void Prepared(FluentConfiguration cfg) {} + public void Building(Configuration cfg) {} + public void Finished(Configuration cfg) {} + + public void ComputingHash(Hash hash) { + hash.AddString("AssetRecord.Ignore.InfoSet"); + hash.AddString("TaskRecord.References.Job.Via.JobId"); + hash.AddString("JobRecord.HasMany.Tasks.KeyColumn.JobId"); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Lib/Azure SDK for Media Services Notice.docx b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Lib/Azure SDK for Media Services Notice.docx new file mode 100644 index 000000000..8400fcd5c Binary files /dev/null and b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Lib/Azure SDK for Media Services Notice.docx differ diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Lib/Microsoft.Practices.TransientFaultHandling.Core.dll b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Lib/Microsoft.Practices.TransientFaultHandling.Core.dll new file mode 100644 index 000000000..01e82b400 Binary files /dev/null and b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Lib/Microsoft.Practices.TransientFaultHandling.Core.dll differ diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Lib/Microsoft.WindowsAzure.MediaServices.Client.dll b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Lib/Microsoft.WindowsAzure.MediaServices.Client.dll new file mode 100644 index 000000000..e2b98a0a3 Binary files /dev/null and b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Lib/Microsoft.WindowsAzure.MediaServices.Client.dll differ diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Migrations.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Migrations.cs new file mode 100644 index 000000000..484dcbdfb --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Migrations.cs @@ -0,0 +1,95 @@ +using System; +using Orchard.ContentManagement.MetaData; +using Orchard.Core.Contents.Extensions; +using Orchard.Data.Migration; + +namespace Orchard.Azure.MediaServices { + public class Migrations : DataMigrationImpl { + public int Create() { + + SchemaBuilder.CreateTable("AssetRecord", table => table + .Column("Id", column => column.PrimaryKey().Identity()) + .Column("VideoContentItemId") + .Column("Type") + .Column("Name", column => column.WithLength(256)) + .Column("Description", column => column.Unlimited()) + .Column("WamsPublicLocatorId") + .Column("WamsPublicLocatorUrl", column => column.WithLength(512)) + .Column("WamsPrivateLocatorId") + .Column("WamsPrivateLocatorUrl", column => column.WithLength(512)) + .Column("WamsAssetId", column => column.WithLength(64)) + .Column("WamsEncoderMetadataXml", column => column.Unlimited()) + .Column("OriginalFileName", column => column.WithLength(256)) + .Column("LocalTempFileName", column => column.WithLength(64)) + .Column("LocalTempFileSize") + .Column("IncludeInPlayer") + .Column("MediaQuery", column => column.WithLength(256)) + .Column("CreatedUtc") + .Column("UploadStatus", column => column.WithLength(64)) + .Column("UploadStartedUtc") + .Column("UploadCompletedUtc") + .Column("UploadBytesComplete") + .Column("PublishStatus", column => column.WithLength(64)) + .Column("PublishedUtc") + .Column("RemovedUtc") + .Column("Data", column => column.Unlimited())); + + SchemaBuilder.CreateTable("JobRecord", table => table + .Column("Id", column => column.PrimaryKey().Identity()) + .Column("CloudVideoPartId") + .Column("WamsJobId", column => column.WithLength(64)) + .Column("Name", column => column.WithLength(256)) + .Column("Description", column => column.WithLength(1024)) + .Column("Status", column => column.WithLength(32)) + .Column("ErrorMessage", column => column.Unlimited()) + .Column("OutputAssetName", column => column.WithLength(256)) + .Column("OutputAssetDescription", column => column.Unlimited()) + .Column("CreatedUtc") + .Column("StartedUtc") + .Column("FinishedUtc")); + + SchemaBuilder.CreateTable("TaskRecord", table => table + .Column("Id", column => column.PrimaryKey().Identity()) + .Column("JobId") + .Column("WamsTaskId", column => column.WithLength(64)) + .Column("TaskProviderName", column => column.WithLength(64)) + .Column("TaskIndex", column => column.NotNull()) + .Column("Status", column => column.WithLength(32)) + .Column("PercentComplete", column => column.NotNull()) + .Column("SettingsXml", column => column.Unlimited()) + .Column("HarvestAssetType", column => column.WithLength(64)) + .Column("HarvestAssetName", column => column.WithLength(256))); + + ContentDefinitionManager.AlterPartDefinition("CloudVideoPart", part => part + .Attachable(false) + .WithDescription("Stores information about a cloud video item and its related assets and jobs in Windows Azure Media Services.")); + + ContentDefinitionManager.AlterTypeDefinition("CloudVideo", type => type + .WithPart("CommonPart") + .WithPart("IdentityPart") + .WithPart("MediaPart") + .WithPart("TitlePart") + .WithPart("PublishLaterPart") + .WithPart("CloudVideoPart") + .DisplayedAs("Cloud Video") + .WithSetting("Stereotype", "Media") + .Draftable()); + + return 3; + } + + public int UpdateFrom1() { + SchemaBuilder.AlterTable("AssetRecord", table => table + .AddColumn("MediaQuery", column => column.WithLength(256))); + + return 2; + } + + public int UpdateFrom2() { + SchemaBuilder.AlterTable("AssetRecord", table => table + .DropColumn("MimeType")); + + return 3; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/Asset.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/Asset.cs new file mode 100644 index 000000000..60134a6e7 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/Asset.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Orchard.Azure.MediaServices.Models.Records; +using Microsoft.WindowsAzure.MediaServices.Client; +using Orchard.ContentManagement.FieldStorage; +using Orchard.Core.Common.Utilities; +using Orchard.FileSystems.Media; + +namespace Orchard.Azure.MediaServices.Models.Assets { + public abstract class Asset { + + internal readonly LazyField _videoPartField = new LazyField(); + private AssetRecord _record; + + public IFieldStorage Storage { get; set; } + public IMimeTypeProvider MimeTypeProvider { get; set; } + + public AssetRecord Record { + get { return _record; } + internal set { + _record = value; + UploadState = new AssetUploadState(_record); + PublishState = new AssetPublishState(_record); + } + } + + public string Name { + get { return Record.Name; } + set { Record.Name = value; } + } + + public string Description { + get { return Record.Description; } + set { Record.Description = value; } + } + + public bool IncludeInPlayer { + get { return Record.IncludeInPlayer; } + set { Record.IncludeInPlayer = value; } + } + + public string MediaQuery { + get { return Record.MediaQuery; } + set { Record.MediaQuery = value; } + } + + public DateTime CreatedUtc { + get { return Record.CreatedUtc; } + set { Record.CreatedUtc = value; } + } + + public CloudVideoPart VideoPart { + get { return _videoPartField.Value; } + set { _videoPartField.Value = value; } + } + + public string WamsPublicLocatorId { + get { return Record.WamsPublicLocatorId; } + set { Record.WamsPublicLocatorId = value; } + } + + public string WamsPublicLocatorUrl { + get { return Record.WamsPublicLocatorUrl; } + set { Record.WamsPublicLocatorUrl = value; } + } + + public string WamsPrivateLocatorId { + get { return Record.WamsPrivateLocatorId; } + set { Record.WamsPrivateLocatorId = value; } + } + + public string WamsPrivateLocatorUrl { + get { return Record.WamsPrivateLocatorUrl; } + set { Record.WamsPrivateLocatorUrl = value; } + } + + public string WamsAssetId { + get { return Record.WamsAssetId; } + set { Record.WamsAssetId = value; } + } + + public string OriginalFileName { + get { return Record.OriginalFileName; } + set { Record.OriginalFileName = value; } + } + + public string LocalTempFileName { + get { return Record.LocalTempFileName; } + set { Record.LocalTempFileName = value; } + } + + public long? LocalTempFileSize { + get { return Record.LocalTempFileSize; } + set { Record.LocalTempFileSize = value; } + } + + public string MimeType { + get { + var fileName = !String.IsNullOrWhiteSpace(PrivateMainFileUrl) ? GetFileName(PrivateMainFileUrl) : OriginalFileName; + return MimeTypeProvider.GetMimeType(fileName); + } + } + + public string PrivateMainFileUrl { + get { return GetMainFileUrl(WamsPrivateLocatorUrl); } + } + + public string PublicMainFileUrl { + get { return GetMainFileUrl(WamsPublicLocatorUrl); } + } + + public AssetUploadState UploadState { get; private set; } + public AssetPublishState PublishState { get; private set; } + + public virtual IEnumerable GetDisplayLocators() { + yield return new DisplayLocator("Private (SAS)", WamsPrivateLocatorId, WamsPrivateLocatorUrl); + yield return new DisplayLocator("Public (SAS)", WamsPublicLocatorId, WamsPublicLocatorUrl); + } + + public override string ToString() { + return String.Format("{0} - {1}", GetType().Name, Name); + } + + protected virtual string GetMainFileUrl(string locatorUrl) { + if (!String.IsNullOrEmpty(locatorUrl)) { + var builder = new UriBuilder(locatorUrl); + builder.Path += "/" + OriginalFileName; + return builder.Uri.AbsoluteUri; + } + return null; + } + + private static string GetFileName(string fileUrl) { + var uri = new Uri(fileUrl, UriKind.Absolute); + return uri.AbsolutePath; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/AssetPublishState.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/AssetPublishState.cs new file mode 100644 index 000000000..a92ffcb2e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/AssetPublishState.cs @@ -0,0 +1,40 @@ +using System; +using Orchard.Azure.MediaServices.Models.Records; + +namespace Orchard.Azure.MediaServices.Models.Assets { + public class AssetPublishState { + + private readonly AssetRecord _record; + + public AssetPublishState(AssetRecord record) { + _record = record; + } + + public AssetPublishStatus Status { + get { + return _record.PublishStatus; + } + set { + _record.PublishStatus = value; + } + } + + public DateTime? PublishedUtc { + get { + return _record.PublishedUtc; + } + set { + _record.PublishedUtc = value; + } + } + + public DateTime? RemovedUtc { + get { + return _record.RemovedUtc; + } + set { + _record.RemovedUtc = value; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/AssetPublishStatus.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/AssetPublishStatus.cs new file mode 100644 index 000000000..a1a8ad85f --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/AssetPublishStatus.cs @@ -0,0 +1,13 @@ +using System; +using Orchard.ContentManagement.FieldStorage; +using Orchard.ContentManagement.FieldStorage.InfosetStorage; +using Orchard.Core.Common.Utilities; +using Orchard.Data.Conventions; + +namespace Orchard.Azure.MediaServices.Models.Assets { + public enum AssetPublishStatus { + None, + Published, + Removed + } +} diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/AssetUploadState.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/AssetUploadState.cs new file mode 100644 index 000000000..5a2932c43 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/AssetUploadState.cs @@ -0,0 +1,55 @@ +using System; +using Orchard.Azure.MediaServices.Models.Records; + +namespace Orchard.Azure.MediaServices.Models.Assets { + public class AssetUploadState { + + private readonly AssetRecord _record; + + public AssetUploadState(AssetRecord record) { + _record = record; + } + + public AssetUploadStatus Status { + get { + return _record.UploadStatus; + } + set { + _record.UploadStatus = value; + } + } + + public DateTime? StartedUtc { + get { + return _record.UploadStartedUtc; + } + set { + _record.UploadStartedUtc = value; + } + } + + public DateTime? CompletedUtc { + get { + return _record.UploadCompletedUtc; + } + set { + _record.UploadCompletedUtc = value; + } + } + + public long? BytesComplete { + get { + return _record.UploadBytesComplete; + } + set { + _record.UploadBytesComplete = value; + } + } + + public double? PercentComplete { + get { + return BytesComplete.HasValue ? (double?)BytesComplete.Value / (double?)_record.LocalTempFileSize * 100 : (double?)null; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/AssetUploadStatus.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/AssetUploadStatus.cs new file mode 100644 index 000000000..78b6e8539 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/AssetUploadStatus.cs @@ -0,0 +1,14 @@ +using System; +using Orchard.ContentManagement.FieldStorage; +using Orchard.ContentManagement.FieldStorage.InfosetStorage; +using Orchard.Core.Common.Utilities; +using Orchard.Data.Conventions; + +namespace Orchard.Azure.MediaServices.Models.Assets { + public enum AssetUploadStatus { + Pending, + Uploading, + Uploaded, + Canceled + } +} diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/DisplayLocator.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/DisplayLocator.cs new file mode 100644 index 000000000..038e5d078 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/DisplayLocator.cs @@ -0,0 +1,13 @@ +namespace Orchard.Azure.MediaServices.Models.Assets { + public class DisplayLocator { + public DisplayLocator(string name, string id, string url) { + Name = name; + Id = id; + Url = url; + } + + public string Name { get; private set; } + public string Id { get; private set; } + public string Url { get; private set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/DynamicVideoAsset.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/DynamicVideoAsset.cs new file mode 100644 index 000000000..a29b04917 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/DynamicVideoAsset.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; + +namespace Orchard.Azure.MediaServices.Models.Assets { + public class DynamicVideoAsset : VideoAsset { + + public string WamsPrivateOnDemandLocatorId { + get { return Storage.Get("WamsPrivateOnDemandLocatorId"); } + set { Storage.Set("WamsPrivateOnDemandLocatorId", value); } + } + + public string WamsPrivateOnDemandLocatorUrl { + get { return Storage.Get("WamsPrivateOnDemandLocatorUrl"); } + set { Storage.Set("WamsPrivateOnDemandLocatorUrl", value); } + } + + public string WamsPublicOnDemandLocatorId { + get { return Storage.Get("WamsPublicOnDemandLocatorId"); } + set { Storage.Set("WamsPublicOnDemandLocatorId", value); } + } + + public string WamsPublicOnDemandLocatorUrl { + get { return Storage.Get("WamsPublicOnDemandLocatorUrl"); } + set { Storage.Set("WamsPublicOnDemandLocatorUrl", value); } + } + + public string WamsManifestFilename { + get { return Storage.Get("WamsManifestFilename"); } + set { Storage.Set("WamsManifestFilename", value); } + } + + public string PrivateManifestUrl { + get { return !String.IsNullOrEmpty(WamsPrivateOnDemandLocatorUrl) && !String.IsNullOrEmpty(WamsManifestFilename) ? String.Format("{0}{1}", WamsPrivateOnDemandLocatorUrl, WamsManifestFilename) : null; } + } + + public string PrivateSmoothStreamingUrl { + get { return !String.IsNullOrEmpty(PrivateManifestUrl) ? String.Format("{0}/manifest", PrivateManifestUrl) : null; } + } + + public string PrivateHlsUrl { + get { return !String.IsNullOrEmpty(PrivateManifestUrl) ? String.Format("{0}/manifest(format=m3u8-aapl)", PrivateManifestUrl) : null; } + } + + public string PrivateMpegDashUrl { + get { return !String.IsNullOrEmpty(PrivateManifestUrl) ? String.Format("{0}/manifest(format=mpd-time-csf)", PrivateManifestUrl) : null; } + } + + public string PublicManifestUrl { + get { return !String.IsNullOrEmpty(WamsPublicOnDemandLocatorUrl) && !String.IsNullOrEmpty(WamsManifestFilename) ? String.Format("{0}{1}", WamsPublicOnDemandLocatorUrl, WamsManifestFilename) : null; } + } + + public string PublicSmoothStreamingUrl { + get { return !String.IsNullOrEmpty(PublicManifestUrl) ? String.Format("{0}/manifest", PublicManifestUrl) : null; } + } + + public string PublicHlsUrl { + get { return !String.IsNullOrEmpty(PublicManifestUrl) ? String.Format("{0}/manifest(format=m3u8-aapl)", PublicManifestUrl) : null; } + } + + public string PublicMpegDashUrl { + get { return !String.IsNullOrEmpty(PublicManifestUrl) ? String.Format("{0}/manifest(format=mpd-time-csf)", PublicManifestUrl) : null; } + } + + public override IEnumerable GetDisplayLocators() { + foreach (var locator in base.GetDisplayLocators()) + yield return locator; + + yield return new DisplayLocator("Private (on-demand)", WamsPrivateOnDemandLocatorId, WamsPrivateOnDemandLocatorUrl); + yield return new DisplayLocator("Public (on-demand)", WamsPublicOnDemandLocatorId, WamsPublicOnDemandLocatorUrl); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/EncoderMetadata/AssetFile.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/EncoderMetadata/AssetFile.cs new file mode 100644 index 000000000..620619c26 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/EncoderMetadata/AssetFile.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using Orchard.FileSystems.Media; +using Orchard.Azure.MediaServices.Helpers; + +namespace Orchard.Azure.MediaServices.Models.Assets.EncoderMetadata { + public class AssetFile { + + private readonly XmlNamespaceManager _nsm; + private readonly XElement _xml; + private readonly Metadata _parentMetadata; + private readonly IMimeTypeProvider _mimeTypeProvider; + private IEnumerable _audioTracks; + private IEnumerable _videoTracks; + private IEnumerable _sources; + + public AssetFile(XElement xml, Metadata parentMetadata, IMimeTypeProvider mimeTypeProvider) { + _nsm = NamespaceHelper.CreateNamespaceManager(xml); + _xml = xml; + _parentMetadata = parentMetadata; + _mimeTypeProvider = mimeTypeProvider; + } + + /// + /// The name of the media file. + /// + public string Name { + get { + return _xml.Attribute(XName.Get("Name")).Value; + } + } + + /// + /// The size of the media file in bytes. + /// + public long Size { + get { + return XmlConvert.ToInt64(_xml.Attribute(XName.Get("Size")).Value); + } + } + + /// + /// The play back duration of the media file. + /// + public TimeSpan Duration { + get { + return XmlConvert.ToTimeSpan(_xml.Attribute(XName.Get("Duration")).Value); + } + } + + /// + /// A collection of audio tracks contained in the media file. + /// + public IEnumerable AudioTracks { + get { + if (_audioTracks == null) { + var audioTracksQuery = + from e in _xml.XPathSelectElements("./me:AudioTracks/me:AudioTrack", _nsm) + select new AudioTrack(e); + _audioTracks = audioTracksQuery.ToArray(); + } + return _audioTracks; + } + } + + /// + /// A collection of video tracks contained in the media file. + /// + public IEnumerable VideoTracks { + get { + if (_videoTracks == null) { + var videoTracksQuery = + from e in _xml.XPathSelectElements("./me:VideoTracks/me:VideoTrack", _nsm) + select new VideoTrack(e); + _videoTracks = videoTracksQuery.ToArray(); + } + return _videoTracks; + } + } + + /// + /// A collection of names of source media files that were processed in order to produce this output media file. + /// + public IEnumerable Sources { + get { + if (_sources == null) { + var sourcesQuery = + from e in _xml.XPathSelectElements("./me:Sources/me:Source", _nsm) + select e.Attribute(XName.Get("Name")).Value; + _sources = sourcesQuery.ToArray(); + } + return _sources; + } + } + + /// + /// The total bit rate in bits per second, including all video and audio tracks. Counts only the elementary stream payload, and does not include the packaging overhead. + /// + public int Bitrate { + get { + var totalVideoBitrate = _videoTracks.Select(videoTrack => videoTrack.Bitrate).Sum(); + var totalAudioBitrate = _audioTracks.Select(audioTrack => audioTrack.Bitrate).Sum(); + return totalVideoBitrate + totalVideoBitrate; + } + } + + /// + /// The mime type of the asset file. + /// + public string MimeType { + get { + return _mimeTypeProvider.GetMimeType(Name); + } + } + + /// + /// A direct URL to download the asset file using a private locator. + /// + public string PrivateUrl { + get { + if (!String.IsNullOrEmpty(_parentMetadata.PrivateLocatorUrl)) { + var builder = new UriBuilder(_parentMetadata.PrivateLocatorUrl); + builder.Path += "/" + Name; + return builder.Uri.AbsoluteUri; + } + return null; + } + } + + /// + /// A direct URL to download the asset file using a public locator. + /// + public string PublicUrl { + get { + if (!String.IsNullOrEmpty(_parentMetadata.PublicLocatorUrl)) { + var builder = new UriBuilder(_parentMetadata.PublicLocatorUrl); + builder.Path += "/" + Name; + return builder.Uri.AbsoluteUri; + } + return null; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/EncoderMetadata/AudioTrack.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/EncoderMetadata/AudioTrack.cs new file mode 100644 index 000000000..2642804fd --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/EncoderMetadata/AudioTrack.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Xml; +using System.Xml.Linq; + +namespace Orchard.Azure.MediaServices.Models.Assets.EncoderMetadata { + public class AudioTrack { + + private readonly XElement _xml; + + public AudioTrack(XElement xml) { + _xml = xml; + } + + /// + /// The zero-based index of the audio track. Note: this is not necessarily the TrackID as used in an MP4 file. + /// + public int Index { + get { + return XmlConvert.ToInt32(_xml.Attribute(XName.Get("Id")).Value); + } + } + + /// + /// The average audio bitrate in bits per second, as calculated from the media file. Takes into consideration only the elementary stream payload and does not include the packaging overhead. + /// + public int Bitrate { + get { + return XmlConvert.ToInt32(_xml.Attribute(XName.Get("Bitrate")).Value); + } + } + + /// + /// The audio sampling rate in samples/sec or Hz + /// + public int SamplingRate { + get { + return XmlConvert.ToInt32(_xml.Attribute(XName.Get("SamplingRate")).Value); + } + } + + /// + /// The bits per sample for the audio track. + /// + public int BitsPerSample { + get { + return XmlConvert.ToInt32(_xml.Attribute(XName.Get("BitsPerSample")).Value); + } + } + + /// + /// The number of audio channels in the audio track. + /// + public int Channels { + get { + return XmlConvert.ToInt32(_xml.Attribute(XName.Get("Channels")).Value); + } + } + + /// + /// The audio codec used for encoding the audio track. + /// + public string Codec { + get { + return _xml.Attribute(XName.Get("Codec")).Value; + } + } + + /// + /// The optional encoder version string, required for EAC3. + /// + public string EncoderVersion { + get { + var encoderVersionAttribute = _xml.Attribute(XName.Get("EncoderVersion")); + if (encoderVersionAttribute != null) + return encoderVersionAttribute.Value; + return null; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/EncoderMetadata/Metadata.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/EncoderMetadata/Metadata.cs new file mode 100644 index 000000000..03d489ce3 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/EncoderMetadata/Metadata.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using Orchard.FileSystems.Media; +using Orchard.Azure.MediaServices.Helpers; + +namespace Orchard.Azure.MediaServices.Models.Assets.EncoderMetadata { + public class Metadata { + + public static Metadata Parse(string encoderMetadataXml, string privateLocatorUrl, string publicLocatorUrl, IMimeTypeProvider mimeTypeProvider) { + var xml = XDocument.Parse(encoderMetadataXml); + return new Metadata(xml, privateLocatorUrl, publicLocatorUrl, mimeTypeProvider); + } + + private readonly XmlNamespaceManager _nsm; + private readonly XDocument _xml; + private readonly string _privateLocatorUrl; + private readonly string _publicLocatorUrl; + private readonly IMimeTypeProvider _mimeTypeProvider; + private IEnumerable _assetFiles; + + public Metadata(XDocument xml, string privateLocatorUrl, string publicLocatorUrl, IMimeTypeProvider mimeTypeProvider) { + _nsm = NamespaceHelper.CreateNamespaceManager(xml); + _xml = xml; + _privateLocatorUrl = privateLocatorUrl; + _publicLocatorUrl = publicLocatorUrl; + _mimeTypeProvider = mimeTypeProvider; + } + + /// + /// A collection of media files contained in this asset. + /// + public IEnumerable AssetFiles { + get { + if (_assetFiles == null) { + var assetFilesQuery = + from e in _xml.Root.XPathSelectElements("./me:AssetFile", _nsm) + select new AssetFile(e, this, _mimeTypeProvider); + _assetFiles = assetFilesQuery.ToArray(); + } + return _assetFiles; + } + } + + public string PrivateLocatorUrl { + get { + return _privateLocatorUrl; + } + } + + public string PublicLocatorUrl { + get { + return _publicLocatorUrl; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/EncoderMetadata/VideoTrack.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/EncoderMetadata/VideoTrack.cs new file mode 100644 index 000000000..7ab0907dd --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/EncoderMetadata/VideoTrack.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Xml; +using System.Xml.Linq; + +namespace Orchard.Azure.MediaServices.Models.Assets.EncoderMetadata { + public class VideoTrack { + + private readonly XElement _xml; + + public VideoTrack(XElement xml) { + _xml = xml; + } + + /// + /// The zero-based index of this video track. Note: this is not necessarily the TrackID as used in an MP4 file. + /// + public int Index { + get { + return XmlConvert.ToInt32(_xml.Attribute(XName.Get("Id")).Value); + } + } + + /// + /// The average video bit rate in bits per second, as calculated from the media file. Counts only the elementary stream payload, and does not include the packaging overhead. + /// + public int Bitrate { + get { + return XmlConvert.ToInt32(_xml.Attribute(XName.Get("Bitrate")).Value); + } + } + + /// + /// The target average bitrate for this video track, as requested in the encoding preset, in bits per second. + /// + public int TargetBitrate { + get { + return XmlConvert.ToInt32(_xml.Attribute(XName.Get("TargetBitrate")).Value); + } + } + + /// + /// The measured video frame rate in frames per second (Hz). + /// + public decimal Framerate { + get { + return XmlConvert.ToDecimal(_xml.Attribute(XName.Get("Framerate")).Value); + } + } + + /// + /// The preset target video frame rate in frames per second (Hz). + /// + public decimal TargetFramerate { + get { + return XmlConvert.ToDecimal(_xml.Attribute(XName.Get("TargetFramerate")).Value); + } + } + + /// + /// The video codec FourCC code. + /// + public string FourCc { + get { + return _xml.Attribute(XName.Get("FourCC")).Value; + } + } + + /// + /// The encoded video width in pixels. + /// + public int Width { + get { + return XmlConvert.ToInt32(_xml.Attribute(XName.Get("Width")).Value); + } + } + + /// + /// The encoded video height in pixels. + /// + public int Height { + get { + return XmlConvert.ToInt32(_xml.Attribute(XName.Get("Height")).Value); + } + } + + /// + /// The numerator of the video display aspect ratio. + /// + public decimal DisplayAspectRatioX { + get { + return XmlConvert.ToDecimal(_xml.Attribute(XName.Get("DisplayAspectRatioNumerator")).Value); + } + } + + /// + /// The demoninator of the video display aspect ratio. + /// + public decimal DisplayAspectRatioY { + get { + return XmlConvert.ToDecimal(_xml.Attribute(XName.Get("DisplayAspectRatioDenominator")).Value); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/MezzanineAsset.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/MezzanineAsset.cs new file mode 100644 index 000000000..a0ea1e43a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/MezzanineAsset.cs @@ -0,0 +1,6 @@ +using System; + +namespace Orchard.Azure.MediaServices.Models.Assets { + public class MezzanineAsset : Asset { + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/PublishAction.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/PublishAction.cs new file mode 100644 index 000000000..1e89f807d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/PublishAction.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using Orchard.ContentManagement; +using Orchard.ContentManagement.Records; +using Orchard.Core.Common.Utilities; + +namespace Orchard.Azure.MediaServices.Models.Assets { + public enum PublishAction { + None, + Publish, + PublishLater + } +} diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/SubtitleAsset.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/SubtitleAsset.cs new file mode 100644 index 000000000..39ac392cf --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/SubtitleAsset.cs @@ -0,0 +1,11 @@ +using System; + +namespace Orchard.Azure.MediaServices.Models.Assets { + public class SubtitleAsset : Asset { + + public string Language { + get { return Storage.Get("Language"); } + set { Storage.Set("Language", value); } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/ThumbnailAsset.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/ThumbnailAsset.cs new file mode 100644 index 000000000..a27ed008e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/ThumbnailAsset.cs @@ -0,0 +1,5 @@ +using System; +namespace Orchard.Azure.MediaServices.Models.Assets { + public class ThumbnailAsset : Asset { + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/VideoAsset.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/VideoAsset.cs new file mode 100644 index 000000000..3575a366e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Assets/VideoAsset.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; +using Orchard.Azure.MediaServices.Models.Assets.EncoderMetadata; +namespace Orchard.Azure.MediaServices.Models.Assets { + public class VideoAsset : Asset { + private Metadata _encoderMetadata; // TODO: Should be made thread-safe. + + public string WamsEncoderMetadataXml { + get { return Storage.Get("WamsEncoderMetadataXml"); } + set { + Storage.Set("WamsEncoderMetadataXml", value); + _encoderMetadata = null; // Clear out cached metadata. + } + } + + public string EncodingPreset { + get { return Storage.Get("EncodingPreset"); } + set { Storage.Set("EncodingPreset", value); } + } + + public Metadata EncoderMetadata { + get { + if (_encoderMetadata == null) { + if (!String.IsNullOrEmpty(WamsEncoderMetadataXml)) { + _encoderMetadata = Metadata.Parse(WamsEncoderMetadataXml, WamsPrivateLocatorUrl, WamsPublicLocatorUrl, MimeTypeProvider); + } + } + return _encoderMetadata; + } + } + + protected override string GetMainFileUrl(string locatorUrl) { + // In the case of a video asset we consider the main file to be the first + // asset file containing one or more video tracks according to the encoder + // metadata. + if (!String.IsNullOrEmpty(locatorUrl) && EncoderMetadata != null) { + var firstVideoFile = EncoderMetadata.AssetFiles.FirstOrDefault(assetFile => assetFile.VideoTracks.Any()); + if (firstVideoFile != null) { + var builder = new UriBuilder(locatorUrl); + builder.Path += "/" + firstVideoFile.Name; + return builder.Uri.AbsoluteUri; + } + } + return null; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/CloudMediaSettingsPart.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/CloudMediaSettingsPart.cs new file mode 100644 index 000000000..b302c9825 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/CloudMediaSettingsPart.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; +using System.Globalization; +using Orchard.ContentManagement; +using System; +using System.Linq; + +namespace Orchard.Azure.MediaServices.Models { + + public class CloudMediaSettingsPart : ContentPart { + + public string WamsAccountName { + get { return this.Retrieve(x => x.WamsAccountName); } + set { this.Store(x => x.WamsAccountName, value); } + } + + public string WamsAccountKey { + get { return this.Retrieve(x => x.WamsAccountKey); } + set { this.Store(x => x.WamsAccountKey, value); } + } + + public string StorageAccountKey { + get { return this.Retrieve(x => x.StorageAccountKey); } + set { this.Store(x => x.StorageAccountKey, value); } + } + + public bool EnableDynamicPackaging { + get { return this.Retrieve(x => x.EnableDynamicPackaging); } + set { this.Store(x => x.EnableDynamicPackaging, value); } + } + + public TimeSpan AccessPolicyDuration { + get { + var duration = Retrieve("AccessPolicyDuration"); + return !String.IsNullOrEmpty(duration) ? TimeSpan.Parse(duration, CultureInfo.InvariantCulture) : TimeSpan.FromDays(365 * 5); + } + set { + Store("AccessPolicyDuration", value.ToString()); + } + } + + public IEnumerable AllowedVideoFilenameExtensions { + get { + var languages = Retrieve("AllowedVideoFilenameExtensions"); + return !String.IsNullOrEmpty(languages) ? languages.Split(';') : new string[] { }; + } + set { + var languages = value != null && value.Any() ? String.Join(";", value) : null; + Store("AllowedVideoFilenameExtensions", languages); + } + } + + public IEnumerable WamsEncodingPresets { + get { + var presets = Retrieve("WamsEncodingPresets"); + return !String.IsNullOrEmpty(presets) ? presets.Split(';') : new string[] { }; + } + set { + var presets = value != null && value.Any() ? String.Join(";", value) : null; + Store("WamsEncodingPresets", presets); + } + } + + public int DefaultWamsEncodingPresetIndex { + get { return this.Retrieve(x => x.DefaultWamsEncodingPresetIndex, 11); } + set { this.Store(x => x.DefaultWamsEncodingPresetIndex, value); } + } + + public string EncryptionKeySeedValue { + get { return this.Retrieve(x => x.EncryptionKeySeedValue); } + set { this.Store(x => x.EncryptionKeySeedValue, value); } + } + + public string EncryptionLicenseAcquisitionUrl { + get { return this.Retrieve(x => x.EncryptionLicenseAcquisitionUrl); } + set { this.Store(x => x.EncryptionLicenseAcquisitionUrl, value); } + } + + public IEnumerable SubtitleLanguages { + get { + var languages = Retrieve("SubtitleLanguages"); + return !String.IsNullOrEmpty(languages) ? languages.Split(';') : new string[] { }; + } + set { + var languages = value != null && value.Any() ? String.Join(";", value) : null; + Store("SubtitleLanguages", languages); + } + } + + public bool IsValid() { + if (String.IsNullOrWhiteSpace(WamsAccountKey) || String.IsNullOrWhiteSpace(WamsAccountName)) + return false; + if (!AllowedVideoFilenameExtensions.Any()) + return false; + if (!WamsEncodingPresets.Any()) + return false; + if (DefaultWamsEncodingPresetIndex < 0 || DefaultWamsEncodingPresetIndex > WamsEncodingPresets.Count() - 1) + return false; + + return true; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/CloudVideoPart.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/CloudVideoPart.cs new file mode 100644 index 000000000..413ca610e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/CloudVideoPart.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Linq; +using Orchard.Azure.MediaServices.Models.Assets; +using Orchard.Azure.MediaServices.Models.Jobs; +using Orchard.Azure.MediaServices.Services.Assets; +using Orchard.Azure.MediaServices.Services.Jobs; +using Orchard.ContentManagement; + +namespace Orchard.Azure.MediaServices.Models { + public class CloudVideoPart : ContentPart { + public IAssetManager _assetManager; + internal IJobManager _jobManager; + + public IEnumerable Assets { + get { return _assetManager.LoadAssetsFor(this); } + } + + public MezzanineAsset MezzanineAsset { + get { return (MezzanineAsset)Assets.FirstOrDefault(x => x is MezzanineAsset); } + } + + public IEnumerable Jobs { + get { return _jobManager.GetJobsFor(this); } + } + + public bool PublishOnUpload { + get { return this.Retrieve(x => x.PublishOnUpload); } + set { this.Store(x => x.PublishOnUpload, value); } + } + + public ThumbnailAsset ThumbnailAsset { + get { return _assetManager.GetThumbnailAssetFor(this); } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Jobs/FinishedJobContext.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Jobs/FinishedJobContext.cs new file mode 100644 index 000000000..58253f654 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Jobs/FinishedJobContext.cs @@ -0,0 +1,10 @@ +using Orchard.Azure.MediaServices.Models.Records; +using Microsoft.WindowsAzure.MediaServices.Client; + +namespace Orchard.Azure.MediaServices.Models.Jobs { + public class FinishedJobContext { + public CloudVideoPart CloudVideoPart { get; set; } + public JobRecord JobRecord { get; set; } + public IJob Job { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Jobs/Job.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Jobs/Job.cs new file mode 100644 index 000000000..6489fd2bb --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Jobs/Job.cs @@ -0,0 +1,109 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using Orchard.Azure.MediaServices.Helpers; +using Orchard.Azure.MediaServices.Models.Records; +using Orchard.Core.Common.Utilities; + +namespace Orchard.Azure.MediaServices.Models.Jobs { + public class Job { + internal readonly LazyField> _tasksField = new LazyField>(); + internal readonly LazyField _cloudVideoPartField = new LazyField(); + + public Job(JobRecord record) { + Record = record; + } + + public JobRecord Record { get; private set; } + + public IEnumerable Tasks { + get { return _tasksField.Value; } + } + + public CloudVideoPart CloudVideoPart { + get { return _cloudVideoPartField.Value; } + } + + public string WamsJobId { + get { return Record.WamsJobId; } + set { Record.WamsJobId = value; } + } + + public string Name { + get { return Record.Name; } + set { Record.Name = value; } + } + + public string Description { + get { return Record.Description; } + set { Record.Description = value; } + } + + public JobStatus Status { + get { return Record.Status; } + set { Record.Status = value; } + } + + public DateTime? CreatedUtc { + get { return Record.CreatedUtc; } + set { Record.CreatedUtc = value; } + } + + public DateTime? StartedUtc { + get { return Record.StartedUtc; } + set { Record.StartedUtc = value; } + } + + public DateTime? FinishedUtc { + get { return Record.FinishedUtc; } + set { Record.FinishedUtc = value; } + } + + public string ErrorMessage { + get { return Record.ErrorMessage; } + set { Record.ErrorMessage = value; } + } + + public string OutputAssetName { + get { return Record.OutputAssetName; } + set { Record.OutputAssetName = value; } + } + + public string OutputAssetDescription { + get { return Record.OutputAssetDescription; } + set { Record.OutputAssetDescription = value; } + } + + public int PercentComplete { + get { + if (Tasks != null && Tasks.Any()) + return (int)Tasks.Select(task => task.PercentComplete).Average(); + return 0; + } + } + + public bool IsActive { + get { + return Status.IsAny(JobStatus.Pending, JobStatus.Queued, JobStatus.Scheduled, JobStatus.Processing, JobStatus.Canceling); + } + } + + public bool IsOpen { + get { + return Status.IsNotAny(JobStatus.Archived); + } + } + + public bool CanArchive { + get { + return Status.IsAny(JobStatus.Canceled, JobStatus.Faulted, JobStatus.Finished); + } + } + + public bool CanCancel { + get { + return Status.IsAny(JobStatus.Pending, JobStatus.Queued, JobStatus.Scheduled, JobStatus.Processing); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Jobs/JobStatus.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Jobs/JobStatus.cs new file mode 100644 index 000000000..b92d62e07 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Jobs/JobStatus.cs @@ -0,0 +1,13 @@ +namespace Orchard.Azure.MediaServices.Models.Jobs { + public enum JobStatus { + Pending, + Processing, + Finished, + Canceling, + Canceled, + Queued, + Scheduled, + Faulted, + Archived + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Jobs/Task.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Jobs/Task.cs new file mode 100644 index 000000000..6a0f9020b --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Jobs/Task.cs @@ -0,0 +1,60 @@ +using System.Xml.Linq; +using Orchard.Azure.MediaServices.Models.Records; +using Orchard.Core.Common.Utilities; + +namespace Orchard.Azure.MediaServices.Models.Jobs { + public class Task { + internal readonly LazyField _jobField = new LazyField(); + + public Task() { + Record = new TaskRecord(); + } + + public TaskRecord Record { get; set; } + + public Job Job { + get { return _jobField.Value; } + set { _jobField.Value = value; } + } + + public string WamsTaskId { + get { return Record.WamsTaskId; } + set { Record.WamsTaskId = value; } + } + + public string TaskProviderName { + get { return Record.TaskProviderName; } + set { Record.TaskProviderName = value; } + } + + public int Index { + get { return Record.TaskIndex; } + set { Record.TaskIndex = value; } + } + + public JobStatus Status { + get { return Record.Status; } + set { Record.Status = value; } + } + + public int PercentComplete { + get { return Record.PercentComplete; } + set { Record.PercentComplete = value; } + } + + public XElement Settings { + get { return Record.SettingsXml != null ? XElement.Parse(Record.SettingsXml) : new XElement("Settings"); } + set { Record.SettingsXml = value != null ? value.ToString() : null; } + } + + public string HarvestAssetType { + get { return Record.HarvestAssetType; } + set { Record.HarvestAssetType = value; } + } + + public string HarvestAssetName { + get { return Record.HarvestAssetName; } + set { Record.HarvestAssetName = value; } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Records/AssetRecord.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Records/AssetRecord.cs new file mode 100644 index 000000000..957a7ce0a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Records/AssetRecord.cs @@ -0,0 +1,46 @@ +using System; +using Orchard.Azure.MediaServices.Models.Assets; +using Orchard.ContentManagement.FieldStorage.InfosetStorage; +using Orchard.Data.Conventions; + +namespace Orchard.Azure.MediaServices.Models.Records { + public class AssetRecord { + + public AssetRecord() { + Infoset = new Infoset(); + } + + public virtual int Id { get; set; } + public virtual int VideoContentItemId { get; set; } + public virtual string Type { get; set; } + public virtual string Name { get; set; } + public virtual string Description { get; set; } + public virtual string WamsPublicLocatorId { get; set; } + public virtual string WamsPublicLocatorUrl { get; set; } + public virtual string WamsPrivateLocatorId { get; set; } + public virtual string WamsPrivateLocatorUrl { get; set; } + public virtual string WamsAssetId { get; set; } + public virtual string WamsEncoderMetadataXml { get; set; } + public virtual string OriginalFileName { get; set; } + public virtual string LocalTempFileName { get; set; } + public virtual long? LocalTempFileSize { get; set; } + public virtual bool IncludeInPlayer { get; set; } + public virtual string MediaQuery { get; set; } + public virtual DateTime CreatedUtc { get; set; } + public virtual AssetUploadStatus UploadStatus { get; set; } + public virtual DateTime? UploadStartedUtc { get; set; } + public virtual DateTime? UploadCompletedUtc { get; set; } + public virtual long? UploadBytesComplete { get; set; } + public virtual AssetPublishStatus PublishStatus { get; set; } + public virtual DateTime? PublishedUtc { get; set; } + public virtual DateTime? RemovedUtc { get; set; } + + [StringLengthMax] + public virtual string Data { + get { return Infoset.Data; } + set { Infoset.Data = value; } + } + + public virtual Infoset Infoset { get; protected set; } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Records/JobRecord.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Records/JobRecord.cs new file mode 100644 index 000000000..cfaf8b071 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Records/JobRecord.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using Orchard.Azure.MediaServices.Models.Jobs; +using Orchard.Data.Conventions; + +namespace Orchard.Azure.MediaServices.Models.Records { + public class JobRecord { + + public JobRecord() { + Tasks = new List(); + } + public virtual int Id { get; set; } + public virtual int CloudVideoPartId { get; set; } + public virtual string WamsJobId { get; set; } + public virtual string Name { get; set; } + public virtual string Description { get; set; } + public virtual JobStatus Status { get; set; } + public virtual DateTime? CreatedUtc { get; set; } + public virtual DateTime? StartedUtc { get; set; } + public virtual DateTime? FinishedUtc { get; set; } + public virtual string ErrorMessage { get; set; } + public virtual string OutputAssetName { get; set; } + public virtual string OutputAssetDescription { get; set; } + + [CascadeAllDeleteOrphan] + public virtual IList Tasks { get; set; } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Records/TaskRecord.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Records/TaskRecord.cs new file mode 100644 index 000000000..dfd77355b --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Models/Records/TaskRecord.cs @@ -0,0 +1,20 @@ +using Orchard.Azure.MediaServices.Models.Jobs; +using Orchard.Data.Conventions; + +namespace Orchard.Azure.MediaServices.Models.Records { + public class TaskRecord { + public virtual int Id { get; set; } + public virtual JobRecord Job { get; set; } + public virtual string WamsTaskId { get; set; } + public virtual string TaskProviderName { get; set; } + public virtual int TaskIndex { get; set; } + public virtual JobStatus Status { get; set; } + public virtual int PercentComplete { get; set; } + + [StringLengthMax] + public virtual string SettingsXml { get; set; } + + public virtual string HarvestAssetType { get; set; } + public virtual string HarvestAssetName { get; set; } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Module.txt b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Module.txt new file mode 100644 index 000000000..53430db9c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Module.txt @@ -0,0 +1,13 @@ +Name: Windows Azure Media Services +AntiForgery: enabled +Author: Microsoft Open Technologies, Inc +Website: http://www.microsoft.com +Version: 1.0 +OrchardVersion: 1.7.2 +Description: Provides integration of Windows Azure Media Services functionality into Orchard. +Features: + Orchard.Azure.MediaServices: + Name: Windows Azure Media Services + Description: Provides integration of Windows Azure Media Services functionality into Orchard. + Category: Hosting + Dependencies: Orchard.MediaLibrary, Orchard.PublishLater, Orchard.TaskLease \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Navigation/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Navigation/AdminMenu.cs new file mode 100644 index 000000000..d91884ffc --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Navigation/AdminMenu.cs @@ -0,0 +1,24 @@ +using Orchard.Localization; +using Orchard.Security; +using Orchard.UI.Navigation; + +namespace Orchard.Azure.MediaServices.Navigation { + public class AdminMenu : INavigationProvider { + public Localizer T { get; set; } + public string MenuName { get { return "admin"; } } + + public void GetNavigation(NavigationBuilder builder) { + builder + .Add(T("Media"), "6", menu => menu + .Add(T("Windows Azure Media Jobs"), "10.0", item => item.Action("Index", "Job", new { area = "Orchard.Azure.MediaServices" }) + .Permission(Permissions.ManageCloudMediaJobs))); + + builder + .Add(T("Settings"), menu => menu + .Add(T("Windows Azure Media"), "10.0", item => item.Action("Index", "Settings", new { area = "Orchard.Azure.MediaServices" }) + .Permission(StandardPermissions.SiteOwner) + .Permission(Permissions.ManageCloudMediaSettings) + )); + } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Navigation/MediaMenu.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Navigation/MediaMenu.cs new file mode 100644 index 000000000..5d79dbe58 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Navigation/MediaMenu.cs @@ -0,0 +1,13 @@ +using Orchard; +using Orchard.UI.Navigation; + +namespace Orchard.Azure.MediaServices.Navigation { + public class MediaMenu : Component, INavigationProvider { + public string MenuName { get { return "mediaproviders"; } } + + public void GetNavigation(NavigationBuilder builder) { + builder.AddImageSet("cloudmedia").Add(T("Windows Azure Media"), "5", + menu => menu.Action("Import", "Media", new { area = "Orchard.Azure.MediaServices" }).Permission(Permissions.ManageCloudMediaContent)); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Orchard.Azure.MediaServices.csproj b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Orchard.Azure.MediaServices.csproj new file mode 100644 index 000000000..fe5ceb21f --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Orchard.Azure.MediaServices.csproj @@ -0,0 +1,614 @@ + + + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {14A96B1A-9DC9-44C8-A675-206329E15263} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Orchard.Azure.MediaServices + Orchard.Azure.MediaServices + v4.5 + false + + + 4.0 + + + false + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + false + + + pdbonly + true + bin\ + TRACE + prompt + 4 + AllRules.ruleset + false + + + + False + ..\..\..\..\lib\nhibernate\FluentNHibernate.dll + + + ..\..\..\..\lib\htmlagilitypack\HtmlAgilityPack.dll + + + + False + ..\..\..\..\lib\windowsazure\Microsoft.Data.Edm.dll + + + False + ..\..\..\..\lib\windowsazure\Microsoft.Data.OData.dll + + + False + ..\..\..\..\lib\windowsazure\Microsoft.Data.Services.Client.dll + + + False + Lib\Microsoft.Practices.TransientFaultHandling.Core.dll + + + False + ..\..\..\..\lib\windowsazure\Microsoft.WindowsAzure.Configuration.dll + + + False + ..\..\..\..\lib\windowsazure\Microsoft.WindowsAzure.Diagnostics.dll + + + False + Lib\Microsoft.WindowsAzure.MediaServices.Client.dll + + + False + ..\..\..\..\lib\windowsazure\Microsoft.WindowsAzure.Storage.dll + + + False + ..\..\..\..\lib\newtonsoft.json\Newtonsoft.Json.dll + True + + + False + ..\..\..\..\lib\nhibernate\NHibernate.dll + + + + + 3.5 + + + + False + ..\..\..\..\lib\windowsazure\System.Spatial.dll + + + + + + + False + ..\..\..\..\lib\aspnetmvc\System.Web.Helpers.dll + + + False + ..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll + + + + + False + ..\..\..\..\lib\aspnetmvc\System.Web.Razor.dll + + + + False + ..\..\..\..\lib\aspnetmvc\System.Web.WebPages.dll + + + False + ..\..\..\..\lib\aspnetmvc\System.Web.WebPages.Deployment.dll + + + False + ..\..\..\..\lib\aspnetmvc\System.Web.WebPages.Razor.dll + + + + + + + + + + + + + + + + + + + + + + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cloudmedia-admin-asset.less + + + cloudmedia-admin-asset.css + + + + + cloudmedia-admin-job.less + + + cloudmedia-admin-job.less + + + cloudmedia-admin-selecttask.less + + + cloudmedia-admin-selecttask.less + + + cloudmedia-admin-settings.less + + + cloudmedia-admin-settings.less + + + cloudmedia-edit-assets.less + + + cloudmedia-edit-assets.css + + + cloudmedia-edit-cloudvideopart.less + + + cloudmedia-edit-cloudvideopart.css + + + cloudmedia-edit-jobs.less + + + cloudmedia-edit-jobs.less + + + cloudmedia-progress.less + + + cloudmedia-progress.less + + + cloudmedia-tabs.less + + + cloudmedia-tabs.css + + + cloudmedia-videoplayer.less + + + cloudmedia-videoplayer.css + + + + + + + + + menu.cloudmedia-mediaproviders.less + + + + + menu.cloudmedia-mediaproviders.css + + + + + + + + + + + + + + + + + + + + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} + Orchard.Framework + + + {9916839C-39FC-4CEB-A5AF-89CA7E87119F} + Orchard.Core + + + {73a7688a-5bd3-4f7e-adfa-ce36c5a10e3b} + Orchard.MediaLibrary + + + {c889167c-e52c-4a65-a419-224b3d1b957d} + Orchard.PublishLater + + + {3f72a4e9-7b72-4260-b010-c16ec54f9baf} + Orchard.TaskLease + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + ES5 + true + True + + + ES5 + false + false + + + + + + + + $(ProjectDir)\..\Manifests + + + + + + + + + + + + False + True + 45979 + / + + + False + True + http://orchard.codeplex.com + False + + + + + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Permissions.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Permissions.cs new file mode 100644 index 000000000..347d18fc9 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Permissions.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using Orchard.Environment.Extensions.Models; +using Orchard.Security.Permissions; + +namespace Orchard.Azure.MediaServices { + public class Permissions : IPermissionProvider { + public static readonly Permission ManageCloudMediaContent = new Permission { Description = "Managing Windows Azure Media", Name = "ManageCloudMediaContent" }; + public static readonly Permission ManageCloudMediaJobs = new Permission { Description = "Managing Windows Azure Media Jobs", Name = "ManageCloudMediaJobs" }; + public static readonly Permission ManageCloudMediaSettings = new Permission { Description = "Managing Windows Azure Media Settings", Name = "ManageCloudMediaSettings" }; + + public virtual Feature Feature { get; set; } + + public IEnumerable GetPermissions() { + yield return ManageCloudMediaContent; + yield return ManageCloudMediaJobs; + yield return ManageCloudMediaSettings; + } + + public IEnumerable GetDefaultStereotypes() { + yield return new PermissionStereotype { + Name = "Administrator", + Permissions = GetPermissions().ToArray() + }; + yield return new PermissionStereotype { + Name = "Editor", + Permissions = new[] {ManageCloudMediaContent} + }; + yield return new PermissionStereotype { + Name = "Moderator", + }; + yield return new PermissionStereotype { + Name = "Author", + Permissions = new[] {ManageCloudMediaContent} + }; + yield return new PermissionStereotype { + Name = "Contributor", + }; + } + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Placement.info b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Placement.info new file mode 100644 index 000000000..19217cce3 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Placement.info @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Properties/AssemblyInfo.cs b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..dc234db85 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Orchard.Azure.MediaServices")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("Orchard")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: InternalsVisibleTo("Orchard.Azure.MediaServices.Tests")] // Not working. :( Someone please fix. + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1ea07ae9-58f9-4be6-a5a1-33d52b722cb1")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Readme.txt b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Readme.txt new file mode 100644 index 000000000..81766119e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Readme.txt @@ -0,0 +1,105 @@ +INSTALLING THE MODULE +********************* + +The module has been continuously built and tested against the latest version of Orchard in the 1.x branch of the official Orchard Codeplex repository. + +The module can be installed into an Orchard site in a few different ways: + +Use the full Orchard source code committed in the module's TFS repository +------------------------------------------------------------------------------ + +1. Clone the repository to the local developer machine. +2. Use Visual Studio to either run the site locally or publish Orchard through any of the standard publishing mechanisms. + +For convenience, a recipe named "Orchard.Azure.MediaServices" has been added to Orchard.Setup in this repository that makes it quicker to get a site up and running where the module is already enabled and configured for use. + +Add it to another copy of the Orchard source code for a "develop + publish" workflow +------------------------------------------------------------------------------------ + +1. Copy the whole module project folder into the Orchard.Web/Modules folder of the Orchard source code. +2. Add the Orchard.Azure.MediaServices and Orchard.Azure.MediaServices.Tests projects to the Orchard.sln solution. +3. Add a project reference from Orchard.Azure.Web to Orchard.Azure.MediaServices to ensure the module is included when publishing to a Windows Azure Cloud Service. +4. Make the necessary modifications to the Web.config files in Orchard.Web and Orchard.Azure.Web (see below for details). + +"XCopy" deploy it into an already published Orchard site +-------------------------------------------------------- + +When dynamic compilation is enabled: +1. If the deployed Orchard site is configured to use dynamic compilation, upload the module's folder without the bin and obj folders to the /Modules folder. + +When dynamic compilation is disabled: +1. If the deployed Orchard site is not configured to use dynamic compilation, upload the module's folder, including the bin folder, to the /Modules folder. To prevent unnecessary duplicate DLLs from being uploaded as part of the module's bin folder, skip this step and instead follow the steps described in the next section. + +When dynamic compilation is disabled and you don't want to upload duplicate DLLs: +1. To prevent unnecessary duplicate DLLs from being uploaded as part of the module's bin folder, build the solution using the ClickToBuild.cmd file that can be found in the root directory of the project. +2. When ClickToBuild.cmd is done, a number of new folders will have been created. Go to "\build\Stage\Modules", and upload the Orchard.Azure.MediaServices folder to the Modules folder of the installation on the hosting server. + + +CONFIGURING ORCHARD FOR THE MODULE +********************************** + +1. Add assembly binding redirects to Orchard +-------------------------------------------- + +The module includes Windows Azure Media Services SDK for .NET version 3.0.0.0 which is distributed via Nuget. This client SDK library in turn depends on specific versions of a number of other libraries, also distributed via Nuget. Orchard ships with newer versions of some of these assemblies, and in the Orchard process there can only be one version of any given assembly at a time. + +Therefore, for the module to work, a few assembly binding redirects need to be added to the main Orchard Web.config file. These redirects cause the Windows Azure Media Services SDK for .NET client library to bind to the newer versions of these dependencies, which are present in the Bin folder of the web application. Without these redirects, the assembly binding will fail and the module will not work. + +Specifically, the following redirects must be added to the the Web.config file in the Orchard.Web and Orchard.Azure.web projects: + + + + + + + + + + + + + + + + + + + + + + +2. Increase the maximum request content length for Orchard +---------------------------------------------------------- + +When uploading large media files using the module, the files are first uploaded to Orchard where they are kept in local file system storage temporarily, before being ingested into WAMS by a server-side background process. By default, Orchard is configured in its Web.config to allow file uploads of up to 64 MB. + +If the module will be used to ingest media files larger than 64 MB these values need to be increased, or the first upload step (from the user's/editor's local machine to Orchard temporary storage) will fail. + +Specifically, the following attributes need to be reconfigured with a value appropriate for the size of media files with which the module is intended to be used: + - system.web/httpRuntime/@maxRequestLength + - system.webServer/security/requestFiltering/requestLimits/@maxAllowedContentLength + +Both of these attributes must be configured with the same value, or whichever value is smaller will effectively impose a limit on the allowed media file upload size. + +3. Configuring the logging level for the module +----------------------------------------------- + +The module logs messages of different severity levels and verbosity through the standard Orchard logging pipeline. + +To ensure the most verbose logging output from the module, while limiting the verbosity level of other logging sources, a element can be added to the file Config/Log4net.config in the Orchard.Web and/or Orchard.Azure.Web projects: + + + + + + + + + +CONFIGURING THE MODULE +********************** + +1. Configuring the WAMS account credentials +------------------------------------------- + +After the module is installed and the feature "Windows Azure Media Services" is enabled, the WAMS account credentials must be configured in the "Windows Azure Media" settings section in the Orchard administration dashboard. \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/Web.config b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/Web.config new file mode 100644 index 000000000..cbd2cd8a2 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/Web.config @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-common.js b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-common.js new file mode 100644 index 000000000..126148af3 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-common.js @@ -0,0 +1,25 @@ +/// +var Orchard; +(function (Orchard) { + (function (Azure) { + (function (MediaServices) { + (function (Admin) { + (function (Common) { + $(function () { + $("form").on("click", "button[data-prompt], a[data-prompt]", function (e) { + var prompt = $(this).data("prompt"); + + if (!confirm(prompt)) + e.preventDefault(); + }); + }); + })(Admin.Common || (Admin.Common = {})); + var Common = Admin.Common; + })(MediaServices.Admin || (MediaServices.Admin = {})); + var Admin = MediaServices.Admin; + })(Azure.MediaServices || (Azure.MediaServices = {})); + var MediaServices = Azure.MediaServices; + })(Orchard.Azure || (Orchard.Azure = {})); + var Azure = Orchard.Azure; +})(Orchard || (Orchard = {})); +//# sourceMappingURL=cloudmedia-admin-common.js.map diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-common.js.map b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-common.js.map new file mode 100644 index 000000000..a27606c2e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-common.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cloudmedia-admin-common.js","sourceRoot":"","sources":["cloudmedia-admin-common.ts"],"names":["Orchard","Orchard.Azure","Orchard.Azure.MediaServices","Orchard.Azure.MediaServices.Admin","Orchard.Azure.MediaServices.Admin.Common"],"mappings":"AAAA,4CAA4C;AAE5C,IAAO,OAAO;AASb,CATD,UAAO,OAAO;KAAdA,UAAeA,KAAKA;SAApBC,UAAqBA,aAAaA;aAAlCC,UAAmCA,KAAKA;iBAAxCC,UAAyCA,MAAMA;oBAC3CC,CAACA,CAACA;wBACEA,CAACA,CAACA,MAAMA,CAACA,CAACA,EAAEA,CAACA,OAAOA,EAAEA,qCAAqCA,EAAEA,UAASA,CAACA;4BACnEA,IAAIA,MAAMA,GAAGA,CAACA,CAACA,IAAIA,CAACA,CAACA,IAAIA,CAACA,QAAQA,CAACA;;4BAEnCA,IAAIA,CAACA,OAAOA,CAACA,MAAMA,CAACA;gCAChBA,CAACA,CAACA,cAAcA,CAACA,CAACA,CAACA;wBAC3BA,CAACA,CAACA;oBACNA,CAACA,CAACA;gBACNA,CAACA,uCAAAD;0CAAAA;YAADA,CAACA,qDAAAD;4CAAAA;QAADA,CAACA,qDAAAD;gDAAAA;IAADA,CAACA,yCAAAD;8BAAAA;AAADA,CAACA,6BAAA"} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-common.ts b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-common.ts new file mode 100644 index 000000000..ae8776cd1 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-common.ts @@ -0,0 +1,12 @@ +/// + +module Orchard.Azure.MediaServices.Admin.Common { + $(() => { + $("form").on("click", "button[data-prompt], a[data-prompt]", function(e) { + var prompt = $(this).data("prompt"); + + if (!confirm(prompt)) + e.preventDefault(); + }); + }); +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-job.js b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-job.js new file mode 100644 index 000000000..6f72d8d64 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-job.js @@ -0,0 +1,2 @@ +/// +//# sourceMappingURL=cloudmedia-admin-job.js.map diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-job.js.map b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-job.js.map new file mode 100644 index 000000000..733bb6cc8 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-job.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cloudmedia-admin-job.js","sourceRoot":"","sources":["cloudmedia-admin-job.ts"],"names":[],"mappings":"AAAA,4CAA4C"} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-job.ts b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-job.ts new file mode 100644 index 000000000..ec6247a60 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-job.ts @@ -0,0 +1 @@ +/// diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-settings.js b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-settings.js new file mode 100644 index 000000000..057ae4770 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-settings.js @@ -0,0 +1,82 @@ +/// +/// + +var Orchard; +(function (Orchard) { + (function (Azure) { + (function (MediaServices) { + (function (Admin) { + (function (Settings) { + var StringItem = (function () { + function StringItem(value) { + this.value = ko.observable(value); + } + return StringItem; + })(); + Settings.StringItem = StringItem; + + Settings.clientViewModel = { + wamsEncodingPresets: ko.observableArray(), + defaultWamsEncodingPresetIndex: ko.observable(), + subtitleLanguages: ko.observableArray() + }; + + function deleteWamsEncodingPreset(preset) { + var removedIndex = Settings.clientViewModel.wamsEncodingPresets.indexOf(preset); + Settings.clientViewModel.wamsEncodingPresets.remove(preset); + if (removedIndex === Settings.clientViewModel.defaultWamsEncodingPresetIndex()) + Settings.clientViewModel.defaultWamsEncodingPresetIndex(0); + else if (removedIndex < Settings.clientViewModel.defaultWamsEncodingPresetIndex()) + Settings.clientViewModel.defaultWamsEncodingPresetIndex(Settings.clientViewModel.defaultWamsEncodingPresetIndex() - 1); + } + Settings.deleteWamsEncodingPreset = deleteWamsEncodingPreset; + + function addNewWamsEncodingPreset() { + Settings.clientViewModel.wamsEncodingPresets.push(new StringItem("Unnamed")); + $("#presets-table tbody:first-of-type tr:last-of-type td:nth-child(2) input").focus().select(); + } + Settings.addNewWamsEncodingPreset = addNewWamsEncodingPreset; + + function deleteSubtitleLanguage(languageCultureCode) { + Settings.clientViewModel.subtitleLanguages.remove(languageCultureCode); + } + Settings.deleteSubtitleLanguage = deleteSubtitleLanguage; + + function addNewSubtitleLanguage() { + Settings.clientViewModel.subtitleLanguages.push(new StringItem("Unnamed")); + $("#languages-table tbody:first-of-type tr:last-of-type td:nth-child(1) input").focus().select(); + } + Settings.addNewSubtitleLanguage = addNewSubtitleLanguage; + + $(function () { + $.each(initWamsEncodingPresets, function (presetIndex, presetName) { + Settings.clientViewModel.wamsEncodingPresets.push(new StringItem(presetName)); + }); + + Settings.clientViewModel.defaultWamsEncodingPresetIndex(initDefaultWamsEncodingPresetIndex); + + $.each(initSubtitleLanguages, function (languageIndex, languageCultureCode) { + Settings.clientViewModel.subtitleLanguages.push(new StringItem(languageCultureCode)); + }); + + ko.applyBindings(Settings.clientViewModel); + + var localStorage = window["localStorage"]; + $("#tabs").tabs({ + activate: function () { + if (localStorage && localStorage.setItem) + localStorage.setItem("selectedCloudMediaSettingsTab", $("#tabs").tabs("option", "active")); + }, + active: localStorage && localStorage.getItem ? localStorage.getItem("selectedCloudMediaSettingsTab") : null + }).show(); + }); + })(Admin.Settings || (Admin.Settings = {})); + var Settings = Admin.Settings; + })(MediaServices.Admin || (MediaServices.Admin = {})); + var Admin = MediaServices.Admin; + })(Azure.MediaServices || (Azure.MediaServices = {})); + var MediaServices = Azure.MediaServices; + })(Orchard.Azure || (Orchard.Azure = {})); + var Azure = Orchard.Azure; +})(Orchard || (Orchard = {})); +//# sourceMappingURL=cloudmedia-admin-settings.js.map diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-settings.js.map b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-settings.js.map new file mode 100644 index 000000000..b425dfe3b --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-settings.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cloudmedia-admin-settings.js","sourceRoot":"","sources":["cloudmedia-admin-settings.ts"],"names":["Orchard","Orchard.Azure","Orchard.Azure.MediaServices","Orchard.Azure.MediaServices.Admin","Orchard.Azure.MediaServices.Admin.Settings","Orchard.Azure.MediaServices.Admin.Settings.StringItem","Orchard.Azure.MediaServices.Admin.Settings.StringItem.constructor","Orchard.Azure.MediaServices.Admin.Settings.deleteWamsEncodingPreset","Orchard.Azure.MediaServices.Admin.Settings.addNewWamsEncodingPreset","Orchard.Azure.MediaServices.Admin.Settings.deleteSubtitleLanguage","Orchard.Azure.MediaServices.Admin.Settings.addNewSubtitleLanguage"],"mappings":"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"} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-settings.ts b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-settings.ts new file mode 100644 index 000000000..bc55ae69c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-admin-settings.ts @@ -0,0 +1,85 @@ +/// +/// + +declare var initWamsEncodingPresets: string[]; +declare var initDefaultWamsEncodingPresetIndex: number; +declare var initSubtitleLanguages: string[]; + +module Orchard.Azure.MediaServices.Admin.Settings +{ + export class StringItem + { + constructor(value: string) + { + this.value = ko.observable(value); + } + + public value: KnockoutObservable; + } + + export interface IClientViewModel + { + wamsEncodingPresets: KnockoutObservableArray; + defaultWamsEncodingPresetIndex: KnockoutObservable; + subtitleLanguages: KnockoutObservableArray; + } + + export var clientViewModel: IClientViewModel = { + wamsEncodingPresets: ko.observableArray(), + defaultWamsEncodingPresetIndex: ko.observable(), + subtitleLanguages: ko.observableArray() + }; + + export function deleteWamsEncodingPreset(preset: StringItem) + { + var removedIndex = clientViewModel.wamsEncodingPresets.indexOf(preset); + clientViewModel.wamsEncodingPresets.remove(preset); + if (removedIndex === clientViewModel.defaultWamsEncodingPresetIndex()) + clientViewModel.defaultWamsEncodingPresetIndex(0); + else if (removedIndex < clientViewModel.defaultWamsEncodingPresetIndex()) + clientViewModel.defaultWamsEncodingPresetIndex(clientViewModel.defaultWamsEncodingPresetIndex() - 1); + } + + export function addNewWamsEncodingPreset() + { + clientViewModel.wamsEncodingPresets.push(new StringItem("Unnamed")); + $("#presets-table tbody:first-of-type tr:last-of-type td:nth-child(2) input").focus().select(); + } + + export function deleteSubtitleLanguage(languageCultureCode: StringItem) + { + clientViewModel.subtitleLanguages.remove(languageCultureCode); + } + + export function addNewSubtitleLanguage() + { + clientViewModel.subtitleLanguages.push(new StringItem("Unnamed")); + $("#languages-table tbody:first-of-type tr:last-of-type td:nth-child(1) input").focus().select(); + } + + $(function () + { + $.each(initWamsEncodingPresets, function (presetIndex: number, presetName: string) + { + clientViewModel.wamsEncodingPresets.push(new StringItem(presetName)); + }); + + clientViewModel.defaultWamsEncodingPresetIndex(initDefaultWamsEncodingPresetIndex); + + $.each(initSubtitleLanguages, function (languageIndex: number, languageCultureCode: string) + { + clientViewModel.subtitleLanguages.push(new StringItem(languageCultureCode)); + }); + + ko.applyBindings(clientViewModel); + + var localStorage = window["localStorage"]; + $("#tabs").tabs({ + activate: function () { + if (localStorage && localStorage.setItem) + localStorage.setItem("selectedCloudMediaSettingsTab", $("#tabs").tabs("option", "active")); + }, + active: localStorage && localStorage.getItem ? localStorage.getItem("selectedCloudMediaSettingsTab") : null + }).show(); + }); +} diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-autorefresh.js b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-autorefresh.js new file mode 100644 index 000000000..13b21841a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-autorefresh.js @@ -0,0 +1,34 @@ +/// +var Orchard; +(function (Orchard) { + (function (Azure) { + (function (MediaServices) { + (function (AutoRefresh) { + // Periodically refresh elements. + $(function () { + $("[data-refresh-url]").each(function () { + var self = $(this); + var update = function () { + var container = self; + var url = container.data("refresh-url"); + + $.ajax({ + url: url, + cache: false + }).then(function (html) { + container.html(html); + setTimeout(update, 5000); + }); + }; + + setTimeout(update, 5000); + }); + }); + })(MediaServices.AutoRefresh || (MediaServices.AutoRefresh = {})); + var AutoRefresh = MediaServices.AutoRefresh; + })(Azure.MediaServices || (Azure.MediaServices = {})); + var MediaServices = Azure.MediaServices; + })(Orchard.Azure || (Orchard.Azure = {})); + var Azure = Orchard.Azure; +})(Orchard || (Orchard = {})); +//# sourceMappingURL=cloudmedia-autorefresh.js.map diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-autorefresh.js.map b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-autorefresh.js.map new file mode 100644 index 000000000..1cdf34cc8 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-autorefresh.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cloudmedia-autorefresh.js","sourceRoot":"","sources":["cloudmedia-autorefresh.ts"],"names":["Orchard","Orchard.Azure","Orchard.Azure.MediaServices","Orchard.Azure.MediaServices.AutoRefresh"],"mappings":"AAAA,4CAA4C;AAE5C,IAAO,OAAO;AAqBb,CArBD,UAAO,OAAO;KAAdA,UAAeA,KAAKA;SAApBC,UAAqBA,aAAaA;aAAlCC,UAAmCA,WAAWA;gBAC1CC,iCAAiCA;gBACjCA,CAACA,CAACA;oBACEA,CAACA,CAACA,oBAAoBA,CAACA,CAACA,IAAIA,CAACA;wBACzBA,IAAIA,IAAIA,GAAGA,CAACA,CAACA,IAAIA,CAACA;wBAClBA,IAAIA,MAAMA,GAAGA;4BACTA,IAAIA,SAASA,GAAGA,IAAIA;4BACpBA,IAAIA,GAAGA,GAAGA,SAASA,CAACA,IAAIA,CAACA,aAAaA,CAACA;;4BAEvCA,CAACA,CAACA,IAAIA,CAACA;gCACHA,GAAGA,EAAEA,GAAGA;gCACRA,KAAKA,EAAEA,KAAKA;6BACfA,CAACA,CAACA,IAAIA,CAACA,UAAAA,IAAIA;gCACRA,SAASA,CAACA,IAAIA,CAACA,IAAIA,CAACA;gCACpBA,UAAUA,CAACA,MAAMA,EAAEA,IAAIA,CAACA;4BAC5BA,CAACA,CAACA;wBACNA,CAACA;;wBAEDA,UAAUA,CAACA,MAAMA,EAAEA,IAAIA,CAACA;oBAC5BA,CAACA,CAACA;gBACNA,CAACA,CAACA;YACNA,CAACA,iEAAAD;wDAAAA;QAADA,CAACA,qDAAAD;gDAAAA;IAADA,CAACA,yCAAAD;8BAAAA;AAADA,CAACA,6BAAA"} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-autorefresh.ts b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-autorefresh.ts new file mode 100644 index 000000000..5e25b2019 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-autorefresh.ts @@ -0,0 +1,24 @@ +/// + +module Orchard.Azure.MediaServices.AutoRefresh { + // Periodically refresh elements. + $(() => { + $("[data-refresh-url]").each(function () { + var self = $(this); + var update = () => { + var container = self; + var url = container.data("refresh-url"); + + $.ajax({ + url: url, + cache: false + }).then(html => { + container.html(html); + setTimeout(update, 5000); + }); + }; + + setTimeout(update, 5000); + }); + }); +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset-video.js b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset-video.js new file mode 100644 index 000000000..0fbdd38e3 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset-video.js @@ -0,0 +1,45 @@ +/// +/// +var Orchard; +(function (Orchard) { + (function (Azure) { + (function (MediaServices) { + (function (AssetEdit) { + (function (Video) { + $(function () { + var treeView = $("#asset-files-treeview"); + + treeView.jstree({ + "core": { + "animation": 0, + "check_callback": true, + "themes": { "stripes": true } + }, + "plugins": ["state", "wholerow"] + }); + + $(".expand-all").on("click", function (e) { + treeView.jstree('open_all'); + }); + + $(".collapse-all").on("click", function (e) { + treeView.jstree('close_all'); + }); + // TODO: Make links work (Private/Public URLS). + //treeView.on("select_node.jstree", function (e, data) { + // var url = data.node.a_attr.href; + // if (url != "#") { + // window.location.href = url; + // } + //}); + }); + })(AssetEdit.Video || (AssetEdit.Video = {})); + var Video = AssetEdit.Video; + })(MediaServices.AssetEdit || (MediaServices.AssetEdit = {})); + var AssetEdit = MediaServices.AssetEdit; + })(Azure.MediaServices || (Azure.MediaServices = {})); + var MediaServices = Azure.MediaServices; + })(Orchard.Azure || (Orchard.Azure = {})); + var Azure = Orchard.Azure; +})(Orchard || (Orchard = {})); +//# sourceMappingURL=cloudmedia-edit-asset-video.js.map diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset-video.js.map b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset-video.js.map new file mode 100644 index 000000000..f2bc91f86 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset-video.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cloudmedia-edit-asset-video.js","sourceRoot":"","sources":["cloudmedia-edit-asset-video.ts"],"names":["Orchard","Orchard.Azure","Orchard.Azure.MediaServices","Orchard.Azure.MediaServices.AssetEdit","Orchard.Azure.MediaServices.AssetEdit.Video"],"mappings":"AAAA,4CAA4C;AAC5C,8CAA8C;AAE9C,IAAO,OAAO;AA6Bb,CA7BD,UAAO,OAAO;KAAdA,UAAeA,KAAKA;SAApBC,UAAqBA,aAAaA;aAAlCC,UAAmCA,SAASA;iBAA5CC,UAA6CA,KAAKA;oBAC9CC,CAACA,CAACA;wBACEA,IAAIA,QAAQA,GAAQA,CAACA,CAACA,uBAAuBA,CAACA;;wBAE9CA,QAAQA,CAACA,MAAMA,CAACA;4BACZA,MAAMA,EAAEA;gCACJA,WAAWA,EAAEA,CAACA;gCACdA,gBAAgBA,EAAEA,IAAIA;gCACtBA,QAAQA,EAAEA,EAAEA,SAASA,EAAEA,IAAIA,EAAEA;6BAChCA;4BACDA,SAASA,EAAEA,CAACA,OAAOA,EAAEA,UAAUA,CAACA;yBACnCA,CAACA;;wBAEFA,CAACA,CAACA,aAAaA,CAACA,CAACA,EAAEA,CAACA,OAAOA,EAAEA,UAASA,CAACA;4BACnCA,QAAQA,CAACA,MAAMA,CAACA,UAAUA,CAACA;wBAC/BA,CAACA,CAACA;;wBAEFA,CAACA,CAACA,eAAeA,CAACA,CAACA,EAAEA,CAACA,OAAOA,EAAEA,UAAUA,CAACA;4BACtCA,QAAQA,CAACA,MAAMA,CAACA,WAAWA,CAACA;wBAChCA,CAACA,CAACA;wBAEFA,+CAA+CA;wBAC/CA,wDAAwDA;wBACxDA,sCAAsCA;wBACtCA,uBAAuBA;wBACvBA,qCAAqCA;wBACrCA,OAAOA;wBACPA,KAAKA;oBACTA,CAACA,CAACA;gBACNA,CAACA,6CAAAD;4CAAAA;YAADA,CAACA,6DAAAD;oDAAAA;QAADA,CAACA,qDAAAD;gDAAAA;IAADA,CAACA,yCAAAD;8BAAAA;AAADA,CAACA,6BAAA"} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset-video.ts b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset-video.ts new file mode 100644 index 000000000..aec1253e3 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset-video.ts @@ -0,0 +1,33 @@ +/// +/// + +module Orchard.Azure.MediaServices.AssetEdit.Video { + $(function () { + var treeView: any = $("#asset-files-treeview"); + + treeView.jstree({ + "core": { + "animation": 0, + "check_callback": true, + "themes": { "stripes": true }, + }, + "plugins": ["state", "wholerow"] + }); + + $(".expand-all").on("click", function(e) { + treeView.jstree('open_all'); + }); + + $(".collapse-all").on("click", function (e) { + treeView.jstree('close_all'); + }); + + // TODO: Make links work (Private/Public URLS). + //treeView.on("select_node.jstree", function (e, data) { + // var url = data.node.a_attr.href; + // if (url != "#") { + // window.location.href = url; + // } + //}); + }); +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset.js b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset.js new file mode 100644 index 000000000..df5048d7c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset.js @@ -0,0 +1,25 @@ +/// +/// +var Orchard; +(function (Orchard) { + (function (Azure) { + (function (MediaServices) { + (function (AssetEdit) { + $(function () { + var localStorage = window["localStorage"]; + $("#tabs").tabs({ + activate: function () { + if (localStorage && localStorage.setItem) + localStorage.setItem("selectedAssetTab", $("#tabs").tabs("option", "active")); + }, + active: localStorage && localStorage.getItem ? localStorage.getItem("selectedAssetTab") : null + }).show(); + }); + })(MediaServices.AssetEdit || (MediaServices.AssetEdit = {})); + var AssetEdit = MediaServices.AssetEdit; + })(Azure.MediaServices || (Azure.MediaServices = {})); + var MediaServices = Azure.MediaServices; + })(Orchard.Azure || (Orchard.Azure = {})); + var Azure = Orchard.Azure; +})(Orchard || (Orchard = {})); +//# sourceMappingURL=cloudmedia-edit-asset.js.map diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset.js.map b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset.js.map new file mode 100644 index 000000000..0536d6e5d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cloudmedia-edit-asset.js","sourceRoot":"","sources":["cloudmedia-edit-asset.ts"],"names":["Orchard","Orchard.Azure","Orchard.Azure.MediaServices","Orchard.Azure.MediaServices.AssetEdit"],"mappings":"AAAA,4CAA4C;AAC5C,8CAA8C;AAE9C,IAAO,OAAO;AAWb,CAXD,UAAO,OAAO;KAAdA,UAAeA,KAAKA;SAApBC,UAAqBA,aAAaA;aAAlCC,UAAmCA,SAASA;gBACxCC,CAACA,CAACA;oBACEA,IAAIA,YAAYA,GAAGA,MAAMA,CAACA,cAAcA,CAACA;oBACzCA,CAACA,CAACA,OAAOA,CAACA,CAACA,IAAIA,CAACA;wBACZA,QAAQA,EAAEA;4BACNA,IAAIA,YAAYA,IAAIA,YAAYA,CAACA,OAAOA;gCACpCA,YAAYA,CAACA,OAAOA,CAACA,kBAAkBA,EAAEA,CAACA,CAACA,OAAOA,CAACA,CAACA,IAAIA,CAACA,QAAQA,EAAEA,QAAQA,CAACA,CAACA,CAACA;wBACtFA,CAACA;wBACDA,MAAMA,EAAEA,YAAYA,IAAIA,YAAYA,CAACA,OAAOA,GAAGA,YAAYA,CAACA,OAAOA,CAACA,kBAAkBA,CAACA,GAAGA,IAAIA;qBACjGA,CAACA,CAACA,IAAIA,CAACA,CAACA;gBACbA,CAACA,CAACA;YACNA,CAACA,6DAAAD;oDAAAA;QAADA,CAACA,qDAAAD;gDAAAA;IAADA,CAACA,yCAAAD;8BAAAA;AAADA,CAACA,6BAAA"} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset.ts b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset.ts new file mode 100644 index 000000000..db145db6d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-asset.ts @@ -0,0 +1,15 @@ +/// +/// + +module Orchard.Azure.MediaServices.AssetEdit { + $(function() { + var localStorage = window["localStorage"]; + $("#tabs").tabs({ + activate: function () { + if (localStorage && localStorage.setItem) + localStorage.setItem("selectedAssetTab", $("#tabs").tabs("option", "active")); + }, + active: localStorage && localStorage.getItem ? localStorage.getItem("selectedAssetTab") : null + }).show(); + }); +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-direct.js b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-direct.js new file mode 100644 index 000000000..7be6a66d1 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-direct.js @@ -0,0 +1,313 @@ +/// +/// +/// +var Orchard; +(function (Orchard) { + (function (Azure) { + (function (MediaServices) { + (function (CloudVideoEdit) { + var requiredUploads; + + function uploadCompleted(sender, e, data) { + var scope = $(sender).closest(".async-upload"); + var status = data.errorThrown && data.errorThrown.length > 0 ? data.errorThrown : data.textStatus; + scope.find(".progress-bar").hide(); + scope.find(".progress-text").hide(); + scope.find(".progress-details").hide(); + scope.find(".status.preparing").hide(); + scope.find(".status.uploading").hide(); + + switch (status) { + case "error": + cleanup(scope, data); + alert("The upload of the selected file failed. You may try again after the cleanup has finished."); + return; + case "abort": + cleanup(scope, data); + return; + } + + var originalFileName = data.files[0].name; + var statusUploaded = scope.find(".status.uploaded").show(); + + statusUploaded.text(statusUploaded.data("text-template").replace("{filename}", originalFileName)); + scope.data("upload-isactive", false); + scope.data("upload-iscompleted", true); + scope.data("upload-start-time", null); + } + + function cleanup(scope, data) { + var wamsAssetInput = scope.find("input[name$='.WamsAssetId']"); + var fileNameInput = scope.find("input[name$='.FileName']"); + var assetId = $.trim(wamsAssetInput.val()); + var wrapper = data.fileInput.closest(".file-upload-wrapper"); + + if (assetId.length > 0) { + var url = scope.data("delete-asset-url"); + var antiForgeryToken = scope.closest("form").find("[name='__RequestVerificationToken']").val(); + var cleanupMessage = scope.find(".status.cleanup"); + + wamsAssetInput.val(""); + fileNameInput.val(""); + + cleanupMessage.show(); + + $.ajax({ + url: url, + type: "DELETE", + data: { + id: assetId, + __RequestVerificationToken: antiForgeryToken + } + }).done(function () { + scope.data("upload-isactive", false); + scope.data("upload-start-time", null); + scope.find(".file-upload-wrapper").show(); + cleanupMessage.hide(); + }).fail(function () { + alert("An error occurred on the server while trying to clean up."); + }); + } + + wrapper.show(); + } + + function pad(value, length) { + var str = value.toString(); + while (str.length < length) { + str = "0" + str; + } + return str; + } + + function createBlockId(blockIndex) { + var blockIdPrefix = "block-"; + return btoa(blockIdPrefix + pad(blockIndex, 6)); + } + + function commitBlockList(scope, data) { + var deferred = $.Deferred(); + var blockIds = scope.data("block-ids"); + + if (blockIds.length == 0) { + // The file was uploaded as a whole, so no manifest to submit. + deferred.resolve(); + } else { + // The file was uploaded in chunks. + var url = scope.data("sas-locator") + "&comp=blocklist"; + var requestData = ''; + for (var i = 0; i < blockIds.length; i++) { + requestData += '' + blockIds[i] + ''; + } + requestData += ''; + + $.ajax({ + url: url, + type: "PUT", + data: requestData, + contentType: "text/plain; charset=UTF-8", + crossDomain: true, + cache: false, + beforeSend: function (xhr) { + xhr.setRequestHeader('x-ms-date', new Date().toUTCString()); + xhr.setRequestHeader('x-ms-blob-content-type', data.files[0].type); + xhr.setRequestHeader('x-ms-version', "2012-02-12"); + xhr.setRequestHeader('Content-Length', requestData.length.toString()); + }, + success: function () { + deferred.resolve(data); + }, + error: function (xhr, status, error) { + data.textStatus = status; + data.errorThrown = error; + deferred.fail(data); + } + }); + } + + return deferred.promise(); + } + + function hasActiveUploads() { + var scope = $(".upload-direct"); + var flag = false; + + scope.find(".async-upload").each(function () { + if ($(this).data("upload-isactive") == true) { + flag = true; + return false; + } + }); + + return flag; + } + + function hasCompletedUploads() { + var scope = $(".upload-direct"); + var flag = false; + + scope.find(".async-upload").each(function () { + if ($(this).data("upload-iscompleted") == true) { + flag = true; + return false; + } + }); + + return flag; + } + + var isSubmitting = function () { + var scope = $(".upload-direct"); + return scope.data("is-submitting") == true; + }; + + function initializeUpload(fileInput) { + var scope = fileInput.closest(".async-upload"); + var acceptFileTypes = scope.data("upload-accept-file-types"); + var antiForgeryToken = scope.closest("form").find("[name='__RequestVerificationToken']").val(); + var preparingText = scope.find(".status.preparing"); + var uploadingContainer = scope.find(".status.uploading"); + var progressText = scope.find(".progress-text"); + var progressDetails = scope.find(".progress-details"); + var cancelLink = scope.find(".cancel-link"); + + fileInput.fileupload({ + autoUpload: false, + acceptFileTypes: new RegExp(acceptFileTypes, "i"), + type: "PUT", + maxChunkSize: 4 * 1024 * 1024, + beforeSend: function (xhr, data) { + xhr.setRequestHeader("x-ms-date", new Date().toUTCString()); + xhr.setRequestHeader("x-ms-blob-type", "BlockBlob"); + xhr.setRequestHeader("content-length", data.data.size.toString()); + }, + chunksend: function (e, data) { + var blockIndex = scope.data("block-index"); + var blockIds = scope.data("block-ids"); + var blockId = createBlockId(blockIndex); + var url = scope.data("sas-locator") + "&comp=block&blockid=" + blockId; + + data.url = url; + blockIds.push(blockId); + scope.data("block-index", blockIndex + 1); + }, + progressall: function (e, data) { + var percentComplete = Math.floor((data.loaded / data.total) * 100); + var startTime = new Date(scope.data("upload-start-time")); + var elapsedMilliseconds = new Date(Date.now()).getTime() - startTime.getTime(); + var elapsed = moment.duration(elapsedMilliseconds, "ms"); + var remaining = moment.duration(elapsedMilliseconds / Math.max(data.loaded, 1) * (data.total - data.loaded), "ms"); + var kbps = Math.floor(data.bitrate / 8 / 1000); + var uploaded = Math.floor(data.loaded / 1000); + var total = Math.floor(data.total / 1000); + + scope.find(".progress-bar").show().find('.progress').css('width', percentComplete + '%'); + progressText.text(progressText.data("text-template").replace("{percentage}", percentComplete)).show(); + progressDetails.text(progressDetails.data("text-template").replace("{uploaded}", uploaded).replace("{total}", total).replace("{kbps}", kbps).replace("{elapsed}", elapsed.humanize()).replace("{remaining}", remaining.humanize())).show(); + }, + done: function (e, data) { + var self = this; + commitBlockList(scope, data).always(function () { + uploadCompleted(self, e, data); + }); + }, + fail: function (e, data) { + uploadCompleted(this, e, data); + }, + processdone: function (e, data) { + scope.find(".validation-text").hide(); + + var filename = data.files[0].name; + scope.data("upload-isactive", true); + scope.data("upload-start-time", Date.now()); + var generateAssetUrl = scope.data("generate-asset-url"); + preparingText.show(); + scope.find(".file-upload-wrapper").hide(); + scope.data("block-index", 0); + scope.data("block-ids", new Array()); + + $.ajax({ + url: generateAssetUrl, + data: { + filename: filename, + __RequestVerificationToken: antiForgeryToken + }, + type: "POST" + }).done(function (asset) { + data.url = asset.sasLocator; + data.multipart = false; + + scope.data("sas-locator", asset.sasLocator); + scope.find("input[name$='.FileName']").val(filename); + scope.find("input[name$='.WamsAssetId']").val(asset.assetId); + + preparingText.hide(); + progressText.text(progressText.data("text-template").replace("{percentage}", 0)).show(); + uploadingContainer.show(); + + var xhr = data.submit(); + scope.data("xhr", xhr); + }).fail(function (xhr, status, error) { + preparingText.hide(); + uploadingContainer.hide(); + scope.data("upload-isactive", false); + scope.data("upload-start-time", null); + scope.find(".file-upload-wrapper").show(); + alert("An error occurred. Error: " + error); + }); + }, + processfail: function (e, data) { + scope.find(".validation-text").show(); + }, + change: function (e, data) { + var prompt = fileInput.data("prompt"); + if (prompt && prompt.length > 0) { + if (!confirm(prompt)) { + e.preventDefault(); + } + } + } + }); + + cancelLink.on("click", function (e) { + e.preventDefault(); + + if (confirm($(this).data("prompt"))) { + var xhr = scope.data("xhr"); + xhr.abort(); + } + }); + } + + function initializeUploadDirect() { + var scope = $(".upload-direct").show(); + requiredUploads = scope.find(".required-upload"); + + scope.find(".async-upload-input").each(function () { + initializeUpload($(this)); + }); + + scope.closest("form").on("submit", function (e) { + if (hasActiveUploads()) { + alert(scope.data("block-submit-prompt")); + e.preventDefault(); + return false; + } + + scope.data("is-submitting", true); + }); + + window.onbeforeunload = function (e) { + if ((hasActiveUploads() || hasCompletedUploads()) && !isSubmitting()) + e.returnValue = scope.data("navigate-away-prompt"); + }; + } + CloudVideoEdit.initializeUploadDirect = initializeUploadDirect; + })(MediaServices.CloudVideoEdit || (MediaServices.CloudVideoEdit = {})); + var CloudVideoEdit = MediaServices.CloudVideoEdit; + })(Azure.MediaServices || (Azure.MediaServices = {})); + var MediaServices = Azure.MediaServices; + })(Orchard.Azure || (Orchard.Azure = {})); + var Azure = Orchard.Azure; +})(Orchard || (Orchard = {})); +//# sourceMappingURL=cloudmedia-edit-cloudvideopart-direct.js.map diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-direct.js.map b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-direct.js.map new file mode 100644 index 000000000..9b43c3267 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-direct.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cloudmedia-edit-cloudvideopart-direct.js","sourceRoot":"","sources":["cloudmedia-edit-cloudvideopart-direct.ts"],"names":["Orchard","Orchard.Azure","Orchard.Azure.MediaServices","Orchard.Azure.MediaServices.CloudVideoEdit","Orchard.Azure.MediaServices.CloudVideoEdit.uploadCompleted","Orchard.Azure.MediaServices.CloudVideoEdit.cleanup","Orchard.Azure.MediaServices.CloudVideoEdit.pad","Orchard.Azure.MediaServices.CloudVideoEdit.createBlockId","Orchard.Azure.MediaServices.CloudVideoEdit.commitBlockList","Orchard.Azure.MediaServices.CloudVideoEdit.hasActiveUploads","Orchard.Azure.MediaServices.CloudVideoEdit.hasCompletedUploads","Orchard.Azure.MediaServices.CloudVideoEdit.initializeUpload","Orchard.Azure.MediaServices.CloudVideoEdit.initializeUploadDirect"],"mappings":"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"} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-direct.ts b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-direct.ts new file mode 100644 index 000000000..5f2666358 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-direct.ts @@ -0,0 +1,302 @@ +/// +/// +/// + +module Orchard.Azure.MediaServices.CloudVideoEdit { + var requiredUploads: JQuery; + + function uploadCompleted(sender, e, data) { + var scope = $(sender).closest(".async-upload"); + var status = data.errorThrown && data.errorThrown.length > 0 ? data.errorThrown : data.textStatus; + scope.find(".progress-bar").hide(); + scope.find(".progress-text").hide(); + scope.find(".progress-details").hide(); + scope.find(".status.preparing").hide(); + scope.find(".status.uploading").hide(); + + switch (status) { + case "error": + cleanup(scope, data); + alert("The upload of the selected file failed. You may try again after the cleanup has finished."); + return; + case "abort": + cleanup(scope, data); + return; + } + + var originalFileName = data.files[0].name; + var statusUploaded = scope.find(".status.uploaded").show(); + + statusUploaded.text(statusUploaded.data("text-template").replace("{filename}", originalFileName)); + scope.data("upload-isactive", false); + scope.data("upload-iscompleted", true); + scope.data("upload-start-time", null); + } + + function cleanup(scope: JQuery, data) { + var wamsAssetInput = scope.find("input[name$='.WamsAssetId']"); + var fileNameInput = scope.find("input[name$='.FileName']"); + var assetId = $.trim(wamsAssetInput.val()); + var wrapper = data.fileInput.closest(".file-upload-wrapper"); + + if (assetId.length > 0) { + var url = scope.data("delete-asset-url"); + var antiForgeryToken = scope.closest("form").find("[name='__RequestVerificationToken']").val(); + var cleanupMessage = scope.find(".status.cleanup"); + + wamsAssetInput.val(""); + fileNameInput.val(""); + + cleanupMessage.show(); + + $.ajax({ + url: url, + type: "DELETE", + data: { + id: assetId, + __RequestVerificationToken: antiForgeryToken + } + }).done(function () { + scope.data("upload-isactive", false); + scope.data("upload-start-time", null); + scope.find(".file-upload-wrapper").show(); + cleanupMessage.hide(); + }).fail(function () { + alert("An error occurred on the server while trying to clean up."); + }); + } + + wrapper.show(); + } + + function pad(value: number, length: number) { + var str = value.toString(); + while (str.length < length) { + str = "0" + str; + } + return str; + } + + function createBlockId(blockIndex: number) { + var blockIdPrefix = "block-"; + return btoa(blockIdPrefix + pad(blockIndex, 6)); + } + + function commitBlockList(scope: JQuery, data) { + var deferred = $.Deferred(); + var blockIds = scope.data("block-ids"); + + if (blockIds.length == 0) { + // The file was uploaded as a whole, so no manifest to submit. + deferred.resolve(); + } else { + // The file was uploaded in chunks. + var url = scope.data("sas-locator") + "&comp=blocklist"; + var requestData = ''; + for (var i = 0; i < blockIds.length; i++) { + requestData += '' + blockIds[i] + ''; + } + requestData += ''; + + $.ajax({ + url: url, + type: "PUT", + data: requestData, + contentType: "text/plain; charset=UTF-8", + crossDomain: true, + cache: false, + beforeSend: function (xhr) { + xhr.setRequestHeader('x-ms-date', new Date().toUTCString()); + xhr.setRequestHeader('x-ms-blob-content-type', data.files[0].type); + xhr.setRequestHeader('x-ms-version', "2012-02-12"); + xhr.setRequestHeader('Content-Length', requestData.length.toString()); + }, + success: function () { + deferred.resolve(data); + }, + error: function (xhr, status, error) { + data.textStatus = status; + data.errorThrown = error; + deferred.fail(data); + } + }); + } + + return deferred.promise(); + } + + function hasActiveUploads() { + var scope = $(".upload-direct"); + var flag = false; + + scope.find(".async-upload").each(function () { + if ($(this).data("upload-isactive") == true) { + flag = true; + return false; + } + }); + + return flag; + } + + function hasCompletedUploads() { + var scope = $(".upload-direct"); + var flag = false; + + scope.find(".async-upload").each(function () { + if ($(this).data("upload-iscompleted") == true) { + flag = true; + return false; + } + }); + + return flag; + } + + var isSubmitting = function () { + var scope = $(".upload-direct"); + return scope.data("is-submitting") == true; + }; + + function initializeUpload(fileInput: JQuery) { + var scope = fileInput.closest(".async-upload"); + var acceptFileTypes: string = scope.data("upload-accept-file-types"); + var antiForgeryToken: string = scope.closest("form").find("[name='__RequestVerificationToken']").val(); + var preparingText = scope.find(".status.preparing"); + var uploadingContainer = scope.find(".status.uploading"); + var progressText = scope.find(".progress-text"); + var progressDetails = scope.find(".progress-details"); + var cancelLink = scope.find(".cancel-link"); + + (fileInput).fileupload({ + autoUpload: false, + acceptFileTypes: new RegExp(acceptFileTypes, "i"), + type: "PUT", + maxChunkSize: 4 * 1024 * 1024, // 4 MB + beforeSend: (xhr: JQueryXHR, data) => { + xhr.setRequestHeader("x-ms-date", new Date().toUTCString()); + xhr.setRequestHeader("x-ms-blob-type", "BlockBlob"); + xhr.setRequestHeader("content-length", data.data.size.toString()); + }, + chunksend: function (e, data) { + var blockIndex = scope.data("block-index"); + var blockIds = scope.data("block-ids"); + var blockId = createBlockId(blockIndex); + var url = scope.data("sas-locator") + "&comp=block&blockid=" + blockId; + + data.url = url; + blockIds.push(blockId); + scope.data("block-index", blockIndex + 1); + }, + progressall: function (e, data) { + var percentComplete = Math.floor((data.loaded / data.total) * 100); + var startTime = new Date(scope.data("upload-start-time")); + var elapsedMilliseconds = new Date(Date.now()).getTime() - startTime.getTime(); + var elapsed = moment.duration(elapsedMilliseconds, "ms"); + var remaining = moment.duration(elapsedMilliseconds / Math.max(data.loaded, 1) * (data.total - data.loaded), "ms"); + var kbps = Math.floor(data.bitrate / 8 / 1000); + var uploaded = Math.floor(data.loaded / 1000); + var total = Math.floor(data.total / 1000); + + scope.find(".progress-bar").show().find('.progress').css('width', percentComplete + '%'); + progressText.text(progressText.data("text-template").replace("{percentage}", percentComplete)).show(); + progressDetails.text(progressDetails.data("text-template").replace("{uploaded}", uploaded).replace("{total}", total).replace("{kbps}", kbps).replace("{elapsed}", elapsed.humanize()).replace("{remaining}", remaining.humanize())).show(); + }, + done: function (e, data) { + var self = this; + commitBlockList(scope, data).always(function () { + uploadCompleted(self, e, data); + }); + }, + fail: function (e, data) { + uploadCompleted(this, e, data); + }, + processdone: function (e, data) { + scope.find(".validation-text").hide(); + + var filename = data.files[0].name; + scope.data("upload-isactive", true); + scope.data("upload-start-time", Date.now()); + var generateAssetUrl = scope.data("generate-asset-url"); + preparingText.show(); + scope.find(".file-upload-wrapper").hide(); + scope.data("block-index", 0); + scope.data("block-ids", new Array()); + + $.ajax({ + url: generateAssetUrl, + data: { + filename: filename, + __RequestVerificationToken: antiForgeryToken + }, + type: "POST" + }).done(function (asset) { + data.url = asset.sasLocator; + data.multipart = false; + + scope.data("sas-locator", asset.sasLocator); + scope.find("input[name$='.FileName']").val(filename); + scope.find("input[name$='.WamsAssetId']").val(asset.assetId); + + preparingText.hide(); + progressText.text(progressText.data("text-template").replace("{percentage}", 0)).show(); + uploadingContainer.show(); + + var xhr = data.submit(); + scope.data("xhr", xhr); + }).fail(function (xhr, status, error) { + preparingText.hide(); + uploadingContainer.hide(); + scope.data("upload-isactive", false); + scope.data("upload-start-time", null); + scope.find(".file-upload-wrapper").show(); + alert("An error occurred. Error: " + error); + }); + }, + processfail: function (e, data) { + scope.find(".validation-text").show(); + }, + change: function (e, data) { + var prompt = fileInput.data("prompt"); + if (prompt && prompt.length > 0) { + if (!confirm(prompt)) { + e.preventDefault(); + } + } + } + }); + + cancelLink.on("click", function (e) { + e.preventDefault(); + + if (confirm($(this).data("prompt"))) { + var xhr = scope.data("xhr"); + xhr.abort(); + } + }); + } + + export function initializeUploadDirect() { + var scope = $(".upload-direct").show(); + requiredUploads = scope.find(".required-upload"); + + scope.find(".async-upload-input").each(function () { + initializeUpload($(this)); + }); + + scope.closest("form").on("submit", function (e) { + if (hasActiveUploads()) { + alert(scope.data("block-submit-prompt")); + e.preventDefault(); + return false; + } + + scope.data("is-submitting", true); + }); + + window.onbeforeunload = function (e) { + if ((hasActiveUploads() || hasCompletedUploads()) && !isSubmitting()) + e.returnValue = scope.data("navigate-away-prompt"); + }; + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-proxied.js b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-proxied.js new file mode 100644 index 000000000..2ce973029 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-proxied.js @@ -0,0 +1,170 @@ +/// +/// +var Orchard; +(function (Orchard) { + (function (Azure) { + (function (MediaServices) { + (function (CloudVideoEdit) { + var requiredUploads; + var blocked; + var hasRequiredUploadsp; + + function getAllFilesCompleted() { + var allFilesCompleted = true; + + requiredUploads.find("input[name$='.OriginalFileName'], input.sync-upload-input").each(function () { + if ($(this).val() == "") { + allFilesCompleted = false; + return false; + } + }); + + return allFilesCompleted; + } + ; + + function unblockIfComplete() { + if (getAllFilesCompleted()) + blocked.unblock(); + } + + function uploadCompleted(sender, data) { + var scope = $(sender).closest("[data-upload-accept-file-types]"); + var status = data.errorThrown && data.errorThrown.length > 0 ? data.errorThrown : data.textStatus; + scope.find(".progress-bar").hide(); + scope.find(".progress-text").hide(); + scope.find(".cancel-upload").hide(); + scope.data("upload-isactive", false); + + switch (status) { + case "error": + alert("The upload of the selected file failed. One possible cause is that the file size exceeds the configured maxRequestLength setting (see: http://msdn.microsoft.com/en-us/library/system.web.configuration.httpruntimesection.maxrequestlength(v=vs.110).aspx). Also make sure the executionTimeOut is set to allow for enough time for the request to execute when debug=\"false\"."); + return; + case "abort": + return; + } + + var temporaryFileName = data.result.temporaryFileName; + var originalFileName = data.result.originalFileName; + var fileSize = data.result.fileSize; + + scope.find("input[name$='.OriginalFileName']").val(originalFileName); + scope.find("input[name$='.TemporaryFileName']").val(temporaryFileName); + scope.find("input[name$='.FileSize']").val(fileSize); + + unblockIfComplete(); + $(sender).replaceWith("Successfully uploaded video file '" + originalFileName + "'."); + } + + function initializeUpload(fileInput) { + var scope = $(fileInput).closest("[data-upload-accept-file-types]"); + var acceptFileTypes = scope.data("upload-accept-file-types"); + var antiForgeryToken = requiredUploads.closest("form").find("[name='__RequestVerificationToken']").val(); + var cancelUpload = scope.find(".cancel-upload"); + + fileInput.fileupload({ + autoUpload: false, + acceptFileTypes: new RegExp(acceptFileTypes, "i"), + type: "POST", + url: scope.data("upload-fallback-url"), + formData: { + __RequestVerificationToken: antiForgeryToken + }, + progressall: function (e, data) { + var percentComplete = Math.floor((data.loaded / data.total) * 100); + scope.find(".progress-bar").show().find('.progress').css('width', percentComplete + '%'); + scope.find(".progress-text").show().text("Uploading (" + percentComplete + "%)..."); + }, + done: function (e, data) { + uploadCompleted(this, data); + }, + fail: function (e, data) { + uploadCompleted(this, data); + }, + processdone: function (e, data) { + scope.find(".validation-text").hide(); + scope.data("upload-isactive", true); + cancelUpload.show(); + var xhr = data.submit(); + scope.data("xhr", xhr); + }, + processfail: function (e, data) { + scope.find(".validation-text").show(); + } + }); + + cancelUpload.on("click", function (e) { + e.preventDefault(); + + if (confirm("Are you sure you want to cancel this upload?")) { + var xhr = scope.data("xhr"); + xhr.abort(); + } + }); + } + + function initializeUploadProxied() { + var scopeProxied = $(".upload-proxied").show(); + requiredUploads = scopeProxied.find(".required-uploads-group"); + blocked = scopeProxied.find(".edit-item-sidebar"); + hasRequiredUploadsp = requiredUploads.length > 0; + + if (hasRequiredUploadsp) { + blocked.block({ + message: requiredUploads.data("block-description"), + overlayCSS: { + backgroundColor: "#fff", + cursor: "default" + }, + css: { + cursor: "default", + border: null, + width: null, + left: 0, + margin: "30px 0 0 0", + backgroundColor: null + } + }); + + scopeProxied.find(".async-upload-input").each(function () { + initializeUpload($(this)); + }); + + window.onbeforeunload = function (e) { + var hasActiveUploads = false; + + scopeProxied.find("[data-upload-accept-file-types]").each(function () { + if ($(this).data("upload-isactive") == true) { + hasActiveUploads = true; + return false; + } + }); + + if (hasActiveUploads) + e.returnValue = "There are uploads in progress. These will be aborted if you navigate away."; + }; + + scopeProxied.find(".sync-upload-input").on("change", function (e) { + unblockIfComplete(); + }); + + unblockIfComplete(); + } + + scopeProxied.find("[data-prompt]").on("change", function (e) { + var sender = $(e.currentTarget); + + if (!confirm(sender.data("prompt"))) { + sender.val(""); + } + }); + } + CloudVideoEdit.initializeUploadProxied = initializeUploadProxied; + })(MediaServices.CloudVideoEdit || (MediaServices.CloudVideoEdit = {})); + var CloudVideoEdit = MediaServices.CloudVideoEdit; + })(Azure.MediaServices || (Azure.MediaServices = {})); + var MediaServices = Azure.MediaServices; + })(Orchard.Azure || (Orchard.Azure = {})); + var Azure = Orchard.Azure; +})(Orchard || (Orchard = {})); +//# sourceMappingURL=cloudmedia-edit-cloudvideopart-proxied.js.map diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-proxied.js.map b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-proxied.js.map new file mode 100644 index 000000000..cfe704a29 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-proxied.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cloudmedia-edit-cloudvideopart-proxied.js","sourceRoot":"","sources":["cloudmedia-edit-cloudvideopart-proxied.ts"],"names":["Orchard","Orchard.Azure","Orchard.Azure.MediaServices","Orchard.Azure.MediaServices.CloudVideoEdit","Orchard.Azure.MediaServices.CloudVideoEdit.getAllFilesCompleted","Orchard.Azure.MediaServices.CloudVideoEdit.unblockIfComplete","Orchard.Azure.MediaServices.CloudVideoEdit.uploadCompleted","Orchard.Azure.MediaServices.CloudVideoEdit.initializeUpload","Orchard.Azure.MediaServices.CloudVideoEdit.initializeUploadProxied"],"mappings":"AAAA,4CAA4C;AAC5C,8CAA8C;AAE9C,IAAO,OAAO;AA2Jb,CA3JD,UAAO,OAAO;KAAdA,UAAeA,KAAKA;SAApBC,UAAqBA,aAAaA;aAAlCC,UAAmCA,cAAcA;gBAE7CC,IAAIA,eAAeA;gBACnBA,IAAIA,OAAOA;gBACXA,IAAIA,mBAAmBA;;gBAEvBA,SAASA,oBAAoBA;oBACzBC,IAAIA,iBAAiBA,GAAYA,IAAIA;;oBAErCA,eAAeA,CAACA,IAAIA,CAACA,2DAA2DA,CAACA,CAACA,IAAIA,CAACA;wBACnFA,IAAIA,CAACA,CAACA,IAAIA,CAACA,CAACA,GAAGA,CAACA,CAACA,IAAIA,EAAEA,CAAEA;4BACrBA,iBAAiBA,GAAGA,KAAKA;4BACzBA,OAAOA,KAAKA;yBACfA;oBACLA,CAACA,CAACA;;oBAEFA,OAAOA,iBAAiBA;gBAC5BA,CAACA;gBAAAD,CAACA;;gBAEFA,SAASA,iBAAiBA;oBACtBE,IAAIA,oBAAoBA,CAACA,CAACA;wBACtBA,OAAaA,CAAEA,OAAOA,CAACA,CAACA,CAACA;gBACjCA,CAACA;;gBAEDF,SAASA,eAAeA,CAACA,MAAMA,EAAEA,IAAIA;oBACjCG,IAAIA,KAAKA,GAAGA,CAACA,CAACA,MAAMA,CAACA,CAACA,OAAOA,CAACA,iCAAiCA,CAACA;oBAChEA,IAAIA,MAAMA,GAAGA,IAAIA,CAACA,WAAWA,IAAIA,IAAIA,CAACA,WAAWA,CAACA,MAAMA,GAAGA,CAACA,GAAGA,IAAIA,CAACA,WAAWA,GAAGA,IAAIA,CAACA,UAAUA;oBACjGA,KAAKA,CAACA,IAAIA,CAACA,eAAeA,CAACA,CAACA,IAAIA,CAACA,CAACA;oBAClCA,KAAKA,CAACA,IAAIA,CAACA,gBAAgBA,CAACA,CAACA,IAAIA,CAACA,CAACA;oBACnCA,KAAKA,CAACA,IAAIA,CAACA,gBAAgBA,CAACA,CAACA,IAAIA,CAACA,CAACA;oBACnCA,KAAKA,CAACA,IAAIA,CAACA,iBAAiBA,EAAEA,KAAKA,CAACA;;oBAEpCA,QAAQA,MAAMA,CAACA;wBACXA,KAAKA,OAAOA;4BACRA,KAAKA,CAACA,mXAAmXA,CAACA;4BAC1XA,MAAOA;AAAAA,wBACXA,KAAKA,OAAOA;4BACRA,MAAOA;AAAAA,qBACdA;;oBAEDA,IAAIA,iBAAiBA,GAAGA,IAAIA,CAACA,MAAMA,CAACA,iBAAiBA;oBACrDA,IAAIA,gBAAgBA,GAAGA,IAAIA,CAACA,MAAMA,CAACA,gBAAgBA;oBACnDA,IAAIA,QAAQA,GAAGA,IAAIA,CAACA,MAAMA,CAACA,QAAQA;;oBAEnCA,KAAKA,CAACA,IAAIA,CAACA,kCAAkCA,CAACA,CAACA,GAAGA,CAACA,gBAAgBA,CAACA;oBACpEA,KAAKA,CAACA,IAAIA,CAACA,mCAAmCA,CAACA,CAACA,GAAGA,CAACA,iBAAiBA,CAACA;oBACtEA,KAAKA,CAACA,IAAIA,CAACA,0BAA0BA,CAACA,CAACA,GAAGA,CAACA,QAAQA,CAACA;;oBAEpDA,iBAAiBA,CAACA,CAACA;oBACnBA,CAACA,CAACA,MAAMA,CAACA,CAACA,WAAWA,CAACA,0CAA0CA,GAAGA,gBAAgBA,GAAGA,WAAWA,CAACA;gBACtGA,CAACA;;gBAEDH,SAASA,gBAAgBA,CAACA,SAASA;oBAC/BI,IAAIA,KAAKA,GAAGA,CAACA,CAACA,SAASA,CAACA,CAACA,OAAOA,CAACA,iCAAiCA,CAACA;oBACnEA,IAAIA,eAAeA,GAAWA,KAAKA,CAACA,IAAIA,CAACA,0BAA0BA,CAACA;oBACpEA,IAAIA,gBAAgBA,GAAGA,eAAeA,CAACA,OAAOA,CAACA,MAAMA,CAACA,CAACA,IAAIA,CAACA,qCAAqCA,CAACA,CAACA,GAAGA,CAACA,CAACA;oBACxGA,IAAIA,YAAYA,GAAGA,KAAKA,CAACA,IAAIA,CAACA,gBAAgBA,CAACA;;oBAE/CA,SAASA,CAACA,UAAUA,CAACA;wBACjBA,UAAUA,EAAEA,KAAKA;wBACjBA,eAAeA,EAAEA,IAAIA,MAAMA,CAACA,eAAeA,EAAEA,GAAGA,CAACA;wBACjDA,IAAIA,EAAEA,MAAMA;wBACZA,GAAGA,EAAEA,KAAKA,CAACA,IAAIA,CAACA,qBAAqBA,CAACA;wBACtCA,QAAQA,EAAEA;4BACNA,0BAA0BA,EAAEA,gBAAgBA;yBAC/CA;wBACDA,WAAWA,EAAEA,UAACA,CAACA,EAAEA,IAAIA;4BACjBA,IAAIA,eAAeA,GAAGA,IAAIA,CAACA,KAAKA,CAACA,CAACA,IAAIA,CAACA,MAAMA,GAAGA,IAAIA,CAACA,KAAKA,CAACA,GAAGA,GAAGA,CAACA;4BAClEA,KAAKA,CAACA,IAAIA,CAACA,eAAeA,CAACA,CAACA,IAAIA,CAACA,CAACA,CAACA,IAAIA,CAACA,WAAWA,CAACA,CAACA,GAAGA,CAACA,OAAOA,EAAEA,eAAeA,GAAGA,GAAGA,CAACA;4BACxFA,KAAKA,CAACA,IAAIA,CAACA,gBAAgBA,CAACA,CAACA,IAAIA,CAACA,CAACA,CAACA,IAAIA,CAACA,aAAaA,GAAGA,eAAeA,GAAGA,OAAOA,CAACA;wBACvFA,CAACA;wBACDA,IAAIA,EAAEA,UAAUA,CAACA,EAAEA,IAAIA;4BACnBA,eAAeA,CAACA,IAAIA,EAAEA,IAAIA,CAACA;wBAC/BA,CAACA;wBACDA,IAAIA,EAAEA,UAAUA,CAACA,EAAEA,IAAIA;4BACnBA,eAAeA,CAACA,IAAIA,EAAEA,IAAIA,CAACA;wBAC/BA,CAACA;wBACDA,WAAWA,EAAEA,UAACA,CAACA,EAAEA,IAAIA;4BACjBA,KAAKA,CAACA,IAAIA,CAACA,kBAAkBA,CAACA,CAACA,IAAIA,CAACA,CAACA;4BACrCA,KAAKA,CAACA,IAAIA,CAACA,iBAAiBA,EAAEA,IAAIA,CAACA;4BACnCA,YAAYA,CAACA,IAAIA,CAACA,CAACA;4BACnBA,IAAIA,GAAGA,GAAGA,IAAIA,CAACA,MAAMA,CAACA,CAACA;4BACvBA,KAAKA,CAACA,IAAIA,CAACA,KAAKA,EAAEA,GAAGA,CAACA;wBAC1BA,CAACA;wBACDA,WAAWA,EAAEA,UAACA,CAACA,EAAEA,IAAIA;4BACjBA,KAAKA,CAACA,IAAIA,CAACA,kBAAkBA,CAACA,CAACA,IAAIA,CAACA,CAACA;wBACzCA,CAACA;qBACJA,CAACA;;oBAEFA,YAAYA,CAACA,EAAEA,CAACA,OAAOA,EAAEA,UAAAA,CAACA;wBACtBA,CAACA,CAACA,cAAcA,CAACA,CAACA;;wBAElBA,IAAIA,OAAOA,CAACA,8CAA8CA,CAACA,CAAEA;4BACzDA,IAAIA,GAAGA,GAAGA,KAAKA,CAACA,IAAIA,CAACA,KAAKA,CAACA;4BAC3BA,GAAGA,CAACA,KAAKA,CAACA,CAACA;yBACdA;oBACLA,CAACA,CAACA;gBACNA,CAACA;;gBAEDJ,SAAgBA,uBAAuBA;oBACnCK,IAAIA,YAAYA,GAAGA,CAACA,CAACA,iBAAiBA,CAACA,CAACA,IAAIA,CAACA,CAACA;oBAC9CA,eAAeA,GAAGA,YAAYA,CAACA,IAAIA,CAACA,yBAAyBA,CAACA;oBAC9DA,OAAOA,GAAGA,YAAYA,CAACA,IAAIA,CAACA,oBAAoBA,CAACA;oBACjDA,mBAAmBA,GAAGA,eAAeA,CAACA,MAAMA,GAAGA,CAACA;;oBAEhDA,IAAIA,mBAAmBA,CAAEA;wBACrBA,OAAaA,CAAEA,KAAKA,CAACA;4BACjBA,OAAOA,EAAEA,eAAeA,CAACA,IAAIA,CAACA,mBAAmBA,CAACA;4BAClDA,UAAUA,EAAEA;gCACRA,eAAeA,EAAEA,MAAMA;gCACvBA,MAAMA,EAAEA,SAASA;6BACpBA;4BACDA,GAAGA,EAAEA;gCACDA,MAAMA,EAAEA,SAASA;gCACjBA,MAAMA,EAAEA,IAAIA;gCACZA,KAAKA,EAAEA,IAAIA;gCACXA,IAAIA,EAAEA,CAACA;gCACPA,MAAMA,EAAEA,YAAYA;gCACpBA,eAAeA,EAAEA,IAAIA;6BACxBA;yBACJA,CAACA;;wBAEFA,YAAYA,CAACA,IAAIA,CAACA,qBAAqBA,CAACA,CAACA,IAAIA,CAACA;4BAC1CA,gBAAgBA,CAACA,CAACA,CAACA,IAAIA,CAACA,CAACA;wBAC7BA,CAACA,CAACA;;wBAEFA,MAAMA,CAACA,cAAcA,GAAGA,UAAAA,CAACA;4BACrBA,IAAIA,gBAAgBA,GAAGA,KAAKA;;4BAE5BA,YAAYA,CAACA,IAAIA,CAACA,iCAAiCA,CAACA,CAACA,IAAIA,CAACA;gCACtDA,IAAIA,CAACA,CAACA,IAAIA,CAACA,CAACA,IAAIA,CAACA,iBAAiBA,CAACA,IAAIA,IAAIA,CAAEA;oCACzCA,gBAAgBA,GAAGA,IAAIA;oCACvBA,OAAOA,KAAKA;iCACfA;4BACLA,CAACA,CAACA;;4BAEFA,IAAIA,gBAAgBA;gCAChBA,CAACA,CAACA,WAAWA,GAAGA,4EAA4EA,CAACA;wBACrGA,CAACA;;wBAEDA,YAAYA,CAACA,IAAIA,CAACA,oBAAoBA,CAACA,CAACA,EAAEA,CAACA,QAAQA,EAAEA,UAAAA,CAACA;4BAClDA,iBAAiBA,CAACA,CAACA;wBACvBA,CAACA,CAACA;;wBAEFA,iBAAiBA,CAACA,CAACA;qBACtBA;;oBAEDA,YAAYA,CAACA,IAAIA,CAACA,eAAeA,CAACA,CAACA,EAAEA,CAACA,QAAQA,EAAEA,UAAAA,CAACA;wBAC7CA,IAAIA,MAAMA,GAAGA,CAACA,CAACA,CAACA,CAACA,aAAaA,CAACA;;wBAE/BA,IAAIA,CAACA,OAAOA,CAACA,MAAMA,CAACA,IAAIA,CAACA,QAAQA,CAACA,CAACA,CAAEA;4BACjCA,MAAMA,CAACA,GAAGA,CAACA,EAAEA,CAACA;yBACjBA;oBACLA,CAACA,CAACA;gBACNA,CAACA;gBAvDDL,iEAuDCA;YACLA,CAACA,uEAAAD;8DAAAA;QAADA,CAACA,qDAAAD;gDAAAA;IAADA,CAACA,yCAAAD;8BAAAA;AAADA,CAACA,6BAAA"} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-proxied.ts b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-proxied.ts new file mode 100644 index 000000000..3bb0c29d9 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart-proxied.ts @@ -0,0 +1,159 @@ +/// +/// + +module Orchard.Azure.MediaServices.CloudVideoEdit { + + var requiredUploads: JQuery; + var blocked: JQuery; + var hasRequiredUploadsp: boolean; + + function getAllFilesCompleted(): boolean { + var allFilesCompleted: boolean = true; + + requiredUploads.find("input[name$='.OriginalFileName'], input.sync-upload-input").each(function () { + if ($(this).val() == "") { + allFilesCompleted = false; + return false; + } + }); + + return allFilesCompleted; + }; + + function unblockIfComplete() { + if (getAllFilesCompleted()) + (blocked).unblock(); + } + + function uploadCompleted(sender, data) { + var scope = $(sender).closest("[data-upload-accept-file-types]"); + var status = data.errorThrown && data.errorThrown.length > 0 ? data.errorThrown : data.textStatus; + scope.find(".progress-bar").hide(); + scope.find(".progress-text").hide(); + scope.find(".cancel-upload").hide(); + scope.data("upload-isactive", false); + + switch (status) { + case "error": + alert("The upload of the selected file failed. One possible cause is that the file size exceeds the configured maxRequestLength setting (see: http://msdn.microsoft.com/en-us/library/system.web.configuration.httpruntimesection.maxrequestlength(v=vs.110).aspx). Also make sure the executionTimeOut is set to allow for enough time for the request to execute when debug=\"false\"."); + return; + case "abort": + return; + } + + var temporaryFileName = data.result.temporaryFileName; + var originalFileName = data.result.originalFileName; + var fileSize = data.result.fileSize; + + scope.find("input[name$='.OriginalFileName']").val(originalFileName); + scope.find("input[name$='.TemporaryFileName']").val(temporaryFileName); + scope.find("input[name$='.FileSize']").val(fileSize); + + unblockIfComplete(); + $(sender).replaceWith("Successfully uploaded video file '" + originalFileName + "'."); + } + + function initializeUpload(fileInput) { + var scope = $(fileInput).closest("[data-upload-accept-file-types]"); + var acceptFileTypes: string = scope.data("upload-accept-file-types"); + var antiForgeryToken = requiredUploads.closest("form").find("[name='__RequestVerificationToken']").val(); + var cancelUpload = scope.find(".cancel-upload"); + + fileInput.fileupload({ + autoUpload: false, + acceptFileTypes: new RegExp(acceptFileTypes, "i"), + type: "POST", + url: scope.data("upload-fallback-url"), + formData: { + __RequestVerificationToken: antiForgeryToken + }, + progressall: (e, data) => { + var percentComplete = Math.floor((data.loaded / data.total) * 100); + scope.find(".progress-bar").show().find('.progress').css('width', percentComplete + '%'); + scope.find(".progress-text").show().text("Uploading (" + percentComplete + "%)..."); + }, + done: function (e, data) { + uploadCompleted(this, data); + }, + fail: function (e, data) { + uploadCompleted(this, data); + }, + processdone: (e, data) => { + scope.find(".validation-text").hide(); + scope.data("upload-isactive", true); + cancelUpload.show(); + var xhr = data.submit(); + scope.data("xhr", xhr); + }, + processfail: (e, data) => { + scope.find(".validation-text").show(); + } + }); + + cancelUpload.on("click", e=> { + e.preventDefault(); + + if (confirm("Are you sure you want to cancel this upload?")) { + var xhr = scope.data("xhr"); + xhr.abort(); + } + }); + } + + export function initializeUploadProxied() { + var scopeProxied = $(".upload-proxied").show(); + requiredUploads = scopeProxied.find(".required-uploads-group"); + blocked = scopeProxied.find(".edit-item-sidebar"); + hasRequiredUploadsp = requiredUploads.length > 0; + + if (hasRequiredUploadsp) { + (blocked).block({ + message: requiredUploads.data("block-description"), + overlayCSS: { + backgroundColor: "#fff", + cursor: "default" + }, + css: { + cursor: "default", + border: null, + width: null, + left: 0, + margin: "30px 0 0 0", + backgroundColor: null + } + }); + + scopeProxied.find(".async-upload-input").each(function () { + initializeUpload($(this)); + }); + + window.onbeforeunload = e => { + var hasActiveUploads = false; + + scopeProxied.find("[data-upload-accept-file-types]").each(function () { + if ($(this).data("upload-isactive") == true) { + hasActiveUploads = true; + return false; + } + }); + + if (hasActiveUploads) + e.returnValue = "There are uploads in progress. These will be aborted if you navigate away."; + }; + + scopeProxied.find(".sync-upload-input").on("change", e=> { + unblockIfComplete(); + }); + + unblockIfComplete(); + } + + scopeProxied.find("[data-prompt]").on("change", e => { + var sender = $(e.currentTarget); + + if (!confirm(sender.data("prompt"))) { + sender.val(""); + } + }); + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart.js b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart.js new file mode 100644 index 000000000..83b0d111a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart.js @@ -0,0 +1,38 @@ +/// +/// +var Orchard; +(function (Orchard) { + (function (Azure) { + (function (MediaServices) { + (function (CloudVideoEdit) { + function hasCorsSupport() { + return 'withCredentials' in new XMLHttpRequest(); + } + + $(function () { + var corsSupported = hasCorsSupport(); + + if (corsSupported) { + Orchard.Azure.MediaServices.CloudVideoEdit.initializeUploadDirect(); + } else { + Orchard.Azure.MediaServices.CloudVideoEdit.initializeUploadProxied(); + } + + var localStorage = window["localStorage"]; + var isCreating = $("#tabs").data("cloudvideo-iscreating"); + $("#tabs").tabs({ + activate: function () { + if (localStorage && localStorage.setItem) + localStorage.setItem("selectedCloudVideoTab", $("#tabs").tabs("option", "active")); + }, + active: !isCreating && localStorage && localStorage.getItem ? localStorage.getItem("selectedCloudVideoTab") : null + }).show(); + }); + })(MediaServices.CloudVideoEdit || (MediaServices.CloudVideoEdit = {})); + var CloudVideoEdit = MediaServices.CloudVideoEdit; + })(Azure.MediaServices || (Azure.MediaServices = {})); + var MediaServices = Azure.MediaServices; + })(Orchard.Azure || (Orchard.Azure = {})); + var Azure = Orchard.Azure; +})(Orchard || (Orchard = {})); +//# sourceMappingURL=cloudmedia-edit-cloudvideopart.js.map diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart.js.map b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart.js.map new file mode 100644 index 000000000..26a177adb --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cloudmedia-edit-cloudvideopart.js","sourceRoot":"","sources":["cloudmedia-edit-cloudvideopart.ts"],"names":["Orchard","Orchard.Azure","Orchard.Azure.MediaServices","Orchard.Azure.MediaServices.CloudVideoEdit","Orchard.Azure.MediaServices.CloudVideoEdit.hasCorsSupport"],"mappings":"AAAA,4CAA4C;AAC5C,8CAA8C;AAE9C,IAAO,OAAO;AAwBb,CAxBD,UAAO,OAAO;KAAdA,UAAeA,KAAKA;SAApBC,UAAqBA,aAAaA;aAAlCC,UAAmCA,cAAcA;gBAC7CC,SAASA,cAAcA;oBACnBC,OAAOA,iBAAiBA,IAAIA,IAAIA,cAAcA,CAACA,CAACA;gBACpDA,CAACA;;gBAEDD,CAACA,CAACA;oBACEA,IAAIA,aAAaA,GAAGA,cAAcA,CAACA,CAACA;;oBAEpCA,IAAIA,aAAaA,CAAEA;wBACfA,iEAAsBA,CAACA,CAACA;qBAC3BA,KAAMA;wBACHA,kEAAuBA,CAACA,CAACA;qBAC5BA;;oBAEDA,IAAIA,YAAYA,GAAGA,MAAMA,CAACA,cAAcA,CAACA;oBACzCA,IAAIA,UAAUA,GAAYA,CAACA,CAACA,OAAOA,CAACA,CAACA,IAAIA,CAACA,uBAAuBA,CAACA;oBAClEA,CAACA,CAACA,OAAOA,CAACA,CAACA,IAAIA,CAACA;wBACZA,QAAQA,EAAEA;4BACNA,IAAIA,YAAYA,IAAIA,YAAYA,CAACA,OAAOA;gCACpCA,YAAYA,CAACA,OAAOA,CAACA,uBAAuBA,EAAEA,CAACA,CAACA,OAAOA,CAACA,CAACA,IAAIA,CAACA,QAAQA,EAAEA,QAAQA,CAACA,CAACA,CAACA;wBAC3FA,CAACA;wBACDA,MAAMA,EAAEA,CAACA,UAAUA,IAAIA,YAAYA,IAAIA,YAAYA,CAACA,OAAOA,GAAGA,YAAYA,CAACA,OAAOA,CAACA,uBAAuBA,CAACA,GAAGA,IAAIA;qBACrHA,CAACA,CAACA,IAAIA,CAACA,CAACA;gBACbA,CAACA,CAACA;YACNA,CAACA,uEAAAD;8DAAAA;QAADA,CAACA,qDAAAD;gDAAAA;IAADA,CAACA,yCAAAD;8BAAAA;AAADA,CAACA,6BAAA"} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart.ts b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart.ts new file mode 100644 index 000000000..d1ea1a093 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-edit-cloudvideopart.ts @@ -0,0 +1,28 @@ +/// +/// + +module Orchard.Azure.MediaServices.CloudVideoEdit { + function hasCorsSupport() { + return 'withCredentials' in new XMLHttpRequest(); + } + + $(function() { + var corsSupported = hasCorsSupport(); + + if (corsSupported) { + initializeUploadDirect(); + } else { + initializeUploadProxied(); + } + + var localStorage = window["localStorage"]; + var isCreating: boolean = $("#tabs").data("cloudvideo-iscreating"); + $("#tabs").tabs({ + activate: function () { + if (localStorage && localStorage.setItem) + localStorage.setItem("selectedCloudVideoTab", $("#tabs").tabs("option", "active")); + }, + active: !isCreating && localStorage && localStorage.getItem ? localStorage.getItem("selectedCloudVideoTab") : null + }).show(); + }); +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-metadata-cloudvideopart.js b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-metadata-cloudvideopart.js new file mode 100644 index 000000000..df63d33f8 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-metadata-cloudvideopart.js @@ -0,0 +1,60 @@ +/// +(function ($) { + var hasFocus = function (videoId) { + var focusedVideoId = $("#media-library-main-list li.has-focus .media-thumbnail-cloud-video").data("id"); + return focusedVideoId == videoId; + }; + + var updateUploadProgressLabel = function () { + var containers = $("#media-library-main-editor-focus .properties"); + + containers.each(function () { + var container = $(this); + var statusWrapper = container.find(".upload-status-wrapper"); + var publicationStatusLabel = container.find(".publication-status span"); + var uploadStatusLabel = container.find(".upload-status-text"); + var progressLabel = container.find(".upload-progress-value"); + var uploadProgressContainer = $(".upload-progress-status"); + var statusUrl = statusWrapper.data("status-url"); + var status = statusWrapper.data("upload-status"); + var published = statusWrapper.data("published"); + var videoId = statusWrapper.data("video-id"); + + if (status == "Uploaded" && published) { + return; + } + + var update = function () { + if (!hasFocus(videoId)) { + return; + } + $.ajax({ + url: statusUrl, + cache: false + }).done(function (data) { + progressLabel.text(data.uploadState.percentComplete + "%"); + uploadStatusLabel.text(data.uploadState.status); + publicationStatusLabel.text(data.published ? statusWrapper.data("published-text") : statusWrapper.data("draft-text")); + + if (data.published) { + var mediathumbnail = $(".media-thumbnail-cloud-video[data-id=" + videoId + "]"); + mediathumbnail.parents("li:first").find(".publication-status").hide(); + } + + if (data.uploadState.status == "Uploaded") { + uploadProgressContainer.hide(); + return; + } else if (data.uploadState.status == "Uploading") { + uploadProgressContainer.show(); + } + + window.setTimeout(update, 5000); + }); + }; + update(); + }); + }; + + updateUploadProgressLabel(); +})(jQuery); +//# sourceMappingURL=cloudmedia-metadata-cloudvideopart.js.map diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-metadata-cloudvideopart.js.map b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-metadata-cloudvideopart.js.map new file mode 100644 index 000000000..64729ca14 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-metadata-cloudvideopart.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cloudmedia-metadata-cloudvideopart.js","sourceRoot":"","sources":["cloudmedia-metadata-cloudvideopart.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAE5C,CAAC,UAAA,CAAC;IACE,IAAI,QAAQ,GAAG,UAAC,OAAe;QAC3B,IAAI,cAAc,GAAW,CAAC,CAAC,oEAAoE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAC/G,OAAO,cAAc,IAAI,OAAO;IACpC,CAAC;;IAED,IAAI,yBAAyB,GAAG;QAC5B,IAAI,UAAU,GAAW,CAAC,CAAC,8CAA8C,CAAC;;QAE1E,UAAU,CAAC,IAAI,CAAC;YACZ,IAAI,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC;YACvB,IAAI,aAAa,GAAG,SAAS,CAAC,IAAI,CAAC,wBAAwB,CAAC;YAC5D,IAAI,sBAAsB,GAAG,SAAS,CAAC,IAAI,CAAC,0BAA0B,CAAC;YACvE,IAAI,iBAAiB,GAAG,SAAS,CAAC,IAAI,CAAC,qBAAqB,CAAC;YAC7D,IAAI,aAAa,GAAG,SAAS,CAAC,IAAI,CAAC,wBAAwB,CAAC;YAC5D,IAAI,uBAAuB,GAAG,CAAC,CAAC,yBAAyB,CAAC;YAC1D,IAAI,SAAS,GAAW,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC;YACxD,IAAI,MAAM,GAAW,aAAa,CAAC,IAAI,CAAC,eAAe,CAAC;YACxD,IAAI,SAAS,GAAW,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC;YACvD,IAAI,OAAO,GAAW,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC;;YAEpD,IAAI,MAAM,IAAI,UAAU,IAAI,SAAS,CAAE;gBACnC,MAAO;aACV;;YAED,IAAI,MAAM,GAAG;gBACT,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAE;oBACpB,MAAO;iBACV;gBACD,CAAC,CAAC,IAAI,CAAC;oBACH,GAAG,EAAE,SAAS;oBACd,KAAK,EAAE,KAAK;iBACf,CAAC,CAAC,IAAI,CAAC,UAAA,IAAI;oBACR,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,eAAe,GAAG,GAAG,CAAC;oBAC1D,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;oBAC/C,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;;oBAErH,IAAI,IAAI,CAAC,SAAS,CAAE;wBAChB,IAAI,cAAc,GAAG,CAAC,CAAC,uCAAuC,GAAG,OAAO,GAAG,GAAG,CAAC;wBAC/E,cAAc,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,CAAC;qBACxE;;oBAED,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,IAAI,UAAU,CAAE;wBACvC,uBAAuB,CAAC,IAAI,CAAC,CAAC;wBAC9B,MAAO;qBACV,MAAM,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,IAAI,WAAW,CAAE;wBAC/C,uBAAuB,CAAC,IAAI,CAAC,CAAC;qBACjC;;oBAED,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC;gBACnC,CAAC,CAAC;YACN,CAAC;YACD,MAAM,CAAC,CAAC;QACZ,CAAC,CAAC;IACN,CAAC;;IAED,yBAAyB,CAAC,CAAC;AAC/B,CAAC,CAAC,CAAC,MAAM,CAAC"} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-metadata-cloudvideopart.ts b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-metadata-cloudvideopart.ts new file mode 100644 index 000000000..1475b2f52 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-metadata-cloudvideopart.ts @@ -0,0 +1,60 @@ +/// + +($=> { + var hasFocus = (videoId: number)=> { + var focusedVideoId: number = $("#media-library-main-list li.has-focus .media-thumbnail-cloud-video").data("id"); + return focusedVideoId == videoId; + }; + + var updateUploadProgressLabel = ()=> { + var containers: JQuery = $("#media-library-main-editor-focus .properties"); + + containers.each(function () { + var container = $(this); + var statusWrapper = container.find(".upload-status-wrapper"); + var publicationStatusLabel = container.find(".publication-status span"); + var uploadStatusLabel = container.find(".upload-status-text"); + var progressLabel = container.find(".upload-progress-value"); + var uploadProgressContainer = $(".upload-progress-status"); + var statusUrl: string = statusWrapper.data("status-url"); + var status: string = statusWrapper.data("upload-status"); + var published: string = statusWrapper.data("published"); + var videoId: number = statusWrapper.data("video-id"); + + if (status == "Uploaded" && published) { + return; + } + + var update = () => { + if (!hasFocus(videoId)) { + return; + } + $.ajax({ + url: statusUrl, + cache: false + }).done(data=> { + progressLabel.text(data.uploadState.percentComplete + "%"); + uploadStatusLabel.text(data.uploadState.status); + publicationStatusLabel.text(data.published ? statusWrapper.data("published-text") : statusWrapper.data("draft-text")); + + if (data.published) { + var mediathumbnail = $(".media-thumbnail-cloud-video[data-id=" + videoId + "]"); + mediathumbnail.parents("li:first").find(".publication-status").hide(); + } + + if (data.uploadState.status == "Uploaded") { + uploadProgressContainer.hide(); + return; + } else if (data.uploadState.status == "Uploading") { + uploadProgressContainer.show(); + } + + window.setTimeout(update, 5000); + }); + }; + update(); + }); + }; + + updateUploadProgressLabel(); +})(jQuery); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-data.js b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-data.js new file mode 100644 index 000000000..6dc84347f --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-data.js @@ -0,0 +1,23 @@ +var Orchard; +(function (Orchard) { + (function (Azure) { + (function (MediaServices) { + (function (VideoPlayer) { + (function (Data) { + (function (AssetType) { + AssetType[AssetType["VideoAsset"] = 0] = "VideoAsset"; + AssetType[AssetType["DynamicVideoAsset"] = 1] = "DynamicVideoAsset"; + AssetType[AssetType["ThumbnailAsset"] = 2] = "ThumbnailAsset"; + AssetType[AssetType["SubtitleAsset"] = 3] = "SubtitleAsset"; + })(Data.AssetType || (Data.AssetType = {})); + var AssetType = Data.AssetType; + })(VideoPlayer.Data || (VideoPlayer.Data = {})); + var Data = VideoPlayer.Data; + })(MediaServices.VideoPlayer || (MediaServices.VideoPlayer = {})); + var VideoPlayer = MediaServices.VideoPlayer; + })(Azure.MediaServices || (Azure.MediaServices = {})); + var MediaServices = Azure.MediaServices; + })(Orchard.Azure || (Orchard.Azure = {})); + var Azure = Orchard.Azure; +})(Orchard || (Orchard = {})); +//# sourceMappingURL=cloudmedia-videoplayer-data.js.map diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-data.js.map b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-data.js.map new file mode 100644 index 000000000..257048924 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-data.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cloudmedia-videoplayer-data.js","sourceRoot":"","sources":["cloudmedia-videoplayer-data.ts"],"names":["Orchard","Orchard.Azure","Orchard.Azure.MediaServices","Orchard.Azure.MediaServices.VideoPlayer","Orchard.Azure.MediaServices.VideoPlayer.Data","Orchard.Azure.MediaServices.VideoPlayer.Data.AssetType"],"mappings":"AAAA,IAAO,OAAO;AAgFb,CAhFD,UAAO,OAAO;KAAdA,UAAeA,KAAKA;SAApBC,UAAqBA,aAAaA;aAAlCC,UAAmCA,WAAWA;iBAA9CC,UAA+CA,IAAIA;qBAkB/CC,UAAYA,SAASA;wBACjBC,qDAAUA;wBACVA,mEAAiBA;wBACjBA,6DAAcA;wBACdA,2DAAaA;gEAChBD;mDAAAA;gBAyDLA,CAACA,+CAAAD;4CAAAA;YAADA,CAACA,iEAAAD;wDAAAA;QAADA,CAACA,qDAAAD;gDAAAA;IAADA,CAACA,yCAAAD;8BAAAA;AAADA,CAACA,6BAAA"} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-data.ts b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-data.ts new file mode 100644 index 000000000..786e92ce3 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-data.ts @@ -0,0 +1,81 @@ +module Orchard.Azure.MediaServices.VideoPlayer.Data { + + export interface IAssetData { + VideoAssets: IVideoAsset[]; + DynamicVideoAssets: IDynamicVideoAsset[]; + ThumbnailAssets: IThumbnailAsset[]; + SubtitleAssets: ISubtitleAsset[]; + } + + export interface IAsset { + Type: AssetType; + Id: number; + Name: string; + MimeType: string; + MainFileUrl: string; + MediaQuery: string; + } + + export enum AssetType { + VideoAsset, + DynamicVideoAsset, + ThumbnailAsset, + SubtitleAsset + } + + export interface IVideoAsset extends IAsset { + EncodingPreset: string; + EncoderMetadata: IEncoderMetadata; + } + + export interface IDynamicVideoAsset extends IVideoAsset { + SmoothStreamingUrl: string; + HlsUrl: string; + MpegDashUrl: string; + } + + export interface IThumbnailAsset extends IAsset { + } + + export interface ISubtitleAsset extends IAsset { + Language: string; + } + + export interface IEncoderMetadata { + AssetFiles: IAssetFile[]; + } + + export interface IAssetFile { + Name: string; + Size: number; + Duration: Duration; + AudioTracks: IAudioTrack[]; + VideoTracks: IVideoTrack[]; + Sources: string[]; + Bitrate: number; + MimeType: string; + } + + export interface IAudioTrack { + Index: number; + Bitrate: number; + SamplingRate: number; + BitsPerSample: number; + Channels: number; + Codec: string; + EncoderVersion: string; + } + + export interface IVideoTrack { + Index: number; + Bitrate: number; + TargetBitrate: number; + Framerate: number; + TargetFramerate: number; + FourCc: string; + Width: number; + Height: number; + DisplayRatioX: number; + DisplayRatioY: number; + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-injectors-alt.js b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-injectors-alt.js new file mode 100644 index 000000000..62221059d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-injectors-alt.js @@ -0,0 +1,63 @@ +/// +/// +var __extends = this.__extends || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + __.prototype = b.prototype; + d.prototype = new __(); +}; +var Orchard; +(function (Orchard) { + (function (Azure) { + (function (MediaServices) { + (function (VideoPlayer) { + (function (Injectors) { + var AltInjector = (function (_super) { + __extends(AltInjector, _super); + function AltInjector(container, playerWidth, playerHeight, assetData, applyMediaQueries, debugToConsole, nextInjector, alternateContent) { + _super.call(this, container, playerWidth, playerHeight, false, assetData, applyMediaQueries, debugToConsole, nextInjector); + this.alternateContent = alternateContent; + } + AltInjector.prototype.isSupported = function () { + return true; + }; + + AltInjector.prototype.inject = function () { + var firstThumbnailAsset = _(this.filteredAssets().ThumbnailAssets).first(); + + this.debug("Injecting alternate content into element '{0}'.", this.container.id); + + var wrapper = $("
").addClass("cloudvideo-player-alt-wrapper").css("width", this.playerWidth).css("height", this.playerHeight); + if (firstThumbnailAsset) + wrapper.css("background-image", "url('" + firstThumbnailAsset.MainFileUrl + "')"); + + var inner = $("
").addClass("cloudvideo-player-alt-inner").appendTo(wrapper); + + if (this.alternateContent) + _(this.alternateContent).each(function (elem) { + $(elem).appendTo(inner); + }); + + wrapper.appendTo(this.container); + }; + + AltInjector.prototype.debug = function (message) { + var args = []; + for (var _i = 0; _i < (arguments.length - 1); _i++) { + args[_i] = arguments[_i + 1]; + } + _super.prototype.debug.call(this, "AltInjector: " + message, args); + }; + return AltInjector; + })(Orchard.Azure.MediaServices.VideoPlayer.Injectors.Injector); + Injectors.AltInjector = AltInjector; + })(VideoPlayer.Injectors || (VideoPlayer.Injectors = {})); + var Injectors = VideoPlayer.Injectors; + })(MediaServices.VideoPlayer || (MediaServices.VideoPlayer = {})); + var VideoPlayer = MediaServices.VideoPlayer; + })(Azure.MediaServices || (Azure.MediaServices = {})); + var MediaServices = Azure.MediaServices; + })(Orchard.Azure || (Orchard.Azure = {})); + var Azure = Orchard.Azure; +})(Orchard || (Orchard = {})); +//# sourceMappingURL=cloudmedia-videoplayer-injectors-alt.js.map diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-injectors-alt.js.map b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-injectors-alt.js.map new file mode 100644 index 000000000..b9b730848 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-injectors-alt.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cloudmedia-videoplayer-injectors-alt.js","sourceRoot":"","sources":["cloudmedia-videoplayer-injectors-alt.ts"],"names":["Orchard","Orchard.Azure","Orchard.Azure.MediaServices","Orchard.Azure.MediaServices.VideoPlayer","Orchard.Azure.MediaServices.VideoPlayer.Injectors","Orchard.Azure.MediaServices.VideoPlayer.Injectors.AltInjector","Orchard.Azure.MediaServices.VideoPlayer.Injectors.AltInjector.constructor","Orchard.Azure.MediaServices.VideoPlayer.Injectors.AltInjector.isSupported","Orchard.Azure.MediaServices.VideoPlayer.Injectors.AltInjector.inject","Orchard.Azure.MediaServices.VideoPlayer.Injectors.AltInjector.debug"],"mappings":"AAAA,4CAA4C;AAC5C,gDAAgD;;;;;;;AAEhD,IAAO,OAAO;AA4Cb,CA5CD,UAAO,OAAO;KAAdA,UAAeA,KAAKA;SAApBC,UAAqBA,aAAaA;aAAlCC,UAAmCA,WAAWA;iBAA9CC,UAA+CA,SAASA;oBAIpDC;wBAAiCC,8BAAQA;wBAErCA,qBACIA,SAAsBA,EACtBA,WAAmBA,EACnBA,YAAoBA,EACpBA,SAA0BA,EAC1BA,iBAA0BA,EAC1BA,cAAuBA,EACvBA,YAAsBA,EACtBA,gBAAkCA;4BAAIC,WAAMA,OAAAA,SAASA,EAAEA,WAAWA,EAAEA,YAAYA,EAAEA,KAAKA,EAAEA,SAASA,EAAEA,iBAAiBA,EAAEA,cAAcA,EAAEA,YAAYA,CAACA;4BAApJA,qBAAwBA,GAAhBA,gBAAgBA;AAAUA,wBAAoHA,CAACA;wBAE3JD,oCAAAA;4BACIE,OAAOA,IAAIA;wBACfA,CAACA;;wBAEDF,+BAAAA;4BACIG,IAAIA,mBAAmBA,GAAGA,CAACA,CAACA,IAAIA,CAACA,cAAcA,CAACA,CAACA,CAACA,eAAeA,CAACA,CAACA,KAAKA,CAACA,CAACA;;4BAE1EA,IAAIA,CAACA,KAAKA,CAACA,iDAAiDA,EAAEA,IAAIA,CAACA,SAASA,CAACA,EAAEA,CAACA;;4BAEhFA,IAAIA,OAAOA,GAAGA,CAACA,CAACA,OAAOA,CAACA,CACnBA,QAAQA,CAACA,+BAA+BA,CAACA,CACzCA,GAAGA,CAACA,OAAOA,EAAEA,IAAIA,CAACA,WAAWA,CAACA,CAC9BA,GAAGA,CAACA,QAAQA,EAAEA,IAAIA,CAACA,YAAYA,CAACA;4BACrCA,IAAIA,mBAAmBA;gCACnBA,OAAOA,CAACA,GAAGA,CAACA,kBAAkBA,EAAEA,OAAOA,GAAGA,mBAAmBA,CAACA,WAAWA,GAAGA,IAAIA,CAACA,CAACA;;4BAEtFA,IAAIA,KAAKA,GAAGA,CAACA,CAACA,OAAOA,CAACA,CAACA,QAAQA,CAACA,6BAA6BA,CAACA,CAACA,QAAQA,CAACA,OAAOA,CAACA;;4BAEhFA,IAAIA,IAAIA,CAACA,gBAAgBA;gCACrBA,CAACA,CAACA,IAAIA,CAACA,gBAAgBA,CAACA,CAACA,IAAIA,CAACA,UAAAA,IAAIA;oCAAMA,CAACA,CAACA,IAAIA,CAACA,CAACA,QAAQA,CAACA,KAAKA,CAACA;gCAAEA,CAACA,CAACA,CAACA;;4BAExEA,OAAOA,CAACA,QAAQA,CAACA,IAAIA,CAACA,SAASA,CAACA;wBACpCA,CAACA;;wBAEDH,8BAAAA,UAAaA,OAAeA;4BAAEI,IAAGA,IAAIA;AAAOA,iCAAdA,WAAcA,CAAdA,2BAAcA,EAAdA,IAAcA;gCAAdA,6BAAcA;;4BACxCA,gBAAKA,CAACA,KAAKA,KAACA,OAAAA,eAAeA,GAAGA,OAAOA,EAAEA,IAAIA,CAACA;wBAChDA,CAACA;wBACLJ,mBAACA;oBAADA,CAACA,EAvCgCD,0DAAQA,EAuCxCA;oBAvCDA,oCAuCCA;gBACLA,CAACA,yDAAAD;sDAAAA;YAADA,CAACA,iEAAAD;wDAAAA;QAADA,CAACA,qDAAAD;gDAAAA;IAADA,CAACA,yCAAAD;8BAAAA;AAADA,CAACA,6BAAA"} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-injectors-alt.ts b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-injectors-alt.ts new file mode 100644 index 000000000..6e30a8e53 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-injectors-alt.ts @@ -0,0 +1,48 @@ +/// +/// + +module Orchard.Azure.MediaServices.VideoPlayer.Injectors { + + import Data = Orchard.Azure.MediaServices.VideoPlayer.Data; + + export class AltInjector extends Injector { + + constructor( + container: HTMLElement, + playerWidth: number, + playerHeight: number, + assetData: Data.IAssetData, + applyMediaQueries: boolean, + debugToConsole: boolean, + nextInjector: Injector, + private alternateContent: JQuery[]) { super(container, playerWidth, playerHeight, false, assetData, applyMediaQueries, debugToConsole, nextInjector); } + + public isSupported(): boolean { + return true; + } + + public inject(): void { + var firstThumbnailAsset = _(this.filteredAssets().ThumbnailAssets).first(); + + this.debug("Injecting alternate content into element '{0}'.", this.container.id); + + var wrapper = $("
") + .addClass("cloudvideo-player-alt-wrapper") + .css("width", this.playerWidth) + .css("height", this.playerHeight); + if (firstThumbnailAsset) + wrapper.css("background-image", "url('" + firstThumbnailAsset.MainFileUrl + "')"); + + var inner = $("
").addClass("cloudvideo-player-alt-inner").appendTo(wrapper); + + if (this.alternateContent) + _(this.alternateContent).each(elem => { $(elem).appendTo(inner); }); + + wrapper.appendTo(this.container); + } + + public debug(message: string, ...args: any[]): void { + super.debug("AltInjector: " + message, args); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-injectors-dash.js b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-injectors-dash.js new file mode 100644 index 000000000..2266a2085 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Azure.MediaServices/Scripts/cloudmedia-videoplayer-injectors-dash.js @@ -0,0 +1,87 @@ +/// +/// +var __extends = this.__extends || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + __.prototype = b.prototype; + d.prototype = new __(); +}; +var Orchard; +(function (Orchard) { + (function (Azure) { + (function (MediaServices) { + (function (VideoPlayer) { + (function (Injectors) { + var DashInjector = (function (_super) { + __extends(DashInjector, _super); + function DashInjector() { + _super.apply(this, arguments); + } + DashInjector.prototype.isSupported = function () { + var videoElement = document.createElement("video"); + var mse = window["MediaSource"] || window["WebKitMediaSource"]; + + var hasH264 = videoElement && videoElement.canPlayType && !!videoElement.canPlayType("video/mp4; codecs=\"avc1.42001E, mp4a.40.2\""); + var hasMse = mse && mse.isTypeSupported && mse.isTypeSupported("video/mp4; codecs=\"avc1.4d404f\""); + var hasDynamicAssets = _(this.filteredAssets().DynamicVideoAssets).any(); + + this.debug("Browser supports HTML5 video and the H264 and AAC codecs: {0}", hasH264); + this.debug("Browser supports the Media Source Extensions API: {0}", hasMse); + this.debug("Item has at least one dynamic video asset: {0}", hasDynamicAssets); + + var result = hasH264 && hasMse && hasDynamicAssets; + this.debug("isSupported() returns {0}.", result); + + return result; + }; + + DashInjector.prototype.inject = function () { + var _this = this; + var firstDynamicAsset = _(this.filteredAssets().DynamicVideoAssets).first(); + var firstThumbnailAsset = _(this.filteredAssets().ThumbnailAssets).first(); + + this.debug("Injecting player into element '{0}'.", this.container.id); + + var videoElement = $("