New WebRTC and Video Player

This commit is contained in:
2025-10-02 18:46:37 +05:30
parent 0928f78a1f
commit 1063c4e9a9
9 changed files with 1495 additions and 60 deletions
+815
View File
@@ -0,0 +1,815 @@
---
import type { HTMLAttributes } from "astro/types";
import { cn } from "../utils/cn";
interface Props extends HTMLAttributes<"div"> {
videoUrl: string;
videoType: string;
autoPlay?: boolean;
muted?: boolean;
volume?: number;
overlayInfo?: string;
}
const {
videoUrl,
videoType,
class: className = "",
autoPlay = false,
muted = false,
volume = 0.75,
overlayInfo,
} = Astro.props;
---
<div class={cn("video-card", className)}>
<div class="video-wrap">
<video
id="video-player"
preload="metadata"
playsinline
autoplay={autoPlay}
muted
>
<source src={videoUrl} type={videoType} />
Your browser does not support the video tag.
</video>
<div id="loading-overlay">
<div class="spinner" aria-hidden="true"></div>
</div>
<div id="placeholder-overlay" aria-hidden="false">
<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 id="overlay">
<button id="overlay-play" class="btn" aria-label="Play/Pause"
><svg
width="36"
height="36"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.8"
>
<path
d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"
>
</path>
</svg>
</button>
</div>
<div
style="position:absolute; top:12px; right:12px; z-index:8; background:rgba(0,0,0,0.6); padding:6px 8px; border-radius:8px; font-size:12px;"
>
1080p • 30 FPS
</div>
</div>
<div class="controls-area">
<div class="progress-block">
<!-- progress slider -->
<div
id="progress-slider"
class="slider"
role="slider"
aria-label="Seek"
aria-valuemin="0"
aria-valuemax="100"
tabindex="0"
aria-valuenow="0"
>
<div class="track"></div>
<div class="buffered" id="buffered-range" style="width:0%">
</div>
<div class="fill" id="progress-range" style="width:0%"></div>
<div
class="thumb"
id="progress-thumb"
style="left:0%"
aria-hidden="true"
>
</div>
</div>
<div
style="display:flex; justify-content:space-between; margin-top:6px; font-size:13px; color:rgba(255,255,255,0.75)"
>
<div id="current-time">0:00</div>
<div id="max-duration">00:00</div>
</div>
</div>
<div class="controls-row">
<div class="controls-left">
<button id="play-pause" class="btn" aria-label="Play/Pause"
>▶</button
>
<button id="skip-back" class="btn" aria-label="Back 10s"
>⟲10s</button
>
<button id="skip-forward" class="btn" aria-label="Forward 10s"
>10s⟳</button
>
<div class="volume-wrap">
<button id="volume-btn" class="btn" aria-label="Toggle mute"
>🔊</button
>
<div
id="volume-slider"
class="slider volume-slider"
role="slider"
aria-label="Volume"
aria-valuemin="0"
aria-valuemax="100"
tabindex="0"
aria-valuenow={volume}
>
<div class="track"></div>
<div class="fill" id="volume-range" style="width:75%">
</div>
<div class="thumb" id="volume-thumb" style="left:75%">
</div>
</div>
</div>
</div>
<div style="display:flex; align-items:center; gap:8px;">
<select
id="speed-select"
aria-label="Playback speed"
style="background:#071028; color:#fff; border-radius:6px; padding:6px; border:0"
>
<option value="0.5">0.5x</option>
<option value="1" selected>1x</option>
<option value="1.25">1.25x</option>
<option value="1.5">1.5x</option>
<option value="2">2x</option>
</select>
<button id="fullscreen-btn" class="btn" aria-label="Fullscreen"
><svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M8 3H5a2 2 0 0 0-2 2v3"></path>
<path d="M21 8V5a2 2 0 0 0-2-2h-3"></path>
<path d="M3 16v3a2 2 0 0 0 2 2h3"></path>
<path d="M16 21h3a2 2 0 0 0 2-2v-3"></path>
</svg></button
>
</div>
</div>
</div>
</div>
<style>
:root {
--thumb-size: 16px;
}
.video-card {
position: relative;
border-radius: 12px;
overflow: hidden;
background: #000;
box-shadow: 0 8px 30px rgba(2, 6, 23, 0.6);
}
.video-wrap {
position: relative;
width: 100%;
aspect-ratio: 16/9;
background: black;
}
.controls-area {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(
to top,
rgba(0, 0, 0, 0.7),
rgba(0, 0, 0, 0)
);
background: rgba(0 0, 0, 0.5);
padding: 14px;
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
z-index: 999;
}
.video-card .controls-area {
opacity: 0 !important;
pointer-events: none !important;
}
.video-card.show-controls .controls-area {
opacity: 1 !important;
pointer-events: auto !important;
}
.video-card.show-controls #overlay {
opacity: 1;
pointer-events: auto;
}
video {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
background: black;
}
/* overlays */
#loading-overlay {
position: absolute;
inset: 0;
display: none;
align-items: center;
justify-content: center;
z-index: 10;
background: rgba(0, 0, 0, 0.6);
}
.spinner {
border: 4px solid rgba(255, 255, 255, 0.15);
border-top: 4px solid #fff;
border-radius: 50%;
width: 36px;
height: 36px;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
#placeholder-overlay {
position: absolute;
inset: 0;
display: flex;
gap: 12px;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 5;
}
#overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 6;
background: rgba(0, 0, 0, 0.15);
opacity: 0;
transition: opacity 0.2s;
}
.video.paused + #overlay {
opacity: 1;
pointer-events: auto;
}
.progress-block {
margin-bottom: 8px;
}
/* Generic slider styles (track + fill + thumb) */
.slider {
position: relative;
height: 18px;
cursor: pointer;
user-select: none;
}
.slider .track {
position: absolute;
left: 0;
right: 0;
top: 50%;
transform: translateY(-50%);
height: 6px;
background: #374151;
border-radius: 9999px;
}
.slider .buffered {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
height: 6px;
border-radius: 9999px;
background: rgba(99, 102, 241, 0.35);
width: 0%;
}
.slider .fill {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
height: 6px;
border-radius: 9999px;
background: linear-gradient(90deg, #1e40af, #3b82f6);
width: 0%;
}
.slider .thumb {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: var(--thumb-size);
height: var(--thumb-size);
border-radius: 50%;
background: #fff;
border: 2px solid #000;
box-shadow: 0 2px 6px rgba(2, 6, 23, 0.5);
}
.controls-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.controls-left {
display: flex;
align-items: center;
gap: 8px;
}
.btn {
background: transparent;
border: 0;
color: #fff;
padding: 8px;
cursor: pointer;
border-radius: 8px;
}
.btn:hover {
background: rgba(255, 255, 255, 0.03);
}
.small {
font-size: 13px;
color: rgba(255, 255, 255, 0.75);
}
/* Volume small slider */
.volume-wrap {
width: 140px;
display: flex;
align-items: center;
gap: 8px;
}
.volume-slider {
width: 100px;
}
/* simple responsive */
@media (max-width: 600px) {
.volume-wrap {
display: none;
}
}
</style>
<script type="module" is:inline define:vars={{ volume, muted }}>
// ---------- Fixed & improved JS ----------
document.addEventListener("DOMContentLoaded", () => {
const videoCard = document.querySelector(".video-card");
const video = document.getElementById("video-player");
const loadingOverlay = document.getElementById("loading-overlay");
const placeholder = document.getElementById("placeholder-overlay");
const overlay = document.getElementById("overlay");
const overlayPlay = document.getElementById("overlay-play");
const playPauseBtn = document.getElementById("play-pause");
const skipBack = document.getElementById("skip-back");
const skipForward = document.getElementById("skip-forward");
const volumeBtn = document.getElementById("volume-btn");
const volumeThumb = document.getElementById("volume-thumb");
const volumeRange = document.getElementById("volume-range");
const volumeSlider = document.getElementById("volume-slider");
const progressThumb = document.getElementById("progress-thumb");
const progressRange = document.getElementById("progress-range");
const bufferedRange = document.getElementById("buffered-range");
const progressSlider = document.getElementById("progress-slider");
const currentTimeEl = document.getElementById("current-time");
const maxDurationEl = document.getElementById("max-duration");
const speedSelect = document.getElementById("speed-select");
const fullscreenBtn = document.getElementById("fullscreen-btn");
let prevVolume = volume;
let draggingProgress = false;
let draggingVolume = false;
let controlsTimeout;
let lastMouseX = 0,
lastMouseY = 0;
let vol = Number(`${volume}`);
if (isFinite(vol) && vol >= 0 && vol <= 1) {
video.volume = vol;
} else {
video.volume = 0.75; // fallback
}
video.muted = muted;
// clamp helper
const clamp = (v, a, b) => Math.min(Math.max(v, a), b);
function formatTime(sec) {
if (!isFinite(sec) || isNaN(sec)) return "00:00";
sec = Math.floor(sec);
const h = Math.floor(sec / 3600);
const m = Math.floor((sec % 3600) / 60);
const s = sec % 60;
if (h > 0)
return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
return `${m}:${String(s).padStart(2, "0")}`;
}
// Update UI helpers
function updateProgressUI() {
if (!isFinite(video.duration) || video.duration === 0) return;
const percent = clamp(
(video.currentTime / video.duration) * 100,
0,
100,
);
progressRange.style.width = percent + "%";
progressThumb.style.left = percent + "%";
progressSlider.setAttribute(
"aria-valuenow",
String(Math.floor(video.currentTime)),
);
currentTimeEl.textContent = formatTime(video.currentTime);
}
function updateBuffered() {
if (!isFinite(video.duration) || video.duration === 0) return;
if (video.buffered && video.buffered.length) {
const end = video.buffered.end(video.buffered.length - 1);
const percent = clamp((end / video.duration) * 100, 0, 100);
bufferedRange.style.width = percent + "%";
}
}
function updateVolumeUI() {
const volPercent = clamp(
video.muted ? 0 : Math.round((video.volume || 0) * 100),
0,
100,
);
volumeRange.style.width = volPercent + "%";
volumeThumb.style.left = volPercent + "%";
volumeSlider.setAttribute("aria-valuenow", String(volPercent));
volumeSlider.setAttribute("aria-valuetext", volPercent + "%");
// update icon
if (volPercent === 0) volumeBtn.textContent = "🔈";
else if (volPercent < 50) volumeBtn.textContent = "🔉";
else volumeBtn.textContent = "🔊";
}
// --- Initial setup ---
video.volume = prevVolume;
updateVolumeUI();
video.addEventListener("loadedmetadata", () => {
maxDurationEl.textContent = formatTime(video.duration);
progressSlider.setAttribute(
"aria-valuemax",
String(Math.floor(video.duration)),
);
updateProgressUI();
updateBuffered();
video.playbackRate = parseFloat(speedSelect.value) || 1;
});
// play/pause logic
function setPlayIcon(isPlaying) {
playPauseBtn.textContent = isPlaying ? "⏸" : "▶";
}
function togglePlayPause() {
if (video.paused) {
video.play().catch(() => {});
setOverlayIcon(true);
} else {
video.pause();
setOverlayIcon(false);
}
}
video.addEventListener("click", togglePlayPause);
overlay.addEventListener("click", togglePlayPause);
overlayPlay.addEventListener("click", togglePlayPause);
playPauseBtn.addEventListener("click", togglePlayPause);
video.addEventListener("play", () => {
setPlayIcon(true);
setOverlayIcon(true);
placeholder.style.display = "none";
loadingOverlay.style.display = "none";
showControls();
});
video.addEventListener("pause", () => {
setPlayIcon(false);
setOverlayIcon(false);
videoCard.classList.add("show-controls");
videoCard.style.cursor = "default";
clearTimeout(controlsTimeout);
});
function setOverlayIcon(isPlaying) {
if (isPlaying) {
overlayPlay.innerHTML = `
<svg width="36" height="36" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="6" y="4" width="4" height="16"></rect>
<rect x="14" y="4" width="4" height="16"></rect>
</svg>`; // pause icon
} else {
overlayPlay.innerHTML = `
<svg width="36" height="36" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 3l14 9-14 9V3z"></path>
</svg>`; // play icon
}
}
video.addEventListener("timeupdate", updateProgressUI);
video.addEventListener("progress", updateBuffered);
video.addEventListener("canplay", () => {
loadingOverlay.style.display = "none";
updateBuffered();
});
video.addEventListener("waiting", () => {
loadingOverlay.style.display = "flex";
});
video.addEventListener("loadstart", () => {
loadingOverlay.style.display = "flex";
});
// Seeking (supports pointer and keyboard)
function handlePointerPositionOnSlider(evt, sliderEl) {
const rect = sliderEl.getBoundingClientRect();
const clientX =
evt.touches && evt.touches[0]
? evt.touches[0].clientX
: evt.clientX;
const percent = clamp(
((clientX - rect.left) / rect.width) * 100,
0,
100,
);
return percent;
}
// progress pointer handling
progressSlider.addEventListener("pointerdown", (e) => {
e.preventDefault();
draggingProgress = true;
progressSlider.setPointerCapture(e.pointerId);
const p = handlePointerPositionOnSlider(e, progressSlider);
const t = (p / 100) * video.duration || 0;
video.currentTime = t;
updateProgressUI();
});
progressSlider.addEventListener("pointermove", (e) => {
if (!draggingProgress) return;
const p = handlePointerPositionOnSlider(e, progressSlider);
const t = (p / 100) * video.duration || 0;
video.currentTime = t;
updateProgressUI();
});
progressSlider.addEventListener("pointerup", (e) => {
draggingProgress = false;
try {
progressSlider.releasePointerCapture(e.pointerId);
} catch {}
});
// keyboard support for seek
progressSlider.addEventListener("keydown", (e) => {
if (!isFinite(video.duration)) return;
if (e.key === "ArrowLeft") {
video.currentTime = clamp(
video.currentTime - 5,
0,
video.duration,
);
}
if (e.key === "ArrowRight") {
video.currentTime = clamp(
video.currentTime + 5,
0,
video.duration,
);
}
});
// skip buttons
skipBack.addEventListener("click", () => {
video.currentTime = clamp(
video.currentTime - 10,
0,
video.duration || Infinity,
);
});
skipForward.addEventListener("click", () => {
video.currentTime = clamp(
video.currentTime + 10,
0,
video.duration || Infinity,
);
});
// Volume interactions
volumeSlider.addEventListener("pointerdown", (e) => {
e.preventDefault();
draggingVolume = true;
volumeSlider.setPointerCapture(e.pointerId);
const p = handlePointerPositionOnSlider(e, volumeSlider);
video.volume = p / 100;
video.muted = p === 0;
prevVolume = video.volume || prevVolume;
updateVolumeUI();
});
volumeSlider.addEventListener("pointermove", (e) => {
if (!draggingVolume) return;
const p = handlePointerPositionOnSlider(e, volumeSlider);
video.volume = p / 100;
video.muted = p === 0;
prevVolume = video.volume || prevVolume;
updateVolumeUI();
});
volumeSlider.addEventListener("pointerup", (e) => {
draggingVolume = false;
try {
volumeSlider.releasePointerCapture(e.pointerId);
} catch {}
});
// click on volume track
volumeSlider.addEventListener("click", (e) => {
const p = handlePointerPositionOnSlider(e, volumeSlider);
video.volume = p / 100;
video.muted = p === 0;
prevVolume = video.volume || prevVolume;
updateVolumeUI();
});
// toggle mute
volumeBtn.addEventListener("click", () => {
if (video.muted || video.volume === 0) {
video.muted = false;
video.volume = prevVolume || 0.75;
} else {
prevVolume = video.volume || prevVolume;
video.muted = true;
}
updateVolumeUI();
});
// sync on change
video.addEventListener("volumechange", updateVolumeUI);
// playback speed
speedSelect.addEventListener("change", () => {
video.playbackRate = parseFloat(speedSelect.value);
});
// fullscreen
fullscreenBtn.addEventListener("click", async () => {
try {
if (!document.fullscreenElement) {
await videoCard.requestFullscreen();
} else {
await document.exitFullscreen();
}
} catch (err) {
console.error("Fullscreen error:", err);
}
});
function showControls() {
videoCard.classList.add("show-controls");
videoCard.style.cursor = "default";
clearTimeout(controlsTimeout);
if (!video.paused) {
controlsTimeout = setTimeout(() => {
videoCard.classList.remove("show-controls");
// Hide cursor too in fullscreen
if (document.fullscreenElement === videoCard) {
videoCard.style.cursor = "none";
}
}, 3000);
}
}
videoCard.addEventListener("mousemove", (e) => {
if (e.clientX !== lastMouseX || e.clientY !== lastMouseY) {
showControls();
lastMouseX = e.clientX;
lastMouseY = e.clientY;
}
});
videoCard.addEventListener("click", showControls);
document.addEventListener("fullscreenchange", () => {
if (!document.fullscreenElement) {
// videoCard.classList.add('show-controls');
// videoCard.style.cursor = 'default';
// clearTimeout(controlsTimeout);
showControls();
}
});
// keyboard shortcuts
document.addEventListener("keydown", (e) => {
// prevent interfering when typing in inputs/selects
const tag =
document.activeElement &&
document.activeElement.tagName.toLowerCase();
if (tag === "input" || tag === "textarea" || tag === "select")
return;
if (e.code === "Space") {
e.preventDefault();
togglePlayPause();
}
if (e.code === "ArrowLeft") {
video.currentTime = clamp(
video.currentTime - 10,
0,
video.duration || Infinity,
);
}
if (e.code === "ArrowRight") {
video.currentTime = clamp(
video.currentTime + 10,
0,
video.duration || Infinity,
);
}
if (e.code === "KeyM") {
if (video.muted) {
video.muted = false;
video.volume = prevVolume || 0.75;
} else {
prevVolume = video.volume || prevVolume;
video.muted = true;
}
updateVolumeUI();
}
});
// pointerup anywhere to cancel drags (safety)
document.addEventListener("pointerup", () => {
draggingProgress = false;
draggingVolume = false;
});
});
</script>
+247
View File
@@ -0,0 +1,247 @@
---
import type { HTMLAttributes } from "astro/types";
import { cn } from "../utils/cn";
interface Props extends HTMLAttributes<"div"> {
streamUrl: string;
poster?: string;
checkStreamFn?: string;
retryInterval?: number;
}
const {
streamUrl,
poster,
class: className,
checkStreamFn,
retryInterval = 5000,
} = Astro.props;
---
<style>
.video-card {
position: relative;
background: #000;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 8px 30px rgba(2, 6, 23, 0.6);
}
.video-wrap {
position: relative;
width: 100%;
aspect-ratio: 16/9;
background: black;
display: flex;
align-items: center;
justify-content: center;
}
.live-badge {
position: absolute;
top: 12px;
left: 12px;
background: red;
color: white;
font-size: 12px;
font-weight: bold;
padding: 4px 8px;
border-radius: 4px;
z-index: 10;
}
.overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 20;
background: rgba(0, 0, 0, 0.5);
color: white;
font-size: 14px;
text-align: center;
padding: 12px;
display: none;
}
.overlay.show {
display: flex;
}
/* Controls bar */
.controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 8px 12px;
background: rgba(0, 0, 0, 0.4);
z-index: 15;
}
.btn {
background: transparent;
border: 0;
color: white;
cursor: pointer;
font-size: 16px;
padding: 6px;
}
.btn:hover {
background: rgba(255, 255, 255, 0.2);
border-radius: 6px;
}
</style>
<div
class={cn("video-card", className)}
data-checkStreamFn={checkStreamFn ?? ""}
data-retryInterval={retryInterval}
>
<div class="video-wrap">
<video
id="live-video"
autoplay
playsinline
muted
poster={poster}
data-stream={streamUrl}></video>
<div class="live-badge">LIVE</div>
<div id="overlay" class="overlay"></div>
<div class="controls">
<button id="volume-btn" class="btn" aria-label="Mute/Unmute"
>🔇</button
>
<button id="fullscreen-btn" class="btn" aria-label="Fullscreen"
>⤢</button
>
</div>
</div>
</div>
<script>
import { WebRTCPlayer } from "@eyevinn/webrtc-player";
document.addEventListener("DOMContentLoaded", () => {
const videoCardEl = document.getElementById("video-card");
const videoEl = document.getElementById("live-video");
const overlayEl = document.getElementById("overlay");
const volumeBtn = document.getElementById("volume-btn");
const fullscreenBtn = document.getElementById("fullscreen-btn");
let player: any;
let retryTimer: any;
let streamUrl = videoEl?.dataset.stream;
const showOverlay = (msg: string) => {
overlayEl!.textContent = msg;
overlayEl!.classList.add("show");
};
const hideOverlay = () => overlayEl!.classList.remove("show");
showOverlay("Connecting to live stream…");
async function startPlayer() {
try {
if (videoEl)
player = new WebRTCPlayer({
video: videoEl as HTMLVideoElement,
type: "whep",
statsTypeFilter: "^candidate-*|^inbound-rtp",
});
player.on("media-recovered", () => {
hideOverlay();
});
player.on("peer-connection-connected", () => {
hideOverlay();
});
player.on("no-media", () => {
showOverlay(
"Stream not started yet or Stream already ended",
);
});
player.on("error", (err: any) => {
showOverlay("Error: " + err.message);
});
await player.load(new URL(streamUrl || ""));
player.unmute();
} catch (err: any) {
showOverlay("Failed to initialize: " + err.message);
}
}
async function checkAndStart() {
try {
const checkStreamFn: any =
videoCardEl?.dataset.checkStreamFn || "";
if (checkStreamFn) {
const fn: any = window[checkStreamFn];
const status = await fn();
if (status.ok) {
clearTimeout(retryTimer);
startPlayer();
hideOverlay();
} else {
showOverlay(status.message);
scheduleRetry();
}
} else {
clearTimeout(retryTimer);
startPlayer();
hideOverlay();
}
} catch (err: any) {
showOverlay("Check failed: " + err.message);
scheduleRetry();
}
}
function scheduleRetry() {
clearTimeout(retryTimer);
const retryInterval: number = parseInt(
videoCardEl?.dataset.retryInterval || "5000",
);
retryTimer = setTimeout(checkAndStart, retryInterval);
}
checkAndStart();
(videoEl as HTMLVideoElement).volume = 0;
(videoEl as HTMLVideoElement).muted = true;
volumeBtn!.textContent = "🔇";
volumeBtn!.addEventListener("click", () => {
if (
(videoEl as HTMLVideoElement).muted ||
(videoEl as HTMLVideoElement).volume === 0
) {
(videoEl as HTMLVideoElement).muted = false;
(videoEl as HTMLVideoElement).volume = 1.0;
volumeBtn!.textContent = "🔊";
} else {
(videoEl as HTMLVideoElement).muted = true;
volumeBtn!.textContent = "🔇";
}
});
fullscreenBtn!.addEventListener("click", async () => {
const videoCard = document.querySelector(".video-card");
if (!document.fullscreenElement) {
await videoCard!.requestFullscreen();
} else {
await document.exitFullscreen();
}
});
});
</script>
+6
View File
@@ -74,6 +74,12 @@ import Base from "./Base.astro";
<li class="list-item">
<a href="/panel">Panel</a>
</li>
<li class="list-item">
<a href="/video-player">Video Player</a>
</li>
<li class="list-item">
<a href="/webrtc-player">WebRTC Player</a>
</li>
</ul>
</div>
+10 -6
View File
@@ -4,7 +4,7 @@ import Panel from "../components/Panel.astro";
---
<ComponentLayout class="wr:p-4">
<div class="wr:h-full wr:w-full wr:relative wr:bg-yellow-200">
<div class="wr:h-full wr:w-full wr:relative wr:bg-orange-800 wr:p-4">
<div id="panel-wrapper">
<button id="openPanelBtn"> Open Panel </button>
@@ -20,12 +20,16 @@ import Panel from "../components/Panel.astro";
id="settings"
isOpen={true}
width={400}
position="left"
class="wr:bg-neutral-700 wr:p-4"
position="right"
class="wr:bg-transparent wr:p-4"
>
<button id=`close-panel-settings`> Close </button>
<h2>Settings Panel</h2>
<p>This is a settings panel with a width of 400px.</p>
<div
class="wr:bg-neutral-700 wr:w-full wr:h-full wr:rounded-2xl wr:p-4"
>
<button id=`close-panel-settings`> Close </button>
<h2>Settings Panel</h2>
<p>This is a settings panel with a width of 400px.</p>
</div>
</Panel>
</div>
</ComponentLayout>
+14
View File
@@ -0,0 +1,14 @@
---
import VideoPlayer from "../components/VideoPlayer.astro";
import ComponentLayout from "../layouts/ComponentLayout.astro";
---
<ComponentLayout>
<VideoPlayer
videoUrl="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4"
videoType="video/mp4"
class="wr:w-[500px]"
muted={false}
volume={0.7}
/>
</ComponentLayout>
+11
View File
@@ -0,0 +1,11 @@
---
import WebRtcPlayer from "../components/WebRtcPlayer.astro";
import ComponentLayout from "../layouts/ComponentLayout.astro";
---
<ComponentLayout>
<WebRtcPlayer
streamUrl="https://stream-webrtc.workroot.in/live/t05Pbqv01N/whep?token=1OZMx5f8WL"
class="wr:w-[500px]"
/>
</ComponentLayout>