diff --git a/cinera/cinera.c b/cinera/cinera.c index 4c1882f..f41374b 100644 --- a/cinera/cinera.c +++ b/cinera/cinera.c @@ -22,8 +22,8 @@ typedef struct version CINERA_APP_VERSION = { .Major = 0, - .Minor = 8, - .Patch = 23 + .Minor = 9, + .Patch = 0 }; #include // NOTE(matt): varargs @@ -2413,12 +2413,14 @@ GetIconTypeFromString(string *Filename, token *T) char *VODPlatformStrings[] = { 0, + "vimeo", "youtube", }; typedef enum { VP_DEFAULT_UNSET, + VP_VIMEO, VP_YOUTUBE, VP_COUNT, } vod_platform; @@ -10604,10 +10606,25 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF CopyStringToBufferHTMLSafe(&PlayerBuffers.Menus, Wrap0(HMML.metadata.title)); CopyStringToBuffer(&PlayerBuffers.Menus, "\n"); + switch(CollationBuffers->VODPlatform) + { + case VP_VIMEO: + { + CopyStringToBuffer(&PlayerBuffers.Main, "\n" + " "); + } break; + case VP_YOUTUBE: + { + CopyStringToBuffer(&PlayerBuffers.Main, "\n" + " "); + } break; + default: break; + } + CopyStringToBuffer(&PlayerBuffers.Main, "
\n" - "
\n" - "
\n", HMML.metadata.id, (int)CurrentProject->Theme.Length, CurrentProject->Theme.Base); + "
\n" + "
\n", VODPlatformStrings[CollationBuffers->VODPlatform], HMML.metadata.id, (int)CurrentProject->Theme.Length, CurrentProject->Theme.Base); if(N) { diff --git a/cinera/cinera_player_post.js b/cinera/cinera_player_post.js index 720e34c..1a788dd 100644 --- a/cinera/cinera_player_post.js +++ b/cinera/cinera_player_post.js @@ -53,6 +53,7 @@ var CineraProps = { IsMobile: IsMobile(), ScrollX: null, ScrollY: null, + VODPlatform: null, }; CineraProps.O = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile); @@ -252,7 +253,16 @@ if(titleBar) setSpriteLightness(focusedElement.firstChild); } } - }) + }); + if(creditItems[i].tagName == "A") + { + creditItems[i].addEventListener("click", function(ev) { + if(player) + { + player.pause(); + } + }); + } } } @@ -379,5 +389,5 @@ if(location.hash) { } else if(lastAnnotation = localStorage.getItem(lastAnnotationStorageItem)) { - player.setTime(lastAnnotation); + player.setTimeThenPlay(lastAnnotation); } diff --git a/cinera/cinera_player_pre.js b/cinera/cinera_player_pre.js index 0a21815..f75149f 100644 --- a/cinera/cinera_player_pre.js +++ b/cinera/cinera_player_pre.js @@ -1,15 +1,33 @@ // 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 = { + VIMEO: 0, + YOUTUBE: 1, +}; + +function +GetVODPlatformFromString(VODPlatformString) +{ + var Result = null; + switch(VODPlatformString) + { + 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-videoId")) { - console.error("Expected to find data-videoId attribute on", this.videoContainer, "for player initialized on", this.container); - throw new Error("Missing data-videoId attribute."); + 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"); @@ -33,10 +51,13 @@ function Player(htmlContainer, refsCallback) { this.markers.push(marker); } + this.vod_platform = GetVODPlatformFromString(this.videoContainer.getAttribute("data-platform")); this.currentMarker = null; this.currentMarkerIdx = null; - this.youtubePlayer = null; - this.youtubePlayerReady = false; + + this.platformPlayer = null; + this.platformPlayerReady = false; + this.playing = false; this.shouldPlay = false; this.buffering = false; @@ -53,7 +74,8 @@ function Player(htmlContainer, refsCallback) { this.scrollTo = -1; }.bind(this)); - Player.initializeYoutube(this.onYoutubeReady.bind(this)); + Player.initializePlatform(this.vod_platform, this.onPlatformReady.bind(this)); + var PendingMobileStyleInitialisation = true; this.updateSize(PendingMobileStyleInitialisation); this.resume(); @@ -62,22 +84,42 @@ function Player(htmlContainer, refsCallback) { // 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.youtubePlayerReady) { + if (this.platformPlayerReady) { if (!this.playing) { - this.youtubePlayer.playVideo(); + switch(this.vod_platform) + { + case vod_platform.VIMEO: + { + this.platformPlayer.play(); + } break; + case vod_platform.YOUTUBE: + { + this.platformPlayer.playVideo(); + } break; + } + this.pauseAfterBuffer = false; + } else { + this.shouldPlay = true; } - 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.youtubePlayerReady) { + if (this.platformPlayerReady) { if (this.playing) { - this.youtubePlayer.pauseVideo(); + switch(this.vod_platform) + { + case vod_platform.VIMEO: + { + this.platformPlayer.pause(); + } break; + case vod_platform.YOUTUBE: + { + this.platformPlayer.pauseVideo(); + } break; + } } else if (this.buffering) { this.pauseAfterBuffer = true; } @@ -86,15 +128,34 @@ Player.prototype.pause = function() { } }; -// Sets the current time. Does not affect play status. +// Sets the current time then plays. // If the player hasn't loaded yet, it will seek to this time when ready. -Player.prototype.setTime = function(time) { +Player.prototype.setTimeThenPlay = function(time) { this.currentTime = time; - if (this.youtubePlayerReady) { - this.currentTime = Math.max(0, Math.min(this.currentTime, this.youtubePlayer.getDuration())); - this.youtubePlayer.seekTo(this.currentTime); + switch(this.vod_platform) + { + 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; } - this.updateProgress(); }; Player.prototype.jumpToNextMarker = function() { @@ -105,8 +166,7 @@ Player.prototype.jumpToNextMarker = function() { ++targetMarkerIdx; targetTime = this.markers[targetMarkerIdx].timestamp; } - this.setTime(targetTime); - this.play(); + this.setTimeThenPlay(targetTime); }; Player.prototype.jumpToPrevMarker = function() { @@ -117,8 +177,7 @@ Player.prototype.jumpToPrevMarker = function() { --targetMarkerIdx; targetTime = this.markers[targetMarkerIdx].timestamp; } - this.setTime(targetTime); - this.play(); + this.setTimeThenPlay(targetTime); }; function @@ -508,8 +567,16 @@ Player.prototype.updateSize = function() { height = this.videoContainer.offsetHeight; } - if (this.youtubePlayerReady) { - this.youtubePlayer.setSize(Math.floor(width), Math.floor(height)); + switch(this.vod_platform) + { + case vod_platform.VIMEO: /* NOTE(matt): It responds automatically */ break; + case vod_platform.YOUTUBE: + { + if (this.platformPlayerReady) { + this.platformPlayer.setSize(Math.floor(width), Math.floor(height)); + } + } break; + default: break; } } @@ -531,24 +598,30 @@ Player.prototype.resume = function() { } } -Player.initializeYoutube = function(callback) { - 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](); - } - }; - var scriptTag = document.createElement("SCRIPT"); - scriptTag.setAttribute("type", "text/javascript"); - scriptTag.setAttribute("src", "https://www.youtube.com/iframe_api"); - document.body.appendChild(scriptTag); - } else if (window.APYoutubeAPIReady === false) { - window.APCallbacks.push(callback); - } else if (window.APYoutubeAPIReady === true) { - callback(); +Player.initializePlatform = function(platform_id, callback) { + switch(platform_id) + { + case vod_platform.VIMEO: + { + callback(); + } break; + case vod_platform.YOUTUBE: + { + 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; } } @@ -559,8 +632,7 @@ Player.prototype.onMarkerClick = function(marker, ev) { if (this.currentMarker == marker && marker.hoverx !== null) { time += (marker.endTime - marker.timestamp) * marker.hoverx; } - this.setTime(time); - this.play(); + this.setTimeThenPlay(time); }; Player.prototype.onMarkerMouseMove = function(marker, ev) { @@ -634,9 +706,24 @@ Player.prototype.updateProgress = function() { Player.prototype.doFrame = function() { if (this.playing) { - this.currentTime = this.youtubePlayer.getCurrentTime(); + switch(this.vod_platform) + { + 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; + } } - this.updateProgress(); if (this.scrollTo >= 0) { var targetPosition = this.scrollTo - this.markersContainer.offsetHeight/2.0; @@ -654,68 +741,182 @@ Player.prototype.doFrame = function() { updateLink(); }; -Player.prototype.onYoutubePlayerReady = function() { - this.youtubePlayerReady = true; - this.markers[this.markers.length-1].endTime = this.youtubePlayer.getDuration(); +Player.prototype.setStyleAndQuality = function() { + switch(this.vod_platform) + { + 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(); - this.youtubePlayer.setPlaybackQuality("hd1080"); +} + +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.youtubePlayer.getDuration())); - this.youtubePlayer.seekTo(this.currentTime, true); + this.currentTime = Math.max(0, Math.min(this.currentTime, this.duration)); + this.setTimeThenPlay(this.currentTime); } if (this.shouldPlay) { - this.youtubePlayer.playVideo(); + this.play(); } +} + +Player.prototype.acquireDurationThenAutoplay = function() { + switch(this.vod_platform) + { + 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.onYoutubePlayerStateChange = function(ev) { - if (ev.data == YT.PlayerState.PLAYING) { - this.playing = true; - this.currentTime = this.youtubePlayer.getCurrentTime(); - } else { - this.playing = false; - if (ev.data == YT.PlayerState.PAUSED || ev.data == YT.PlayerState.BUFFERING) { - this.currentTime = this.youtubePlayer.getCurrentTime(); - this.updateProgress(); - } else if (ev.data == YT.PlayerState.ENDED) { - localStorage.removeItem(lastAnnotationStorageItem); - this.currentTime = null; - this.updateProgress(); - } - } +Player.prototype.onPlaybackRateChange = function(Rate) +{ + this.speed = Rate; +} - this.buffering = ev.data == YT.PlayerState.BUFFERING; - if (this.playing && this.pauseAfterBuffer) { - this.pauseAfterBuffering = false; +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.playing = false; + this.updateProgress(); +} + +Player.prototype.onStateBufferEnd = function() +{ + this.buffering = false; + this.updateProgress(); +} + +Player.prototype.onStateEnded = function() +{ + this.buffering = false; + this.playing = false; + localStorage.removeItem(lastAnnotationStorageItem); + 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.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.onYoutubePlayerPlaybackRateChange = function(ev) { - this.speed = ev.data; +Player.prototype.onPlatformReady = function() { + var platformPlayerDiv = document.createElement("DIV"); + platformPlayerDiv.id = "platform_player_" + Player.platformPlayerCount++; + this.videoContainer.appendChild(platformPlayerDiv); + + switch(this.vod_platform) + { + case vod_platform.VIMEO: + { + this.videoContainer.style.position = "relative"; + this.videoContainer.style.alignSelf = "unset"; + this.platformPlayer = new Vimeo.Player(platformPlayerDiv.id, { + id: this.videoContainer.getAttribute("data-videoId"), + title: false, + }); + 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: + { + this.platformPlayer = new YT.Player(platformPlayerDiv.id, { + 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) + } + }); + } break; + } }; -Player.prototype.onYoutubeReady = function() { - var youtubePlayerDiv = document.createElement("DIV"); - youtubePlayerDiv.id = "youtube_player_" + Player.youtubePlayerCount++; - this.videoContainer.appendChild(youtubePlayerDiv); - this.youtubePlayer = new YT.Player(youtubePlayerDiv.id, { - videoId: this.videoContainer.getAttribute("data-videoId"), - width: this.videoContainer.offsetWidth, - height: this.videoContainer.offsetWidth / 16 * 9, - playerVars: { 'playsinline': 1 }, - //playerVars: { disablekb: 1 }, - events: { - "onReady": this.onYoutubePlayerReady.bind(this), - "onStateChange": this.onYoutubePlayerStateChange.bind(this), - "onPlaybackRateChange": this.onYoutubePlayerPlaybackRateChange.bind(this) - } - }); -}; - -Player.youtubePlayerCount = 0; - -// NOTE(matt): Hereafter is my stuff. Beware! +Player.platformPlayerCount = 0; function toggleFilterMode() { if(filterMode == "inclusive") @@ -750,7 +951,21 @@ function updateLink() } else { - link.value = baseURL + "#" + Math.round(player.youtubePlayer.getCurrentTime()); + switch(player.vod_platform) + { + 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; + } } } } @@ -891,30 +1106,30 @@ function handleMouseOverViewsMenu() } } +function IsFullScreen() +{ + return (document.fullscreen || document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement || document.fullscreenElement); +} + function enterFullScreen_() { - if(!document.mozFullScreen && !document.webkitFullScreen) + if(!IsFullScreen()) { - if(document.mozRequestFullScreen) - { - document.mozRequestFullScreen(); - } - else - { - document.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT); - } + 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(document.mozCancelFullScreen) + if(IsFullScreen()) { - document.mozCancelFullScreen(); - } - else - { - document.webkitExitFullscreen(); + if(document.exitFullscreen) { document.exitFullscreen(); } + else if(document.mozCancelFullScreen) { document.mozCancelFullScreen(); } + else if(document.webkitCancelFullScreen) { document.webkitCancelFullScreen(); } + else if(document.msExitFullscreen) { document.msExitFullscreen(); } } } @@ -1083,14 +1298,12 @@ function handleKey(key) { if(focusedElement.parentNode.classList.contains("quotes_container")) { var time = focusedElement.querySelector(".timecode").getAttribute("data-timestamp"); - player.setTime(parseInt(time, 10)); - player.play(); + player.setTimeThenPlay(parseInt(time)); } else if(focusedElement.parentNode.classList.contains("references_container")) { var time = focusedIdentifier.getAttribute("data-timestamp"); - player.setTime(parseInt(time, 10)); - player.play(); + player.setTimeThenPlay(parseInt(time)); } else if(focusedElement.parentNode.classList.contains("credit")) { @@ -1113,6 +1326,7 @@ function handleKey(key) { 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"); } @@ -1120,6 +1334,7 @@ function handleKey(key) { { if(focusedElement.hasAttribute("href")) { + if(player) { player.pause(); } var url = focusedElement.getAttribute("href"); window.open(url, "_blank"); } @@ -1880,8 +2095,7 @@ function mouseOverReferences(reference) { function mouseSkipToTimecode(player, time, ev) { - player.setTime(parseInt(time, 10)); - player.play(); + player.setTimeThenPlay(parseInt(time, 10)); ev.preventDefault(); ev.stopPropagation(); return false;