Appearance
Product Video Management (Admin)
Flow ID: AD-30 Module(s): video, eshop, job Complexity: High Last Updated: 2026-04-04
Business Context
Two independent video subsystems serve different purposes. YouTube Videos (v1) store YouTube IDs with auto-downloaded thumbnails, linked to products and vendors via lookup tables. Video Showcase / Stream (v2) provides self-hosted video with Bunny.net CDN integration, file uploads (video + thumbnail + preview), async processing via job queue, view metrics tracking, and product assignment with ordering. The Showcase subsystem powers the "Reels" storefront page and the home page video slider. Both subsystems are managed from the admin panel under the CONTENT menu group.
Architecture
Legacy Admin (video module)
├── Adv_video_admin.php → YouTube video CRUD (199 lines)
├── AdvVideoShowcaseAdmin.php → Bunny Stream CRUD (405 lines)
├── Adv_video_model.php → YouTube model: tables video, video_mui, lookup tables
└── AdvVideoShowcaseModel.php → Stream model: tables video_streams, video_streams_mui, video_streams_lp
Modern Domain Layer (src/Domains/Cms/Video/)
├── Repository/Entity.php → YouTube video entity (video table)
├── Repository/Repository.php → Read repo with Specification pattern
├── Repository/RepositoryConfigurator.php → Relations: translations, products, vendors
├── Repository/MuiEntity.php → Translation entity (video_mui)
├── Repository/MuiRepository.php
├── Service.php → Read service (list, item, get)
├── WriteService.php → CRUD with transactional MUI handling
├── WriteData.php / MuiWriteData.php → DTOs with OpenAPI schemas
├── Validator.php → Validation (lang required per translation)
└── ListRequest.php → Filters: id, youtubeVideoId, isPromo, name.{locale}, slug.{locale}
REST API (src/Rest/Cms/)
└── Controllers/Video.php → Full CRUD + file upload (thumb field)
Resources/Video/Resource.php, Collection.php, MuiResource.php, MuiCollection.php
VideoStream Integration (src/VideoStream/)
├── Contract/VideoStream.php → Interface: createVideo, uploadVideo, getVideo, deleteVideo, getUrls, getStatus
├── VideoManager.php → Orchestrator: createAndUpload, getUrls, getVideoStatus, deleteVideo
└── Stream/BunnyStream.php → Bunny.net CDN implementation (Guzzle HTTP, library-scoped API)
Job Queue (ecommercen/job/libraries/)
├── AdvUploadVideoToStream.php → Upload local file to Bunny CDN, then chain CheckVideoStatusStream
├── AdvDeleteVideoToStream.php → Delete video from Bunny CDN by cdn_video_id
└── AdvCheckVideoStatusStream.php → Poll Bunny API for encoding status, re-queue if not FINISHED/ERRORDatabase Schema
YouTube Videos (v1)
| Table | Columns | Notes |
|---|---|---|
video | id, youtube_video_id (unique), custom_datetime, thumb, is_promo | Master record |
video_mui | id, video_id, name, description, slug, lang | MUI with slug+lang index |
shop_product_video_lp | id, product_id, video_id | Product-video many-to-many |
shop_vendor_video_lp | id, video_id, vendor_id | Vendor-video many-to-many |
Video Showcase / Stream (v2)
| Table | Columns | Notes |
|---|---|---|
video_streams | id, cdn_video_id, cdn_video_uri, cdn_video_thumbnail, custom_thumbnail, custom_preview, is_promo, active, status, created_at, updated_at | Migration: 20250328114125_video_stream_data.php |
video_streams_mui | id, video_stream_id, name, lang | Name-only MUI (no slug/description) |
video_streams_lp | id, video_stream_id, product_id, order | Ordered product assignment |
view_metrics | object_type ("stream"), object_id, since, expires_after, hits, impressions | View counting for stream videos |
Key Features
YouTube Video Subsystem (v1)
- YouTube ID validation:
is_valid_youtube_id+getYoutubeIdcustom CI form validation rules; uniqueness enforced in DB and on form submit. - Auto-thumbnail download: On add/update,
getYoutubeThumb()tries 3 quality levels fromimg.youtube.com/vi/{id}/{0,1,2}.jpg, saves first successful one viastorage()->put(). - MUI: Name, description, slug (auto-generated via
createSlug()) per language. - Product assignment: Batch assign via
assignMultipleProductsToVideo()with deduplication against existing DB relations. Product admin usesassign_products_to_videobatch operation. - Vendor assignment: Replace-all strategy via
assignMultipleVideosToVendor()(deletes existing, re-inserts). - Product-level YouTube embed: Each product has a per-language
videofield inshop_product_muistoring a YouTube ID, rendered inAdvProductGalleryWithVideo.vueas an embedded iframe.
Video Showcase / Stream Subsystem (v2)
- Feature gate: Registry key
VIDEOSHOWCASE.ENABLEDmust be truthy; controller returns 404 otherwise. Admin menu item conditionally shown. - File uploads: Three file fields handled by
AdvUploader:video_file: up to 200 MB, typesmp4|avi|wmv|mov|gif|webp, stored infiles/videos/videofiles/thumbnail_image: up to 2 MB, standard image types, stored infiles/videos/video_preview_file: up to 100 MB, typesgif|webp, stored infiles/videos/previews/
- Bunny CDN pipeline: On add,
afterAdd()enqueuesUploadVideoToStreamjob (queue:fileManager, 3 retries, 300s grace). The job uploads the local file viaVideoManager::createAndUpload(), stores the returned GUID and thumbnail URL, sets status toUPLOADED, then chainsCheckVideoStatusStream. The status checker pollsBunnyStream::getStatus()and re-queues itself until the status reachesFINISHEDorERROR. - Status lifecycle:
CREATED->UPLOADED->FINISHED(orERROR). Bunny API statuses 0=CREATED, 1/2/3=UPLOADED (encoding), 4=FINISHED, 5-8=ERROR. - On edit with new video: If a new video file is uploaded and the record already has a
cdn_video_id,afterEdit()enqueuesDeleteVideoToStream(to remove the old CDN video) followed byUploadVideoToStream(to upload the new one). - On delete: Removes from
video_streams,video_streams_mui,video_streams_lp, andview_metrics. EnqueuesDeleteVideoToStreamif acdn_video_idexists. - Product assignment: JSON array of
{product_id, order}objects posted asselected_products. Product search endpoint (search_stream_products) searches by product name (LIKE), product ID (exact), product code (exact), or barcode (exact), returns up to 50 results with product code images. - View metrics:
record_view()endpoint records hits viaview_metrics_model::recordMetrics(). Listing page shows total hits per video formatted viaformat_stream_views(). - Admin search: Session-persisted search by video name (LIKE) or
cdn_video_id(LIKE).
Admin Controllers
| Controller | Lines | Subsystem | Key Methods |
|---|---|---|---|
Adv_video_admin.php | 199 | YouTube | index($offset), add(), edit($id), delete($videoId) |
AdvVideoShowcaseAdmin.php | 405 | Stream | index($offset), add(), edit($id), delete($videoId), search_stream_products(), get_video_status($id), record_view() |
REST API (YouTube Videos only)
The modern REST layer covers the YouTube video entity (video table) with full CRUD + file upload support.
| Method | Endpoint | Action | Auth |
|---|---|---|---|
| GET | /rest/cms/video | List with filters, sorts, pagination | Guest |
| GET | /rest/cms/video/{id} | Single by ID | Guest |
| GET | /rest/cms/video/item | Single by filter | Guest |
| POST | /rest/cms/video | Create (JSON or multipart) | Backend: ADMIN, MEDIA |
| POST | /rest/cms/video/{id} | Update (JSON or multipart) | Backend: ADMIN, MEDIA |
| DELETE | /rest/cms/video/{id} | Delete | Backend: ADMIN, MEDIA |
Relations: translations (one-to-many), products (many-to-many via shop_product_video_lp), vendors (many-to-many via shop_vendor_video_lp).
Upload field: thumb -> files/videos/.
Routes
Admin (Legacy):
video_admin/video_admin/(.+)->video/video_adminvideo_showcase_admin/video/video_showcase_admin->video/video_showcase_adminvideo/video_showcase_admin/search_stream_products-> product search API
Storefront (Stream):
reels->eshop/reels/index(paginated Reels page)api/reels->eshop/reels/getPaginatedVideos(JSON pagination endpoint)
Configuration
| Setting | Location | Description |
|---|---|---|
VIDEOSHOWCASE.ENABLED | Registry | Feature gate for Stream subsystem |
VIDEOSHOWCASE.PROVIDER | Registry | Provider string (currently only BUNNY) |
BUNNY.BUNNY_API_KEY | Registry (encrypted) | Bunny.net API key |
BUNNY.BUNNY_LIBRARY_ID | Registry | Bunny video library ID |
BUNNY.BUNNY_CDN_HOSTNAME | Registry | CDN hostname for URL generation |
streamAllowedTypes | app.php | mp4|avi|wmv|mov|gif|webp |
streamPreviewAllowedTypesList | app.php | gif|webp |
Settings page: settings/videoShowcase (controller: Adv_settings::videoShowcase()). Validates Bunny credentials are provided when BUNNY is selected as provider.
Admin Menu
Under CONTENT group, parent "Video" with two children:
- Videos (
video_admin) -- YouTube video management. Roles: ADVISABLE, ADMIN, CMS, MEDIA. - Video Showcase (
video_showcase_admin) -- Stream video management. Roles: ADVISABLE, ADMIN, CMS, MEDIA. Conditionally displayed based onVIDEOSHOWCASE.ENABLED.
Authorization
| Layer | Roles |
|---|---|
YouTube admin (Adv_video_admin) | ADVISABLE, ADMIN, MEDIA |
Stream admin (AdvVideoShowcaseAdmin) | ADVISABLE, ADMIN, MEDIA |
REST API write (rest_policies.php) | ADMIN, MEDIA (backend auth) |
| REST API read | Guest (no auth) |
Business Rules
| Rule | Description |
|---|---|
| YouTube ID uniqueness | Enforced via is_unique form validation rule on video.youtube_video_id, excluding current record on update |
| Feature gate | Stream subsystem returns 404 if VIDEOSHOWCASE.ENABLED is falsy |
| Async CDN upload | Video file uploaded locally first, then pushed to Bunny CDN asynchronously via job queue |
| Status polling | CheckVideoStatusStream re-queues itself until Bunny reports FINISHED or ERROR |
| CDN replacement | On video file replacement, old CDN video deleted before new one uploaded |
| Cascade delete (Stream) | Removes stream record, MUI, product lookup, view metrics, and enqueues CDN deletion |
| Home page display | Stream videos shown on home page if active=1, is_promo=1, status=FINISHED, and cdn_video_id IS NOT NULL |
| Product search (Stream) | Searches by name (LIKE), product ID (exact), product code (exact), or barcode (exact); excludes soft-deleted products |
Storefront Integration
- Home page:
Adv_home::getStreamVideos()fetches up to 8 promo/active/finished stream videos with their assigned products, enriches with CDN URLs, and passes to the template ashomeStreamSliderData. - Reels page:
Adv_reelscontroller provides server-rendered initial page + AJAX pagination (getPaginatedVideos) for the full stream video catalog. Products enriched with live pricing, stock, and image data. All video products collected for structured data output. - Product page YouTube embed:
AdvProductGalleryWithVideo.vuerenders a YouTube iframe (youtube.com/embed/{id}) when the product's MUIvideofield is populated. Responsive 16:9 aspect ratio. - Vendor page: Vendors can have YouTube videos assigned via
shop_vendor_video_lp, displayed on vendor detail pages.
Video Player Library
The storefront video player uses video.js (video.js npm package) in assets/main/vue/streamSlider/VideoPlayer.vue for HLS playback of Bunny CDN streams. Video.js handles adaptive bitrate streaming, playback controls, and responsive sizing for the Reels page and home page video slider.
Related Flows
- AD-02 Product Management -- product-level YouTube video field, batch video assignment
- AD-13 Settings -- Video Showcase configuration (Bunny credentials)
- AD-19 Vendor Management -- vendor video assignment
- SY-23 MUI Translation Pattern --
video_muiandvideo_streams_muicompanion tables store per-language names, descriptions, and slugs - SY-25 File Upload & Storage --
AdvUploaderhandles thumbnail and video file uploads; Showcase uploads are processed locally before async CDN push to Bunny.net