Added FLV Player
This commit is contained in:
@@ -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>
|
||||
@@ -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,
|
||||
|
||||
Vendored
+3
-1
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user