Skip to content

Commit 62382d4

Browse files
committed
mod code for livestream app route
1 parent f395693 commit 62382d4

File tree

7 files changed

+135
-22
lines changed

7 files changed

+135
-22
lines changed

.github/workflows/deploy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ jobs:
7979
echo "NEXT_PUBLIC_LINKEDIN_PARTNER_ID=8634577" >> ./client/.env
8080
echo "NEXT_PUBLIC_META_PIXEL_ID=1234567890" >> ./client/.env
8181
echo "NEXT_PUBLIC_TRUSTPILOT_INTENT_KEY=YqW4fUPXceqlZGwZ" >> ./client/.env
82+
echo "NEXT_PUBLIC_STREAM_KEY=${{ secrets.NEXT_PUBLIC_STREAM_KEY }}" >> ./client/.env
8283

8384

8485

client/.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,7 @@ NEXT_PUBLIC_GOOGLE_ANALYTICS=YOUR_GAID
3939
NEXT_PUBLIC_MICROSOFT_CLARITY=YOUR_CLARITY_PROJECTID
4040
NEXT_PUBLIC_LINKEDIN_PARTNER_ID=LINEDIN_PARTNERID
4141
NEXT_PUBLIC_META_PIXEL_ID=PIXELID
42-
NEXT_PUBLIC_TRUSTPILOT_INTENT_KEY=TPIK
42+
NEXT_PUBLIC_TRUSTPILOT_INTENT_KEY=TPIK
43+
44+
# Livestream
45+
NEXT_PUBLIC_STREAM_KEY=test

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"sanitize-html": "^2.17.0",
8484
"sharp": "^0.34.3",
8585
"socket.io-client": "^4.8.1",
86+
"sonner": "^2.0.7",
8687
"swiper": "^11.2.10",
8788
"tailwind-merge": "^3.3.1",
8889
"tailwindcss-animate": "^1.0.7",

client/pnpm-lock.yaml

Lines changed: 25 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/src/app/blog/[slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { strapiImage } from '@/lib/strapi/strapiImage';
1616
import RelatedPosts from "@/components/custom/related-posts";
1717
import SocialShareButtons from "@/components/custom/SocialShareButtons";
1818
import Image from "next/image";
19-
import DisqusComments from "@/components/custom/DisqusComments"; // The updated component
19+
import DisqusComments from "@/components/custom/DisqusComments";
2020

2121
interface PageProps {
2222
params: Promise<{ slug: string }>;

client/src/app/live/page.tsx

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
"use client";
22
import React, { useRef, useEffect, useState } from "react";
3-
import { Play, Radio, Signal, Zap, Users, Clipboard, X } from "lucide-react";
3+
import { Play, Radio, Signal, Zap, Users, Clipboard, X, RotateCcw } from "lucide-react";
44
import videojs from "video.js";
55
import "video.js/dist/video-js.css";
6+
import { usePathname } from "next/navigation";
7+
8+
9+
const streamKey = process.env.NEXT_PUBLIC_STREAM_KEY || "fallback-key";
10+
11+
const dashStream = `https://live.bitmutex.com/dash/${streamKey}_src.mpd`;
12+
const hlsStream = `https://live.bitmutex.com/hls/${streamKey}.m3u8`;
13+
console.log("DASH Stream URL:", dashStream);
14+
console.log("HLS Stream URL:", hlsStream);
615

7-
const dashStream = "https://live.bitmutex.com/dash/test_src.mpd";
8-
const hlsStream = "https://live.bitmutex.com/hls/test.m3u8";
916
const genericHlsUrl= "https://live.bitmutex.com/dash/<key>_src.mpd";
1017
const genericDashUrl = "https://live.bitmutex.com/hls/<key>.m3u8";
1118
const rtmpServerUrl = "rtmp://152.67.172.75/live/<key>";
@@ -36,12 +43,21 @@ const StreamInfoPopover = ({ hlsUrl, dashUrl, rtmpServer }: { hlsUrl: string, da
3643

3744
return (
3845
<div className="relative" ref={popoverRef}>
39-
<button
40-
onClick={() => setIsOpen(!isOpen)}
41-
className="p-2 rounded-full bg-gray-200/50 backdrop-blur-lg border border-gray-300 hover:bg-gray-300 dark:bg-gray-800/50 dark:border-gray-700 dark:hover:bg-gray-700 transition-colors"
42-
>
43-
<Signal size={20} className="text-gray-600 dark:text-gray-400" />
44-
</button>
46+
<div className="flex items-center gap-3 p-2 bg-gray-100/50 dark:bg-gray-900/50 rounded-xl backdrop-blur-lg border border-gray-300 dark:border-gray-700">
47+
<button
48+
onClick={() => setIsOpen(!isOpen)}
49+
className="p-2 rounded-full bg-gray-200/50 backdrop-blur-lg border border-gray-300 hover:bg-gray-300 dark:bg-gray-800/50 dark:border-gray-700 dark:hover:bg-gray-700 transition-colors"
50+
>
51+
<Signal size={20} className="text-gray-600 dark:text-gray-400" />
52+
</button>
53+
54+
<button
55+
onClick={() => window.location.reload()}
56+
className="p-2 rounded-full bg-gray-200/50 backdrop-blur-lg border border-gray-300 hover:bg-gray-300 dark:bg-gray-800/50 dark:border-gray-700 dark:hover:bg-gray-700 transition-colors"
57+
>
58+
<RotateCcw size={20} className="text-gray-600 dark:text-gray-400" />
59+
</button>
60+
</div>
4561

4662
{isOpen && (
4763
<div className="absolute left-1/2 -translate-x-1/2 mt-3 w-96 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl shadow-lg z-50 p-4 transition-all duration-300 ease-out transform scale-100 opacity-100">
@@ -121,6 +137,8 @@ function VideoPlayer({
121137
const videoRef = useRef<HTMLVideoElement>(null);
122138
const playerRef = useRef<any>(null);
123139
const [isLoading, setIsLoading] = useState(true);
140+
const [hasError, setHasError] = useState(false);
141+
124142

125143
useEffect(() => {
126144
if (videoRef.current) {
@@ -133,16 +151,36 @@ function VideoPlayer({
133151
playerRef.current = videojs(videoRef.current, {
134152
controls: true,
135153
autoplay: false,
154+
pip: true,
136155
preload: "auto",
137156
fluid: true,
138157
responsive: true,
158+
userActions: {
159+
hotkeys: true
160+
},
139161
sources: [{ src, type }],
140162
playbackRates: [0.5, 1, 1.25, 1.5, 2],
163+
poster: "https://i.ibb.co/PzxzqC7R/streamsoon.jpg", // thumbnail before playback
164+
aspectRatio: "16:9", // keep consistent aspect ratio
165+
html5: {
166+
hls: { overrideNative: true }, // HLS options
167+
nativeAudioTracks: false,
168+
nativeVideoTracks: false
169+
},
141170
plugins: {},
142171
});
143172

173+
videojs.addLanguage('en', {
174+
"Play": "▶ Play",
175+
"Pause": "⏸ Pause",
176+
"Video Not Supported": "Video cannot be played.",
177+
"The media could not be loaded, either because the server or network failed or because the format is not supported.":
178+
"Oops! Something went wrong or we are not live yet, check back later! 🤖",
179+
});
180+
144181
playerRef.current.ready(() => {
145182
setIsLoading(false);
183+
setHasError(false);
146184
});
147185

148186
playerRef.current.on('loadstart', () => {
@@ -151,10 +189,13 @@ function VideoPlayer({
151189

152190
playerRef.current.on('canplay', () => {
153191
setIsLoading(false);
192+
setHasError(false);
154193
});
155194

156-
playerRef.current.on('error', () => {
195+
playerRef.current.on("error", () => {
196+
console.error("Video.js player error:", playerRef.current.error());
157197
setIsLoading(false);
198+
setHasError(true); // Mark as error
158199
});
159200
}
160201

@@ -182,16 +223,27 @@ function VideoPlayer({
182223
style={{ display: hidden ? "none" : "block" }}
183224
>
184225
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-br from-gray-900 via-gray-800 to-black shadow-2xl dark:from-gray-950 dark:via-gray-900 dark:to-gray-800">
185-
{/* Stream info overlay */}
186-
<div className="absolute top-4 left-4 z-20 flex items-center gap-3">
187-
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-red-500/90 backdrop-blur-sm">
188-
<div className="w-2 h-2 bg-emerald-400 rounded-full animate-pulse"></div>
189-
<span className="text-emerald text-sm font-medium">LIVE</span>
190-
</div>
191-
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-black/40 backdrop-blur-sm text-white/90 dark:bg-white/10 dark:text-gray-200">
192-
<Users size={14} />
193-
</div>
226+
{/* Stream info overlay */}
227+
<div className="absolute top-4 left-4 z-20 flex items-center gap-3">
228+
<div
229+
className={`flex items-center gap-2 px-3 py-1.5 rounded-full backdrop-blur-sm ${
230+
hasError ? "bg-gray-500/80" : "bg-red-500/90"
231+
}`}
232+
>
233+
<div
234+
className={`w-2 h-2 rounded-full ${
235+
hasError ? "bg-red-600 animate-none" : "bg-emerald-400 animate-pulse"
236+
}`}
237+
></div>
238+
<span className={`text-sm font-medium ${hasError ? "text-white" : "text-emerald"}`}>
239+
{hasError ? "OFFLINE" : "LIVE"}
240+
</span>
241+
</div>
242+
243+
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-black/40 backdrop-blur-sm text-white/90 dark:bg-white/10 dark:text-gray-200">
244+
<Users size={14} />
194245
</div>
246+
</div>
195247

196248
{/* Stream quality badge */}
197249
<div className="absolute top-4 right-4 z-20">
@@ -351,6 +403,7 @@ export default function LivePage() {
351403
</div>
352404

353405
</div>
406+
354407
</div>
355408
);
356409
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"use client"
2+
3+
import { useTheme } from "next-themes"
4+
import { Toaster as Sonner } from "sonner"
5+
6+
type ToasterProps = React.ComponentProps<typeof Sonner>
7+
8+
const Toaster = ({ ...props }: ToasterProps) => {
9+
const { theme = "system" } = useTheme()
10+
11+
return (
12+
<Sonner
13+
theme={theme as ToasterProps["theme"]}
14+
className="toaster group"
15+
toastOptions={{
16+
classNames: {
17+
toast:
18+
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
19+
description: "group-[.toast]:text-muted-foreground",
20+
actionButton:
21+
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
22+
cancelButton:
23+
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
24+
},
25+
}}
26+
{...props}
27+
/>
28+
)
29+
}
30+
31+
export { Toaster }

0 commit comments

Comments
 (0)