Skip to content

Commit 7c29a91

Browse files
author
Ahtesham Quraish
committed
address review
1 parent d1d6977 commit 7c29a91

3 files changed

Lines changed: 109 additions & 30 deletions

File tree

frontends/main/src/page-components/TiptapEditor/extensions/node/MediaEmbed/MediaDisplay.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from "react"
22
import styled from "@emotion/styled"
3-
import { isVideoUrl, isHlsVideo } from "./lib"
3+
import { isVideoUrl } from "./lib"
44
import { VideoJsPlayer } from "./VideoJsPlayer"
55

66
const MediaContainer = styled.div(({ theme }) => ({
@@ -56,16 +56,15 @@ export const MediaDisplay = ({ src, caption }: MediaDisplayProps) => {
5656
return (
5757
<MediaContainer>
5858
{isVideoUrl(src) ? (
59-
isHlsVideo(src) ? (
60-
<VideoJsPlayer src={src} caption={caption} />
61-
) : (
62-
// eslint-disable-next-line jsx-a11y/media-has-caption
63-
<video src={src} controls title={caption}>
64-
Your browser does not support the video tag.
65-
</video>
66-
)
59+
<VideoJsPlayer src={src} caption={caption} />
6760
) : (
68-
<iframe src={src} frameBorder="0" allowFullScreen title={caption} />
61+
<iframe
62+
src={src}
63+
frameBorder="0"
64+
allowFullScreen
65+
title={caption}
66+
inert={true}
67+
/>
6968
)}
7069
</MediaContainer>
7170
)

frontends/main/src/page-components/TiptapEditor/extensions/node/MediaEmbed/VideoJsPlayer.tsx

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useRef } from "react"
1+
import React, { useEffect, useRef, useState } from "react"
22
import videojs from "video.js"
33
import type Player from "video.js/dist/types/player"
44
import "video.js/dist/video-js.css"
@@ -8,12 +8,88 @@ interface VideoJsPlayerProps {
88
caption?: string
99
}
1010

11+
const getVideoType = (url: string): string => {
12+
const lowerUrl = url.toLowerCase()
13+
if (lowerUrl.endsWith(".m3u8")) {
14+
return "application/x-mpegURL"
15+
}
16+
if (lowerUrl.endsWith(".mp4")) {
17+
return "video/mp4"
18+
}
19+
// Default to mp4 for other cases
20+
return "video/mp4"
21+
}
22+
23+
const deriveMediaUrls = (
24+
m3u8Url: string,
25+
): { subtitlesUrl: string; posterUrl: string } | null => {
26+
try {
27+
// Extract directory and filename
28+
const lastSlashIndex = m3u8Url.lastIndexOf("/")
29+
if (lastSlashIndex === -1) return null
30+
31+
const directory = m3u8Url.substring(0, lastSlashIndex)
32+
const filename = m3u8Url.substring(lastSlashIndex + 1)
33+
34+
// Extract base name (remove .5M.m3u8 or similar pattern)
35+
// Pattern: video_HLS1.5M.m3u8 -> video_HLS1
36+
const baseMatch = filename.match(/^(.+?)(?:\.\d+[MK])?\.m3u8$/)
37+
if (!baseMatch) return null
38+
39+
const baseName = baseMatch[1]
40+
41+
return {
42+
subtitlesUrl: `${directory}/${baseName}.srt`,
43+
posterUrl: `${directory}/${baseName}_cover.jpeg`,
44+
}
45+
} catch {
46+
return null
47+
}
48+
}
49+
50+
const checkUrlExists = async (url: string): Promise<boolean> => {
51+
try {
52+
const response = await fetch(url, { method: "HEAD" })
53+
return response.ok
54+
} catch {
55+
return false
56+
}
57+
}
58+
1159
export const VideoJsPlayer: React.FC<VideoJsPlayerProps> = ({
1260
src,
1361
caption,
1462
}) => {
1563
const videoRef = useRef<HTMLDivElement>(null)
1664
const playerRef = useRef<Player | null>(null)
65+
const [posterUrl, setPosterUrl] = useState<string | undefined>()
66+
const [subtitlesUrl, setSubtitlesUrl] = useState<string | undefined>()
67+
68+
// Check for related media files (poster and subtitles) for m3u8 videos
69+
useEffect(() => {
70+
const loadRelatedMedia = async () => {
71+
if (!src.toLowerCase().endsWith(".m3u8")) {
72+
return
73+
}
74+
75+
const derivedUrls = deriveMediaUrls(src)
76+
if (!derivedUrls) return
77+
console.log("Derived URLs:", derivedUrls)
78+
// Check if poster exists
79+
const posterExists = await checkUrlExists(derivedUrls.posterUrl)
80+
if (posterExists) {
81+
setPosterUrl(derivedUrls.posterUrl)
82+
}
83+
84+
// Check if subtitles exist
85+
const subtitlesExist = await checkUrlExists(derivedUrls.subtitlesUrl)
86+
if (subtitlesExist) {
87+
setSubtitlesUrl(derivedUrls.subtitlesUrl)
88+
}
89+
}
90+
91+
loadRelatedMedia()
92+
}, [src])
1793

1894
useEffect(() => {
1995
// Make sure Video.js player is only initialized once
@@ -27,7 +103,9 @@ export const VideoJsPlayer: React.FC<VideoJsPlayerProps> = ({
27103
controls: true,
28104
responsive: true,
29105
fluid: true,
106+
aspectRatio: "16:9",
30107
preload: "auto",
108+
poster: posterUrl,
31109
html5: {
32110
vhs: {
33111
// Enable HLS.js integration
@@ -37,18 +115,32 @@ export const VideoJsPlayer: React.FC<VideoJsPlayerProps> = ({
37115
sources: [
38116
{
39117
src,
40-
type: "application/x-mpegURL",
118+
type: getVideoType(src),
41119
},
42120
],
43121
}))
44122

123+
// Add subtitles track if available
124+
if (subtitlesUrl) {
125+
player.addRemoteTextTrack(
126+
{
127+
kind: "captions",
128+
src: subtitlesUrl,
129+
srclang: "en",
130+
label: "English",
131+
default: true,
132+
},
133+
false,
134+
)
135+
}
136+
45137
// Error handling
46138
player.on("error", () => {
47139
const error = player.error()
48140
console.error("Video.js error:", error)
49141
})
50142
}
51-
}, [src])
143+
}, [src, posterUrl, subtitlesUrl])
52144

53145
// Dispose the Video.js player when the component unmounts
54146
useEffect(() => {

frontends/main/src/page-components/TiptapEditor/extensions/node/MediaEmbed/lib.ts

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,6 @@ export function isVideoUrl(url: string): boolean {
1010
}
1111
}
1212

13-
export function isHlsVideo(url: string): boolean {
14-
try {
15-
const parsed = new URL(url)
16-
return parsed.pathname.toLowerCase().endsWith(".m3u8")
17-
} catch {
18-
return false
19-
}
20-
}
21-
2213
export function convertToEmbedUrl(url: string): string | null {
2314
let parsed: URL
2415

@@ -32,9 +23,7 @@ export function convertToEmbedUrl(url: string): string | null {
3223
// --- MIT LEARN MP4 VIDEOS ---
3324
if (
3425
hostname ===
35-
new URL(process.env.NEXT_PUBLIC_ORIGIN || "https://learn.mit.edu")
36-
.hostname &&
37-
parsed.pathname.toLowerCase().endsWith(".mp4")
26+
new URL(process.env.NEXT_PUBLIC_ORIGIN || "https://learn.mit.edu").hostname
3827
) {
3928
return url // Return the URL as-is for video element
4029
}
@@ -43,11 +32,10 @@ export function convertToEmbedUrl(url: string): string | null {
4332

4433
if (
4534
hostname ===
46-
new URL(
47-
process.env.NEXT_PUBLIC_CLOUDFRONT_DOMAIN ||
48-
"https://d3tsb3m56iwvoq.cloudfront.net",
49-
).hostname &&
50-
parsed.pathname.toLowerCase().endsWith(".m3u8")
35+
new URL(
36+
process.env.NEXT_PUBLIC_CLOUDFRONT_DOMAIN ||
37+
"https://d3tsb3m56iwvoq.cloudfront.net",
38+
).hostname
5139
) {
5240
return url // Return the URL as-is for video element
5341
}

0 commit comments

Comments
 (0)