From c839e2ed85839ff56d9c805842c78db249bc8af0 Mon Sep 17 00:00:00 2001 From: Matt Mascarenhas Date: Wed, 7 Jul 2021 16:33:54 +0100 Subject: [PATCH] cinera.c: Add "direct" video support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: • Rename Annotation → Timestamp in cinera_player_*.js • Redo VideoIsPrivate() to only return once, at the end --- cinera/cinera.c | 52 ++++++++-------- cinera/cinera.css | 8 +++ cinera/cinera_player_post.js | 10 +-- cinera/cinera_player_pre.js | 116 +++++++++++++++++++++++++++++------ 4 files changed, 135 insertions(+), 51 deletions(-) diff --git a/cinera/cinera.c b/cinera/cinera.c index 13ef91d..30c3787 100644 --- a/cinera/cinera.c +++ b/cinera/cinera.c @@ -22,8 +22,8 @@ typedef struct version CINERA_APP_VERSION = { .Major = 0, - .Minor = 9, - .Patch = 2 + .Minor = 10, + .Patch = 0 }; #include // 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 diff --git a/cinera/cinera.css b/cinera/cinera.css index 2789e81..d7baffc 100644 --- a/cinera/cinera.css +++ b/cinera/cinera.css @@ -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; diff --git a/cinera/cinera_player_post.js b/cinera/cinera_player_post.js index 59b157f..71113f5 100644 --- a/cinera/cinera_player_post.js +++ b/cinera/cinera_player_post.js @@ -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); } diff --git a/cinera/cinera_player_pre.js b/cinera/cinera_player_pre.js index f75149f..f927d03 100644 --- a/cinera/cinera_player_pre.js +++ b/cinera/cinera_player_pre.js @@ -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(); }