Added FLV Player

This commit is contained in:
2025-10-08 01:10:13 +05:30
parent f6be5084ad
commit 40ae8d5f3d
7 changed files with 282 additions and 38 deletions
+207
View File
@@ -0,0 +1,207 @@
---
import type { HTMLAttributes } from "astro/types";
import { cn } from "../utils/cn";
interface Props extends HTMLAttributes<"div"> {
videoUrl: string;
autoPlay?: boolean;
muted?: boolean;
volume?: number;
overlayInfo?: string;
poster?: string;
}
const {
videoUrl,
class: className = "",
autoPlay = true,
muted = true,
volume = 0.75,
overlayInfo = "Live Stream",
poster = "",
} = Astro.props;
const id = `vp_${Math.random().toString(36).slice(2)}`;
---
<div id={`${id}_card`} class={cn("video-card", className)}>
<div class="video-wrap">
<video
id={`${id}_video`}
preload="metadata"
playsinline
{poster}
autoplay={autoPlay}
muted={muted}></video>
<!-- overlays -->
<div id={`${id}_loading`} class="loading-overlay">
<div class="spinner"></div>
</div>
<div id={`${id}_placeholder`} class="placeholder-overlay">
<svg
width="64"
height="64"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
style="opacity:.7"
>
<rect x="2" y="6" width="14" height="12" rx="2"></rect>
<path
d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5"
></path>
</svg>
<div class="small">{overlayInfo}</div>
</div>
</div>
<!-- Controls -->
<div class="controls">
<div class="volume-wrap">
<button id={`${id}_volbtn`} class="btn" aria-label="Toggle mute"
>🔊</button
>
</div>
<button id={`${id}_fsbtn`} class="btn" aria-label="Fullscreen">⤢</button
>
</div>
</div>
<style>
.video-card {
position: relative;
width: 100%;
aspect-ratio: 16/9;
background: #000;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 8px 30px rgba(2, 6, 23, 0.6);
}
video {
width: 100%;
height: 100%;
object-fit: contain;
}
.loading-overlay,
.placeholder-overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 5;
}
.loading-overlay {
display: none;
background: rgba(0, 0, 0, 0.6);
}
.spinner {
border: 4px solid rgba(255, 255, 255, 0.2);
border-top: 4px solid #fff;
border-radius: 50%;
width: 36px;
height: 36px;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 10px;
background: linear-gradient(to top, rgba(0, 0, 0, 0.6), transparent);
display: flex;
align-items: center;
justify-content: space-between;
z-index: 10;
}
</style>
<script is:inline define:vars={{ videoUrl, autoPlay, muted, volume, id }}>
document.addEventListener("DOMContentLoaded", () => {
const get = (suffix) => document.getElementById(`${id}_${suffix}`);
const card = get("card");
const video = get("video");
const loading = get("loading");
const placeholder = get("placeholder");
const volumeBtn = get("volbtn");
const fullscreenBtn = get("fsbtn");
const flvjs = window.flvjs;
// Load FLV or fallback
if (
(flvjs && flvjs.isSupported() && videoUrl.endsWith(".flv")) ||
videoUrl.includes(":8089/")
) {
const player = flvjs.createPlayer({
type: "flv",
isLive: true,
url: videoUrl,
});
player.attachMediaElement(video);
player.load();
player.play().catch(() => {
console.log(
"[INFO] Autoplay blocked, waiting for user gesture.",
);
});
} else {
video.src = videoUrl;
}
// Initial settings
video.volume = volume || 0.75;
video.muted = muted;
// Events
video.addEventListener(
"waiting",
() => (loading.style.display = "flex"),
);
video.addEventListener("canplay", () => {
loading.style.display = "none";
placeholder.style.display = "none";
});
const updateVolIcon = () => {
const val = video.muted ? 0 : Math.round(video.volume * 100);
volumeBtn.textContent = val === 0 ? "🔈" : val < 50 ? "🔉" : "🔊";
};
// --- Handle all mute/unmute logic robustly ---
volumeBtn.addEventListener("click", async () => {
try {
if (video.paused) await video.play().catch(() => {});
video.muted = !video.muted;
if (!video.muted) {
await video.play().catch(() => {});
}
updateVolIcon();
} catch (err) {
console.warn("[Volume] Toggle error:", err);
}
});
updateVolIcon();
// Fullscreen toggle
fullscreenBtn.addEventListener("click", async () => {
if (!document.fullscreenElement) await card.requestFullscreen();
else await document.exitFullscreen();
});
});
</script>
+2 -2
View File
@@ -4,7 +4,7 @@ import { cn } from "../utils/cn";
interface Props extends HTMLAttributes<"div"> {
videoUrl: string;
videoType: string;
videoType?: string;
isLive?: boolean;
autoPlay?: boolean;
muted?: boolean;
@@ -15,7 +15,7 @@ interface Props extends HTMLAttributes<"div"> {
const {
videoUrl,
videoType,
videoType = "application/x-mpegURL",
isLive = false,
class: className = "",
autoPlay = false,
+3 -1
View File
@@ -1,3 +1,4 @@
import type FlvJs from 'flv.js';
import Hls from 'hls.js';
declare global {
@@ -7,7 +8,8 @@ declare global {
upload: any;
myModal_open: any;
myModal_close: any;
hls: typeof Hls
hls: typeof Hls,
flvjs: typeof FlvJs
}
}
+10 -1
View File
@@ -1,21 +1,30 @@
---
import FLVPLayer from "../components/FLVPLayer.astro";
import HLSPLayer from "../components/HLSPLayer.astro";
import ComponentLayout from "../layouts/ComponentLayout.astro";
---
<ComponentLayout>
<HLSPLayer
<!-- <HLSPLayer
videoUrl="https://stream-middleware.workroot.in/hls/live/t05Pbqv01N/2025-10-04_15-26-30/master.m3u8"
poster="https://stream-middleware.workroot.in/thumbnail/live/t05Pbqv01N/2025-10-04_15-26-30.jpg"
videoType="application/x-mpegURL"
class="wr:w-[500px]"
muted={false}
volume={0.7}
/> -->
<FLVPLayer
videoUrl="http://127.0.0.1:8089/live/hIaBJegynP?token=p9HmND9I6r"
class="wr:w-[500px]"
muted={true}
volume={0.7}
/>
</ComponentLayout>
<script>
import Hls from "hls.js";
import FlvJs from "flv.js";
window.hls = Hls;
window.flvjs = FlvJs;
</script>