Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

adds dynamic loading to video-test #698

Merged
merged 2 commits into from
Jun 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const withMDX = require(`@next/mdx`)({
},
})

const withTM = require('next-transpile-modules')(['unist-util-visit'], {
debug: true,
})

const searchUrlRoot = `/q`

checkEnv({
Expand Down Expand Up @@ -369,6 +373,7 @@ module.exports = withPlugins(
enabled: process.env.ANALYZE === `true`,
}),
withSvgr,
withTM,
withImages(),
withMDX({
pageExtensions: [`ts`, `tsx`, `mdx`],
Expand Down
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@emotion/css": "^11.1.3",
"@emotion/react": "^11.1.5",
"@emotion/server": "^11.0.0",
"@mdx-js/mdx": "^2.0.0-next.9",
"@mdx-js/react": "^1.6.22",
"@reach/dialog": "^0.15.0",
"@reach/disclosure": "^0.15.0",
Expand All @@ -53,6 +54,7 @@
"@tailwindcss/ui": "^0.7.2",
"@tanem/react-nprogress": "^3.0.66",
"@tippyjs/react": "^4.2.5",
"@types/isomorphic-fetch": "^0.0.35",
"@types/mixpanel-browser": "^2.35.6",
"@types/react-scroll": "^1.8.2",
"@videojs/http-streaming": "^2.8.2",
Expand Down Expand Up @@ -82,11 +84,14 @@
"honeycomb-beeline": "^2.7.0",
"humanize-list": "^1.0.1",
"husky": "4.3.8",
"isomorphic-fetch": "^3.0.0",
"js-cookie": "^2.2.1",
"libhoney": "^2.3.0",
"load-egghead-notes-mdx-url": "^0.1.0",
"load-script": "^1.0.0",
"lodash": "^4.17.21",
"logrocket": "^1.2.3",
"mdast-util-to-markdown": "^0.6.5",
"mdx-bundler": "^4.0.1",
"mixpanel-browser": "^2.41.0",
"next": "^10.2.3",
Expand All @@ -96,6 +101,7 @@
"next-seo": "^4.24.0",
"next-svgr": "^0.0.2",
"next-themes": "^0.0.14",
"next-transpile-modules": "^7.3.0",
"nextjs-sitemap-generator": "^1.3.1",
"pluralize": "^8.0.0",
"prism-react-renderer": "^1.2.1",
Expand Down Expand Up @@ -126,8 +132,11 @@
"tailwindcss": "^2.1.4",
"tincanjs": "^0.50.0",
"ts-toolbelt": "^9.6.0",
"unist-util-visit": "^2.0.3",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an older version of this library

syntax-tree/unist-util-visit#23

apparently the entire unified ecosystem is making a principled stance on using ESM modules which is incompatible with Next.js (and most of the rest of the world) at this time. This causes a massive headache and debugging nightmare to implement, but it's a principled stance and open source so you can only complain about it so much I guess.

Kind of sucks when you just want to make stuff, particularly since it'd be relatively simple to support commonjs syntax for now.

This issue, in fact, has caused mdx to be forked :trollface:

I don't like it.

Copy link

@ChristianMurphy ChristianMurphy Aug 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainer of unified and mdx here, sorry you ran into a spot of trouble.
This characterization is unfair on a few fronts:

  1. It is not a libraries fault if a build tool does not support standard JavaScript syntax.
  2. Next has been working to add ESM support, and merged add support for esm externals vercel/next.js#27069 to address this specifically
  3. MDX was forked over a disagreement on how to handle presentation context and runtime Remove runtime renderer, still support MDXProvider mdx-js/mdx#1425 and An alternative “shortcode” (mdxType alternative) proposal mdx-js/mdx#1385, it has nothing to do with ESM
  4. Unified had experimented with adding a CJS fallback to support older versions of CRA and Next, but due to some quirks of webpack 4, it actually made things harder for adopters than pure ESM 🤷‍♂️

If you have questions on ESM or MDX in general, feel free to reach out to the MDX community https://github.com/mdx-js/mdx/discussions and/or unified community https://github.com/unifiedjs/unified/discussions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this detailed list of corrections.

"vfile": "^4.2.1",
"video.js": "^7.11.8",
"xstate": "^4.18.0",
"yaml": "^1.10.2",
"yup": "^0.32.9"
},
"devDependencies": {
Expand Down
9 changes: 2 additions & 7 deletions src/components/player/cue-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const NoteCue: React.FC<any> = ({
const visible =
player.activeMetadataTrackCues.includes(cue) && !player.seeking
const startPosition = `${(cue.startTime / duration) * 100}%`
const note = JSON.parse(cue.text)
const note = cue.text
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not using JSON, which might be something we test for in the future. We can just not needed for the current approach.


React.useEffect(() => {
if (visible) {
Expand All @@ -120,12 +120,7 @@ const NoteCue: React.FC<any> = ({
appendTo="parent"
content={
<div className="p-2">
{note.title && (
<span className="pb-2 font-semibold inline-block">
{note.title}
</span>
)}
<div className="line-clamp-2">{note.description}</div>
<div className="line-clamp-2">{note}</div>
{/* <ReactMarkdown className="prose prose-sm dark:prose-dark max-w-none"> */}
{/* {note.description}</div> */}
{/* {truncate(note.description, {length: 220, separator: '...'})} */}
Expand Down
1 change: 1 addition & 0 deletions src/lib/lessons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export async function loadBasicLesson(slug: string) {
description
free_forever
media_url
hls_url
thumb_url
subtitles_url
path
Expand Down
89 changes: 89 additions & 0 deletions src/pages/api/github-load-notes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {NextApiRequest, NextApiResponse} from 'next'
import mdx from '@mdx-js/mdx'
import VFile from 'vfile'
import visit from 'unist-util-visit'
import fetch from 'isomorphic-fetch'
import toMarkdown from 'mdast-util-to-markdown'

const loadGithubNotes = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET' && req.query.url) {
const test = await loadNotesFromUrl(req.query.url as string)

res.status(200).json(test)
} else {
res.status(200).end()
}
}

export default loadGithubNotes

export async function loadNotesFromUrl(url: string) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this loads mdx from a url and parses it looking for timestamp components which creates JS objects that are used to make a webvtt.

it's currently very fragile! works though

const result = await fetch(url)

const text = await result.text()

// @ts-ignore
const file = new VFile(text.trimStart())

function extractNotes() {
return function transformer(tree: any, file: any) {
file.data.notes = []
visit(tree, 'mdxJsxFlowElement', function visitor(node) {
file.data.notes.push(node)
})
// remove(tree, 'mdxJsxFlowElement')
}
}

// @ts-ignore
const mdxCompiler = mdx.createCompiler({
remarkPlugins: [extractNotes],
})

return mdxCompiler.process(file).then((file: any) => {
function convertHMS(timeString: string) {
let seconds = 0

const arr = timeString.split(':')

if (arr.length === 3) {
seconds = Number(arr[0]) * 3600 + Number(arr[1]) * 60 + +Number(arr[2])
} else if (arr.length === 2) {
seconds = Number(arr[0]) * 60 + +Number(arr[1])
} else {
throw new Error(`can't parse ${timeString}`)
}
return seconds
}

const notes = file.data.notes.map((note: any) => {
const attributes = note.attributes.reduce((acc: any, attribute: any) => {
return {
...acc,
[attribute.name]: convertHMS(attribute.value),
}
}, {})
note.type = 'root'
const contents = toMarkdown(note)
return {
text: contents.trim(),
...attributes,
}
})

let vtt = `WEBVTT\n\n`

notes.forEach((note: any) => {
vtt =
vtt +
`note\n${new Date(note.start * 1000)
.toISOString()
.substr(11, 8)}.000 --> ${new Date(note.end * 1000)
.toISOString()
.substr(11, 8)}.000
${note.text}\n\n`
})

return vtt
})
}
66 changes: 31 additions & 35 deletions src/pages/video-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ import CueBar from 'components/player/cue-bar'
import ControlBarDivider from 'components/player/control-bar-divider'
import {useEggheadPlayerPrefs} from 'components/EggheadPlayer/use-egghead-player'
import {Element, scroller} from 'react-scroll'
import {VideoResource} from '../types'
import {loadBasicLesson} from '../lib/lessons'
import {loadNotesFromUrl} from './api/github-load-notes'

type VideoResource = {hls_url: string; subtitlesUrl: string; poster: string}

const EggheadPlayer: React.FC<{videoResource: VideoResource}> = ({
videoResource,
}) => {
const EggheadPlayer: React.FC<{
videoResource: VideoResource
notesUrl: string
}> = ({videoResource, notesUrl}) => {
const playerContainer = React.useRef<any>()
const playerPrefs = useEggheadPlayerPrefs()
const {player} = usePlayer()
Expand Down Expand Up @@ -69,23 +71,18 @@ const EggheadPlayer: React.FC<{videoResource: VideoResource}> = ({
autoplay
crossOrigin="anonymous"
className="font-sans"
poster={videoResource.poster}
poster={videoResource.thumb_url}
>
<BigPlayButton position="center" />
<HLSSource isVideoChild src={videoResource.hls_url} />
<track
src={videoResource.subtitlesUrl}
src={videoResource.subtitles_url}
kind="subtitles"
srcLang="en"
label="English"
default
/>
<track
id="notes"
src="https://gist.githubusercontent.com/joelhooks/bd3c1d68cb5a67adfcd6c035200d1fde/raw/aa7060f584e04db26c5fa6b464bf2058ed6f6e93/notes.vtt"
kind="metadata"
label="notes"
/>
<track id="notes" src={notesUrl} kind="metadata" label="notes" />
<CueBar key="cue-bar" order={6.0} scroller={scroller} />

<ControlBar disableDefaultControls autoHide={false}>
Expand Down Expand Up @@ -161,7 +158,7 @@ const NotesTabContent: React.FC<{cues: VTTCue[]}> = ({cues}) => {
return disabled ? null : (
<div>
{cues.map((cue: VTTCue) => {
const note = JSON.parse(cue.text)
const note = cue.text
const active = player.activeMetadataTrackCues.includes(cue)
return (
<div key={cue.startTime}>
Expand All @@ -175,14 +172,9 @@ const NotesTabContent: React.FC<{cues: VTTCue[]}> = ({cues}) => {
},
)}
>
{note.title && (
<div className="text-base font-semibold text-black dark:text-white pb-3">
{note.title}
</div>
)}
{note.description && (
{note && (
<ReactMarkdown className="leading-normal prose-sm prose dark:prose-dark">
{note.description}
{note}
</ReactMarkdown>
)}
{cue.startTime && (
Expand Down Expand Up @@ -232,34 +224,38 @@ const PlayerContainer: React.ForwardRefExoticComponent<any> = React.forwardRef<
)
})

const VideoTest: React.FC<{videoResource: VideoResource}> = ({
const VideoTest: React.FC<{videoResource: VideoResource; notesUrl: string}> = ({
videoResource,
notesUrl,
}) => {
return (
<PlayerProvider>
<EggheadPlayer videoResource={videoResource} />
<EggheadPlayer videoResource={videoResource} notesUrl={notesUrl} />
</PlayerProvider>
)
}

export default VideoTest

export const getServerSideProps: GetServerSideProps = async function ({query}) {
const videoResource = {
id: 'video',
name: 'get started with react',
title: 'Create a User Interface with Vanilla JavaScript and DOM',
poster:
'https://dcv19h61vib2d.cloudfront.net/thumbs/react-v2-01-create-a-user-interface-with-vanilla-javascript-and-dom-rJShvuIrI/react-v2-01-create-a-user-interface-with-vanilla-javascript-and-dom-rJShvuIrI.jpg',
hls_url:
'https://d2c5owlt6rorc3.cloudfront.net/react-v2-01-create-a-user-interface-with-vanilla-javascript-and-dom-rJShvuIrI/hls/react-v2-01-create-a-user-interface-with-vanilla-javascript-and-dom-rJShvuIrI.m3u8',
subtitlesUrl:
'https://app.egghead.io/api/v1/lessons/react-create-a-user-interface-with-vanilla-javascript-and-dom/subtitles',
}
const lessonNotes = {
'react-a-beginners-guide-to-react-introduction':
'https://cdn.jsdelivr.net/gh/eggheadio/eggheadio-course-notes/the-beginners-guide-to-react/notes/00-react-a-beginners-guide-to-react-introduction.md',
}

export const getServerSideProps: GetServerSideProps = async function ({
req,
res,
params,
}) {
const lesson = 'react-a-beginners-guide-to-react-introduction'
const videoResource: VideoResource = (await loadBasicLesson(
lesson,
)) as VideoResource

return {
props: {
videoResource,
notesUrl: `/api/github-load-notes?url=${lessonNotes[lesson]}`,
},
}
}
10 changes: 8 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ export type CardResource = Resource & {
}

export type LessonResource = Resource & {
dash_url: string
hls_url: string
media_url: string
thumb_url: string
lesson_view_url: string
id: string | number
tags: any[]
Expand All @@ -31,6 +30,13 @@ export type LessonResource = Resource & {
instructor: any
}

export type VideoResource = LessonResource & {
dash_url: string
hls_url: string
thumb_url: string
subtitles_url: string
}

export type PodcastResource = Resource & {
duration: number
episode_number: number
Expand Down
Loading