hmml_to_html.c: Partially implement refs [#15]
It is missing the onRefChanged() function PPiC 051
This commit is contained in:
parent
ce2feef7f6
commit
3394daf8b0
Binary file not shown.
|
@ -0,0 +1,331 @@
|
||||||
|
#if 0
|
||||||
|
ctime -begin ${0%.*}.ctm
|
||||||
|
gcc -g -fsanitize=address $0 -o ${0%.*} hmml.a
|
||||||
|
ctime -end ${0%.*}.ctm
|
||||||
|
exit
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "hmmlib.h"
|
||||||
|
|
||||||
|
#ifndef bool
|
||||||
|
typedef bool unsigned int;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TRUE 1
|
||||||
|
#define FALSE 0
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char *Location;
|
||||||
|
char *Ptr;
|
||||||
|
int Size;
|
||||||
|
} buffer;
|
||||||
|
|
||||||
|
int
|
||||||
|
TimecodeToSeconds(char *Timecode)
|
||||||
|
{
|
||||||
|
int HMS[3] = { 0, 0, 0 }; // 0 == Seconds; 1 == Minutes; 2 == Hours
|
||||||
|
int Colons = 0;
|
||||||
|
while(*Timecode)
|
||||||
|
{
|
||||||
|
if((*Timecode < '0' || *Timecode > '9') && *Timecode != ':') { return FALSE; }
|
||||||
|
|
||||||
|
if(*Timecode == ':')
|
||||||
|
{
|
||||||
|
++Colons;
|
||||||
|
if(Colons > 2) { return FALSE; }
|
||||||
|
for(int i = 0; i < Colons; ++i)
|
||||||
|
{
|
||||||
|
HMS[Colons - i] = HMS[Colons - (i + 1)];
|
||||||
|
}
|
||||||
|
HMS[0] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HMS[0] = HMS[0] * 10 + *Timecode - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
++Timecode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(HMS[0] > 59 || HMS[1] > 59 || Timecode[-1] == ':') { return FALSE; }
|
||||||
|
|
||||||
|
return HMS[2] * 60 * 60 + HMS[1] * 60 + HMS[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int ArgC, char **Args)
|
||||||
|
{
|
||||||
|
if(ArgC < 2)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s filename(s)\n", Args[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(matt): Init MemoryArena
|
||||||
|
char *MemoryArena;
|
||||||
|
int ArenaSize = 1024 * 64;
|
||||||
|
if(!(MemoryArena = calloc(ArenaSize, 1)))
|
||||||
|
{
|
||||||
|
perror(Args[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int ClaimedMemory = 0;
|
||||||
|
|
||||||
|
// NOTE(matt): Setup buffers and ptrs
|
||||||
|
char *InPtr;
|
||||||
|
buffer Template;
|
||||||
|
buffer Working;
|
||||||
|
buffer Out;
|
||||||
|
|
||||||
|
FILE *TemplateFile;
|
||||||
|
if(!(TemplateFile = fopen("style.css", "r")))
|
||||||
|
{
|
||||||
|
perror(Args[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(TemplateFile, 0, SEEK_END);
|
||||||
|
Template.Size = ftell(TemplateFile);
|
||||||
|
fseek(TemplateFile, 0, SEEK_SET);
|
||||||
|
|
||||||
|
Template.Location = MemoryArena + ClaimedMemory;
|
||||||
|
ClaimedMemory += Template.Size;
|
||||||
|
fread(Template.Location, Template.Size, 1, TemplateFile);
|
||||||
|
fclose(TemplateFile);
|
||||||
|
|
||||||
|
Out.Location = MemoryArena + ClaimedMemory;
|
||||||
|
Out.Size = 1024 * 32;
|
||||||
|
ClaimedMemory += Out.Size;
|
||||||
|
|
||||||
|
for(int FileIndex = 1; FileIndex < ArgC; ++FileIndex)
|
||||||
|
{
|
||||||
|
FILE *InFile;
|
||||||
|
if(!(InFile = fopen(Args[FileIndex], "r")))
|
||||||
|
{
|
||||||
|
perror(Args[0]);
|
||||||
|
free(MemoryArena);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
HMML_Output HMML = hmml_parse_file(InFile);
|
||||||
|
fclose(InFile);
|
||||||
|
|
||||||
|
if(HMML.well_formed)
|
||||||
|
{
|
||||||
|
Working.Location = MemoryArena + ClaimedMemory;
|
||||||
|
Working.Size = 351;
|
||||||
|
ClaimedMemory += Working.Size;
|
||||||
|
|
||||||
|
sprintf(Working.Location,
|
||||||
|
"<html>\n"
|
||||||
|
" <head>\n"
|
||||||
|
" <meta charset=\"UTF-8\">\n"
|
||||||
|
"\n"
|
||||||
|
" <!-- Load the player -->\n"
|
||||||
|
" <script type=\"text/javascript\" src=\"player.js\"></script>\n"
|
||||||
|
" <link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">\n"
|
||||||
|
" </head>\n"
|
||||||
|
" <body>\n"
|
||||||
|
" <div class=\"title\">\n"
|
||||||
|
" <span class=\"episode_name\">%s</span>\n", HMML.metadata.title);
|
||||||
|
|
||||||
|
Working.Ptr = Working.Location;
|
||||||
|
Out.Ptr = Out.Location;
|
||||||
|
|
||||||
|
while(*Working.Ptr)
|
||||||
|
{
|
||||||
|
*Out.Ptr++ = *Working.Ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Working.Location = '\0';
|
||||||
|
ClaimedMemory -= Working.Size;
|
||||||
|
|
||||||
|
Working.Location = MemoryArena + ClaimedMemory;
|
||||||
|
Working.Size = 1024;
|
||||||
|
ClaimedMemory += Working.Size;
|
||||||
|
|
||||||
|
int AnnotationIndex = 0;
|
||||||
|
while(AnnotationIndex < HMML.annotation_count)
|
||||||
|
{
|
||||||
|
if(HMML.annotations[AnnotationIndex].reference_count)
|
||||||
|
{
|
||||||
|
sprintf(Working.Location,
|
||||||
|
" <div class=\"refs_container\">\n"
|
||||||
|
" <span>References ▼</span>\n"
|
||||||
|
" <div class=\"mouse_catcher\"></div>\n"
|
||||||
|
" <div class=\"refs\">\n");
|
||||||
|
|
||||||
|
Working.Ptr = Working.Location;
|
||||||
|
while(*Working.Ptr)
|
||||||
|
{
|
||||||
|
*Out.Ptr++ = *Working.Ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(AnnotationIndex < HMML.annotation_count)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < HMML.annotations[AnnotationIndex].reference_count; ++i)
|
||||||
|
{
|
||||||
|
sprintf(Working.Location,
|
||||||
|
" <a href=\"%s\" target=\"_blank\" class=\"ref\">\n"
|
||||||
|
" <span data-timestamp=\"%d\" class=\"timecode\">(<span class=\"time\">%s</span>)</span>\n"
|
||||||
|
" <span class=\"ref_content\">\n"
|
||||||
|
" <div class=\"source\">%s</div>\n"
|
||||||
|
" <div class=\"ref_title\">%s</div>\n"
|
||||||
|
" </span>\n"
|
||||||
|
" </a>\n",
|
||||||
|
HMML.annotations[AnnotationIndex].references[i].url,
|
||||||
|
TimecodeToSeconds(HMML.annotations[AnnotationIndex].time),
|
||||||
|
HMML.annotations[AnnotationIndex].time,
|
||||||
|
HMML.annotations[AnnotationIndex].references[i].site ? HMML.annotations[AnnotationIndex].references[i].site : HMML.annotations[AnnotationIndex].references[i].author,
|
||||||
|
HMML.annotations[AnnotationIndex].references[i].page ? HMML.annotations[AnnotationIndex].references[i].page : HMML.annotations[AnnotationIndex].references[i].title);
|
||||||
|
|
||||||
|
Working.Ptr = Working.Location;
|
||||||
|
while(*Working.Ptr)
|
||||||
|
{
|
||||||
|
*Out.Ptr++ = *Working.Ptr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++AnnotationIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++AnnotationIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
Working.Location = '\0';
|
||||||
|
ClaimedMemory -= Working.Size;
|
||||||
|
|
||||||
|
Working.Location = MemoryArena + ClaimedMemory;
|
||||||
|
Working.Size = 256;
|
||||||
|
ClaimedMemory += Working.Size;
|
||||||
|
|
||||||
|
Working.Location = '\0';
|
||||||
|
ClaimedMemory -= Working.Size;
|
||||||
|
|
||||||
|
Working.Location = MemoryArena + ClaimedMemory;
|
||||||
|
Working.Size = 1024;
|
||||||
|
ClaimedMemory += Working.Size;
|
||||||
|
|
||||||
|
sprintf(Working.Location,
|
||||||
|
" </div>\n"
|
||||||
|
" </div>\n"
|
||||||
|
" <span class=\"annotator_container\">Annotator: <span class=\"annotator\">%s</span></span>\n"
|
||||||
|
"</div>\n"
|
||||||
|
"<div class=\"player_container\">\n"
|
||||||
|
" <div class=\"video_container\" data-videoId=\"%s\"></div>\n"
|
||||||
|
" <div class=\"markers_container\">\n", HMML.metadata.annotator, HMML.metadata.id);
|
||||||
|
|
||||||
|
Working.Ptr = Working.Location;
|
||||||
|
|
||||||
|
while(*Working.Ptr)
|
||||||
|
{
|
||||||
|
*Out.Ptr++ = *Working.Ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Working.Location = '\0';
|
||||||
|
ClaimedMemory -= Working.Size;
|
||||||
|
|
||||||
|
int DataRef = 0;
|
||||||
|
|
||||||
|
for(int AnnotationIndex = 0; AnnotationIndex < HMML.annotation_count; ++AnnotationIndex)
|
||||||
|
{
|
||||||
|
Working.Location = MemoryArena + ClaimedMemory;
|
||||||
|
Working.Size = 1024;
|
||||||
|
ClaimedMemory += Working.Size;
|
||||||
|
|
||||||
|
if(HMML.annotations[AnnotationIndex].reference_count || HMML.annotations[AnnotationIndex].is_quote)
|
||||||
|
{
|
||||||
|
sprintf(Working.Location, "<div class=\"marker\" data-timestamp=\"%d\" data-ref=\"%d\">\n", TimecodeToSeconds(HMML.annotations[AnnotationIndex].time), DataRef);
|
||||||
|
++DataRef;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprintf(Working.Location, "<div class=\"marker\" data-timestamp=\"%d\">\n", TimecodeToSeconds(HMML.annotations[AnnotationIndex].time));
|
||||||
|
}
|
||||||
|
|
||||||
|
Working.Ptr = Working.Location;
|
||||||
|
while(*Working.Ptr)
|
||||||
|
{
|
||||||
|
*Out.Ptr++ = *Working.Ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(Working.Location, "<div class=\"content\"><span class=\"timecode\">%s</span>%s</div>\n"
|
||||||
|
"<div class=\"progress faded\">\n"
|
||||||
|
"<div class=\"content\"><span class=\"timecode\">%s</span>%s</div>\n"
|
||||||
|
"</div>\n"
|
||||||
|
"<div class=\"progress main\">\n"
|
||||||
|
"<div class=\"content\"><span class=\"timecode\">%s</span>%s</div>\n"
|
||||||
|
"</div>\n"
|
||||||
|
"</div>\n",
|
||||||
|
HMML.annotations[AnnotationIndex].time,
|
||||||
|
HMML.annotations[AnnotationIndex].text,
|
||||||
|
HMML.annotations[AnnotationIndex].time,
|
||||||
|
HMML.annotations[AnnotationIndex].text,
|
||||||
|
HMML.annotations[AnnotationIndex].time,
|
||||||
|
HMML.annotations[AnnotationIndex].text);
|
||||||
|
Working.Ptr = Working.Location;
|
||||||
|
|
||||||
|
while(*Working.Ptr)
|
||||||
|
{
|
||||||
|
*Out.Ptr++ = *Working.Ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Working.Location = '\0';
|
||||||
|
ClaimedMemory -= Working.Size;
|
||||||
|
|
||||||
|
*Out.Ptr++ = '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
Working.Location = MemoryArena + ClaimedMemory;
|
||||||
|
Working.Size = 256;
|
||||||
|
ClaimedMemory += Working.Size;
|
||||||
|
|
||||||
|
sprintf(Working.Location, "</div>\n"
|
||||||
|
"</div>\n"
|
||||||
|
"<script>\n"
|
||||||
|
"var player = new Player(document.querySelector(\".player_container\"));\n"
|
||||||
|
"window.addEventListener(\"resize\", function() { player.updateSize(); });\n"
|
||||||
|
"document.addEventListener(\"keypress\", function(ev) {\n"
|
||||||
|
" switch (ev.key) {\n"
|
||||||
|
" case 'n':\n"
|
||||||
|
" case 'd':\n"
|
||||||
|
" case 's': {\n"
|
||||||
|
" player.jumpToNextMarker();\n"
|
||||||
|
" } break;\n"
|
||||||
|
"\n"
|
||||||
|
" case 'p':\n"
|
||||||
|
" case 'a':\n"
|
||||||
|
" case 'w': {\n"
|
||||||
|
" player.jumpToPrevMarker();\n"
|
||||||
|
" } break;\n"
|
||||||
|
" }\n"
|
||||||
|
"});\n"
|
||||||
|
"</script>\n"
|
||||||
|
"</body>\n"
|
||||||
|
"</html>\n");
|
||||||
|
|
||||||
|
Working.Ptr = Working.Location;
|
||||||
|
while(*Working.Ptr)
|
||||||
|
{
|
||||||
|
*Out.Ptr++ = *Working.Ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
hmml_free(&HMML);
|
||||||
|
|
||||||
|
FILE *OutFile;
|
||||||
|
//char *OutFilename;
|
||||||
|
//sprintf(OutFilename, "%s.html", Args[FileIndex]);
|
||||||
|
if(!(OutFile = fopen("out.html", "w")))
|
||||||
|
{
|
||||||
|
perror(Args[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite(Out.Location, Out.Ptr - Out.Location, 1, OutFile);
|
||||||
|
fclose(OutFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(MemoryArena);
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
#ifndef HMML_H_
|
||||||
|
#define HMML_H_
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
// Data structures
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* member;
|
||||||
|
char* twitch;
|
||||||
|
char* project;
|
||||||
|
char* title;
|
||||||
|
char* platform;
|
||||||
|
char* id;
|
||||||
|
char* annotator;
|
||||||
|
} HMML_VideoMetaData;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* site;
|
||||||
|
char* page;
|
||||||
|
char* url;
|
||||||
|
char* title;
|
||||||
|
char* article;
|
||||||
|
char* author;
|
||||||
|
char* editor;
|
||||||
|
char* publisher;
|
||||||
|
char* isbn;
|
||||||
|
int offset;
|
||||||
|
} HMML_Reference;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
HMML_CATEGORY,
|
||||||
|
HMML_MEMBER,
|
||||||
|
HMML_PROJECT,
|
||||||
|
|
||||||
|
HMML_MARKER_COUNT,
|
||||||
|
} HMML_MarkerType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HMML_MarkerType type;
|
||||||
|
char* text;
|
||||||
|
int offset;
|
||||||
|
} HMML_Marker;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int id;
|
||||||
|
char* author;
|
||||||
|
} HMML_Quote;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int line;
|
||||||
|
char* time;
|
||||||
|
char* text;
|
||||||
|
char* author;
|
||||||
|
|
||||||
|
HMML_Reference* references;
|
||||||
|
size_t reference_count;
|
||||||
|
|
||||||
|
HMML_Marker* markers;
|
||||||
|
size_t marker_count;
|
||||||
|
|
||||||
|
HMML_Quote quote;
|
||||||
|
bool is_quote;
|
||||||
|
} HMML_Annotation;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int line;
|
||||||
|
char* message;
|
||||||
|
} HMML_Error;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool well_formed;
|
||||||
|
HMML_VideoMetaData metadata;
|
||||||
|
HMML_Annotation* annotations;
|
||||||
|
size_t annotation_count;
|
||||||
|
HMML_Error error;
|
||||||
|
} HMML_Output;
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
|
||||||
|
HMML_Output hmml_parse_file (FILE* file);
|
||||||
|
void hmml_dump (HMML_Output* output);
|
||||||
|
void hmml_free (HMML_Output* output);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,287 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
|
<!-- Load the player -->
|
||||||
|
<script type="text/javascript" src="player.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="title">
|
||||||
|
<span class="episode_name">Ripple Carry Adders & Two's Complement</span>
|
||||||
|
<div class="refs_container">
|
||||||
|
<span>References ▼</span>
|
||||||
|
<div class="mouse_catcher"></div>
|
||||||
|
<div class="refs">
|
||||||
|
<a href="http://www.charlespetzold.com/code/" target="_blank" class="ref">
|
||||||
|
<span data-timestamp="61" class="timecode">(<span class="time">1:01</span>)</span>
|
||||||
|
<span class="ref_content">
|
||||||
|
<div class="source">Charles Petzold</div>
|
||||||
|
<div class="ref_title">Code: The Hidden Language of Computer Hardware and Software</div>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://en.wikipedia.org/wiki/Logic_gate" target="_blank" class="ref">
|
||||||
|
<span data-timestamp="1082" class="timecode">(<span class="time">18:02</span>)</span>
|
||||||
|
<span class="ref_content">
|
||||||
|
<div class="source">Wikipedia</div>
|
||||||
|
<div class="ref_title">Logic gate</div>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://en.wikipedia.org/wiki/Adder_(electronics)" target="_blank" class="ref">
|
||||||
|
<span data-timestamp="2218" class="timecode">(<span class="time">36:58</span>)</span>
|
||||||
|
<span class="ref_content">
|
||||||
|
<div class="source">Wikipedia</div>
|
||||||
|
<div class="ref_title">Adder (electronics)</div>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://en.wikipedia.org/wiki/Ones'_complement" target="_blank" class="ref">
|
||||||
|
<span data-timestamp="2886" class="timecode">(<span class="time">48:06</span>)</span>
|
||||||
|
<span class="ref_content">
|
||||||
|
<div class="source">Wikipedia</div>
|
||||||
|
<div class="ref_title">Ones' complement</div>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://en.wikipedia.org/wiki/Two's_complement" target="_blank" class="ref">
|
||||||
|
<span data-timestamp="3268" class="timecode">(<span class="time">54:28</span>)</span>
|
||||||
|
<span class="ref_content">
|
||||||
|
<div class="source">Wikipedia</div>
|
||||||
|
<div class="ref_title">Two's complement</div>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html" target="_blank" class="ref">
|
||||||
|
<span data-timestamp="3883" class="timecode">(<span class="time">1:04:43</span>)</span>
|
||||||
|
<span class="ref_content">
|
||||||
|
<div class="source">GCC, the GNU Compiler Collection</div>
|
||||||
|
<div class="ref_title">Statements and Declarations in Expressions</div>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="annotator_container">Annotator: <span class="annotator">Miblo</span></span>
|
||||||
|
</div>
|
||||||
|
<div class="player_container">
|
||||||
|
<div class="video_container" data-videoId="BObABjzvVPw"></div>
|
||||||
|
<div class="markers_container">
|
||||||
|
<div class="marker" data-timestamp="7">
|
||||||
|
<div class="content"><span class="timecode">0:07</span>Set the stage for the day, looking at two's complement and sign extension</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">0:07</span>Set the stage for the day, looking at two's complement and sign extension</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">0:07</span>Set the stage for the day, looking at two's complement and sign extension</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="41" data-ref="0">
|
||||||
|
<div class="content"><span class="timecode">0:41</span>"It seems like, as you get older, the years start to go by like weeks"</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">0:41</span>"It seems like, as you get older, the years start to go by like weeks"</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">0:41</span>"It seems like, as you get older, the years start to go by like weeks"</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="61" data-ref="1">
|
||||||
|
<div class="content"><span class="timecode">1:01</span>Recommend Code by Charles Petzold</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">1:01</span>Recommend Code by Charles Petzold</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">1:01</span>Recommend Code by Charles Petzold</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="303">
|
||||||
|
<div class="content"><span class="timecode">5:03</span>Using an electromagnet with a telegraph to communicate in Morse code</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">5:03</span>Using an electromagnet with a telegraph to communicate in Morse code</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">5:03</span>Using an electromagnet with a telegraph to communicate in Morse code</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="596">
|
||||||
|
<div class="content"><span class="timecode">9:56</span>Adder circuit</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">9:56</span>Adder circuit</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">9:56</span>Adder circuit</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="1082" data-ref="2">
|
||||||
|
<div class="content"><span class="timecode">18:02</span>Research logic gates</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">18:02</span>Research logic gates</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">18:02</span>Research logic gates</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="1167">
|
||||||
|
<div class="content"><span class="timecode">19:27</span>XOR</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">19:27</span>XOR</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">19:27</span>XOR</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="1538">
|
||||||
|
<div class="content"><span class="timecode">25:38</span>Redraw the tables</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">25:38</span>Redraw the tables</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">25:38</span>Redraw the tables</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="1681">
|
||||||
|
<div class="content"><span class="timecode">28:01</span>What an XOR gate will do when the carry is off</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">28:01</span>What an XOR gate will do when the carry is off</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">28:01</span>What an XOR gate will do when the carry is off</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="2007">
|
||||||
|
<div class="content"><span class="timecode">33:27</span>Propagating the carry through our circuit</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">33:27</span>Propagating the carry through our circuit</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">33:27</span>Propagating the carry through our circuit</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="2218" data-ref="3">
|
||||||
|
<div class="content"><span class="timecode">36:58</span>Consult Wikipedia for a ripple carry adder</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">36:58</span>Consult Wikipedia for a ripple carry adder</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">36:58</span>Consult Wikipedia for a ripple carry adder</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="2338">
|
||||||
|
<div class="content"><span class="timecode">38:58</span>This circuit does 1 bit of the adder computation</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">38:58</span>This circuit does 1 bit of the adder computation</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">38:58</span>This circuit does 1 bit of the adder computation</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="2485">
|
||||||
|
<div class="content"><span class="timecode">41:25</span>Subtraction circuit</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">41:25</span>Subtraction circuit</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">41:25</span>Subtraction circuit</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="2686">
|
||||||
|
<div class="content"><span class="timecode">44:46</span>Agreeing on an encoding in order to communicate useful information</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">44:46</span>Agreeing on an encoding in order to communicate useful information</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">44:46</span>Agreeing on an encoding in order to communicate useful information</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="2793" data-ref="4">
|
||||||
|
<div class="content"><span class="timecode">46:33</span>"You can quote whatever you like, Miblo"</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">46:33</span>"You can quote whatever you like, Miblo"</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">46:33</span>"You can quote whatever you like, Miblo"</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="2886" data-ref="5">
|
||||||
|
<div class="content"><span class="timecode">48:06</span>Start with looking at ones' complement</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">48:06</span>Start with looking at ones' complement</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">48:06</span>Start with looking at ones' complement</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="3268" data-ref="6">
|
||||||
|
<div class="content"><span class="timecode">54:28</span>Go to two's complement</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">54:28</span>Go to two's complement</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">54:28</span>Go to two's complement</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="3552">
|
||||||
|
<div class="content"><span class="timecode">59:12</span>Using both ones' complement and two's complement to enable our circuit to perform subtraction</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">59:12</span>Using both ones' complement and two's complement to enable our circuit to perform subtraction</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">59:12</span>Using both ones' complement and two's complement to enable our circuit to perform subtraction</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="3883" data-ref="7">
|
||||||
|
<div class="content"><span class="timecode">1:04:43</span>GCC's "Statements and Declarations in Expressions" Extension</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">1:04:43</span>GCC's "Statements and Declarations in Expressions" Extension</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">1:04:43</span>GCC's "Statements and Declarations in Expressions" Extension</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="marker" data-timestamp="3972">
|
||||||
|
<div class="content"><span class="timecode">1:06:12</span>That's all for now</div>
|
||||||
|
<div class="progress faded">
|
||||||
|
<div class="content"><span class="timecode">1:06:12</span>That's all for now</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress main">
|
||||||
|
<div class="content"><span class="timecode">1:06:12</span>That's all for now</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var player = new Player(document.querySelector(".player_container"));
|
||||||
|
window.addEventListener("resize", function() { player.updateSize(); });
|
||||||
|
document.addEventListener("keypress", function(ev) {
|
||||||
|
switch (ev.key) {
|
||||||
|
case 'n':
|
||||||
|
case 'd':
|
||||||
|
case 's': {
|
||||||
|
player.jumpToNextMarker();
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
case 'a':
|
||||||
|
case 'w': {
|
||||||
|
player.jumpToPrevMarker();
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,390 @@
|
||||||
|
// 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`.
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
this.markers = [];
|
||||||
|
var markerEls = this.markersContainer.querySelectorAll(".marker");
|
||||||
|
if (markerEls.length == 0) {
|
||||||
|
console.error("No markers found in", this.markersContainer, "for player initialized on", this.container);
|
||||||
|
throw new Error("Missing markers.");
|
||||||
|
}
|
||||||
|
for (var i = 0; i < markerEls.length; ++i) {
|
||||||
|
var marker = {
|
||||||
|
timestamp: parseInt(markerEls[i].getAttribute("data-timestamp"), 10),
|
||||||
|
ref: markerEls[i].getAttribute("data-ref"),
|
||||||
|
endTime: (i < markerEls.length - 1 ? parseInt(markerEls[i+1].getAttribute("data-timestamp"), 10) : null),
|
||||||
|
el: markerEls[i],
|
||||||
|
fadedProgress: markerEls[i].querySelector(".progress.faded"),
|
||||||
|
progress: markerEls[i].querySelector(".progress.main"),
|
||||||
|
hoverx: null
|
||||||
|
};
|
||||||
|
marker.el.addEventListener("click", this.onMarkerClick.bind(this, marker));
|
||||||
|
marker.el.addEventListener("mousemove", this.onMarkerMouseMove.bind(this, marker));
|
||||||
|
marker.el.addEventListener("mouseleave", this.onMarkerMouseLeave.bind(this, marker));
|
||||||
|
this.markers.push(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentMarker = null;
|
||||||
|
this.currentMarkerIdx = null;
|
||||||
|
this.youtubePlayer = null;
|
||||||
|
this.youtubePlayerReady = false;
|
||||||
|
this.playing = false;
|
||||||
|
this.shouldPlay = false;
|
||||||
|
this.speed = 1;
|
||||||
|
this.currentTime = 0;
|
||||||
|
this.lastFrameTime = 0;
|
||||||
|
this.scrollTo = -1;
|
||||||
|
this.scrollPosition = 0;
|
||||||
|
this.nextFrame = null;
|
||||||
|
this.looping = false;
|
||||||
|
|
||||||
|
this.markersContainer.addEventListener("wheel", function(ev) {
|
||||||
|
this.scrollTo = -1;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
Player.initializeYoutube(this.onYoutubeReady.bind(this));
|
||||||
|
this.updateSize();
|
||||||
|
|
||||||
|
this.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.playing) {
|
||||||
|
this.youtubePlayer.playVideo();
|
||||||
|
}
|
||||||
|
} 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.playing) {
|
||||||
|
this.youtubePlayer.pauseVideo();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.shouldPlay = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sets the current time. Does not affect play status.
|
||||||
|
// If the player hasn't loaded yet, it will seek to this time when ready.
|
||||||
|
Player.prototype.setTime = 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);
|
||||||
|
}
|
||||||
|
this.updateProgress();
|
||||||
|
};
|
||||||
|
|
||||||
|
Player.prototype.jumpToNextMarker = function() {
|
||||||
|
var targetMarkerIdx = Math.min((this.currentMarkerIdx === null ? 0 : this.currentMarkerIdx + 1), this.markers.length-1);
|
||||||
|
var targetTime = this.markers[targetMarkerIdx].timestamp;
|
||||||
|
this.setTime(targetTime);
|
||||||
|
this.play();
|
||||||
|
};
|
||||||
|
|
||||||
|
Player.prototype.jumpToPrevMarker = function() {
|
||||||
|
var targetMarkerIdx = Math.max(0, (this.currentMarkerIdx === null ? 0 : this.currentMarkerIdx - 1));
|
||||||
|
var targetTime = this.markers[targetMarkerIdx].timestamp;
|
||||||
|
this.setTime(targetTime);
|
||||||
|
this.play();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call this after changing the size of the video container in order to update the youtube player.
|
||||||
|
Player.prototype.updateSize = function() {
|
||||||
|
var width = this.videoContainer.offsetWidth;
|
||||||
|
var height = width / 16 * 9;
|
||||||
|
this.markersContainer.style.height = height;
|
||||||
|
if (this.youtubePlayerReady) {
|
||||||
|
this.youtubePlayer.setSize(Math.floor(width), Math.floor(height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stops the per-frame work that the player does. Call when you want to hide or get rid of the player.
|
||||||
|
Player.prototype.halt = function() {
|
||||||
|
this.pause();
|
||||||
|
this.looping = false;
|
||||||
|
if (this.nextFrame) {
|
||||||
|
cancelAnimationFrame(this.nextFrame);
|
||||||
|
this.nextFrame = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resumes the per-frame work that the player does. Call when you want to show the player again after hiding.
|
||||||
|
Player.prototype.resume = function() {
|
||||||
|
this.looping = true;
|
||||||
|
if (!this.nextFrame) {
|
||||||
|
this.doFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Player.createHTMLSkeleton = function(videoId, markers) {
|
||||||
|
};
|
||||||
|
|
||||||
|
// timestamp can be either an int (number of seconds from beginning of the video) or a string ([hh:][mm:]ss)
|
||||||
|
Player.createHTMLMarker = function(timestamp, text, ref) {
|
||||||
|
if (typeof(timestamp) == "string") {
|
||||||
|
var timeParts = timestamp.split(":");
|
||||||
|
var hours = (timeParts.length == 3 ? parseInt(timeParts[0], 10) : 0);
|
||||||
|
var minutes = (timeParts.length > 1 ? parseInt(timeParts[timeParts.length-2], 10) : 0);
|
||||||
|
var seconds = parseInt(timeParts[timeParts.length-1], 10);
|
||||||
|
timestamp = hours * 60 * 60 + minutes * 60 + seconds;
|
||||||
|
}
|
||||||
|
var marker = document.createElement("DIV");
|
||||||
|
marker.classList.add("marker");
|
||||||
|
marker.setAttribute("data-timestamp", timestamp);
|
||||||
|
if (ref !== undefined && ref !== null) {
|
||||||
|
marker.setAttribute("data-ref", ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = document.createElement("DIV");
|
||||||
|
content.classList.add("content");
|
||||||
|
|
||||||
|
var markerTime = "(";
|
||||||
|
var hours = Math.floor(timestamp / 60 / 60);
|
||||||
|
var minutes = Math.floor(timestamp / 60) % 60;
|
||||||
|
var seconds = timestamp % 60;
|
||||||
|
if (hours > 0) {
|
||||||
|
markerTime += (hours < 10 ? "0" + hours : hours) + ":";
|
||||||
|
}
|
||||||
|
markerTime += (minutes < 10 ? "0" + minutes : minutes) + ":" + (seconds < 10 ? "0" + seconds : seconds) + ")";
|
||||||
|
|
||||||
|
content.textContent = markerTime + " " + text;
|
||||||
|
marker.appendChild(content);
|
||||||
|
|
||||||
|
var fadedProgress = document.createElement("DIV");
|
||||||
|
fadedProgress.classList.add("progress");
|
||||||
|
fadedProgress.classList.add("faded");
|
||||||
|
fadedProgress.appendChild(content.cloneNode(true));
|
||||||
|
marker.appendChild(fadedProgress);
|
||||||
|
|
||||||
|
var progress = document.createElement("DIV");
|
||||||
|
progress.classList.add("progress");
|
||||||
|
progress.classList.add("main");
|
||||||
|
progress.appendChild(content.cloneNode(true));
|
||||||
|
marker.appendChild(progress);
|
||||||
|
|
||||||
|
|
||||||
|
return marker;
|
||||||
|
};
|
||||||
|
|
||||||
|
Player.parseHMHAnnotation = function(text) {
|
||||||
|
var annotation = {
|
||||||
|
title: null,
|
||||||
|
videoId: null,
|
||||||
|
author: null,
|
||||||
|
markers: []
|
||||||
|
};
|
||||||
|
|
||||||
|
var lines = text.split("\n");
|
||||||
|
var mode = "none";
|
||||||
|
for (var i = 0; i < lines.length; ++i) {
|
||||||
|
var line = lines[i];
|
||||||
|
if (line == "---") {
|
||||||
|
mode = "none";
|
||||||
|
} else if (line.startsWith("title:")) {
|
||||||
|
annotation.title = line.slice(7).replace(/"/g, "");
|
||||||
|
} else if (line.startsWith("videoId:")) {
|
||||||
|
annotation.videoId = line.slice(9).replace(/"/g, "");
|
||||||
|
} else if (line.startsWith("author:")) {
|
||||||
|
annotation.author = line.slice(8).replace(/"/g, ""); // Miblo, where's the author field?
|
||||||
|
} else if (line.startsWith("markers")) {
|
||||||
|
mode = "markers";
|
||||||
|
} else if (mode == "markers") {
|
||||||
|
var match = line.match(/"((\d+):)?(\d+):(\d+)": "(.+)"/);
|
||||||
|
var marker = {
|
||||||
|
timestamp: (match[2] ? parseInt(match[2], 10) : 0) * 60 * 60 + parseInt(match[3], 10) * 60 + parseInt(match[4], 10),
|
||||||
|
text: match[5].replace(/\\"/g, "\"")
|
||||||
|
}
|
||||||
|
annotation.markers.push(marker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return annotation;
|
||||||
|
};
|
||||||
|
|
||||||
|
Player.parseAnnotation = function(text) {
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// END PUBLIC INTERFACE
|
||||||
|
|
||||||
|
Player.prototype.onMarkerClick = function(marker, ev) {
|
||||||
|
var time = marker.timestamp;
|
||||||
|
if (this.currentMarker == marker && marker.hoverx !== null) {
|
||||||
|
time += (marker.endTime - marker.timestamp) * marker.hoverx;
|
||||||
|
}
|
||||||
|
this.setTime(time);
|
||||||
|
this.play();
|
||||||
|
};
|
||||||
|
|
||||||
|
Player.prototype.onMarkerMouseMove = function(marker, ev) {
|
||||||
|
if (this.currentMarker == marker) {
|
||||||
|
marker.hoverx = (ev.offsetX - marker.el.offsetLeft) / marker.el.offsetWidth;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Player.prototype.onMarkerMouseLeave = function(marker, ev) {
|
||||||
|
marker.hoverx = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
Player.prototype.updateProgress = function() {
|
||||||
|
var prevMarker = this.currentMarker;
|
||||||
|
this.currentMarker = null;
|
||||||
|
this.currentMarkerIdx = null;
|
||||||
|
|
||||||
|
for (var i = 0; i < this.markers.length; ++i) {
|
||||||
|
var marker = this.markers[i];
|
||||||
|
if (marker.timestamp <= this.currentTime && this.currentTime < marker.endTime) {
|
||||||
|
this.currentMarker = marker;
|
||||||
|
this.currentMarkerIdx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentMarker) {
|
||||||
|
var totalWidth = this.currentMarker.el.offsetWidth;
|
||||||
|
var progress = (this.currentTime - this.currentMarker.timestamp) / (this.currentMarker.endTime - this.currentMarker.timestamp);
|
||||||
|
if (this.currentMarker.hoverx === null) {
|
||||||
|
var pixelWidth = progress * totalWidth;
|
||||||
|
this.currentMarker.fadedProgress.style.width = Math.ceil(pixelWidth) + "px";
|
||||||
|
this.currentMarker.fadedProgress.style.opacity = pixelWidth - Math.floor(pixelWidth);
|
||||||
|
this.currentMarker.progress.style.width = Math.floor(pixelWidth) + "px";
|
||||||
|
} else {
|
||||||
|
this.currentMarker.fadedProgress.style.opacity = 1;
|
||||||
|
this.currentMarker.progress.style.width = Math.floor(Math.min(this.currentMarker.hoverx, progress) * totalWidth) + "px";
|
||||||
|
this.currentMarker.fadedProgress.style.width = Math.floor(Math.max(this.currentMarker.hoverx, progress) * totalWidth) + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentMarker != prevMarker) {
|
||||||
|
if (prevMarker) {
|
||||||
|
prevMarker.el.classList.remove("current");
|
||||||
|
prevMarker.fadedProgress.style.width = "0px";
|
||||||
|
prevMarker.progress.style.width = "0px";
|
||||||
|
prevMarker.hoverx = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentMarker) {
|
||||||
|
this.currentMarker.el.classList.add("current");
|
||||||
|
this.scrollTo = this.currentMarker.el.offsetTop + this.currentMarker.el.offsetHeight/2.0;
|
||||||
|
this.scrollPosition = this.markersContainer.scrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentMarker && this.currentMarker.ref) {
|
||||||
|
this.refsCallback(this.currentMarker.ref);
|
||||||
|
} else if (prevMarker && prevMarker.ref) {
|
||||||
|
this.refsCallback(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Player.prototype.doFrame = function() {
|
||||||
|
var now = performance.now();
|
||||||
|
var delta = (now - this.lastFrameTime) / 1000.0;
|
||||||
|
this.lastFrameTime = now;
|
||||||
|
if (this.playing) {
|
||||||
|
this.currentTime += delta * this.speed;
|
||||||
|
}
|
||||||
|
this.updateProgress();
|
||||||
|
|
||||||
|
if (this.scrollTo >= 0) {
|
||||||
|
var targetPosition = this.scrollTo - this.markersContainer.offsetHeight/2.0;
|
||||||
|
targetPosition = Math.max(0, Math.min(targetPosition, this.markersContainer.scrollHeight - this.markersContainer.offsetHeight));
|
||||||
|
this.scrollPosition += (targetPosition - this.scrollPosition) * 0.1;
|
||||||
|
if (Math.abs(this.scrollPosition - targetPosition) < 1.0) {
|
||||||
|
this.markersContainer.scrollTop = targetPosition;
|
||||||
|
this.scrollTo = -1;
|
||||||
|
} else {
|
||||||
|
this.markersContainer.scrollTop = this.scrollPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nextFrame = requestAnimationFrame(this.doFrame.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
Player.prototype.onYoutubePlayerReady = function() {
|
||||||
|
this.youtubePlayerReady = true;
|
||||||
|
this.markers[this.markers.length-1].endTime = this.youtubePlayer.getDuration();
|
||||||
|
this.updateSize();
|
||||||
|
this.youtubePlayer.setPlaybackQuality("hd1080");
|
||||||
|
if (this.currentTime > 0) {
|
||||||
|
this.currentTime = Math.max(0, Math.min(this.currentTime, this.youtubePlayer.getDuration()));
|
||||||
|
this.youtubePlayer.seekTo(this.currentTime, true);
|
||||||
|
}
|
||||||
|
if (this.shouldPlay) {
|
||||||
|
this.youtubePlayer.playVideo();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Player.prototype.onYoutubePlayerStateChange = function(ev) {
|
||||||
|
if (ev.data == YT.PlayerState.PLAYING) {
|
||||||
|
this.playing = true;
|
||||||
|
this.currentTime = this.youtubePlayer.getCurrentTime();
|
||||||
|
} else if (ev.data == YT.PlayerState.PAUSED || ev.data == YT.PlayerState.BUFFERING) {
|
||||||
|
this.playing = false;
|
||||||
|
this.currentTime = this.youtubePlayer.getCurrentTime();
|
||||||
|
this.updateProgress();
|
||||||
|
} else {
|
||||||
|
this.playing = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Player.prototype.onYoutubePlayerPlaybackRateChange = function(ev) {
|
||||||
|
this.speed = ev.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
events: {
|
||||||
|
"onReady": this.onYoutubePlayerReady.bind(this),
|
||||||
|
"onStateChange": this.onYoutubePlayerStateChange.bind(this),
|
||||||
|
"onPlaybackRateChange": this.onYoutubePlayerPlaybackRateChange.bind(this)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Player.youtubePlayerCount = 0;
|
|
@ -0,0 +1,202 @@
|
||||||
|
/* USER-DEFINED */
|
||||||
|
|
||||||
|
.marker .content {
|
||||||
|
width: 320px;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timecode {
|
||||||
|
font-size: 9px;
|
||||||
|
padding-right: 8px;
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker {
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker:hover > .content {
|
||||||
|
background-color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker:hover .faded .content {
|
||||||
|
background-color: rgba(139, 61, 35, 0.7);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker > .content {
|
||||||
|
background-color: #161616;
|
||||||
|
color: #8A877D;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker.current > .content {
|
||||||
|
color: #B57714;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker .progress .content {
|
||||||
|
background-color: #8B3D23;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MANDATORY */
|
||||||
|
|
||||||
|
.player_container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video_container {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markers_container {
|
||||||
|
overflow-y: scroll;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker .content {
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker .progress {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 0px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CUSTOM PAGE STYLE */
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #222;
|
||||||
|
font-family: sans-serif;
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title > * {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title .episode_name {
|
||||||
|
flex: 1 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title > a {
|
||||||
|
color: rgba(38, 139, 210, 1);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title > a:visited {
|
||||||
|
color: rgba(38, 139, 210, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title > a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title .refs_container {
|
||||||
|
position: relative;
|
||||||
|
transition: box-shadow 800ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
box-shadow: inset 0 0 0 #B57714;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title .refs_container:hover {
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title .refs_container.current {
|
||||||
|
box-shadow: inset 0px 0px 30px #B57714;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title .refs_container .mouse_catcher {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title .refs_container:hover .mouse_catcher {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title .refs_container .refs {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
width: 350px;
|
||||||
|
background-color: black;
|
||||||
|
border: 3px solid #444;
|
||||||
|
border-top: none;
|
||||||
|
z-index: 1;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title .refs_container:hover .refs {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refs .ref {
|
||||||
|
padding: 10px;
|
||||||
|
border-bottom: 1px solid rgb(51, 51, 51);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refs .ref.current {
|
||||||
|
background-color: #8B3D23;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refs .ref:hover {
|
||||||
|
background-color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refs .ref.current:hover {
|
||||||
|
background-color: rgba(139, 61, 35, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.refs .ref:last-child {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refs .ref .timecode {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 55px;
|
||||||
|
font-size: 12px;
|
||||||
|
padding-right: 10px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refs .ref .timecode:hover .time {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refs .ref .source {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #888;
|
||||||
|
line-height: 8px;
|
||||||
|
}
|
Loading…
Reference in New Issue