update display

This commit is contained in:
Flatlogic Bot 2026-04-15 07:14:44 +00:00
parent 3ec25535e5
commit bfd0f1a7e8
2 changed files with 365 additions and 154 deletions

View File

@ -4,17 +4,45 @@ require_once __DIR__ . '/queue_bootstrap.php';
qh_boot();
qh_admin_handle_request();
// Ensure the table exists
// Ensure the tables exist
$pdo = db();
function qh_ads_column_exists(PDO $pdo, string $column): bool
{
static $cache = [];
if (array_key_exists($column, $cache)) {
return $cache[$column];
}
$stmt = $pdo->prepare(
"SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = 'hospital_ads' AND COLUMN_NAME = ?"
);
$stmt->execute([DB_NAME, $column]);
return $cache[$column] = ((int) $stmt->fetchColumn() > 0);
}
$pdo->exec("CREATE TABLE IF NOT EXISTS hospital_ads (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NULL,
video_path VARCHAR(255) NOT NULL,
media_type VARCHAR(20) NOT NULL DEFAULT 'video',
media_path VARCHAR(255) NULL,
is_active TINYINT(1) DEFAULT 1,
sort_order INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
if (!qh_ads_column_exists($pdo, 'media_type')) {
$pdo->exec("ALTER TABLE hospital_ads ADD COLUMN media_type VARCHAR(20) NOT NULL DEFAULT 'video' AFTER video_path");
}
if (!qh_ads_column_exists($pdo, 'media_path')) {
$pdo->exec("ALTER TABLE hospital_ads ADD COLUMN media_path VARCHAR(255) NULL AFTER media_type");
}
$pdo->exec("UPDATE hospital_ads SET media_type = 'video' WHERE media_type IS NULL OR media_type = ''");
$pdo->exec("UPDATE hospital_ads SET media_path = video_path WHERE (media_path IS NULL OR media_path = '') AND video_path <> ''");
$pdo->exec("CREATE TABLE IF NOT EXISTS hospital_news (
id INT AUTO_INCREMENT PRIMARY KEY,
phrase VARCHAR(1000) NOT NULL,
@ -26,18 +54,30 @@ $pdo->exec("CREATE TABLE IF NOT EXISTS hospital_news (
// Handle Form Submissions
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action']) && $_POST['action'] === 'add_video' && !empty($_FILES['video']['name'])) {
$uploadDir = __DIR__ . '/assets/videos/uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
$ext = strtolower(pathinfo($_FILES['video']['name'], PATHINFO_EXTENSION));
$allowed = ['mp4', 'webm', 'ogg'];
if (!in_array($ext, $allowed)) {
$message = qh_t("Invalid video format (.$ext). Allowed formats: MP4, WebM, OGG.", "تنسيق فيديو غير صالح (.$ext). الصيغ المسموحة: MP4, WebM, OGG.");
} elseif ($_FILES['video']['error'] !== UPLOAD_ERR_OK) {
$action = (string) ($_POST['action'] ?? '');
$adId = (int) ($_POST['ad_id'] ?? $_POST['video_id'] ?? 0);
if ($action === 'add_media' && !empty($_FILES['media']['name'])) {
$mediaFile = $_FILES['media'];
$title = trim((string) ($_POST['title'] ?? ''));
$ext = strtolower(pathinfo($mediaFile['name'], PATHINFO_EXTENSION));
$typeMap = [
'mp4' => 'video',
'webm' => 'video',
'ogg' => 'video',
'jpg' => 'image',
'jpeg' => 'image',
'png' => 'image',
'webp' => 'image',
'gif' => 'image',
];
if (!isset($typeMap[$ext])) {
$message = qh_t(
"Invalid media format (.$ext). Allowed formats: JPG, JPEG, PNG, WEBP, GIF, MP4, WebM, OGG.",
"تنسيق وسائط غير صالح (.$ext). الصيغ المسموحة: JPG, JPEG, PNG, WEBP, GIF, MP4, WebM, OGG."
);
} elseif ($mediaFile['error'] !== UPLOAD_ERR_OK) {
$errorMap = [
UPLOAD_ERR_INI_SIZE => 'File exceeds max size in php.ini.',
UPLOAD_ERR_FORM_SIZE => 'File exceeds max size in HTML form.',
@ -45,53 +85,66 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder.',
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.',
UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the upload.'
UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the upload.',
];
$errMsg = $errorMap[$_FILES['video']['error']] ?? 'Unknown error';
$errMsg = $errorMap[$mediaFile['error']] ?? 'Unknown error';
$message = qh_t('Upload error: ' . $errMsg, 'خطأ في الرفع: ' . $errMsg);
} else {
$filename = uniqid('vid_') . '.' . $ext;
$mediaType = $typeMap[$ext];
$uploadDir = __DIR__ . ($mediaType === 'video' ? '/assets/videos/uploads/' : '/assets/images/uploads/');
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
$filename = uniqid($mediaType === 'video' ? 'vid_' : 'img_') . '.' . $ext;
$dest = $uploadDir . $filename;
if (move_uploaded_file($_FILES['video']['tmp_name'], $dest)) {
$title = $_POST['title'] ?? '';
$path = 'assets/videos/uploads/' . $filename;
$stmt = $pdo->prepare("INSERT INTO hospital_ads (title, video_path, is_active) VALUES (?, ?, 1)");
$stmt->execute([$title, $path]);
$message = qh_t('Video uploaded successfully.', 'تم رفع الفيديو بنجاح.');
if (move_uploaded_file($mediaFile['tmp_name'], $dest)) {
$path = ($mediaType === 'video' ? 'assets/videos/uploads/' : 'assets/images/uploads/') . $filename;
$legacyVideoPath = $mediaType === 'video' ? $path : '';
$stmt = $pdo->prepare(
"INSERT INTO hospital_ads (title, video_path, media_type, media_path, is_active) VALUES (?, ?, ?, ?, 1)"
);
$stmt->execute([$title, $legacyVideoPath, $mediaType, $path]);
$message = $mediaType === 'video'
? qh_t('Video uploaded successfully.', 'تم رفع الفيديو بنجاح.')
: qh_t('Image uploaded successfully.', 'تم رفع الصورة بنجاح.');
} else {
$message = qh_t('Failed to save uploaded file.', 'فشل في حفظ الملف المرفوع.');
}
}
} elseif (isset($_POST['action']) && $_POST['action'] === 'delete_video' && !empty($_POST['video_id'])) {
$id = (int) $_POST['video_id'];
$stmt = $pdo->prepare("SELECT video_path FROM hospital_ads WHERE id = ?");
$stmt->execute([$id]);
$video = $stmt->fetch();
if ($video) {
$fullPath = __DIR__ . '/' . $video['video_path'];
if (file_exists($fullPath)) {
unlink($fullPath);
} elseif ($action === 'delete_media' && $adId > 0) {
$stmt = $pdo->prepare(
"SELECT COALESCE(NULLIF(media_path, ''), NULLIF(video_path, '')) AS file_path FROM hospital_ads WHERE id = ?"
);
$stmt->execute([$adId]);
$media = $stmt->fetch();
if ($media) {
$filePath = (string) ($media['file_path'] ?? '');
if ($filePath !== '') {
$fullPath = __DIR__ . '/' . $filePath;
if (file_exists($fullPath)) {
unlink($fullPath);
}
}
$pdo->prepare("DELETE FROM hospital_ads WHERE id = ?")->execute([$id]);
$message = qh_t('Video deleted successfully.', 'تم حذف الفيديو بنجاح.');
$pdo->prepare("DELETE FROM hospital_ads WHERE id = ?")->execute([$adId]);
$message = qh_t('Ad deleted successfully.', 'تم حذف الإعلان بنجاح.');
}
} elseif (isset($_POST['action']) && $_POST['action'] === 'toggle_status' && !empty($_POST['video_id'])) {
$id = (int) $_POST['video_id'];
} elseif ($action === 'toggle_status' && $adId > 0) {
$stmt = $pdo->prepare("UPDATE hospital_ads SET is_active = NOT is_active WHERE id = ?");
$stmt->execute([$id]);
$message = qh_t('Video status updated.', 'تم تحديث حالة الفيديو.');
} elseif (isset($_POST['action']) && $_POST['action'] === 'add_news' && !empty($_POST['phrase'])) {
$stmt->execute([$adId]);
$message = qh_t('Ad status updated.', 'تم تحديث حالة الإعلان.');
} elseif ($action === 'add_news' && !empty($_POST['phrase'])) {
$phrase = trim($_POST['phrase']);
$stmt = $pdo->prepare("INSERT INTO hospital_news (phrase, is_active) VALUES (?, 1)");
$stmt->execute([$phrase]);
$message = qh_t('Phrase added successfully.', 'تمت إضافة العبارة بنجاح.');
} elseif (isset($_POST['action']) && $_POST['action'] === 'delete_news' && !empty($_POST['news_id'])) {
} elseif ($action === 'delete_news' && !empty($_POST['news_id'])) {
$id = (int) $_POST['news_id'];
$pdo->prepare("DELETE FROM hospital_news WHERE id = ?")->execute([$id]);
$message = qh_t('Phrase deleted successfully.', 'تم حذف العبارة بنجاح.');
} elseif (isset($_POST['action']) && $_POST['action'] === 'toggle_news_status' && !empty($_POST['news_id'])) {
} elseif ($action === 'toggle_news_status' && !empty($_POST['news_id'])) {
$id = (int) $_POST['news_id'];
$pdo->prepare("UPDATE hospital_news SET is_active = NOT is_active WHERE id = ?")->execute([$id]);
$message = qh_t('Phrase status updated.', 'تم تحديث حالة العبارة.');
@ -99,7 +152,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
// Fetch existing ads
$stmt = $pdo->query("SELECT * FROM hospital_ads ORDER BY sort_order ASC, id DESC");
$stmt = $pdo->query(
"SELECT *,
COALESCE(NULLIF(media_type, ''), 'video') AS effective_media_type,
COALESCE(NULLIF(media_path, ''), NULLIF(video_path, '')) AS effective_media_path
FROM hospital_ads
ORDER BY sort_order ASC, id DESC"
);
$ads = $stmt->fetchAll();
$stmt2 = $pdo->query("SELECT * FROM hospital_news ORDER BY sort_order ASC, id DESC");
@ -108,7 +167,7 @@ $newsPhrases = $stmt2->fetchAll();
qh_page_start(
'admin_ads',
qh_t('Manage Advertisements', 'إدارة الإعلانات'),
qh_t('Upload and manage videos to display on the queue screen.', 'رفع وإدارة الفيديوهات لعرضها على شاشة الطابور.')
qh_t('Upload and manage image and video ads for the queue display.', 'رفع وإدارة إعلانات الصور والفيديوهات لعرضها على شاشة الطابور.')
);
?>
<div class="container-fluid container-xxl px-3 px-lg-4">
@ -120,8 +179,8 @@ qh_page_start(
<div class="admin-content-stack">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-0 text-gray-800 fw-bold"><?= qh_h(qh_t('Advertisements (Videos)', 'الإعلانات (فيديوهات)')) ?></h1>
<p class="text-muted mb-0 mt-1"><?= qh_h(qh_t('These videos will be played sequentially on the display screen.', 'سيتم تشغيل هذه الفيديوهات بالتتابع على شاشة العرض.')) ?></p>
<h1 class="h3 mb-0 text-gray-800 fw-bold"><?= qh_h(qh_t('Advertisements (Images & Videos)', 'الإعلانات (صور وفيديوهات)')) ?></h1>
<p class="text-muted mb-0 mt-1"><?= qh_h(qh_t('Videos play until they end, while images stay on screen for 10 seconds in the same rotation.', 'تعمل الفيديوهات حتى نهايتها، بينما تبقى الصور على الشاشة لمدة 10 ثوانٍ ضمن نفس التدوير.')) ?></p>
</div>
</div>
@ -131,22 +190,22 @@ qh_page_start(
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white border-bottom py-3">
<h5 class="mb-0 font-weight-bold text-dark"><?= qh_h(qh_t('Upload New Video', 'رفع فيديو جديد')) ?></h5>
<h5 class="mb-0 font-weight-bold text-dark"><?= qh_h(qh_t('Upload New Ad Media', 'رفع وسائط إعلان جديدة')) ?></h5>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data" class="row g-3">
<input type="hidden" name="action" value="add_video">
<input type="hidden" name="action" value="add_media">
<div class="col-md-6">
<label class="form-label fw-semibold text-dark"><?= qh_h(qh_t('Video Title (Optional)', 'عنوان الفيديو (اختياري)')) ?></label>
<label class="form-label fw-semibold text-dark"><?= qh_h(qh_t('Ad Title (Optional)', 'عنوان الإعلان (اختياري)')) ?></label>
<input type="text" name="title" class="form-control" placeholder="<?= qh_h(qh_t('e.g. Summer Promo', 'مثال: عرض الصيف')) ?>">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold text-dark"><?= qh_h(qh_t('Video File', 'ملف الفيديو')) ?></label>
<input type="file" name="video" class="form-control" accept="video/mp4,video/webm,video/ogg" required>
<div class="form-text"><?= qh_h(qh_t('Formats: MP4, WebM, OGG.', 'الصيغ: MP4, WebM, OGG.')) ?></div>
<label class="form-label fw-semibold text-dark"><?= qh_h(qh_t('Image or Video File', 'ملف صورة أو فيديو')) ?></label>
<input type="file" name="media" class="form-control" accept="image/jpeg,image/png,image/webp,image/gif,video/mp4,video/webm,video/ogg" required>
<div class="form-text"><?= qh_h(qh_t('Formats: JPG, JPEG, PNG, WEBP, GIF, MP4, WebM, OGG.', 'الصيغ: JPG, JPEG, PNG, WEBP, GIF, MP4, WebM, OGG.')) ?></div>
</div>
<div class="col-12 mt-3">
<button type="submit" class="btn btn-primary px-4"><?= qh_h(qh_t('Upload Video', 'رفع الفيديو')) ?></button>
<button type="submit" class="btn btn-primary px-4"><?= qh_h(qh_t('Upload Ad', 'رفع الإعلان')) ?></button>
</div>
</form>
</div>
@ -154,7 +213,7 @@ qh_page_start(
<div class="card shadow-sm border-0">
<div class="card-header bg-white border-bottom py-3">
<h5 class="mb-0 font-weight-bold text-dark"><?= qh_h(qh_t('Uploaded Videos', 'الفيديوهات المرفوعة')) ?></h5>
<h5 class="mb-0 font-weight-bold text-dark"><?= qh_h(qh_t('Uploaded Ads', 'الإعلانات المرفوعة')) ?></h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
@ -163,6 +222,7 @@ qh_page_start(
<tr>
<th class="border-0 px-4 py-3"><?= qh_h(qh_t('Preview', 'معاينة')) ?></th>
<th class="border-0 py-3"><?= qh_h(qh_t('Title', 'العنوان')) ?></th>
<th class="border-0 py-3 text-center"><?= qh_h(qh_t('Type', 'النوع')) ?></th>
<th class="border-0 py-3 text-center"><?= qh_h(qh_t('Status', 'الحالة')) ?></th>
<th class="border-0 px-4 py-3 text-end"><?= qh_h(qh_t('Actions', 'الإجراءات')) ?></th>
</tr>
@ -170,19 +230,28 @@ qh_page_start(
<tbody>
<?php if (empty($ads)): ?>
<tr>
<td colspan="4" class="text-center py-4 text-muted"><?= qh_h(qh_t('No videos uploaded yet.', 'لم يتم رفع فيديوهات بعد.')) ?></td>
<td colspan="5" class="text-center py-4 text-muted"><?= qh_h(qh_t('No ads uploaded yet.', 'لم يتم رفع أي إعلانات بعد.')) ?></td>
</tr>
<?php else: ?>
<?php foreach ($ads as $ad): ?>
<tr>
<td class="px-4 py-3">
<video width="120" class="rounded shadow-sm border" muted>
<source src="<?= qh_h($ad['video_path']) ?>" type="video/mp4">
</video>
<?php if (($ad['effective_media_type'] ?? 'video') === 'image'): ?>
<img src="<?= qh_h((string) $ad['effective_media_path']) ?>" alt="<?= qh_h($ad['title'] ?: qh_t('Advertisement preview', 'معاينة الإعلان')) ?>" width="120" height="68" class="rounded shadow-sm border object-fit-cover">
<?php else: ?>
<video width="120" class="rounded shadow-sm border" muted playsinline>
<source src="<?= qh_h((string) $ad['effective_media_path']) ?>" type="video/mp4">
</video>
<?php endif; ?>
</td>
<td class="py-3 fw-semibold text-dark">
<?= qh_h($ad['title'] ?: qh_t('Untitled', 'بدون عنوان')) ?>
</td>
<td class="py-3 text-center">
<span class="badge <?= ($ad['effective_media_type'] ?? 'video') === 'image' ? 'bg-info-subtle text-info-emphasis' : 'bg-dark-subtle text-dark-emphasis' ?> rounded-pill px-3">
<?= qh_h(($ad['effective_media_type'] ?? 'video') === 'image' ? qh_t('Image', 'صورة') : qh_t('Video', 'فيديو')) ?>
</span>
</td>
<td class="py-3 text-center">
<?php if ($ad['is_active']): ?>
<span class="badge bg-success rounded-pill px-3"><?= qh_h(qh_t('Active', 'مفعل')) ?></span>
@ -194,14 +263,14 @@ qh_page_start(
<div class="d-flex justify-content-end gap-2">
<form method="post" class="m-0 p-0">
<input type="hidden" name="action" value="toggle_status">
<input type="hidden" name="video_id" value="<?= (int) $ad['id'] ?>">
<input type="hidden" name="ad_id" value="<?= (int) $ad['id'] ?>">
<button type="submit" class="btn btn-sm <?= $ad['is_active'] ? 'btn-outline-secondary' : 'btn-outline-success' ?>">
<?= qh_h($ad['is_active'] ? qh_t('Disable', 'تعطيل') : qh_t('Enable', 'تفعيل')) ?>
</button>
</form>
<form method="post" class="m-0 p-0" onsubmit="return confirm('<?= qh_h(qh_t('Are you sure you want to delete this video?', 'هل أنت متأكد أنك تريد حذف هذا الفيديو؟')) ?>');">
<input type="hidden" name="action" value="delete_video">
<input type="hidden" name="video_id" value="<?= (int) $ad['id'] ?>">
<form method="post" class="m-0 p-0" onsubmit="return confirm('<?= qh_h(qh_t('Are you sure you want to delete this ad?', 'هل أنت متأكد أنك تريد حذف هذا الإعلان؟')) ?>');">
<input type="hidden" name="action" value="delete_media">
<input type="hidden" name="ad_id" value="<?= (int) $ad['id'] ?>">
<button type="submit" class="btn btn-sm btn-outline-danger"><?= qh_h(qh_t('Delete', 'حذف')) ?></button>
</form>
</div>

View File

@ -6,11 +6,68 @@ qh_boot();
$activeCalls = qh_fetch_tickets(['called', 'in_progress', 'nursing_called'], null, 8);
$queueOverview = qh_queue_overview();
$activeVideos = [];
function qh_display_ads_column_exists(PDO $pdo, string $column): bool
{
static $cache = [];
if (array_key_exists($column, $cache)) {
return $cache[$column];
}
$stmt = $pdo->prepare(
"SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = 'hospital_ads' AND COLUMN_NAME = ?"
);
$stmt->execute([DB_NAME, $column]);
return $cache[$column] = ((int) $stmt->fetchColumn() > 0);
}
$activeAds = [];
try {
$stmt = db()->query("SELECT video_path FROM hospital_ads WHERE is_active = 1 ORDER BY sort_order ASC, id DESC ");
$pdo = db();
$hasMediaType = qh_display_ads_column_exists($pdo, 'media_type');
$hasMediaPath = qh_display_ads_column_exists($pdo, 'media_path');
if ($hasMediaType && $hasMediaPath) {
$stmt = $pdo->query(
"SELECT id, title,
COALESCE(NULLIF(media_type, ''), 'video') AS media_type,
COALESCE(NULLIF(media_path, ''), NULLIF(video_path, '')) AS media_path
FROM hospital_ads
WHERE is_active = 1
ORDER BY sort_order ASC, id DESC"
);
} elseif ($hasMediaPath) {
$stmt = $pdo->query(
"SELECT id, title, 'video' AS media_type,
COALESCE(NULLIF(media_path, ''), NULLIF(video_path, '')) AS media_path
FROM hospital_ads
WHERE is_active = 1
ORDER BY sort_order ASC, id DESC"
);
} else {
$stmt = $pdo->query(
"SELECT id, title, 'video' AS media_type, video_path AS media_path
FROM hospital_ads
WHERE is_active = 1
ORDER BY sort_order ASC, id DESC"
);
}
if ($stmt) {
$activeVideos = $stmt->fetchAll(PDO::FETCH_COLUMN);
foreach ($stmt->fetchAll() as $ad) {
$mediaPath = (string) ($ad['media_path'] ?? '');
if ($mediaPath === '') {
continue;
}
$activeAds[] = [
'id' => (int) ($ad['id'] ?? 0),
'title' => (string) ($ad['title'] ?? ''),
'type' => (($ad['media_type'] ?? 'video') === 'image') ? 'image' : 'video',
'path' => $mediaPath,
'duration' => 10,
];
}
}
} catch (Throwable $e) {
// Table might not exist yet, safe to ignore
@ -189,108 +246,193 @@ qh_page_start(
<div class="col-xl-4 col-lg-5">
<div class="sticky-top" style="top: 1rem; height: 60vh;">
<?php if (!empty($activeVideos)): ?>
<?php if (!empty($activeAds)): ?>
<div class="card shadow-sm border-0 h-100 bg-black overflow-hidden d-flex justify-content-center align-items-center">
<video id="adsVideoPlayer" class="w-100 h-100 object-fit-contain" src="<?= qh_h($activeVideos[0]) ?>" autoplay muted playsinline>
</video>
<div class="w-100 h-100 position-relative bg-black">
<video id="adsVideoPlayer" class="w-100 h-100 object-fit-contain d-none" muted playsinline></video>
<img id="adsImagePlayer" class="w-100 h-100 object-fit-contain d-none" src="" alt="<?= qh_h(qh_t('Hospital advertisement', 'إعلان المستشفى')) ?>">
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const videos = <?= json_encode($activeVideos, JSON_UNESCAPED_SLASHES) ?>;
const playlist = <?= json_encode($activeAds, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>;
const player = document.getElementById('adsVideoPlayer');
if (videos.length === 0) return;
let currentIdx = parseInt(sessionStorage.getItem('adsVideoIndex') || '0', 10);
const image = document.getElementById('adsImagePlayer');
const defaultImageAlt = "<?= qh_h(qh_t('Hospital advertisement', 'إعلان المستشفى')) ?>";
let imageTimer = null;
let savedType = sessionStorage.getItem('adsMediaType') || 'video';
let currentIdx = parseInt(sessionStorage.getItem('adsMediaIndex') || sessionStorage.getItem('adsVideoIndex') || '0', 10);
let currentTime = parseFloat(sessionStorage.getItem('adsVideoTime') || '0');
if (isNaN(currentIdx) || currentIdx < 0 || currentIdx >= videos.length) {
let imageStartedAt = parseInt(sessionStorage.getItem('adsImageStartedAt') || '0', 10);
if (!player || !image || playlist.length === 0) {
return;
}
if (isNaN(currentIdx) || currentIdx < 0 || currentIdx >= playlist.length) {
currentIdx = 0;
}
if (isNaN(currentTime) || currentTime < 0) {
currentTime = 0;
}
player.src = videos[currentIdx];
player.load();
player.addEventListener('loadedmetadata', function() {
if (currentTime > 0) {
player.currentTime = isNaN(currentTime) ? 0 : currentTime;
}
}, { once: true });
if (window.localStorage.getItem('hospitalQueue:audioEnabled') !== 'false') {
if (isNaN(imageStartedAt) || imageStartedAt < 0) {
imageStartedAt = 0;
}
const persistIndex = function() {
sessionStorage.setItem('adsMediaIndex', String(currentIdx));
sessionStorage.setItem('adsVideoIndex', String(currentIdx));
};
const clearImageTimer = function() {
if (imageTimer) {
clearTimeout(imageTimer);
imageTimer = null;
}
};
const showAudioPrompt = function() {
if (window.audioPromptShown || window.localStorage.getItem('hospitalQueue:audioEnabled') === 'false') {
return;
}
window.audioPromptShown = true;
const prompt = document.createElement('div');
prompt.style.position = 'fixed';
prompt.style.bottom = '20px';
prompt.style.left = '50%';
prompt.style.transform = 'translateX(-50%)';
prompt.style.zIndex = '9999';
prompt.style.backgroundColor = '#dc3545';
prompt.style.color = 'white';
prompt.style.padding = '15px 30px';
prompt.style.borderRadius = '50px';
prompt.style.cursor = 'pointer';
prompt.style.display = 'flex';
prompt.style.alignItems = 'center';
prompt.style.gap = '15px';
prompt.style.boxShadow = '0 10px 25px rgba(0,0,0,0.5)';
const msg = "<?= qh_h(qh_t('Sound blocked by TV/Browser. Click here or press OK on remote to enable.', 'تم حظر الصوت بواسطة المتصفح/التلفاز. انقر هنا أو اضغط OK في الريموت للتفعيل.')) ?>";
prompt.innerHTML = '<i class="bi bi-volume-mute-fill fs-3"></i> <span class="fs-6 fw-bold">' + msg + '</span>';
const enableSound = function(ev) {
if (ev && ev.type === 'keydown' && ev.key !== 'Enter' && ev.key !== ' ') return;
player.muted = false;
}
player.play().catch(function(e) {
console.error("Error playing video:", e);
player.play().catch(e => console.warn('Still cannot play:', e));
if (document.body.contains(prompt)) {
document.body.removeChild(prompt);
}
if (ev && ev.type === 'keydown') {
document.body.click();
}
};
prompt.addEventListener('click', enableSound);
document.body.appendChild(prompt);
document.addEventListener('keydown', enableSound);
document.addEventListener('click', function(ev2) {
if (document.body.contains(prompt) && ev2.target !== prompt && !prompt.contains(ev2.target)) {
enableSound(ev2);
}
}, { once: true });
};
const playVideo = function() {
player.play().catch(function(e) {
console.error('Error playing video:', e);
if (!player.muted) {
player.muted = true;
player.play().catch(err => console.error("Fallback play failed:", err));
player.muted = true;
player.play().catch(err => console.error('Fallback play failed:', err));
}
if (!window.audioPromptShown && window.localStorage.getItem('hospitalQueue:audioEnabled') !== 'false') {
window.audioPromptShown = true;
const prompt = document.createElement('div');
prompt.style.position = 'fixed';
prompt.style.bottom = '20px';
prompt.style.left = '50%';
prompt.style.transform = 'translateX(-50%)';
prompt.style.zIndex = '9999';
prompt.style.backgroundColor = '#dc3545'; // danger red
prompt.style.color = 'white';
prompt.style.padding = '15px 30px';
prompt.style.borderRadius = '50px';
prompt.style.cursor = 'pointer';
prompt.style.display = 'flex';
prompt.style.alignItems = 'center';
prompt.style.gap = '15px';
prompt.style.boxShadow = '0 10px 25px rgba(0,0,0,0.5)';
const msg = "<?= qh_h(qh_t('Sound blocked by TV/Browser. Click here or press OK on remote to enable.', 'تم حظر الصوت بواسطة المتصفح/التلفاز. انقر هنا أو اضغط OK في الريموت للتفعيل.')) ?>";
prompt.innerHTML = '<i class="bi bi-volume-mute-fill fs-3"></i> <span class="fs-6 fw-bold">' + msg + '</span>';
const enableSound = function(ev) {
if (ev && ev.type === 'keydown' && ev.key !== 'Enter' && ev.key !== ' ') return;
player.muted = false;
player.play().catch(e => console.warn("Still cannot play:", e));
if (document.body.contains(prompt)) {
document.body.removeChild(prompt);
}
if (ev && ev.type === 'keydown') {
document.body.click();
}
};
prompt.addEventListener('click', enableSound);
document.body.appendChild(prompt);
document.addEventListener('keydown', enableSound);
// Also listen for any click anywhere to dismiss it
document.addEventListener('click', function(ev2) {
if (document.body.contains(prompt) && ev2.target !== prompt && !prompt.contains(ev2.target)) {
enableSound(ev2);
}
}, { once: true });
}
});
if (videos.length > 1) {
player.addEventListener('ended', function() {
currentIdx = (currentIdx + 1) % videos.length;
player.src = videos[currentIdx];
player.load();
player.play().catch(function(e) { console.error("Error playing video:", e); });
showAudioPrompt();
});
} else {
player.loop = true;
}
window.addEventListener('beforeunload', function() {
sessionStorage.setItem('adsVideoIndex', currentIdx);
sessionStorage.setItem('adsVideoTime', player.currentTime);
};
const showNextItem = function() {
clearImageTimer();
currentIdx = (currentIdx + 1) % playlist.length;
currentTime = 0;
imageStartedAt = 0;
savedType = 'video';
renderCurrent(true);
};
const renderCurrent = function(resetPosition) {
const item = playlist[currentIdx] || playlist[0];
persistIndex();
clearImageTimer();
sessionStorage.setItem('adsMediaType', item.type);
savedType = item.type;
if (item.type === 'image') {
player.pause();
player.removeAttribute('src');
player.load();
player.classList.add('d-none');
image.classList.remove('d-none');
image.src = item.path;
image.alt = item.title || defaultImageAlt;
const durationMs = Math.max(parseInt(item.duration || 10, 10), 3) * 1000;
let remainingMs = durationMs;
const canResumeImage = !resetPosition && imageStartedAt > 0;
if (!canResumeImage) {
imageStartedAt = Date.now();
} else {
const elapsedMs = Date.now() - imageStartedAt;
if (elapsedMs >= durationMs) {
showNextItem();
return;
}
remainingMs = durationMs - elapsedMs;
}
sessionStorage.setItem('adsImageStartedAt', String(imageStartedAt));
sessionStorage.setItem('adsVideoTime', '0');
imageTimer = window.setTimeout(showNextItem, remainingMs);
return;
}
image.classList.add('d-none');
player.classList.remove('d-none');
imageStartedAt = 0;
sessionStorage.removeItem('adsImageStartedAt');
const resumeTime = (!resetPosition && currentTime > 0) ? currentTime : 0;
player.loop = playlist.length === 1;
player.src = item.path;
player.load();
player.addEventListener('loadedmetadata', function onLoadedMetadata() {
if (resumeTime > 0 && resumeTime < player.duration) {
player.currentTime = resumeTime;
}
playVideo();
}, { once: true });
if (window.localStorage.getItem('hospitalQueue:audioEnabled') !== 'false') {
player.muted = false;
}
};
player.addEventListener('ended', function() {
if (playlist.length > 1) {
showNextItem();
}
});
window.addEventListener('beforeunload', function() {
persistIndex();
sessionStorage.setItem('adsMediaType', savedType);
if (savedType === 'video') {
sessionStorage.setItem('adsVideoTime', String(player.currentTime || 0));
} else {
sessionStorage.setItem('adsVideoTime', '0');
sessionStorage.setItem('adsImageStartedAt', String(imageStartedAt || Date.now()));
}
});
renderCurrent(false);
});
</script>
</div>