mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-10 09:28:53 -05:00
Fix audio poster display for Safari in video players (144)
Adds persistent background poster images for audio files in both VideoPlayer and IOSVideoPlayer components to address Safari rendering issues. Updates CSS and component logic to ensure poster images remain visible for audio files, improving user experience and visual consistency.
This commit is contained in:
parent
3abc012de1
commit
9a8a34317d
@ -13,6 +13,7 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
|
|||||||
const [videoUrl, setVideoUrl] = useState<string>('');
|
const [videoUrl, setVideoUrl] = useState<string>('');
|
||||||
const [iosVideoRef, setIosVideoRef] = useState<HTMLVideoElement | null>(null);
|
const [iosVideoRef, setIosVideoRef] = useState<HTMLVideoElement | null>(null);
|
||||||
const [posterImage, setPosterImage] = useState<string | undefined>(undefined);
|
const [posterImage, setPosterImage] = useState<string | undefined>(undefined);
|
||||||
|
const [isAudioFile, setIsAudioFile] = useState(false);
|
||||||
|
|
||||||
// Refs for hold-to-continue functionality
|
// Refs for hold-to-continue functionality
|
||||||
const incrementIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
const incrementIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
@ -41,12 +42,13 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
|
|||||||
setVideoUrl(url);
|
setVideoUrl(url);
|
||||||
|
|
||||||
// Check if the media is an audio file and set poster image
|
// Check if the media is an audio file and set poster image
|
||||||
const isAudioFile = url.match(/\.(mp3|wav|ogg|m4a|aac|flac)$/i) !== null;
|
const audioFile = url.match(/\.(mp3|wav|ogg|m4a|aac|flac)$/i) !== null;
|
||||||
|
setIsAudioFile(audioFile);
|
||||||
|
|
||||||
// Get posterUrl from MEDIA_DATA, or use audio-poster.jpg as fallback for audio files when posterUrl is empty, null, or "None"
|
// Get posterUrl from MEDIA_DATA, or use audio-poster.jpg as fallback for audio files when posterUrl is empty, null, or "None"
|
||||||
const mediaPosterUrl = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.posterUrl) || '';
|
const mediaPosterUrl = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.posterUrl) || '';
|
||||||
const isValidPoster = mediaPosterUrl && mediaPosterUrl !== 'None' && mediaPosterUrl.trim() !== '';
|
const isValidPoster = mediaPosterUrl && mediaPosterUrl !== 'None' && mediaPosterUrl.trim() !== '';
|
||||||
setPosterImage(isValidPoster ? mediaPosterUrl : (isAudioFile ? AUDIO_POSTER_URL : undefined));
|
setPosterImage(isValidPoster ? mediaPosterUrl : (audioFile ? AUDIO_POSTER_URL : undefined));
|
||||||
}, [videoRef]);
|
}, [videoRef]);
|
||||||
|
|
||||||
// Function to jump 15 seconds backward
|
// Function to jump 15 seconds backward
|
||||||
@ -128,22 +130,34 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* iOS-optimized Video Element with Native Controls */}
|
{/* Video container with persistent background for audio files */}
|
||||||
<video
|
<div className="ios-video-wrapper">
|
||||||
ref={(ref) => setIosVideoRef(ref)}
|
{/* Persistent background image for audio files (Safari fix) */}
|
||||||
className="w-full rounded-md"
|
{isAudioFile && posterImage && (
|
||||||
src={videoUrl}
|
<div
|
||||||
controls
|
className="ios-audio-poster-background"
|
||||||
playsInline
|
style={{ backgroundImage: `url(${posterImage})` }}
|
||||||
webkit-playsinline="true"
|
aria-hidden="true"
|
||||||
x-webkit-airplay="allow"
|
/>
|
||||||
preload="auto"
|
)}
|
||||||
crossOrigin="anonymous"
|
|
||||||
poster={posterImage}
|
{/* iOS-optimized Video Element with Native Controls */}
|
||||||
>
|
<video
|
||||||
<source src={videoUrl} type="video/mp4" />
|
ref={(ref) => setIosVideoRef(ref)}
|
||||||
<p>Your browser doesn't support HTML5 video.</p>
|
className={`w-full rounded-md ${isAudioFile && posterImage ? 'audio-with-poster' : ''}`}
|
||||||
</video>
|
src={videoUrl}
|
||||||
|
controls
|
||||||
|
playsInline
|
||||||
|
webkit-playsinline="true"
|
||||||
|
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>
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* iOS Video Skip Controls */}
|
{/* iOS Video Skip Controls */}
|
||||||
<div className="ios-skip-controls mt-3 flex justify-center gap-4">
|
<div className="ios-skip-controls mt-3 flex justify-center gap-4">
|
||||||
|
|||||||
@ -353,8 +353,18 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="video-player-container">
|
<div className="video-player-container">
|
||||||
|
{/* Persistent background image for audio files (Safari fix) */}
|
||||||
|
{isAudioFile && posterImage && (
|
||||||
|
<div
|
||||||
|
className="audio-poster-background"
|
||||||
|
style={{ backgroundImage: `url(${posterImage})` }}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
|
className={isAudioFile && posterImage ? 'audio-with-poster' : ''}
|
||||||
preload="metadata"
|
preload="metadata"
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
onClick={handleVideoClick}
|
onClick={handleVideoClick}
|
||||||
|
|||||||
@ -8,12 +8,40 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Video wrapper for positioning background */
|
||||||
|
.ios-video-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
background-color: black;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Persistent background poster for audio files (Safari fix) */
|
||||||
|
.ios-audio-poster-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.ios-video-player-container video {
|
.ios-video-player-container video {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 360px;
|
max-height: 360px;
|
||||||
aspect-ratio: 16/9;
|
aspect-ratio: 16/9;
|
||||||
background-color: black;
|
}
|
||||||
|
|
||||||
|
/* Make video transparent only for audio files with poster so background shows through */
|
||||||
|
.ios-video-player-container video.audio-with-poster {
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ios-time-display {
|
.ios-time-display {
|
||||||
|
|||||||
@ -76,10 +76,26 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Persistent background poster for audio files (Safari fix) */
|
||||||
|
.audio-poster-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.video-player-container video {
|
.video-player-container video {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
z-index: 2;
|
||||||
/* Force hardware acceleration */
|
/* Force hardware acceleration */
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
-webkit-transform: translateZ(0);
|
-webkit-transform: translateZ(0);
|
||||||
@ -88,6 +104,11 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Make video transparent only for audio files with poster so background shows through */
|
||||||
|
.video-player-container video.audio-with-poster {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
/* iOS-specific styles */
|
/* iOS-specific styles */
|
||||||
@supports (-webkit-touch-callout: none) {
|
@supports (-webkit-touch-callout: none) {
|
||||||
.video-player-container video {
|
.video-player-container video {
|
||||||
@ -109,6 +130,7 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-player-container:hover .play-pause-indicator {
|
.video-player-container:hover .play-pause-indicator {
|
||||||
@ -187,6 +209,7 @@
|
|||||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
|
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-player-container:hover .video-controls {
|
.video-player-container:hover .video-controls {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
|
|||||||
const [videoUrl, setVideoUrl] = useState<string>('');
|
const [videoUrl, setVideoUrl] = useState<string>('');
|
||||||
const [iosVideoRef, setIosVideoRef] = useState<HTMLVideoElement | null>(null);
|
const [iosVideoRef, setIosVideoRef] = useState<HTMLVideoElement | null>(null);
|
||||||
const [posterImage, setPosterImage] = useState<string | undefined>(undefined);
|
const [posterImage, setPosterImage] = useState<string | undefined>(undefined);
|
||||||
|
const [isAudioFile, setIsAudioFile] = useState(false);
|
||||||
|
|
||||||
// Refs for hold-to-continue functionality
|
// Refs for hold-to-continue functionality
|
||||||
const incrementIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
const incrementIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
@ -41,12 +42,13 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
|
|||||||
setVideoUrl(url);
|
setVideoUrl(url);
|
||||||
|
|
||||||
// Check if the media is an audio file and set poster image
|
// Check if the media is an audio file and set poster image
|
||||||
const isAudioFile = url.match(/\.(mp3|wav|ogg|m4a|aac|flac)$/i) !== null;
|
const audioFile = url.match(/\.(mp3|wav|ogg|m4a|aac|flac)$/i) !== null;
|
||||||
|
setIsAudioFile(audioFile);
|
||||||
|
|
||||||
// Get posterUrl from MEDIA_DATA, or use audio-poster.jpg as fallback for audio files when posterUrl is empty, null, or "None"
|
// Get posterUrl from MEDIA_DATA, or use audio-poster.jpg as fallback for audio files when posterUrl is empty, null, or "None"
|
||||||
const mediaPosterUrl = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.posterUrl) || '';
|
const mediaPosterUrl = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.posterUrl) || '';
|
||||||
const isValidPoster = mediaPosterUrl && mediaPosterUrl !== 'None' && mediaPosterUrl.trim() !== '';
|
const isValidPoster = mediaPosterUrl && mediaPosterUrl !== 'None' && mediaPosterUrl.trim() !== '';
|
||||||
setPosterImage(isValidPoster ? mediaPosterUrl : (isAudioFile ? AUDIO_POSTER_URL : undefined));
|
setPosterImage(isValidPoster ? mediaPosterUrl : (audioFile ? AUDIO_POSTER_URL : undefined));
|
||||||
}, [videoRef]);
|
}, [videoRef]);
|
||||||
|
|
||||||
// Function to jump 15 seconds backward
|
// Function to jump 15 seconds backward
|
||||||
@ -128,22 +130,34 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* iOS-optimized Video Element with Native Controls */}
|
{/* Video container with persistent background for audio files */}
|
||||||
<video
|
<div className="ios-video-wrapper">
|
||||||
ref={(ref) => setIosVideoRef(ref)}
|
{/* Persistent background image for audio files (Safari fix) */}
|
||||||
className="w-full rounded-md"
|
{isAudioFile && posterImage && (
|
||||||
src={videoUrl}
|
<div
|
||||||
controls
|
className="ios-audio-poster-background"
|
||||||
playsInline
|
style={{ backgroundImage: `url(${posterImage})` }}
|
||||||
webkit-playsinline="true"
|
aria-hidden="true"
|
||||||
x-webkit-airplay="allow"
|
/>
|
||||||
preload="auto"
|
)}
|
||||||
crossOrigin="anonymous"
|
|
||||||
poster={posterImage}
|
{/* iOS-optimized Video Element with Native Controls */}
|
||||||
>
|
<video
|
||||||
<source src={videoUrl} type="video/mp4" />
|
ref={(ref) => setIosVideoRef(ref)}
|
||||||
<p>Your browser doesn't support HTML5 video.</p>
|
className={`w-full rounded-md ${isAudioFile && posterImage ? 'audio-with-poster' : ''}`}
|
||||||
</video>
|
src={videoUrl}
|
||||||
|
controls
|
||||||
|
playsInline
|
||||||
|
webkit-playsinline="true"
|
||||||
|
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>
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* iOS Video Skip Controls */}
|
{/* iOS Video Skip Controls */}
|
||||||
<div className="ios-skip-controls mt-3 flex justify-center gap-4">
|
<div className="ios-skip-controls mt-3 flex justify-center gap-4">
|
||||||
|
|||||||
@ -353,8 +353,18 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="video-player-container">
|
<div className="video-player-container">
|
||||||
|
{/* Persistent background image for audio files (Safari fix) */}
|
||||||
|
{isAudioFile && posterImage && (
|
||||||
|
<div
|
||||||
|
className="audio-poster-background"
|
||||||
|
style={{ backgroundImage: `url(${posterImage})` }}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
|
className={isAudioFile && posterImage ? 'audio-with-poster' : ''}
|
||||||
preload="metadata"
|
preload="metadata"
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
onClick={handleVideoClick}
|
onClick={handleVideoClick}
|
||||||
|
|||||||
@ -8,12 +8,40 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Video wrapper for positioning background */
|
||||||
|
.ios-video-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
background-color: black;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Persistent background poster for audio files (Safari fix) */
|
||||||
|
.ios-audio-poster-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.ios-video-player-container video {
|
.ios-video-player-container video {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 360px;
|
max-height: 360px;
|
||||||
aspect-ratio: 16/9;
|
aspect-ratio: 16/9;
|
||||||
background-color: black;
|
}
|
||||||
|
|
||||||
|
/* Make video transparent only for audio files with poster so background shows through */
|
||||||
|
.ios-video-player-container video.audio-with-poster {
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ios-time-display {
|
.ios-time-display {
|
||||||
|
|||||||
@ -76,10 +76,26 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Persistent background poster for audio files (Safari fix) */
|
||||||
|
.audio-poster-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.video-player-container video {
|
.video-player-container video {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
z-index: 2;
|
||||||
/* Force hardware acceleration */
|
/* Force hardware acceleration */
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
-webkit-transform: translateZ(0);
|
-webkit-transform: translateZ(0);
|
||||||
@ -88,6 +104,11 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Make video transparent only for audio files with poster so background shows through */
|
||||||
|
.video-player-container video.audio-with-poster {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
/* iOS-specific styles */
|
/* iOS-specific styles */
|
||||||
@supports (-webkit-touch-callout: none) {
|
@supports (-webkit-touch-callout: none) {
|
||||||
.video-player-container video {
|
.video-player-container video {
|
||||||
@ -109,6 +130,7 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-player-container:hover .play-pause-indicator {
|
.video-player-container:hover .play-pause-indicator {
|
||||||
@ -187,6 +209,7 @@
|
|||||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
|
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-player-container:hover .video-controls {
|
.video-player-container:hover .video-controls {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user