Integrate smaller AdSense ads into the video player interface
Introduce a new component for displaying companion ads and integrate it into the video modal, allowing for smaller, compliant ad placements alongside video content. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 946a0075-7e32-454b-b348-9e7f576d7f45 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/60d372ff-2c10-46c7-b01b-10c3435136b0/946a0075-7e32-454b-b348-9e7f576d7f45/wfV3fPt
This commit is contained in:
parent
00f9419cb8
commit
0772ae1aab
133
client/src/components/video-companion-ad.tsx
Normal file
133
client/src/components/video-companion-ad.tsx
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { X } from 'lucide-react';
|
||||||
|
import AdSenseAd from './adsense-ad';
|
||||||
|
|
||||||
|
interface VideoCompanionAdProps {
|
||||||
|
isVideoPlaying: boolean;
|
||||||
|
showControls: boolean;
|
||||||
|
onClose?: () => void;
|
||||||
|
adSlot: string;
|
||||||
|
position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
|
||||||
|
autoHide?: number; // Auto hide after X seconds
|
||||||
|
size?: 'small' | 'medium';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function VideoCompanionAd({
|
||||||
|
isVideoPlaying,
|
||||||
|
showControls,
|
||||||
|
onClose,
|
||||||
|
adSlot,
|
||||||
|
position = 'bottom-left',
|
||||||
|
autoHide = 0, // No auto-hide by default
|
||||||
|
size = 'small'
|
||||||
|
}: VideoCompanionAdProps) {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const [shouldShow, setShouldShow] = useState(false);
|
||||||
|
|
||||||
|
// Show ad after 3 seconds of video playing
|
||||||
|
useEffect(() => {
|
||||||
|
let showTimer: NodeJS.Timeout;
|
||||||
|
let hideTimer: NodeJS.Timeout;
|
||||||
|
|
||||||
|
if (isVideoPlaying) {
|
||||||
|
// Show ad after 3 seconds
|
||||||
|
showTimer = setTimeout(() => {
|
||||||
|
setShouldShow(true);
|
||||||
|
setIsVisible(true);
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
// Auto hide if specified
|
||||||
|
if (autoHide > 0) {
|
||||||
|
hideTimer = setTimeout(() => {
|
||||||
|
setIsVisible(false);
|
||||||
|
}, (3 + autoHide) * 1000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setShouldShow(false);
|
||||||
|
setIsVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(showTimer);
|
||||||
|
clearTimeout(hideTimer);
|
||||||
|
};
|
||||||
|
}, [isVideoPlaying, autoHide]);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setIsVisible(false);
|
||||||
|
onClose?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!shouldShow || !isVisible) return null;
|
||||||
|
|
||||||
|
const getPositionClasses = () => {
|
||||||
|
switch (position) {
|
||||||
|
case 'top-left':
|
||||||
|
return 'top-4 left-4';
|
||||||
|
case 'top-right':
|
||||||
|
return 'top-4 right-4';
|
||||||
|
case 'bottom-left':
|
||||||
|
return 'bottom-20 left-4'; // Above video controls
|
||||||
|
case 'bottom-right':
|
||||||
|
return 'bottom-20 right-4'; // Above video controls
|
||||||
|
default:
|
||||||
|
return 'bottom-20 left-4';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSizeClasses = () => {
|
||||||
|
switch (size) {
|
||||||
|
case 'small':
|
||||||
|
return { width: '200px', height: '150px' };
|
||||||
|
case 'medium':
|
||||||
|
return { width: '300px', height: '200px' };
|
||||||
|
default:
|
||||||
|
return { width: '200px', height: '150px' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sizeStyle = getSizeClasses();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`absolute z-40 ${getPositionClasses()} transition-all duration-300 ${
|
||||||
|
isVisible ? 'opacity-100 scale-100' : 'opacity-0 scale-95'
|
||||||
|
}`}
|
||||||
|
style={sizeStyle}
|
||||||
|
>
|
||||||
|
{/* Ad Container - AdSense Compliant Companion Ad */}
|
||||||
|
<div className="relative bg-white/95 backdrop-blur-sm rounded-lg overflow-hidden border border-gray-200 shadow-xl">
|
||||||
|
{/* Close Button */}
|
||||||
|
<button
|
||||||
|
onClick={handleClose}
|
||||||
|
className="absolute top-1 right-1 z-10 w-5 h-5 bg-gray-800/80 hover:bg-gray-800 rounded-full flex items-center justify-center transition-colors"
|
||||||
|
>
|
||||||
|
<X className="w-3 h-3 text-white" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Ad Label */}
|
||||||
|
<div className="absolute top-1 left-1 z-10">
|
||||||
|
<span className="bg-[#da234d] text-white text-xs px-1.5 py-0.5 rounded font-medium">
|
||||||
|
AD
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* AdSense Companion Ad */}
|
||||||
|
<div className="p-1 pt-6 w-full h-full">
|
||||||
|
<AdSenseAd
|
||||||
|
adSlot={adSlot}
|
||||||
|
adFormat="rectangle"
|
||||||
|
width={size === 'small' ? 200 : 300}
|
||||||
|
height={size === 'small' ? 150 : 200}
|
||||||
|
className="w-full h-full"
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -12,6 +12,8 @@ import {
|
|||||||
|
|
||||||
import QualityIndicator from "./quality-indicator";
|
import QualityIndicator from "./quality-indicator";
|
||||||
import VASTPlayer from "./vast-player";
|
import VASTPlayer from "./vast-player";
|
||||||
|
import AdSenseAd from "./adsense-ad";
|
||||||
|
import VideoCompanionAd from "./video-companion-ad";
|
||||||
|
|
||||||
// HLS.js types for video streaming
|
// HLS.js types for video streaming
|
||||||
|
|
||||||
@ -56,7 +58,7 @@ function formatDate(date: Date | string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function VideoModal({ video, isOpen, onClose, enableAds = true }: VideoModalProps) {
|
export default function VideoModal({ video, isOpen, onClose, enableAds = true }: VideoModalProps) {
|
||||||
const [useVASTPlayer, setUseVASTPlayer] = useState(true);
|
const [useVASTPlayer, setUseVASTPlayer] = useState(enableAds);
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
const hlsRef = useRef<Hls | null>(null);
|
const hlsRef = useRef<Hls | null>(null);
|
||||||
const [showShareMenu, setShowShareMenu] = useState(false);
|
const [showShareMenu, setShowShareMenu] = useState(false);
|
||||||
@ -71,6 +73,7 @@ export default function VideoModal({ video, isOpen, onClose, enableAds = true }:
|
|||||||
const [volume, setVolume] = useState(1);
|
const [volume, setVolume] = useState(1);
|
||||||
const [hoverTime, setHoverTime] = useState(-1);
|
const [hoverTime, setHoverTime] = useState(-1);
|
||||||
|
|
||||||
|
|
||||||
// All hooks must be declared before any conditional returns
|
// All hooks must be declared before any conditional returns
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEscape = (e: KeyboardEvent) => {
|
const handleEscape = (e: KeyboardEvent) => {
|
||||||
@ -650,6 +653,25 @@ export default function VideoModal({ video, isOpen, onClose, enableAds = true }:
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Video Companion Ads (AdSense Compliant) */}
|
||||||
|
<VideoCompanionAd
|
||||||
|
isVideoPlaying={isPlaying}
|
||||||
|
showControls={showControls}
|
||||||
|
adSlot="5629279662"
|
||||||
|
position="bottom-right"
|
||||||
|
size="small"
|
||||||
|
autoHide={20}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<VideoCompanionAd
|
||||||
|
isVideoPlaying={isPlaying}
|
||||||
|
showControls={showControls}
|
||||||
|
adSlot="1372934919"
|
||||||
|
position="top-right"
|
||||||
|
size="small"
|
||||||
|
autoHide={25}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user