diff --git a/hmml_to_html/hmml.a b/hmml_to_html/hmml.a new file mode 100644 index 0000000..0d4e751 Binary files /dev/null and b/hmml_to_html/hmml.a differ diff --git a/hmml_to_html/hmml_to_html.c b/hmml_to_html/hmml_to_html.c new file mode 100644 index 0000000..f61e758 --- /dev/null +++ b/hmml_to_html/hmml_to_html.c @@ -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 +#include +#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, +"\n" +" \n" +" \n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" %s\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, +"
\n" +" References ▼\n" +"
\n" +"
\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, +" \n" +" (%s)\n" +" \n" +"
%s
\n" +"
%s
\n" +"
\n" +"
\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, +"
\n" +"
\n" +" Annotator: %s\n" +"
\n" +"
\n" +"
\n" +"
\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, "
\n", TimecodeToSeconds(HMML.annotations[AnnotationIndex].time), DataRef); + ++DataRef; + } + else + { + sprintf(Working.Location, "
\n", TimecodeToSeconds(HMML.annotations[AnnotationIndex].time)); + } + + Working.Ptr = Working.Location; + while(*Working.Ptr) + { + *Out.Ptr++ = *Working.Ptr++; + } + + sprintf(Working.Location, "
%s%s
\n" + "
\n" + "
%s%s
\n" + "
\n" + "
\n" + "
%s%s
\n" + "
\n" + "
\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, "
\n" + "
\n" + "\n" + "\n" + "\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); +} diff --git a/hmml_to_html/hmmlib.h b/hmml_to_html/hmmlib.h new file mode 100644 index 0000000..0841fa3 --- /dev/null +++ b/hmml_to_html/hmmlib.h @@ -0,0 +1,87 @@ +#ifndef HMML_H_ +#define HMML_H_ +#include +#include +#include +#include + +// 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 diff --git a/hmml_to_html/out.html b/hmml_to_html/out.html new file mode 100644 index 0000000..c972ff3 --- /dev/null +++ b/hmml_to_html/out.html @@ -0,0 +1,287 @@ + + + + + + + + + + +
+
+
+
+
0:07Set the stage for the day, looking at two's complement and sign extension
+
+
0:07Set the stage for the day, looking at two's complement and sign extension
+
+
+
0:07Set the stage for the day, looking at two's complement and sign extension
+
+
+ +
+
0:41"It seems like, as you get older, the years start to go by like weeks"
+
+
0:41"It seems like, as you get older, the years start to go by like weeks"
+
+
+
0:41"It seems like, as you get older, the years start to go by like weeks"
+
+
+ +
+
1:01Recommend Code by Charles Petzold
+
+
1:01Recommend Code by Charles Petzold
+
+
+
1:01Recommend Code by Charles Petzold
+
+
+ +
+
5:03Using an electromagnet with a telegraph to communicate in Morse code
+
+
5:03Using an electromagnet with a telegraph to communicate in Morse code
+
+
+
5:03Using an electromagnet with a telegraph to communicate in Morse code
+
+
+ +
+
9:56Adder circuit
+
+
9:56Adder circuit
+
+
+
9:56Adder circuit
+
+
+ +
+
18:02Research logic gates
+
+
18:02Research logic gates
+
+
+
18:02Research logic gates
+
+
+ +
+
19:27XOR
+
+
19:27XOR
+
+
+
19:27XOR
+
+
+ +
+
25:38Redraw the tables
+
+
25:38Redraw the tables
+
+
+
25:38Redraw the tables
+
+
+ +
+
28:01What an XOR gate will do when the carry is off
+
+
28:01What an XOR gate will do when the carry is off
+
+
+
28:01What an XOR gate will do when the carry is off
+
+
+ +
+
33:27Propagating the carry through our circuit
+
+
33:27Propagating the carry through our circuit
+
+
+
33:27Propagating the carry through our circuit
+
+
+ +
+
36:58Consult Wikipedia for a ripple carry adder
+
+
36:58Consult Wikipedia for a ripple carry adder
+
+
+
36:58Consult Wikipedia for a ripple carry adder
+
+
+ +
+
38:58This circuit does 1 bit of the adder computation
+
+
38:58This circuit does 1 bit of the adder computation
+
+
+
38:58This circuit does 1 bit of the adder computation
+
+
+ +
+
41:25Subtraction circuit
+
+
41:25Subtraction circuit
+
+
+
41:25Subtraction circuit
+
+
+ +
+
44:46Agreeing on an encoding in order to communicate useful information
+
+
44:46Agreeing on an encoding in order to communicate useful information
+
+
+
44:46Agreeing on an encoding in order to communicate useful information
+
+
+ +
+
46:33"You can quote whatever you like, Miblo"
+
+
46:33"You can quote whatever you like, Miblo"
+
+
+
46:33"You can quote whatever you like, Miblo"
+
+
+ +
+
48:06Start with looking at ones' complement
+
+
48:06Start with looking at ones' complement
+
+
+
48:06Start with looking at ones' complement
+
+
+ +
+
54:28Go to two's complement
+
+
54:28Go to two's complement
+
+
+
54:28Go to two's complement
+
+
+ +
+
59:12Using both ones' complement and two's complement to enable our circuit to perform subtraction
+
+
59:12Using both ones' complement and two's complement to enable our circuit to perform subtraction
+
+
+
59:12Using both ones' complement and two's complement to enable our circuit to perform subtraction
+
+
+ +
+
1:04:43GCC's "Statements and Declarations in Expressions" Extension
+
+
1:04:43GCC's "Statements and Declarations in Expressions" Extension
+
+
+
1:04:43GCC's "Statements and Declarations in Expressions" Extension
+
+
+ +
+
1:06:12That's all for now
+
+
1:06:12That's all for now
+
+
+
1:06:12That's all for now
+
+
+ +
+
+ + + diff --git a/hmml_to_html/player.js b/hmml_to_html/player.js new file mode 100644 index 0000000..a5475ae --- /dev/null +++ b/hmml_to_html/player.js @@ -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; diff --git a/hmml_to_html/style.css b/hmml_to_html/style.css new file mode 100644 index 0000000..aa490df --- /dev/null +++ b/hmml_to_html/style.css @@ -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; +}