Add audio poster support for audio files in video players

Introduces an audio-poster.jpg image and updates both chapters and video editor React video player components to display a poster image for audio files when no poster is provided. Also adds a posterUrl field to MEDIA_DATA and ensures fallback logic for poster images is consistent across iOS and standard video players.
This commit is contained in:
Yiannis Christodoulou 2025-10-19 13:56:17 +03:00
parent bfcb774183
commit 54e2f1d7e4
13 changed files with 133 additions and 93 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 KiB

View File

@ -11,6 +11,7 @@ interface IOSVideoPlayerProps {
const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps) => {
const [videoUrl, setVideoUrl] = useState<string>('');
const [iosVideoRef, setIosVideoRef] = useState<HTMLVideoElement | null>(null);
const [posterImage, setPosterImage] = useState<string | undefined>(undefined);
// Refs for hold-to-continue functionality
const incrementIntervalRef = useRef<NodeJS.Timeout | null>(null);
@ -26,15 +27,24 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
// Get the video source URL from the main player
useEffect(() => {
let url = '';
if (videoRef.current && videoRef.current.querySelector('source')) {
const source = videoRef.current.querySelector('source') as HTMLSourceElement;
if (source && source.src) {
setVideoUrl(source.src);
url = source.src;
}
} else {
// Fallback to sample video if needed
setVideoUrl('/videos/sample-video.mp4');
url = '/videos/sample-video.mp4';
}
setVideoUrl(url);
// Check if the media is an audio file and set poster image
const isAudioFile = url.match(/\.(mp3|wav|ogg|m4a|aac|flac)$/i) !== null;
// Get posterUrl from MEDIA_DATA, or use audio-poster.jpg as fallback for audio files when posterUrl is empty
const mediaPosterUrl = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.posterUrl) || '';
setPosterImage(mediaPosterUrl || (isAudioFile ? '/audio-poster.jpg' : undefined));
}, [videoRef]);
// Function to jump 15 seconds backward
@ -127,6 +137,7 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
x-webkit-airplay="allow"
preload="auto"
crossOrigin="anonymous"
poster={posterImage}
>
<source src={videoUrl} type="video/mp4" />
<p>Your browser doesn't support HTML5 video.</p>

View File

@ -38,6 +38,13 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
const sampleVideoUrl =
(typeof window !== 'undefined' && (window as any).MEDIA_DATA?.videoUrl) || '/videos/sample-video.mp4';
// Check if the media is an audio file
const isAudioFile = sampleVideoUrl.match(/\.(mp3|wav|ogg|m4a|aac|flac)$/i) !== null;
// Get posterUrl from MEDIA_DATA, or use audio-poster.jpg as fallback for audio files when posterUrl is empty
const mediaPosterUrl = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.posterUrl) || '';
const posterImage = mediaPosterUrl || (isAudioFile ? '/audio-poster.jpg' : undefined);
// Detect iOS device and Safari browser
useEffect(() => {
const checkIOS = () => {
@ -354,6 +361,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
x-webkit-airplay="allow"
controls={false}
muted={isMuted}
poster={posterImage}
>
<source src={sampleVideoUrl} type="video/mp4" />
{/* Safari fallback for audio files */}

View File

@ -6,6 +6,7 @@ if (typeof window !== 'undefined') {
window.MEDIA_DATA = {
videoUrl: '',
mediaId: '',
posterUrl: ''
};
window.lastSeekedPosition = 0;
}
@ -15,6 +16,7 @@ declare global {
MEDIA_DATA: {
videoUrl: string;
mediaId: string;
posterUrl?: string;
};
seekToFunction?: (time: number) => void;
lastSeekedPosition: number;

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 KiB

View File

@ -11,6 +11,7 @@ interface IOSVideoPlayerProps {
const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps) => {
const [videoUrl, setVideoUrl] = useState<string>('');
const [iosVideoRef, setIosVideoRef] = useState<HTMLVideoElement | null>(null);
const [posterImage, setPosterImage] = useState<string | undefined>(undefined);
// Refs for hold-to-continue functionality
const incrementIntervalRef = useRef<NodeJS.Timeout | null>(null);
@ -26,15 +27,24 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
// Get the video source URL from the main player
useEffect(() => {
let url = '';
if (videoRef.current && videoRef.current.querySelector('source')) {
const source = videoRef.current.querySelector('source') as HTMLSourceElement;
if (source && source.src) {
setVideoUrl(source.src);
url = source.src;
}
} else {
// Fallback to sample video if needed
setVideoUrl('/videos/sample-video.mp4');
url = '/videos/sample-video.mp3';
}
setVideoUrl(url);
// Check if the media is an audio file and set poster image
const isAudioFile = url.match(/\.(mp3|wav|ogg|m4a|aac|flac)$/i) !== null;
// Get posterUrl from MEDIA_DATA, or use audio-poster.jpg as fallback for audio files when posterUrl is empty
const mediaPosterUrl = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.posterUrl) || '';
setPosterImage(mediaPosterUrl || (isAudioFile ? '/audio-poster.jpg' : undefined));
}, [videoRef]);
// Function to jump 15 seconds backward
@ -127,6 +137,7 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
x-webkit-airplay="allow"
preload="auto"
crossOrigin="anonymous"
poster={posterImage}
>
<source src={videoUrl} type="video/mp4" />
<p>Your browser doesn't support HTML5 video.</p>

View File

@ -36,7 +36,14 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
const [tooltipTime, setTooltipTime] = useState(0);
const sampleVideoUrl =
(typeof window !== 'undefined' && (window as any).MEDIA_DATA?.videoUrl) || '/videos/sample-video.mp4';
(typeof window !== 'undefined' && (window as any).MEDIA_DATA?.videoUrl) || '/videos/sample-video.mp3';
// Check if the media is an audio file
const isAudioFile = sampleVideoUrl.match(/\.(mp3|wav|ogg|m4a|aac|flac)$/i) !== null;
// Get posterUrl from MEDIA_DATA, or use audio-poster.jpg as fallback for audio files when posterUrl is empty
const mediaPosterUrl = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.posterUrl) || '';
const posterImage = mediaPosterUrl || (isAudioFile ? '/audio-poster.jpg' : undefined);
// Detect iOS device
useEffect(() => {
@ -344,6 +351,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
x-webkit-airplay="allow"
controls={false}
muted={isMuted}
poster={posterImage}
>
<source src={sampleVideoUrl} type="video/mp4" />
<p>Your browser doesn't support HTML5 video.</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long