- Implemented audio support with Video.js.

What changed:
Added uploaded/direct audio playback paths in 
[index.php](/Users/tyemeclifford/Documents/GH/mediaplayer/index.php) and 
[embed.php](/Users/tyemeclifford/Documents/GH/mediaplayer/embed.php).
Added common audio upload support: MP3, M4A, AAC, WAV, FLAC, OGG, OGA, 
OPUS.
Added waveform poster generation for uploaded audio via ffmpeg, stored 
like normal thumbnails under media/thumbs/.
Updated add/edit forms to accept media files, not just video files.
Added audio-friendly labels like Audio, 320kbps, 256kbps, 128kbps.
Updated docs and changelog.
This commit is contained in:
Ty Clifford
2026-06-25 13:34:20 -04:00
parent 8a39bc02fa
commit 7deae1a399
10 changed files with 365 additions and 80 deletions
+27 -19
View File
@@ -88,7 +88,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$inserted_sources = 0;
$moved_files = [];
$allowed_video = ['mp4','webm','ogv','ogg','mov','mkv','avi'];
$allowed_media = media_allowed_upload_extensions();
for ($i = 0; $i < $source_count; $i++) {
$type = ($source_types[$i] ?? 'local') === 'remote' ? 'remote' : 'local';
@@ -99,7 +99,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($type === 'remote') {
$url = normalize_media_url($source_urls[$i] ?? '');
if ($url === '') {
if (trim($source_urls[$i] ?? '') !== '') $error = 'External video URLs must start with http:// or https://.';
if (trim($source_urls[$i] ?? '') !== '') $error = 'External media URLs must start with http:// or https://.';
continue;
}
$metadata = $source_metadata[$url] ?? [];
@@ -119,15 +119,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (empty($tmp) || !is_uploaded_file($tmp)) continue;
$orig_name = $source_files['name'][$i] ?? '';
$ext = strtolower(pathinfo($orig_name, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed_video, true)) continue;
if (!in_array($ext, $allowed_media, true)) {
$error = 'Uploaded media files must be one of: ' . media_upload_format_summary() . '.';
break;
}
$fname = $slug . '_' . ($i+1) . '_' . time() . '.' . $ext;
if (move_uploaded_file($tmp, $videos_dir . $fname)) {
$moved_files[] = $videos_dir . $fname;
$target = $videos_dir . $fname;
$moved_files[] = $target;
$db->prepare("
INSERT INTO video_sources (video_id, label, source_type, file_path, source_url, mime_type, quality, sort_order)
VALUES (?,?,?,?,?,?,?,?)
")->execute([$video_id, $label, 'local', 'videos/' . $fname, '', mime_from_ext($fname), $quality, $sort]);
")->execute([$video_id, $label, 'local', 'videos/' . $fname, '', media_mime_from_local_file($target, $fname), $quality, $sort]);
$inserted_sources++;
}
}
@@ -154,7 +158,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
render_head('Add Video — Admin', '
render_head('Add Media — Admin', '
.form-grid { display:grid; grid-template-columns:1fr; gap:1.2rem; }
@media(min-width:700px) { .form-grid { grid-template-columns:1fr 1fr; } }
.form-grid .span2 { grid-column:1/-1; }
@@ -206,7 +210,7 @@ render_head('Add Video — Admin', '
<div class="topbar-brand">
<div>
<div class="brand-name">Ty Clifford</div>
<div class="brand-sub">admin / add video</div>
<div class="brand-sub">admin / add media</div>
</div>
</div>
<nav class="topbar-social">
@@ -234,7 +238,7 @@ render_head('Add Video — Admin', '
</a>
<a href="add.php" class="active">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
Add Video
Add Media
</a>
<a href="youtube_import.php">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22.54 6.42a2.78 2.78 0 00-1.95-2C18.88 4 12 4 12 4s-6.88 0-8.59.46a2.78 2.78 0 00-1.95 2A29 29 0 001 12a29 29 0 00.46 5.58 2.78 2.78 0 001.95 2C5.12 20 12 20 12 20s6.88 0 8.59-.46a2.78 2.78 0 001.95-2A29 29 0 0023 12a29 29 0 00-.46-5.58z"/><polygon points="10,15 15,12 10,9"/></svg>
@@ -251,7 +255,7 @@ render_head('Add Video — Admin', '
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.25rem;flex-wrap:wrap;gap:.75rem;">
<div>
<p class="eyebrow eyebrow-purple" style="margin-bottom:.3rem">New Entry</p>
<h1 style="font-size:1.05rem;font-weight:700;color:var(--text);">Add Video</h1>
<h1 style="font-size:1.05rem;font-weight:700;color:var(--text);">Add Media</h1>
</div>
<a href="index.php" class="btn btn-secondary btn-sm">← Back</a>
</div>
@@ -260,7 +264,7 @@ render_head('Add Video — Admin', '
<!-- ── Video details ── -->
<div class="card" style="margin-bottom:1rem;">
<p class="section-heading">Video Details</p>
<p class="section-heading">Media Details</p>
<div class="form-grid">
<div class="form-group span2">
<label class="form-label" for="title">Title</label>
@@ -290,9 +294,9 @@ render_head('Add Video — Admin', '
accept="image/jpeg,image/png,image/webp,image/gif" style="padding:.45rem .6rem;">
<label style="display:flex;align-items:center;gap:.55rem;margin-top:.55rem;cursor:pointer;">
<input type="checkbox" name="auto_thumbnail" value="1" <?= isset($_POST['auto_thumbnail']) || $_SERVER['REQUEST_METHOD'] !== 'POST' ? 'checked' : '' ?>>
<span style="font-size:.78rem;color:var(--text-2);">Auto-grab thumbnail from source if no image is uploaded</span>
<span style="font-size:.78rem;color:var(--text-2);">Auto-grab thumbnail or waveform from source if no image is uploaded</span>
</label>
<span class="hint">JPG, PNG, WebP or GIF. Auto-grab supports YouTube, Vimeo, Dailymotion and other oEmbed providers; local uploads use ffmpeg when available.</span>
<span class="hint">JPG, PNG, WebP or GIF. Auto-grab supports providers, local video first frames, and local audio waveforms when ffmpeg is available.</span>
</div>
<div class="form-group" style="justify-content:flex-end;padding-bottom:.2rem;">
<label style="display:flex;align-items:center;gap:.6rem;cursor:pointer;">
@@ -304,12 +308,12 @@ render_head('Add Video — Admin', '
</div>
</div>
<!-- ── Video sources ── -->
<!-- ── Media sources ── -->
<div class="card">
<p class="section-heading">Video Sources</p>
<p class="section-heading">Media Sources</p>
<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.
Uploaded files: <strong>MP4, WebM, OGV, MOV, MKV, AVI</strong>. Remote URLs must use <strong>http://</strong> or <strong>https://</strong>.
Upload a video or audio 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.
Uploaded files: <strong><?= h(media_upload_format_summary()) ?></strong>. Remote URLs must use <strong>http://</strong> or <strong>https://</strong>.
</p>
<div class="sources-section" id="sources-container">
@@ -323,9 +327,9 @@ render_head('Add Video — Admin', '
</select>
</div>
<div class="form-group source-location-panel source-location-local">
<label class="form-label">Video File</label>
<label class="form-label">Media File</label>
<input class="form-input" type="file" name="source_file[]"
accept="video/mp4,video/webm,video/ogg,.mp4,.webm,.ogv,.mov,.mkv,.avi"
accept="<?= h(media_upload_accept_attribute()) ?>"
style="padding:.45rem .6rem;">
</div>
<div class="form-group source-location-panel source-location-remote" hidden>
@@ -344,6 +348,10 @@ render_head('Add Video — Admin', '
<option value="480p">480p</option>
<option value="360p">360p</option>
<option value="4K">4K</option>
<option value="Audio">Audio</option>
<option value="320kbps">320kbps</option>
<option value="256kbps">256kbps</option>
<option value="128kbps">128kbps</option>
<option value="">—</option>
</select>
</div>
@@ -367,7 +375,7 @@ render_head('Add Video — Admin', '
<div style="display:flex;gap:.75rem;margin-top:1rem;flex-wrap:wrap;">
<button class="btn btn-primary" type="submit">
<svg width="14" height="14" 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 Video
Save Media
</button>
<a href="index.php" class="btn btn-secondary">Cancel</a>
</div>