Compare commits

..

8 Commits

Author SHA1 Message Date
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 266 additions and 181 deletions

View File

@ -23,7 +23,7 @@ typedef struct
version CINERA_APP_VERSION = {
.Major = 0,
.Minor = 10,
.Patch = 25
.Patch = 30
};
#define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
@ -210,7 +210,12 @@ typedef struct
int C;
int Seconds;
};
} v3;
union
{
int D;
int Milliseconds;
};
} v4;
void
Clear(void *V, uint64_t Size)
@ -898,8 +903,8 @@ FreeBook(memory_book *M)
void
FreeAndReinitialiseBook(memory_book *M)
{
int PageSize = M->PageSize;
int DataWidthInBytes = M->DataWidthInBytes;
uint64_t PageSize = M->PageSize;
uint64_t DataWidthInBytes = M->DataWidthInBytes;
FreeBook(M);
@ -2167,7 +2172,7 @@ IndexingError(string Filename, uint64_t LineNumber, severity Severity, char *Mes
}
void
PrintTimecode(FILE *Dest, v3 Timecode)
PrintTimecode(FILE *Dest, v4 Timecode)
{
Colourise(CS_BLUE_BOLD);
if(Timecode.Hours)
@ -2182,7 +2187,7 @@ PrintTimecode(FILE *Dest, v3 Timecode)
}
void
IndexingChronologyError(string *Filename, uint64_t LineNumber, v3 ThisTimecode, v3 PrevTimecode)
IndexingChronologyError(string *Filename, uint64_t LineNumber, v4 ThisTimecode, v4 PrevTimecode)
{
severity Severity = S_ERROR;
ErrorFilenameAndLineNumber(Filename, LineNumber, Severity, ED_INDEXING);
@ -4167,6 +4172,68 @@ LogEdit(edit_type_id EditType, string Lineage, string EntryID, string *EntryTitl
}
}
#define CINERA_HSL_TRANSPARENT_HUE 65535
typedef struct
{
unsigned int Hue:16;
unsigned int Saturation:8;
unsigned int Lightness:8;
} hsl_colour;
hsl_colour
CharToColour(char Char)
{
hsl_colour Colour;
if(Char >= 'a' && Char <= 'z')
{
Colour.Hue = (((float)Char - 'a') / ('z' - 'a') * 360);
Colour.Saturation = (((float)Char - 'a') / ('z' - 'a') * 26 + 74);
}
else if(Char >= 'A' && Char <= 'Z')
{
Colour.Hue = (((float)Char - 'A') / ('Z' - 'A') * 360);
Colour.Saturation = (((float)Char - 'A') / ('Z' - 'A') * 26 + 74);
}
else if(Char >= '0' && Char <= '9')
{
Colour.Hue = (((float)Char - '0') / ('9' - '0') * 360);
Colour.Saturation = (((float)Char - '0') / ('9' - '0') * 26 + 74);
}
else
{
Colour.Hue = 180;
Colour.Saturation = 50;
}
return Colour;
}
void
StringToColourHash(hsl_colour *Colour, string String)
{
Colour->Hue = 0;
Colour->Saturation = 0;
Colour->Lightness = 74;
for(int i = 0; i < String.Length; ++i)
{
Colour->Hue += CharToColour(String.Base[i]).Hue;
Colour->Saturation += CharToColour(String.Base[i]).Saturation;
}
Colour->Hue = Colour->Hue % 360;
Colour->Saturation = Colour->Saturation % 26 + 74;
}
bool
IsValidHSLColour(hsl_colour Colour)
{
return (Colour.Hue >= 0 && Colour.Hue < 360)
&& (Colour.Saturation >= 0 && Colour.Saturation <= 100)
&& (Colour.Lightness >= 0 && Colour.Lightness <= 100);
}
#define SLASH 1
#define NULLTERM 1
typedef struct
@ -4304,7 +4371,7 @@ typedef struct
typedef struct
{
v3 Timecode;
v4 Timecode;
int Identifier;
} identifier;
@ -4321,6 +4388,7 @@ typedef struct
{
string Marker;
string WrittenText;
hsl_colour Colour;
} category_info;
#define CopyString(Dest, DestSize, Format, ...) CopyString_(__LINE__, (Dest), (DestSize), (Format), ##__VA_ARGS__)
@ -4430,7 +4498,7 @@ CopyStringToBuffer_(int LineNumber, buffer *Dest, char *Format, ...)
}
int
DigitsInTimecode(v3 Timecode)
DigitsInTimecode(v4 Timecode)
{
int Result = 0;
int ColonChar = 1;
@ -4453,7 +4521,7 @@ DigitsInTimecode(v3 Timecode)
#define CopyTimecodeToBuffer(Dest, Timecode) CopyTimecodeToBuffer_(__LINE__, Dest, Timecode)
void
CopyTimecodeToBuffer_(int LineNumber, buffer *Dest, v3 Timecode)
CopyTimecodeToBuffer_(int LineNumber, buffer *Dest, v4 Timecode)
{
if(DigitsInTimecode(Timecode) + (Dest->Ptr - Dest->Location) >= Dest->Size)
{
@ -4715,18 +4783,16 @@ typedef struct
bool Resolving;
} clash_resolver;
clash_resolver
InitClashResolver(void)
void
InitClashResolver(clash_resolver *ClashResolver)
{
clash_resolver Result = {};
Result.Book[0] = InitBook(sizeof(clash_entry), 8);
Result.Book[1] = InitBook(sizeof(clash_entry), 8);
Result.Main = &Result.Book[0];
Result.Holder = &Result.Book[1];
Result.Chain = InitBookOfPointers(8);
Result.ChainStructure = CS_OPEN_ENDED;
Result.Resolving = FALSE;
return Result;
ClashResolver->Book[0] = InitBook(sizeof(clash_entry), 8);
ClashResolver->Book[1] = InitBook(sizeof(clash_entry), 8);
ClashResolver->Main = &ClashResolver->Book[0];
ClashResolver->Holder = &ClashResolver->Book[1];
ClashResolver->Chain = InitBookOfPointers(8);
ClashResolver->ChainStructure = CS_OPEN_ENDED;
ClashResolver->Resolving = FALSE;
}
void
@ -5463,69 +5529,17 @@ InitTemplate(template *Template, string Location, template_type Type)
Template->Metadata.NavBuffer = InitBook(sizeof(navigation_buffer), 4);
}
v3
V3(int A, int B, int C)
v4
V4(int A, int B, int C, int D)
{
v3 Result = { .A = A, .B = B, .C = C };
v4 Result = { .A = A, .B = B, .C = C, .D = D };
return Result;
}
int
TimecodeToSeconds(v3 Timecode)
float
TimecodeToDottedSeconds(v4 Timecode)
{
return Timecode.Hours * SECONDS_PER_HOUR + Timecode.Minutes * SECONDS_PER_MINUTE + Timecode.Seconds;
}
typedef struct
{
unsigned int Hue:16;
unsigned int Saturation:8;
unsigned int Lightness:8;
} hsl_colour;
hsl_colour
CharToColour(char Char)
{
hsl_colour Colour;
if(Char >= 'a' && Char <= 'z')
{
Colour.Hue = (((float)Char - 'a') / ('z' - 'a') * 360);
Colour.Saturation = (((float)Char - 'a') / ('z' - 'a') * 26 + 74);
}
else if(Char >= 'A' && Char <= 'Z')
{
Colour.Hue = (((float)Char - 'A') / ('Z' - 'A') * 360);
Colour.Saturation = (((float)Char - 'A') / ('Z' - 'A') * 26 + 74);
}
else if(Char >= '0' && Char <= '9')
{
Colour.Hue = (((float)Char - '0') / ('9' - '0') * 360);
Colour.Saturation = (((float)Char - '0') / ('9' - '0') * 26 + 74);
}
else
{
Colour.Hue = 180;
Colour.Saturation = 50;
}
return Colour;
}
void
StringToColourHash(hsl_colour *Colour, string String)
{
Colour->Hue = 0;
Colour->Saturation = 0;
Colour->Lightness = 74;
for(int i = 0; i < String.Length; ++i)
{
Colour->Hue += CharToColour(String.Base[i]).Hue;
Colour->Saturation += CharToColour(String.Base[i]).Saturation;
}
Colour->Hue = Colour->Hue % 360;
Colour->Saturation = Colour->Saturation % 26 + 74;
return (float)Timecode.Hours * SECONDS_PER_HOUR + (float)Timecode.Minutes * SECONDS_PER_MINUTE + (float)Timecode.Seconds + (float)Timecode.Milliseconds / 1000;
}
char *
@ -8097,7 +8111,7 @@ BuildCredits(string HMMLFilepath, buffer *CreditsMenu, HMML_VideoMetaData *Metad
}
void
InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_info) *LocalTopics, _memory_book(category_info) *GlobalMedia, _memory_book(category_info) *LocalMedia, string Marker)
InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_info) *LocalTopics, _memory_book(category_info) *GlobalMedia, _memory_book(category_info) *LocalMedia, string Marker, hsl_colour *Colour)
{
medium *Medium = GetMediumFromProject(CurrentProject, Marker);
@ -8198,11 +8212,15 @@ InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_
{
category_info *Src = GetPlaceInBook(LocalTopics, CategoryCount - 1);
category_info *Dest = GetPlaceInBook(LocalTopics, CategoryCount);
Dest->Marker = Src->Marker;
*Dest = *Src;
}
category_info *New = GetPlaceInBook(LocalTopics, CategoryCount);
New->Marker = Marker;
if(Colour)
{
New->Colour = *Colour;
}
break;
}
}
@ -8213,6 +8231,10 @@ InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_
{
category_info *New = GetPlaceInBook(LocalTopics, TopicIndex);
New->Marker = Marker;
if(Colour)
{
New->Colour = *Colour;
}
}
bool MadeGlobalSpace = FALSE;
@ -8236,11 +8258,15 @@ InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_
{
category_info *Src = GetPlaceInBook(GlobalTopics, CategoryCount - 1);
category_info *Dest = GetPlaceInBook(GlobalTopics, CategoryCount);
Dest->Marker = Src->Marker;
*Dest = *Src;
}
category_info *New = GetPlaceInBook(GlobalTopics, CategoryCount);
New->Marker = Marker;
if(Colour)
{
New->Colour = *Colour;
}
break;
}
}
@ -8252,6 +8278,10 @@ InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_
{
category_info *New = GetPlaceInBook(GlobalTopics, TopicIndex);
New->Marker = Marker;
if(Colour)
{
New->Colour = *Colour;
}
}
}
}
@ -8343,9 +8373,18 @@ BuildCategoryIcons(buffer *CategoryIcons, _memory_book(category_info) *LocalTopi
CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)This->Marker.Length, This->Marker.Base);
SanitisePunctuation(SanitisedMarker);
CopyStringToBuffer(CategoryIcons, "<div title=\"%.*s\" class=\"category %s\"></div>",
CopyStringToBuffer(CategoryIcons,
"<div title=\"%.*s\" class=\"category %s\"",
(int)This->Marker.Length, This->Marker.Base,
SanitisedMarker);
if(IsValidHSLColour(This->Colour))
{
CopyStringToBuffer(CategoryIcons,
" data-hue=\"%u\" data-saturation=\"%u%%\"",
This->Colour.Hue, This->Colour.Saturation);
}
CopyStringToBuffer(CategoryIcons,
"></div>");
}
}
@ -8625,7 +8664,7 @@ BuildQuote(memory_book *Strings, quote_info *Info, string Speaker, int ID, bool
}
rc
GenerateTopicColours(neighbourhood *N, string Topic)
GenerateTopicColours(neighbourhood *N, string Topic, hsl_colour *Dest)
{
rc Result = RC_SUCCESS;
// NOTE(matt): Stack-string
@ -8636,6 +8675,15 @@ GenerateTopicColours(neighbourhood *N, string Topic)
medium *Medium = GetMediumFromProject(CurrentProject, Topic);
if(!Medium)
{
if(StringsMatch(Topic, Wrap0("nullTopic")))
{
Dest->Hue = CINERA_HSL_TRANSPARENT_HUE;
}
else
{
StringToColourHash(Dest, Topic);
}
file Topics = {};
Topics.Path = 0;
Topics.Buffer.ID = BID_TOPICS;
@ -8701,10 +8749,8 @@ GenerateTopicColours(neighbourhood *N, string Topic)
}
else
{
hsl_colour Colour;
StringToColourHash(&Colour, Topic);
WriteToFile(Topics.Handle, ".category.%s { border: 1px solid hsl(%d, %d%%, %d%%); background: hsl(%d, %d%%, %d%%); }\n",
SanitisedTopic, Colour.Hue, Colour.Saturation, Colour.Lightness, Colour.Hue, Colour.Saturation, Colour.Lightness);
SanitisedTopic, Dest->Hue, Dest->Saturation, Dest->Lightness, Dest->Hue, Dest->Saturation, Dest->Lightness);
}
#if DEBUG_MEM
@ -10816,9 +10862,9 @@ HMMLOutputLocationIs(neighbourhood *N, char *OutputLocation)
}
bool
TimecodeIs(v3 Timecode, int Hours, int Minutes, int Seconds)
TimecodeIs(v4 Timecode, int Hours, int Minutes, int Seconds, int Milliseconds)
{
return Timecode.Hours == Hours && Timecode.Minutes == Minutes && Timecode.Seconds == Seconds;
return Timecode.Hours == Hours && Timecode.Minutes == Minutes && Timecode.Seconds == Seconds && Timecode.Milliseconds == Milliseconds;
}
rc
@ -10829,14 +10875,14 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
bool *HasQuoteMenu, bool *HasReferenceMenu, bool *HasFilterMenu, bool *RequiresCineraJS,
int *QuoteIdentifier, int *RefIdentifier,
_memory_book(category_info) *Topics, _memory_book(category_info) *Media,
HMML_Timestamp *Timestamp, v3 *PreviousTimecode)
HMML_Timestamp *Timestamp, v4 *PreviousTimecode)
{
MEM_TEST_TOP();
// TODO(matt): Introduce and use a SystemError() in here
rc Result = RC_SUCCESS;
v3 Timecode = V3(Timestamp->h, Timestamp->m, Timestamp->s);
if(TimecodeToSeconds(Timecode) >= TimecodeToSeconds(*PreviousTimecode))
v4 Timecode = V4(Timestamp->h, Timestamp->m, Timestamp->s, Timestamp->ms);
if(TimecodeToDottedSeconds(Timecode) >= TimecodeToDottedSeconds(*PreviousTimecode))
{
*PreviousTimecode = Timecode;
@ -10857,8 +10903,8 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
CopyStringToBuffer(&IndexBuffers->Header,
" <div data-timestamp=\"%d\"",
TimecodeToSeconds(Timecode));
" <div data-timestamp=\"%.3f\"",
TimecodeToDottedSeconds(Timecode));
CopyStringToBuffer(&IndexBuffers->Class,
" class=\"marker");
@ -10894,7 +10940,7 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
{
*HasFilterMenu = TRUE;
}
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("authored"));
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("authored"), NULL);
hsl_colour AuthorColour;
StringToColourHash(&AuthorColour, Author);
// TODO(matt): That EDITION_NETWORK site database API-polling stuff
@ -10919,14 +10965,15 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
HMML_MarkerType Type = Timestamp->markers[MarkerIndex].type;
if(Type == HMML_CATEGORY)
{
Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker));
hsl_colour TopicColour = {};
Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour);
if(Result == RC_SUCCESS)
{
if(!*HasFilterMenu)
{
*HasFilterMenu = TRUE;
}
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker));
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour);
CopyStringToBuffer(&IndexBuffers->Text, "%.*s", (int)StringLength(Readable), InPtr);
}
else
@ -11115,10 +11162,10 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
" <div class=\"quote_byline\">&mdash;%.*s, %.*s</div>\n"
" </span>\n"
" <div class=\"ref_indices\">\n"
" <span data-timestamp=\"%d\" class=\"timecode\"><span class=\"ref_index\">[&#%d;]</span><span class=\"time\">",
" <span data-timestamp=\"%.3f\" class=\"timecode\"><span class=\"ref_index\">[&#%d;]</span><span class=\"time\">",
(int)QuoteUsername.Length, QuoteUsername.Base,
(int)DateString.Length, DateString.Base, // TODO(matt): Convert Unixtime to date-string
TimecodeToSeconds(Timecode),
TimecodeToDottedSeconds(Timecode),
*QuoteIdentifier);
CopyTimecodeToBuffer(&MenuBuffers->Quote, Timecode);
CopyStringToBuffer(&MenuBuffers->Quote, "</span></span>\n"
@ -11142,7 +11189,7 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
if(Result == RC_SUCCESS)
{
CopyStringToBuffer(&CollationBuffers->SearchEntry, "\"%d\": \"", TimecodeToSeconds(Timecode));
CopyStringToBuffer(&CollationBuffers->SearchEntry, "\"%.3f\": \"", TimecodeToDottedSeconds(Timecode));
if(Timestamp->quote.present && !Timestamp->text[0])
{
CopyStringToBuffer(&CollationBuffers->SearchEntry, "\u201C");
@ -11157,14 +11204,15 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
while(MarkerIndex < Timestamp->marker_count)
{
Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker));
hsl_colour TopicColour = {};
Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour);
if(Result == RC_SUCCESS)
{
if(!*HasFilterMenu)
{
*HasFilterMenu = TRUE;
}
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker));
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour);
++MarkerIndex;
}
else
@ -11177,10 +11225,11 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
{
if(LocalTopics.ItemCount == 0)
{
Result = GenerateTopicColours(N, Wrap0("nullTopic"));
hsl_colour TopicColour = {};
Result = GenerateTopicColours(N, Wrap0("nullTopic"), &TopicColour);
if(Result == RC_SUCCESS)
{
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("nullTopic"));
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("nullTopic"), &TopicColour);
}
}
@ -11188,7 +11237,7 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
{
if(LocalMedia.ItemCount == 0)
{
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, DefaultMedium->ID);
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, DefaultMedium->ID, NULL);
}
BuildTimestampClass(&IndexBuffers->Class, &LocalTopics, &LocalMedia, DefaultMedium->ID);
@ -11831,7 +11880,7 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF
Print(stdout, "\n\n --- Entering Timestamps Loop ---\n\n\n\n");
#endif
v3 PreviousTimecode = {};
v4 PreviousTimecode = {};
for(int TimestampIndex = 0; TimestampIndex < HMML.timestamp_count; ++TimestampIndex)
{
// TODO(matt): Thoroughly test this reorganisation
@ -11918,7 +11967,7 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF
{
identifier *ThisIdentifier = GetPlaceInBook(&This->Identifier, j);
CopyStringToBuffer(&MenuBuffers.Reference,
"<span data-timestamp=\"%d\" class=\"timecode\"><span class=\"ref_index\">[%d]</span><span class=\"time\">", TimecodeToSeconds(ThisIdentifier->Timecode), ThisIdentifier->Identifier);
"<span data-timestamp=\"%.3f\" class=\"timecode\"><span class=\"ref_index\">[%d]</span><span class=\"time\">", TimecodeToDottedSeconds(ThisIdentifier->Timecode), ThisIdentifier->Identifier);
CopyTimecodeToBuffer(&MenuBuffers.Reference, ThisIdentifier->Timecode);
CopyStringToBuffer(&MenuBuffers.Reference, "</span></span>");
}
@ -11987,12 +12036,20 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF
bool NullTopic = StringsMatch(This->Marker, Wrap0("nullTopic"));
CopyStringToBuffer(&MenuBuffers.FilterTopics,
" <div%s class=\"filter_content %s\">\n"
" <span class=\"icon category %s\"></span><span class=\"cineraText\">%.*s</span>\n"
" </div>\n",
" <span class=\"icon category %s\"",
NullTopic ? " title=\"Timestamps that don't fit into the above topic(s) may be filtered using this pseudo-topic\"" : "",
SanitisedMarker,
SanitisedMarker,
SanitisedMarker);
if(IsValidHSLColour(This->Colour))
{
CopyStringToBuffer(&MenuBuffers.FilterTopics,
" data-hue=\"%u\" data-saturation=\"%u%%\"",
This->Colour.Hue, This->Colour.Saturation);
}
CopyStringToBuffer(&MenuBuffers.FilterTopics,
"></span><span class=\"cineraText\">%.*s</span>\n"
" </div>\n",
NullTopic ? (int)sizeof("(null topic)")-1 : (int)This->Marker.Length,
NullTopic ? "(null topic)" : This->Marker.Base);
}
@ -18266,7 +18323,8 @@ main(int ArgC, char **Args)
CollationBuffers.Search.ID = BID_COLLATION_BUFFERS_SEARCH; // NOTE(matt): Allocated by SearchToBuffer()
memory_book TokensList = InitBook(sizeof(tokens), 8);
clash_resolver ClashResolver = InitClashResolver();
clash_resolver ClashResolver = {};
InitClashResolver(&ClashResolver);
template BespokeTemplate = {};
neighbourhood Neighbourhood = {};

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";
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,6 +581,8 @@ function setTextLightness(textElement)
{
var textHue = textElement.getAttribute("data-hue");
var textSaturation = textElement.getAttribute("data-saturation");
if(textHue && textSaturation)
{
if(getBackgroundBrightness(textElement.parentNode) < 127)
{
textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 76%)");
@ -608,19 +592,23 @@ function setTextLightness(textElement)
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];
var dotHue = topicDot.getAttribute("data-hue");
var dotSaturation = topicDot.getAttribute("data-saturation");
if(dotHue && dotSaturation)
{
if(getBackgroundBrightness(topicDot.parentNode) < 127)
{
topicDot.style.backgroundColor = ("hsl(" + Hue + ", " + Saturation + "%, 76%)");
topicDot.style.borderColor = ("hsl(" + Hue + ", " + Saturation + "%, 76%)");
topicDot.style.backgroundColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 76%)");
topicDot.style.borderColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 76%)");
}
else
{
topicDot.style.backgroundColor = ("hsl(" + Hue + ", " + Saturation + "%, 47%)");
topicDot.style.borderColor = ("hsl(" + Hue + ", " + Saturation + "%, 47%)");
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