diff --git a/viewer/public/data/MIT Regressions intro metadata.vtt b/viewer/public/data/MIT Regressions intro metadata.vtt
index ba9e4e6..1b0301f 100644
--- a/viewer/public/data/MIT Regressions intro metadata.vtt
+++ b/viewer/public/data/MIT Regressions intro metadata.vtt
@@ -4,5 +4,6 @@ Language: en
NOTE We could have a separation between MUSIC SOURCE, VIDEO SOURCE, NARRATION SOURCE, COMMENTARY, and TRANSCRIPT.
+NOTE ["The Vanishing American Family" by ScubaZ]
00:00:01.550 --> 00:01:15.448
-["The Vanishing American Family" by ScubaZ]
\ No newline at end of file
+{"keywords_alt": "", "gpspoints": {"gps_zoom": "11", "gps_text_alt": "", "gps_text": "Stockholm, Sweden", "gps": "59.3388502,18.0616981"}, "synopsis": "", "subjects": "", "hyperlinks": {"hyperlink_text_alt": "", "hyperlink_text": "", "hyperlink": ""}, "synopsis_alt": "", "title": "The Vanishing American Family", "keywords": "ScubaZ", "title_alt": "", "subjects_alt": ""}
\ No newline at end of file
diff --git a/viewer/src/components/Player.tsx b/viewer/src/components/Player.tsx
index d156fa9..265dec2 100644
--- a/viewer/src/components/Player.tsx
+++ b/viewer/src/components/Player.tsx
@@ -2,7 +2,7 @@ import dynamic from "next/dynamic";
const ReactPlayer = dynamic(() => import("react-player/lazy"), { ssr: false });
import { useState, useRef, useEffect } from "react";
import { useRouter } from 'next/router';
-import { Player as WebVttPlayer } from "webvtt-player";
+import WebVttPlayer from "./WebVttPlayer/WebVttPlayer";
// functional component PlayerReact that uses ReactPlayer
// TODO: parameterize video source and VTT source with props (general spec for 3rd party use!). must define spec.
@@ -39,12 +39,11 @@ export default function Player() {
/>
- {/* div "control-panel" has multiple buttons for displaying settings on WebVttPlayer */}
diff --git a/viewer/src/components/WebVttPlayer/Metadata.module.css b/viewer/src/components/WebVttPlayer/Metadata.module.css
new file mode 100644
index 0000000..e69de29
diff --git a/viewer/src/components/WebVttPlayer/Metadata.tsx b/viewer/src/components/WebVttPlayer/Metadata.tsx
new file mode 100644
index 0000000..0c1091e
--- /dev/null
+++ b/viewer/src/components/WebVttPlayer/Metadata.tsx
@@ -0,0 +1,34 @@
+import React, { Component } from 'react'
+import MetadataPoint from './MetadataPoint'
+
+class Metadata extends Component
{
+
+ render() {
+ const lines = []
+ if (this.props.track && this.props.track.cues) {
+ for (let i = 0; i < this.props.track.cues.length; i++) {
+ lines.push(
+
+ )
+ }
+ }
+ return (
+
+ {lines}
+
+ )
+ }
+
+}
+
+type MetadataProps = {
+ url: string,
+ track: TextTrack,
+ seek: (time: number) => void
+}
+
+export default Metadata
\ No newline at end of file
diff --git a/viewer/src/components/WebVttPlayer/MetadataPoint.module.css b/viewer/src/components/WebVttPlayer/MetadataPoint.module.css
new file mode 100644
index 0000000..e69de29
diff --git a/viewer/src/components/WebVttPlayer/MetadataPoint.tsx b/viewer/src/components/WebVttPlayer/MetadataPoint.tsx
new file mode 100644
index 0000000..4a36ef2
--- /dev/null
+++ b/viewer/src/components/WebVttPlayer/MetadataPoint.tsx
@@ -0,0 +1,113 @@
+import React, { Component } from 'react'
+// import './MetadataPoint.css'
+
+
+type MetadataPointProps = {
+ cue: VTTCue
+ active: boolean
+ seek: (time: number) => void
+}
+
+class MetadataPoint extends Component {
+
+ constructor(props: MetadataPointProps) {
+ super(props)
+ this.onClick = this.onClick.bind(this)
+ }
+
+ render() {
+ const data = JSON.parse(this.props.cue.text)
+ const titleAlt = data.title_alt
+ ? {data.title_alt}
+ : ""
+ const synopsis = data.synopsis
+ ?
+ Synopsis
+ {data.synopsis}
+
+ : ""
+ const synopsisAlt = data.synopsis_alt
+ ? {data.synopsis_alt}
+ : ""
+ const keywords = data.keywords
+ ?
+ Keywords:
+ {data.keywords}
+
+ : ""
+ const keywordsAlt = data.keywords_alt
+ ?
+ Alternative Keywords:
+ {data.keywords_alt}
+
+ : ""
+ const subjects = data.subjects
+ ?
+ Subjects:
+ {data.subjects}
+
+ : ""
+ const subjectsAlt = data.subjects_alt
+ ?
+ Alternative Subjects:
+ {data.subjects_alt}
+
+ : ""
+ const gpsLink = data.gpspoints.gps
+ ?
+ : ""
+ const hyperlinks = data.hyperlinks.hyperlink_text
+ ?
+ : ""
+ return (
+
+
+ [{this.startTime()}]
+
+
+
{data.title}
+ {titleAlt}
+ {synopsis}
+ {synopsisAlt}
+ {keywords}
+ {keywordsAlt}
+ {subjects}
+ {subjectsAlt}
+ {gpsLink}
+ {hyperlinks}
+
+
+ )
+ }
+
+ onClick() {
+ this.props.seek(this.props.cue.startTime)
+ }
+
+ startTime() {
+ return this.formatSeconds(this.props.cue.startTime)
+ }
+
+ formatSeconds(t) {
+ let mins = Math.floor(t / 60)
+ if (mins < 10) {
+ mins = `0${mins}`
+ }
+
+ let secs = Math.floor(t % 60)
+ if (secs < 10) {
+ secs = `0${secs}`
+ }
+
+ return `${mins}:${secs}`
+ }
+
+}
+
+export default MetadataPoint
\ No newline at end of file
diff --git a/viewer/src/components/WebVttPlayer/Search.module.css b/viewer/src/components/WebVttPlayer/Search.module.css
new file mode 100644
index 0000000..8c57aab
--- /dev/null
+++ b/viewer/src/components/WebVttPlayer/Search.module.css
@@ -0,0 +1,25 @@
+.search {
+ border-top: thin solid #ccc;
+ padding: 5px 5px 5px 5px;
+ font-size: 14pt;
+ margin-top: 5px;
+ }
+
+ .search .container {
+ border: thin solid #ccc;
+ }
+
+ .search input {
+ font-size: 12pt;
+ border: none;
+ width: 90%;
+ }
+
+ .search input:focus {
+ outline: none;
+ }
+
+ .search .icon {
+ padding-left: 5px;
+ padding-right: 5px;
+ }
\ No newline at end of file
diff --git a/viewer/src/components/WebVttPlayer/Search.tsx b/viewer/src/components/WebVttPlayer/Search.tsx
new file mode 100644
index 0000000..599ba8f
--- /dev/null
+++ b/viewer/src/components/WebVttPlayer/Search.tsx
@@ -0,0 +1,24 @@
+import React, { FunctionComponent } from "react" // TODO: fix warning
+import styles from "./Search.module.css"
+
+const Search: FunctionComponent = ({ query, updateQuery }) => {
+ return (
+
+
+ 🔍
+ updateQuery(e.target.value)} />
+
+
+ )
+}
+
+// set prop types using TypeScript
+type SearchProps = {
+ query: string,
+ updateQuery: (query: string) => void
+}
+
+
+export default Search
\ No newline at end of file
diff --git a/viewer/src/components/WebVttPlayer/Transcript.module.css b/viewer/src/components/WebVttPlayer/Transcript.module.css
new file mode 100644
index 0000000..e69de29
diff --git a/viewer/src/components/WebVttPlayer/Transcript.tsx b/viewer/src/components/WebVttPlayer/Transcript.tsx
new file mode 100644
index 0000000..70e82ce
--- /dev/null
+++ b/viewer/src/components/WebVttPlayer/Transcript.tsx
@@ -0,0 +1,37 @@
+import React, { Component } from 'react'
+import TranscriptLine from './TranscriptLine'
+// import './Track.css' // currently exists in global instead. gotta consolidate this
+
+class Transcript extends Component {
+
+ render() {
+ const lines = []
+ if (this.props.track && this.props.track.cues) {
+ for (let i = 0; i < this.props.track.cues.length; i++) {
+ lines.push(
+
+ )
+ }
+ }
+ return (
+
+ {lines}
+
+ )
+ }
+
+}
+
+type TranscriptProps = {
+ track: TextTrack,
+ url: string,
+ seek: (time: number) => void,
+ query: string
+}
+
+export default Transcript
\ No newline at end of file
diff --git a/viewer/src/components/WebVttPlayer/TranscriptLine.module.css b/viewer/src/components/WebVttPlayer/TranscriptLine.module.css
new file mode 100644
index 0000000..e69de29
diff --git a/viewer/src/components/WebVttPlayer/TranscriptLine.tsx b/viewer/src/components/WebVttPlayer/TranscriptLine.tsx
new file mode 100644
index 0000000..2e190e9
--- /dev/null
+++ b/viewer/src/components/WebVttPlayer/TranscriptLine.tsx
@@ -0,0 +1,79 @@
+import React, { Component } from 'react'
+import './TranscriptLine.module.css'
+
+class TranscriptLine extends Component {
+
+ constructor(props: TranscriptLineProps) {
+ super(props)
+ this.state = {
+ isActive: false
+ }
+ this.props.cue.onenter = this.onEnter.bind(this)
+ this.props.cue.onexit = this.onExit.bind(this)
+ this.onClick = this.onClick.bind(this)
+ }
+
+ render() {
+ let style = ''
+ if (this.props.query && this.props.cue.text.match(new RegExp(this.props.query, 'i'))) {
+ style = 'match'
+ } else if (this.state.isActive) {
+ style = 'active'
+ }
+
+ // note: dangerouslySetInnerHTML is used because the text may contain HTML
+ return (
+
+
+ [{this.startTime()} - {this.endTime()}]
+
+
+
+ )
+ }
+
+ onEnter() {
+ this.setState({isActive: true})
+ }
+
+ onExit() {
+ this.setState({isActive: false})
+ }
+
+ onClick() {
+ this.props.seek(this.props.cue.startTime)
+ }
+
+ startTime() {
+ return this.formatSeconds(this.props.cue.startTime)
+ }
+
+ endTime() {
+ return this.formatSeconds(this.props.cue.endTime)
+ }
+
+ formatSeconds(t) {
+ let mins = Math.floor(t / 60)
+ if (mins < 10) {
+ mins = `0${mins}`
+ }
+
+ let secs = Math.floor(t % 60)
+ if (secs < 10) {
+ secs = `0${secs}`
+ }
+
+ return `${mins}:${secs}`
+ }
+
+}
+
+type TranscriptLineProps = {
+ cue: TextTrackCue,
+ seek: (time: number) => void,
+ query: string
+}
+
+export default TranscriptLine
\ No newline at end of file
diff --git a/viewer/src/components/WebVttPlayer/WebVttPlayer.module.css b/viewer/src/components/WebVttPlayer/WebVttPlayer.module.css
new file mode 100644
index 0000000..e69de29
diff --git a/viewer/src/components/WebVttPlayer/WebVttPlayer.tsx b/viewer/src/components/WebVttPlayer/WebVttPlayer.tsx
new file mode 100644
index 0000000..a70f740
--- /dev/null
+++ b/viewer/src/components/WebVttPlayer/WebVttPlayer.tsx
@@ -0,0 +1,120 @@
+import React, { Component } from 'react'
+import Transcript from './Transcript'
+import Metadata from './Metadata'
+import Search from './Search'
+import './WebVttPlayer.module.css'
+
+// type for props
+type WebVttProps = {
+ audio: string,
+ transcript: string,
+ metadata: string,
+ preload: boolean
+}
+
+
+class WebVttPlayer extends Component {
+ metatrack: React.RefObject
+ audio: React.RefObject
+ track: React.RefObject
+
+
+ constructor(props: WebVttProps) {
+ super(props)
+ this.state = {
+ loaded: false,
+ currentTime: 0,
+ query: ''
+ }
+
+ this.track = React.createRef()
+ this.metatrack = React.createRef()
+ this.audio = React.createRef()
+
+ this.onLoaded = this.onLoaded.bind(this)
+ this.seek = this.seek.bind(this)
+ this.checkIfLoaded = this.checkIfLoaded.bind(this)
+ this.updateQuery = this.updateQuery.bind(this)
+ }
+
+ componentDidMount() {
+ this.checkIfLoaded()
+ }
+
+ render() {
+ let track = null
+ let metatrack = null
+ if (this.state.loaded) {
+ track = this.track.current.track
+ metatrack = this.metatrack.current.track
+ }
+ const preload = this.props.preload ? "true" : "false"
+ const metadata = this.props.metadata
+ ?
+ : ""
+
+ return (
+
+ )
+ }
+
+ onLoaded() {
+ this.setState({ loaded: true })
+ }
+
+ checkIfLoaded(tries = 0) {
+ tries += 1
+ const e = this.track.current
+ if (e && e.track && e.track.cues && e.track.cues.length > 0) {
+ this.onLoaded()
+ } else if (!this.state.loaded) {
+ const wait = 25 * Math.pow(tries, 2)
+ setTimeout(this.checkIfLoaded, wait, tries)
+ }
+ }
+
+ seek(secs: number) {
+ this.audio.current.currentTime = secs
+ this.audio.current.play()
+ }
+
+ updateQuery(query: string) {
+ this.setState({ query: query })
+ }
+
+}
+
+export default WebVttPlayer
\ No newline at end of file
diff --git a/viewer/src/styles/globals.css b/viewer/src/styles/globals.css
index 4c71417..1a82fef 100644
--- a/viewer/src/styles/globals.css
+++ b/viewer/src/styles/globals.css
@@ -20,7 +20,7 @@
/* tailwind flex-CUSTOMNUMBER */
/* @apply h-96 p-[15px] overflow-y-scroll flex-[6]; */
- @apply text-orange-200;
+ /* @apply text-orange-200; /* this works lol */
}
}