- Live homepage:
Added default Live settings in [includes/db.php](/Users/tyemeclifford/Documents/GH/mediaplayer/includes/db.php) using https://stream.place/embed/tyclifford.com. Added Admin → Settings → Live Section controls in [admin/settings.php](/Users/tyemeclifford/Documents/GH/mediaplayer/admin/settings.php): enable/disable, live title, and embed URL/iframe input. Added a homepage Videos / Live toggle in [index.php](/Users/tyemeclifford/Documents/GH/mediaplayer/index.php). Videos remains the default view, so the homepage behaves normally until Live is selected. Imgur: Added Imgur URL normalization, embed support, oEmbed/thumbnail hooks, and external view count scraping support in [includes/db.php](/Users/tyemeclifford/Documents/GH/mediaplayer/includes/db.php). Supports imgur.com, albums/galleries, and direct i.imgur.com photos/GIFs/videos by routing them through Imgur embeds. Imgur external views now flow into external_view_count, so per-item and overall site totals include readable Imgur provider counts. Updated the add-video hint and README docs.
This commit is contained in:
@@ -3,7 +3,6 @@
|
|||||||
A self-hosted multimedia player website built with PHP + SQLite + Video.js.
|
A self-hosted multimedia player website built with PHP + SQLite + Video.js.
|
||||||
Styled to match the existing `gate.php` / `index.php` dark theme exactly.
|
Styled to match the existing `gate.php` / `index.php` dark theme exactly.
|
||||||
|
|
||||||
<iframe src="https://docs.google.com/presentation/d/e/2PACX-1vQt7Hl5PtsdYbTXvRTWxMm_3BKj3PrAIyUeMUFn5_t3vwJRX_Aa3yG__bWpmogaxA/pubembed?start=false&loop=false&delayms=3000" frameborder="0" width="1280" height="749" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -78,12 +77,16 @@ Styled to match the existing `gate.php` / `index.php` dark theme exactly.
|
|||||||
6. Set quality labels (1080p, 720p, 480p, etc.)
|
6. Set quality labels (1080p, 720p, 480p, etc.)
|
||||||
7. Save — Video.js will use the stored source location and auto-select the best format the browser supports
|
7. Save — Video.js will use the stored source location and auto-select the best format the browser supports
|
||||||
|
|
||||||
External URLs must use `http://` or `https://`. Uploaded files and remote URLs can be mixed on the same video. Recognized provider URLs such as YouTube, Vimeo, Dailymotion, Twitch, Facebook, TikTok, Instagram, X/Twitter, SoundCloud, Spotify, Google Drive, Wistia, Streamable, and Loom render through their embed players.
|
External URLs must use `http://` or `https://`. Uploaded files and remote URLs can be mixed on the same video. Recognized provider URLs such as YouTube, Vimeo, Dailymotion, Imgur, Twitch, Facebook, TikTok, Instagram, X/Twitter, SoundCloud, Spotify, Google Drive, Wistia, Streamable, and Loom render through their embed players.
|
||||||
|
|
||||||
When adding an external URL, the admin form can fetch title, description, duration, thumbnail metadata, and readable provider view counts through oEmbed, page metadata, and JSON-LD. The save action repeats the metadata lookup as a fallback, so blank titles can be filled from supported external pages even if the browser autofill was not used.
|
When adding an external URL, the admin form can fetch title, description, duration, thumbnail metadata, and readable provider view counts through oEmbed, page metadata, and JSON-LD. The save action repeats the metadata lookup as a fallback, so blank titles can be filled from supported external pages even if the browser autofill was not used.
|
||||||
|
|
||||||
If no thumbnail image is uploaded, the add/edit forms can auto-grab one from the first supported source. YouTube and Dailymotion use direct thumbnail URLs; Vimeo, Wistia, TikTok, SoundCloud, and Spotify use oEmbed when available. Local uploaded videos can generate a first-frame thumbnail when `ffmpeg` is installed on the server.
|
If no thumbnail image is uploaded, the add/edit forms can auto-grab one from the first supported source. YouTube and Dailymotion use direct thumbnail URLs; Vimeo, Wistia, TikTok, SoundCloud, and Spotify use oEmbed when available. Local uploaded videos can generate a first-frame thumbnail when `ffmpeg` is installed on the server.
|
||||||
|
|
||||||
|
### Live Homepage Option
|
||||||
|
|
||||||
|
The homepage can show a **Videos / Live** switch above the main player. Configure it in **Admin → Settings → Live Section** with either a direct embeddable URL or an iframe snippet. The default live embed is `https://stream.place/embed/tyclifford.com`. Disable the setting to return the homepage to the normal video-only layout.
|
||||||
|
|
||||||
### Importing YouTube Channels
|
### Importing YouTube Channels
|
||||||
|
|
||||||
Use **Admin → YouTube Importer** to paste a YouTube channel URL, `@handle`, or `UC...` channel ID. The importer scrapes the channel's `/videos` page, extracts video data from `ytInitialData`, previews the results, and imports selected videos as remote YouTube embed sources. Duplicate YouTube watch URLs are skipped.
|
Use **Admin → YouTube Importer** to paste a YouTube channel URL, `@handle`, or `UC...` channel ID. The importer scrapes the channel's `/videos` page, extracts video data from `ytInitialData`, previews the results, and imports selected videos as remote YouTube embed sources. Duplicate YouTube watch URLs are skipped.
|
||||||
@@ -100,7 +103,7 @@ The public display is configurable in **Admin → Settings → Public Display**:
|
|||||||
- show or hide the total public view count
|
- show or hide the total public view count
|
||||||
- show or hide public page statistics on the homepage and catalogue
|
- show or hide public page statistics on the homepage and catalogue
|
||||||
|
|
||||||
The admin dashboard also includes **Upkeeping → Correct external view counts**. It checks existing remote-source videos in batches, scrapes readable provider counts when available, and adds those provider totals to the media-site totals without double-counting local plays. YouTube has dedicated parsing, and generic metadata patterns cover readable Vimeo, Dailymotion, and similar provider pages when they expose counts. Unsupported providers and local-only uploads can still be corrected manually on the edit screen.
|
The admin dashboard also includes **Upkeeping → Correct external view counts**. It checks existing remote-source videos in batches, scrapes readable provider counts when available, and adds those provider totals to the media-site totals without double-counting local plays. YouTube and Imgur have dedicated handling, and generic metadata patterns cover readable Vimeo, Dailymotion, and similar provider pages when they expose counts. Unsupported providers and local-only uploads can still be corrected manually on the edit screen.
|
||||||
|
|
||||||
### Public Search
|
### Public Search
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -308,7 +308,7 @@ render_head('Add Video — Admin', '
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<p class="section-heading">Video Sources</p>
|
<p class="section-heading">Video Sources</p>
|
||||||
<p class="hint" style="margin-bottom:1rem;">
|
<p class="hint" style="margin-bottom:1rem;">
|
||||||
Upload a video file, link a direct media file, or paste a provider page URL such as YouTube or Vimeo. The player will use a provider embed when the URL supports it.
|
Upload a video file, link a direct media file, or paste a provider page URL such as YouTube, Vimeo, or Imgur. The player will use a provider embed when the URL supports it.
|
||||||
Uploaded files: <strong>MP4, WebM, OGV, MOV, MKV, AVI</strong>. Remote URLs must use <strong>http://</strong> or <strong>https://</strong>.
|
Uploaded files: <strong>MP4, WebM, OGV, MOV, MKV, AVI</strong>. Remote URLs must use <strong>http://</strong> or <strong>https://</strong>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$show_video_views = isset($_POST['public_show_video_views']) ? '1' : '0';
|
$show_video_views = isset($_POST['public_show_video_views']) ? '1' : '0';
|
||||||
$show_total_views = isset($_POST['public_show_total_views']) ? '1' : '0';
|
$show_total_views = isset($_POST['public_show_total_views']) ? '1' : '0';
|
||||||
$show_page_stats = isset($_POST['public_show_page_stats']) ? '1' : '0';
|
$show_page_stats = isset($_POST['public_show_page_stats']) ? '1' : '0';
|
||||||
|
$live_enabled = isset($_POST['live_enabled']) ? '1' : '0';
|
||||||
|
$live_title = trim($_POST['live_title'] ?? 'Ty Clifford live stream');
|
||||||
|
$live_embed = trim($_POST['live_embed'] ?? '');
|
||||||
|
|
||||||
set_setting('catalogue_per_page', (string)$per_page);
|
set_setting('catalogue_per_page', (string)$per_page);
|
||||||
set_setting('site_title', $site_title);
|
set_setting('site_title', $site_title);
|
||||||
@@ -25,6 +28,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
set_setting('public_show_video_views', $show_video_views);
|
set_setting('public_show_video_views', $show_video_views);
|
||||||
set_setting('public_show_total_views', $show_total_views);
|
set_setting('public_show_total_views', $show_total_views);
|
||||||
set_setting('public_show_page_stats', $show_page_stats);
|
set_setting('public_show_page_stats', $show_page_stats);
|
||||||
|
set_setting('live_enabled', $live_enabled);
|
||||||
|
set_setting('live_title', $live_title);
|
||||||
|
set_setting('live_embed', $live_embed);
|
||||||
$msg = 'Settings saved.';
|
$msg = 'Settings saved.';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +59,9 @@ $url_rewrite_mode = url_rewrite_mode();
|
|||||||
$public_show_video_views = setting_bool('public_show_video_views', true);
|
$public_show_video_views = setting_bool('public_show_video_views', true);
|
||||||
$public_show_total_views = setting_bool('public_show_total_views', true);
|
$public_show_total_views = setting_bool('public_show_total_views', true);
|
||||||
$public_show_page_stats = setting_bool('public_show_page_stats', true);
|
$public_show_page_stats = setting_bool('public_show_page_stats', true);
|
||||||
|
$live_setting_enabled = setting_bool('live_enabled', true);
|
||||||
|
$live_title = setting('live_title', 'Ty Clifford live stream');
|
||||||
|
$live_embed = setting('live_embed', 'https://stream.place/embed/tyclifford.com');
|
||||||
|
|
||||||
// DB stats
|
// DB stats
|
||||||
$db = get_db();
|
$db = get_db();
|
||||||
@@ -220,6 +229,27 @@ render_head('Settings — Admin', '
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="form-label" style="margin-bottom:.55rem;">Live Section</p>
|
||||||
|
<div class="option-list" style="grid-template-columns:1fr;margin-bottom:1rem;">
|
||||||
|
<label class="check-option">
|
||||||
|
<input type="checkbox" name="live_enabled" value="1" <?= $live_setting_enabled ? 'checked' : '' ?>>
|
||||||
|
<span>
|
||||||
|
<strong>Show Live option</strong>
|
||||||
|
Adds a Live button on the homepage that switches from the normal video player to the configured live embed.
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-grid" style="margin-bottom:1rem;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="live_title">Live Title</label>
|
||||||
|
<input class="form-input" type="text" id="live_title" name="live_title" value="<?= h($live_title) ?>">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="live_embed">Live Embed URL or iframe</label>
|
||||||
|
<textarea class="form-textarea" id="live_embed" name="live_embed" rows="4"><?= h($live_embed) ?></textarea>
|
||||||
|
<span class="hint">Paste an iframe embed code or a direct embeddable URL. Default: https://stream.place/embed/tyclifford.com</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button class="btn btn-primary btn-sm" type="submit">
|
<button class="btn btn-primary btn-sm" type="submit">
|
||||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17,21 17,13 7,13 7,21"/><polyline points="7,3 7,8 15,8"/></svg>
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17,21 17,13 7,13 7,21"/><polyline points="7,3 7,8 15,8"/></svg>
|
||||||
Save Settings
|
Save Settings
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ function get_db(): PDO {
|
|||||||
'public_show_video_views' => '1',
|
'public_show_video_views' => '1',
|
||||||
'public_show_total_views' => '1',
|
'public_show_total_views' => '1',
|
||||||
'public_show_page_stats' => '1',
|
'public_show_page_stats' => '1',
|
||||||
|
'live_enabled' => '1',
|
||||||
|
'live_title' => 'Ty Clifford live stream',
|
||||||
|
'live_embed' => 'https://stream.place/embed/tyclifford.com',
|
||||||
];
|
];
|
||||||
$ins = $pdo->prepare("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)");
|
$ins = $pdo->prepare("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)");
|
||||||
foreach ($defaults as $k => $v) $ins->execute([$k, $v]);
|
foreach ($defaults as $k => $v) $ins->execute([$k, $v]);
|
||||||
@@ -171,6 +174,30 @@ function public_page_stats(): array {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function media_iframe_src_from_input(string $value): string {
|
||||||
|
$value = trim($value);
|
||||||
|
if ($value === '') return '';
|
||||||
|
|
||||||
|
if (stripos($value, '<iframe') !== false && preg_match('/\ssrc=["\']([^"\']+)["\']/i', $value, $match)) {
|
||||||
|
$value = html_entity_decode($match[1], ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalize_media_url($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function live_embed_url(): string {
|
||||||
|
return media_iframe_src_from_input(setting('live_embed', 'https://stream.place/embed/tyclifford.com'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function live_embed_title(): string {
|
||||||
|
$title = trim(setting('live_title', 'Ty Clifford live stream'));
|
||||||
|
return $title !== '' ? $title : 'Live stream';
|
||||||
|
}
|
||||||
|
|
||||||
|
function live_enabled(): bool {
|
||||||
|
return setting_bool('live_enabled', true) && live_embed_url() !== '';
|
||||||
|
}
|
||||||
|
|
||||||
function url_rewrite_mode(): string {
|
function url_rewrite_mode(): string {
|
||||||
$mode = strtolower(setting('url_rewrite_mode', 'pretty'));
|
$mode = strtolower(setting('url_rewrite_mode', 'pretty'));
|
||||||
return in_array($mode, ['pretty', 'query'], true) ? $mode : 'pretty';
|
return in_array($mode, ['pretty', 'query'], true) ? $mode : 'pretty';
|
||||||
@@ -485,6 +512,41 @@ function media_youtube_id_from_url(string $url): string {
|
|||||||
return media_clean_token($id);
|
return media_clean_token($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function media_imgur_path_from_url(string $url): string {
|
||||||
|
$url = normalize_media_url($url);
|
||||||
|
if ($url === '') return '';
|
||||||
|
|
||||||
|
$host = strtolower((string)(parse_url($url, PHP_URL_HOST) ?? ''));
|
||||||
|
$segments = media_path_segments($url);
|
||||||
|
if (!$segments) return '';
|
||||||
|
|
||||||
|
if (media_host_matches($host, 'i.imgur.com')) {
|
||||||
|
$id = pathinfo($segments[0], PATHINFO_FILENAME);
|
||||||
|
$id = media_clean_token($id);
|
||||||
|
return $id !== '' ? $id : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!media_host_matches($host, 'imgur.com')) return '';
|
||||||
|
|
||||||
|
if (in_array($segments[0], ['a', 'gallery'], true) && isset($segments[1])) {
|
||||||
|
$id = media_clean_token($segments[1]);
|
||||||
|
return $id !== '' ? $segments[0] . '/' . $id : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = media_clean_token($segments[0]);
|
||||||
|
return $id !== '' ? $id : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function media_imgur_page_url(string $url): string {
|
||||||
|
$path = media_imgur_path_from_url($url);
|
||||||
|
return $path !== '' ? 'https://imgur.com/' . $path : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function media_imgur_embed_url(string $url): string {
|
||||||
|
$page = media_imgur_page_url($url);
|
||||||
|
return $page !== '' ? rtrim($page, '/') . '/embed' : '';
|
||||||
|
}
|
||||||
|
|
||||||
function media_normalize_datetime(string $value): string {
|
function media_normalize_datetime(string $value): string {
|
||||||
$value = trim(html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
|
$value = trim(html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
|
||||||
if ($value === '') return '';
|
if ($value === '') return '';
|
||||||
@@ -638,6 +700,10 @@ function media_external_view_count_from_url(string $url): int {
|
|||||||
if (media_host_matches($host, 'youtube.com') || media_host_matches($host, 'youtu.be') || media_host_matches($host, 'youtube-nocookie.com')) {
|
if (media_host_matches($host, 'youtube.com') || media_host_matches($host, 'youtu.be') || media_host_matches($host, 'youtube-nocookie.com')) {
|
||||||
return media_youtube_view_count($url);
|
return media_youtube_view_count($url);
|
||||||
}
|
}
|
||||||
|
if (media_host_matches($host, 'imgur.com') || media_host_matches($host, 'i.imgur.com')) {
|
||||||
|
$imgur_page = media_imgur_page_url($url);
|
||||||
|
if ($imgur_page !== '') $url = $imgur_page;
|
||||||
|
}
|
||||||
|
|
||||||
$fetch = media_http_get($url, 15, 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8');
|
$fetch = media_http_get($url, 15, 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8');
|
||||||
if ($fetch['body'] === '' || $fetch['error'] !== '') return 0;
|
if ($fetch['body'] === '' || $fetch['error'] !== '') return 0;
|
||||||
@@ -844,6 +910,10 @@ function media_provider_oembed_endpoint(string $url, string $host): string {
|
|||||||
if (media_host_matches($host, 'dailymotion.com') || media_host_matches($host, 'dai.ly')) {
|
if (media_host_matches($host, 'dailymotion.com') || media_host_matches($host, 'dai.ly')) {
|
||||||
return 'https://www.dailymotion.com/services/oembed?url=' . rawurlencode($url);
|
return 'https://www.dailymotion.com/services/oembed?url=' . rawurlencode($url);
|
||||||
}
|
}
|
||||||
|
if (media_host_matches($host, 'imgur.com') || media_host_matches($host, 'i.imgur.com')) {
|
||||||
|
$imgur_page = media_imgur_page_url($url);
|
||||||
|
if ($imgur_page !== '') return 'https://api.imgur.com/oembed.json?url=' . rawurlencode($imgur_page);
|
||||||
|
}
|
||||||
if (media_host_matches($host, 'tiktok.com')) {
|
if (media_host_matches($host, 'tiktok.com')) {
|
||||||
return 'https://www.tiktok.com/oembed?url=' . rawurlencode($url);
|
return 'https://www.tiktok.com/oembed?url=' . rawurlencode($url);
|
||||||
}
|
}
|
||||||
@@ -975,6 +1045,14 @@ function media_external_metadata(string $url): array {
|
|||||||
if ($url === '') return $metadata;
|
if ($url === '') return $metadata;
|
||||||
|
|
||||||
$host = strtolower((string)(parse_url($url, PHP_URL_HOST) ?? ''));
|
$host = strtolower((string)(parse_url($url, PHP_URL_HOST) ?? ''));
|
||||||
|
if (media_host_matches($host, 'imgur.com') || media_host_matches($host, 'i.imgur.com')) {
|
||||||
|
$imgur_page = media_imgur_page_url($url);
|
||||||
|
if ($imgur_page !== '') {
|
||||||
|
$url = $imgur_page;
|
||||||
|
$metadata['url'] = $url;
|
||||||
|
$host = strtolower((string)(parse_url($url, PHP_URL_HOST) ?? ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
$provider = media_provider_embed($url);
|
$provider = media_provider_embed($url);
|
||||||
$metadata['provider'] = $provider['provider'] ?? '';
|
$metadata['provider'] = $provider['provider'] ?? '';
|
||||||
|
|
||||||
@@ -1049,6 +1127,13 @@ function media_provider_thumbnail_url(string $url): string {
|
|||||||
if ($id !== '') return 'https://www.dailymotion.com/thumbnail/video/' . rawurlencode($id);
|
if ($id !== '') return 'https://www.dailymotion.com/thumbnail/video/' . rawurlencode($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (media_host_matches($host, 'imgur.com') || media_host_matches($host, 'i.imgur.com')) {
|
||||||
|
$imgur_path = media_imgur_path_from_url($url);
|
||||||
|
if ($imgur_path !== '' && !str_contains($imgur_path, '/')) {
|
||||||
|
return 'https://i.imgur.com/' . rawurlencode($imgur_path) . 'h.jpg';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return media_oembed_thumbnail_url($url, $host);
|
return media_oembed_thumbnail_url($url, $host);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1184,6 +1269,11 @@ function media_provider_embed(string $url): ?array {
|
|||||||
if ($id !== '') return ['provider' => 'dailymotion', 'embed' => 'https://www.dailymotion.com/embed/video/' . rawurlencode($id)];
|
if ($id !== '') return ['provider' => 'dailymotion', 'embed' => 'https://www.dailymotion.com/embed/video/' . rawurlencode($id)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (media_host_matches($host, 'imgur.com') || media_host_matches($host, 'i.imgur.com')) {
|
||||||
|
$embed = media_imgur_embed_url($url);
|
||||||
|
if ($embed !== '') return ['provider' => 'imgur', 'embed' => $embed];
|
||||||
|
}
|
||||||
|
|
||||||
if (media_host_matches($host, 'twitch.tv')) {
|
if (media_host_matches($host, 'twitch.tv')) {
|
||||||
$parent = media_twitch_parent_host();
|
$parent = media_twitch_parent_host();
|
||||||
if (preg_match('#/videos/(\d+)#', $path, $m)) {
|
if (preg_match('#/videos/(\d+)#', $path, $m)) {
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ $public_stats = $show_page_stats ? public_page_stats() : [
|
|||||||
'sources' => 0,
|
'sources' => 0,
|
||||||
'views' => $total_public_views,
|
'views' => $total_public_views,
|
||||||
];
|
];
|
||||||
|
$live_enabled = live_enabled();
|
||||||
|
$live_url = $live_enabled ? live_embed_url() : '';
|
||||||
|
$live_title = live_embed_title();
|
||||||
|
|
||||||
$per_page = (int)setting('catalogue_per_page', '10');
|
$per_page = (int)setting('catalogue_per_page', '10');
|
||||||
$page = max(1, (int)($_GET['page'] ?? 1));
|
$page = max(1, (int)($_GET['page'] ?? 1));
|
||||||
@@ -150,6 +153,24 @@ render_head('Media — TyClifford.com', '
|
|||||||
display:block; width:100%; height:100%;
|
display:block; width:100%; height:100%;
|
||||||
border:0; background:#000;
|
border:0; background:#000;
|
||||||
}
|
}
|
||||||
|
.home-mode-toggle {
|
||||||
|
display:inline-flex; align-items:center; gap:.4rem;
|
||||||
|
align-self:flex-start;
|
||||||
|
padding:.25rem; border:1px solid var(--border-2); border-radius:var(--r);
|
||||||
|
background:rgba(255,255,255,.03);
|
||||||
|
}
|
||||||
|
.home-mode-toggle button {
|
||||||
|
border:0; border-radius:calc(var(--r) - 1px); cursor:pointer;
|
||||||
|
padding:.38rem .75rem;
|
||||||
|
font-family:"Share Tech Mono",monospace; font-size:.65rem; letter-spacing:.08em;
|
||||||
|
color:var(--text-3); background:transparent;
|
||||||
|
transition:color .2s, background .2s;
|
||||||
|
}
|
||||||
|
.home-mode-toggle button:hover { color:var(--text-2); }
|
||||||
|
.home-mode-toggle button.active { color:var(--green); background:rgba(0,232,122,.12); }
|
||||||
|
.home-mode-panel[hidden] { display:none; }
|
||||||
|
.live-footer { justify-content:space-between; }
|
||||||
|
.live-footer .stream-meta { max-width:60ch; line-height:1.5; letter-spacing:.06em; }
|
||||||
|
|
||||||
/* ── Lower grid ── */
|
/* ── Lower grid ── */
|
||||||
.lower-grid { display:grid; grid-template-columns:1fr; gap:1rem; }
|
.lower-grid { display:grid; grid-template-columns:1fr; gap:1rem; }
|
||||||
@@ -252,8 +273,30 @@ render_head('Media — TyClifford.com', '
|
|||||||
|
|
||||||
<?php render_topbar('home'); ?>
|
<?php render_topbar('home'); ?>
|
||||||
|
|
||||||
|
<?php if ($live_enabled): ?>
|
||||||
|
<div class="home-mode-toggle" role="tablist" aria-label="Homepage media mode">
|
||||||
|
<button type="button" class="active" data-home-mode="videos" role="tab" aria-selected="true" aria-controls="home-video-panel">Videos</button>
|
||||||
|
<button type="button" data-home-mode="live" role="tab" aria-selected="false" aria-controls="home-live-panel">Live</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="stream-card home-mode-panel" id="home-live-panel" aria-label="Live stream" hidden>
|
||||||
|
<div class="stream-ratio">
|
||||||
|
<iframe class="provider-embed-frame"
|
||||||
|
src="<?= h($live_url) ?>"
|
||||||
|
allowfullscreen
|
||||||
|
allow="autoplay; fullscreen"
|
||||||
|
title="<?= h($live_title) ?>"
|
||||||
|
referrerpolicy="no-referrer-when-downgrade"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="stream-footer live-footer">
|
||||||
|
<p class="eyebrow eyebrow-green"><?= h($live_title) ?></p>
|
||||||
|
<p class="stream-meta"><span>live embed</span></p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- ═══════════════════════════ PLAYER CARD ════════════════════════════════ -->
|
<!-- ═══════════════════════════ PLAYER CARD ════════════════════════════════ -->
|
||||||
<section class="stream-card" aria-label="Media player">
|
<section class="stream-card home-mode-panel" id="home-video-panel" aria-label="Media player">
|
||||||
<div class="stream-ratio">
|
<div class="stream-ratio">
|
||||||
<?php if ($primary_player_source && ($primary_player_source['kind'] ?? '') === 'iframe'): ?>
|
<?php if ($primary_player_source && ($primary_player_source['kind'] ?? '') === 'iframe'): ?>
|
||||||
<iframe class="provider-embed-frame"
|
<iframe class="provider-embed-frame"
|
||||||
@@ -502,6 +545,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
toggleBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
toggleBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var modeButtons = document.querySelectorAll('[data-home-mode]');
|
||||||
|
var livePanel = document.getElementById('home-live-panel');
|
||||||
|
var videoPanel = document.getElementById('home-video-panel');
|
||||||
|
modeButtons.forEach(function(btn) {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
var mode = btn.getAttribute('data-home-mode');
|
||||||
|
modeButtons.forEach(function(other) {
|
||||||
|
var active = other === btn;
|
||||||
|
other.classList.toggle('active', active);
|
||||||
|
other.setAttribute('aria-selected', active ? 'true' : 'false');
|
||||||
|
});
|
||||||
|
if (livePanel) livePanel.hidden = mode !== 'live';
|
||||||
|
if (videoPanel) videoPanel.hidden = mode === 'live';
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copy embed code to clipboard
|
// Copy embed code to clipboard
|
||||||
|
|||||||
Reference in New Issue
Block a user