cinera_player_pre.js: Add Vimeo support

Fixes

• Fix fullscreen
• Pause whenever / however following a reference or credit URL
This commit is contained in:
Matt Mascarenhas 2021-06-23 15:13:41 +01:00
parent e39a09c0ad
commit dd33bb49b3
3 changed files with 364 additions and 123 deletions

View File

@ -22,8 +22,8 @@ typedef struct
version CINERA_APP_VERSION = {
.Major = 0,
.Minor = 8,
.Patch = 23
.Minor = 9,
.Patch = 0
};
#include <stdarg.h> // 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, "</span>\n");
switch(CollationBuffers->VODPlatform)
{
case VP_VIMEO:
{
CopyStringToBuffer(&PlayerBuffers.Main, "<script src=\"https://player.vimeo.com/api/player.js\"></script>\n"
" ");
} break;
case VP_YOUTUBE:
{
CopyStringToBuffer(&PlayerBuffers.Main, "<script src=\"https://www.youtube.com/iframe_api\"></script>\n"
" ");
} break;
default: break;
}
CopyStringToBuffer(&PlayerBuffers.Main,
"<div class=\"cineraPlayerContainer\">\n"
" <div class=\"video_container\" data-videoId=\"%s\"></div>\n"
" <div class=\"markers_container %.*s\">\n", HMML.metadata.id, (int)CurrentProject->Theme.Length, CurrentProject->Theme.Base);
" <div class=\"video_container\" data-platform=\"%s\" data-videoId=\"%s\"></div>\n"
" <div class=\"markers_container %.*s\">\n", VODPlatformStrings[CollationBuffers->VODPlatform], HMML.metadata.id, (int)CurrentProject->Theme.Length, CurrentProject->Theme.Base);
if(N)
{

View File

@ -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);
}

View File

@ -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;