feat(reader): embed external videos in /r/ using react-player; add vimeo/dailymotion detection

This commit is contained in:
Gigi
2025-10-13 17:25:34 +02:00
parent da67135f5e
commit a26c7497b5
2 changed files with 34 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
import React, { useMemo, useState, useEffect, useRef } from 'react' import React, { useMemo, useState, useEffect, useRef } from 'react'
import ReactPlayer from 'react-player'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm' import remarkGfm from 'remark-gfm'
import rehypeRaw from 'rehype-raw' import rehypeRaw from 'rehype-raw'
@@ -29,6 +30,7 @@ import {
} from '../services/reactionService' } from '../services/reactionService'
import AuthorCard from './AuthorCard' import AuthorCard from './AuthorCard'
import { faBooks } from '../icons/customIcons' import { faBooks } from '../icons/customIcons'
import { classifyUrl } from '../utils/helpers'
interface ContentPanelProps { interface ContentPanelProps {
loading: boolean loading: boolean
@@ -136,6 +138,7 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
// Determine if we're on a nostr-native article (/a/) or external URL (/r/) // Determine if we're on a nostr-native article (/a/) or external URL (/r/)
const isNostrArticle = selectedUrl && selectedUrl.startsWith('nostr:') const isNostrArticle = selectedUrl && selectedUrl.startsWith('nostr:')
const isExternalVideo = !isNostrArticle && !!selectedUrl && ['youtube', 'video'].includes(classifyUrl(selectedUrl).type)
// Get article links for menu // Get article links for menu
const getArticleLinks = () => { const getArticleLinks = () => {
@@ -312,7 +315,31 @@ const ContentPanel: React.FC<ContentPanelProps> = ({
highlights={relevantHighlights} highlights={relevantHighlights}
highlightVisibility={highlightVisibility} highlightVisibility={highlightVisibility}
/> />
{markdown || html ? ( {isExternalVideo ? (
<>
<div className="reader-video">
<ReactPlayer url={selectedUrl as string} controls width="100%" height="60vh" />
</div>
{activeAccount && (
<div className="mark-as-read-container">
<button
className={`mark-as-read-btn ${isMarkedAsRead ? 'marked' : ''} ${showCheckAnimation ? 'animating' : ''}`}
onClick={handleMarkAsRead}
disabled={isMarkedAsRead || isCheckingReadStatus}
title={isMarkedAsRead ? 'Already Marked as Read' : 'Mark as Read'}
>
<FontAwesomeIcon
icon={isCheckingReadStatus ? faSpinner : isMarkedAsRead ? faCheckCircle : faBooks}
spin={isCheckingReadStatus}
/>
<span>
{isCheckingReadStatus ? 'Checking...' : isMarkedAsRead ? 'Marked as Read' : 'Mark as Read'}
</span>
</button>
</div>
)}
</>
) : markdown || html ? (
<> <>
{markdown ? ( {markdown ? (
renderedMarkdownHtml && finalHtml ? ( renderedMarkdownHtml && finalHtml ? (

View File

@@ -23,6 +23,12 @@ export const classifyUrl = (url: string | undefined): UrlClassification => {
return { type: 'youtube' } return { type: 'youtube' }
} }
// Check for popular video hosts
const videoHosts = ['vimeo.com', 'dailymotion.com', 'dai.ly', 'video.twimg.com']
if (videoHosts.some(host => urlLower.includes(host))) {
return { type: 'video' }
}
// Check for video extensions // Check for video extensions
const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv', '.m4v'] const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv', '.m4v']
if (videoExtensions.some(ext => urlLower.includes(ext))) { if (videoExtensions.some(ext => urlLower.includes(ext))) {