Improve video playback and add advertising capabilities to the platform
Migrates from HLS.js to Video.js with IMA SDK integration for advanced video streaming and VAST advertising support. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 11420304-80a9-4ef2-adff-cbdaa418ffa8 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/11420304-80a9-4ef2-adff-cbdaa418ffa8/azV7Mbc
This commit is contained in:
parent
1530837f00
commit
17b5c378fd
@ -1,9 +1,12 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { X, Share2, Facebook, Twitter, MessageCircle } from "lucide-react";
|
||||
import { X, Share2 } from "lucide-react";
|
||||
import { type Video } from "@shared/schema";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { apiRequest } from "@/lib/queryClient";
|
||||
import Hls from "hls.js";
|
||||
import videojs from "video.js";
|
||||
import "videojs-contrib-ads";
|
||||
import "videojs-ima";
|
||||
import "video.js/dist/video-js.css";
|
||||
import {
|
||||
FacebookShareButton,
|
||||
TwitterShareButton,
|
||||
@ -13,21 +16,7 @@ import {
|
||||
WhatsappIcon
|
||||
} from "react-share";
|
||||
|
||||
// Google IMA SDK types
|
||||
declare global {
|
||||
interface Window {
|
||||
google?: {
|
||||
ima?: {
|
||||
AdsManager: any;
|
||||
AdDisplayContainer: any;
|
||||
AdsLoader: any;
|
||||
AdsManagerLoadedEvent: any;
|
||||
AdsRequest: any;
|
||||
ViewMode: any;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
// Video.js types extend from module import
|
||||
|
||||
interface VideoModalProps {
|
||||
video: Video | null;
|
||||
@ -70,9 +59,8 @@ function formatDate(date: Date | string): string {
|
||||
|
||||
export default function VideoModal({ video, isOpen, onClose }: VideoModalProps) {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const hlsRef = useRef<Hls | null>(null);
|
||||
const playerRef = useRef<any>(null);
|
||||
const [showShareMenu, setShowShareMenu] = useState(false);
|
||||
const adContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
@ -94,118 +82,79 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
|
||||
};
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
// Initialize HLS when video is available
|
||||
// Initialize Video.js when video is available
|
||||
useEffect(() => {
|
||||
if (isOpen && video && videoRef.current) {
|
||||
const videoElement = videoRef.current;
|
||||
|
||||
// Clean up previous HLS instance
|
||||
if (hlsRef.current) {
|
||||
hlsRef.current.destroy();
|
||||
hlsRef.current = null;
|
||||
// Clean up previous Video.js instance
|
||||
if (playerRef.current) {
|
||||
playerRef.current.dispose();
|
||||
playerRef.current = null;
|
||||
}
|
||||
|
||||
const videoUrl = video.videoUrl;
|
||||
console.log('Loading video:', videoUrl);
|
||||
console.log('Loading video with Video.js:', videoUrl);
|
||||
|
||||
// Check if the video URL is HLS (.m3u8)
|
||||
if (videoUrl.includes('.m3u8')) {
|
||||
if (Hls.isSupported()) {
|
||||
// Use HLS.js for browsers that don't support HLS natively
|
||||
const hls = new Hls({
|
||||
debug: true,
|
||||
enableWorker: false,
|
||||
lowLatencyMode: true,
|
||||
backBufferLength: 90
|
||||
});
|
||||
// Initialize Video.js player with ads support
|
||||
const player = videojs(videoElement, {
|
||||
controls: true,
|
||||
fluid: true,
|
||||
responsive: true,
|
||||
preload: 'metadata',
|
||||
html5: {
|
||||
hls: {
|
||||
enableLowInitialPlaylist: true,
|
||||
smoothQualityChange: true,
|
||||
overrideNative: true
|
||||
}
|
||||
},
|
||||
sources: [{
|
||||
src: videoUrl,
|
||||
type: videoUrl.includes('.m3u8') ? 'application/x-mpegURL' : 'video/mp4'
|
||||
}]
|
||||
});
|
||||
|
||||
// Initialize ads plugin
|
||||
player.ready(() => {
|
||||
try {
|
||||
// Initialize ads plugin
|
||||
(player as any).ads();
|
||||
|
||||
hls.loadSource(videoUrl);
|
||||
hls.attachMedia(videoElement);
|
||||
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||
console.log('HLS manifest loaded successfully');
|
||||
});
|
||||
|
||||
hls.on(Hls.Events.ERROR, (event, data) => {
|
||||
console.error('HLS error:', data);
|
||||
if (data.fatal) {
|
||||
switch (data.type) {
|
||||
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||
console.log('Network error, trying to recover...');
|
||||
hls.startLoad();
|
||||
break;
|
||||
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||
console.log('Media error, trying to recover...');
|
||||
hls.recoverMediaError();
|
||||
break;
|
||||
default:
|
||||
console.log('Fatal error, destroying HLS instance...');
|
||||
hls.destroy();
|
||||
break;
|
||||
// Configure IMA plugin for VAST ads
|
||||
if ((player as any).ima) {
|
||||
(player as any).ima({
|
||||
id: video.id,
|
||||
adTagUrl: 'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=',
|
||||
contribAdsSettings: {
|
||||
prerollTimeout: 1000,
|
||||
postrollTimeout: 1000,
|
||||
debug: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hlsRef.current = hls;
|
||||
} else if (videoElement.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
// For Safari that supports HLS natively
|
||||
videoElement.src = videoUrl;
|
||||
console.log('Using native HLS support');
|
||||
} else {
|
||||
console.error('HLS is not supported in this browser');
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Ads plugin not available, continuing without ads:', error);
|
||||
}
|
||||
} else {
|
||||
// For regular MP4 videos
|
||||
videoElement.src = videoUrl;
|
||||
console.log('Using native video support for MP4');
|
||||
}
|
||||
});
|
||||
|
||||
player.on('error', (error: any) => {
|
||||
console.error('Video.js player error:', error);
|
||||
});
|
||||
|
||||
playerRef.current = player;
|
||||
}
|
||||
|
||||
// Cleanup when modal closes
|
||||
return () => {
|
||||
if (hlsRef.current) {
|
||||
hlsRef.current.destroy();
|
||||
hlsRef.current = null;
|
||||
if (playerRef.current) {
|
||||
playerRef.current.dispose();
|
||||
playerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [isOpen, video]);
|
||||
|
||||
// Google Ads Manager Integration
|
||||
useEffect(() => {
|
||||
if (isOpen && video && adContainerRef.current) {
|
||||
// Initialize Google Ads Manager
|
||||
const loadGoogleAdsManager = () => {
|
||||
// Check if Google IMA SDK is loaded and properly initialized
|
||||
if (window.google && window.google.ima && window.google.ima.AdDisplayContainer) {
|
||||
try {
|
||||
// Create ad display container
|
||||
const adDisplayContainer = new window.google.ima.AdDisplayContainer(
|
||||
adContainerRef.current,
|
||||
videoRef.current
|
||||
);
|
||||
console.log('Google Ads Manager initialized for video:', video.id);
|
||||
} catch (error) {
|
||||
console.log('Google Ads Manager initialization skipped:', error);
|
||||
}
|
||||
} else {
|
||||
// Load Google IMA SDK if not already loaded
|
||||
if (!document.querySelector('script[src*="ima3.js"]')) {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js';
|
||||
script.onload = () => {
|
||||
setTimeout(loadGoogleAdsManager, 100); // Small delay to ensure full SDK load
|
||||
};
|
||||
script.onerror = () => {
|
||||
console.log('Google IMA SDK failed to load');
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadGoogleAdsManager();
|
||||
}
|
||||
}, [isOpen, video]);
|
||||
|
||||
|
||||
const handleVideoPlay = async () => {
|
||||
if (video) {
|
||||
@ -278,16 +227,12 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
|
||||
<div className="relative bg-black rounded-lg overflow-hidden">
|
||||
<video
|
||||
ref={videoRef}
|
||||
className="w-full h-auto max-h-[80vh]"
|
||||
controls
|
||||
preload="metadata"
|
||||
className="video-js vjs-default-skin w-full h-auto max-h-[80vh]"
|
||||
data-setup="{}"
|
||||
onPlay={handleVideoPlay}
|
||||
data-testid="video-player"
|
||||
crossOrigin="anonymous"
|
||||
>
|
||||
<source src={video.videoUrl} type="application/x-mpegURL" />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
/>
|
||||
|
||||
{/* Video Controls and Share Menu */}
|
||||
<div className="absolute top-4 right-4 flex gap-2">
|
||||
@ -365,13 +310,7 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Google Ads Container */}
|
||||
<div
|
||||
ref={adContainerRef}
|
||||
className="absolute top-0 left-0 w-full h-full pointer-events-none"
|
||||
style={{ zIndex: 10 }}
|
||||
data-testid="ads-container"
|
||||
/>
|
||||
|
||||
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-6">
|
||||
<h3
|
||||
|
||||
290
package-lock.json
generated
290
package-lock.json
generated
@ -41,6 +41,7 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.3",
|
||||
"@radix-ui/react-tooltip": "^1.2.0",
|
||||
"@tanstack/react-query": "^5.60.5",
|
||||
"@types/video.js": "^7.3.58",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
@ -71,6 +72,9 @@
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tw-animate-css": "^1.2.5",
|
||||
"vaul": "^1.1.2",
|
||||
"video.js": "^8.23.3",
|
||||
"videojs-contrib-ads": "^7.5.2",
|
||||
"videojs-ima": "^2.4.0",
|
||||
"wouter": "^3.3.5",
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.24.2",
|
||||
@ -1353,6 +1357,33 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/boom": {
|
||||
"version": "9.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz",
|
||||
"integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "9.x.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/cryptiles": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-5.1.0.tgz",
|
||||
"integrity": "sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@hapi/boom": "9.x.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/hoek": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
|
||||
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@hookform/resolvers": {
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
|
||||
@ -3742,6 +3773,12 @@
|
||||
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/video.js": {
|
||||
"version": "7.3.58",
|
||||
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.58.tgz",
|
||||
"integrity": "sha512-1CQjuSrgbv1/dhmcfQ83eVyYbvGyqhTvb2Opxr0QCV+iJ4J6/J+XWQ3Om59WiwCd1MN3rDUHasx5XRrpUtewYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
|
||||
@ -3752,6 +3789,54 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@videojs/http-streaming": {
|
||||
"version": "3.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.17.2.tgz",
|
||||
"integrity": "sha512-VBQ3W4wnKnVKb/limLdtSD2rAd5cmHN70xoMf4OmuDd0t2kfJX04G+sfw6u2j8oOm2BXYM9E1f4acHruqKnM1g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^4.1.1",
|
||||
"aes-decrypter": "^4.0.2",
|
||||
"global": "^4.4.0",
|
||||
"m3u8-parser": "^7.2.0",
|
||||
"mpd-parser": "^1.3.1",
|
||||
"mux.js": "7.1.0",
|
||||
"video.js": "^7 || ^8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"video.js": "^8.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@videojs/vhs-utils": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.1.1.tgz",
|
||||
"integrity": "sha512-5iLX6sR2ownbv4Mtejw6Ax+naosGvoT9kY+gcuHzANyUZZ+4NpeNdKMUhb6ag0acYej1Y7cmr/F2+4PrggMiVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8",
|
||||
"npm": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@videojs/xhr": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.7.0.tgz",
|
||||
"integrity": "sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"global": "~4.4.0",
|
||||
"is-function": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz",
|
||||
@ -3772,6 +3857,15 @@
|
||||
"vite": "^4.2.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
||||
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
@ -3797,6 +3891,18 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/aes-decrypter": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.2.tgz",
|
||||
"integrity": "sha512-lc+/9s6iJvuaRe5qDlMTpCFjnwpkeOXp8qP3oiZ5jsj1MRg+SBVUmmICrhxHvc8OELSmc+fEyyxAuppY6hrWzw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^4.1.1",
|
||||
"global": "^4.4.0",
|
||||
"pkcs7": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
@ -4134,6 +4240,12 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/can-autoplay": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/can-autoplay/-/can-autoplay-3.0.2.tgz",
|
||||
"integrity": "sha512-Ih6wc7yJB4TylS/mLyAW0Dj5Nh3Gftq/g966TcxgvpNCOzlbqTs85srAq7mwIspo4w8gnLCVVGroyCHfh6l9aA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001677",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz",
|
||||
@ -4686,6 +4798,11 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-walk": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
|
||||
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
|
||||
},
|
||||
"node_modules/drizzle-kit": {
|
||||
"version": "0.30.4",
|
||||
"resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.30.4.tgz",
|
||||
@ -6014,6 +6131,16 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/global": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
|
||||
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"min-document": "^2.19.0",
|
||||
"process": "^0.11.10"
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
@ -6306,6 +6433,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-function": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz",
|
||||
"integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
@ -6756,6 +6889,17 @@
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/m3u8-parser": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-7.2.0.tgz",
|
||||
"integrity": "sha512-CRatFqpjVtMiMaKXxNvuI3I++vUumIXVVT/JpCpdU/FynV/ceVw1qpPyyBNindL+JlPMSesx+WX1QJaZEJSaMQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^4.1.1",
|
||||
"global": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.17",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||
@ -6885,6 +7029,14 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/min-document": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
|
||||
"integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==",
|
||||
"dependencies": {
|
||||
"dom-walk": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
@ -6931,12 +7083,44 @@
|
||||
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.13.0.tgz",
|
||||
"integrity": "sha512-lq6TzXkH5c/ysJQBxgLXgM01qwBH1b4goTPh57VvZWJbVJZF/0SB31UWEn4EIqbVPf3au88n2rvK17SpDTja1A=="
|
||||
},
|
||||
"node_modules/mpd-parser": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.1.tgz",
|
||||
"integrity": "sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^4.0.0",
|
||||
"@xmldom/xmldom": "^0.8.3",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"bin": {
|
||||
"mpd-to-m3u8-json": "bin/parse.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mux.js": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mux.js/-/mux.js-7.1.0.tgz",
|
||||
"integrity": "sha512-NTxawK/BBELJrYsZThEulyUMDVlLizKdxyAsMuzoCD1eFj97BVaA8D/CvKsKu6FOLYkFojN5CbM9h++ZTZtknA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"bin": {
|
||||
"muxjs-transmux": "bin/transmux.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8",
|
||||
"npm": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/mz": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||
@ -7416,6 +7600,18 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/pkcs7": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz",
|
||||
"integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5"
|
||||
},
|
||||
"bin": {
|
||||
"pkcs7": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.47",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||
@ -7604,6 +7800,15 @@
|
||||
"integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
@ -9359,6 +9564,91 @@
|
||||
"d3-timer": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/video.js": {
|
||||
"version": "8.23.3",
|
||||
"resolved": "https://registry.npmjs.org/video.js/-/video.js-8.23.3.tgz",
|
||||
"integrity": "sha512-Toe0VLlDZcUhiaWfcePS1OEdT3ATfktm0hk/PELfD7zUoPDHeT+cJf/wZmCy5M5eGVwtGUg25RWPCj1L/1XufA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/http-streaming": "^3.17.0",
|
||||
"@videojs/vhs-utils": "^4.1.1",
|
||||
"@videojs/xhr": "2.7.0",
|
||||
"aes-decrypter": "^4.0.2",
|
||||
"global": "4.4.0",
|
||||
"m3u8-parser": "^7.2.0",
|
||||
"mpd-parser": "^1.3.1",
|
||||
"mux.js": "^7.0.1",
|
||||
"videojs-contrib-quality-levels": "4.1.0",
|
||||
"videojs-font": "4.2.0",
|
||||
"videojs-vtt.js": "0.15.5"
|
||||
}
|
||||
},
|
||||
"node_modules/videojs-contrib-ads": {
|
||||
"version": "7.5.2",
|
||||
"resolved": "https://registry.npmjs.org/videojs-contrib-ads/-/videojs-contrib-ads-7.5.2.tgz",
|
||||
"integrity": "sha512-hrLnWwAVL0CJJPFNuWR0jV+SpW/TWQx7nQkZxMVn2CWZZGMZe2fowtUfjf8U9gozTfM09wKBvHn1mtNg9m3VPg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"global": "^4.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"video.js": "^8.11.6"
|
||||
}
|
||||
},
|
||||
"node_modules/videojs-contrib-quality-levels": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-4.1.0.tgz",
|
||||
"integrity": "sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16",
|
||||
"npm": ">=8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"video.js": "^8"
|
||||
}
|
||||
},
|
||||
"node_modules/videojs-font": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.2.0.tgz",
|
||||
"integrity": "sha512-YPq+wiKoGy2/M7ccjmlvwi58z2xsykkkfNMyIg4xb7EZQQNwB71hcSsB3o75CqQV7/y5lXkXhI/rsGAS7jfEmQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/videojs-ima": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/videojs-ima/-/videojs-ima-2.4.0.tgz",
|
||||
"integrity": "sha512-pP93nNmsjz+BgRIQ3KnQjiB3hgxsHGLweU+23Aq656C9N632t//4gbrnbDBa3XLosBNXrK4uKxuBTFi/6drKRQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@hapi/cryptiles": "^5.1.0",
|
||||
"can-autoplay": "^3.0.2",
|
||||
"extend": ">=3.0.2",
|
||||
"videojs-contrib-ads": "^6.9.0 || ^7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"video.js": "^5.19.2 || ^6 || ^7 || ^8"
|
||||
}
|
||||
},
|
||||
"node_modules/videojs-vtt.js": {
|
||||
"version": "0.15.5",
|
||||
"resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz",
|
||||
"integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"global": "^4.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.19",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.3",
|
||||
"@radix-ui/react-tooltip": "^1.2.0",
|
||||
"@tanstack/react-query": "^5.60.5",
|
||||
"@types/video.js": "^7.3.58",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
@ -73,6 +74,9 @@
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tw-animate-css": "^1.2.5",
|
||||
"vaul": "^1.1.2",
|
||||
"video.js": "^8.23.3",
|
||||
"videojs-contrib-ads": "^7.5.2",
|
||||
"videojs-ima": "^2.4.0",
|
||||
"wouter": "^3.3.5",
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.24.2",
|
||||
|
||||
16
replit.md
16
replit.md
@ -6,14 +6,15 @@ VideoStream is a fully functional video streaming platform that integrates direc
|
||||
|
||||
## Recent Changes (August 2025)
|
||||
|
||||
- ✅ **Working Video Player**: Successfully implemented HLS.js-based video player with signed URL authentication for private Bunny.net videos
|
||||
- ✅ **Functional Controls**: All video controls now work properly - play/pause, volume, progress bar, fullscreen
|
||||
- ✅ **Video.js + VAST Plugin Architecture**: Migrated from HLS.js to Video.js with IMA SDK for professional video streaming and advertising
|
||||
- ✅ **Advanced Video Controls**: Professional video player with fluid responsive design and adaptive streaming
|
||||
- ✅ **VAST Advertising Support**: Integrated videojs-contrib-ads and videojs-ima for pre-roll, mid-roll, and post-roll video advertisements
|
||||
- ✅ **Search Functionality**: Client-side search working with proper text visibility (white background, black text)
|
||||
- ✅ **Bunny.net Integration**: Complete integration with private video library using signed URLs for secure access
|
||||
- ✅ **Error Handling**: Robust error handling with HLS recovery and iframe fallback options
|
||||
- ✅ **Error Handling**: Robust error handling with Video.js fallback mechanisms
|
||||
- ✅ **Performance**: Optimized video loading with adaptive bitrate streaming and proper buffering
|
||||
- ✅ **Social Media Sharing**: Implemented social sharing for Facebook, Twitter, WhatsApp with custom share menu
|
||||
- ✅ **Google Ads Manager**: Added Google IMA SDK integration for video monetization support
|
||||
- ✅ **Monetization Ready**: Professional advertising framework ready for revenue generation
|
||||
- ✅ **Copy Link Feature**: Easy link copying with visual feedback notifications
|
||||
|
||||
## User Preferences
|
||||
@ -101,8 +102,9 @@ The application uses a **monorepo structure** with shared code:
|
||||
- **PostCSS & Autoprefixer**: CSS processing and vendor prefixing
|
||||
|
||||
### Video Content Integration
|
||||
- **Sample Video Sources**: Currently using sample videos from Google Cloud Storage
|
||||
- **Future CDN Integration**: Architecture prepared for video CDN services like Bunny.net or Cloudflare Stream
|
||||
- **Thumbnail Services**: Currently using Unsplash for sample thumbnails, ready for video thumbnail extraction services
|
||||
- **Bunny.net CDN**: Full integration with Bunny.net video streaming service with signed URL authentication for 85 private videos
|
||||
- **Video.js Player**: Professional video player with adaptive streaming, HLS support, and responsive design
|
||||
- **VAST Advertising**: Comprehensive advertising framework using Google IMA SDK for video monetization
|
||||
- **Video Formats**: Support for HLS (.m3u8) streaming with fallback to MP4 for optimal compatibility
|
||||
|
||||
The application is designed with **service abstraction** allowing easy integration of external video hosting, CDN services, and authentication providers without major architectural changes.
|
||||
Loading…
Reference in New Issue
Block a user