From e44822e74f8368d4e21d43b8cd95a8c03778ee07 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Fri, 26 Oct 2012 10:48:10 -0400 Subject: [PATCH 01/27] FLUID-4558: Move toward error handling by starting with console logs, to identify when/where errors happen --- js/VideoPlayer.js | 12 +++++++++++- js/VideoPlayer_media.js | 17 +++++++++++++++++ js/VideoPlayer_transcript.js | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/js/VideoPlayer.js b/js/VideoPlayer.js index ba13a04..756a65d 100644 --- a/js/VideoPlayer.js +++ b/js/VideoPlayer.js @@ -699,7 +699,17 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt // Hard coded URL to amara here var url = encodeURI("http://www.universalsubtitles.org/api/1.0/subtitles/?video_url=" + videoUrl + "&callback=?"); - $.getJSON(url, callback); + $.ajax({ + url: url, + dataType: 'json', + success: callback, + timeout: 1500, // only this timeout will force the error function to be called + error: function () { + console.log("Error loading transcript from Amara: " + videoUrl); + } + }); + + }; })(jQuery); diff --git a/js/VideoPlayer_media.js b/js/VideoPlayer_media.js index 8cde784..a6e5c3b 100644 --- a/js/VideoPlayer_media.js +++ b/js/VideoPlayer_media.js @@ -19,12 +19,25 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt (function ($) { + fluid.registerNamespace("fluid.videoPlayer.media"); + /********************************************************************************* * Video Player Media * * * * Composes markup for video sources and responds to the video events * *********************************************************************************/ + /* These media error codes and descriptions were taken from + * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-media-error + * TODO: Find better strings!! + */ + fluid.videoPlayer.media.errorStrings = { + "1": "The fetching process for the media resource was aborted by the user agent at the user's request.", + "2": "A network error of some description caused the user agent to stop fetching the media resource, after the resource was established to be usable.", + "3": "An error of some description occurred while decoding the media resource, after the resource was established to be usable.", + "4": "The media resource indicated by the src attribute was not suitable." + }; + fluid.defaults("fluid.videoPlayer.media", { gradeNames: ["fluid.viewComponent", "autoInit"], components: { @@ -189,6 +202,10 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }); + mediaElementVideo.addEventListener("error", function (err) { + console.log("Error: " + fluid.videoPlayer.media.errorStrings[mediaElementVideo.error.code]); + }); + // Fire onMediaReady here rather than finalInit() because the instantiation // of the media element object is asynchronous that.events.onMediaReady.fire(that); diff --git a/js/VideoPlayer_transcript.js b/js/VideoPlayer_transcript.js index 192b0f5..debe593 100644 --- a/js/VideoPlayer_transcript.js +++ b/js/VideoPlayer_transcript.js @@ -280,7 +280,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.videoPlayer.transcript.parseTranscriptFile(that, data, currentIndex, that.convertToMilli, "transcript", "inTime", "outTime"); }, error: function () { - fluid.log("Error loading transcript: " + transcriptSource.src + ". Are you sure this file exists?"); + console.log("Error loading transcript: " + transcriptSource.src + ". Are you sure this file exists?"); that.events.onLoadTranscriptError.fire(transcriptSource); } }; From 82e26f0e97dc56e44cad879dd1e22b01a5ddf984 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Fri, 26 Oct 2012 11:43:35 -0400 Subject: [PATCH 02/27] FLUID-4558: Fix error message for Amara files. --- js/VideoPlayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/VideoPlayer.js b/js/VideoPlayer.js index 756a65d..1f3dba5 100644 --- a/js/VideoPlayer.js +++ b/js/VideoPlayer.js @@ -705,7 +705,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt success: callback, timeout: 1500, // only this timeout will force the error function to be called error: function () { - console.log("Error loading transcript from Amara: " + videoUrl); + console.log("Error loading file from Amara: " + videoUrl); } }); From a3b156a7cb4865501e269adfc187d36ef1afe550 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Mon, 29 Oct 2012 16:38:41 -0400 Subject: [PATCH 03/27] FLUID-4558: First pass at rudimentary error handling. Still needs work. --- css/VideoPlayer.css | 14 ++++++++++++ html/videoPlayer_template.html | 2 ++ js/MenuButton.js | 24 +++++++++++++++----- js/VideoPlayer.js | 36 +++++++++++++++++++++--------- js/VideoPlayer_controllers.js | 11 ++++++--- js/VideoPlayer_html5Captionator.js | 20 +++++++++++++++-- js/VideoPlayer_transcript.js | 11 +++++---- tests/js/MenuButtonTests.js | 21 +++++++++++++++++ 8 files changed, 114 insertions(+), 25 deletions(-) diff --git a/css/VideoPlayer.css b/css/VideoPlayer.css index 6b6696a..a21e584 100644 --- a/css/VideoPlayer.css +++ b/css/VideoPlayer.css @@ -34,6 +34,14 @@ .fl-theme-uio-by .fl-videoPlayer-video-element:focus, .fl-theme-uio-bw .fl-videoPlayer-video-element:focus { outline-color: #000000; } +/* + * Error message area + */ +.fl-videoPlayer-errorMessage { + border: 1px solid red; + color: red; +} + /* * Controller area */ @@ -374,6 +382,12 @@ a.fl-videoPlayer-button-wrapper { .fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem:hover { background-color: #ffcc00; } +.fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem-disabled { + text-decoration: line-through; +} +.fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem.fl-videoPlayer-menuItem-disabled:hover { + background-color: #FFFFFF; +} .fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem-selected, .fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem-active.fl-videoPlayer-menuItem-selected { color: #FFFFFF; diff --git a/html/videoPlayer_template.html b/html/videoPlayer_template.html index 00212d1..6a1d2a8 100644 --- a/html/videoPlayer_template.html +++ b/html/videoPlayer_template.html @@ -71,5 +71,7 @@
+
+ diff --git a/js/MenuButton.js b/js/MenuButton.js index 6b302f9..5353332 100644 --- a/js/MenuButton.js +++ b/js/MenuButton.js @@ -41,7 +41,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt onReady: null, activated: null, hiddenByKeyboard: null, - onControlledElementReady: null + onControlledElementReady: null, + onLoadLanguageError: null }, listeners: { onControlledElementReady: { @@ -61,7 +62,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }, styles: { selected: "fl-videoPlayer-menuItem-selected", - active: "fl-videoPlayer-menuItem-active" + active: "fl-videoPlayer-menuItem-active", + disabled: "fl-videoPlayer-menuItem-disabled" }, invokers: { updateTracks: { funcName: "fluid.videoPlayer.languageMenu.updateTracks", args: ["{languageMenu}"] }, @@ -112,7 +114,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.videoPlayer.languageMenu.setUpKeyboardA11y = function (that) { that.container.fluid("tabbable"); - that.container.fluid("selectable", { + that.selectableContext = fluid.selectable(that.container, { direction: fluid.a11y.orientation.VERTICAL, selectableSelector: that.options.selectors.menuItem, // TODO: add simple style class support to selectable @@ -203,12 +205,21 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt that.writeIndirect("showHidePath", !that.readIndirect("showHidePath"), "menuButton"); that.hideMenu(); }; + that.disableItem = function (index) { + var item = $(that.locate("language")[index]); + item.attr("aria-disabled", true); + item.addClass(that.options.styles.disabled); + item.removeClass(that.options.selectors.menuItem.substring(1)); + that.selectableContext.refresh(); + }; }; fluid.videoPlayer.languageMenu.finalInit = function (that) { fluid.videoPlayer.languageMenu.bindEventListeners(that); fluid.videoPlayer.languageMenu.setUpKeyboardA11y(that); + that.events.onLoadLanguageError.addListener(that.disableItem); + that.container.attr("role", "menu"); that.hideMenu(); that.updateTracks(); @@ -234,7 +245,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt events: { onReady: null, onRenderingComplete: null, - onControlledElementReady: null + onControlledElementReady: null, + onLoadLanguageError: null }, languages: [], currentLanguagePath: "", @@ -274,7 +286,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt currentLanguagePath: "{languageControls}.options.currentLanguagePath", strings: "{languageControls}.options.strings", events: { - onControlledElementReady: "{languageControls}.events.onControlledElementReady" + onControlledElementReady: "{languageControls}.events.onControlledElementReady", + onLoadLanguageError: "{languageControls}.events.onLoadLanguageError" } } }, @@ -343,6 +356,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt var showHide = that.readIndirect("showHidePath"); that.button.locate("button").toggleClass(that.options.styles.buttonWithShowing, showHide); } + that.applier.modelChanged.addListener(that.options.showHidePath, refreshButtonClass); refreshButtonClass(); that.events.onReady.fire(that); diff --git a/js/VideoPlayer.js b/js/VideoPlayer.js index 342cc22..cd07c60 100644 --- a/js/VideoPlayer.js +++ b/js/VideoPlayer.js @@ -158,7 +158,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt onTranscriptHide: "{videoPlayer}.events.onTranscriptHide", onTranscriptShow: "{videoPlayer}.events.onTranscriptShow", onTranscriptElementChange: "{videoPlayer}.events.onTranscriptElementChange", - onTranscriptsLoaded: "{videoPlayer}.events.onTranscriptsLoaded" + onTranscriptsLoaded: "{videoPlayer}.events.onTranscriptsLoaded", + onLoadTranscriptError: "{videoPlayer}.events.onLoadTranscriptError" } } } @@ -185,7 +186,9 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt onScrub: "{videoPlayer}.events.onScrub", afterScrub: "{videoPlayer}.events.afterScrub", onTranscriptsReady: "{videoPlayer}.events.canBindTranscriptMenu", - onCaptionsReady: "{videoPlayer}.events.canBindCaptionMenu" + onCaptionsReady: "{videoPlayer}.events.canBindCaptionMenu", + onLoadCaptionError: "{videoPlayer}.events.onLoadCaptionError", + onLoadTranscriptError: "{videoPlayer}.events.onLoadTranscriptError" } } }, @@ -198,7 +201,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt applier: "{videoPlayer}.applier", captions: "{videoPlayer}.options.video.captions", events: { - onReady: "{videoPlayer}.events.onCaptionsReady" + onReady: "{videoPlayer}.events.onCaptionsReady", + onLoadCaptionError: "{videoPlayer}.events.onLoadCaptionError" } } } @@ -216,10 +220,12 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt afterScrub: null, onStartScrub: null, onTemplateLoadError: null, + onLoadCaptionError: null, onCurrentTranscriptChanged: null, onTranscriptHide: null, onTranscriptShow: null, onTranscriptElementChange: null, + onLoadTranscriptError: null, onReady: null, // public, time events @@ -263,7 +269,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt caption: ".flc-videoPlayer-captionArea", controllers: ".flc-videoPlayer-controller", transcript: ".flc-videoPlayer-transcriptArea", - overlay: ".flc-videoPlayer-overlay" + overlay: ".flc-videoPlayer-overlay", + errorMessage: ".flc-videoPlayer-errorMessage" }, strings: { captionsOff: "Captions OFF", @@ -272,7 +279,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt turnTranscriptsOff: "Turn Transcripts OFF", videoTitlePreface: "Video" }, - selectorsToIgnore: ["overlay", "caption", "videoPlayer", "transcript", "video", "videoContainer"], + selectorsToIgnore: ["overlay", "caption", "videoPlayer", "transcript", "video", "videoContainer", "errorMessage"], keyBindings: fluid.videoPlayer.defaultKeys, produceTree: "fluid.videoPlayer.produceTree", controls: "custom", @@ -580,6 +587,15 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt $("object", that.locate("video")).attr("tabindex", "-1"); that.events.onReady.fire(that); + + // TODO: this needs to be reworked + that.events.onLoadTranscriptError.addListener(function (index, source) { + that.locate("errorMessage").text("Error loading transcript: " + source.label); + }); + that.events.onLoadCaptionError.addListener(function (index, source) { + that.locate("errorMessage").text("Error loading caption: " + source.label); + }); + }); return that; @@ -689,9 +705,9 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt return vtt; }; - fluid.videoPlayer.fetchAmaraJson = function (videoUrl, callback) { + fluid.videoPlayer.fetchAmaraJson = function (videoUrl, success, error) { // No point continuing because we can't get a useful JSONP response without the url and a callback - if (!videoUrl || !callback) { + if (!videoUrl || !success) { return; } @@ -701,11 +717,9 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt $.ajax({ url: url, dataType: 'json', - success: callback, + success: success, timeout: 1500, // only this timeout will force the error function to be called - error: function () { - console.log("Error loading file from Amara: " + videoUrl); - } + error: error }); diff --git a/js/VideoPlayer_controllers.js b/js/VideoPlayer_controllers.js index f86e707..cfcee85 100644 --- a/js/VideoPlayer_controllers.js +++ b/js/VideoPlayer_controllers.js @@ -84,7 +84,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt release: "Captions" }, events: { - onControlledElementReady: "{controllers}.events.onCaptionsReady" + onControlledElementReady: "{controllers}.events.onCaptionsReady", + onLoadLanguageError: "{controllers}.events.onLoadCaptionError" } } }, @@ -112,7 +113,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt release: "Transcripts" }, events: { - onControlledElementReady: "{controllers}.events.onTranscriptsReady" + onControlledElementReady: "{controllers}.events.onTranscriptsReady", + onLoadLanguageError: "{controllers}.events.onLoadTranscriptError" } } }, @@ -171,7 +173,10 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt // private event used for associating transcript menu with transcript via ARIA onTranscriptsReady: null, - onCaptionsReady: null + onCaptionsReady: null, + + onLoadTranscriptError: null, + onLoadCaptionError: null }, selectors: { diff --git a/js/VideoPlayer_html5Captionator.js b/js/VideoPlayer_html5Captionator.js index 956de31..4647b93 100644 --- a/js/VideoPlayer_html5Captionator.js +++ b/js/VideoPlayer_html5Captionator.js @@ -33,7 +33,8 @@ https://source.fluidproject.org/svn/LICENSE.txt events: { afterTrackElCreated: null, onTracksReady: null, - onReady: null + onReady: null, + onLoadCaptionError: null }, elPaths: { currentCaptions: "currentTracks.captions", @@ -83,6 +84,16 @@ https://source.fluidproject.org/svn/LICENSE.txt if (display) { fluid.videoPlayer.html5Captionator.showCurrentTrack(that.readIndirect("elPaths.currentCaptions"), tracks, that.options.captions); + + // captionator doesn't fire any events or support a configurable error callback, + // so we have to wait a bit and check the track's readyState + setTimeout(function () { + fluid.each($("track", that.locate("video")), function (element, key) { + if (element.track.readyState === captionator.TextTrack.ERROR) { + that.events.onLoadCaptionError.fire(key, that.options.captions[key]); + } + }); + }, 3000); } else { fluid.videoPlayer.html5Captionator.hideAllTracks(tracks); } @@ -129,7 +140,11 @@ https://source.fluidproject.org/svn/LICENSE.txt that.events.afterTrackElCreated.fire(that); }; - fluid.videoPlayer.fetchAmaraJson(opts.src, afterFetch); + var errorHandler = function () { + that.events.onLoadCaptionError.fire(key, opts); + }; + + fluid.videoPlayer.fetchAmaraJson(opts.src, afterFetch, errorHandler); }; fluid.videoPlayer.html5Captionator.createVttTrack = function (that, key, opts) { @@ -152,6 +167,7 @@ https://source.fluidproject.org/svn/LICENSE.txt appendCueCanvasTo: that.locate("caption")[0], sizeCuesByTextBoundingBox: true }); + bindCaptionatorModel(that); that.events.onReady.fire(that, fluid.allocateSimpleId(that.locate("caption"))); }; diff --git a/js/VideoPlayer_transcript.js b/js/VideoPlayer_transcript.js index debe593..9efc77c 100644 --- a/js/VideoPlayer_transcript.js +++ b/js/VideoPlayer_transcript.js @@ -269,9 +269,13 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt // Handle Universal Subtitles JSON files for transcripts if (transcriptSource.type === "text/amarajson") { - fluid.videoPlayer.fetchAmaraJson(transcriptSource.src, function (data) { + var handler = function (data) { fluid.videoPlayer.transcript.parseTranscriptFile(that, data, currentIndex, that.convertSecsToMilli, "text", "start_time", "end_time"); - }); + }; + var errorHandler = function () { + that.events.onLoadTranscriptError.fire(currentIndex, transcriptSource); + }; + fluid.videoPlayer.fetchAmaraJson(transcriptSource.src, handler, errorHandler); } else { var opts = { type: "GET", @@ -280,8 +284,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.videoPlayer.transcript.parseTranscriptFile(that, data, currentIndex, that.convertToMilli, "transcript", "inTime", "outTime"); }, error: function () { - console.log("Error loading transcript: " + transcriptSource.src + ". Are you sure this file exists?"); - that.events.onLoadTranscriptError.fire(transcriptSource); + that.events.onLoadTranscriptError.fire(currentIndex, transcriptSource); } }; diff --git a/tests/js/MenuButtonTests.js b/tests/js/MenuButtonTests.js index 2e43b71..a32c786 100644 --- a/tests/js/MenuButtonTests.js +++ b/tests/js/MenuButtonTests.js @@ -248,5 +248,26 @@ fluid.registerNamespace("fluid.tests"); }); }); + menuButtonTests.asyncTest("Disabling menu item", function () { + jqUnit.expect(6); + var testControls = fluid.tests.initLangControls({ + listeners: { + onReady: function (that) { + var languageIndex = 1; + var item = $($(".flc-videoPlayer-menuItem")[languageIndex]); + jqUnit.assertTrue("Menu item is initially selectable", item.hasClass("flc-videoPlayer-menuItem")); + jqUnit.assertUndefined("Menu item is not aria-disabled", item.attr("aria-disabled")); + jqUnit.assertFalse("Menu item is normally styled", item.hasClass("fl-videoPlayer-menuItem-disabled")); + that.menu.disableItem(languageIndex); + jqUnit.assertFalse("Menu item not selectable", item.hasClass("flc-videoPlayer-menuItem")); + jqUnit.assertTrue("Menu item is aria-disabled", !!item.attr("aria-disabled")); + jqUnit.assertTrue("Menu item has disabled styling", item.hasClass("fl-videoPlayer-menuItem-disabled")); + + start(); + } + } + }); + }); + }); })(jQuery); From 757175f10e8f568f7db42d52d03d5d61e28d14ca Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Tue, 30 Oct 2012 12:24:58 -0400 Subject: [PATCH 04/27] FLUID-4558: Use events and error messag display for video load errors. --- js/VideoPlayer.js | 20 +++++++++++++++----- js/VideoPlayer_html5Captionator.js | 4 ++-- js/VideoPlayer_media.js | 5 +++-- js/VideoPlayer_transcript.js | 4 ++-- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/js/VideoPlayer.js b/js/VideoPlayer.js index cd07c60..aa4a2ec 100644 --- a/js/VideoPlayer.js +++ b/js/VideoPlayer.js @@ -166,7 +166,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }, events: { onLoadedMetadata: "{videoPlayer}.events.onLoadedMetadata", - onMediaReady: "{videoPlayer}.events.onMediaReady" + onMediaReady: "{videoPlayer}.events.onMediaReady", + onMediaLoadError: "{videoPlayer}.events.onMediaLoadError" }, sources: "{videoPlayer}.options.video.sources" } @@ -216,6 +217,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt onViewReady: null, onLoadedMetadata: null, onMediaReady: null, + onMediaLoadError: null, onControllersReady: null, afterScrub: null, onStartScrub: null, @@ -589,11 +591,19 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt that.events.onReady.fire(that); // TODO: this needs to be reworked - that.events.onLoadTranscriptError.addListener(function (index, source) { - that.locate("errorMessage").text("Error loading transcript: " + source.label); + var handleLoadError = function (trackType, source, display) { + if (display) { + that.locate("errorMessage").text("Error loading " + trackType + ": " + source.label); + } + }; + that.events.onLoadTranscriptError.addListener(function (index, source, display) { + handleLoadError("transcript", source, display); + }); + that.events.onLoadCaptionError.addListener(function (index, source, display) { + handleLoadError("caption", source, display); }); - that.events.onLoadCaptionError.addListener(function (index, source) { - that.locate("errorMessage").text("Error loading caption: " + source.label); + that.events.onMediaLoadError.addListener(function (message) { + that.locate("errorMessage").append(message); }); }); diff --git a/js/VideoPlayer_html5Captionator.js b/js/VideoPlayer_html5Captionator.js index 4647b93..0a305d7 100644 --- a/js/VideoPlayer_html5Captionator.js +++ b/js/VideoPlayer_html5Captionator.js @@ -90,7 +90,7 @@ https://source.fluidproject.org/svn/LICENSE.txt setTimeout(function () { fluid.each($("track", that.locate("video")), function (element, key) { if (element.track.readyState === captionator.TextTrack.ERROR) { - that.events.onLoadCaptionError.fire(key, that.options.captions[key]); + that.events.onLoadCaptionError.fire(key, that.options.captions[key], true); } }); }, 3000); @@ -141,7 +141,7 @@ https://source.fluidproject.org/svn/LICENSE.txt }; var errorHandler = function () { - that.events.onLoadCaptionError.fire(key, opts); + that.events.onLoadCaptionError.fire(key, opts, false); }; fluid.videoPlayer.fetchAmaraJson(opts.src, afterFetch, errorHandler); diff --git a/js/VideoPlayer_media.js b/js/VideoPlayer_media.js index a6e5c3b..8adaba1 100644 --- a/js/VideoPlayer_media.js +++ b/js/VideoPlayer_media.js @@ -58,7 +58,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt preInitFunction: "fluid.videoPlayer.media.preInit", events: { onLoadedMetadata: null, - onMediaReady: null + onMediaReady: null, + onMediaLoadError: null }, sourceRenderers: { "video/mp4": "fluid.videoPlayer.media.createSourceMarkup.html5SourceTag", @@ -203,7 +204,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }); mediaElementVideo.addEventListener("error", function (err) { - console.log("Error: " + fluid.videoPlayer.media.errorStrings[mediaElementVideo.error.code]); + that.events.onMediaLoadError.fire("Error: " + fluid.videoPlayer.media.errorStrings[mediaElementVideo.error.code]); }); // Fire onMediaReady here rather than finalInit() because the instantiation diff --git a/js/VideoPlayer_transcript.js b/js/VideoPlayer_transcript.js index 9efc77c..be68a24 100644 --- a/js/VideoPlayer_transcript.js +++ b/js/VideoPlayer_transcript.js @@ -273,7 +273,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.videoPlayer.transcript.parseTranscriptFile(that, data, currentIndex, that.convertSecsToMilli, "text", "start_time", "end_time"); }; var errorHandler = function () { - that.events.onLoadTranscriptError.fire(currentIndex, transcriptSource); + that.events.onLoadTranscriptError.fire(currentIndex, transcriptSource, false); }; fluid.videoPlayer.fetchAmaraJson(transcriptSource.src, handler, errorHandler); } else { @@ -284,7 +284,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.videoPlayer.transcript.parseTranscriptFile(that, data, currentIndex, that.convertToMilli, "transcript", "inTime", "outTime"); }, error: function () { - that.events.onLoadTranscriptError.fire(currentIndex, transcriptSource); + that.events.onLoadTranscriptError.fire(currentIndex, transcriptSource, true); } }; From 9f57081f072a8cd85aa091b8a0471de4e94021ab Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Tue, 30 Oct 2012 12:36:39 -0400 Subject: [PATCH 05/27] FLUID-4558: Ensure other captions load even if there's an error with an amara caption. --- js/VideoPlayer_html5Captionator.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/VideoPlayer_html5Captionator.js b/js/VideoPlayer_html5Captionator.js index 0a305d7..e9cc585 100644 --- a/js/VideoPlayer_html5Captionator.js +++ b/js/VideoPlayer_html5Captionator.js @@ -142,6 +142,7 @@ https://source.fluidproject.org/svn/LICENSE.txt var errorHandler = function () { that.events.onLoadCaptionError.fire(key, opts, false); + that.events.afterTrackElCreated.fire(that); }; fluid.videoPlayer.fetchAmaraJson(opts.src, afterFetch, errorHandler); From f9abde7d8917826197ba1b796bbf1e6cbdd3470a Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Tue, 30 Oct 2012 12:44:21 -0400 Subject: [PATCH 06/27] FLUID-4558: Ensure disabled language menu item is not clickable. --- js/MenuButton.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/MenuButton.js b/js/MenuButton.js index 5353332..33363a4 100644 --- a/js/MenuButton.js +++ b/js/MenuButton.js @@ -207,6 +207,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }; that.disableItem = function (index) { var item = $(that.locate("language")[index]); + item.unbind("click"); item.attr("aria-disabled", true); item.addClass(that.options.styles.disabled); item.removeClass(that.options.selectors.menuItem.substring(1)); From bd0c56d9277a8e2ec9ffd23997aeab028b9515d4 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Tue, 30 Oct 2012 16:05:09 -0400 Subject: [PATCH 07/27] FLUID-4558: Tests for various loading errors. Could use a bit more refactoring. --- tests/all-tests.html | 3 +- tests/html/VideoPlayerErrors-test.html | 48 +++++ tests/js/VideoPlayerErrorsTests.js | 245 +++++++++++++++++++++++++ 3 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 tests/html/VideoPlayerErrors-test.html create mode 100644 tests/js/VideoPlayerErrorsTests.js diff --git a/tests/all-tests.html b/tests/all-tests.html index 031343d..b7762ad 100644 --- a/tests/all-tests.html +++ b/tests/all-tests.html @@ -21,7 +21,8 @@ "./html/VideoPlayerIntervalEventsConductor-test.html", "./html/VideoPlayerIntervalEventsConductorIntegration-test.html", "./html/VideoPlayer-test.html", - "./html/VideoPlayerIntegration-test.html" + "./html/VideoPlayerIntegration-test.html", + "./html/VideoPlayerErrors-test.html" ]); diff --git a/tests/html/VideoPlayerErrors-test.html b/tests/html/VideoPlayerErrors-test.html new file mode 100644 index 0000000..a62c376 --- /dev/null +++ b/tests/html/VideoPlayerErrors-test.html @@ -0,0 +1,48 @@ + + + + + Video Player Error Handling Test Suite + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Video Player Error Handling Test Suite

+

+
+

+
    +
    + +
    + +
    + + + diff --git a/tests/js/VideoPlayerErrorsTests.js b/tests/js/VideoPlayerErrorsTests.js new file mode 100644 index 0000000..992f08d --- /dev/null +++ b/tests/js/VideoPlayerErrorsTests.js @@ -0,0 +1,245 @@ +/* +Copyright 2012 OCAD University + +Licensed under the Educational Community License (ECL), Version 2.0 or the New +BSD license. You may not use this file except in compliance with one these +Licenses. + +You may obtain a copy of the ECL 2.0 License and BSD License at +https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt + +*/ + +// Declare dependencies +/*global fluid, jqUnit, jQuery, start*/ + +// JSLint options +/*jslint white: true, funcinvoke: true, undef: true, newcap: true, nomen: true, regexp: true, bitwise: true, browser: true, forin: true, maxerr: 100, indent: 4 */ + +(function ($) { + $(document).ready(function () { + + fluid.registerNamespace("fluid.tests"); + + var testsCompleted; + var setup = function () { + testsCompleted = false; + }; + + var timeoutId; + var videoPlayerErrorsTests = new jqUnit.TestCase("Video Player Error Handling Tests", setup); + + var baseOpts = { + video: { + sources: [ + { + src: "../../demos/videos/ReorganizeFuture/ReorganizeFuture.webm", + type: "video/webm" + } + ] + }, + templates: { + videoPlayer: { + forceCache: true, + href: "../../html/videoPlayer_template.html" + } + } + }; + + fluid.tests.initVideoPlayer = function () { + var opts = fluid.copy(baseOpts); + var container = arguments[0]; + for (var index = 1; index < arguments.length; index++) { + $.extend(true, opts, arguments[index]); + } + return fluid.videoPlayer(container, opts); + }; + + fluid.tests.testMenuItemBeforeLoad = function (itemSelector) { + var item = $(itemSelector); + jqUnit.assertEquals("Before language is selected, language is present in caption menu", 1, item.length); + jqUnit.assertTrue("Before language is selected, language is selectable in caption menu", item.hasClass("flc-videoPlayer-menuItem")); + jqUnit.assertFalse("Before language is selected, language is not styled as disabled in caption menu", item.hasClass("fl-videoPlayer-menuItem-disabled")); + }; + + fluid.tests.testMenuItemAfterLoadError = function (itemSelector, displayMessage) { + jqUnit.assertTrue("Error event fires", true); + var msgLen = $(".flc-videoPlayer-errorMessage").text().length; + jqUnit.assertTrue("Error message is " + (displayMessage ? "" : "not ") + "displayed", (displayMessage ? msgLen > 0 : msgLen === 0)); + var item = $(itemSelector); + jqUnit.assertEquals("Language is still present in caption menu", 1, item.length); + jqUnit.assertFalse("Language is not selectable in caption menu", item.hasClass("flc-videoPlayer-menuItem")); + jqUnit.assertTrue("Language is styled as disabled in caption menu", item.hasClass("fl-videoPlayer-menuItem-disabled")); + }; + + fluid.tests.runTestWithTimeout = function (config) { + videoPlayerErrorsTests.asyncTest(config.desc, function () { + jqUnit.expect(config.expect); + fluid.tests.initVideoPlayer($(".videoPlayer-errors"), config.opts); + timeoutId = setTimeout(function () { + if (!testsCompleted) { + jqUnit.assertFalse("Expected error event didn't fire", true); + start(); + } + }, 5000); + }); + }; + + fluid.tests.runTestWithTimeout({ + desc: "Video load error", + expect: 2, + opts: { + video: { + sources: [ + { + src: "bad.video.webm", + type: "video/webm" + } + ] + }, + listeners: { + onMediaLoadError: { + listener: function (that) { + jqUnit.assertTrue("Error event fires", true); + jqUnit.assertTrue("Error message is displayed", $(".flc-videoPlayer-errorMessage").text().length > 0); + testsCompleted = true; + clearTimeout(timeoutId); + start(); + }, + priority: "last" + } + } + } + }); + + var captionItemSelector = ".flc-videoPlayer-captionControls-container .flc-videoPlayer-language"; + + fluid.tests.runTestWithTimeout({ + desc: "Caption (amara) load error", + expect: 5, + opts: { + video: { + captions: [ + { + src: "bad.amara.url", + type: "text/amarajson", + srclang: "en", + label: "English" + } + ] + }, + listeners: { + onLoadCaptionError: { + listener: function (that) { + fluid.tests.testMenuItemAfterLoadError(captionItemSelector, false); + testsCompleted = true; + clearTimeout(timeoutId); + start(); + }, + priority: "last" + } + } + } + }); + + fluid.tests.runTestWithTimeout({ + desc: "Caption (non-amara) load error", + expect: 8, + opts: { + video: { + captions: [ + { + src: "bad.vtt.url", + type: "text/vtt", + srclang: "en", + label: "English" + } + ] + }, + listeners: { + onReady: function (tjat) { + fluid.tests.testMenuItemBeforeLoad(captionItemSelector); + $(captionItemSelector).click(); + }, + onLoadCaptionError: { + listener: function (that) { + fluid.tests.testMenuItemAfterLoadError(captionItemSelector, true); + testsCompleted = true; + clearTimeout(timeoutId); + start(); + }, + priority: "last" + } + } + } + }); + + var transcriptItemSelector = ".flc-videoPlayer-transcriptControls-container .flc-videoPlayer-language"; + + fluid.tests.runTestWithTimeout({ + desc: "Transcript (amara, default selection) load error", + expect: 5, + opts: { + video: { + transcripts: [ + { + src: "bad.amara.url", + type: "text/amarajson", + srclang: "en", + label: "English" + } + ] + }, + model: { + currentTracks: { + transcripts: [0] + } + }, + listeners: { + onLoadTranscriptError: { + listener: function (that) { + fluid.tests.testMenuItemAfterLoadError(transcriptItemSelector, false); + testsCompleted = true; + clearTimeout(timeoutId); + start(); + }, + priority: "last" + } + } + } + }); + + fluid.tests.runTestWithTimeout({ + desc: "Transcript (non-amara) load error", + expect: 8, + opts: { + video: { + transcripts: [ + { + src: "bad.json.url", + type: "JSONcc", + srclang: "en", + label: "English" + } + ] + }, + listeners: { + onReady: function (tjat) { + fluid.tests.testMenuItemBeforeLoad(transcriptItemSelector); + $(transcriptItemSelector).click(); + }, + onLoadTranscriptError: { + listener: function (that) { + fluid.tests.testMenuItemAfterLoadError(transcriptItemSelector, true); + testsCompleted = true; + clearTimeout(timeoutId); + start(); + }, + priority: "last" + } + } + } + }); + }); + +})(jQuery); From 5bb93235834beb7ce0801e86e703dea1bca5a70a Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Tue, 30 Oct 2012 16:27:12 -0400 Subject: [PATCH 08/27] FLUID-4558: Refactor tests to reduce duplication. --- tests/js/VideoPlayerErrorsTests.js | 95 +++++++++++++----------------- 1 file changed, 40 insertions(+), 55 deletions(-) diff --git a/tests/js/VideoPlayerErrorsTests.js b/tests/js/VideoPlayerErrorsTests.js index 992f08d..2446f3e 100644 --- a/tests/js/VideoPlayerErrorsTests.js +++ b/tests/js/VideoPlayerErrorsTests.js @@ -29,6 +29,9 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt var timeoutId; var videoPlayerErrorsTests = new jqUnit.TestCase("Video Player Error Handling Tests", setup); + var captionItemSelector = ".flc-videoPlayer-captionControls-container .flc-videoPlayer-language"; + var transcriptItemSelector = ".flc-videoPlayer-transcriptControls-container .flc-videoPlayer-language"; + var baseOpts = { video: { sources: [ @@ -55,6 +58,39 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt return fluid.videoPlayer(container, opts); }; + fluid.tests.makeListenersForLoadTriggeredTest = function (selector, errorEventName) { + var obj = {}; + obj[errorEventName] = { + listener: function (that) { + fluid.tests.testMenuItemAfterLoadError(selector, false); + testsCompleted = true; + clearTimeout(timeoutId); + start(); + }, + priority: "last" + }; + return obj; + }; + + fluid.tests.makeListenersForClickTriggeredTest = function (selector, errorEventName) { + var obj = { + onReady: function (tjat) { + fluid.tests.testMenuItemBeforeLoad(selector); + $(selector).click(); + } + }; + obj[errorEventName] = { + listener: function (that) { + fluid.tests.testMenuItemAfterLoadError(selector, true); + testsCompleted = true; + clearTimeout(timeoutId); + start(); + }, + priority: "last" + }; + return obj; + }; + fluid.tests.testMenuItemBeforeLoad = function (itemSelector) { var item = $(itemSelector); jqUnit.assertEquals("Before language is selected, language is present in caption menu", 1, item.length); @@ -112,7 +148,6 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } }); - var captionItemSelector = ".flc-videoPlayer-captionControls-container .flc-videoPlayer-language"; fluid.tests.runTestWithTimeout({ desc: "Caption (amara) load error", @@ -128,17 +163,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } ] }, - listeners: { - onLoadCaptionError: { - listener: function (that) { - fluid.tests.testMenuItemAfterLoadError(captionItemSelector, false); - testsCompleted = true; - clearTimeout(timeoutId); - start(); - }, - priority: "last" - } - } + listeners: fluid.tests.makeListenersForLoadTriggeredTest(captionItemSelector, "onLoadCaptionError") } }); @@ -156,26 +181,10 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } ] }, - listeners: { - onReady: function (tjat) { - fluid.tests.testMenuItemBeforeLoad(captionItemSelector); - $(captionItemSelector).click(); - }, - onLoadCaptionError: { - listener: function (that) { - fluid.tests.testMenuItemAfterLoadError(captionItemSelector, true); - testsCompleted = true; - clearTimeout(timeoutId); - start(); - }, - priority: "last" - } - } + listeners: fluid.tests.makeListenersForClickTriggeredTest(captionItemSelector, "onLoadCaptionError") } }); - var transcriptItemSelector = ".flc-videoPlayer-transcriptControls-container .flc-videoPlayer-language"; - fluid.tests.runTestWithTimeout({ desc: "Transcript (amara, default selection) load error", expect: 5, @@ -195,17 +204,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt transcripts: [0] } }, - listeners: { - onLoadTranscriptError: { - listener: function (that) { - fluid.tests.testMenuItemAfterLoadError(transcriptItemSelector, false); - testsCompleted = true; - clearTimeout(timeoutId); - start(); - }, - priority: "last" - } - } + listeners: fluid.tests.makeListenersForLoadTriggeredTest(transcriptItemSelector, "onLoadTranscriptError") } }); @@ -223,21 +222,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } ] }, - listeners: { - onReady: function (tjat) { - fluid.tests.testMenuItemBeforeLoad(transcriptItemSelector); - $(transcriptItemSelector).click(); - }, - onLoadTranscriptError: { - listener: function (that) { - fluid.tests.testMenuItemAfterLoadError(transcriptItemSelector, true); - testsCompleted = true; - clearTimeout(timeoutId); - start(); - }, - priority: "last" - } - } + listeners: fluid.tests.makeListenersForClickTriggeredTest(transcriptItemSelector, "onLoadTranscriptError") } }); }); From 52bab4a72f0dd377648bdb055dada18e824e03d9 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Tue, 30 Oct 2012 17:04:14 -0400 Subject: [PATCH 09/27] FLUID-4558: Begin to improve visual styling of error handling. --- css/VideoPlayer.css | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/css/VideoPlayer.css b/css/VideoPlayer.css index a21e584..2748ab2 100644 --- a/css/VideoPlayer.css +++ b/css/VideoPlayer.css @@ -38,8 +38,9 @@ * Error message area */ .fl-videoPlayer-errorMessage { - border: 1px solid red; + border: 1px solid #999999; color: red; + text-align: center; } /* @@ -382,8 +383,9 @@ a.fl-videoPlayer-button-wrapper { .fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem:hover { background-color: #ffcc00; } -.fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem-disabled { - text-decoration: line-through; +.fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem-disabled:after { + /* TODO: This is not internationalizable */ + content: " (unavailable)"; } .fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem.fl-videoPlayer-menuItem-disabled:hover { background-color: #FFFFFF; From a1ddd9a9a11d32afd6c70ee1d11ffb1739ff614c Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Wed, 31 Oct 2012 16:13:37 -0400 Subject: [PATCH 10/27] FLUID-4558: First pass at ErrorPanel component, with tests. Not used by app yet. --- js/ErrorPanel.js | 94 +++++++++ tests/all-tests.html | 1 + tests/html/ErrorPanel-test.html | 40 ++++ tests/html/errorPanel_template.html | 11 ++ tests/html/errorPanel_template_noDismiss.html | 8 + tests/html/errorPanel_template_noRetry.html | 8 + tests/js/ErrorPanelTests.js | 178 ++++++++++++++++++ 7 files changed, 340 insertions(+) create mode 100644 js/ErrorPanel.js create mode 100644 tests/html/ErrorPanel-test.html create mode 100644 tests/html/errorPanel_template.html create mode 100644 tests/html/errorPanel_template_noDismiss.html create mode 100644 tests/html/errorPanel_template_noRetry.html create mode 100644 tests/js/ErrorPanelTests.js diff --git a/js/ErrorPanel.js b/js/ErrorPanel.js new file mode 100644 index 0000000..51022a4 --- /dev/null +++ b/js/ErrorPanel.js @@ -0,0 +1,94 @@ +/* +Copyright 2012 OCAD University + +Licensed under the Educational Community License (ECL), Version 2.0 or the New +BSD license. You may not use this file except in compliance with one these +Licenses. + +You may obtain a copy of the ECL 2.0 License and BSD License at +https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt +*/ + +/*global jQuery, fluid*/ + +// JSLint options +/*jslint white: true, funcinvoke: true, undef: true, newcap: true, nomen: true, regexp: true, bitwise: true, browser: true, forin: true, maxerr: 100, indent: 4 */ + + +(function ($) { + + /*************************************************************** + * A simple component for rendering messages when errors happen. + ***************************************************************/ + fluid.defaults("fluid.errorPanel", { + gradeNames: ["fluid.viewComponent", "autoInit"], + postInitFunction: "fluid.errorPanel.postInit", + finalInitFunction: "fluid.errorPanel.finalInit", + selectors: { + message: ".flc-errorPanel-message", + dismissButton: ".flc-errorPanel-dismissButton", + dismissButtonText: ".flc-errorPanel-dismissButton-text", + retryButton: ".flc-errorPanel-retryButton", + retryButtonText: ".flc-errorPanel-retryButton-text" + }, + retryCallback: null, + templates: { + panel: { + href: "errorPanel_template.html" + } + }, + strings: { + messageTemplate: "Sorry, %0 %1 is not currently available.", // %0 = language, %1 = medium + dismissLabel: "Dismiss error", + retryLabel: "Retry action" + }, + styles: { + hidden: "fl-hidden" + }, + events: { + onReady: null + } + }); + + fluid.errorPanel.postInit = function (that) { + that.refreshView = function (message) { + }; + + /** + * @param {Object} values A collection of token keys and values. + * Keys and values can be of any data type that can be coerced into a string. + * Arrays will work here as well. + */ + that.show = function (values) { + that.locate("message").text(fluid.stringTemplate(that.options.strings.messageTemplate, values)); + that.container.show(); + }; + + that.hide = function () { + that.container.hide(); + }; + }; + + fluid.errorPanel.finalInit = function (that) { + that.container.hide(); + fluid.fetchResources(that.options.templates, function (res) { + if (res.panel.fetchError) { + fluid.log("couldn't fetch error message template"); + fluid.log("status: " + res.panel.fetchError.status + + ", textStatus: " + res.panel.fetchError.textStatus + + ", errorThrown: " + res.panel.fetchError.errorThrown); + return; + } + + that.container.append(res.panel.resourceText); + that.locate("dismissButtonText").text(that.options.strings.dismissLabel); + that.locate("retryButtonText").text(that.options.strings.retryLabel); + + that.locate("dismissButton").click(that.hide); + + that.locate("retryButton").click(that.options.retryCallback); + + that.events.onReady.fire(that); + }); + }; +})(jQuery); diff --git a/tests/all-tests.html b/tests/all-tests.html index b7762ad..6ffc760 100644 --- a/tests/all-tests.html +++ b/tests/all-tests.html @@ -12,6 +12,7 @@ QUnit.testSuites([ "./html/MenuButton-test.html", "./html/ToggleButton-test.html", + "./html/ErrorPanel-test.html", "./html/VideoFramework-test.html", "./html/VideoPlayerAria-test.html", "./html/VideoPlayerControls-test.html", diff --git a/tests/html/ErrorPanel-test.html b/tests/html/ErrorPanel-test.html new file mode 100644 index 0000000..95d7bbe --- /dev/null +++ b/tests/html/ErrorPanel-test.html @@ -0,0 +1,40 @@ + + + + + Error Panel Test Suite + + + + + + + + + + + + + + + + + + + +

    Error Panel Test Suite

    +

    +
    +

    +
      + +
      + +
      +
      +
      + +
      + + + diff --git a/tests/html/errorPanel_template.html b/tests/html/errorPanel_template.html new file mode 100644 index 0000000..54b0b75 --- /dev/null +++ b/tests/html/errorPanel_template.html @@ -0,0 +1,11 @@ +
      +
      + error message +
      + + +
      diff --git a/tests/html/errorPanel_template_noDismiss.html b/tests/html/errorPanel_template_noDismiss.html new file mode 100644 index 0000000..d84228b --- /dev/null +++ b/tests/html/errorPanel_template_noDismiss.html @@ -0,0 +1,8 @@ +
      +
      + error message +
      + +
      diff --git a/tests/html/errorPanel_template_noRetry.html b/tests/html/errorPanel_template_noRetry.html new file mode 100644 index 0000000..5f1d6d5 --- /dev/null +++ b/tests/html/errorPanel_template_noRetry.html @@ -0,0 +1,8 @@ +
      +
      + error message +
      + +
      diff --git a/tests/js/ErrorPanelTests.js b/tests/js/ErrorPanelTests.js new file mode 100644 index 0000000..e3418fb --- /dev/null +++ b/tests/js/ErrorPanelTests.js @@ -0,0 +1,178 @@ +/* +Copyright 2012 OCAD University + +Licensed under the Educational Community License (ECL), Version 2.0 or the New +BSD license. You may not use this file except in compliance with one these +Licenses. + +You may obtain a copy of the ECL 2.0 License and BSD License at +https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt +*/ + +// Declare dependencies +/*global fluid, jqUnit, jQuery, start*/ + +// JSLint options +/*jslint white: true, funcinvoke: true, undef: true, newcap: true, nomen: true, regexp: true, bitwise: true, browser: true, forin: true, maxerr: 100, indent: 4 */ + +fluid.registerNamespace("fluid.tests"); + +(function ($) { + $(document).ready(function () { + + fluid.registerNamespace("fluid.tests"); + + var errorPanelTests = new jqUnit.TestCase("Error Panel Tests"); + + errorPanelTests.asyncTest("Basic functioning", function () { + var panel = fluid.errorPanel(".panel1", { + listeners: { + onReady: function (that) { + jqUnit.notVisible("Initially, error panel should be hidden", ".panel1"); + jqUnit.exists("Error panel should populated with template", ".panel1 .flc-errorPanel-message"); + var testStrings = ["string1", "string2"]; + var expectedString = fluid.stringTemplate(panel.options.strings.messageTemplate, testStrings); + panel.show(testStrings); + jqUnit.isVisible("After show, error panel should be visible", ".panel1"); + jqUnit.assertEquals("Panel should contain correct error message", expectedString, $(".panel1 .flc-errorPanel-message").text()); + + panel.hide(); + jqUnit.notVisible("After hide, error panel should not be visible", ".panel1"); + + testStrings = ["string3", "string4"]; + expectedString = fluid.stringTemplate(panel.options.strings.messageTemplate, testStrings); + panel.show(testStrings); + jqUnit.isVisible("After show with new values, error panel should be visible", ".panel1"); + jqUnit.assertEquals("Panel should contain correct error message", expectedString, $(".panel1 .flc-errorPanel-message").text()); + + panel.show(); + jqUnit.assertEquals("After show with no values, panel should contain correct error message", panel.options.strings.messageTemplate, $(".panel1 .flc-errorPanel-message").text()); + start(); + } + } + }); + }); + + errorPanelTests.asyncTest("Custom string template", function () { + var panel = fluid.errorPanel(".panel1", { + strings: { + messageTemplate: "This template has %0 configurable %1" + }, + listeners: { + onReady: function (that) { + var testStrings = ["two", "things"]; + var expectedString = "This template has two configurable things"; + panel.show(testStrings); + jqUnit.assertEquals("Panel should contain correct error message", expectedString, $(".panel1 .flc-errorPanel-message").text()); + start(); + } + } + }); + }); + + errorPanelTests.asyncTest("Interactions", function () { + jqUnit.expect(3); + var panel = fluid.errorPanel(".panel0", { + retryCallback: function () { + jqUnit.assertTrue("retry callback is called", true); + }, + listeners: { + onReady: function (that) { + panel.show(); + jqUnit.isVisible("After show, error panel should be visible", ".panel0"); + + $(".panel0 .flc-errorPanel-retryButton").click(); + + $(".panel0 .flc-errorPanel-dismissButton").click(); + jqUnit.notVisible("After clicking dismiss button, error panel should not be visible", ".panel0"); + + start(); + } + } + }); + }); + + errorPanelTests.asyncTest("Custom template (no dismiss)", function () { + jqUnit.expect(2); + var panel = fluid.errorPanel(".panel0", { + templates: { + panel: { + href: "errorPanel_template_noDismiss.html" + } + }, + retryCallback: function () { + jqUnit.assertTrue("retry callback is called", true); + }, + listeners: { + onReady: function (that) { + panel.show(); + jqUnit.isVisible("After show, error panel should be visible", ".panel0"); + + $(".panel0 .flc-errorPanel-retryButton").click(); + + start(); + } + } + }); + }); + + errorPanelTests.asyncTest("Custom template (no retry)", function () { + jqUnit.expect(2); + var panel = fluid.errorPanel(".panel0", { + templates: { + panel: { + href: "errorPanel_template_noRetry.html" + } + }, + listeners: { + onReady: function (that) { + panel.show(); + jqUnit.isVisible("After show, error panel should be visible", ".panel0"); + + $(".panel0 .flc-errorPanel-dismissButton").click(); + jqUnit.notVisible("After clicking dismiss button, error panel should not be visible", ".panel0"); + + start(); + } + } + }); + }); + + errorPanelTests.asyncTest("Multiple panels", function () { + var count = 0; + var panels = []; + var testFunction = function (that) { + if (count === 3) { + fluid.each(panels, function (value, key) { + jqUnit.notVisible("Initially, error panel should be hidden", ".panel" + key); + jqUnit.exists("Error panel should populated with template", ".panel" + key + " .flc-errorPanel-message"); + }); + panels[1].show(); + jqUnit.isVisible("After showing one panel, it should be visible", ".panel1"); + jqUnit.notVisible("Other panels should not be visible", ".panel0"); + jqUnit.notVisible("Other panels should not be visible", ".panel2"); + + var testStrings = ["string1", "string2"]; + var expectedString = fluid.stringTemplate(panels[0].options.strings.messageTemplate, testStrings); + panels[0].show(testStrings); + jqUnit.assertEquals("After showing another panel with different message, first panel's message should be correct", panels[1].options.strings.messageTemplate, $(".panel1 .flc-errorPanel-message").text()); + jqUnit.assertEquals("Sirst panel's message should be correct", expectedString, $(".panel0 .flc-errorPanel-message").text()); + + start(); + } + }; + var panelCount = function (that) { + count++; + testFunction(that); + }; + for (var i = 0; i < 3; i++) { + panels[i] = fluid.errorPanel(".panel" + i, { + listeners: { + onReady: panelCount + } + }); + } + }); + + }); +})(jQuery); \ No newline at end of file From 7af8044691161958d9da743451399ef05abd3a04 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Thu, 1 Nov 2012 10:14:41 -0400 Subject: [PATCH 11/27] FLUID-4558: Display and style video load error; retry callback not yet implemented --- css/VideoPlayer.css | 28 +++++++++++++++++++++++----- demos/Mammals.html | 1 + demos/VideoPlayer.html | 1 + html/videoError_template.html | 8 ++++++++ html/videoPlayer_template.html | 2 ++ js/ErrorPanel.js | 16 +++++++++++----- js/VideoPlayer.js | 4 ++-- js/VideoPlayer_media.js | 28 ++++++++++++++++++++++++++++ tests/html/errorPanel_template.html | 4 ++-- tests/js/ErrorPanelTests.js | 13 +++++++------ 10 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 html/videoError_template.html diff --git a/css/VideoPlayer.css b/css/VideoPlayer.css index 2748ab2..fd0ea66 100644 --- a/css/VideoPlayer.css +++ b/css/VideoPlayer.css @@ -35,14 +35,32 @@ .fl-theme-uio-bw .fl-videoPlayer-video-element:focus { outline-color: #000000; } /* - * Error message area + * Error message areas */ -.fl-videoPlayer-errorMessage { - border: 1px solid #999999; - color: red; - text-align: center; +.fl-videoPlayer-videoError { + border: 2px solid #999999; + width: 32em; + height: 20em; + background: url("../images/default/video-load-error.png") no-repeat 50% 30%; } +.fl-videoPlayer-videoError .fl-errorPanel-container { + margin-top: 40%; + text-align: center; +} +.fl-videoPlayer-videoError .fl-errorPanel-message { + text-transform: lowercase; + font-style: italic; +} +.fl-videoPlayer-videoError .fl-errorPanel-retryButton { + border: none; + background: url("../images/default/uio.png") no-repeat left center; + padding-left: 1.5em; + font-size: inherit; + height: 2em; + background-size: contain; + text-transform: uppercase; +} /* * Controller area */ diff --git a/demos/Mammals.html b/demos/Mammals.html index 157e8d6..0f812ae 100644 --- a/demos/Mammals.html +++ b/demos/Mammals.html @@ -36,6 +36,7 @@ + diff --git a/demos/VideoPlayer.html b/demos/VideoPlayer.html index 78d5064..f105e37 100644 --- a/demos/VideoPlayer.html +++ b/demos/VideoPlayer.html @@ -35,6 +35,7 @@ + diff --git a/html/videoError_template.html b/html/videoError_template.html new file mode 100644 index 0000000..8592457 --- /dev/null +++ b/html/videoError_template.html @@ -0,0 +1,8 @@ +
      +
      + error message +
      + +
      diff --git a/html/videoPlayer_template.html b/html/videoPlayer_template.html index 6a1d2a8..be75ac5 100644 --- a/html/videoPlayer_template.html +++ b/html/videoPlayer_template.html @@ -4,6 +4,8 @@ +
      +
      diff --git a/js/ErrorPanel.js b/js/ErrorPanel.js index 51022a4..f0a7ce3 100644 --- a/js/ErrorPanel.js +++ b/js/ErrorPanel.js @@ -22,6 +22,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt ***************************************************************/ fluid.defaults("fluid.errorPanel", { gradeNames: ["fluid.viewComponent", "autoInit"], + preInitFunction: "fluid.errorPanel.preInit", postInitFunction: "fluid.errorPanel.postInit", finalInitFunction: "fluid.errorPanel.finalInit", selectors: { @@ -50,10 +51,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } }); - fluid.errorPanel.postInit = function (that) { - that.refreshView = function (message) { - }; - + fluid.errorPanel.preInit = function (that) { /** * @param {Object} values A collection of token keys and values. * Keys and values can be of any data type that can be coerced into a string. @@ -64,6 +62,12 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt that.container.show(); }; + }; + + fluid.errorPanel.postInit = function (that) { + that.refreshView = function (message) { + }; + that.hide = function () { that.container.hide(); }; @@ -86,7 +90,9 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt that.locate("dismissButton").click(that.hide); - that.locate("retryButton").click(that.options.retryCallback); + that.locate("retryButton").click(function () { + fluid.invokeGlobalFunction(that.options.retryCallback, [that]); + }); that.events.onReady.fire(that); }); diff --git a/js/VideoPlayer.js b/js/VideoPlayer.js index aa4a2ec..36479c2 100644 --- a/js/VideoPlayer.js +++ b/js/VideoPlayer.js @@ -272,7 +272,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt controllers: ".flc-videoPlayer-controller", transcript: ".flc-videoPlayer-transcriptArea", overlay: ".flc-videoPlayer-overlay", - errorMessage: ".flc-videoPlayer-errorMessage" + videoError: ".flc-videoPlayer-videoError" }, strings: { captionsOff: "Captions OFF", @@ -281,7 +281,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt turnTranscriptsOff: "Turn Transcripts OFF", videoTitlePreface: "Video" }, - selectorsToIgnore: ["overlay", "caption", "videoPlayer", "transcript", "video", "videoContainer", "errorMessage"], + selectorsToIgnore: ["overlay", "caption", "videoPlayer", "transcript", "video", "videoContainer", "videoError"], keyBindings: fluid.videoPlayer.defaultKeys, produceTree: "fluid.videoPlayer.produceTree", controls: "custom", diff --git a/js/VideoPlayer_media.js b/js/VideoPlayer_media.js index 8adaba1..7b17ce0 100644 --- a/js/VideoPlayer_media.js +++ b/js/VideoPlayer_media.js @@ -52,6 +52,21 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt transcript: { type: "fluid.videoPlayer.transcript", createOnEvent: "onMediaReady" + }, + errorPanel: { + type: "fluid.errorPanel", + options: { + strings: { + messageTemplate: "Problem loading video", + retryLabel: "Retry" + }, + templates: { + panel: { + href: "../html/videoError_template.html" + } + }, + retryCallback: "fluid.videoPlayer.media.tempFunc" + } } }, finalInitFunction: "fluid.videoPlayer.media.finalInit", @@ -71,6 +86,10 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt sources: [] }); + fluid.videoPlayer.media.tempFunc = function () { + console.log("callback called, at least"); + }; + fluid.videoPlayer.media.createSourceMarkup = { html5SourceTag: function (videoPlayer, mediaSource) { var sourceTag = $(""); @@ -266,4 +285,13 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt bindMediaDOMEvents(that); }; + fluid.demands("fluid.errorPanel", ["fluid.videoPlayer", "fluid.videoPlayer.media"], { + container: "{videoPlayer}.dom.videoError", + options: { + listeners: { + "{media}.events.onMediaLoadError": "{errorPanel}.show" + } + } + }); + })(jQuery); diff --git a/tests/html/errorPanel_template.html b/tests/html/errorPanel_template.html index 54b0b75..5e9fb61 100644 --- a/tests/html/errorPanel_template.html +++ b/tests/html/errorPanel_template.html @@ -3,9 +3,9 @@ error message
      diff --git a/tests/js/ErrorPanelTests.js b/tests/js/ErrorPanelTests.js index e3418fb..ac2efb6 100644 --- a/tests/js/ErrorPanelTests.js +++ b/tests/js/ErrorPanelTests.js @@ -35,6 +35,8 @@ fluid.registerNamespace("fluid.tests"); panel.show(testStrings); jqUnit.isVisible("After show, error panel should be visible", ".panel1"); jqUnit.assertEquals("Panel should contain correct error message", expectedString, $(".panel1 .flc-errorPanel-message").text()); + jqUnit.assertEquals("Dismiss should have correct label", that.options.strings.dismissLabel, $(".panel1 .flc-errorPanel-dismissButton-text").text()); + jqUnit.assertEquals("Retry should have correct label", that.options.strings.retryLabel, $(".panel1 .flc-errorPanel-retryButton-text").text()); panel.hide(); jqUnit.notVisible("After hide, error panel should not be visible", ".panel1"); @@ -70,12 +72,13 @@ fluid.registerNamespace("fluid.tests"); }); }); + fluid.tests.retryCallback = function () { + jqUnit.assertTrue("retry callback is called", true); + }; errorPanelTests.asyncTest("Interactions", function () { jqUnit.expect(3); var panel = fluid.errorPanel(".panel0", { - retryCallback: function () { - jqUnit.assertTrue("retry callback is called", true); - }, + retryCallback: "fluid.tests.retryCallback", listeners: { onReady: function (that) { panel.show(); @@ -100,9 +103,7 @@ fluid.registerNamespace("fluid.tests"); href: "errorPanel_template_noDismiss.html" } }, - retryCallback: function () { - jqUnit.assertTrue("retry callback is called", true); - }, + retryCallback: "fluid.tests.retryCallback", listeners: { onReady: function (that) { panel.show(); From caf689044177e47b58d1f66ac71d8aed96ad3a52 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Thu, 1 Nov 2012 10:47:20 -0400 Subject: [PATCH 12/27] FLUID-4558: Update tests to include ErrorPanel where necessary. --- tests/html/VideoPlayer-test.html | 1 + tests/html/VideoPlayerAria-test.html | 2 + tests/html/VideoPlayerControls-test.html | 1 + tests/html/VideoPlayerErrors-test.html | 1 + .../VideoPlayerHTML5Captionator-test.html | 1 + tests/html/VideoPlayerIntegration-test.html | 1 + ...VideoPlayerTranscriptIntegration-test.html | 1 + tests/js/TestUtils.js | 17 ++++++ tests/js/VideoPlayerAriaTests.js | 60 +------------------ tests/js/VideoPlayerErrorsTests.js | 20 ++++++- 10 files changed, 45 insertions(+), 60 deletions(-) diff --git a/tests/html/VideoPlayer-test.html b/tests/html/VideoPlayer-test.html index 81ac03c..e844892 100644 --- a/tests/html/VideoPlayer-test.html +++ b/tests/html/VideoPlayer-test.html @@ -14,6 +14,7 @@ + diff --git a/tests/html/VideoPlayerAria-test.html b/tests/html/VideoPlayerAria-test.html index 85269f5..720318e 100644 --- a/tests/html/VideoPlayerAria-test.html +++ b/tests/html/VideoPlayerAria-test.html @@ -14,6 +14,7 @@ + @@ -24,6 +25,7 @@ + diff --git a/tests/html/VideoPlayerControls-test.html b/tests/html/VideoPlayerControls-test.html index 7174fd3..78309e6 100644 --- a/tests/html/VideoPlayerControls-test.html +++ b/tests/html/VideoPlayerControls-test.html @@ -15,6 +15,7 @@ + diff --git a/tests/html/VideoPlayerErrors-test.html b/tests/html/VideoPlayerErrors-test.html index a62c376..accf607 100644 --- a/tests/html/VideoPlayerErrors-test.html +++ b/tests/html/VideoPlayerErrors-test.html @@ -14,6 +14,7 @@ + diff --git a/tests/html/VideoPlayerHTML5Captionator-test.html b/tests/html/VideoPlayerHTML5Captionator-test.html index a4ca941..f51c9d1 100644 --- a/tests/html/VideoPlayerHTML5Captionator-test.html +++ b/tests/html/VideoPlayerHTML5Captionator-test.html @@ -14,6 +14,7 @@ + diff --git a/tests/html/VideoPlayerIntegration-test.html b/tests/html/VideoPlayerIntegration-test.html index dbb2870..3382eff 100644 --- a/tests/html/VideoPlayerIntegration-test.html +++ b/tests/html/VideoPlayerIntegration-test.html @@ -14,6 +14,7 @@ + diff --git a/tests/html/VideoPlayerTranscriptIntegration-test.html b/tests/html/VideoPlayerTranscriptIntegration-test.html index 75e0aaa..c6be76a 100644 --- a/tests/html/VideoPlayerTranscriptIntegration-test.html +++ b/tests/html/VideoPlayerTranscriptIntegration-test.html @@ -14,6 +14,7 @@ + diff --git a/tests/js/TestUtils.js b/tests/js/TestUtils.js index 54ac290..6665c59 100644 --- a/tests/js/TestUtils.js +++ b/tests/js/TestUtils.js @@ -66,6 +66,23 @@ fluid.registerNamespace("fluid.testUtils"); forceCache: true, href: "../../html/videoPlayer_template.html" } + }, + components: { + media: { + options: { + components: { + errorPanel: { + options: { + templates: { + panel: { + href: "errorPanel_template.html" + } + } + } + } + } + } + } } }; diff --git a/tests/js/VideoPlayerAriaTests.js b/tests/js/VideoPlayerAriaTests.js index 4e0d5e9..6e3040c 100644 --- a/tests/js/VideoPlayerAriaTests.js +++ b/tests/js/VideoPlayerAriaTests.js @@ -23,64 +23,6 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt var videoPlayerARIATests = new jqUnit.TestCase("Video Player ARIA Tests"); - var baseOpts = { - video: { - sources: [ - { - src: "TestVideo.mp4", - type: "video/mp4" - }, - { - src: "../../demos/videos/ReorganizeFuture/ReorganizeFuture.webm", - type: "video/webm" - } - ], - captions: [ - { - src: "TestCaptions.en.vtt", - type: "text/vtt", - srclang: "en", - label: "English" - }, - { - src: "TestCaptions.fr.vtt", - type: "text/vtt", - srclang: "fr", - label: "French" - } - ], - transcripts: [ - { - src: "TestTranscripts.en.json", - type: "JSONcc", - srclang: "en", - label: "English" - }, - { - src: "TestTranscripts.fr.json", - type: "JSONcc", - srclang: "fr", - label: "French" - } - ] - }, - templates: { - videoPlayer: { - forceCache: true, - href: "../../html/videoPlayer_template.html" - } - } - }; - - var initVideoPlayer = function () { - var opts = fluid.copy(baseOpts); - var container = arguments[0]; - for (var index = 1; index < arguments.length; index++) { - $.extend(true, opts, arguments[index]); - } - return fluid.videoPlayer(container, opts); - }; - fluid.tests.videoPlayer.checkAriaControlsAttr = function (controlsToTest) { fluid.each(controlsToTest, function (spec, index) { jqUnit.expect(2); @@ -137,7 +79,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt onTranscriptsLoaded: "fluid.tests.videoPlayer.testAriaControlsAttrs" } }; - initVideoPlayer($(".videoPlayer-aria"), testOpts); + fluid.testUtils.initVideoPlayer($(".videoPlayer-aria"), testOpts); }); }); diff --git a/tests/js/VideoPlayerErrorsTests.js b/tests/js/VideoPlayerErrorsTests.js index 2446f3e..6492f4c 100644 --- a/tests/js/VideoPlayerErrorsTests.js +++ b/tests/js/VideoPlayerErrorsTests.js @@ -32,6 +32,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt var captionItemSelector = ".flc-videoPlayer-captionControls-container .flc-videoPlayer-language"; var transcriptItemSelector = ".flc-videoPlayer-transcriptControls-container .flc-videoPlayer-language"; + /* Using custom baseOpts here because we specifically don't want the default base opts */ var baseOpts = { video: { sources: [ @@ -46,6 +47,23 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt forceCache: true, href: "../../html/videoPlayer_template.html" } + }, + components: { + media: { + options: { + components: { + errorPanel: { + options: { + templates: { + panel: { + href: "errorPanel_template.html" + } + } + } + } + } + } + } } }; @@ -137,7 +155,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt onMediaLoadError: { listener: function (that) { jqUnit.assertTrue("Error event fires", true); - jqUnit.assertTrue("Error message is displayed", $(".flc-videoPlayer-errorMessage").text().length > 0); + jqUnit.assertTrue("Error message is displayed", $(".flc-videoPlayer-videoError .flc-errorPanel-message").text().length > 0); testsCompleted = true; clearTimeout(timeoutId); start(); From 6072189915daee56402e17c9145801078d676653 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Thu, 1 Nov 2012 14:07:30 -0400 Subject: [PATCH 13/27] FLUID-4558: Begin integrating video and transcript errors. --- html/transcriptError_template.html | 8 ++++++++ html/videoPlayer_template.html | 5 ++--- js/ErrorPanel.js | 1 + js/MenuButton.js | 22 ++++------------------ js/VideoPlayer.js | 14 ++++++++++---- js/VideoPlayer_controllers.js | 8 ++++---- js/VideoPlayer_media.js | 6 +++--- js/VideoPlayer_transcript.js | 28 ++++++++++++++++++++++++++-- tests/js/VideoPlayerErrorsTests.js | 4 ++-- 9 files changed, 60 insertions(+), 36 deletions(-) create mode 100644 html/transcriptError_template.html diff --git a/html/transcriptError_template.html b/html/transcriptError_template.html new file mode 100644 index 0000000..5f1d6d5 --- /dev/null +++ b/html/transcriptError_template.html @@ -0,0 +1,8 @@ +
      +
      + error message +
      + +
      diff --git a/html/videoPlayer_template.html b/html/videoPlayer_template.html index be75ac5..fa93000 100644 --- a/html/videoPlayer_template.html +++ b/html/videoPlayer_template.html @@ -1,11 +1,10 @@
      +
      -
      -
      @@ -70,10 +69,10 @@
      +
      -
      diff --git a/js/ErrorPanel.js b/js/ErrorPanel.js index f0a7ce3..973607a 100644 --- a/js/ErrorPanel.js +++ b/js/ErrorPanel.js @@ -58,6 +58,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt * Arrays will work here as well. */ that.show = function (values) { +console.log("errorPanel.show(); values = "+values.toString()); that.locate("message").text(fluid.stringTemplate(that.options.strings.messageTemplate, values)); that.container.show(); }; diff --git a/js/MenuButton.js b/js/MenuButton.js index 33363a4..13a1eb9 100644 --- a/js/MenuButton.js +++ b/js/MenuButton.js @@ -41,8 +41,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt onReady: null, activated: null, hiddenByKeyboard: null, - onControlledElementReady: null, - onLoadLanguageError: null + onControlledElementReady: null }, listeners: { onControlledElementReady: { @@ -62,8 +61,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }, styles: { selected: "fl-videoPlayer-menuItem-selected", - active: "fl-videoPlayer-menuItem-active", - disabled: "fl-videoPlayer-menuItem-disabled" + active: "fl-videoPlayer-menuItem-active" }, invokers: { updateTracks: { funcName: "fluid.videoPlayer.languageMenu.updateTracks", args: ["{languageMenu}"] }, @@ -205,22 +203,12 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt that.writeIndirect("showHidePath", !that.readIndirect("showHidePath"), "menuButton"); that.hideMenu(); }; - that.disableItem = function (index) { - var item = $(that.locate("language")[index]); - item.unbind("click"); - item.attr("aria-disabled", true); - item.addClass(that.options.styles.disabled); - item.removeClass(that.options.selectors.menuItem.substring(1)); - that.selectableContext.refresh(); - }; }; fluid.videoPlayer.languageMenu.finalInit = function (that) { fluid.videoPlayer.languageMenu.bindEventListeners(that); fluid.videoPlayer.languageMenu.setUpKeyboardA11y(that); - that.events.onLoadLanguageError.addListener(that.disableItem); - that.container.attr("role", "menu"); that.hideMenu(); that.updateTracks(); @@ -246,8 +234,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt events: { onReady: null, onRenderingComplete: null, - onControlledElementReady: null, - onLoadLanguageError: null + onControlledElementReady: null }, languages: [], currentLanguagePath: "", @@ -287,8 +274,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt currentLanguagePath: "{languageControls}.options.currentLanguagePath", strings: "{languageControls}.options.strings", events: { - onControlledElementReady: "{languageControls}.events.onControlledElementReady", - onLoadLanguageError: "{languageControls}.events.onLoadLanguageError" + onControlledElementReady: "{languageControls}.events.onControlledElementReady" } } }, diff --git a/js/VideoPlayer.js b/js/VideoPlayer.js index 36479c2..f61ad93 100644 --- a/js/VideoPlayer.js +++ b/js/VideoPlayer.js @@ -423,6 +423,13 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt bindKeyboardControl(that); }); + + that.events.onMediaLoadError.addListener(function () { + that.locate("video").hide(); + // TODO: this is not good: add a class instead? need to remove it on success + that.locate("videoPlayer").css("height", "20em"); + that.locate("overlay").css("width", "32em"); + }); }; var bindVideoPlayerModel = function (that) { @@ -593,7 +600,9 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt // TODO: this needs to be reworked var handleLoadError = function (trackType, source, display) { if (display) { - that.locate("errorMessage").text("Error loading " + trackType + ": " + source.label); + console.log("Video player trying to display error"); + } else { + console.log("Video player trying not to display error"); } }; that.events.onLoadTranscriptError.addListener(function (index, source, display) { @@ -602,9 +611,6 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt that.events.onLoadCaptionError.addListener(function (index, source, display) { handleLoadError("caption", source, display); }); - that.events.onMediaLoadError.addListener(function (message) { - that.locate("errorMessage").append(message); - }); }); diff --git a/js/VideoPlayer_controllers.js b/js/VideoPlayer_controllers.js index cfcee85..afd2cff 100644 --- a/js/VideoPlayer_controllers.js +++ b/js/VideoPlayer_controllers.js @@ -84,8 +84,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt release: "Captions" }, events: { - onControlledElementReady: "{controllers}.events.onCaptionsReady", - onLoadLanguageError: "{controllers}.events.onLoadCaptionError" + onControlledElementReady: "{controllers}.events.onCaptionsReady" //, +// onLoadLanguageError: "{controllers}.events.onLoadCaptionError" } } }, @@ -113,8 +113,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt release: "Transcripts" }, events: { - onControlledElementReady: "{controllers}.events.onTranscriptsReady", - onLoadLanguageError: "{controllers}.events.onLoadTranscriptError" + onControlledElementReady: "{controllers}.events.onTranscriptsReady" //, +// onLoadLanguageError: "{controllers}.events.onLoadTranscriptError" } } }, diff --git a/js/VideoPlayer_media.js b/js/VideoPlayer_media.js index 7b17ce0..0ea7298 100644 --- a/js/VideoPlayer_media.js +++ b/js/VideoPlayer_media.js @@ -53,7 +53,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt type: "fluid.videoPlayer.transcript", createOnEvent: "onMediaReady" }, - errorPanel: { + videoError: { type: "fluid.errorPanel", options: { strings: { @@ -285,11 +285,11 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt bindMediaDOMEvents(that); }; - fluid.demands("fluid.errorPanel", ["fluid.videoPlayer", "fluid.videoPlayer.media"], { + fluid.demands("videoError", ["fluid.videoPlayer", "fluid.videoPlayer.intervalEventsConductor"], { container: "{videoPlayer}.dom.videoError", options: { listeners: { - "{media}.events.onMediaLoadError": "{errorPanel}.show" + "{media}.events.onMediaLoadError": "{videoError}.show" } } }); diff --git a/js/VideoPlayer_transcript.js b/js/VideoPlayer_transcript.js index be68a24..9d49728 100644 --- a/js/VideoPlayer_transcript.js +++ b/js/VideoPlayer_transcript.js @@ -39,6 +39,20 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt transcriptEventBinder: { type: "fluid.videoPlayer.eventBinder", createOnEvent: "onReady" + }, + transcriptError: { + type: "fluid.errorPanel", + options: { + strings: { + messageTemplate: "Sorry, %0 transcripts currently unavailable", + dismissLabel: "Dismiss error" + }, + templates: { + panel: { + href: "../html/transcriptError_template.html" + } + } + } } }, events: { @@ -72,9 +86,10 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt selectors: { languageDropdown: ".flc-videoPlayer-transcripts-language-dropdown", closeButton: ".flc-videoPlayer-transcripts-close-button", - transcriptText: ".flc-videoPlayer-transcript-text" + transcriptText: ".flc-videoPlayer-transcript-text", + transcriptError: ".flc-videoPlayer-transcriptError" }, - selectorsToIgnore: ["closeButton", "transcriptText"], + selectorsToIgnore: ["closeButton", "transcriptText", "transcriptError"], styles: { element: "fl-videoPlayer-transcript-element", highlight: "fl-videoPlayer-transcript-element-highlight", @@ -443,4 +458,13 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt that.events.onReady.fire(that); }; + fluid.demands("transcriptError", "fluid.videoPlayer.intervalEventsConductor", { + container: "{transcript}.dom.transcriptError", + options: { + listeners: { + "{transcript}.events.onLoadTranscriptError": "{transcriptError}.show" + } + } + }); + })(jQuery); diff --git a/tests/js/VideoPlayerErrorsTests.js b/tests/js/VideoPlayerErrorsTests.js index 6492f4c..99df2fb 100644 --- a/tests/js/VideoPlayerErrorsTests.js +++ b/tests/js/VideoPlayerErrorsTests.js @@ -122,8 +122,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt jqUnit.assertTrue("Error message is " + (displayMessage ? "" : "not ") + "displayed", (displayMessage ? msgLen > 0 : msgLen === 0)); var item = $(itemSelector); jqUnit.assertEquals("Language is still present in caption menu", 1, item.length); - jqUnit.assertFalse("Language is not selectable in caption menu", item.hasClass("flc-videoPlayer-menuItem")); - jqUnit.assertTrue("Language is styled as disabled in caption menu", item.hasClass("fl-videoPlayer-menuItem-disabled")); + jqUnit.assertTrue("Language is not selectable in caption menu", item.hasClass("flc-videoPlayer-menuItem")); + jqUnit.assertFalse("Language is styled as disabled in caption menu", item.hasClass("fl-videoPlayer-menuItem-disabled")); }; fluid.tests.runTestWithTimeout = function (config) { From 46a6d7fa30098bdb0b7f0b328295eb768b1fb986 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Thu, 1 Nov 2012 16:22:41 -0400 Subject: [PATCH 14/27] FLUID-4558: Fixes so transcript handles switching between tracks properly when there are load errors. --- js/ErrorPanel.js | 12 ++---------- js/VideoPlayer.js | 20 ++------------------ js/VideoPlayer_controllers.js | 11 +++-------- js/VideoPlayer_transcript.js | 25 ++++++++++++++++++------- 4 files changed, 25 insertions(+), 43 deletions(-) diff --git a/js/ErrorPanel.js b/js/ErrorPanel.js index 973607a..0c122d9 100644 --- a/js/ErrorPanel.js +++ b/js/ErrorPanel.js @@ -23,7 +23,6 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.defaults("fluid.errorPanel", { gradeNames: ["fluid.viewComponent", "autoInit"], preInitFunction: "fluid.errorPanel.preInit", - postInitFunction: "fluid.errorPanel.postInit", finalInitFunction: "fluid.errorPanel.finalInit", selectors: { message: ".flc-errorPanel-message", @@ -58,22 +57,15 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt * Arrays will work here as well. */ that.show = function (values) { -console.log("errorPanel.show(); values = "+values.toString()); - that.locate("message").text(fluid.stringTemplate(that.options.strings.messageTemplate, values)); + that.locate("message").text(fluid.stringTemplate(that.options.strings.messageTemplate, fluid.makeArray(values))); that.container.show(); }; - }; - - fluid.errorPanel.postInit = function (that) { - that.refreshView = function (message) { - }; - that.hide = function () { that.container.hide(); }; }; - + fluid.errorPanel.finalInit = function (that) { that.container.hide(); fluid.fetchResources(that.options.templates, function (res) { diff --git a/js/VideoPlayer.js b/js/VideoPlayer.js index f61ad93..6dd7692 100644 --- a/js/VideoPlayer.js +++ b/js/VideoPlayer.js @@ -154,7 +154,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } }, events: { - onCurrentTranscriptChanged: "{videoPlayer}.events.onCurrentTranscriptChanged", + afterCurrentTranscriptChanged: "{videoPlayer}.events.afterCurrentTranscriptChanged", onTranscriptHide: "{videoPlayer}.events.onTranscriptHide", onTranscriptShow: "{videoPlayer}.events.onTranscriptShow", onTranscriptElementChange: "{videoPlayer}.events.onTranscriptElementChange", @@ -223,7 +223,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt onStartScrub: null, onTemplateLoadError: null, onLoadCaptionError: null, - onCurrentTranscriptChanged: null, + afterCurrentTranscriptChanged: null, onTranscriptHide: null, onTranscriptShow: null, onTranscriptElementChange: null, @@ -596,22 +596,6 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt $("object", that.locate("video")).attr("tabindex", "-1"); that.events.onReady.fire(that); - - // TODO: this needs to be reworked - var handleLoadError = function (trackType, source, display) { - if (display) { - console.log("Video player trying to display error"); - } else { - console.log("Video player trying not to display error"); - } - }; - that.events.onLoadTranscriptError.addListener(function (index, source, display) { - handleLoadError("transcript", source, display); - }); - that.events.onLoadCaptionError.addListener(function (index, source, display) { - handleLoadError("caption", source, display); - }); - }); return that; diff --git a/js/VideoPlayer_controllers.js b/js/VideoPlayer_controllers.js index afd2cff..f86e707 100644 --- a/js/VideoPlayer_controllers.js +++ b/js/VideoPlayer_controllers.js @@ -84,8 +84,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt release: "Captions" }, events: { - onControlledElementReady: "{controllers}.events.onCaptionsReady" //, -// onLoadLanguageError: "{controllers}.events.onLoadCaptionError" + onControlledElementReady: "{controllers}.events.onCaptionsReady" } } }, @@ -113,8 +112,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt release: "Transcripts" }, events: { - onControlledElementReady: "{controllers}.events.onTranscriptsReady" //, -// onLoadLanguageError: "{controllers}.events.onLoadTranscriptError" + onControlledElementReady: "{controllers}.events.onTranscriptsReady" } } }, @@ -173,10 +171,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt // private event used for associating transcript menu with transcript via ARIA onTranscriptsReady: null, - onCaptionsReady: null, - - onLoadTranscriptError: null, - onLoadCaptionError: null + onCaptionsReady: null }, selectors: { diff --git a/js/VideoPlayer_transcript.js b/js/VideoPlayer_transcript.js index 9d49728..bdb2118 100644 --- a/js/VideoPlayer_transcript.js +++ b/js/VideoPlayer_transcript.js @@ -42,6 +42,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }, transcriptError: { type: "fluid.errorPanel", + createOnEvent: "onReady", options: { strings: { messageTemplate: "Sorry, %0 transcripts currently unavailable", @@ -59,7 +60,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt onTranscriptsLoaded: null, onLoadTranscriptError: null, onIntervalChange: null, - onCurrentTranscriptChanged: null, + onCurrentTranscriptChanging: null, + afterCurrentTranscriptChanged: null, onTranscriptHide: null, onTranscriptShow: null, onTranscriptElementChange: null, @@ -112,6 +114,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt // Update visibility of the transcript area based on the flag "model.displayTranscripts" fluid.videoPlayer.transcript.switchTranscriptArea = function (that) { if (that.model.displayTranscripts) { + fluid.videoPlayer.transcript.prepareTranscript(that); fluid.videoPlayer.transcript.showTranscriptArea(that); that.events.onTranscriptShow.fire(); } else { @@ -196,7 +199,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }; fluid.videoPlayer.transcript.displayTranscript = function (that, transcriptText) { - that.locate("transcriptText").html(transcriptText); + that.locate("transcriptText").html(transcriptText).show(); that.updateTranscriptHighlight(); $('span[id|="' + that.options.transcriptElementIdPrefix + '"]').click(function (evt) { @@ -288,7 +291,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.videoPlayer.transcript.parseTranscriptFile(that, data, currentIndex, that.convertSecsToMilli, "text", "start_time", "end_time"); }; var errorHandler = function () { - that.events.onLoadTranscriptError.fire(currentIndex, transcriptSource, false); + that.events.onLoadTranscriptError.fire(that, transcriptSource); }; fluid.videoPlayer.fetchAmaraJson(transcriptSource.src, handler, errorHandler); } else { @@ -299,7 +302,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.videoPlayer.transcript.parseTranscriptFile(that, data, currentIndex, that.convertToMilli, "transcript", "inTime", "outTime"); }, error: function () { - that.events.onLoadTranscriptError.fire(currentIndex, transcriptSource, true); + that.events.onLoadTranscriptError.fire(that, transcriptSource); } }; @@ -386,6 +389,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt // actual choice of track hasn't changed return; } + that.events.onCurrentTranscriptChanging.fire(); fluid.videoPlayer.transcript.prepareTranscript(that); @@ -395,12 +399,15 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt that.locate("languageDropdown").find("option[value='" + currentTranscriptIndex + "']").attr("selected", "selected"); that.updateTranscriptHighlight(); - that.events.onCurrentTranscriptChanged.fire(currentTranscriptIndex); + that.events.afterCurrentTranscriptChanged.fire(currentTranscriptIndex); }); that.events.onTranscriptsLoaded.addListener(function (intervalList) { that.transcriptInterval.setIntervalList(intervalList); }); + that.events.onLoadTranscriptError.addListener(function () { + that.locate("transcriptText").hide(); + }); that.events.onIntervalChange.addListener(function (currentInterval, previousInterval) { if (currentInterval !== that.model.transcriptIntervalId) { @@ -447,7 +454,6 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.videoPlayer.transcript.bindTranscriptDOMEvents(that); fluid.videoPlayer.transcript.bindTranscriptModel(that); - fluid.videoPlayer.transcript.prepareTranscript(that); fluid.videoPlayer.transcript.switchTranscriptArea(that); that.transcriptTextId = function () { @@ -462,7 +468,12 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt container: "{transcript}.dom.transcriptError", options: { listeners: { - "{transcript}.events.onLoadTranscriptError": "{transcriptError}.show" + "{transcript}.events.onLoadTranscriptError": { + listener: "{transcriptError}.show", + args: "{arguments}.1.label" + }, + "{transcript}.events.onTranscriptsLoaded": "{transcriptError}.hide", + "{transcript}.events.onCurrentTranscriptChanging": "{transcriptError}.hide" } } }); From 4fa6d96d3b92490e10babeacc3c7f638262b1881 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Fri, 2 Nov 2012 12:40:09 -0400 Subject: [PATCH 15/27] FLUID-4558: Cleaning up tests --- html/transcriptError_template.html | 3 - js/VideoPlayer_transcript.js | 4 +- tests/html/VideoPlayerTranscript-test.html | 2 + tests/js/MenuButtonTests.js | 21 ---- tests/js/TestUtils.js | 17 ++- tests/js/VideoPlayerErrorsTests.js | 123 ++++++++++----------- tests/js/VideoPlayerIntegrationTests.js | 4 +- tests/js/VideoPlayerTranscriptTests.js | 48 ++++++++ 8 files changed, 128 insertions(+), 94 deletions(-) diff --git a/html/transcriptError_template.html b/html/transcriptError_template.html index 5f1d6d5..e765e9c 100644 --- a/html/transcriptError_template.html +++ b/html/transcriptError_template.html @@ -2,7 +2,4 @@
      error message
      - diff --git a/js/VideoPlayer_transcript.js b/js/VideoPlayer_transcript.js index bdb2118..adeba8b 100644 --- a/js/VideoPlayer_transcript.js +++ b/js/VideoPlayer_transcript.js @@ -45,8 +45,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt createOnEvent: "onReady", options: { strings: { - messageTemplate: "Sorry, %0 transcripts currently unavailable", - dismissLabel: "Dismiss error" + messageTemplate: "Sorry, %0 transcripts currently unavailable" }, templates: { panel: { @@ -284,6 +283,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.videoPlayer.transcript.loadTranscript = function (that, currentIndex) { var transcriptSource = that.options.transcripts[currentIndex]; if (transcriptSource) { + transcriptSource.transcriptText = "loading..."; // Handle Universal Subtitles JSON files for transcripts if (transcriptSource.type === "text/amarajson") { diff --git a/tests/html/VideoPlayerTranscript-test.html b/tests/html/VideoPlayerTranscript-test.html index 4e549f8..2440415 100644 --- a/tests/html/VideoPlayerTranscript-test.html +++ b/tests/html/VideoPlayerTranscript-test.html @@ -8,6 +8,7 @@ + @@ -36,6 +37,7 @@

      +
      diff --git a/tests/js/MenuButtonTests.js b/tests/js/MenuButtonTests.js index a32c786..2e43b71 100644 --- a/tests/js/MenuButtonTests.js +++ b/tests/js/MenuButtonTests.js @@ -248,26 +248,5 @@ fluid.registerNamespace("fluid.tests"); }); }); - menuButtonTests.asyncTest("Disabling menu item", function () { - jqUnit.expect(6); - var testControls = fluid.tests.initLangControls({ - listeners: { - onReady: function (that) { - var languageIndex = 1; - var item = $($(".flc-videoPlayer-menuItem")[languageIndex]); - jqUnit.assertTrue("Menu item is initially selectable", item.hasClass("flc-videoPlayer-menuItem")); - jqUnit.assertUndefined("Menu item is not aria-disabled", item.attr("aria-disabled")); - jqUnit.assertFalse("Menu item is normally styled", item.hasClass("fl-videoPlayer-menuItem-disabled")); - that.menu.disableItem(languageIndex); - jqUnit.assertFalse("Menu item not selectable", item.hasClass("flc-videoPlayer-menuItem")); - jqUnit.assertTrue("Menu item is aria-disabled", !!item.attr("aria-disabled")); - jqUnit.assertTrue("Menu item has disabled styling", item.hasClass("fl-videoPlayer-menuItem-disabled")); - - start(); - } - } - }); - }); - }); })(jQuery); diff --git a/tests/js/TestUtils.js b/tests/js/TestUtils.js index 6665c59..b5a3ddc 100644 --- a/tests/js/TestUtils.js +++ b/tests/js/TestUtils.js @@ -71,7 +71,7 @@ fluid.registerNamespace("fluid.testUtils"); media: { options: { components: { - errorPanel: { + videoError: { options: { templates: { panel: { @@ -79,6 +79,21 @@ fluid.registerNamespace("fluid.testUtils"); } } } + }, + transcript: { + options: { + components: { + transcriptError: { + options: { + templates: { + panel: { + href: "errorPanel_template.html" + } + } + } + }, + } + } } } } diff --git a/tests/js/VideoPlayerErrorsTests.js b/tests/js/VideoPlayerErrorsTests.js index 99df2fb..1a6ce42 100644 --- a/tests/js/VideoPlayerErrorsTests.js +++ b/tests/js/VideoPlayerErrorsTests.js @@ -48,11 +48,16 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt href: "../../html/videoPlayer_template.html" } }, + model: { + currentTracks: { + transcripts: [0] + } + }, components: { media: { options: { components: { - errorPanel: { + videoError: { options: { templates: { panel: { @@ -60,6 +65,21 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } } } + }, + transcript: { + options: { + components: { + transcriptError: { + options: { + templates: { + panel: { + href: "errorPanel_template.html" + } + } + } + }, + } + } } } } @@ -76,56 +96,19 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt return fluid.videoPlayer(container, opts); }; - fluid.tests.makeListenersForLoadTriggeredTest = function (selector, errorEventName) { - var obj = {}; - obj[errorEventName] = { - listener: function (that) { - fluid.tests.testMenuItemAfterLoadError(selector, false); - testsCompleted = true; - clearTimeout(timeoutId); - start(); - }, - priority: "last" - }; - return obj; - }; - - fluid.tests.makeListenersForClickTriggeredTest = function (selector, errorEventName) { + fluid.tests.makeListenersForClickTriggeredTest = function (selector, errorEventName, listenerFn) { var obj = { onReady: function (tjat) { - fluid.tests.testMenuItemBeforeLoad(selector); $(selector).click(); } }; obj[errorEventName] = { - listener: function (that) { - fluid.tests.testMenuItemAfterLoadError(selector, true); - testsCompleted = true; - clearTimeout(timeoutId); - start(); - }, + listener: listenerFn, priority: "last" }; return obj; }; - fluid.tests.testMenuItemBeforeLoad = function (itemSelector) { - var item = $(itemSelector); - jqUnit.assertEquals("Before language is selected, language is present in caption menu", 1, item.length); - jqUnit.assertTrue("Before language is selected, language is selectable in caption menu", item.hasClass("flc-videoPlayer-menuItem")); - jqUnit.assertFalse("Before language is selected, language is not styled as disabled in caption menu", item.hasClass("fl-videoPlayer-menuItem-disabled")); - }; - - fluid.tests.testMenuItemAfterLoadError = function (itemSelector, displayMessage) { - jqUnit.assertTrue("Error event fires", true); - var msgLen = $(".flc-videoPlayer-errorMessage").text().length; - jqUnit.assertTrue("Error message is " + (displayMessage ? "" : "not ") + "displayed", (displayMessage ? msgLen > 0 : msgLen === 0)); - var item = $(itemSelector); - jqUnit.assertEquals("Language is still present in caption menu", 1, item.length); - jqUnit.assertTrue("Language is not selectable in caption menu", item.hasClass("flc-videoPlayer-menuItem")); - jqUnit.assertFalse("Language is styled as disabled in caption menu", item.hasClass("fl-videoPlayer-menuItem-disabled")); - }; - fluid.tests.runTestWithTimeout = function (config) { videoPlayerErrorsTests.asyncTest(config.desc, function () { jqUnit.expect(config.expect); @@ -166,13 +149,12 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } }); - fluid.tests.runTestWithTimeout({ - desc: "Caption (amara) load error", - expect: 5, + desc: "Transcript (amara) load error", + expect: 3, opts: { video: { - captions: [ + transcripts: [ { src: "bad.amara.url", type: "text/amarajson", @@ -181,34 +163,49 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } ] }, - listeners: fluid.tests.makeListenersForLoadTriggeredTest(captionItemSelector, "onLoadCaptionError") + listeners: fluid.tests.makeListenersForClickTriggeredTest(transcriptItemSelector, "onLoadTranscriptError", function (that) { + jqUnit.isVisible("Transcript are should be visible", $(".flc-videoPlayer-transcriptArea")); + jqUnit.isVisible("Transcript are should container error message", $(".flc-videoPlayer-transcriptArea .flc-videoPlayer-transcriptError")); + jqUnit.notVisible("Transcript are should not container transcript text", $(".flc-videoPlayer-transcriptArea .flc-videoPlayer-transcript-text")); + testsCompleted = true; + clearTimeout(timeoutId); + start(); + }) } }); fluid.tests.runTestWithTimeout({ - desc: "Caption (non-amara) load error", - expect: 8, + desc: "Transcript (non-amara) load error", + expect: 3, opts: { video: { - captions: [ + transcripts: [ { - src: "bad.vtt.url", - type: "text/vtt", + src: "bad.json.url", + type: "JSONcc", srclang: "en", label: "English" } ] }, - listeners: fluid.tests.makeListenersForClickTriggeredTest(captionItemSelector, "onLoadCaptionError") + listeners: fluid.tests.makeListenersForClickTriggeredTest(transcriptItemSelector, "onLoadTranscriptError", function (that) { + jqUnit.isVisible("Transcript are should be visible", $(".flc-videoPlayer-transcriptArea")); + jqUnit.isVisible("Transcript are should container error message", $(".flc-videoPlayer-transcriptArea .flc-videoPlayer-transcriptError")); + jqUnit.notVisible("Transcript are should not container transcript text", $(".flc-videoPlayer-transcriptArea .flc-videoPlayer-transcript-text")); + testsCompleted = true; + clearTimeout(timeoutId); + start(); + }) } }); +/* fluid.tests.runTestWithTimeout({ - desc: "Transcript (amara, default selection) load error", + desc: "Caption (amara) load error", expect: 5, opts: { video: { - transcripts: [ + captions: [ { src: "bad.amara.url", type: "text/amarajson", @@ -217,32 +214,28 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } ] }, - model: { - currentTracks: { - transcripts: [0] - } - }, - listeners: fluid.tests.makeListenersForLoadTriggeredTest(transcriptItemSelector, "onLoadTranscriptError") + listeners: fluid.tests.makeListenersForClickTriggeredTest(captionItemSelector, "onLoadCaptionError") } }); fluid.tests.runTestWithTimeout({ - desc: "Transcript (non-amara) load error", + desc: "Caption (non-amara) load error", expect: 8, opts: { video: { - transcripts: [ + captions: [ { - src: "bad.json.url", - type: "JSONcc", + src: "bad.vtt.url", + type: "text/vtt", srclang: "en", label: "English" } ] }, - listeners: fluid.tests.makeListenersForClickTriggeredTest(transcriptItemSelector, "onLoadTranscriptError") + listeners: fluid.tests.makeListenersForClickTriggeredTest(captionItemSelector, "onLoadCaptionError") } }); - }); +*/ + }); })(jQuery); diff --git a/tests/js/VideoPlayerIntegrationTests.js b/tests/js/VideoPlayerIntegrationTests.js index 3aa43f4..7c8407c 100644 --- a/tests/js/VideoPlayerIntegrationTests.js +++ b/tests/js/VideoPlayerIntegrationTests.js @@ -135,7 +135,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt videoPlayerIntegrationTests.asyncTest("Switch transcript language buttons", function () { jqUnit.expect(3); - var initialTranscriptText; + var initialTranscriptText = null; var testedTranscriptSpanClick = false; fluid.videoPlayer.testTranscript = function (that) { @@ -153,7 +153,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt // Depending on the connection with universal subtitle site, the test below may not get run with remote universal subtitle transcript files. if (!initialTranscriptText) { initialTranscriptText = transcriptTextArea.text(); - jqUnit.assertNotNull("The transcript text is filled in", initialTranscriptText); + jqUnit.assertNotNull("Initially, he transcript text is filled in", initialTranscriptText); } else { jqUnit.assertNotEquals("The transcript text is switched", transcriptTextArea.text(), initialTranscriptText); } diff --git a/tests/js/VideoPlayerTranscriptTests.js b/tests/js/VideoPlayerTranscriptTests.js index 440578d..713a012 100644 --- a/tests/js/VideoPlayerTranscriptTests.js +++ b/tests/js/VideoPlayerTranscriptTests.js @@ -27,6 +27,17 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt currentTracks: { transcripts: [0] } + }, + components: { + transcriptError: { + options: { + templates: { + panel: { + href: "errorPanel_template_noRetry.html" + } + } + } + } } }; @@ -195,5 +206,42 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }; var that = initTranscript(localTranscriptOpts, testOpts); }); + + var badDefaultTranscriptOpts = { + transcripts: [ + { + src: "bad.file", + type: "JSONcc", + srclang: "en", + label: "English" + }, + { + src: "TestTranscripts.fr.json", + type: "JSONcc", + srclang: "fr", + label: "French" + } + ] + }; + videoPlayerTranscriptTests.asyncTest("Load error", function () { + var testOpts = { + listeners: { + onReady: function (that) { + // show transcripts + that.applier.requestChange("displayTranscripts", true); + }, + onLoadTranscriptError: { + listener: function () { + jqUnit.isVisible("Initally, error message should be visible", $(".flc-videoPlayer-transcriptError")); + jqUnit.notVisible("Transcript text is hidden", $(".flc-videoPlayer-transcript-text")); + start(); + }, + priority: "last" + } + } + }; + initTranscript(badDefaultTranscriptOpts, testOpts); + }); + }); })(jQuery); From b5c844016e88e9d7d26eaf062e57d732f2f95db1 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Fri, 2 Nov 2012 13:28:54 -0400 Subject: [PATCH 16/27] FLUID-4558: Style transcript error message. --- css/VideoPlayer.css | 4 ++++ html/transcriptError_template.html | 6 ++---- js/VideoPlayer_transcript.js | 16 +++++++++------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/css/VideoPlayer.css b/css/VideoPlayer.css index fd0ea66..f941156 100644 --- a/css/VideoPlayer.css +++ b/css/VideoPlayer.css @@ -630,6 +630,10 @@ ul.fl-videoPlayer-transcripts-languageList li { width: 65%; } +.fl-videoPlayer-transcriptError .fl-errorPanel-message { + text-align: center; + padding-top: 30%; +} .fl-videoPlayer-transcript-text { word-spacing: 0.1em; overflow-x: hidden; diff --git a/html/transcriptError_template.html b/html/transcriptError_template.html index e765e9c..5458099 100644 --- a/html/transcriptError_template.html +++ b/html/transcriptError_template.html @@ -1,5 +1,3 @@ -
      -
      - error message -
      +
      + error message
      diff --git a/js/VideoPlayer_transcript.js b/js/VideoPlayer_transcript.js index adeba8b..ce25388 100644 --- a/js/VideoPlayer_transcript.js +++ b/js/VideoPlayer_transcript.js @@ -96,6 +96,9 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt highlight: "fl-videoPlayer-transcript-element-highlight", selected: "fl-videoPlayer-transcript-element-selected" }, + strings: { + loading: "loading..." + }, transcriptElementIdPrefix: "flc-videoPlayer-transcript-element" }); @@ -283,16 +286,17 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.videoPlayer.transcript.loadTranscript = function (that, currentIndex) { var transcriptSource = that.options.transcripts[currentIndex]; if (transcriptSource) { - transcriptSource.transcriptText = "loading..."; + transcriptSource.transcriptText = that.options.strings.loading; + var errorHandler = function () { + transcriptSource.transcriptText = null; + that.events.onLoadTranscriptError.fire(that, transcriptSource); + }; // Handle Universal Subtitles JSON files for transcripts if (transcriptSource.type === "text/amarajson") { var handler = function (data) { fluid.videoPlayer.transcript.parseTranscriptFile(that, data, currentIndex, that.convertSecsToMilli, "text", "start_time", "end_time"); }; - var errorHandler = function () { - that.events.onLoadTranscriptError.fire(that, transcriptSource); - }; fluid.videoPlayer.fetchAmaraJson(transcriptSource.src, handler, errorHandler); } else { var opts = { @@ -301,9 +305,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt success: function (data) { fluid.videoPlayer.transcript.parseTranscriptFile(that, data, currentIndex, that.convertToMilli, "transcript", "inTime", "outTime"); }, - error: function () { - that.events.onLoadTranscriptError.fire(that, transcriptSource); - } + error: errorHandler }; if (transcriptSource.type !== "JSONcc") { From 1bd9c37fd79387eb34e3d4a42ec1946ebb185d69 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Fri, 2 Nov 2012 13:56:20 -0400 Subject: [PATCH 17/27] FLUID-4558: First pass at caption load error messages. --- css/VideoPlayer.css | 18 +++++++++++++++- html/captionError_template.html | 8 ++++++++ html/videoPlayer_template.html | 4 +++- js/VideoPlayer_html5Captionator.js | 33 +++++++++++++++++++++++++++++- 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 html/captionError_template.html diff --git a/css/VideoPlayer.css b/css/VideoPlayer.css index f941156..eb8ba25 100644 --- a/css/VideoPlayer.css +++ b/css/VideoPlayer.css @@ -597,7 +597,8 @@ ul.fl-videoPlayer-transcripts-languageList li { .fl-theme-uio-bw .fl-videoPlayer-overlay, .fl-theme-uio-bw .fl-videoPlayer-captionArea, .fl-theme-uio-bw .captionator-cue-canvas { background-color: transparent !important; } -.fl-videoPlayer-caption-captionText { +.fl-videoPlayer-caption-captionText, +.fl-videoPlayer-captionError { color: white; background-color: black; opacity: 0.7; @@ -610,6 +611,21 @@ ul.fl-videoPlayer-transcripts-languageList li { z-index: 101; /* To make our div showin on top since captionator has 100 so */ } +.fl-videoPlayer-captionError { + position: absolute; + bottom: 0; + left: 10%; +} +.fl-videoPlayer-captionError .fl-errorPanel-dismissButton { + pointer-events: visible; + cursor: pointer; + border: none; + background: url("../images/default/transcriptclose.png") no-repeat center center; + font-size: inherit; + height: 2em; + background-size: contain; + text-transform: uppercase; +} /* * Transcript area diff --git a/html/captionError_template.html b/html/captionError_template.html new file mode 100644 index 0000000..8737eab --- /dev/null +++ b/html/captionError_template.html @@ -0,0 +1,8 @@ +
      +
      + error message +
      + +
      diff --git a/html/videoPlayer_template.html b/html/videoPlayer_template.html index fa93000..edd58ce 100644 --- a/html/videoPlayer_template.html +++ b/html/videoPlayer_template.html @@ -7,7 +7,9 @@
      -
      +
      +
      +
      diff --git a/js/VideoPlayer_html5Captionator.js b/js/VideoPlayer_html5Captionator.js index e9cc585..e77276b 100644 --- a/js/VideoPlayer_html5Captionator.js +++ b/js/VideoPlayer_html5Captionator.js @@ -43,7 +43,8 @@ https://source.fluidproject.org/svn/LICENSE.txt // TODO: Those selectors should come from the parent component!! selectors: { video: ".flc-videoPlayer-video", - caption: ".flc-videoPlayer-captionArea" + caption: ".flc-videoPlayer-captionArea", + captionError: ".flc-videoPlayer-captionError" }, listeners: { afterTrackElCreated: "fluid.videoPlayer.html5Captionator.waitForTracks", @@ -52,6 +53,23 @@ https://source.fluidproject.org/svn/LICENSE.txt createTrackFns: { "text/amarajson": "fluid.videoPlayer.html5Captionator.createAmaraTrack", "text/vtt": "fluid.videoPlayer.html5Captionator.createVttTrack" + }, + components: { + captionError: { + type: "fluid.errorPanel", + createOnEvent: "onReady", + options: { + strings: { + messageTemplate: "Sorry, %0 captions currently unavailable", + dismissLabel: "Dismiss error" + }, + templates: { + panel: { + href: "../html/captionError_template.html" + } + } + } + } } }); @@ -173,4 +191,17 @@ https://source.fluidproject.org/svn/LICENSE.txt that.events.onReady.fire(that, fluid.allocateSimpleId(that.locate("caption"))); }; + fluid.demands("captionError", "fluid.videoPlayer.html5Captionator", { + container: "{html5Captionator}.dom.captionError", + options: { + listeners: { + "{html5Captionator}.events.onLoadCaptionError": { + listener: "{captionError}.show", + args: "{arguments}.1.label" + }, + "{html5Captionator}.events.onTracksReady": "{captionError}.hide" + } + } + }); + })(jQuery); From daea8a11dd905cec2229834d4ab405887dbbbc32 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Fri, 2 Nov 2012 14:42:53 -0400 Subject: [PATCH 18/27] FLUID-4558: Tests for caption load errors. --- js/VideoPlayer_html5Captionator.js | 1 - tests/js/VideoPlayerErrorsTests.js | 61 +++++++++++++++++++----------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/js/VideoPlayer_html5Captionator.js b/js/VideoPlayer_html5Captionator.js index e77276b..23678e3 100644 --- a/js/VideoPlayer_html5Captionator.js +++ b/js/VideoPlayer_html5Captionator.js @@ -57,7 +57,6 @@ https://source.fluidproject.org/svn/LICENSE.txt components: { captionError: { type: "fluid.errorPanel", - createOnEvent: "onReady", options: { strings: { messageTemplate: "Sorry, %0 captions currently unavailable", diff --git a/tests/js/VideoPlayerErrorsTests.js b/tests/js/VideoPlayerErrorsTests.js index 1a6ce42..fcae9f4 100644 --- a/tests/js/VideoPlayerErrorsTests.js +++ b/tests/js/VideoPlayerErrorsTests.js @@ -83,6 +83,21 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } } } + }, + html5Captionator: { + options: { + components: { + captionError: { + options: { + templates: { + panel: { + href: "errorPanel_template.html" + } + } + } + } + } + } } } }; @@ -149,6 +164,15 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } }); + var testTranscriptLoadError = function (that) { + jqUnit.isVisible("Transcript are should be visible", $(".flc-videoPlayer-transcriptArea")); + jqUnit.isVisible("Transcript are should container error message", $(".flc-videoPlayer-transcriptArea .flc-videoPlayer-transcriptError")); + jqUnit.notVisible("Transcript are should not container transcript text", $(".flc-videoPlayer-transcriptArea .flc-videoPlayer-transcript-text")); + testsCompleted = true; + clearTimeout(timeoutId); + start(); + }; + fluid.tests.runTestWithTimeout({ desc: "Transcript (amara) load error", expect: 3, @@ -163,14 +187,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } ] }, - listeners: fluid.tests.makeListenersForClickTriggeredTest(transcriptItemSelector, "onLoadTranscriptError", function (that) { - jqUnit.isVisible("Transcript are should be visible", $(".flc-videoPlayer-transcriptArea")); - jqUnit.isVisible("Transcript are should container error message", $(".flc-videoPlayer-transcriptArea .flc-videoPlayer-transcriptError")); - jqUnit.notVisible("Transcript are should not container transcript text", $(".flc-videoPlayer-transcriptArea .flc-videoPlayer-transcript-text")); - testsCompleted = true; - clearTimeout(timeoutId); - start(); - }) + listeners: fluid.tests.makeListenersForClickTriggeredTest(transcriptItemSelector, "onLoadTranscriptError", testTranscriptLoadError) } }); @@ -188,21 +205,22 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } ] }, - listeners: fluid.tests.makeListenersForClickTriggeredTest(transcriptItemSelector, "onLoadTranscriptError", function (that) { - jqUnit.isVisible("Transcript are should be visible", $(".flc-videoPlayer-transcriptArea")); - jqUnit.isVisible("Transcript are should container error message", $(".flc-videoPlayer-transcriptArea .flc-videoPlayer-transcriptError")); - jqUnit.notVisible("Transcript are should not container transcript text", $(".flc-videoPlayer-transcriptArea .flc-videoPlayer-transcript-text")); - testsCompleted = true; - clearTimeout(timeoutId); - start(); - }) + listeners: fluid.tests.makeListenersForClickTriggeredTest(transcriptItemSelector, "onLoadTranscriptError", testTranscriptLoadError) } }); -/* + var testCaptionLoadError = function (that) { + jqUnit.isVisible("Caption error message should be visible", $(".flc-videoPlayer-captionArea .flc-videoPlayer-captionError")); + $(".flc-videoPlayer-captionArea .flc-videoPlayer-captionError .flc-errorPanel-dismissButton").click(); + jqUnit.notVisible("After dismiss, caption error message should not be visible", $(".flc-videoPlayer-captionArea .flc-videoPlayer-captionError")); + testsCompleted = true; + clearTimeout(timeoutId); + start(); + }; + fluid.tests.runTestWithTimeout({ desc: "Caption (amara) load error", - expect: 5, + expect: 2, opts: { video: { captions: [ @@ -214,13 +232,13 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } ] }, - listeners: fluid.tests.makeListenersForClickTriggeredTest(captionItemSelector, "onLoadCaptionError") + listeners: fluid.tests.makeListenersForClickTriggeredTest(captionItemSelector, "onLoadCaptionError", testCaptionLoadError) } }); fluid.tests.runTestWithTimeout({ desc: "Caption (non-amara) load error", - expect: 8, + expect: 2, opts: { video: { captions: [ @@ -232,10 +250,9 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } ] }, - listeners: fluid.tests.makeListenersForClickTriggeredTest(captionItemSelector, "onLoadCaptionError") + listeners: fluid.tests.makeListenersForClickTriggeredTest(captionItemSelector, "onLoadCaptionError", testCaptionLoadError) } }); -*/ }); })(jQuery); From b215e331c4bf7e5cf81cb2ea8afabbed67adacbd Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Fri, 2 Nov 2012 14:50:11 -0400 Subject: [PATCH 19/27] FLUID-4558: Tests for dismiss callback --- tests/js/VideoPlayerErrorsTests.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/js/VideoPlayerErrorsTests.js b/tests/js/VideoPlayerErrorsTests.js index fcae9f4..932eb3a 100644 --- a/tests/js/VideoPlayerErrorsTests.js +++ b/tests/js/VideoPlayerErrorsTests.js @@ -32,6 +32,13 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt var captionItemSelector = ".flc-videoPlayer-captionControls-container .flc-videoPlayer-language"; var transcriptItemSelector = ".flc-videoPlayer-transcriptControls-container .flc-videoPlayer-language"; + fluid.tests.testRetryCallback = function () { + jqUnit.assertTrue("Retrycallback is called", true); + testsCompleted = true; + clearTimeout(timeoutId); + start(); + }; + /* Using custom baseOpts here because we specifically don't want the default base opts */ var baseOpts = { video: { @@ -63,7 +70,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt panel: { href: "errorPanel_template.html" } - } + }, + retryCallback: "fluid.tests.testRetryCallback" } }, transcript: { @@ -77,7 +85,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } } } - }, + } } } } @@ -130,7 +138,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.tests.initVideoPlayer($(".videoPlayer-errors"), config.opts); timeoutId = setTimeout(function () { if (!testsCompleted) { - jqUnit.assertFalse("Expected error event didn't fire", true); + jqUnit.assertFalse("Expected error event or callback didn't fire", true); start(); } }, 5000); @@ -139,7 +147,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.tests.runTestWithTimeout({ desc: "Video load error", - expect: 2, + expect: 3, opts: { video: { sources: [ @@ -154,9 +162,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt listener: function (that) { jqUnit.assertTrue("Error event fires", true); jqUnit.assertTrue("Error message is displayed", $(".flc-videoPlayer-videoError .flc-errorPanel-message").text().length > 0); - testsCompleted = true; - clearTimeout(timeoutId); - start(); + // trigger the retry callback + $(".flc-videoPlayer-videoError .flc-errorPanel-retryButton").click(); }, priority: "last" } From f8ecf377dd1686fd355ffab8654cef5245699600 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Mon, 5 Nov 2012 11:07:55 -0500 Subject: [PATCH 20/27] FLUID-4558: Implement reload functionality in video error panel. --- css/VideoPlayer.css | 2 ++ js/ErrorPanel.js | 5 +++-- js/VideoPlayer.js | 6 ++---- js/VideoPlayer_media.js | 13 ++++++++----- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/css/VideoPlayer.css b/css/VideoPlayer.css index eb8ba25..20991b7 100644 --- a/css/VideoPlayer.css +++ b/css/VideoPlayer.css @@ -60,6 +60,8 @@ height: 2em; background-size: contain; text-transform: uppercase; + pointer-events: all; + cursor: pointer; } /* * Controller area diff --git a/js/ErrorPanel.js b/js/ErrorPanel.js index 0c122d9..e7d75a5 100644 --- a/js/ErrorPanel.js +++ b/js/ErrorPanel.js @@ -83,8 +83,9 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt that.locate("dismissButton").click(that.hide); - that.locate("retryButton").click(function () { - fluid.invokeGlobalFunction(that.options.retryCallback, [that]); + that.locate("retryButton").click(function (ev) { + ev.preventDefault(); + fluid.invokeGlobalFunction(that.options.retryCallback, fluid.makeArray(that.options.retryArgs)); }); that.events.onReady.fire(that); diff --git a/js/VideoPlayer.js b/js/VideoPlayer.js index 6dd7692..ba8ee83 100644 --- a/js/VideoPlayer.js +++ b/js/VideoPlayer.js @@ -419,16 +419,14 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }); that.events.onLoadedMetadata.addListener(function () { + that.locate("videoError").hide(); that.resize(); - + bindKeyboardControl(that); }); that.events.onMediaLoadError.addListener(function () { that.locate("video").hide(); - // TODO: this is not good: add a class instead? need to remove it on success - that.locate("videoPlayer").css("height", "20em"); - that.locate("overlay").css("width", "32em"); }); }; diff --git a/js/VideoPlayer_media.js b/js/VideoPlayer_media.js index 0ea7298..83887c1 100644 --- a/js/VideoPlayer_media.js +++ b/js/VideoPlayer_media.js @@ -65,7 +65,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt href: "../html/videoError_template.html" } }, - retryCallback: "fluid.videoPlayer.media.tempFunc" + retryCallback: "fluid.videoPlayer.media.reloadSources", + retryArgs: "{media}" } } }, @@ -86,8 +87,10 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt sources: [] }); - fluid.videoPlayer.media.tempFunc = function () { - console.log("callback called, at least"); + fluid.videoPlayer.media.reloadSources = function (that) { + $("source", that.container).detach(); + fluid.videoPlayer.media.renderSources(that); + that.container.show().load(); }; fluid.videoPlayer.media.createSourceMarkup = { @@ -106,7 +109,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } }; - var renderSources = function (that) { + fluid.videoPlayer.media.renderSources = function (that) { $.each(that.options.sources, function (idx, source) { var renderer = that.options.sourceRenderers[source.type]; if ($.isFunction(renderer)) { @@ -280,7 +283,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }; fluid.videoPlayer.media.finalInit = function (that) { - renderSources(that); + fluid.videoPlayer.media.renderSources(that); bindMediaModel(that); bindMediaDOMEvents(that); }; From 34f8eff7e74eab3455521a4407b8a6455b3f76ff Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Mon, 5 Nov 2012 11:17:33 -0500 Subject: [PATCH 21/27] FLUID-4558: Focus and hover styling for video error retry button. --- css/VideoPlayer.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/css/VideoPlayer.css b/css/VideoPlayer.css index 20991b7..5a434bf 100644 --- a/css/VideoPlayer.css +++ b/css/VideoPlayer.css @@ -95,7 +95,8 @@ cursor: pointer; background-color: rgba(0, 0, 0, 0); } -.fl-videoPlayer-button:focus { +.fl-videoPlayer-button:focus, +.fl-videoPlayer-videoError .fl-errorPanel-retryButton:focus { background-color: #3195C7; } .fl-theme-uio-yb .fl-videoPlayer-button:focus { background-color: #FFFF00 !important; } @@ -108,7 +109,8 @@ background-size: cover; } -.fl-videoPlayer-controller-buttons .fl-videoPlayer-button:hover { +.fl-videoPlayer-controller-buttons .fl-videoPlayer-button:hover, +.fl-videoPlayer-videoError .fl-errorPanel-retryButton:hover { background-color: rgba(153, 153, 153, 0.45); box-shadow: inset 0 0 0.25em rgba(0, 0, 0, 0.30); } From 7d12c1bc46e46dea32634657784a5afeeaa2aad5 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Mon, 5 Nov 2012 15:20:18 -0500 Subject: [PATCH 22/27] FLUID-4558: Clean up error panel template loading. --- js/ErrorPanel.js | 48 ++++++++++++-------- js/VideoPlayer.js | 9 ++++ js/VideoPlayer_html5Captionator.js | 4 +- js/VideoPlayer_media.js | 4 +- js/VideoPlayer_transcript.js | 4 +- tests/js/TestUtils.js | 42 ++++------------- tests/js/VideoPlayerErrorsTests.js | 44 ++++-------------- tests/js/VideoPlayerHTML5CaptionatorTests.js | 9 ++++ 8 files changed, 67 insertions(+), 97 deletions(-) diff --git a/js/ErrorPanel.js b/js/ErrorPanel.js index e7d75a5..ba2f21a 100644 --- a/js/ErrorPanel.js +++ b/js/ErrorPanel.js @@ -66,29 +66,37 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }; }; - fluid.errorPanel.finalInit = function (that) { - that.container.hide(); - fluid.fetchResources(that.options.templates, function (res) { - if (res.panel.fetchError) { - fluid.log("couldn't fetch error message template"); - fluid.log("status: " + res.panel.fetchError.status + - ", textStatus: " + res.panel.fetchError.textStatus + - ", errorThrown: " + res.panel.fetchError.errorThrown); - return; - } - - that.container.append(res.panel.resourceText); - that.locate("dismissButtonText").text(that.options.strings.dismissLabel); - that.locate("retryButtonText").text(that.options.strings.retryLabel); + fluid.errorPanel.processTemplate = function (that) { + if (that.options.templates.panel.fetchError) { + fluid.log("couldn't fetch error message template"); + fluid.log("status: " + that.options.templates.panel.fetchError.status + + ", textStatus: " + that.options.templates.panel.fetchError.textStatus + + ", errorThrown: " + that.options.templates.panel.fetchError.errorThrown); + return; + } - that.locate("dismissButton").click(that.hide); + that.container.append(that.options.templates.panel.resourceText); + that.locate("dismissButtonText").text(that.options.strings.dismissLabel); + that.locate("retryButtonText").text(that.options.strings.retryLabel); - that.locate("retryButton").click(function (ev) { - ev.preventDefault(); - fluid.invokeGlobalFunction(that.options.retryCallback, fluid.makeArray(that.options.retryArgs)); - }); + that.locate("dismissButton").click(that.hide); - that.events.onReady.fire(that); + that.locate("retryButton").click(function (ev) { + ev.preventDefault(); + fluid.invokeGlobalFunction(that.options.retryCallback, fluid.makeArray(that.options.retryArgs)); }); + + that.events.onReady.fire(that); + }; + + fluid.errorPanel.finalInit = function (that) { + that.container.hide(); + if (!that.options.templates.panel.resourceText) { + fluid.fetchResources(that.options.templates, function (res) { + fluid.errorPanel.processTemplate(that); + }); + } else { + fluid.errorPanel.processTemplate(that); + } }; })(jQuery); diff --git a/js/VideoPlayer.js b/js/VideoPlayer.js index ba8ee83..145759b 100644 --- a/js/VideoPlayer.js +++ b/js/VideoPlayer.js @@ -313,6 +313,15 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt videoPlayer: { forceCache: true, href: "../html/videoPlayer_template.html" + }, + videoError: { + href: "../html/videoError_template.html" + }, + transcriptError: { + href: "../html/transcriptError_template.html" + }, + captionError: { + href: "../html/captionError_template.html" } }, videoTitle: "unnamed video" diff --git a/js/VideoPlayer_html5Captionator.js b/js/VideoPlayer_html5Captionator.js index 23678e3..227b066 100644 --- a/js/VideoPlayer_html5Captionator.js +++ b/js/VideoPlayer_html5Captionator.js @@ -63,9 +63,7 @@ https://source.fluidproject.org/svn/LICENSE.txt dismissLabel: "Dismiss error" }, templates: { - panel: { - href: "../html/captionError_template.html" - } + panel: "{videoPlayer}.options.templates.captionError" } } } diff --git a/js/VideoPlayer_media.js b/js/VideoPlayer_media.js index 83887c1..de0144a 100644 --- a/js/VideoPlayer_media.js +++ b/js/VideoPlayer_media.js @@ -61,9 +61,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt retryLabel: "Retry" }, templates: { - panel: { - href: "../html/videoError_template.html" - } + panel: "{videoPlayer}.options.templates.videoError" }, retryCallback: "fluid.videoPlayer.media.reloadSources", retryArgs: "{media}" diff --git a/js/VideoPlayer_transcript.js b/js/VideoPlayer_transcript.js index ce25388..8418eae 100644 --- a/js/VideoPlayer_transcript.js +++ b/js/VideoPlayer_transcript.js @@ -48,9 +48,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt messageTemplate: "Sorry, %0 transcripts currently unavailable" }, templates: { - panel: { - href: "../html/transcriptError_template.html" - } + panel: "{videoPlayer}.options.templates.transcriptError" } } } diff --git a/tests/js/TestUtils.js b/tests/js/TestUtils.js index b5a3ddc..9c9f1e9 100644 --- a/tests/js/TestUtils.js +++ b/tests/js/TestUtils.js @@ -63,40 +63,16 @@ fluid.registerNamespace("fluid.testUtils"); }, templates: { videoPlayer: { - forceCache: true, href: "../../html/videoPlayer_template.html" - } - }, - components: { - media: { - options: { - components: { - videoError: { - options: { - templates: { - panel: { - href: "errorPanel_template.html" - } - } - } - }, - transcript: { - options: { - components: { - transcriptError: { - options: { - templates: { - panel: { - href: "errorPanel_template.html" - } - } - } - }, - } - } - } - } - } + }, + videoError: { + href: "errorPanel_template.html" + }, + transcriptError: { + href: "errorPanel_template.html" + }, + captionError: { + href: "errorPanel_template.html" } } }; diff --git a/tests/js/VideoPlayerErrorsTests.js b/tests/js/VideoPlayerErrorsTests.js index 932eb3a..288a9cc 100644 --- a/tests/js/VideoPlayerErrorsTests.js +++ b/tests/js/VideoPlayerErrorsTests.js @@ -53,6 +53,15 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt videoPlayer: { forceCache: true, href: "../../html/videoPlayer_template.html" + }, + videoError: { + href: "../../html/videoError_template.html" + }, + transcriptError: { + href: "../../html/transcriptError_template.html" + }, + captionError: { + href: "../../html/captionError_template.html" } }, model: { @@ -66,43 +75,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt components: { videoError: { options: { - templates: { - panel: { - href: "errorPanel_template.html" - } - }, retryCallback: "fluid.tests.testRetryCallback" } - }, - transcript: { - options: { - components: { - transcriptError: { - options: { - templates: { - panel: { - href: "errorPanel_template.html" - } - } - } - } - } - } - } - } - } - }, - html5Captionator: { - options: { - components: { - captionError: { - options: { - templates: { - panel: { - href: "errorPanel_template.html" - } - } - } } } } diff --git a/tests/js/VideoPlayerHTML5CaptionatorTests.js b/tests/js/VideoPlayerHTML5CaptionatorTests.js index 787e599..d0656b8 100644 --- a/tests/js/VideoPlayerHTML5CaptionatorTests.js +++ b/tests/js/VideoPlayerHTML5CaptionatorTests.js @@ -87,6 +87,15 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt templates: { videoPlayer: { href: "../../html/videoPlayer_template.html" + }, + videoError: { + href: "errorPanel_template.html" + }, + transcriptError: { + href: "errorPanel_template.html" + }, + captionError: { + href: "errorPanel_template.html" } } }; From 127ed6e4b9afa66f8d5d8c044a92c32cf73ea0ec Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Mon, 5 Nov 2012 15:55:04 -0500 Subject: [PATCH 23/27] FLUID-4558: Code clean-up --- js/MenuButton.js | 3 +-- js/VideoPlayer.js | 18 +++++++++--------- js/VideoPlayer_html5Captionator.js | 9 ++++----- js/VideoPlayer_media.js | 13 +------------ js/VideoPlayer_transcript.js | 12 ++++++------ tests/js/VideoPlayerErrorsTests.js | 8 ++++---- tests/js/VideoPlayerIntegrationTests.js | 2 +- tests/js/VideoPlayerTranscriptTests.js | 2 +- 8 files changed, 27 insertions(+), 40 deletions(-) diff --git a/js/MenuButton.js b/js/MenuButton.js index 13a1eb9..6b302f9 100644 --- a/js/MenuButton.js +++ b/js/MenuButton.js @@ -112,7 +112,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.videoPlayer.languageMenu.setUpKeyboardA11y = function (that) { that.container.fluid("tabbable"); - that.selectableContext = fluid.selectable(that.container, { + that.container.fluid("selectable", { direction: fluid.a11y.orientation.VERTICAL, selectableSelector: that.options.selectors.menuItem, // TODO: add simple style class support to selectable @@ -343,7 +343,6 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt var showHide = that.readIndirect("showHidePath"); that.button.locate("button").toggleClass(that.options.styles.buttonWithShowing, showHide); } - that.applier.modelChanged.addListener(that.options.showHidePath, refreshButtonClass); refreshButtonClass(); that.events.onReady.fire(that); diff --git a/js/VideoPlayer.js b/js/VideoPlayer.js index 145759b..283b08d 100644 --- a/js/VideoPlayer.js +++ b/js/VideoPlayer.js @@ -159,7 +159,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt onTranscriptShow: "{videoPlayer}.events.onTranscriptShow", onTranscriptElementChange: "{videoPlayer}.events.onTranscriptElementChange", onTranscriptsLoaded: "{videoPlayer}.events.onTranscriptsLoaded", - onLoadTranscriptError: "{videoPlayer}.events.onLoadTranscriptError" + onTranscriptLoadError: "{videoPlayer}.events.onTranscriptLoadError" } } } @@ -188,8 +188,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt afterScrub: "{videoPlayer}.events.afterScrub", onTranscriptsReady: "{videoPlayer}.events.canBindTranscriptMenu", onCaptionsReady: "{videoPlayer}.events.canBindCaptionMenu", - onLoadCaptionError: "{videoPlayer}.events.onLoadCaptionError", - onLoadTranscriptError: "{videoPlayer}.events.onLoadTranscriptError" + onCaptionLoadError: "{videoPlayer}.events.onCaptionLoadError", + onTranscriptLoadError: "{videoPlayer}.events.onTranscriptLoadError" } } }, @@ -203,7 +203,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt captions: "{videoPlayer}.options.video.captions", events: { onReady: "{videoPlayer}.events.onCaptionsReady", - onLoadCaptionError: "{videoPlayer}.events.onLoadCaptionError" + onCaptionLoadError: "{videoPlayer}.events.onCaptionLoadError" } } } @@ -222,12 +222,12 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt afterScrub: null, onStartScrub: null, onTemplateLoadError: null, - onLoadCaptionError: null, + onCaptionLoadError: null, afterCurrentTranscriptChanged: null, onTranscriptHide: null, onTranscriptShow: null, onTranscriptElementChange: null, - onLoadTranscriptError: null, + onTranscriptLoadError: null, onReady: null, // public, time events @@ -458,7 +458,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt controls: "true" } }] - } + }; } // Keep the selector to render "fluid.videoPlayer.controllers" @@ -682,8 +682,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt seconds = parseFloat(seconds); seconds = seconds < 0 || isNaN(seconds) ? 0 : seconds; - var hours = parseInt(seconds / 3600); - var minutes = parseInt(seconds / 60) % 60; + var hours = parseInt(seconds / 3600, 10); + var minutes = parseInt(seconds / 60, 10) % 60; seconds = (seconds % 60).toFixed(3); // Return result of type HH:MM:SS.mmm diff --git a/js/VideoPlayer_html5Captionator.js b/js/VideoPlayer_html5Captionator.js index 227b066..a296469 100644 --- a/js/VideoPlayer_html5Captionator.js +++ b/js/VideoPlayer_html5Captionator.js @@ -34,7 +34,7 @@ https://source.fluidproject.org/svn/LICENSE.txt afterTrackElCreated: null, onTracksReady: null, onReady: null, - onLoadCaptionError: null + onCaptionLoadError: null }, elPaths: { currentCaptions: "currentTracks.captions", @@ -105,7 +105,7 @@ https://source.fluidproject.org/svn/LICENSE.txt setTimeout(function () { fluid.each($("track", that.locate("video")), function (element, key) { if (element.track.readyState === captionator.TextTrack.ERROR) { - that.events.onLoadCaptionError.fire(key, that.options.captions[key], true); + that.events.onCaptionLoadError.fire(key, that.options.captions[key], true); } }); }, 3000); @@ -156,7 +156,7 @@ https://source.fluidproject.org/svn/LICENSE.txt }; var errorHandler = function () { - that.events.onLoadCaptionError.fire(key, opts, false); + that.events.onCaptionLoadError.fire(key, opts, false); that.events.afterTrackElCreated.fire(that); }; @@ -183,7 +183,6 @@ https://source.fluidproject.org/svn/LICENSE.txt appendCueCanvasTo: that.locate("caption")[0], sizeCuesByTextBoundingBox: true }); - bindCaptionatorModel(that); that.events.onReady.fire(that, fluid.allocateSimpleId(that.locate("caption"))); }; @@ -192,7 +191,7 @@ https://source.fluidproject.org/svn/LICENSE.txt container: "{html5Captionator}.dom.captionError", options: { listeners: { - "{html5Captionator}.events.onLoadCaptionError": { + "{html5Captionator}.events.onCaptionLoadError": { listener: "{captionError}.show", args: "{arguments}.1.label" }, diff --git a/js/VideoPlayer_media.js b/js/VideoPlayer_media.js index de0144a..0ac9b5c 100644 --- a/js/VideoPlayer_media.js +++ b/js/VideoPlayer_media.js @@ -27,17 +27,6 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt * Composes markup for video sources and responds to the video events * *********************************************************************************/ - /* These media error codes and descriptions were taken from - * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-media-error - * TODO: Find better strings!! - */ - fluid.videoPlayer.media.errorStrings = { - "1": "The fetching process for the media resource was aborted by the user agent at the user's request.", - "2": "A network error of some description caused the user agent to stop fetching the media resource, after the resource was established to be usable.", - "3": "An error of some description occurred while decoding the media resource, after the resource was established to be usable.", - "4": "The media resource indicated by the src attribute was not suitable." - }; - fluid.defaults("fluid.videoPlayer.media", { gradeNames: ["fluid.viewComponent", "autoInit"], components: { @@ -224,7 +213,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }); mediaElementVideo.addEventListener("error", function (err) { - that.events.onMediaLoadError.fire("Error: " + fluid.videoPlayer.media.errorStrings[mediaElementVideo.error.code]); + that.events.onMediaLoadError.fire(); }); // Fire onMediaReady here rather than finalInit() because the instantiation diff --git a/js/VideoPlayer_transcript.js b/js/VideoPlayer_transcript.js index 8418eae..452c08c 100644 --- a/js/VideoPlayer_transcript.js +++ b/js/VideoPlayer_transcript.js @@ -55,7 +55,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt }, events: { onTranscriptsLoaded: null, - onLoadTranscriptError: null, + onTranscriptLoadError: null, onIntervalChange: null, onCurrentTranscriptChanging: null, afterCurrentTranscriptChanged: null, @@ -287,15 +287,15 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt transcriptSource.transcriptText = that.options.strings.loading; var errorHandler = function () { transcriptSource.transcriptText = null; - that.events.onLoadTranscriptError.fire(that, transcriptSource); + that.events.onTranscriptLoadError.fire(that, transcriptSource); }; // Handle Universal Subtitles JSON files for transcripts if (transcriptSource.type === "text/amarajson") { - var handler = function (data) { + var successHandler = function (data) { fluid.videoPlayer.transcript.parseTranscriptFile(that, data, currentIndex, that.convertSecsToMilli, "text", "start_time", "end_time"); }; - fluid.videoPlayer.fetchAmaraJson(transcriptSource.src, handler, errorHandler); + fluid.videoPlayer.fetchAmaraJson(transcriptSource.src, successHandler, errorHandler); } else { var opts = { type: "GET", @@ -405,7 +405,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt that.events.onTranscriptsLoaded.addListener(function (intervalList) { that.transcriptInterval.setIntervalList(intervalList); }); - that.events.onLoadTranscriptError.addListener(function () { + that.events.onTranscriptLoadError.addListener(function () { that.locate("transcriptText").hide(); }); @@ -468,7 +468,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt container: "{transcript}.dom.transcriptError", options: { listeners: { - "{transcript}.events.onLoadTranscriptError": { + "{transcript}.events.onTranscriptLoadError": { listener: "{transcriptError}.show", args: "{arguments}.1.label" }, diff --git a/tests/js/VideoPlayerErrorsTests.js b/tests/js/VideoPlayerErrorsTests.js index 288a9cc..c9ed5d2 100644 --- a/tests/js/VideoPlayerErrorsTests.js +++ b/tests/js/VideoPlayerErrorsTests.js @@ -168,7 +168,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } ] }, - listeners: fluid.tests.makeListenersForClickTriggeredTest(transcriptItemSelector, "onLoadTranscriptError", testTranscriptLoadError) + listeners: fluid.tests.makeListenersForClickTriggeredTest(transcriptItemSelector, "onTranscriptLoadError", testTranscriptLoadError) } }); @@ -186,7 +186,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } ] }, - listeners: fluid.tests.makeListenersForClickTriggeredTest(transcriptItemSelector, "onLoadTranscriptError", testTranscriptLoadError) + listeners: fluid.tests.makeListenersForClickTriggeredTest(transcriptItemSelector, "onTranscriptLoadError", testTranscriptLoadError) } }); @@ -213,7 +213,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } ] }, - listeners: fluid.tests.makeListenersForClickTriggeredTest(captionItemSelector, "onLoadCaptionError", testCaptionLoadError) + listeners: fluid.tests.makeListenersForClickTriggeredTest(captionItemSelector, "onCaptionLoadError", testCaptionLoadError) } }); @@ -231,7 +231,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt } ] }, - listeners: fluid.tests.makeListenersForClickTriggeredTest(captionItemSelector, "onLoadCaptionError", testCaptionLoadError) + listeners: fluid.tests.makeListenersForClickTriggeredTest(captionItemSelector, "onCaptionLoadError", testCaptionLoadError) } }); diff --git a/tests/js/VideoPlayerIntegrationTests.js b/tests/js/VideoPlayerIntegrationTests.js index 7c8407c..c314dad 100644 --- a/tests/js/VideoPlayerIntegrationTests.js +++ b/tests/js/VideoPlayerIntegrationTests.js @@ -153,7 +153,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt // Depending on the connection with universal subtitle site, the test below may not get run with remote universal subtitle transcript files. if (!initialTranscriptText) { initialTranscriptText = transcriptTextArea.text(); - jqUnit.assertNotNull("Initially, he transcript text is filled in", initialTranscriptText); + jqUnit.assertNotNull("Initially, the transcript text is filled in", initialTranscriptText); } else { jqUnit.assertNotEquals("The transcript text is switched", transcriptTextArea.text(), initialTranscriptText); } diff --git a/tests/js/VideoPlayerTranscriptTests.js b/tests/js/VideoPlayerTranscriptTests.js index 713a012..a74911c 100644 --- a/tests/js/VideoPlayerTranscriptTests.js +++ b/tests/js/VideoPlayerTranscriptTests.js @@ -230,7 +230,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt // show transcripts that.applier.requestChange("displayTranscripts", true); }, - onLoadTranscriptError: { + onTranscriptLoadError: { listener: function () { jqUnit.isVisible("Initally, error message should be visible", $(".flc-videoPlayer-transcriptError")); jqUnit.notVisible("Transcript text is hidden", $(".flc-videoPlayer-transcript-text")); From eb6932d521a912b0a278f76d3febd79448d1b9cd Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Tue, 6 Nov 2012 16:58:04 -0500 Subject: [PATCH 24/27] FLUID-4558: Styling of errors with proper assets --- css/VideoPlayer.css | 100 ++++++++++++++++-- .../error/2Xcloseerror-blackonwhite-hover.png | Bin 0 -> 1589 bytes images/error/2Xcloseerror-blackonwhite.png | Bin 0 -> 1503 bytes .../2Xcloseerror-blackonyellow-hover.png | Bin 0 -> 1543 bytes images/error/2Xcloseerror-blackonyellow.png | Bin 0 -> 1493 bytes .../error/2Xcloseerror-whiteonblack-hover.png | Bin 0 -> 1578 bytes images/error/2Xcloseerror-whiteonblack.png | Bin 0 -> 1514 bytes .../2Xcloseerror-yellowonblack-hover.png | Bin 0 -> 1534 bytes images/error/2Xcloseerror-yellowonblack.png | Bin 0 -> 1504 bytes images/error/2Xretry-black-hover.png | Bin 0 -> 1556 bytes images/error/2Xretry-black.png | Bin 0 -> 1512 bytes images/error/2Xretry-hover.png | Bin 0 -> 2800 bytes images/error/2Xretry-white-hover.png | Bin 0 -> 1552 bytes images/error/2Xretry-white.png | Bin 0 -> 1553 bytes images/error/2Xretry-yellow-hover.png | Bin 0 -> 1569 bytes images/error/2Xretry-yellow.png | Bin 0 -> 1565 bytes images/error/2xretry.png | Bin 0 -> 2741 bytes .../error/closeerror-blackonwhite-hover.png | Bin 0 -> 1265 bytes images/error/closeerror-blackonwhite.png | Bin 0 -> 1191 bytes .../error/closeerror-blackonyellow-hover.png | Bin 0 -> 1250 bytes images/error/closeerror-blackonyellow.png | Bin 0 -> 1188 bytes .../error/closeerror-whiteonblack-hover.png | Bin 0 -> 1273 bytes images/error/closeerror-whiteonblack.png | Bin 0 -> 1204 bytes .../error/closeerror-yellowonblack-hover.png | Bin 0 -> 1253 bytes images/error/closeerror-yellowonblack.png | Bin 0 -> 1195 bytes images/error/exclamation-black.png | Bin 0 -> 3370 bytes images/error/exclamation-white.png | Bin 0 -> 3008 bytes images/error/exclamation-yellow.png | Bin 0 -> 3184 bytes images/error/exclamation.png | Bin 0 -> 7240 bytes images/error/retry-black-hover.png | Bin 0 -> 1203 bytes images/error/retry-black.png | Bin 0 -> 1190 bytes images/error/retry-hover.png | Bin 0 -> 1731 bytes images/error/retry-white-hover.png | Bin 0 -> 1186 bytes images/error/retry-white.png | Bin 0 -> 1199 bytes images/error/retry-yellow-hover.png | Bin 0 -> 1202 bytes images/error/retry-yellow.png | Bin 0 -> 1190 bytes images/error/retry.png | Bin 0 -> 1754 bytes 37 files changed, 90 insertions(+), 10 deletions(-) create mode 100644 images/error/2Xcloseerror-blackonwhite-hover.png create mode 100644 images/error/2Xcloseerror-blackonwhite.png create mode 100644 images/error/2Xcloseerror-blackonyellow-hover.png create mode 100644 images/error/2Xcloseerror-blackonyellow.png create mode 100644 images/error/2Xcloseerror-whiteonblack-hover.png create mode 100644 images/error/2Xcloseerror-whiteonblack.png create mode 100644 images/error/2Xcloseerror-yellowonblack-hover.png create mode 100644 images/error/2Xcloseerror-yellowonblack.png create mode 100644 images/error/2Xretry-black-hover.png create mode 100644 images/error/2Xretry-black.png create mode 100644 images/error/2Xretry-hover.png create mode 100644 images/error/2Xretry-white-hover.png create mode 100644 images/error/2Xretry-white.png create mode 100644 images/error/2Xretry-yellow-hover.png create mode 100644 images/error/2Xretry-yellow.png create mode 100644 images/error/2xretry.png create mode 100644 images/error/closeerror-blackonwhite-hover.png create mode 100644 images/error/closeerror-blackonwhite.png create mode 100644 images/error/closeerror-blackonyellow-hover.png create mode 100644 images/error/closeerror-blackonyellow.png create mode 100644 images/error/closeerror-whiteonblack-hover.png create mode 100644 images/error/closeerror-whiteonblack.png create mode 100644 images/error/closeerror-yellowonblack-hover.png create mode 100644 images/error/closeerror-yellowonblack.png create mode 100644 images/error/exclamation-black.png create mode 100644 images/error/exclamation-white.png create mode 100644 images/error/exclamation-yellow.png create mode 100644 images/error/exclamation.png create mode 100644 images/error/retry-black-hover.png create mode 100644 images/error/retry-black.png create mode 100644 images/error/retry-hover.png create mode 100644 images/error/retry-white-hover.png create mode 100644 images/error/retry-white.png create mode 100644 images/error/retry-yellow-hover.png create mode 100644 images/error/retry-yellow.png create mode 100644 images/error/retry.png diff --git a/css/VideoPlayer.css b/css/VideoPlayer.css index 5a434bf..60b9cf8 100644 --- a/css/VideoPlayer.css +++ b/css/VideoPlayer.css @@ -41,20 +41,38 @@ border: 2px solid #999999; width: 32em; height: 20em; - background: url("../images/default/video-load-error.png") no-repeat 50% 30%; + background: url("../images/error/exclamation.png") no-repeat 50% 30% rgba(0,0,0,.5); +} +.fl-theme-uio-yb .fl-videoPlayer-videoError { + background-image: url("../images/error/exclamation-yellow.png"); +} +.fl-theme-uio-wb .fl-videoPlayer-videoError { + background-image: url("../images/error/exclamation-white.png"); +} +.fl-theme-uio-by .fl-videoPlayer-videoError, +.fl-theme-uio-bw .fl-videoPlayer-videoError { + background-image: url("../images/error/exclamation-black.png"); } .fl-videoPlayer-videoError .fl-errorPanel-container { margin-top: 40%; text-align: center; + color: #FFFFFF; } .fl-videoPlayer-videoError .fl-errorPanel-message { text-transform: lowercase; font-style: italic; + text-shadow: 1px 1px 0 rgba(0,0,0,.3); +} +.fl-theme-uio-yb .fl-videoPlayer-videoError .fl-errorPanel-message, +.fl-theme-uio-wb .fl-videoPlayer-videoError .fl-errorPanel-message, +.fl-theme-uio-by .fl-videoPlayer-videoError .fl-errorPanel-message, +.fl-theme-uio-bw .fl-videoPlayer-videoError .fl-errorPanel-message { + text-shadow: none; } .fl-videoPlayer-videoError .fl-errorPanel-retryButton { border: none; - background: url("../images/default/uio.png") no-repeat left center; + background: url("../images/error/2xretry.png") no-repeat left center; padding-left: 1.5em; font-size: inherit; height: 2em; @@ -62,7 +80,41 @@ text-transform: uppercase; pointer-events: all; cursor: pointer; + color: #FFFFFF; + text-shadow: 1px 1px 0 rgba(0,0,0,.3); +} +.fl-videoPlayer-videoError .fl-errorPanel-retryButton:hover { + background-color: rgba(0, 0, 0, 0.25); + box-shadow: inset 0 0 0.25em rgba(0,0,0,.5); + background-image: url("../images/error/2xretry-hover.png"); } +.fl-theme-yb .fl-videoPlayer-videoError .fl-errorPanel-retryButton { + background-image: url("../images/error/2xretry-yellow.png"); +} +.fl-theme-yb .fl-videoPlayer-videoError .fl-errorPanel-retryButton:hover { + background-image: url("../images/error/2xretry-yellow-hover.png"); + box-shadow: none; + border: 1px solid #FFFF00; +} +.fl-theme-wb .fl-videoPlayer-videoError .fl-errorPanel-retryButton { + background-image: url("../images/error/2xretry-white.png"); +} +.fl-theme-wb .fl-videoPlayer-videoError .fl-errorPanel-retryButton:hover { + background-image: url("../images/error/2xretry-white-hover.png"); + box-shadow: none; + border: 1px solid #FFFFFF; +} +.fl-theme-by .fl-videoPlayer-videoError .fl-errorPanel-retryButton, +.fl-theme-bw .fl-videoPlayer-videoError .fl-errorPanel-retryButton { + background-image: url("../images/error/2xretry-black.png"); +} +.fl-theme-by .fl-videoPlayer-videoError .fl-errorPanel-retryButton:hover, +.fl-theme-bw .fl-videoPlayer-videoError .fl-errorPanel-retryButton:hover { + background-image: url("../images/error/2xretry-black-hover.png"); + box-shadow: none; + border: 1px solid #000000; +} + /* * Controller area */ @@ -109,8 +161,7 @@ background-size: cover; } -.fl-videoPlayer-controller-buttons .fl-videoPlayer-button:hover, -.fl-videoPlayer-videoError .fl-errorPanel-retryButton:hover { +.fl-videoPlayer-controller-buttons .fl-videoPlayer-button:hover { background-color: rgba(153, 153, 153, 0.45); box-shadow: inset 0 0 0.25em rgba(0, 0, 0, 0.30); } @@ -606,6 +657,7 @@ ul.fl-videoPlayer-transcripts-languageList li { color: white; background-color: black; opacity: 0.7; + padding: 0.5em; } .fl-videoPlayer-controller-menu-captions { @@ -618,17 +670,45 @@ ul.fl-videoPlayer-transcripts-languageList li { .fl-videoPlayer-captionError { position: absolute; bottom: 0; - left: 10%; + margin: 0 10%; + width: 80% +} +.fl-videoPlayer-captionError .fl-errorPanel-message { + width: 95%; } .fl-videoPlayer-captionError .fl-errorPanel-dismissButton { pointer-events: visible; cursor: pointer; border: none; - background: url("../images/default/transcriptclose.png") no-repeat center center; + background: url("../images/error/2Xcloseerror-blackonwhite.png") no-repeat center center; + background-color: transparent !important; + height: 1.5em; + width: 1.5em; + background-size: cover; + margin-top: -1em; + margin-right: -1em; font-size: inherit; - height: 2em; - background-size: contain; - text-transform: uppercase; +} +.fl-videoPlayer-captionError .fl-errorPanel-dismissButton:hover { + background-image: url("../images/error/2Xcloseerror-blackonwhite-hover.png"); +} +.fl-theme-uio-yb .fl-videoPlayer-captionError .fl-errorPanel-dismissButton { + background-image: url("../images/error/2Xcloseerror-blackonyellow.png"); +} +.fl-theme-uio-yb .fl-videoPlayer-captionError .fl-errorPanel-dismissButton:hover { + background-image: url("../images/error/2Xcloseerror-blackonyellow-hover.png"); +} +.fl-theme-uio-by .fl-videoPlayer-captionError .fl-errorPanel-dismissButton { + background-image: url("../images/error/2Xcloseerror-yellowonblack.png"); +} +.fl-theme-uio-by .fl-videoPlayer-captionError .fl-errorPanel-dismissButton:hover { + background-image: url("../images/error/2Xcloseerror-yellowonblack-hover.png"); +} +.fl-theme-uio-bw .fl-videoPlayer-captionError .fl-errorPanel-dismissButton { + background-image: url("../images/error/2Xcloseerror-whiteonblack.png"); +} +.fl-theme-uio-bw .fl-videoPlayer-captionError .fl-errorPanel-dismissButton:hover { + background-image: url("../images/error/2Xcloseerror-whiteonblack-hover.png"); } /* @@ -652,7 +732,7 @@ ul.fl-videoPlayer-transcripts-languageList li { .fl-videoPlayer-transcriptError .fl-errorPanel-message { text-align: center; - padding-top: 30%; + margin: 3em; } .fl-videoPlayer-transcript-text { word-spacing: 0.1em; diff --git a/images/error/2Xcloseerror-blackonwhite-hover.png b/images/error/2Xcloseerror-blackonwhite-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..7ef051ec17e975e21e75469473db351e83910fbd GIT binary patch literal 1589 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 S|2haNnmt|pT-G@yGywppKTKi( literal 0 HcmV?d00001 diff --git a/images/error/2Xcloseerror-blackonyellow.png b/images/error/2Xcloseerror-blackonyellow.png new file mode 100644 index 0000000000000000000000000000000000000000..5984648373d107c70ca65e008cd917859832a417 GIT binary patch literal 1493 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#2=9ZF3nBND}m`v0qS*jGd6NEay7!K7nB%s3xGDeq!wkCrKY$Q<>xAZJ#CeV z+bw1|&4cPq!QvLEKg@CJ)dxC89~8AnQ4JFUrXLU!o^XL2c+yYJ1E%*PV8WiOxAHRs z1CzU_i(^Q|tvfUKdpR459JAMLQn(gaSg3j9=E80fnI+Bo51wVkJN#mDVn5zDSN8{# z(o%0ham{m98ZQ*MSP$AyUi-1ebYj4|?Vj1W>l*J*sP;ei^F;1B&r6y-*V9@`6N4j! z!&+Sv{r1-j%e87hbjsjJKV*IEj**jV2KTAS+vH|{5|FmfhPK+m> zH|B^%L^3{mzP~3vuHkm{gHJ*hwydv8A3S_`J3QP%=v&M0`!lx2KD>G6$ThJmCpklw zGcTPYz2&Jy-`2D*k-z13o#irpY%sk!YwI1?zbv&ErmJpQmRz}&b)hHwN|BJAJXhcL z>78?k|KfQ+Dkb=M$-JHOc9cl1y7ZQPo>eE*{mAE7edXF^x5&x<&@CrSKYTyE;N?}FmuSFYc4 z;0=4b%q-_aWu?MmL`Z$ehT z(=O^*J8iRtlWrBy)`xAfH^nTUuSsG7H6Qr`_6T zw#L*{+I||X!?KTp#G!>mwi5TRw*sjd&hDk{*~IM$RC=1SJD)JybV(_ zTXQh~r*L5g*S{MxJDRJN*4Z`fxA8jiHQGRY#{WlL7J}1X2>+IT6x_hb@WHLRIkQ|S Q2UO5{y85}Sb4q9e0P}M-=l}o! literal 0 HcmV?d00001 diff --git a/images/error/2Xcloseerror-whiteonblack-hover.png b/images/error/2Xcloseerror-whiteonblack-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..5c4cea5c57abe7c42365cf7b22bb78614b55d04f GIT binary patch literal 1578 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 literal 0 HcmV?d00001 diff --git a/images/error/2Xcloseerror-yellowonblack-hover.png b/images/error/2Xcloseerror-yellowonblack-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..8d983ad550372a1d68969d02735a5e9b12092ab1 GIT binary patch literal 1534 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 RP#IL_db;|#taD0e0sxOrIbQ$( literal 0 HcmV?d00001 diff --git a/images/error/2Xretry-black-hover.png b/images/error/2Xretry-black-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..0fbc934e8910cf30685de91490adfed83132da2e GIT binary patch literal 1556 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 ZGuZq(_Gil3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|81#=KlDb#X~hD#E>34K5C;EJ)Q4 zN-fSWElLJPT$(b-ssbzLqSVBa{GyQj{2W*)24v)y-fM)1rW~NwK8kkzTSUS5J82|&@(ACJ&$k@Ww+|tR})X~w+ z%*Y6)*Cju>G&eP`1g19yq1O$kUQlAlEdbi=l3J8mmYU*Ll%J~r_Ow+dZnrq&G!Lpb z1-DyVaO%|uIz}H9wMbD769T3m5EGtofgE_!Pt60S_ab1zUb`>XnSp_++0(@_q~g|_ z+2_4Q5@n91pS9WXw?W~OMmL|MfJ?{Uh*u9>TLSpG%ii5u`(nZW#@Hskm#!alqy-gQ z+!EPM9W{1U2#ATa#&l(IW+^UzzP9|}zJ0IXyg6fSt#89$lW>3L-14^b`#zuhz3*^Z z&He8)_GT_*>RKp)f)X{_k`U-v&>uF5^zNIpISZyIoe8Sx^7QZuO44gstmoF2hIOzk-uzQya8&5nhqPcPuF;jYkPUCN`^R2m+AW^Qay$hwB#hF7X3Y|6GC zm0`$!@Mc}EwgS7BV)Pm3*!#>I{~A@~=ew{PioN*$|2*H0kmH3$$yL?T5yHRKbYg$U z=iK#TUi3$(h9j~)OH=vR=B(8IBmW%5f6thsVc)H=bXL=jt5ydkuOFEs{8Y|etOtl8eN&NZ2iIEl*|Nt;_USh% zPg{TcRaK9d*d9jPw%YE5sO^h*KX8{cyxwJhO8S^?>fe^=!peoKW<5?=);7oB=Q{Pi z&z?OVlO=vm`?B6fbYWlrgRG#SNppn$T+ptnRm^YB-Rp2Qx7gtNn&^{LvLBf3c=@-w zJ78|fKQ`A7d^YU*2hOL(B&ezW4?p#)@7wC>lTv=TuY2~*P{bL8c#a03A_Iu{x6kek Zj0}ej__MF@ls1A2X-`)_mvv4FO#q#(M?U}n literal 0 HcmV?d00001 diff --git a/images/error/2Xretry-yellow-hover.png b/images/error/2Xretry-yellow-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..27f4778a90b10b2cca7a7ad9cdf6a50662154b39 GIT binary patch literal 1569 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 jTf8wp6M*>f7*W6$fWh7ydk4US zKw383;4n!TH2}vF@ZgNa9Jq`pRHH#4yr)daU_}EG2os3n@;$6(uGU*YxNHxrP-iNV zDkK3Mu5Y{u*cu-g%!-d@xv{N0H$w0-oSJ|KNEi?qFNQD1$vmt+^5WFv#W2DO@-amc z?P2w|s4yxGLK27oh%+1mVKh9~IpLhKIJ67o(`BWu zCSph8wvZ{GYN=-)Rvd{$h(jQxQYl=DfeS=Y2$Y+f+ad=V4O3^p#C!P?h786RTYqLC z17enlE0k~re8?grgDHrUcvz{G{=EdA@Cz+p{Arui8-|cEga{NIxwxdyiB#(UPv!Bx zWQ!$RfPeIUl~^3SR|p`s0AfL$h^0QbNbAK?LL5m1FeCy|us{&=c^7FMfkYtY2!s&Q zRxHGh%3yK%ivjzO2r3ol#}`W&d=}tG_OMcOz`0yDjzYn>5?x)-&S0)1@?jC?m5eayZkBi1}zoLai z!Jvs~Z=#yd>FZkl#g-yJvq6=q2Z{t%~FJ+2AJH~NwgPwF%nwZu_Kk9 zk+0LQMi2NERZ1~6?$7&X@Xy;PEp=JvE^)t~yQI4v2{&7n3vO95k`mj1KjCL;pJtTJ zR!;704@%o@+ItoyO3iD!;>yg4S@7<$PO^xZP*n~{L{Ghf` zwt}g%bZJg_czA;5ZUVN1Gi6KDOsNSco3fmg#qQGS$qfz;yFGqBrm1ek+!$r>h$VZi|V^mjkW-d1~vy(Htk7nPK72xQ>Nf>_$z))%tAUhrLQsZ z{lXi3tG|Z-siTzrb`BO`8_>1zpddnIiKx;b++}}}i(g%rxgq_~H)S@izwsK&&z%dP z`uoqPz(6&(IRG$*5Qc490~?LFa^=dt^x^J~eW5fOO|x+hrtg(M7Fu8cJ;7-Dk9^>l z(X6bjiSUgLG|Nk~XASL)puvlL&{#0}3Ub`VMuI!E^>$KPN?9Xol_X7J8o+nOO{ zsH!NRg(UvDjQGsE&g=W{=aC)-8WHGd$FdQB9eoXy&NtU&f2te+%O);rb zLx)mCVlhIz$^2>dl*-mH{Bbl(;2KXaZdl&e357xxFND9Ab#!&DM77&NTcwncM{Dak z9zJ~775I|WqK6vP$Vp^--6TcCqJavFRVC2b_5@B)T=(*FfuaYS*1{W`Az8g?QXtW zZtn94mK9382xt@T-Ull>=sZf@??tt~?l@A22KU#};=pEn7x-{CdamMk;W2UGHww7iPX za+1Ww)&4m1@b2Ba1%opcRhn8IuR_WO@APCSqE~;DUu&|-wAZrX$2f9BrEsO}k(0CY zl!lR3e8kcc+OzjBUPz4F9ep~BJd~k9LHXjTC$*szA0N-OlIPZa@elfQxEv1W^zQXJ z4;lwlK^(IwC$sgBF4WaMZ4{c_Vtkua-8C?fpfD#!l}*TMXB#=U8#2o%sWnP@cW>`; zV*?%c0-{2xTvKuW{QKPGIm6zK9T$(6LsfJ~>70{m)4`iZes#iNFjo573GK{?h<<5# zdAYMmrVjLA@-2Rpez~`{!OAz=3|~J!-I|KAwq6F`D4iboBP1lGV(jdc1yxoO;=4{l{{=gsj41#B literal 0 HcmV?d00001 diff --git a/images/error/closeerror-blackonwhite-hover.png b/images/error/closeerror-blackonwhite-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..db17b3ddcec2035487e19bf61943896a13878b01 GIT binary patch literal 1265 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#2=9ZF3nBND}m`vLFl!_sTY(OatnYqyQCInmZhe+73JqDfIV%MiQ6q^IL(9V zO~LIJbDVnhfsWA!MJ-ZP!-Rn82gHOYTp$OY^i%VI>AeV;uxBST3otM+CVRR#hE&{2 zN;$y0q|rjA<88wYjtMWa8rDc#a)A z#uF42lyEZr{5-=sw$;-TZ*R-xVdsnL|DP~Th?^i8u=&9P=lJ;l z_DdO3*iPuoSh&dH&-*VgFH3M;zI5rIx{8W`E}s!|ir5vOwj^zBZ64Q`FJCISCd7n= zv5Bu=xw5hQ`0?Y(FPLYt8VQTYtxHTy{1L?E93C#-+|j|oEG;eV?Cs4Rw^d)?b%goZc(Aj7#P_Y YgihRDKJR9{8>kTSboFyt=akR{0Gd;)2mk;8 literal 0 HcmV?d00001 diff --git a/images/error/closeerror-blackonwhite.png b/images/error/closeerror-blackonwhite.png new file mode 100644 index 0000000000000000000000000000000000000000..cccd2abbe7645ecf607d014c3a3f722d6b1b1653 GIT binary patch literal 1191 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;=WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2v2cW|hp4h>{3jAFJg2T)jk)8oi3#0-$aN1{?c|g2d$P)DnfH z)bz|eTc!8A_bVx6rr0WloBA5~7C5J7WO`H;r3P2|g(O#HCtIc{+1n}DR9FEG$W1Lt zRH(?!$t$+1uvG$^YXxM3g!Ppaz)DK8ZIvL7itr6kaLzAERWQ{v)=f4rG*mD%(=#+N zH8V5RQ7|$vG}1Q!A~Rh>6Dw0QDv55FG|-pw6wGYnPFt43sj+7T$xvrSfQI&tPC^3CAB!YD6^m>Ge1uOWMX1cerbuV z640d(FXR@$jm;~D1`{yA^eYkz^bPe4Kwg3=^!3HBG&dKny0|1L72#g21{a4^7NqJ2 zr55Lx79|5CE=?I^Re_arQEFmIeo;t%ehw@Y12XbU@{2R_3lyA#%@j0z6O%LZKmwXz z9lpL+o_WP3iFwJXo-VdZKr{3*GgGWg%*`w;-3-l)49tuT4PA{ajf^c^%`KgrO&uNG z%#4g+dR_99OLJ56N?>|Z5PHpV>IEf++ybD@E~!PCWvMA{Mftf3U{70R;&zJ(PV=C8 zQ*gV*6sKN&pkwqwQHvDSFd<<20WskT7s!Dp{nR{QdM^Sd>@5>7Wiv1^N_x6DhE&|z zGIcL+vw?t{Zii5pf&g!}!ncLpB4!C}T2XB$n7+(x;C!JuVUb9Ph0PQXKEt1ZXVfDP z{Cs!t=Q%fyz2By#$~G%lSoal6&R1=&&N=f?dEdm`+wF!Qq>dNHi?ptJ9aR=<&iiho zVLSi6_Yu+8OZ^iizJ-RK^gqNNpVU#i&`{7d@cy>7f5LtU6}Q_hKY6>Zab-Z#)i2-I z9Je{Eb2??#vLcDL{<5S*pVmc5I-hJ_1V)-xoa{?DaO=mu`8~IELs#jXe)+0$-igmO z8y6YOTIR{J*VX#W(z>%XFQzda+nav$O0w0<1y&MG2luG@oUS&ax!<0V|HgFNyq|{?r#{Z^WxjSo|AE>82DZhDm)W1jzXuf^p00i_>zopr01L^V AP5=M^ literal 0 HcmV?d00001 diff --git a/images/error/closeerror-blackonyellow-hover.png b/images/error/closeerror-blackonyellow-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..bc548910b852d6071c9ebbbabd6084feff80536b GIT binary patch literal 1250 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#Mv>2~2MaLa!@My`aR9TL84#CABECEH%ZgC_h&L>}jh^EN*dhG<7p{ zc60=qXO7)15WOk5-QtYXE&4#m=!2pbDXL*Y!1M!R!V@l#15f&?dBF5u1Wee8bPlHaB6FSt8q zUwWX#=BwxH=*O$;=skDd{<*Wm&+l9=ZpHuS!KXXVe-^&4c7q% zF7rcMKV)R{>Fms1s66NC=Nr0WjXrE1OYP$I#17=J@vt4J=n40X}GN%AKc0 z0gDBv1m#5>j{5c@E?b7nzxUYpL%uf-$Gwf{mVLM{?*Cqsmc1nz*TZ9$U9O+rcEK@R zC24x{J@1>33SmlK!R2 z!H82u$YJ-}bxX6292?K`Z<1S^EcSPo@1lCaRy`}tx|!!6t1r24zVvQhjcsg6-ofMw z=`O|k>sz%td=?n$YztkO-F9!YdiN}*_~|d(8JHP_JO7m* TvU2_pDs?{3jAFJg2T)jk)8oi3#0-$aN1{?c|g2d$P)DnfH z)bz|eTc!8A_bVx6rr0WloBA5~7C5J7WO`H;r3P2|g(O#HCtIc{+1n}DR9FEG$W1Lt zRH(?!$t$+1uvG$^YXxM3g!Ppaz)DK8ZIvL7itr6kaLzAERWQ{v)=f4rG*mD%(=#+N zH8V5RQ7|$vG}1Q!A~Rh>6Dw0QDv55FG|-pw6wGYnPFt43sj+7T$xvrSfQI&tPC^3CAB!YD6^m>Ge1uOWMX1cerbuV z640d(FXR@$jm;~D1`{yA^eYkz^bPe4Kwg3=^!3HBG&dKny0|1L72#g21{a4^7NqJ2 zr55Lx79|5CE=?I^Re_arQEFmIeo;t%ehw@Y12XbU@{2R_3lyA#%@j0z6O%LZKmwXz z9lpL+o_WP3iFwJXo-VdZKr{3*GgGWg%*`w;-3%R#49tuT4PA{ajf^c^%`KgrO&uNG z%#4g+dR_99OLJ56N?>|Z5PF?(>IEf++ybD@E~!PCWvMA{Mftf3U{70R;&zJ#PV=C8 zQ*gV*5~p5$pkwqwQHvDSFd<<20WskT7s!Dp{nR{QdM^SdZ0}#!f*BYX#XMacLn>}< znQF+_ylfuUH)I=jY{qpYTSvwa$F z{^T!9E1P&HS%xL*eA=ykw_P^#c#qlIJ?pq=zWDm(SBiV|rk~m#J@c^Rvl_jg>Y6ix z&N&`I|E{{({H9;70fXNBwT+Cg*KgZs x8hZOj{u{k*^Zp-RIQjAFZsu#J^&jXRU{GKz`k9gb;wY%x@O1TaS?83{1OU+WnkxVR literal 0 HcmV?d00001 diff --git a/images/error/closeerror-whiteonblack-hover.png b/images/error/closeerror-whiteonblack-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..e6a0a2833bc9b60f941910d661d74b167def312b GIT binary patch literal 1273 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#Mv>2~2MaLaz}{y`aR9TL84#CABECEH%ZgC_h&L>}jh^oNlpn!)YE= zZwhX=7~s^a4|I$^C~A?S8YToxKOiPN;Q~4Eq@S7xOz%a&gdO51*vP=ZnC~IdwfYakrHK*}hNb03 z6FUzcJu>0N*F9_;y7F~LswbsCC^*Oby=Z6nqdA-VEL6^Q1V3uIq5nz$h;5{3jAFJg2T)jk)8oi3#0-$aN1{?c|g2d$P)DnfH z)bz|eTc!8A_bVx6rr0WloBA5~7C5J7WO`H;r3P2|g(O#HCtIc{+1n}DR9FEG$W1Lt zRH(?!$t$+1uvG$^YXxM3g!Ppaz)DK8ZIvL7itr6kaLzAERWQ{v)=f4rG*mD%(=#+N zH8V5RQ7|$vG}1Q!A~Rh>6Dw0QDv55FG|-pw6wGYnPFt43sj+7T$xvrSfQI&tPC^3CAB!YD6^m>Ge1uOWMX1cerbuV z640d(FXR@$jm;~D1`{yA^eYkz^bPe4Kwg3=^!3HBG&dKny0|1L72#g21{a4^7NqJ2 zr55Lx79|5CE=?I^Re_arQEFmIeo;t%ehw@Y12XbU@{2R_3lyA#%@j0z6O%LZKmwXz z9lpL+o_WP3iFwJXo-VdZKr{3*GgGWwU5rgk+}uo!49tuT4PA{ajf^c^%`KgrO&uNG z%#4g+dR_99OLJ56N?>|Z5PHpU>IEf++ybD@E~!PCWvMA{Mftf3U{70RVsVR!xtWEf zo1rsK^PqZDaJ$6>r(S)aWAs5$ixkx`Az=CeG2sap$bl#Q)I4B%F9IfPp%+t6FfcHx zdAc};RNUG!)iJBpK%niod!~8*!$Zs!jO!oj-dh(i{oS4>^A8Mp)jfGfvv&xFaP0~R zvah`Kpxe&t@qvUHXa3LZnXKWZY51db*0TOx>-PWJ_x{RV3E|{EnaST24f-Dc*7>8? zmDKS#g4Jc87s>tiyVJ5q%6`oeem<1CUw1k%@;(wNO?B;E~e~;NcesOcZjNi_E=XNBl{liqpBEisj WGVmtzf?Z2M<%p-NpUXO@geCxyk*X2^ literal 0 HcmV?d00001 diff --git a/images/error/closeerror-yellowonblack-hover.png b/images/error/closeerror-yellowonblack-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..c1d66242b7efdf25572930a2a7c96c8b383e55f2 GIT binary patch literal 1253 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#2=9ZF3nBND}m`vLFhHbsTY(OatnYqyQCInmZhe+73JqDfIV%MiQ6qkIL(9V zO~LIJW1M>RfsWA!MJ-ZP!-Rn82gHOYTp$OY^i%VI>AeV;u&?m{2Iexxa8DPCg2~dXQn`+A!uE=?J~X zDqFkobuoOZUw^C&;Fxb*|Gw;F|DpbvBCkKQ{M`Rf@KV)y%Axf6>7-)*WwTDMl*rss zxY{XX^}$TeXwkfLCvFrO?{e(P&0O3*VPo>=`}|eO;geHe8T}PWU|{Mh)pk0a`T$h$ Nc)I$ztaD0e0susAx<~*3 literal 0 HcmV?d00001 diff --git a/images/error/closeerror-yellowonblack.png b/images/error/closeerror-yellowonblack.png new file mode 100644 index 0000000000000000000000000000000000000000..1294966b835bf3af59a2e6f370d48b7ca649ef5f GIT binary patch literal 1195 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 literal 0 HcmV?d00001 diff --git a/images/error/exclamation-white.png b/images/error/exclamation-white.png new file mode 100644 index 0000000000000000000000000000000000000000..82aa98a72ad8b43387a4180b0b4ebf6eaa2125f1 GIT binary patch literal 3008 zcmaJ@3pkVeAD>%9hRiA@+r*(}v$YFkpkB zzPUaEfiR@|(U|ZzAAZvG7QpYa^&}zuStj)bq+5j1QW;y!MR-REBe@v5fE~kSa@kQS ziNA6^5D2YRd=?-D7@H^@p@6`i#}JYQBAAUpcz7j?*qk`76cfpf;U{?FUNM7UGTfj2JNYCe>RLKSn_QlXe76vltaVjxc_ z6-szQ5r(?O1!K!#bNC7K19qPn3-0_Td@%?_aAX=+%unJ*trrUg zm`{tQ@W1Va^i93b+^BDRA%Bxggv}7=xAs4_`pXbpKl9D6g$sASmOeKDu6Hq9*p_!s z3?LACWID~8mHcS@%x2!!4X7K>Hl_#rR`F22htpUPE8Q1IE4_ePQUYL$4ZN55Trx&^ zi%fsU1!0d9(2jr|b+2^LXzgoPB-9_R~J6gZ*nS1l;}okY0*S%&Vf4 zjuNF2lDO*Kox+oEQ{TFNNzU@9*hnOGF>u#KaK@uK1M}!dU#y;#Um>8SLf9Z#|`Wj*sd(tHl{(m4C#Yz zj+&4SBhpB1W%c8ZdRxTFUH#_0SDVe7pvWyo$36XJ)>y`C&M=C9}Eh*2v+2aB zDiJ`-n%vmOsEK$9uUreXUW6X%rzLLGYtJseie-n^-?fpSAOO<`FdijHe|K#2nYn(kTX9E*9>H}klbc0DaTe9_uUV|J zGf3HjUQPU5x&<1|qdgDlJ?QX`EgzB^Lz$N>yR^~o&cetLD6{@kr~DDpjtlA6Su*wVW8KSEwXW{i>t_l)Dc|m1#%f)y z-s^`2Vkfe5w9dy*V;Nwga`i+uQ~Nw?8k+&0tEKwu<0Gc1r&I&idLCk}wep3ZKy#Jj z*-Xt2^w%1j+(g`KN#)RJ@$wH<<-#I@^CgK<1tpPpHG>_o&|L-Q!|nC^R-uo>E^8iEiUe5 zb#*!5GW8U7d+WfH78tr9KWip%jcV8N(DQHN6(~bs{gs*QXlSk1#Uww{{-H=Or{y5y zmazT}Pl3__Y&55_6JX-e{_*TcjW}Q}*2?CdQuB~@Rnwa&McGQcRfFXv*;x8RBQ*j& z@^JW1CqG#Zc=F)5B2ptuV*eCLcL$i$C8XTWy%nWk-JgZ@?jKRWjog_)v(|y#8|$cD zi}JhojxPUM<>BQ6ol1GN%6yY1eh@}if{Ay|KE$5W>S%muw4>TP<#`jnFz!3|RY$;- zI!jGps1JKibAHYck=6FQx$&JVeMtIppnpP9EFRD9mUEwsE2cCRZO4*jrz(>LmHfY1Y;R83J4ab0WmYp7j_;J|6D+?l^3 zqYta2(_st25e;wVJVx3%0j(1XL~wh}i+Za5g8ZOJFKsB??WeFLKW`N|b6kPw&EwpH z^Qw<`o}%8%1G)9ozmWMs(O%jNgKakMWyT;qqHq#sF%0svESUR@q4&|{lObc_d0P#` znC@jYhZI`7)2**|{&9P+-ePz^6t%8vfRx#DrIVLh_wTwoi?Z&)R;%}?#f9BdUmO|p zL>cchIm>DuYO<|()?RhE+cErylrxcG&=dDb!z&r&O6@q$J-e|$_q7)v`cgUyOsafQ zO8xve9JJ_G#bSJ_$>d2eD0NQMT{ly86WDy9;{tVIPwbA?x;sW_LB?#^F;Z)3Z)1|K z8s}kG8NHDkSJU3R9(up@!K09re#$lan^~uhs^a%AhD>#AS3mu&De|Wd!=lLk+-<|= zF_h%;+ngiM=+nm|R}Op8AMOCpKQ)VHuX>BldUtgLY3|pTyxzg{VWIeS(U%%x*Oiab zEONT^)TySjO)UvEOC289TpgdYjy--j-o9bvs^Nn?R|9`lcv2sYbl?Y44+){v&<>x1UnGZn{KlIn39oUF?fT1-wd%H5H~ThiNK9{F zwzKZXZ0YVc0*qg9x9Rxf4EJi7!$RMaX(YgX6}&Hq!=eFJFKK499v0iE6M AIsgCw literal 0 HcmV?d00001 diff --git a/images/error/exclamation-yellow.png b/images/error/exclamation-yellow.png new file mode 100644 index 0000000000000000000000000000000000000000..39f474d7d129b01239f699468a5f8d5cf4b8f952 GIT binary patch literal 3184 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 GjQKD12Pwt? literal 0 HcmV?d00001 diff --git a/images/error/retry-black-hover.png b/images/error/retry-black-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..5e2762c674d1467677bdea7f65c5435968407487 GIT binary patch literal 1203 zcmeAS@N?(olHy`uVBq!ia0vp^iXhCv1|-9u9Lfh$k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#2=9ZF3nBND}m`vLFjeFsTY(OatnYqyQCInmZhe+73JqDfIV%MiQ6saIL(9V zO~LIJ3!HlOfsWA!MJ-ZP!-Rn82gHOYTp$OY^i%VI>AeV;us7`NdBwoMsOstB7*cU7 z>CgZF_Jzx?9|INsv5T@lba#m1JtDJY2DkZq7Wak^dMe-I73%no*iSfej%lODT3N9F8}6&=SOP4HRqN3P0(>DdGY z6L${Br*@McakwR|DiYd!M_wU~ZPsavfYzxZCm0?66_#tcv?V+k9>rS}adWI^Owt3|&vK=q zf-i{kCu>JJbD@ET^2FeBgT8Is+#x~GW0j;~$)s?Ap=A52taE)o{6WQtr>mdKI;Vst E0QCuo;s5{u literal 0 HcmV?d00001 diff --git a/images/error/retry-black.png b/images/error/retry-black.png new file mode 100644 index 0000000000000000000000000000000000000000..dfb3a791002b08ebe66f29d7d778282e082532c9 GIT binary patch literal 1190 zcmaJ>O>Em#9Cz7PwvItN!8WQ^Je&!&T4Tq~w`pkH*iIwiE=!{|;#9|eO{~m*p6xbn zB~V*F6b>6gAORYOAcUB97~;TSnzU*VG=UHob{Q9>iUS&yshoO1Eze!kaUfW>-+O=k zf4~3NPw&nAWcEl;e-A+rM{*NF0k6;c-@$JDpZ@i0gqNpXvE)vnS+}m(kl-~`h9GAs z6S9ezYUJ1pVASIN!^`2lZ>CAGid~2G}W@(>v!9@5r}{)jH>r=!m6pGPFP1e~r3r!us*Id&0$sJ?TRe>)pn}PW*Ya zN)TNqasprUelGvLy*>6#df#(z@PstT+-sc4K8CU(mfgC&{Cp#}zgRA;@Y2k!3nxF| zSKy)aK>D@7{M&!@k%P_d*6PwG-TV96`PTH%x_CJI!*c)4$Fmbl1MhXW*7I9uUP`PD z-?3^VHh5IX+%4y~#=x=WwZP1+U_+!1Uf3SEa`sXw+j~h|9oPFtuGa<&^YB3O$?so2 zy>721-n|H#KR(mAyS&Lngv-;D1RpBgi+}r(bZmIN_!FtHk8Jc`KJd%ZS4SJ4H0|;9 zhlg69HUFwCzyIpl*PlAxOJ`3V-)e3=(e?R|+;b{&+aor*!7GQq*bMjH?^>J4_Xj@x rtukO3Z*k>2eIH*uG1to5`_oHZg#ONu)c7)c&A&Nv;-qkOd}iez1&4>U literal 0 HcmV?d00001 diff --git a/images/error/retry-hover.png b/images/error/retry-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..fb7891db305b1b32889e004606a492a4676af025 GIT binary patch literal 1731 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 BjWPfL literal 0 HcmV?d00001 diff --git a/images/error/retry-yellow-hover.png b/images/error/retry-yellow-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..b015a9299f78e7f305b4f904cbd347518a2d0673 GIT binary patch literal 1202 zcmeAS@N?(olHy`uVBq!ia0vp^iXhCv1|-9u9Lfh$k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#2=9ZF3nBND}m`vLFl!_sTY(OatnYqyQCInmZhe+73JqDfIV%MiQ6q^IL(9V zO~LIJbDVnhfsWA!MJ-ZP!-Rn82gHOYTp$OY^i%VI>AeV;un&Jx{>#9?sN(737*cU7 z>CgZF_J zykR}!-uOd)(j<3>7~Uf?0j4gqxDPI|U}`B>-1wjAW3j@&MwK$L4%f`L90LEij=Xiy zVf$*TEzr*JF?huvMTN9QTa_Iwcm>}S=x`rdzu?ovF9j?bT8lPXs0eHAZmb8(GIz;!*`CE~-v+w(SS4K%T-+z&)yKeLz;N*S>gc}{t;<2Bh^MQc J%Q~loCIC|Rk01a5 literal 0 HcmV?d00001 diff --git a/images/error/retry-yellow.png b/images/error/retry-yellow.png new file mode 100644 index 0000000000000000000000000000000000000000..69fb527a480d88d9cf62883dbd374c0e504a590a GIT binary patch literal 1190 zcmeAS@N?(olHy`uVBq!ia0vp^iXhCv1|-9u9Lfh$k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#2=9ZF3nBND}m`vLFhHcsTY(OatnYqyQCInmZhe+73JqDfIV%MiQ6p(IL(9V zO~LIJL!5f`fsWA!MJ-ZP!-Rn82gHOYTp$OY^i%VI>AeV;uyCgZF_J zTc?k~QbFpJSo5d8beL-|u$lQ|vO=BT+ae_i&9Ac`Y3Qd+yyR!9E2O3@u)eWqKVzY)fVZQr zIMX8^g*w)$_S{EgwrH){(NJMI>x#U|KSc$xrJ;$fJ+=)M%mVvcz3SN>**eH@dc9bY w)mhB)I3v6Iis0ftiP9r0l-njLGcfTmm|M<0R=t1#n8m`)JTW>u8D9%?^c(TK1xBY;2tN`Dg=I)8sD?0XDI5y{LIt=J5<{RgE$Ig2 zg~6Dvm4`(lk)lAR1Xd70qYpu=P@!lH#>+>m0woC$0>nZ)k^qEf@uvT)P9OmuBD6LG+_3zERX zy%QBF3IVvV8Uknps=I_lCIfUjf$Tw})5&fCg+!(hN$5j&CwnkybSA|Um>D=Unp!Gj zig|*WSm=(0i$f357FqYiJRA1e*ELm@V*NVShV%q$lPlSW%j1F=*fysgmYp82 z#czpwXdchDa<6ngdE?m6Q=NKGkZ#rScu?v38+SpoG(|=k_Jn(GHQV@C4`kS09Kakl zHF>(i(ZZ?e)_sHD-Y?wqGB5gRV@-4X4G+I|vA1(^R)p1rW_w?^k^SK8oP0DtDc<~< zA?bIXMdAZ9&a!>Xo=-aK2h;D@chBeaC5uK|9N>C|mgCX-e93_Bwa=k>;b!=nn&y?Q zh00THkFQzQCQ%Le_V)IY9i(FRSZ%(`U&ps@Yw_K6%@1&VPFz`#O84}<{+W-tSy^9* z&k_H!kcpuYt;2W2c8<1BH2n^1%Wv0hTgZuNidfJlrM#qxLIGMyU=Jr+XX5L^&lFUQ z*h%;;F18nIZ*SjgtsCFM z!xaK=jwOy378bg85mcFUUZptAG86w0_BFjNJ9o7;C@|3Lg~ZLtt+%|pyStP+{Av;# zvvtrqmlJ$;^hDKeC}~Il8phH~)xjxERi}sY?}+juqa{c5dc9MTWPJZFAd~o1^vJp@ zqoAPRa^*l{!MacPRJ-2Pym%(Aex0DzPJG$CA_nYVK9p-gsIpFs@jmfSPF9TG`jfR}^gQYV>Ac4wZ)?d)1_24ix$;n&I1XD=;B`(H(~v89LVJk<@4 zu)bk3iz}yE`rnq8mPSnM&NHkZDeCfHyA&oJU)^VdcU{fZ_4H?+?9hKxzhO%?8-KJt zH_~m~s&9yE%gFq^XAxt5v936Np?)=4o2KtAYiMZbuBfPp`01%C<3dz;ZRcRu)YR1a z*z&Bu?XPClUJE|r+!S8@ZBGdsyQht`D`vYgBsV?_T;hAU9iOwxik!v@&Cc0-Gk<>( zFCxgd$}BU$F2UknzyYB<<_r*%q6}>8q+cjHYll5Q=GY;;371^jL{nmm^D=bSP9Z5~ ZSWNV$z0ico1LNNdBjgA1e&p=P`WN8ZtV;j@ literal 0 HcmV?d00001 From c5912a6ec4e21d7f890ba6fac121577fdfa93bcb Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Tue, 6 Nov 2012 17:10:33 -0500 Subject: [PATCH 25/27] FLUID-4558: Focus styling for errors --- css/VideoPlayer.css | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/css/VideoPlayer.css b/css/VideoPlayer.css index 60b9cf8..3ed9c75 100644 --- a/css/VideoPlayer.css +++ b/css/VideoPlayer.css @@ -114,6 +114,38 @@ box-shadow: none; border: 1px solid #000000; } +.fl-theme-uio-yb .fl-videoPlayer-videoError .fl-errorPanel-retryButton:focus { + background-color: #FFFF00 !important; + background-image: url("../images/error/2xretry-black-hover.png"); +} +.fl-theme-uio-yb .fl-videoPlayer-videoError .fl-errorPanel-retryButton:focus span { + color: #000000 !important; + background-color: #FFFF00 !important; +} +.fl-theme-uio-wb .fl-videoPlayer-videoError .fl-errorPanel-retryButton:focus { + background-color: #FFFFFF !important; + background-image: url("../images/error/2xretry-black-hover.png"); +} +.fl-theme-uio-wb .fl-videoPlayer-videoError .fl-errorPanel-retryButton:focus span { + color: #000000 !important; + background-color: #FFFFFF !important; +} +.fl-theme-uio-by .fl-videoPlayer-videoError .fl-errorPanel-retryButton:focus { + background-color: #000000 !important; + background-image: url("../images/error/2xretry-yellow-hover.png"); +} +.fl-theme-uio-by .fl-videoPlayer-videoError .fl-errorPanel-retryButton:focus span { + color: #FFFF00 !important; + background-color: #000000 !important; +} +.fl-theme-uio-bw .fl-videoPlayer-videoError .fl-errorPanel-retryButton:focus { + background-color: #000000 !important; + background-image: url("../images/error/2xretry-white-hover.png"); +} +.fl-theme-uio-bw .fl-videoPlayer-videoError .fl-errorPanel-retryButton:focus span { + color: #FFFFFF !important; + background-color: #000000 !important; +} /* * Controller area @@ -692,24 +724,36 @@ ul.fl-videoPlayer-transcripts-languageList li { .fl-videoPlayer-captionError .fl-errorPanel-dismissButton:hover { background-image: url("../images/error/2Xcloseerror-blackonwhite-hover.png"); } +.fl-videoPlayer-captionError .fl-errorPanel-dismissButton:focus { + background-image: url("../images/error/2Xcloseerror-whiteonblack.png"); +} .fl-theme-uio-yb .fl-videoPlayer-captionError .fl-errorPanel-dismissButton { background-image: url("../images/error/2Xcloseerror-blackonyellow.png"); } .fl-theme-uio-yb .fl-videoPlayer-captionError .fl-errorPanel-dismissButton:hover { background-image: url("../images/error/2Xcloseerror-blackonyellow-hover.png"); } +.fl-theme-uio-yb .fl-videoPlayer-captionError .fl-errorPanel-dismissButton:focus { + background-image: url("../images/error/2Xcloseerror-yellowonblack.png"); +} .fl-theme-uio-by .fl-videoPlayer-captionError .fl-errorPanel-dismissButton { background-image: url("../images/error/2Xcloseerror-yellowonblack.png"); } .fl-theme-uio-by .fl-videoPlayer-captionError .fl-errorPanel-dismissButton:hover { background-image: url("../images/error/2Xcloseerror-yellowonblack-hover.png"); } +.fl-theme-uio-by .fl-videoPlayer-captionError .fl-errorPanel-dismissButton:focus { + background-image: url("../images/error/2Xcloseerror-blackonyellow.png"); +} .fl-theme-uio-bw .fl-videoPlayer-captionError .fl-errorPanel-dismissButton { background-image: url("../images/error/2Xcloseerror-whiteonblack.png"); } .fl-theme-uio-bw .fl-videoPlayer-captionError .fl-errorPanel-dismissButton:hover { background-image: url("../images/error/2Xcloseerror-whiteonblack-hover.png"); } +.fl-theme-uio-bw .fl-videoPlayer-captionError .fl-errorPanel-dismissButton:focus { + background-image: url("../images/error/2Xcloseerror-blackonwhite.png"); +} /* * Transcript area From b0c87085d86c3901968d854f0343d94b1700bb89 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Thu, 8 Nov 2012 14:49:13 -0500 Subject: [PATCH 26/27] FLUID-4558: Clean up unused CSS. --- css/VideoPlayer.css | 7 ------- 1 file changed, 7 deletions(-) diff --git a/css/VideoPlayer.css b/css/VideoPlayer.css index 3ed9c75..147a921 100644 --- a/css/VideoPlayer.css +++ b/css/VideoPlayer.css @@ -488,13 +488,6 @@ a.fl-videoPlayer-button-wrapper { .fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem:hover { background-color: #ffcc00; } -.fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem-disabled:after { - /* TODO: This is not internationalizable */ - content: " (unavailable)"; -} -.fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem.fl-videoPlayer-menuItem-disabled:hover { - background-color: #FFFFFF; -} .fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem-selected, .fl-videoPlayer-languageMenu .fl-videoPlayer-menuItem-active.fl-videoPlayer-menuItem-selected { color: #FFFFFF; From e2809762ee332c13da4734b4f356ecf408b15380 Mon Sep 17 00:00:00 2001 From: Anastasia Cheetham Date: Tue, 13 Nov 2012 12:13:48 -0500 Subject: [PATCH 27/27] FLUID-4558: Switch video error 'retry callback' to be event based. --- .../ReorganizeFuture/ReorganizeFuture.fr.vtt | 178 +++++++++--------- js/ErrorPanel.js | 4 +- js/VideoPlayer_media.js | 14 +- tests/js/ErrorPanelTests.js | 4 +- tests/js/VideoPlayerErrorsTests.js | 4 +- 5 files changed, 107 insertions(+), 97 deletions(-) diff --git a/demos/videos/ReorganizeFuture/ReorganizeFuture.fr.vtt b/demos/videos/ReorganizeFuture/ReorganizeFuture.fr.vtt index bcc90b5..bc8e54a 100644 --- a/demos/videos/ReorganizeFuture/ReorganizeFuture.fr.vtt +++ b/demos/videos/ReorganizeFuture/ReorganizeFuture.fr.vtt @@ -77,357 +77,357 @@ et nous développons des stratégies pour faire face à cette diversité. Nous essayons de rendre les choses plus simples, 20 -00:00:55.600 -> 00:00:57.500 +00:00:55.600 --> 00:00:57.500 moins complexe, moins chaotique. 21 -00:00:57.500 -> 00:01:00.864 +00:00:57.500 --> 00:01:00.864 Une autre partie de la condition humaine est que nous 22 -00:01:00.864 -> 00:01:03.185 +00:01:00.864 --> 00:01:03.185 essayer de trouver communité et connexions. 23 -00:01:03.200 -> 00:01:09.538 +00:01:03.200 --> 00:01:09.538 Nous formons des groupes formels et informels avec des critères explicites et implicites. 24 -00:01:09.554 -> 00:01:13.979 +00:01:09.554 --> 00:01:13.979 Nous organisons, nous créons des catégories, on filtre, on étiquette. 25 -00:01:13.979 -> 00:01:19.679 +00:01:13.979 --> 00:01:19.679 A notre plus précaires et accablé nous divisons en deux, nous créons des binaires: 26 -00:01:19.679 -> 00:01:21.867 +00:01:19.679 --> 00:01:21.867 masculin, féminin 27 -00:01:21.867 -> 00:01:24.215 +00:01:21.867 --> 00:01:24.215 handicapés, normale 28 -00:01:24.215 -> 00:01:27.031 +00:01:24.215 --> 00:01:27.031 gauche, droite 29 -00:01:27.031 -> 00:01:29.579 +00:01:27.031 --> 00:01:29.579 nous, eux. 30 -00:01:29.579 -> 00:01:34.982 +00:01:29.579 --> 00:01:34.982 Cela se traduit tout dans les questions de qui appartient et qui est exclu. 31 -00:01:34.982 -> 00:01:37.536 +00:01:34.982 --> 00:01:37.536 L'adhésion à des groupes peuvent être auto assignés, 32 -00:01:37.536 -> 00:01:40.467 +00:01:37.536 --> 00:01:40.467 peut être imposée, peut-être même policée. 33 -00:01:40.467 -> 00:01:44.179 +00:01:40.467 --> 00:01:44.179 Les groupes sont utilisés pour affirmer ou d'attribuer des privilèges et des pouvoirs. 34 -00:01:44.179 -> 00:01:46.929 +00:01:44.179 --> 00:01:46.929 Nous utilisons des groupes de juger 35 -00:01:46.929 -> 00:01:49.225 +00:01:46.929 --> 00:01:49.225 valeurs sont attribuées à des groupes 36 -00:01:49.225 -> 00:01:51.887 +00:01:49.225 --> 00:01:51.887 souvent des caractéristiques qui n'ont rien à voir avec 37 -00:01:51.887 -> 00:01:53.356 +00:01:51.887 --> 00:01:53.356 les propriétés originales des groupes fondateurs du 38 -00:01:53.356 -> 00:01:55.982 +00:01:53.356 --> 00:01:55.982 sont généralisés à tous les individus dans le groupe. 39 -00:01:55.982 -> 00:02:00.071 +00:01:55.982 --> 00:02:00.071 Parfois, les gens qui sont dans un groupe imposé 40 -00:02:00.071 -> 00:02:02.831 +00:02:00.071 --> 00:02:02.831 prendre la propriété du groupe et de la réforme 41 -00:02:02.831 -> 00:02:04.423 +00:02:02.831 --> 00:02:04.423 les classifications et les valeurs de l'intérieur. 42 -00:02:04.423 -> 00:02:08.446 +00:02:04.423 --> 00:02:08.446 Parfois, quelqu'un a l'audace 43 -00:02:08.446 -> 00:02:11.746 +00:02:08.446 --> 00:02:11.746 pour sortir de la catégorie, nous avons la mettre dans 44 -00:02:11.746 -> 00:02:16.910 +00:02:11.746 --> 00:02:16.910 mais pour préserver notre catégorie, nous pouvons la renvoyer comme une anomalie. 45 -00:02:16.910 -> 00:02:20.031 +00:02:16.910 --> 00:02:20.031 Certains groupes sont plus fluides tandis que d'autres sont plus fixes. 46 -00:02:20.031 -> 00:02:23.561 +00:02:20.031 --> 00:02:23.561 Nous les groupes se forment pas seulement, mais des groupes de groupes 47 -00:02:23.561 -> 00:02:25.662 +00:02:23.561 --> 00:02:25.662 et des groupes, des groupes, des groupes. 48 -00:02:25.662 -> 00:02:29.100 +00:02:25.662 --> 00:02:29.100 L'adhésion à un groupe peut nous accorder l'adhésion à d'autres groupes. 49 -00:02:29.100 -> 00:02:33.036 +00:02:29.100 --> 00:02:33.036 Mais malgré tout cela, nous sommes diversifiés 50 -00:02:33.036 -> 00:02:34.859 +00:02:33.036 --> 00:02:34.859 nous sommes complexe 51 -00:02:34.859 -> 00:02:36.452 +00:02:34.859 --> 00:02:36.452 nous sommes chaotique. 52 -00:02:36.452 -> 00:02:38.818 +00:02:36.452 --> 00:02:38.818 Individuellement, nous sommes différents 53 -00:02:38.818 -> 00:02:40.441 +00:02:38.818 --> 00:02:40.441 au fil du temps, dans des contextes différents 54 -00:02:40.441 -> 00:02:42.356 +00:02:40.441 --> 00:02:42.356 dans des rôles différents, dans des groupes différents. 55 -00:02:42.356 -> 00:02:45.429 +00:02:42.356 --> 00:02:45.429 Nous devons affirmer notre spécificité 56 -00:02:45.429 -> 00:02:47.864 +00:02:45.429 --> 00:02:47.864 nous avons besoin de former et de perfectionner notre identité. 57 -00:02:47.864 -> 00:02:50.914 +00:02:47.864 --> 00:02:50.914 Nous luttons avec l'identité qui nous est imposé. 58 -00:02:50.914 -> 00:02:56.367 +00:02:50.914 --> 00:02:56.367 Généralement, les gens ne s'intègrent pas facilement dans les catégories assignées 59 -00:02:56.367 -> 00:02:58.982 +00:02:56.367 --> 00:02:58.982 et pourtant nous persistons à les affecter. 60 -00:02:58.982 -> 00:03:02.631 +00:02:58.982 --> 00:03:02.631 Et puis, quelque chose de nouveau arrive 61 -00:03:02.631 -> 00:03:05.415 +00:03:02.631 --> 00:03:05.415 et secoue nos groupes, nos catégories et nos règles 62 -00:03:05.415 -> 00:03:08.266 +00:03:05.415 --> 00:03:08.266 et nous avons besoin d'ajuster, de reconstruire et de repenser. 63 -00:03:08.266 -> 00:03:12.533 +00:03:08.266 --> 00:03:12.533 Quelque chose comme, réseaux et des trucs numérique. 64 -00:03:12.533 -> 00:03:15.471 +00:03:12.533 --> 00:03:15.471 Ce nouveau monde numérique et connecté 65 -00:03:15.471 -> 00:03:17.875 +00:03:15.471 --> 00:03:17.875 remet en question la façon dont nous les choses de groupe 66 -00:03:17.875 -> 00:03:20.759 +00:03:17.875 --> 00:03:20.759 et les défis nos excuses pour laisser les gens sortir. 67 -00:03:20.759 -> 00:03:25.469 +00:03:20.759 --> 00:03:25.469 Le numérique change notre vision du temps, d'espace et de distance 68 -00:03:25.469 -> 00:03:31.085 +00:03:25.469 --> 00:03:31.085 et par extension notre point de vue du design, ce qui est possible et quelles choses coût. 69 -00:03:31.085 -> 00:03:36.048 +00:03:31.085 --> 00:03:36.048 Things Digital sont en plastique, mutable, malléable et adaptable. 70 -00:03:36.048 -> 00:03:39.500 +00:03:36.048 --> 00:03:39.500 Avant, tout le monde ne pouvait en forme 71 -00:03:39.500 -> 00:03:42.167 +00:03:39.500 --> 00:03:42.167 permettre à quelqu'un de quelqu'un d'autre était destiné à l'écart. 72 -00:03:42.167 -> 00:03:46.067 +00:03:42.167 --> 00:03:46.067 . Dans le numérique, la chambre est très extensible 73 -00:03:46.067 -> 00:03:49.767 +00:03:46.067 --> 00:03:49.767 Avant, ce que nous avons créé ne pouvait pas convenir à tous 74 -00:03:49.767 -> 00:03:51.777 +00:03:49.767 --> 00:03:51.777 . Alors nous avons fait l'adapter le plus grand groupe 75 -00:03:51.777 -> 00:03:54.533 +00:03:51.777 --> 00:03:54.533 Nous l'avons fait pour le groupe appelé moyen ou typique 76 -00:03:54.533 -> 00:03:58.267 +00:03:54.533 --> 00:03:58.267 cette gauche à tous de ne pas en moyenne ou typique. 77 -00:03:58.267 -> 00:04:03.399 +00:03:58.267 --> 00:04:03.399 Dans la réalité numérique des choses que nous faisons peut reconfigurer, adapter 78 -00:04:03.399 -> 00:04:06.274 +00:04:03.399 --> 00:04:06.274 et prendre une forme qui est le mieux pour chaque individu. 79 -00:04:06.274 -> 00:04:11.900 +00:04:06.274 --> 00:04:11.900 Dans le monde solide, chaque copie coûte presque le même que l'original. 80 -00:04:11.900 -> 00:04:14.351 +00:04:11.900 --> 00:04:14.351 la consommation a réellement consommé. 81 -00:04:14.351 -> 00:04:18.567 +00:04:14.351 --> 00:04:18.567 Dans le monde numérique, nous pouvons copier presque sans coût. 82 -00:04:18.567 -> 00:04:21.000 +00:04:18.567 --> 00:04:21.000 La consommation ne consomme plus. 83 -00:04:21.000 -> 00:04:24.522 +00:04:21.000 --> 00:04:24.522 Avant, il a fallu beaucoup de temps et d'effort 84 -00:04:24.522 -> 00:04:27.233 +00:04:24.522 --> 00:04:27.233 pour livrer des choses, surtout pour les gens très loin. 85 -00:04:27.233 -> 00:04:30.933 +00:04:27.233 --> 00:04:30.933 Maintenant, il est aussi facile de livrer des choses dans le monde 86 -00:04:30.933 -> 00:04:33.133 +00:04:30.933 --> 00:04:33.133 . Comme il est de livrer des choses à côté 87 -00:04:33.133 -> 00:04:36.852 +00:04:33.133 --> 00:04:36.852 Avant, si on ne place pas les choses dans un endroit fixe 88 -00:04:33.133 -> 00:04:36.852 +00:04:33.133 --> 00:04:36.852 nous aurions du mal à les retrouver. 89 -00:04:39.533 -> 00:04:43.633 +00:04:39.533 --> 00:04:43.633 Maintenant, nous pouvons les placer n'importe où sur le réseau et 90 -00:04:43.633 -> 00:04:46.267 +00:04:43.633 --> 00:04:46.267 les récupérer n'importe où sur le réseau. 91 -00:04:46.267 -> 00:04:50.133 +00:04:46.267 --> 00:04:50.133 Avant, nous avions besoin d'étiqueter les choses clairement et simplement 92 -00:04:50.133 -> 00:04:52.800 +00:04:50.133 --> 00:04:52.800 . Afin que nous puissions les reconnaître et de savoir quoi faire avec eux 93 -00:04:52.800 -> 00:04:56.449 +00:04:52.800 --> 00:04:56.449 Maintenant nous pouvons voir une description de chaque personne ou une chose 94 -00:04:56.449 -> 00:04:59.027 +00:04:56.449 --> 00:04:59.027 ce qui est utile et pertinente à notre but. 95 -00:04:59.027 -> 00:05:03.020 +00:04:59.027 --> 00:05:03.020 Et en passant, nous avons appris que 96 -00:05:03.020 -> 00:05:06.367 +00:05:03.020 --> 00:05:06.367 l'inclusion et l'égalité sont bons pour nous tous. 97 -00:05:06.367 -> 00:05:09.359 +00:05:06.367 --> 00:05:09.359 Nous sommes tous sains, plus riches et plus sage 98 -00:05:09.359 -> 00:05:12.200 +00:05:09.359 --> 00:05:12.200 quand notre société est inclusive et égalitaire. 99 -00:05:12.200 -> 00:05:15.367 +00:05:12.200 --> 00:05:15.367 Nous avons également découvert que les divers groupes 100 -00:05:15.367 -> 00:05:18.936 +00:05:15.367 --> 00:05:18.936 sont plus innovantes et créatives, et mieux à la planification et la prévision. 101 -00:05:18.936 -> 00:05:23.733 +00:05:18.936 --> 00:05:23.733 Nous avons expérimenté avec la nouvelle organisation comme 102 -00:05:23.733 -> 00:05:26.333 +00:05:23.733 --> 00:05:26.333 le plus populaire, pour être ignoré 103 -00:05:26.333 -> 00:05:28.700 +00:05:26.333 --> 00:05:28.700 ami, pas un ami. 104 -00:05:28.700 -> 00:05:31.033 +00:05:28.700 --> 00:05:31.033 Mais nous pouvons faire mieux. 105 -00:05:31.033 -> 00:05:33.267 +00:05:31.033 --> 00:05:33.267 Nous pouvons nous permettre d'être généreux dans notre conception 106 -00:05:33.267 -> 00:05:35.400 +00:05:33.267 --> 00:05:35.400 nous avons moins d'excuses à exclure. 107 -00:05:35.400 -> 00:05:37.567 +00:05:35.400 --> 00:05:37.567 Nous pouvons être fidèles à notre diversité. 108 -00:05:37.567 -> 00:05:43.067 +00:05:37.567 --> 00:05:43.067 Peut-être maintenant, nous pouvons trouver un moyen de faire de la place pour nous tous. \ No newline at end of file diff --git a/js/ErrorPanel.js b/js/ErrorPanel.js index ba2f21a..214bc3f 100644 --- a/js/ErrorPanel.js +++ b/js/ErrorPanel.js @@ -31,7 +31,6 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt retryButton: ".flc-errorPanel-retryButton", retryButtonText: ".flc-errorPanel-retryButton-text" }, - retryCallback: null, templates: { panel: { href: "errorPanel_template.html" @@ -46,6 +45,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt hidden: "fl-hidden" }, events: { + onRetry: null, onReady: null } }); @@ -83,7 +83,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt that.locate("retryButton").click(function (ev) { ev.preventDefault(); - fluid.invokeGlobalFunction(that.options.retryCallback, fluid.makeArray(that.options.retryArgs)); + that.events.onRetry.fire(); }); that.events.onReady.fire(that); diff --git a/js/VideoPlayer_media.js b/js/VideoPlayer_media.js index baa1402..08637ab 100644 --- a/js/VideoPlayer_media.js +++ b/js/VideoPlayer_media.js @@ -52,8 +52,9 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt templates: { panel: "{videoPlayer}.options.templates.videoError" }, - retryCallback: "fluid.videoPlayer.media.reloadSources", - retryArgs: "{media}" + events: { + onRetry: "{media}.events.onRetry" + } } } }, @@ -62,7 +63,8 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt events: { onLoadedMetadata: null, onMediaReady: null, - onMediaLoadError: null + onMediaLoadError: null, + onRetry: null }, sourceRenderers: { "video/mp4": "fluid.videoPlayer.media.createSourceMarkup.html5SourceTag", @@ -276,6 +278,12 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt fluid.videoPlayer.media.renderSources(that); bindMediaModel(that); bindMediaDOMEvents(that); + + that.events.onRetry.addListener(function () { + $("source", that.container).detach(); + fluid.videoPlayer.media.renderSources(that); + that.container.show().load(); + }); }; fluid.demands("videoError", ["fluid.videoPlayer", "fluid.videoPlayer.intervalEventsConductor"], { diff --git a/tests/js/ErrorPanelTests.js b/tests/js/ErrorPanelTests.js index ac2efb6..13bdd37 100644 --- a/tests/js/ErrorPanelTests.js +++ b/tests/js/ErrorPanelTests.js @@ -78,8 +78,8 @@ fluid.registerNamespace("fluid.tests"); errorPanelTests.asyncTest("Interactions", function () { jqUnit.expect(3); var panel = fluid.errorPanel(".panel0", { - retryCallback: "fluid.tests.retryCallback", listeners: { + onRetry: fluid.tests.retryCallback, onReady: function (that) { panel.show(); jqUnit.isVisible("After show, error panel should be visible", ".panel0"); @@ -103,8 +103,8 @@ fluid.registerNamespace("fluid.tests"); href: "errorPanel_template_noDismiss.html" } }, - retryCallback: "fluid.tests.retryCallback", listeners: { + onRetry: fluid.tests.retryCallback, onReady: function (that) { panel.show(); jqUnit.isVisible("After show, error panel should be visible", ".panel0"); diff --git a/tests/js/VideoPlayerErrorsTests.js b/tests/js/VideoPlayerErrorsTests.js index c9ed5d2..e55dde1 100644 --- a/tests/js/VideoPlayerErrorsTests.js +++ b/tests/js/VideoPlayerErrorsTests.js @@ -75,7 +75,9 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt components: { videoError: { options: { - retryCallback: "fluid.tests.testRetryCallback" + listeners: { + onRetry: "fluid.tests.testRetryCallback" + } } } }