// refsCallback: (optional) // Will be called when the player enters a marker that has a `data-ref` attribute. The value of `data-ref` will be passed to the function. // When leaving a marker that a `data-ref` attribute, and entering a marker without one (or not entering a new marker at all), the function will be called with `null`. var vod_platform = { DIRECT: 0, VIMEO: 1, YOUTUBE: 2, }; function GetVODPlatformFromString(VODPlatformString) { var Result = null; switch(VODPlatformString) { case "direct": Result = vod_platform.DIRECT; break; case "vimeo": Result = vod_platform.VIMEO; break; case "youtube": Result = vod_platform.YOUTUBE; break; default: break; } return Result; } function Player(htmlContainer, refsCallback) { this.container = htmlContainer; this.markersContainer = this.container.querySelector(".markers_container"); this.videoContainer = this.container.querySelector(".video_container"); this.refsCallback = refsCallback || function() {}; if (!(this.videoContainer.getAttribute("data-platform") || this.videoContainer.getAttribute("data-videoId"))) { console.error("Expected to find data-platform and data-videoId attribute on", this.videoContainer, "for player initialized on", this.container); throw new Error("Missing data-platform or data-videoId attribute."); } this.markers = []; var markerEls = this.markersContainer.querySelectorAll(".marker"); if (markerEls.length == 0) { console.error("No markers found in", this.markersContainer, "for player initialized on", this.container); throw new Error("Missing markers."); } for (var i = 0; i < markerEls.length; ++i) { var marker = { timestamp: parseInt(markerEls[i].getAttribute("data-timestamp"), 10), ref: markerEls[i].getAttribute("data-ref"), endTime: (i < markerEls.length - 1 ? parseInt(markerEls[i+1].getAttribute("data-timestamp"), 10) : null), el: markerEls[i], fadedProgress: markerEls[i].querySelector(".progress.faded"), progress: markerEls[i].querySelector(".progress.main"), hoverx: null }; marker.el.addEventListener("click", this.onMarkerClick.bind(this, marker)); marker.el.addEventListener("mousemove", this.onMarkerMouseMove.bind(this, marker)); marker.el.addEventListener("mouseleave", this.onMarkerMouseLeave.bind(this, marker)); this.markers.push(marker); } this.vod_platform = GetVODPlatformFromString(this.videoContainer.getAttribute("data-platform")); this.currentMarker = null; this.currentMarkerIdx = null; this.platformPlayer = null; this.platformPlayerReady = false; this.duration = null; this.playing = false; this.shouldPlay = false; this.buffering = false; this.pauseAfterBuffer = false; this.speed = 1; this.currentTime = -1; this.scrollTo = -1; this.scrollPosition = 0; this.nextFrame = null; this.looping = false; this.markersContainer.addEventListener("wheel", function(ev) { this.scrollTo = -1; }.bind(this)); Player.initializePlatform(this.vod_platform, this.onPlatformReady.bind(this)); var PendingMobileStyleInitialisation = true; this.updateSize(PendingMobileStyleInitialisation); this.resume(); } // Start playing the video from the current position. // If the player hasn't loaded yet, it will autoplay when ready. Player.prototype.play = function() { if (this.platformPlayerReady) { if (!this.playing) { switch(this.vod_platform) { case vod_platform.DIRECT: { this.platformPlayer.play(); } break; case vod_platform.VIMEO: { this.platformPlayer.play(); } break; case vod_platform.YOUTUBE: { this.platformPlayer.playVideo(); } break; } this.pauseAfterBuffer = false; } else { this.shouldPlay = true; } } }; // Pause the video at the current position. // If the player hasn't loaded yet, it will not autoplay when ready. (This is the default) Player.prototype.pause = function() { if (this.platformPlayerReady) { if (this.playing) { switch(this.vod_platform) { case vod_platform.DIRECT: { this.platformPlayer.pause(); } break; case vod_platform.VIMEO: { this.platformPlayer.pause(); } break; case vod_platform.YOUTUBE: { this.platformPlayer.pauseVideo(); } break; } } else if (this.buffering) { this.pauseAfterBuffer = true; } } else { this.shouldPlay = false; } }; // Sets the current time then plays. // If the player hasn't loaded yet, it will seek to this time when ready. Player.prototype.setTimeThenPlay = function(time) { this.currentTime = time; switch(this.vod_platform) { case vod_platform.DIRECT: { if (this.platformPlayerReady) { this.currentTime = Math.max(0, Math.min(this.currentTime, this.duration)); this.platformPlayer.currentTime = this.currentTime; this.updateProgress(); this.play(); } } break; case vod_platform.VIMEO: { if (this.platformPlayerReady) { this.currentTime = Math.max(0, Math.min(this.currentTime, this.duration)); var Parent = this; this.platformPlayer.setCurrentTime(this.currentTime) .then(function() { Parent.updateProgress(); Parent.play(); }); } } break; case vod_platform.YOUTUBE: { if (this.platformPlayerReady) { this.currentTime = Math.max(0, Math.min(this.currentTime, this.duration)); this.platformPlayer.seekTo(this.currentTime); this.updateProgress(); this.play(); } } break; } }; Player.prototype.jumpToNextMarker = function() { var targetMarkerIdx = Math.min((this.currentMarkerIdx === null ? 0 : this.currentMarkerIdx + 1), this.markers.length-1); var targetTime = this.markers[targetMarkerIdx].timestamp; while(targetMarkerIdx < this.markers.length && this.markers[targetMarkerIdx].el.classList.contains("skip")) { ++targetMarkerIdx; targetTime = this.markers[targetMarkerIdx].timestamp; } this.setTimeThenPlay(targetTime); }; Player.prototype.jumpToPrevMarker = function() { var targetMarkerIdx = Math.max(0, (this.currentMarkerIdx === null ? 0 : this.currentMarkerIdx - 1)); var targetTime = this.markers[targetMarkerIdx].timestamp; while(targetMarkerIdx >= 0 && this.markers[targetMarkerIdx].el.classList.contains("skip")) { --targetMarkerIdx; targetTime = this.markers[targetMarkerIdx].timestamp; } this.setTimeThenPlay(targetTime); }; function GetHeightOfHideableElement(Element, UnhidingClass) { var Result = 0; if(Element.classList.contains(UnhidingClass)) { Result = Element.offsetHeight; } else { var ZOffset = Element.style.zOffset; Element.style.zOffset = -1; Element.classList.add(UnhidingClass); Result = Element.offsetHeight; Element.classList.remove(UnhidingClass); Element.style.zOffset = ZOffset; } return Result; } function GetWidthOfHideableElement(Element, UnhidingClass) { var Result = 0; if(Element.classList.contains(UnhidingClass)) { Result = Element.offsetWidth; } else { var ZOffset = Element.style.zOffset; Element.style.zOffset = -1; Element.classList.add(UnhidingClass); Result = Element.offsetWidth; Element.classList.remove(UnhidingClass); Element.style.zOffset = ZOffset; } return Result; } function ComputeVerticalOffsetForMenu(Menu, Toggler, VideoContainerDimY) { var Result = 0; var MenuHeight = GetHeightOfHideableElement(Menu, "visible"); var TogglerHeight = Toggler.offsetHeight; var TogglerOffset = Toggler.offsetTop; var Result = TogglerOffset + (TogglerHeight / 2) - (MenuHeight / 2); var LowerProtrusion = MenuHeight + Result - VideoContainerDimY; if(LowerProtrusion > 0) { Result -= LowerProtrusion; } if(Result < 0) { Result = 0; } return Result; } function sizeAndPositionMenuContainer(TitleBar, SizerElement, Menu) { if(Menu) { var Toggler = Menu.parentElement; var TitleBarDimX = TitleBar.offsetWidth; var TitleBarDimY = TitleBar.offsetHeight; var SizerElementDimX = SizerElement.offsetWidth; var SizerElementDimY = SizerElement.offsetHeight; var ContainerDimX = SizerElementDimX; var ContainerDimY = SizerElementDimY; switch(CineraProps.O) { case orientations.PORTRAIT: { Menu.style.borderTopWidth = 0; Menu.style.borderTopStyle = "none"; Menu.style.borderRightWidth = "1px"; Menu.style.borderRightStyle = "solid"; Menu.style.borderLeftWidth = "1px"; Menu.style.borderLeftStyle = "solid"; Menu.style.maxWidth = ContainerDimX + "px"; ContainerDimY -= TitleBarDimY; Menu.style.maxHeight = ContainerDimY + "px"; Menu.style.top = TitleBarDimY + "px"; Menu.style.left = 0 + "px"; } break; case orientations.LANDSCAPE_LEFT: { Menu.style.borderTopWidth = "1px"; Menu.style.borderTopStyle = "solid"; Menu.style.borderRightWidth = "1px"; Menu.style.borderRightStyle = "solid"; Menu.style.borderLeftWidth = 0; Menu.style.borderLeftStyle = "none"; ContainerDimX -= TitleBarDimX; Menu.style.maxWidth = ContainerDimX + "px"; Menu.style.maxHeight = ContainerDimY + "px"; var MenuVerticalOffset = ComputeVerticalOffsetForMenu(Menu, Toggler, SizerElementDimY); Menu.style.top = MenuVerticalOffset + "px"; Menu.style.left = TitleBarDimX + "px"; } break; case orientations.LANDSCAPE_RIGHT: { Menu.style.borderTopWidth = "1px"; Menu.style.borderTopStyle = "solid"; Menu.style.borderRightWidth = 0; Menu.style.borderRightStyle = "none"; Menu.style.borderLeftWidth = "1px"; Menu.style.borderLeftStyle = "solid"; ContainerDimX -= TitleBarDimX; Menu.style.maxWidth = ContainerDimX + "px"; Menu.style.maxHeight = ContainerDimY + "px"; var MenuVerticalOffset = ComputeVerticalOffsetForMenu(Menu, Toggler, SizerElementDimY); Menu.style.top = MenuVerticalOffset + "px"; var MenuWidth = GetWidthOfHideableElement(Menu, "visible"); Menu.style.left = -MenuWidth + "px"; } break; } } } function ComputeTallest(Elements, UnhidingClass) { var Result = null; for(var i = 0; i < Elements.length; ++i) { var This = Elements[i]; var Height = UnhidingClass ? GetHeightOfHideableElement(This, UnhidingClass) : This.offsetHeight; if(Height > Result) { Result = Height; } } return Result; } function ComputeAndSetTallest(Selector, Elements, UnhidingClass) { var Result; Selector.style.height = "unset"; Result = ComputeTallest(Elements, UnhidingClass); Selector.style.height = Result + "px"; return Result; } function ApplyMobileStyle(VideoContainer) { var WindowDim = DeriveReliableWindowDimensions(); var MaxWidth = MaxWidthOfElement(cinera, WindowDim); var MaxHeight = MaxHeightOfElement(cinera, WindowDim); var IndicesBar = playerContainer.querySelector(".markers_container"); var Markers = IndicesBar.querySelector(".markers"); var CineraContentWidth = MaxWidth; var EpisodeMarkers = IndicesBar.querySelectorAll(".episodeMarker"); for(var i = 0; i < EpisodeMarkers.length; ++i) { CineraContentWidth -= EpisodeMarkers[i].offsetWidth; } switch(CineraProps.O) { case orientations.PORTRAIT: { cinera.style.flexDirection = "column"; titleBar.style.flexDirection = "row"; } break; case orientations.LANDSCAPE_LEFT: { cinera.style.flexDirection = "row"; titleBar.style.flexDirection = "column-reverse"; CineraContentWidth -= titleBar.offsetWidth; } break; case orientations.LANDSCAPE_RIGHT: { cinera.style.flexDirection = "row-reverse"; titleBar.style.flexDirection = "column"; CineraContentWidth -= titleBar.offsetWidth; } break; } var HeightOfTallestIndex; if(MobileCineraContentRule !== undefined) { MobileCineraContentRule.style.width = CineraContentWidth + "px"; var MarkerList = Markers.querySelectorAll(".marker"); HeightOfTallestIndex = ComputeAndSetTallest(MobileCineraContentRule, MarkerList, "current"); IndicesBar.style.height = HeightOfTallestIndex + "px"; Markers.style.width = CineraContentWidth + "px"; } var VideoMaxDimX = MaxWidth; var VideoMaxDimY = MaxHeight; var MinimumVideoHeight = 32; if(MaxHeight - HeightOfTallestIndex > MinimumVideoHeight) { VideoMaxDimY -= HeightOfTallestIndex; } switch(CineraProps.O) { case orientations.PORTRAIT: { VideoMaxDimY -= titleBar.offsetHeight; } break; case orientations.LANDSCAPE_LEFT: case orientations.LANDSCAPE_RIGHT: { VideoMaxDimX -= titleBar.offsetWidth; } break; } var VideoDimYFromMaxX = VideoMaxDimX * 9 / 16; var VideoDimXFromMaxY = VideoMaxDimY * 16 / 9; var VideoDimX = 0; var VideoDimY = 0; if(VideoDimXFromMaxY > VideoMaxDimX) { VideoDimX = Math.floor(VideoMaxDimX); VideoDimY = Math.floor(VideoDimYFromMaxX); } else if(VideoDimYFromMaxX > VideoMaxDimY) { VideoDimY = Math.floor(VideoMaxDimY); VideoDimX = Math.floor(VideoDimXFromMaxY); } else { VideoDimX = Math.floor(VideoMaxDimX); VideoDimY = Math.floor(VideoDimYFromMaxX); } VideoContainer.style.width = VideoDimX + "px"; VideoContainer.style.height = VideoDimY + "px"; sizeAndPositionMenuContainer(titleBar, cinera, quotesMenu); sizeAndPositionMenuContainer(titleBar, cinera, referencesMenu); sizeAndPositionMenuContainer(titleBar, cinera, filterMenu); sizeAndPositionMenuContainer(titleBar, cinera, linkMenu); sizeAndPositionMenuContainer(titleBar, cinera, creditsMenu); } function IconifyMenuTogglers() { if(quotesMenu) { quotesMenu.previousElementSibling.textContent = '\u{1F5E9}'; } if(referencesMenu) { referencesMenu.previousElementSibling.textContent = '\u{1F4D6}'; } if(creditsMenu) { creditsMenu.previousElementSibling.textContent = '\u{1F46A}'; } if(viewsMenu) { viewsMenu.remove(); viewsMenu = null; } } function InitMobileControls() { var rightmost = {}; var markersContainer = cinera.querySelector(".markers_container"); markersContainer.style.height = "auto"; var episodeMarkerFirst = markersContainer.querySelector(".episodeMarker.first"); var episodeMarkerPrev = markersContainer.querySelector(".episodeMarker.prev"); var episodeMarkerNext = markersContainer.querySelector(".episodeMarker.next"); var episodeMarkerLast = markersContainer.querySelector(".episodeMarker.last"); if(episodeMarkerPrev) { episodeMarkerPrev.firstChild.textContent = '\u{23EE}'; } if(episodeMarkerNext) { episodeMarkerNext.firstChild.textContent = '\u{23ED}'; rightmost = episodeMarkerNext; } else if (episodeMarkerLast) { rightmost = episodeMarkerLast; } var controlPrevTimestamp = document.createElement("a"); controlPrevTimestamp.classList.add("episodeMarker"); controlPrevTimestamp.classList.add("prevTimestamp"); var controlPrevTimestampContent = document.createElement("div"); controlPrevTimestampContent.appendChild(document.createTextNode('\u{25C0}')); controlPrevTimestamp.appendChild(controlPrevTimestampContent); var markers = markersContainer.querySelector(".markers"); markersContainer.insertBefore(controlPrevTimestamp, markers); var controlNextTimestamp = document.createElement("a"); controlNextTimestamp.classList.add("episodeMarker"); controlNextTimestamp.classList.add("nextTimestamp"); var controlNextTimestampContent = document.createElement("div"); controlNextTimestampContent.appendChild(document.createTextNode('\u{25B6}')); controlNextTimestamp.appendChild(controlNextTimestampContent); if(rightmost) { markersContainer.insertBefore(controlNextTimestamp, rightmost); } else { markersContainer.appendChild(controlNextTimestamp); } } function InitMobileStyle() { cinera.classList.add("mobile"); IconifyMenuTogglers(); InitMobileControls(); var VideoContainer = cinera.querySelector(".video_container"); ApplyMobileStyle(VideoContainer); } function ConnectMobileControls(player) { var markersContainer = player.markersContainer; var ControlPrevTimestamp = markersContainer.querySelector(".episodeMarker.prevTimestamp"); ControlPrevTimestamp.addEventListener("click", function(ev) { player.jumpToPrevMarker(); }); var ControlNextTimestamp = markersContainer.querySelector(".episodeMarker.nextTimestamp"); ControlNextTimestamp.addEventListener("click", function(ev) { player.jumpToNextMarker(); }); } // Call this after changing the size of the video container in order to update the platform player. Player.prototype.updateSize = function() { var width = 0; var height = 0; CineraProps.O = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile); if(!CineraProps.IsMobile) { var VisibleArea = MaxDimensionsOfElement(this.container, GetWindowDim(false)); var AvailableHeight = VisibleArea.Y - titleBar.offsetHeight; var VerticalScrollBarWidth = this.markersContainer.offsetWidth - this.markersContainer.clientWidth; width = VisibleArea.X - (this.markersContainer.scrollWidth + VerticalScrollBarWidth); height = width / 16 * 9; // TODO(matt): Get the aspect ratio from the video itself? if(height > AvailableHeight) { height = AvailableHeight; width = height / 9 * 16; } this.markersContainer.style.height = height + "px"; var VacantPixelsBelowMenus = 4; var MenuMaxHeight = cinera.offsetHeight - titleBar.offsetHeight - VacantPixelsBelowMenus; MenuContainerRule.style.maxHeight = MenuMaxHeight + "px"; } else { ApplyMobileStyle(this.videoContainer); width = this.videoContainer.offsetWidth; height = this.videoContainer.offsetHeight; } if(this.platformPlayerReady) { switch(this.vod_platform) { case vod_platform.DIRECT: { this.platformPlayer.setAttribute("width", Math.floor(width)); this.platformPlayer.setAttribute("height", Math.floor(height)); } break; case vod_platform.VIMEO: break; // NOTE(matt): It responds automatically case vod_platform.YOUTUBE: { this.platformPlayer.setSize(Math.floor(width), Math.floor(height)); } break; default: break; } } } // Stops the per-frame work that the player does. Call when you want to hide or get rid of the player. Player.prototype.halt = function() { this.pause(); this.looping = false; if (this.nextFrame) { cancelAnimationFrame(this.nextFrame); this.nextFrame = null; } } // Resumes the per-frame work that the player does. Call when you want to show the player again after hiding. Player.prototype.resume = function() { this.looping = true; if (!this.nextFrame) { this.doFrame(); } } Player.initializePlatform = function(platform_id, callback) { switch(platform_id) { case vod_platform.DIRECT: case vod_platform.VIMEO: { callback(); } break; case vod_platform.YOUTUBE: { if(window.YT && window.YT.loaded) { callback() } else { if (window.APYoutubeAPIReady === undefined) { window.APYoutubeAPIReady = false; window.APCallbacks = (callback ? [callback] : []); window.onYouTubeIframeAPIReady = function() { window.APYoutubeAPIReady = true; for (var i = 0; i < APCallbacks.length; ++i) { APCallbacks[i](); } }; } else if (window.APYoutubeAPIReady === false) { window.APCallbacks.push(callback); } else if (window.APYoutubeAPIReady === true) { callback(); } } } break; } } // END PUBLIC INTERFACE Player.prototype.onMarkerClick = function(marker, ev) { var time = marker.timestamp; if (this.currentMarker == marker && marker.hoverx !== null) { time += (marker.endTime - marker.timestamp) * marker.hoverx; } this.setTimeThenPlay(time); }; Player.prototype.onMarkerMouseMove = function(marker, ev) { if (this.currentMarker == marker) { var CineraContent = this.currentMarker.el.querySelector(".cineraContent"); marker.hoverx = (ev.pageX - getElementXOffsetFromPage(CineraContent)) / CineraContent.offsetWidth; } }; Player.prototype.onMarkerMouseLeave = function(marker, ev) { marker.hoverx = null; }; Player.prototype.updateProgress = function() { var prevMarker = this.currentMarker; this.currentMarker = null; this.currentMarkerIdx = null; for (var i = 0; i < this.markers.length; ++i) { var marker = this.markers[i]; if (marker.timestamp <= this.currentTime && this.currentTime < marker.endTime) { this.currentMarker = marker; this.currentMarkerIdx = i; break; } } if (this.currentMarker) { var CineraContent = this.currentMarker.el.querySelector(".cineraContent"); var totalWidth = CineraContent.offsetWidth; var progress = (this.currentTime - this.currentMarker.timestamp) / (this.currentMarker.endTime - this.currentMarker.timestamp); if (this.currentMarker.hoverx === null) { var pixelWidth = progress * totalWidth; this.currentMarker.fadedProgress.style.width = Math.ceil(pixelWidth) + "px"; this.currentMarker.fadedProgress.style.opacity = pixelWidth - Math.floor(pixelWidth); this.currentMarker.progress.style.width = Math.floor(pixelWidth) + "px"; } else { this.currentMarker.fadedProgress.style.opacity = 1; this.currentMarker.progress.style.width = Math.floor(Math.min(this.currentMarker.hoverx, progress) * totalWidth) + "px"; this.currentMarker.fadedProgress.style.width = Math.floor(Math.max(this.currentMarker.hoverx, progress) * totalWidth) + "px"; } } if (this.currentMarker != prevMarker) { if (prevMarker) { prevMarker.el.classList.remove("current"); prevMarker.fadedProgress.style.width = "0px"; prevMarker.progress.style.width = "0px"; prevMarker.hoverx = null; } if (this.currentMarker) { if(this.currentMarkerIdx == this.markers.length - 1) { localStorage.removeItem(lastTimestampStorageItem); } else { localStorage.setItem(lastTimestampStorageItem, this.currentMarker.timestamp); } this.currentMarker.el.classList.add("current"); this.scrollTo = this.currentMarker.el.offsetTop + this.currentMarker.el.offsetHeight/2.0; this.scrollPosition = this.markersContainer.scrollTop; this.refsCallback(this.currentMarker.ref, this.currentMarker.el, this); } else if (prevMarker && prevMarker.ref) { this.refsCallback(null); } } }; Player.prototype.doFrame = function() { if (this.playing) { switch(this.vod_platform) { case vod_platform.DIRECT: { this.currentTime = this.platformPlayer.currentTime; this.updateProgress(); } break; case vod_platform.VIMEO: { var Parent = this; this.platformPlayer.getCurrentTime() .then(function(Result) { Parent.currentTime = Result; Parent.updateProgress(); }); } break; case vod_platform.YOUTUBE: { this.currentTime = this.platformPlayer.getCurrentTime(); this.updateProgress(); } break; } } if (this.scrollTo >= 0) { var targetPosition = this.scrollTo - this.markersContainer.offsetHeight/2.0; targetPosition = Math.max(0, Math.min(targetPosition, this.markersContainer.scrollHeight - this.markersContainer.offsetHeight)); this.scrollPosition += (targetPosition - this.scrollPosition) * 0.1; if (Math.abs(this.scrollPosition - targetPosition) < 1.0) { this.markersContainer.scrollTop = targetPosition; this.scrollTo = -1; } else { this.markersContainer.scrollTop = this.scrollPosition; } } this.nextFrame = requestAnimationFrame(this.doFrame.bind(this)); updateLink(); }; Player.prototype.setStyleAndQuality = function() { switch(this.vod_platform) { case vod_platform.DIRECT: { // NOTE(matt): onPlatformReady() has set the width and height } break; case vod_platform.VIMEO: { this.platformPlayer.setQuality("1080p"); var frame = this.videoContainer.querySelector("iframe"); frame.style.width = "100%"; frame.style.height = "100%"; frame.style.position = "absolute" frame.style.top = 0; frame.style.left = 0; } break; case vod_platform.YOUTUBE: { this.platformPlayer.setPlaybackQuality("hd1080"); } break; } this.updateSize(); } Player.prototype.setDurationThenAutoplay = function(Duration) { this.duration = Duration; this.markers[this.markers.length-1].endTime = this.duration; if (this.currentTime > 0) { this.currentTime = Math.max(0, Math.min(this.currentTime, this.duration)); this.setTimeThenPlay(this.currentTime); } if (this.shouldPlay) { this.play(); } } Player.prototype.acquireDurationThenAutoplay = function() { switch(this.vod_platform) { case vod_platform.DIRECT: { this.setDurationThenAutoplay(this.platformPlayer.duration); } break; case vod_platform.VIMEO: { var Parent = this; this.platformPlayer.getDuration() .then(function(Response) { Parent.setDurationThenAutoplay(Response); }); } break; case vod_platform.YOUTUBE: { this.setDurationThenAutoplay(this.platformPlayer.getDuration()); } break; } } Player.prototype.onPlatformPlayerReady = function() { this.platformPlayerReady = true; this.setStyleAndQuality(); this.acquireDurationThenAutoplay(); }; Player.prototype.onPlaybackRateChange = function(Rate) { this.speed = Rate; } Player.prototype.onDirectPlayerPlaybackRateChange = function(ev) { this.onPlaybackRateChange(this.platformPlayer.playbackRate); }; Player.prototype.onVimeoPlayerPlaybackRateChange = function(ev) { this.onPlaybackRateChange(ev.playbackRate); }; Player.prototype.onYouTubePlayerPlaybackRateChange = function(ev) { this.onPlaybackRateChange(ev.data); }; Player.prototype.onStateBufferStart = function() { this.buffering = true; this.updateProgress(); } Player.prototype.onStateBufferEnd = function() { this.buffering = false; this.updateProgress(); } Player.prototype.onStateEnded = function() { this.buffering = false; this.playing = false; localStorage.removeItem(lastTimestampStorageItem); this.currentTime = null; this.updateProgress(); } Player.prototype.onStatePaused = function(CurrentTime) { this.buffering = false; this.playing = false; this.currentTime = CurrentTime; this.updateProgress(); } Player.prototype.onStatePlaying = function(CurrentTime) { this.buffering = false; this.playing = true; this.currentTime = CurrentTime; if(this.pauseAfterBuffer) { this.pauseAfterBuffer = false; this.pause(); } } Player.prototype.onDirectPlayerStateChange_Paused = function(ev) { this.onStatePaused(this.platformPlayer.currentTime); } Player.prototype.onDirectPlayerStateChange_Playing = function(ev) { this.onStatePlaying(this.platformPlayer.currentTime); } Player.prototype.onVimeoPlayerStateChange_Paused = function(ev) { this.onStatePaused(ev.seconds); } Player.prototype.onVimeoPlayerStateChange_Playing = function(ev) { this.onStatePlaying(ev.seconds); } Player.prototype.onYouTubePlayerStateChange = function(ev) { switch(ev.data) { case YT.PlayerState.BUFFERING: { this.onStateBufferStart(); }; break; case YT.PlayerState.ENDED: { this.onStateEnded(); }; break; case YT.PlayerState.PAUSED: { this.onStatePaused(this.platformPlayer.getCurrentTime()); }; break; case YT.PlayerState.PLAYING: { this.onStatePlaying(this.platformPlayer.getCurrentTime()); }; break; default: break; } }; Player.prototype.onPlatformReady = function() { var platformPlayerDiv = document.createElement("DIV"); platformPlayerDiv.id = "platform_player_" + Player.platformPlayerCount++; var platformPlayerDivPlaced = this.videoContainer.appendChild(platformPlayerDiv); switch(this.vod_platform) { case vod_platform.DIRECT: { platformPlayerDivPlaced.classList.add("direct_video"); var videoNode = document.createElement("VIDEO"); videoNode.controls = true; videoNode.setAttribute("width", this.videoContainer.offsetWidth); videoNode.setAttribute("height", this.videoContainer.offsetWidth / 16 * 9); var videoSourceNode = document.createElement("SOURCE"); videoSourceNode.setAttribute("src", this.videoContainer.getAttribute("data-videoId")); videoNode.appendChild(videoSourceNode); this.platformPlayer = platformPlayerDiv.appendChild(videoNode); this.platformPlayer.addEventListener("loadedmetadata", this.onPlatformPlayerReady.bind(this)); this.platformPlayer.addEventListener("ratechange", this.onDirectPlayerPlaybackRateChange.bind(this)); this.platformPlayer.addEventListener("play", this.onDirectPlayerStateChange_Playing.bind(this)); this.platformPlayer.addEventListener("pause", this.onDirectPlayerStateChange_Paused.bind(this)); this.platformPlayer.addEventListener("waiting", this.onStateBufferStart.bind(this)); this.platformPlayer.addEventListener("playing", this.onStateBufferEnd.bind(this)); this.platformPlayer.addEventListener("ended", this.onStateEnded.bind(this)); } break; case vod_platform.VIMEO: { this.videoContainer.style.position = "relative"; this.videoContainer.style.alignSelf = "unset"; var CallData = { id: this.videoContainer.getAttribute("data-videoId"), title: false, }; var CCLang = this.videoContainer.getAttribute("data-ccLang"); if(CCLang != null) { CallData.texttrack = CCLang; } this.platformPlayer = new Vimeo.Player(platformPlayerDiv.id, CallData); this.platformPlayer.ready() .then(this.onPlatformPlayerReady.bind(this)); this.platformPlayer.on("playbackratechange", this.onVimeoPlayerPlaybackRateChange.bind(this)); this.platformPlayer.on("play", this.onVimeoPlayerStateChange_Playing.bind(this)); this.platformPlayer.on("pause", this.onVimeoPlayerStateChange_Paused.bind(this)); this.platformPlayer.on("bufferstart", this.onStateBufferStart.bind()); this.platformPlayer.on("bufferend", this.onStateBufferEnd.bind()); this.platformPlayer.on("ended", this.onStateEnded.bind()); } break; case vod_platform.YOUTUBE: { var CallData = { videoId: this.videoContainer.getAttribute("data-videoId"), width: this.videoContainer.offsetWidth, height: this.videoContainer.offsetWidth / 16 * 9, playerVars: { 'playsinline': 1 }, events: { "onReady": this.onPlatformPlayerReady.bind(this), "onStateChange": this.onYouTubePlayerStateChange.bind(this), "onPlaybackRateChange": this.onYouTubePlayerPlaybackRateChange.bind(this) } }; var CCLang = this.videoContainer.getAttribute("data-ccLang"); if(CCLang != null) { CallData.cc_lang_pref = CCLang; CallData.cc_load_policy = 1; } this.platformPlayer = new YT.Player(platformPlayerDiv.id, CallData); } break; } }; Player.platformPlayerCount = 0; function toggleFilterMode() { if(filterMode == "inclusive") { filterModeElement.classList.remove("inclusive"); filterModeElement.classList.add("exclusive"); filterMode = "exclusive"; } else { filterModeElement.classList.remove("exclusive"); filterModeElement.classList.add("inclusive"); filterMode = "inclusive"; } applyFilter(); } function updateLink() { if(link && player) { if(linkTimestamp == true) { if(player.currentMarker) { link.value = baseURL + "#" + player.currentMarker.timestamp; } else { link.value = baseURL; } } else { switch(player.vod_platform) { case vod_platform.DIRECT: { link.value = baseURL + "#" + Math.round(player.platformPlayer.currentTime); } break; case vod_platform.VIMEO: { player.platformPlayer.getCurrentTime() .then(function(Response) { link.value = baseURL + "#" + Math.round(Response); }); } break; case vod_platform.YOUTUBE: { link.value = baseURL + "#" + Math.round(player.platformPlayer.getCurrentTime()); } break; } } } } function toggleLinkMode(linkMode, link) { linkTimestamp = !linkTimestamp; if(linkTimestamp == true) { linkMode.textContent = "Link to: current timestamp"; } else { linkMode.textContent = "Link to: nearest second"; } updateLink(); } function toggleFilterOrLinkMode() { for(menuIndex in menuState) { if(menuState[menuIndex].classList.contains("filter_container") && menuState[menuIndex].classList.contains("visible")) { toggleFilterMode(); } if(menuState[menuIndex].classList.contains("link_container") && menuState[menuIndex].classList.contains("visible")) { toggleLinkMode(linkMode, link); } } } function toggleMenuVisibility(element) { if(element.classList.contains("visible")) { element.classList.remove("visible"); element.parentNode.classList.remove("visible"); if(focusedElement) { focusedElement.classList.remove("focused"); focusedElement = null; } if(focusedIdentifier) { focusedIdentifier.classList.remove("focused"); focusedIdentifier = null; } } else { for(menuIndex in menuState) { menuState[menuIndex].classList.remove("visible"); menuState[menuIndex].parentNode.classList.remove("visible"); if(focusedElement) { focusedElement.classList.remove("focused"); } if(focusedIdentifier) { focusedIdentifier.classList.remove("focused"); } } element.classList.add("visible"); element.parentNode.classList.add("visible"); if(element.classList.contains("quotes_container")) { if(!lastFocusedQuote) { lastFocusedQuote = element.querySelectorAll(".ref")[0]; } focusedElement = lastFocusedQuote; focusedElement.classList.add("focused"); } else if(element.classList.contains("references_container")) { if(!lastFocusedReference || !lastFocusedIdentifier) { lastFocusedReference = element.querySelectorAll(".ref")[0]; lastFocusedIdentifier = lastFocusedReference.querySelector(".ref_indices").firstElementChild; } focusedElement = lastFocusedReference; focusedElement.classList.add("focused"); focusedIdentifier = lastFocusedIdentifier; focusedIdentifier.classList.add("focused"); } else if(element.classList.contains("filter_container")) { if(!lastFocusedCategory) { lastFocusedCategory = element.querySelectorAll(".filter_content")[0]; } focusedElement = lastFocusedCategory; focusedElement.classList.add("focused"); } else if(element.classList.contains("credits_container")) { if(!lastFocusedCreditItem) { if(element.querySelectorAll(".credit .person")[0].nextElementSibling) { lastFocusedCreditItem = element.querySelectorAll(".credit .support")[0]; focusedElement = lastFocusedCreditItem; focusedElement.classList.add("focused"); setSpriteLightness(focusedElement.firstChild); } else { lastFocusedCreditItem = element.querySelectorAll(".credit .person")[0]; focusedElement = lastFocusedCreditItem; focusedElement.classList.add("focused"); } } else { focusedElement = lastFocusedCreditItem; focusedElement.classList.add("focused"); } } } } function handleMouseOverViewsMenu() { switch(CineraProps.V) { case views.REGULAR: case views.THEATRE: { viewsContainer.style.display = "block"; } break; case views.SUPERTHEATRE: { viewsContainer.style.display = "none"; } break; } } function IsFullScreen() { return (document.fullscreen || document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement || document.fullscreenElement); } function enterFullScreen_() { if(!IsFullScreen()) { if(document.body.requestFullscreen) { document.body.requestFullscreen(); } else if(document.body.mozRequestFullScreen) { document.body.mozRequestFullScreen(); } else if(document.body.webkitRequestFullScreen) { document.body.webkitRequestFullScreen(); } else if(document.body.msRequestFullscreen) { document.body.msRequestFullscreen(); } } } function leaveFullScreen_() { if(IsFullScreen()) { if(document.exitFullscreen) { document.exitFullscreen(); } else if(document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if(document.webkitCancelFullScreen) { document.webkitCancelFullScreen(); } else if(document.msExitFullscreen) { document.msExitFullscreen(); } } } function toggleTheatreMode() { switch(CineraProps.V) { case views.REGULAR: { CineraProps.C = cinera.style.backgroundColor; CineraProps.Z = cinera.style.zIndex; CineraProps.X = cinera.style.left; CineraProps.Y = cinera.style.top; CineraProps.W = cinera.style.width; CineraProps.mW = cinera.style.maxWidth; CineraProps.H = cinera.style.height; CineraProps.mH = cinera.style.maxHeight; CineraProps.P = cinera.style.position; CineraProps.Display = cinera.style.display; CineraProps.FlexDirection = cinera.style.flexDirection; CineraProps.JustifyContent = cinera.style.justifyContent; CineraProps.ScrollX = window.scrollX; CineraProps.ScrollY = window.scrollY; cinera.style.backgroundColor = "#000"; cinera.style.zIndex = 64; cinera.style.left = 0; cinera.style.top = 0; cinera.style.width = "100%"; cinera.style.maxWidth = "100%"; cinera.style.height = "100%"; cinera.style.maxHeight = "100%"; cinera.style.position = "fixed"; cinera.style.display = "flex"; cinera.style.flexDirection = "column"; cinera.style.justifyContent = "center"; viewItems[0].setAttribute("data-id", "regular"); viewItems[0].setAttribute("title", "Regular mode"); viewItems[0].firstChild.nodeValue = "📺"; } CineraProps.V = views.THEATRE; localStorage.setItem(cineraViewStorageItem, views.THEATRE); break; case views.SUPERTHEATRE: { leaveFullScreen_(); } case views.THEATRE: { cinera.style.backgroundColor = CineraProps.C; cinera.style.zIndex = CineraProps.Z; cinera.style.left = CineraProps.X; cinera.style.top = CineraProps.Y; cinera.style.width = CineraProps.W; cinera.style.maxWidth = CineraProps.mW; cinera.style.height = CineraProps.H; cinera.style.maxHeight = CineraProps.mH; cinera.style.position = CineraProps.P; cinera.style.display = CineraProps.Display; cinera.style.flexDirection = CineraProps.FlexDirection; cinera.style.justifyContent = CineraProps.JustifyContent; window.scroll( { top: CineraProps.ScrollY, left: CineraProps.ScrollX } ); viewItems[0].setAttribute("data-id", "theatre"); viewItems[0].setAttribute("title", "Theatre mode"); viewItems[0].firstChild.nodeValue = "🎭"; } CineraProps.V = views.REGULAR; localStorage.removeItem(cineraViewStorageItem); break; } player.updateSize(); } function toggleSuperTheatreMode() { switch(CineraProps.V) { case views.REGULAR: { toggleTheatreMode(); } case views.THEATRE: { enterFullScreen_(); } CineraProps.V = views.SUPERTHEATRE; localStorage.setItem(cineraViewStorageItem, views.SUPERTHEATRE); break; case views.SUPERTHEATRE: { leaveFullScreen_(); toggleTheatreMode(); } CineraProps.V = views.REGULAR; localStorage.removeItem(cineraViewStorageItem); break; } player.updateSize(); } function AscribeTemporaryResponsibility(Element, Milliseconds) { if(!Element.classList.contains("responsible")) { Element.classList.add("responsible"); } setTimeout(function() { Element.classList.remove("responsible"); }, Milliseconds); } function SelectText(inputElement) { inputElement.select(); } function CopyToClipboard(inputElement) { SelectText(inputElement); document.execCommand("copy"); AscribeTemporaryResponsibility(linkMenu.parentNode, 8000); } function handleKey(key) { var gotKey = true; switch (key) { case "q": { if(quotesMenu) { toggleMenuVisibility(quotesMenu) } } break; case "r": { if(referencesMenu) { toggleMenuVisibility(referencesMenu) } } break; case "f": { if(filterMenu) { toggleMenuVisibility(filterMenu) } } break; case "y": { if(linkMenu) { toggleMenuVisibility(linkMenu) } break; } case "c": { if(creditsMenu) { toggleMenuVisibility(creditsMenu) } } break; case "t": { if(cinera) { toggleTheatreMode(); } } break; case "T": { if(cinera) { toggleSuperTheatreMode(); } } break; case "Enter": { if(focusedElement) { if(focusedElement.parentNode.classList.contains("quotes_container")) { var time = focusedElement.querySelector(".timecode").getAttribute("data-timestamp"); player.setTimeThenPlay(parseInt(time)); } else if(focusedElement.parentNode.classList.contains("references_container")) { var time = focusedIdentifier.getAttribute("data-timestamp"); player.setTimeThenPlay(parseInt(time)); } else if(focusedElement.parentNode.classList.contains("credit")) { if(focusedElement.hasAttribute) { var url = focusedElement.getAttribute("href"); window.open(url, "_blank"); } } } else { console.log("TODO(matt): Implement me, perhaps?\n"); } } break; case "o": { if(focusedElement) { if(focusedElement.parentNode.classList.contains("references_container") || focusedElement.parentNode.classList.contains("quotes_container")) { if(player) { player.pause(); } var url = focusedElement.getAttribute("href"); window.open(url, "_blank"); } else if(focusedElement.parentNode.classList.contains("credit")) { if(focusedElement.hasAttribute("href")) { if(player) { player.pause(); } var url = focusedElement.getAttribute("href"); window.open(url, "_blank"); } } } } break; case "w": case "k": case "ArrowUp": { if(focusedElement) { if(focusedElement.parentNode.classList.contains("quotes_container")) { if(focusedElement.previousElementSibling) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); lastFocusedQuote = focusedElement.previousElementSibling; focusedElement = lastFocusedQuote; focusedElement.classList.add("focused"); focusSprite(focusedElement); } } else if(focusedElement.parentNode.classList.contains("references_container")) { if(focusedElement.previousElementSibling) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); focusedIdentifier.classList.remove("focused"); unfocusSprite(focusedIdentifier); lastFocusedReference = focusedElement.previousElementSibling; focusedElement = lastFocusedReference; focusedElement.classList.add("focused"); focusSprite(focusedElement); lastFocusedIdentifier = focusedElement.querySelector(".ref_indices").firstElementChild; focusedIdentifier = lastFocusedIdentifier; focusedIdentifier.classList.add("focused"); focusSprite(focusedIdentifier); } } else if(focusedElement.parentNode.parentNode.classList.contains("filters")) { if(focusedElement.previousElementSibling && focusedElement.previousElementSibling.classList.contains("filter_content")) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); lastFocusedCategory = focusedElement.previousElementSibling; focusedElement = lastFocusedCategory; focusedElement.classList.add("focused"); focusSprite(focusedElement); } } else if(focusedElement.parentNode.classList.contains("credit")) { if(focusedElement.parentNode.previousElementSibling) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); if(focusedElement.parentNode.previousElementSibling.querySelector(".support") && focusedElement.classList.contains("support")) { setSpriteLightness(focusedElement.firstChild); lastFocusedCreditItem = focusedElement.parentNode.previousElementSibling.querySelector(".support"); focusedElement = lastFocusedCreditItem; focusedElement.classList.add("focused"); focusSprite(focusedElement); } else { lastFocusedCreditItem = focusedElement.parentNode.previousElementSibling.querySelector(".person"); focusedElement = lastFocusedCreditItem; focusedElement.classList.add("focused"); focusSprite(focusedElement); } } } } } break; case "s": case "j": case "ArrowDown": { if(focusedElement) { if(focusedElement.parentNode.classList.contains("quotes_container")) { if(focusedElement.nextElementSibling) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); lastFocusedQuote = focusedElement.nextElementSibling; focusedElement = lastFocusedQuote; focusedElement.classList.add("focused"); focusSprite(focusedElement); } } else if(focusedElement.parentNode.classList.contains("references_container")) { if(focusedElement.nextElementSibling) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); focusedIdentifier.classList.remove("focused"); unfocusSprite(focusedIdentifier); lastFocusedReference = focusedElement.nextElementSibling; focusedElement = lastFocusedReference; focusedElement.classList.add("focused"); focusSprite(focusedElement); lastFocusedIdentifier = focusedElement.querySelector(".ref_indices").firstElementChild; focusedIdentifier = lastFocusedIdentifier; focusedIdentifier.classList.add("focused"); focusSprite(focusedIdentifier); } } else if(focusedElement.parentNode.parentNode.classList.contains("filters")) { if(focusedElement.nextElementSibling && focusedElement.nextElementSibling.classList.contains("filter_content")) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); lastFocusedCategory = focusedElement.nextElementSibling; focusedElement = lastFocusedCategory; focusedElement.classList.add("focused"); focusSprite(focusedElement); } } else if(focusedElement.parentNode.classList.contains("credit")) { if(focusedElement.parentNode.nextElementSibling) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); if(focusedElement.parentNode.nextElementSibling.querySelector(".support") && focusedElement.classList.contains("support")) { setSpriteLightness(focusedElement.firstChild); lastFocusedCreditItem = focusedElement.parentNode.nextElementSibling.querySelector(".support"); focusedElement = lastFocusedCreditItem; focusedElement.classList.add("focused"); focusSprite(focusedElement); } else { lastFocusedCreditItem = focusedElement.parentNode.nextElementSibling.querySelector(".person"); focusedElement = lastFocusedCreditItem; focusedElement.classList.add("focused"); focusSprite(focusedElement); } } } } } break; case "a": case "h": case "ArrowLeft": { if(focusedElement) { if(focusedElement.parentNode.classList.contains("references_container")) { if(focusedIdentifier.previousElementSibling) { focusedIdentifier.classList.remove("focused"); unfocusSprite(focusedIdentifier); lastFocusedIdentifier = focusedIdentifier.previousElementSibling; focusedIdentifier = lastFocusedIdentifier; focusedIdentifier.classList.add("focused"); focusSprite(focusedIdentifier); } else if(focusedIdentifier.parentNode.previousElementSibling.classList.contains("ref_indices")) { focusedIdentifier.classList.remove("focused"); unfocusSprite(focusedIdentifier); lastFocusedIdentifier = focusedIdentifier.parentNode.previousElementSibling.lastElementChild; focusedIdentifier = lastFocusedIdentifier; focusedIdentifier.classList.add("focused"); focusSprite(focusedIdentifier); } } else if(focusedElement.classList.contains("filter_content")) { if(focusedElement.parentNode.classList.contains("filter_media") && focusedElement.parentNode.previousElementSibling) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); lastFocusedMedium = focusedElement; if(!lastFocusedTopic) { lastFocusedTopic = focusedElement.parentNode.previousElementSibling.children[1]; } lastFocusedCategory = lastFocusedTopic; focusedElement = lastFocusedCategory; focusedElement.classList.add("focused"); focusSprite(focusedElement); } } else if(focusedElement.parentNode.classList.contains("credit")) { if(focusedElement.classList.contains("support")) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); lastFocusedCreditItem = focusedElement.previousElementSibling; setSpriteLightness(focusedElement.firstChild); focusedElement = lastFocusedCreditItem; focusedElement.classList.add("focused"); focusSprite(focusedElement); } } } } break; case "d": case "l": case "ArrowRight": { if(focusedElement) { if(focusedElement.parentNode.classList.contains("references_container")) { if(focusedIdentifier.nextElementSibling) { focusedIdentifier.classList.remove("focused"); unfocusSprite(focusedIdentifier); lastFocusedIdentifier = focusedIdentifier.nextElementSibling; focusedIdentifier = lastFocusedIdentifier; focusedIdentifier.classList.add("focused"); focusSprite(focusedIdentifier); } else if(focusedIdentifier.parentNode.nextElementSibling) { focusedIdentifier.classList.remove("focused"); unfocusSprite(focusedIdentifier); lastFocusedIdentifier = focusedIdentifier.parentNode.nextElementSibling.firstElementChild; focusedIdentifier = lastFocusedIdentifier; focusedIdentifier.classList.add("focused"); focusSprite(focusedIdentifier); } } else if(focusedElement.classList.contains("filter_content")) { if(focusedElement.parentNode.classList.contains("filter_topics") && focusedElement.parentNode.nextElementSibling) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); lastFocusedTopic = focusedElement; if(!lastFocusedMedium) { lastFocusedMedium = focusedElement.parentNode.nextElementSibling.children[1]; } lastFocusedCategory = lastFocusedMedium; focusedElement = lastFocusedCategory; focusedElement.classList.add("focused"); focusSprite(focusedElement); } } else if(focusedElement.parentNode.classList.contains("credit")) { if(focusedElement.classList.contains("person") && focusedElement.nextElementSibling) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); lastFocusedCreditItem = focusedElement.nextElementSibling; focusedElement = lastFocusedCreditItem; focusedElement.classList.add("focused"); focusSprite(focusedElement); } } } } break; case "x": case " ": { if(focusedElement && focusedElement.classList.contains("filter_content")) { filterItemToggle(focusedElement); if(focusedElement.nextElementSibling && focusedElement.nextElementSibling.classList.contains("filter_content")) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); if(focusedElement.parentNode.classList.contains("filter_topics")) { lastFocusedTopic = focusedElement.nextElementSibling; lastFocusedCategory = lastFocusedTopic; } else { lastFocusedMedium = focusedElement.nextElementSibling; lastFocusedCategory = lastFocusedMedium; } lastFocusedElement = lastFocusedCategory; focusedElement = lastFocusedElement; focusedElement.classList.add("focused"); focusSprite(focusedElement); } } } break; case "X": case "capitalSpace": { if(focusedElement && focusedElement.classList.contains("filter_content")) { filterItemToggle(focusedElement); if(focusedElement.previousElementSibling && focusedElement.previousElementSibling.classList.contains("filter_content")) { focusedElement.classList.remove("focused"); unfocusSprite(focusedElement); if(focusedElement.parentNode.classList.contains("filter_topics")) { lastFocusedTopic = focusedElement.previousElementSibling; lastFocusedCategory = lastFocusedTopic; } else { lastFocusedMedium = focusedElement.previousElementSibling; lastFocusedCategory = lastFocusedMedium; } lastFocusedElement = lastFocusedCategory; focusedElement = lastFocusedElement; focusedElement.classList.add("focused"); focusSprite(focusedElement); } } } break; case "z": { toggleFilterOrLinkMode(); } break; case "v": { if(focusedElement && focusedElement.classList.contains("filter_content")) { invertFilter(focusedElement) } } break; case "V": { resetFilter(); } break; case "?": { if(helpDocumentation) { helpDocumentation.classList.toggle("visible"); } } break; case 'N': case 'J': case 'S': { player.jumpToNextMarker(); } break; case 'P': case 'K': case 'W': { player.jumpToPrevMarker(); } break; case '[': case '<': { if(prevEpisode) { location = prevEpisode.href; } } break; case ']': case '>': { if(nextEpisode) { location = nextEpisode.href; } } break; case 'Y': { if(cineraLink) { if(linkTimestamp == false && player.playing) { player.pause(); } if(linkMenu && !linkMenu.classList.contains("visible")) { toggleMenuVisibility(linkMenu); } SelectText(cineraLink); } } default: { gotKey = false; } break; } return gotKey; } function applyFilter() { if(filterMode == "exclusive") { for(var i = 0; i < testMarkers.length; ++i) { var testCategories = testMarkers[i].classList; for(var j = 0; j < testCategories.length; ++j) { if((testCategories[j].startsWith("off_")) && !testMarkers[i].classList.contains("skip")) { testMarkers[i].classList.add("skip"); } } } } else { for(var i = 0; i < testMarkers.length; ++i) { var testCategories = testMarkers[i].classList; for(var j = 0; j < testCategories.length; ++j) { if((testCategories[j] in filterState || testCategories[j].startsWith("cat_")) && testMarkers[i].classList.contains("skip")) { testMarkers[i].classList.remove("skip"); } } } } } function filterItemToggle(filterItem) { var selectedCategory = filterItem.classList[1]; filterState[selectedCategory].off = !filterState[selectedCategory].off; if(filterState[selectedCategory].off) { filterItem.classList.add("off"); disableSprite(filterItem); if(!filterItem.parentNode.classList.contains("filter_media")) { filterItem.querySelector(".icon").style.backgroundColor = "transparent"; } var testMarkers = playerContainer.querySelectorAll(".marker." + selectedCategory + ", .marker.cat_" + selectedCategory); for(var j = 0; j < testMarkers.length; ++j) { if(filterState[selectedCategory].type == "topic") { testMarkers[j].classList.remove("cat_" + selectedCategory); testMarkers[j].classList.add("off_" + selectedCategory); var markerCategories = testMarkers[j].querySelectorAll(".category." + selectedCategory); for(var k = 0; k < markerCategories.length; ++k) { if(markerCategories[k].classList.contains(selectedCategory)) { markerCategories[k].classList.add("off"); markerCategories[k].style.backgroundColor = "transparent"; } } } else { var markerCategories = testMarkers[j].querySelectorAll(".categoryMedium." + selectedCategory); for(var k = 0; k < markerCategories.length; ++k) { if(markerCategories[k].classList.contains(selectedCategory)) { markerCategories[k].classList.add("off"); disableSprite(markerCategories[k]); } } testMarkers[j].classList.remove(selectedCategory); testMarkers[j].classList.add("off_" + selectedCategory); } Skipping = 1; if(filterMode == "exclusive") { testMarkers[j].classList.add("skip"); } else { var markerClasses = testMarkers[j].classList; for(var k = 0; k < markerClasses.length; ++k) { if(markerClasses[k] in filterState || markerClasses[k].replace(/^cat_/, "") in filterState) { Skipping = 0; } } if(Skipping) { testMarkers[j].classList.add("skip"); } } } } else { filterItem.classList.remove("off"); enableSprite(filterItem); if(!filterItem.parentNode.classList.contains("filter_media")) { filterItem.querySelector(".icon").style.backgroundColor = getComputedStyle(filterItem.querySelector(".icon")).getPropertyValue("border-color"); } setDotLightness(filterItem.querySelector(".icon")); var testMarkers = cinera.querySelectorAll(".marker.off_" + selectedCategory); for(var j = 0; j < testMarkers.length; ++j) { if(filterState[selectedCategory].type == "topic") { testMarkers[j].classList.remove("off_" + selectedCategory); testMarkers[j].classList.add("cat_" + selectedCategory); var markerCategories = testMarkers[j].querySelectorAll(".category." + selectedCategory); for(var k = 0; k < markerCategories.length; ++k) { if(markerCategories[k].classList.contains(selectedCategory)) { markerCategories[k].classList.remove("off"); markerCategories[k].style.backgroundColor = getComputedStyle(markerCategories[k]).getPropertyValue("border-color"); setDotLightness(markerCategories[k]); } } } else { testMarkers[j].classList.remove("off_" + selectedCategory); testMarkers[j].classList.add(selectedCategory); var markerCategories = testMarkers[j].querySelectorAll(".categoryMedium." + selectedCategory); for(var k = 0; k < markerCategories.length; ++k) { if(markerCategories[k].classList.contains(selectedCategory)) { markerCategories[k].classList.remove("off"); enableSprite(markerCategories[k]); } } } Skipping = 0; if(filterMode == "inclusive") { testMarkers[j].classList.remove("skip"); } else { var markerClasses = testMarkers[j].classList; for(var k = 0; k < markerClasses.length; ++k) { if(markerClasses[k].startsWith("off_")) { Skipping = 1; } } if(!Skipping) { testMarkers[j].classList.remove("skip"); } } } } } function resetFilter() { for(i in filterItems) { if(filterItems[i].classList) { var selectedCategory = filterItems[i].classList[1]; if(filterInitState[selectedCategory].off ^ filterState[selectedCategory].off) { filterItemToggle(filterItems[i]); } } } if(filterMode == "inclusive") { toggleFilterMode(); } } function invertFilter(focusedElement) { var siblings = focusedElement.parentNode.querySelectorAll(".filter_content"); for(i in siblings) { if(siblings[i].classList) { filterItemToggle(siblings[i]); } } } function resetFade() { filter.classList.remove("responsible"); filter.querySelector(".filter_mode").classList.remove("responsible"); var responsibleCategories = filter.querySelectorAll(".filter_content.responsible"); for(var i = 0; i < responsibleCategories.length; ++i) { responsibleCategories[i].classList.remove("responsible"); } } function onRefChanged(ref, element, player) { if(element.classList.contains("skip")) { var ErrorCount = 0; if(!filter) { console.log("Missing filter_container div"); ErrorCount++; } if(!filterState) { console.log("Missing filterState object"); ErrorCount++; } if(ErrorCount > 0) { switch(ErrorCount) { case 1: { console.log("This should have been generated by Cinera along with the following element containing the \"skip\" class:"); } break; default: { console.log("These should have been generated by Cinera along with the following element containing the \"skip\" class:"); } break; } console.log(element); return; } if(!filter.classList.contains("responsible")) { filter.classList.add("responsible"); } for(var selector = 0; selector < element.classList.length; ++selector) { if(element.classList[selector].startsWith("off_")) { if(!filter.querySelector(".filter_content." + element.classList[selector].replace(/^off_/, "")).classList.contains("responsible")) { filter.querySelector(".filter_content." + element.classList[selector].replace(/^off_/, "")).classList.add("responsible"); } } if(element.classList[selector].startsWith("cat_") || element.classList[selector] in filterState) { if(!filter.querySelector(".filter_mode").classList.add("responsible")) { filter.querySelector(".filter_mode").classList.add("responsible"); } } setTimeout(resetFade, 8000); } if(player && player.playing) { player.jumpToNextMarker(); } } else { for (var MenuIndex = 0; MenuIndex < sourceMenus.length; ++MenuIndex) { var SetMenu = 0; if (ref !== undefined && ref !== null) { var refElements = sourceMenus[MenuIndex].querySelectorAll(".refs .ref"); var refs = ref.split(","); for (var i = 0; i < refElements.length; ++i) { if (refs.includes(refElements[i].getAttribute("data-id"))) { refElements[i].classList.add("current"); SetMenu = 1; } else { refElements[i].classList.remove("current"); } } if(SetMenu) { sourceMenus[MenuIndex].classList.add("current"); } else { sourceMenus[MenuIndex].classList.remove("current"); } } else { sourceMenus[MenuIndex].classList.remove("current"); var refs = sourceMenus[MenuIndex].querySelectorAll(".refs .ref"); for (var i = 0; i < refs.length; ++i) { refs[i].classList.remove("current"); } } } } } function navigateFilter(filterItem) { if(filterItem != lastFocusedCategory) { lastFocusedCategory.classList.remove("focused"); unfocusSprite(lastFocusedCategory); if(filterItem.parentNode.classList.contains("filter_topics")) { lastFocusedTopic = filterItem; lastFocusedCategory = lastFocusedTopic; } else { lastFocusedMedium = filterItem; lastFocusedCategory = lastFocusedMedium; } focusedElement = lastFocusedCategory; focusedElement.classList.add("focused"); focusSprite(focusedElement); } } function mouseOverQuotes(quote) { if(focusedElement && quote != lastFocusedQuote) { focusedElement.classList.remove("focused"); lastFocusedQuote = quote; focusedElement = lastFocusedQuote; focusedElement.classList.add("focused"); } } function mouseOverReferences(reference) { if(focusedElement && reference != lastFocusedReference) { focusedElement.classList.remove("focused") lastFocusedReference = reference; } focusedElement = lastFocusedReference; focusedElement.classList.add("focused"); var ourIdentifiers = reference.querySelectorAll(".timecode"); weWereLastFocused = false; for(var k = 0; k < ourIdentifiers.length; ++k) { if(ourIdentifiers[k] == lastFocusedIdentifier) { weWereLastFocused = true; } } if(!weWereLastFocused) { lastFocusedIdentifier.classList.remove("focused"); lastFocusedIdentifier = ourIdentifiers[0]; } focusedIdentifer = lastFocusedIdentifier; focusedIdentifer.classList.add("focused"); for(var l = 0; l < ourIdentifiers.length; ++l) { ourIdentifiers[l].addEventListener("mouseenter", function(ev) { if(this != lastFocusedIdentifier) { lastFocusedIdentifier.classList.remove("focused"); lastFocusedIdentifier = this; lastFocusedIdentifier.classList.add("focused"); } }) } } function mouseSkipToTimecode(player, time, ev) { player.setTimeThenPlay(parseInt(time, 10)); ev.preventDefault(); ev.stopPropagation(); return false; } function handleMenuTogglerInteraction(menu, eventType) { if(!(menu.classList.contains("visible")) && eventType == "mouseenter" || menu.classList.contains("visible") && eventType == "mouseleave" || (eventType == "click" && !menu.classList.contains("cineraHelp"))) { if(menu.classList.contains("quotes")) { toggleMenuVisibility(quotesMenu); } else if(menu.classList.contains("references")) { toggleMenuVisibility(referencesMenu); } else if(menu.classList.contains("filter")) { toggleMenuVisibility(filterMenu); } else if(menu.classList.contains("link")) { toggleMenuVisibility(linkMenu); } else if(menu.classList.contains("credits")) { toggleMenuVisibility(creditsMenu); } } }