cinera.c: Add "direct" video support

Fixes:
• Rename Annotation → Timestamp in cinera_player_*.js
• Redo VideoIsPrivate() to only return once, at the end
This commit is contained in:
Matt Mascarenhas 2021-07-07 16:33:54 +01:00
parent 012d1608a0
commit c839e2ed85
4 changed files with 135 additions and 51 deletions

View File

@ -22,8 +22,8 @@ typedef struct
version CINERA_APP_VERSION = {
.Major = 0,
.Minor = 9,
.Patch = 2
.Minor = 10,
.Patch = 0
};
#include <stdarg.h> // NOTE(matt): varargs
@ -103,7 +103,8 @@ clock_t TIMING_START;
#define MAX_BASE_DIR_LENGTH 128
#define MAX_BASE_URL_LENGTH 128
#define MAX_RELATIVE_PAGE_LOCATION_LENGTH 32
#define MAX_VOD_ID_LENGTH 32
#define MAX_VOD_ID_LENGTH 256 // TODO(matt): This was 32, upped to 256 to fit full paths of "raw" videos.
// It hasn't mattered, but will do when the database contains everything.
#define MAX_ROOT_DIR_LENGTH 128
#define MAX_ROOT_URL_LENGTH 128
@ -1493,7 +1494,7 @@ fill the slots left vacant by positioned roles in the order in which they are co
{ "title_suffix", "Currently not implemented, probably to be removed." },
{ "unit", "This works in conjunction with the numbering_scheme. It is a freely-configurable string - e.g. \"Day\", \"Session\", \"Episode\", \"Meeting\" - which is written on the search page, \
preceding the derived number of each entry. If the unit is not set, then the entries will not be numbered." },
{ "vod_platform", "Possible VOD platforms: \"vimeo\", \"youtube\"." },
{ "vod_platform", "Possible VOD platforms: \"direct\" (for .webm, .mp4, etc. files), \"vimeo\", \"youtube\"." },
{ "url", "The URL where viewers may support the person, e.g. their page on a crowd funding site, the \"pledge\" page on their own website." },
};
@ -2378,6 +2379,7 @@ GetIconTypeFromString(string *Filename, token *T)
char *VODPlatformStrings[] =
{
0,
"direct",
"vimeo",
"youtube",
};
@ -2385,6 +2387,7 @@ char *VODPlatformStrings[] =
typedef enum
{
VP_DEFAULT_UNSET,
VP_DIRECT,
VP_VIMEO,
VP_YOUTUBE,
VP_COUNT,
@ -9010,12 +9013,11 @@ ExamineDB(void)
bool
VideoIsPrivate(vod_platform VODPlatform, char *VideoID)
{
// TODO(matt): Redo this to only return once, at the end
bool Result = FALSE;
// NOTE(matt): Currently only supports YouTube
// NOTE(matt): Stack-string
if(VODPlatform == VP_YOUTUBE)
{
// NOTE(matt): Stack-string
char Message[128];
CopyString(Message, sizeof(Message), "%sChecking%s privacy status of: https://youtube.com/watch?v=%s", ColourStrings[CS_ONGOING], ColourStrings[CS_END], VideoID);
fprintf(stderr, "%s", Message);
@ -9047,34 +9049,32 @@ VideoIsPrivate(vod_platform VODPlatform, char *VideoID)
SeekBufferForString(&VideoAPIResponse, "\"totalResults\": ", C_SEEK_FORWARDS, C_SEEK_AFTER);
if(*VideoAPIResponse.Ptr == '0')
{
DeclaimBuffer(&VideoAPIResponse);
// printf("Private video: https://youtube.com/watch?v=%s\n", VideoID);
ClearTerminalRow(MessageLength);
return TRUE;
Result = TRUE;
}
VideoAPIResponse.Ptr = VideoAPIResponse.Location;
SeekBufferForString(&VideoAPIResponse, "{", C_SEEK_FORWARDS, C_SEEK_AFTER);
SeekBufferForString(&VideoAPIResponse, "\"privacyStatus\": \"", C_SEEK_FORWARDS, C_SEEK_AFTER);
// NOTE(matt): Stack-string
char Status[16];
CopyStringNoFormatT(Status, sizeof(Status), VideoAPIResponse.Ptr, '\"');
if(!StringsDiffer0(Status, "public"))
else
{
DeclaimBuffer(&VideoAPIResponse);
ClearTerminalRow(MessageLength);
return FALSE;
VideoAPIResponse.Ptr = VideoAPIResponse.Location;
SeekBufferForString(&VideoAPIResponse, "{", C_SEEK_FORWARDS, C_SEEK_AFTER);
SeekBufferForString(&VideoAPIResponse, "\"privacyStatus\": \"", C_SEEK_FORWARDS, C_SEEK_AFTER);
// NOTE(matt): Stack-string
char Status[16];
CopyStringNoFormatT(Status, sizeof(Status), VideoAPIResponse.Ptr, '\"');
if(!StringsDiffer0(Status, "public"))
{
Result = FALSE;
}
else
{
Result = TRUE;
}
}
}
DeclaimBuffer(&VideoAPIResponse);
// printf("Unlisted video: https://youtube.com/watch?v=%s\n", VideoID);
ClearTerminalRow(MessageLength);
return TRUE;
}
else
{
return FALSE;
}
return Result;
}
bool

View File

@ -823,6 +823,14 @@ ul.cineraNavPlain li.current > a {
overflow: hidden;
}
.cineraPlayerContainer .video_container .direct_video {
display: flex;
}
.cineraPlayerContainer .video_container .direct_video video {
outline: none;
}
.cineraPlayerContainer .markers_container {
flex-shrink: 1;
overflow-y: scroll;

View File

@ -206,7 +206,7 @@ if(titleBar)
}
linkMenu = titleBar.querySelector(".link_container");
linkAnnotation = true;
linkTimestamp = true;
if(linkMenu)
{
menuState.push(linkMenu);
@ -382,12 +382,12 @@ for(var i = 0; i < topicDots.length; ++i)
setDotLightness(topicDots[i]);
}
var lastAnnotationStorageItem = "cineraTimecode_" + window.location.pathname;
var lastAnnotation;
var lastTimestampStorageItem = "cineraTimecode_" + window.location.pathname;
var lastTimestamp;
if(location.hash) {
player.setTimeThenPlay(location.hash.startsWith('#') ? location.hash.substr(1) : location.hash);
}
else if(lastAnnotation = localStorage.getItem(lastAnnotationStorageItem))
else if(lastTimestamp = localStorage.getItem(lastTimestampStorageItem))
{
player.setTimeThenPlay(lastAnnotation);
player.setTimeThenPlay(lastTimestamp);
}

View File

@ -2,8 +2,9 @@
// 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,
DIRECT: 0,
VIMEO: 1,
YOUTUBE: 2,
};
function
@ -12,6 +13,7 @@ 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;
@ -58,6 +60,7 @@ function Player(htmlContainer, refsCallback) {
this.platformPlayer = null;
this.platformPlayerReady = false;
this.duration = null;
this.playing = false;
this.shouldPlay = false;
this.buffering = false;
@ -88,6 +91,10 @@ Player.prototype.play = function() {
if (!this.playing) {
switch(this.vod_platform)
{
case vod_platform.DIRECT:
{
this.platformPlayer.play();
} break;
case vod_platform.VIMEO:
{
this.platformPlayer.play();
@ -111,6 +118,10 @@ Player.prototype.pause = function() {
if (this.playing) {
switch(this.vod_platform)
{
case vod_platform.DIRECT:
{
this.platformPlayer.pause();
} break;
case vod_platform.VIMEO:
{
this.platformPlayer.pause();
@ -134,6 +145,15 @@ 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) {
@ -537,7 +557,7 @@ ConnectMobileControls(player)
});
}
// Call this after changing the size of the video container in order to update the youtube player.
// 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;
@ -567,16 +587,22 @@ Player.prototype.updateSize = function() {
height = this.videoContainer.offsetHeight;
}
switch(this.vod_platform)
if(this.platformPlayerReady)
{
case vod_platform.VIMEO: /* NOTE(matt): It responds automatically */ break;
case vod_platform.YOUTUBE:
{
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;
} break;
default: break;
}
}
}
@ -601,6 +627,7 @@ Player.prototype.resume = function() {
Player.initializePlatform = function(platform_id, callback) {
switch(platform_id)
{
case vod_platform.DIRECT:
case vod_platform.VIMEO:
{
callback();
@ -688,11 +715,11 @@ Player.prototype.updateProgress = function() {
if (this.currentMarker) {
if(this.currentMarkerIdx == this.markers.length - 1)
{
localStorage.removeItem(lastAnnotationStorageItem);
localStorage.removeItem(lastTimestampStorageItem);
}
else
{
localStorage.setItem(lastAnnotationStorageItem, this.currentMarker.timestamp);
localStorage.setItem(lastTimestampStorageItem, this.currentMarker.timestamp);
}
this.currentMarker.el.classList.add("current");
this.scrollTo = this.currentMarker.el.offsetTop + this.currentMarker.el.offsetHeight/2.0;
@ -708,6 +735,11 @@ 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;
@ -744,6 +776,10 @@ Player.prototype.doFrame = function() {
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");
@ -777,6 +813,10 @@ Player.prototype.setDurationThenAutoplay = function(Duration) {
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;
@ -804,6 +844,10 @@ 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);
};
@ -815,7 +859,6 @@ Player.prototype.onYouTubePlayerPlaybackRateChange = function(ev) {
Player.prototype.onStateBufferStart = function()
{
this.buffering = true;
this.playing = false;
this.updateProgress();
}
@ -829,7 +872,7 @@ Player.prototype.onStateEnded = function()
{
this.buffering = false;
this.playing = false;
localStorage.removeItem(lastAnnotationStorageItem);
localStorage.removeItem(lastTimestampStorageItem);
this.currentTime = null;
this.updateProgress();
}
@ -854,6 +897,16 @@ Player.prototype.onStatePlaying = function(CurrentTime)
}
}
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);
@ -878,10 +931,29 @@ Player.prototype.onYouTubePlayerStateChange = function(ev) {
Player.prototype.onPlatformReady = function() {
var platformPlayerDiv = document.createElement("DIV");
platformPlayerDiv.id = "platform_player_" + Player.platformPlayerCount++;
this.videoContainer.appendChild(platformPlayerDiv);
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";
@ -938,7 +1010,7 @@ function updateLink()
{
if(link && player)
{
if(linkAnnotation == true)
if(linkTimestamp == true)
{
if(player.currentMarker)
{
@ -953,6 +1025,10 @@ function updateLink()
{
switch(player.vod_platform)
{
case vod_platform.DIRECT:
{
link.value = baseURL + "#" + Math.round(player.platformPlayer.currentTime);
} break;
case vod_platform.VIMEO:
{
player.platformPlayer.getCurrentTime()
@ -972,8 +1048,8 @@ function updateLink()
function toggleLinkMode(linkMode, link)
{
linkAnnotation = !linkAnnotation;
if(linkAnnotation == true)
linkTimestamp = !linkTimestamp;
if(linkTimestamp == true)
{
linkMode.textContent = "Link to: current timestamp";
}
@ -1721,7 +1797,7 @@ function handleKey(key) {
case 'Y': {
if(cineraLink)
{
if(linkAnnotation == false && player.playing)
if(linkTimestamp == false && player.playing)
{
player.pause();
}