Rozdział 11: Wideo i audio
Rozszerzamy media o wideo i audio. Nie potrzebujemy nowej encji — BlogMedia z Rozdziału 10 obsługuje
już trzy typy (image, video, audio). Dokładamy tylko render przez natywne znaczniki <video>
i <audio>, z fallbackiem i zachowaną kolejnością.
Stan wejściowy: encja
BlogMediai galeria obrazków (Rozdz. 10). Tu tylko rozszerzamy szablon strony artykułu — bez zmian w PHP.
Te same media, inne typy
W Rozdziale 10 encja BlogMedia dostała stałe na wszystkie typy:
public const TYPE_IMAGE = 'image';
public const TYPE_VIDEO = 'video';
public const TYPE_AUDIO = 'audio';
Kolekcja article.media zawiera więc wszystkie media artykułu razem, posortowane po position.
Na stronie renderujemy każdy typ osobno, filtrując po type — dokładnie jak obrazki, tylko z innym
znacznikiem HTML.
Osadzanie wideo
Wideo osadzamy natywnym <video controls> ze źródłem z path. Gdy plik się nie wczyta, onerror
podmienia zawartość na czytelną informację (zamiast pustego odtwarzacza):
{# templates/blog/show.html.twig — sekcja wideo #}
{% set videos = article.media|filter(m => m.type == 'video') %}
{% if videos|length > 0 %}
<div class="mb-4">
{% for video in videos %}
<div class="card border-0 shadow-sm mb-3">
<div class="card-body p-3">
<video controls class="w-100 rounded"
onerror="this.parentElement.innerHTML='<p class=\'text-muted small p-2\'>{{ video.originalName }}</p>'">
<source src="/{{ video.path }}">
</video>
{% if video.altText %}
<p class="text-muted small mt-2 mb-0">{{ video.altText }}</p>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
controls— natywne przyciski przeglądarki (play/pauza/głośność/pełny ekran). Zero własnego JS.<source src="/{{ video.path }}">— ścieżka względempublic/, z wiodącym/.altTextsłuży tu jako opis/podpis pod materiałem (dla obrazków był toalt).
Osadzanie audio
Audio działa analogicznie — natywny <audio controls>, dla czytelności z ikoną obok:
{% set audios = article.media|filter(m => m.type == 'audio') %}
{% if audios|length > 0 %}
<div class="mb-4">
{% for audio in audios %}
<div class="card border-0 shadow-sm mb-3">
<div class="card-body p-3">
<div class="d-flex align-items-center gap-3">
<i class="fa-solid fa-headphones fa-2x text-primary"></i>
<audio controls class="flex-grow-1">
<source src="/{{ audio.path }}">
</audio>
</div>
{% if audio.altText %}
<p class="text-muted small mt-2 mb-0">{{ audio.altText }}</p>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
Ikona (fa-headphones) to tylko element wizualny — sam odtwarzacz to natywny <audio controls>, więc
działa wszędzie bez dodatkowych bibliotek.
Fallback i kolejność
Dwie rzeczy wspólne dla obrazków, wideo i audio:
- Kolejność — wszystkie media biorą się z jednej kolekcji
article.mediaz#[ORM\OrderBy(['position' => 'ASC'])](Rozdział 10). Ustawiającposition, sterujesz kolejnością niezależnie od typu. - Fallback — dla wideo
onerrorchowa/zamienia zepsuty odtwarzacz. Dla audio wystarcza natywny komunikat przeglądarki, gdy formatu nie da się odtworzyć.
Formaty: przeglądarki grają najlepiej
mp4(wideo) imp3(audio) — do bloga to bezpieczny domyślny wybór. Możesz podać wiele<source>z różnymi formatami, jeśli potrzebujesz szerszej zgodności.
Podsumowanie
Artykuł obsługuje pełen zestaw mediów:
- ✅ te same
BlogMedia(typyvideo/audioz Rozdziału 10) — bez nowej encji i migracji, - ✅ wideo przez natywny
<video controls>+<source>, z fallbackiemonerror, - ✅ audio przez natywny
<audio controls>(z ikoną), - ✅ kolejność wspólna dla wszystkich mediów (
position), filtrowanie potypew szablonie, - ✅ działający stan: artykuł wyświetla obrazki, wideo i audio (media z fixtures — upload w Rozdziale 12).
W Rozdziale 12 dobudujemy upload mediów w panelu admina — bezpieczne przesyłanie plików
(UploadedFile, walidacja, unikalna nazwa) i powiązanie z artykułem.