Compare commits

...

10 Commits

Author SHA1 Message Date
Matt Mascarenhas e5ffec7a55 cinera.c: Make @author and :categories searchable
This is a stop-gap gap solution pending CINERA_DB_VERSION 6. It simply
augments the .index files, prepending "@author: " to timestamps bearing
an author – e.g. audience questions – and appending " [:each :category]"
to those bearing topic or medium categorisation.
2025-04-16 18:56:26 +01:00
Matt Mascarenhas a67a5ee34b cinera.c: Fix segfault in CurlIntoBuffer()
This was caused by trying to read InPtr[i] before checking if i < Length
in the loop's header. An -O2 build didn't exhibit the bug, but an -Od
one had begun to since I'd last run a debug build.

Also upped the sizes of the PlayerBuffers' Menus and Main buffers.
2025-04-13 01:38:18 +01:00
Matt Mascarenhas 14dafa4abe cinera.c: Fix stack-use-after-return segfault
The ClashResolver, or a memory_book variable therein, triggered a
stack-use-after-return segfault. Fixed by initialising the ClashResolver
in main() and passing it to InitClashResolver() by pointer, rather than
initialising it in InitClashResolver().
2024-03-12 13:03:08 +00:00
Matt Mascarenhas 213bb2f882 cinera.c: Fix colouring of topic dots
The colour computed for topic dots and put into cinera_topics.css in HSL
format has its lightness value modified depending on whether it's on a
dark or light background. Web browsers do not tell us an element's
computed colour in HSL format, but in RGB, so we would need to convert
it from RGB to HSL when setting the lightness. Previously, this
conversion was busted, calculating too small a value for the saturation.

This commit obviates the need for any RGB→HSL conversion by writing the
hue and saturation values as attributes to the elements, which the
function responsible for setting the lightness may use directly.
2024-02-21 20:52:34 +00:00
Matt Mascarenhas 77aec74483 cinera_player_post.js: Fix fullscreen handling
It is possible to bring our player out of fullscreen mode without using
the provided views menu or keyboard shortcut, e.g. by pressing Escape.
Doing so leaves our state in the SUPERTHEATRE view, which omits
SUPERtheatre mode itself from the views menu. This commit fixes this by
switching our state to the THEATRE view.

Thanks to Aske Bisgaard Vammen for the report.
2024-01-30 15:41:30 +00:00
Matt Mascarenhas 6852d06e04 cinera_player_pre.js: Unset focused menu
When hiding any menu, set this.MenusFocused.MenuID = menu_id.UNSET, to
let hovering over the markers focus them after hiding the link menu.
2023-03-25 01:51:40 +00:00
Matt Mascarenhas 52d6d989f8 cinera_player_pre.js: Fix scoping of "this" 2023-03-25 01:31:13 +00:00
Matt Mascarenhas df93674bf7 cinera: Increase precision to milliseconds
This commit simply adds millisecond precision of timecodes. It includes
small changes to hmmlib.h, cinera.c and the frontend JS files.
2023-03-25 00:04:34 +00:00
Matt Mascarenhas 026585e50b cinera: Fix mobile scrolling and centring
This commit fixes spurious scrolling in DeriveReliableWindowDimensions()
on mobile. It also fixes centring of Vimeo videos on mobile.

In addition, it replaces some deprecated JavaScript:
•   window.orientation → screen.orientation.type
•   window.onorientationchange → screen.orientation.onchange
2023-03-21 19:34:24 +00:00
Matt Mascarenhas 9d5f0f9146 cinera_player_pre.js: Fix scoping of "this" 2023-03-18 01:54:42 +00:00
6 changed files with 861 additions and 586 deletions

File diff suppressed because it is too large Load Diff

View File

@ -42,7 +42,7 @@ window.addEventListener("resize", function() {
}
});
window.onorientationchange = function() {
screen.orientation.onchange = function() {
if(CineraProps.IsMobile)
{
setTimeout(DelayedUpdateSize, 512, player);
@ -65,3 +65,12 @@ document.addEventListener("keydown", function(ev) {
ev.preventDefault();
}
});
document.addEventListener("fullscreenchange", function() {
if(!document.fullscreenElement && CineraProps.V == views.SUPERTHEATRE)
{
CineraProps.V = views.THEATRE;
localStorage.setItem(player.cineraViewStorageItem, views.THEATRE);
player.updateSize();
}
});

View File

@ -163,9 +163,9 @@ function Player(cineraElement, refsCallback) {
for (var i = 0; i < markerEls.length; ++i) {
var markerEl = markerEls[i];
var marker = {
timestamp: parseInt(markerEl.getAttribute("data-timestamp"), 10),
timestamp: parseFloat(markerEl.getAttribute("data-timestamp")),
ref: markerEl.getAttribute("data-ref"),
endTime: (i < markerEls.length - 1 ? parseInt(markerEls[i+1].getAttribute("data-timestamp"), 10) : null),
endTime: (i < markerEls.length - 1 ? parseFloat(markerEls[i+1].getAttribute("data-timestamp")) : null),
el: markerEl,
fadedProgress: markerEl.querySelector(".progress.faded"),
progress: markerEl.querySelector(".progress.main"),
@ -955,8 +955,7 @@ Player.prototype.InitMobileStyle = function()
this.IconifyMenuTogglers();
this.InitMobileControls();
this.ConnectMobileControls();
var VideoContainer = this.root.querySelector(".video_container");
this.ApplyMobileStyle(VideoContainer);
this.ApplyMobileStyle(this.videoContainer);
}
// Call this after changing the size of the video container in order to update the platform player.
@ -1238,7 +1237,7 @@ Player.prototype.doFrame = function() {
this.platformPlayer.getCurrentTime()
.then(function(Result) {
Parent.currentTime = Result;
if(this.desiredTime == -1) { this.desiredTime = this.currentTime; }
if(Parent.desiredTime == -1) { Parent.desiredTime = Parent.currentTime; }
Parent.updateProgress();
});
} break;
@ -1461,7 +1460,10 @@ Player.prototype.onPlatformReady = function() {
case vod_platform.VIMEO:
{
this.videoContainer.style.position = "relative";
this.videoContainer.style.alignSelf = "unset";
if(!CineraProps.IsMobile)
{
this.videoContainer.style.alignSelf = "unset";
}
var CallData = {
id: this.videoContainer.getAttribute("data-videoId"),
@ -1552,10 +1554,11 @@ Player.prototype.updateLink = function()
} break;
case vod_platform.VIMEO:
{
var Parent = this;
this.platformPlayer.getCurrentTime()
.then(function(Response)
{
this.link.value = baseURL + "#" + Math.round(Response);
Parent.link.value = baseURL + "#" + Math.round(Response);
});
} break;
case vod_platform.YOUTUBE:
@ -1628,6 +1631,7 @@ Player.prototype.toggleMenuVisibility = function(MenuID, Trigger) {
if(element.classList.contains("visible"))
{
HideMenu(element);
this.MenusFocused.MenuID = menu_id.UNSET;
if(Trigger == trigger_id.KEYBOARD && this.Menus[menu_id.MARKERS].Item.LastFocused)
{
@ -2013,12 +2017,12 @@ Player.prototype.handleKey = function(key) {
case menu_id.MARKERS:
{
var time = this.MenusFocused.Item.getAttribute("data-timestamp");
this.setTimeThenPlay(parseInt(time));
this.setTimeThenPlay(parseFloat(time));
} break;
case menu_id.QUOTES:
{
var time = this.MenusFocused.Item.querySelector(".timecode").getAttribute("data-timestamp");
this.setTimeThenPlay(parseInt(time));
this.setTimeThenPlay(parseFloat(time));
if(this.currentMarker)
{
this.setScroller(this.Menus[menu_id.MARKERS], this.currentMarker.el, true, false);
@ -2028,7 +2032,7 @@ Player.prototype.handleKey = function(key) {
case menu_id.REFERENCES:
{
var time = this.MenusFocused.Identifier.getAttribute("data-timestamp");
this.setTimeThenPlay(parseInt(time));
this.setTimeThenPlay(parseFloat(time));
if(this.currentMarker)
{
this.setScroller(this.Menus[menu_id.MARKERS], this.currentMarker.el, true, false);
@ -2050,7 +2054,7 @@ Player.prototype.handleKey = function(key) {
if(this.currentMarker && this.currentMarker.el)
{
var time = this.currentMarker.el.getAttribute("data-timestamp");
this.setTimeThenPlay(parseInt(time));
this.setTimeThenPlay(parseFloat(time));
this.setScroller(this.Menus[menu_id.MARKERS], this.currentMarker.el, true, false);
}
}
@ -2910,7 +2914,7 @@ Player.prototype.mouseOverCredits = function(item) {
function mouseSkipToTimecode(player, time, ev)
{
player.setTimeThenPlay(parseInt(time, 10));
player.setTimeThenPlay(parseFloat(time));
ev.preventDefault();
ev.stopPropagation();
return false;

View File

@ -13,6 +13,9 @@ DeriveReliableWindowDimensions()
Y: null,
};
var ScrollPosX = window.scrollX;
var ScrollPosY = window.scrollY;
var DisplaySettings = [];
for(var i = 0; i < document.body.children.length; ++i)
{
@ -40,6 +43,9 @@ DeriveReliableWindowDimensions()
Child.style.display = DisplaySettings.shift();
}
ScrollTriggeredInternally = true;
window.scroll(ScrollPosX, ScrollPosY);
return Result;
}
@ -76,7 +82,7 @@ function IsVisible(Element, WindowDim) {
function
GetRealOrientation(PreferredLandscape, IsMobile)
{
var Result = window.orientation;
var Result = screen.orientation.angle;
var WindowDim = GetWindowDim(IsMobile);
if(WindowDim.Y > WindowDim.X)
{
@ -317,6 +323,7 @@ function IsInRangeEx(Min, N, Max)
}
/* Auto-scrolling */
var ScrollTriggeredInternally = false;
var LastScrollYPos = 0;
var ScrollTicking = false;
var ScrollerFunction;
@ -383,7 +390,11 @@ function
InitScrollEventListener(Element, IsMobile, StickyObscuringElement)
{
window.addEventListener('scroll', function() {
if(ScrollCondition == undefined || ScrollCondition == true)
if(ScrollTriggeredInternally)
{
ScrollTriggeredInternally = false;
}
else if(ScrollCondition == undefined || ScrollCondition == true)
{
LastScrollYPos = window.scrollY;
@ -540,35 +551,6 @@ BindHelp(Button, DocumentationContainer)
})
}
function RGBtoHSL(colour)
{
var rgb = colour.slice(4, -1).split(", ");
var red = rgb[0];
var green = rgb[1];
var blue = rgb[2];
var min = Math.min(red, green, blue);
var max = Math.max(red, green, blue);
var chroma = max - min;
var hue = 0;
if(max == red)
{
hue = ((green - blue) / chroma) % 6;
}
else if(max == green)
{
hue = ((blue - red) / chroma) + 2;
}
else if(max == blue)
{
hue = ((red - green) / chroma) + 4;
}
var saturation = chroma / 255 * 100;
hue = (hue * 60) < 0 ? 360 + (hue * 60) : (hue * 60);
return [hue, saturation]
}
function getBackgroundColourRGB(element) {
var Colour = getComputedStyle(element).getPropertyValue("background-color");
var depth = 0;
@ -599,28 +581,34 @@ function setTextLightness(textElement)
{
var textHue = textElement.getAttribute("data-hue");
var textSaturation = textElement.getAttribute("data-saturation");
if(getBackgroundBrightness(textElement.parentNode) < 127)
if(textHue && textSaturation)
{
textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 76%)");
}
else
{
textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 24%)");
if(getBackgroundBrightness(textElement.parentNode) < 127)
{
textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 76%)");
}
else
{
textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 24%)");
}
}
}
function setDotLightness(topicDot)
{
var Hue = RGBtoHSL(getComputedStyle(topicDot).getPropertyValue("background-color"))[0];
var Saturation = RGBtoHSL(getComputedStyle(topicDot).getPropertyValue("background-color"))[1];
if(getBackgroundBrightness(topicDot.parentNode) < 127)
var dotHue = topicDot.getAttribute("data-hue");
var dotSaturation = topicDot.getAttribute("data-saturation");
if(dotHue && dotSaturation)
{
topicDot.style.backgroundColor = ("hsl(" + Hue + ", " + Saturation + "%, 76%)");
topicDot.style.borderColor = ("hsl(" + Hue + ", " + Saturation + "%, 76%)");
}
else
{
topicDot.style.backgroundColor = ("hsl(" + Hue + ", " + Saturation + "%, 47%)");
topicDot.style.borderColor = ("hsl(" + Hue + ", " + Saturation + "%, 47%)");
if(getBackgroundBrightness(topicDot.parentNode) < 127)
{
topicDot.style.backgroundColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 76%)");
topicDot.style.borderColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 76%)");
}
else
{
topicDot.style.backgroundColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 47%)");
topicDot.style.borderColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 47%)");
}
}
}

View File

@ -213,11 +213,11 @@ function prepareToParseIndexFile(project)
mode = "markers";
episode.markers = [];
} else if (mode == "markers") {
var match = line.match(/"(\d+)": "(.+)"/);
var match = line.match(/"(\d+.\d+)": "(.+)"/);
if (match == null) {
console.log(name, line);
} else {
var totalTime = parseInt(line.slice(1));
var totalTime = parseFloat(line.slice(1));
var marker = {
totalTime: totalTime,
prettyTime: markerTime(totalTime),
@ -292,7 +292,7 @@ function markerTime(totalTime) {
var markTime = "(";
var hours = Math.floor(totalTime / 60 / 60);
var minutes = Math.floor(totalTime / 60) % 60;
var seconds = totalTime % 60;
var seconds = Math.floor(totalTime) % 60;
if (hours > 0) {
markTime += padTimeComponent(hours) + ":";
}
@ -460,7 +460,7 @@ function runSearch(refresh) {
Search.ResultsSummary.style.display = "none";
}
var totalTime = Math.floor(totalSeconds/60/60) + "h " + Math.floor(totalSeconds/60)%60 + "m " + totalSeconds%60 + "s ";
var totalTime = Math.floor(totalSeconds/60/60) + "h " + Math.floor(totalSeconds/60)%60 + "m " + Math.floor(totalSeconds)%60 + "s ";
Search.ResultsSummary.textContent = "Found: " + numEpisodes + " episodes, " + numMarkers + " markers, " + totalTime + "total.";
}
@ -3866,7 +3866,7 @@ InitResizeEventListener()
function
InitOrientationChangeListener()
{
window.onorientationchange = function()
screen.orientation.onchange = function()
{
if(CineraProps.IsMobile)
{

View File

@ -76,7 +76,7 @@ typedef struct {
typedef struct {
int line;
int h, m, s;
int h, m, s, ms;
char* text;
char* author;
@ -471,7 +471,7 @@ next_attr:
static void _hmml_parse_timecode(struct _hmml_parser* p, HMML_Timestamp* ts)
{
unsigned int h = 0, m = 0, s = 0;
unsigned int h = 0, m = 0, s = 0, ms = 0;
int offset = 0;
int count = sscanf(p->cursor, "[%u:%u%n", &m, &s, &offset);
@ -485,7 +485,7 @@ static void _hmml_parse_timecode(struct _hmml_parser* p, HMML_Timestamp* ts)
if(c == ':') {
unsigned int tmp;
offset = 0;
if(sscanf(p->cursor, ":%u]%n", &tmp, &offset) != 1 || offset == 0) {
if(sscanf(p->cursor, ":%u%n", &tmp, &offset) != 1 || offset == 0) {
_hmml_err(p, "Unable to parse 3-part timecode");
}
@ -494,6 +494,27 @@ static void _hmml_parse_timecode(struct _hmml_parser* p, HMML_Timestamp* ts)
s = tmp;
p->cursor += offset;
c = *p->cursor;
}
if(c == '.') {
unsigned int tmp;
offset = 0;
int non_number_chars = 2;
int digits_in_100 = 3;
int max_chars_to_parse = non_number_chars + digits_in_100;
if(sscanf(p->cursor, ".%u]%n", &tmp, &offset) != 1 || offset == 0 || offset > max_chars_to_parse) {
_hmml_err(p, "Unable to parse %u.5-part timecode", h ? 3 : 2);
}
for(int i = offset - non_number_chars; i < digits_in_100; ++i) {
tmp *= 10;
}
ms = tmp;
p->cursor += offset;
} else if(c != ']') {
_hmml_err(p, "Unable to parse timecode");
@ -501,6 +522,10 @@ static void _hmml_parse_timecode(struct _hmml_parser* p, HMML_Timestamp* ts)
++p->cursor;
}
if(ms >= 1000) {
_hmml_err(p, "Milliseconds cannot exceed 999");
}
if(s >= 60) {
_hmml_err(p, "Seconds cannot exceed 59");
}
@ -512,6 +537,7 @@ static void _hmml_parse_timecode(struct _hmml_parser* p, HMML_Timestamp* ts)
ts->h = h;
ts->m = m;
ts->s = s;
ts->ms = ms;
}
static void _hmml_store_marker(struct _hmml_parser* p, HMML_Timestamp* ts, char** out, char* text_mem, size_t text_mem_size)
@ -824,7 +850,7 @@ void hmml_free(HMML_Output* out)
}
const struct HMML_Version hmml_version = {
2, 0, 14
2, 0, 15
};
#undef HSTX