@codingfactory/mediables-vue 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/PixiFrameExporter-BTU38EVl.cjs +2 -0
- package/dist/PixiFrameExporter-BTU38EVl.cjs.map +1 -0
- package/dist/PixiFrameExporter-Bb3QNWP-.js +199 -0
- package/dist/PixiFrameExporter-Bb3QNWP-.js.map +1 -0
- package/dist/adapters/MediablesAdapter.d.ts +19 -0
- package/dist/adapters/SpatieAdapter.d.ts +18 -0
- package/dist/adapters/index.d.ts +18 -0
- package/dist/components/AdminMediaBrowser.vue.d.ts +11 -0
- package/dist/components/AdminMediaBrowserExample.vue.d.ts +2 -0
- package/dist/components/AdminMediaBulkActionsToolbar.vue.d.ts +2 -0
- package/dist/components/AdminMediaGrid.vue.d.ts +17 -0
- package/dist/components/AdminMediaListItem.vue.d.ts +10 -0
- package/dist/components/AdminMediaManager.vue.d.ts +25 -0
- package/dist/components/AdminMediaUploader.vue.d.ts +11 -0
- package/dist/components/AlbumBrowser.vue.d.ts +17 -0
- package/dist/components/AlbumManager.vue.d.ts +16 -0
- package/dist/components/AlbumMediaGrid.vue.d.ts +28 -0
- package/dist/components/AlbumTree.vue.d.ts +35 -0
- package/dist/components/BulkActionsToolbar.vue.d.ts +2 -0
- package/dist/components/ConversionProgressIndicator.vue.d.ts +2 -0
- package/dist/components/Editor.vue.d.ts +29 -0
- package/dist/components/ExampleGridAndList.vue.d.ts +2 -0
- package/dist/components/ExampleUsage.vue.d.ts +2 -0
- package/dist/components/Grid.vue.d.ts +2 -0
- package/dist/components/ImageEditor/ImageEditor.vue.d.ts +3 -0
- package/dist/components/ImageEditorModal.vue.d.ts +16 -0
- package/dist/components/ImagePicker.vue.d.ts +32 -0
- package/dist/components/ImageUploadZone.vue.d.ts +7 -0
- package/dist/components/Item.vue.d.ts +2 -0
- package/dist/components/Library.vue.d.ts +2 -0
- package/dist/components/ManagedMediaGallery.vue.d.ts +12 -0
- package/dist/components/MediaAttacher.vue.d.ts +21 -0
- package/dist/components/MediaBrowser.vue.d.ts +2 -0
- package/dist/components/MediaCard.vue.d.ts +2 -0
- package/dist/components/MediaFilters.vue.d.ts +2 -0
- package/dist/components/MediaGrid.vue.d.ts +31 -0
- package/dist/components/MediaInfoEditor.vue.d.ts +7 -0
- package/dist/components/MediaManager.vue.d.ts +2 -0
- package/dist/components/MediaUploadWithProgress.vue.d.ts +2 -0
- package/dist/components/MediaUploader.vue.d.ts +2 -0
- package/dist/components/MediaWorkspace.vue.d.ts +19 -0
- package/dist/components/Modal.vue.d.ts +2 -0
- package/dist/components/ModelMediaManager.vue.d.ts +80 -0
- package/dist/components/Pagination.vue.d.ts +2 -0
- package/dist/components/Search.vue.d.ts +2 -0
- package/dist/components/VideoEditorSimple.vue.d.ts +2 -0
- package/dist/components/VideoExportPanel.vue.d.ts +2 -0
- package/dist/components/VideoTimeline.vue.d.ts +2 -0
- package/dist/components/VideoTimelineSimple.vue.d.ts +2 -0
- package/dist/components/VideoToolsPanel.vue.d.ts +2 -0
- package/dist/components/albums/AlbumTreeNode.vue.d.ts +23 -0
- package/dist/components/attachment/MediaAttachment.vue.d.ts +23 -0
- package/dist/components/attachment/index.d.ts +4 -0
- package/dist/components/collection/MediaCollection.vue.d.ts +27 -0
- package/dist/components/collection/MediaCollectionDropzone.vue.d.ts +18 -0
- package/dist/components/collection/MediaCollectionItem.vue.d.ts +2 -0
- package/dist/components/collection/index.d.ts +6 -0
- package/dist/components/form/MediaHiddenFields.vue.d.ts +2 -0
- package/dist/components/form/index.d.ts +4 -0
- package/dist/components/image/ImageEditor.vue.d.ts +2 -0
- package/dist/components/image/ImageItem.vue.d.ts +2 -0
- package/dist/components/renderless/MediaAttachmentProvider.vue.d.ts +12 -0
- package/dist/components/renderless/MediaCollectionProvider.vue.d.ts +12 -0
- package/dist/components/renderless/index.d.ts +7 -0
- package/dist/components/timeline/TimeRuler.vue.d.ts +2 -0
- package/dist/components/timeline/VideoTrack.vue.d.ts +2 -0
- package/dist/components/tools/VideoFiltersPanel.vue.d.ts +7 -0
- package/dist/components/tools/VideoTextPanel.vue.d.ts +2 -0
- package/dist/components/video/AudioTrackManager.vue.d.ts +2 -0
- package/dist/components/video/EditorControls.vue.d.ts +2 -0
- package/dist/components/video/ExportPanel.vue.d.ts +2 -0
- package/dist/components/video/FilterSelector.vue.d.ts +2 -0
- package/dist/components/video/LiveStreamManager.vue.d.ts +2 -0
- package/dist/components/video/StreamCredentials.vue.d.ts +2 -0
- package/dist/components/video/StreamStatus.vue.d.ts +2 -0
- package/dist/components/video/TextOverlayPanel.vue.d.ts +2 -0
- package/dist/components/video/ThumbnailPicker.vue.d.ts +2 -0
- package/dist/components/video/TimelineClip.vue.d.ts +2 -0
- package/dist/components/video/TimelineControls.vue.d.ts +2 -0
- package/dist/components/video/TransitionSelector.vue.d.ts +2 -0
- package/dist/components/video/VideoControls.vue.d.ts +2 -0
- package/dist/components/video/VideoEditor.vue.d.ts +8 -0
- package/dist/components/video/VideoEditorDialog.vue.d.ts +12 -0
- package/dist/components/video/VideoFilterCarousel.vue.d.ts +2 -0
- package/dist/components/video/VideoFilterPreview.vue.d.ts +18 -0
- package/dist/components/video/VideoPlayer.vue.d.ts +2 -0
- package/dist/components/video/VideoPreview.vue.d.ts +3 -0
- package/dist/components/video/VideoPreviewCSS.vue.d.ts +2 -0
- package/dist/components/video/VideoPreviewEngine.vue.d.ts +3 -0
- package/dist/components/video/VideoTimeline.vue.d.ts +2 -0
- package/dist/components/video/VideoUploadProgress.vue.d.ts +2 -0
- package/dist/components/video/VideoUploader.vue.d.ts +29 -0
- package/dist/components/video/index.d.ts +19 -0
- package/dist/composables/useAccordion.d.ts +138 -0
- package/dist/composables/useAlbumDragDrop.d.ts +24 -0
- package/dist/composables/useAlbums.d.ts +17 -0
- package/dist/composables/useFloatingPills.d.ts +111 -0
- package/dist/composables/useHaptic.d.ts +10 -0
- package/dist/composables/useImageEditorModal.d.ts +277 -0
- package/dist/composables/useLiveStream.d.ts +66 -0
- package/dist/composables/useMediaAttachment.d.ts +105 -0
- package/dist/composables/useMediaCollection.d.ts +122 -0
- package/dist/composables/useMediaConversionProgress.d.ts +31 -0
- package/dist/composables/useMediaDragSort.d.ts +56 -0
- package/dist/composables/useMediaSelection.d.ts +27 -0
- package/dist/composables/useMediaUploadQueue.d.ts +61 -0
- package/dist/composables/useMediaValidation.d.ts +59 -0
- package/dist/composables/useRadialMenu.d.ts +116 -0
- package/dist/composables/useSanctumClient.d.ts +31 -0
- package/dist/composables/useTheme.d.ts +7 -0
- package/dist/composables/useToast.d.ts +25 -0
- package/dist/composables/useVideoEditor.d.ts +127 -0
- package/dist/composables/useVideoFilters.d.ts +176 -0
- package/dist/composables/useVideoPlayer.d.ts +50 -0
- package/dist/composables/useVideoUpload.d.ts +134 -0
- package/dist/filters/controlMapping.d.ts +31 -0
- package/dist/filters/css-registry.d.ts +83 -0
- package/dist/filters/definitions/adjustment.d.ts +2 -0
- package/dist/filters/definitions/adjustmentAdvanced.d.ts +2 -0
- package/dist/filters/definitions/advancedBloom.d.ts +2 -0
- package/dist/filters/definitions/alpha.d.ts +2 -0
- package/dist/filters/definitions/ascii.d.ts +2 -0
- package/dist/filters/definitions/backdropBlur.d.ts +2 -0
- package/dist/filters/definitions/bevel.d.ts +2 -0
- package/dist/filters/definitions/bloom.d.ts +2 -0
- package/dist/filters/definitions/blur.d.ts +2 -0
- package/dist/filters/definitions/bulgePinch.d.ts +2 -0
- package/dist/filters/definitions/colorGradient.d.ts +2 -0
- package/dist/filters/definitions/colorMap.d.ts +2 -0
- package/dist/filters/definitions/colorMatrix.d.ts +2 -0
- package/dist/filters/definitions/colorOverlay.d.ts +2 -0
- package/dist/filters/definitions/colorReplace.d.ts +2 -0
- package/dist/filters/definitions/convolution.d.ts +2 -0
- package/dist/filters/definitions/crossHatch.d.ts +2 -0
- package/dist/filters/definitions/crt.d.ts +2 -0
- package/dist/filters/definitions/displacement.d.ts +2 -0
- package/dist/filters/definitions/dot.d.ts +2 -0
- package/dist/filters/definitions/dropShadow.d.ts +2 -0
- package/dist/filters/definitions/emboss.d.ts +2 -0
- package/dist/filters/definitions/glitch.d.ts +2 -0
- package/dist/filters/definitions/glow.d.ts +2 -0
- package/dist/filters/definitions/godray.d.ts +2 -0
- package/dist/filters/definitions/grayscale.d.ts +2 -0
- package/dist/filters/definitions/hslAdjustment.d.ts +2 -0
- package/dist/filters/definitions/kawaseBlur.d.ts +2 -0
- package/dist/filters/definitions/lightmap.d.ts +2 -0
- package/dist/filters/definitions/motionBlur.d.ts +2 -0
- package/dist/filters/definitions/multiColorReplace.d.ts +2 -0
- package/dist/filters/definitions/noise.d.ts +2 -0
- package/dist/filters/definitions/oldFilm.d.ts +2 -0
- package/dist/filters/definitions/outline.d.ts +2 -0
- package/dist/filters/definitions/pixelate.d.ts +2 -0
- package/dist/filters/definitions/radialBlur.d.ts +2 -0
- package/dist/filters/definitions/reflection.d.ts +2 -0
- package/dist/filters/definitions/rgbSplit.d.ts +2 -0
- package/dist/filters/definitions/shockwave.d.ts +2 -0
- package/dist/filters/definitions/simplexNoise.d.ts +2 -0
- package/dist/filters/definitions/tiltShift.d.ts +2 -0
- package/dist/filters/definitions/twist.d.ts +2 -0
- package/dist/filters/definitions/vignette.d.ts +2 -0
- package/dist/filters/definitions/zoomBlur.d.ts +2 -0
- package/dist/filters/factory.d.ts +38 -0
- package/dist/filters/filters/controlMapping.d.ts +31 -0
- package/dist/filters/filters/definitions/adjustment.d.ts +2 -0
- package/dist/filters/filters/definitions/adjustmentAdvanced.d.ts +2 -0
- package/dist/filters/filters/definitions/advancedBloom.d.ts +2 -0
- package/dist/filters/filters/definitions/alpha.d.ts +2 -0
- package/dist/filters/filters/definitions/ascii.d.ts +2 -0
- package/dist/filters/filters/definitions/backdropBlur.d.ts +2 -0
- package/dist/filters/filters/definitions/bevel.d.ts +2 -0
- package/dist/filters/filters/definitions/bloom.d.ts +2 -0
- package/dist/filters/filters/definitions/blur.d.ts +2 -0
- package/dist/filters/filters/definitions/bulgePinch.d.ts +2 -0
- package/dist/filters/filters/definitions/colorGradient.d.ts +2 -0
- package/dist/filters/filters/definitions/colorMap.d.ts +2 -0
- package/dist/filters/filters/definitions/colorMatrix.d.ts +2 -0
- package/dist/filters/filters/definitions/colorOverlay.d.ts +2 -0
- package/dist/filters/filters/definitions/colorReplace.d.ts +2 -0
- package/dist/filters/filters/definitions/convolution.d.ts +2 -0
- package/dist/filters/filters/definitions/crossHatch.d.ts +2 -0
- package/dist/filters/filters/definitions/crt.d.ts +2 -0
- package/dist/filters/filters/definitions/displacement.d.ts +2 -0
- package/dist/filters/filters/definitions/dot.d.ts +2 -0
- package/dist/filters/filters/definitions/dropShadow.d.ts +2 -0
- package/dist/filters/filters/definitions/emboss.d.ts +2 -0
- package/dist/filters/filters/definitions/glitch.d.ts +2 -0
- package/dist/filters/filters/definitions/glow.d.ts +2 -0
- package/dist/filters/filters/definitions/godray.d.ts +2 -0
- package/dist/filters/filters/definitions/grayscale.d.ts +2 -0
- package/dist/filters/filters/definitions/hslAdjustment.d.ts +2 -0
- package/dist/filters/filters/definitions/kawaseBlur.d.ts +2 -0
- package/dist/filters/filters/definitions/lightmap.d.ts +2 -0
- package/dist/filters/filters/definitions/motionBlur.d.ts +2 -0
- package/dist/filters/filters/definitions/multiColorReplace.d.ts +2 -0
- package/dist/filters/filters/definitions/noise.d.ts +2 -0
- package/dist/filters/filters/definitions/oldFilm.d.ts +2 -0
- package/dist/filters/filters/definitions/outline.d.ts +2 -0
- package/dist/filters/filters/definitions/pixelate.d.ts +2 -0
- package/dist/filters/filters/definitions/radialBlur.d.ts +2 -0
- package/dist/filters/filters/definitions/reflection.d.ts +2 -0
- package/dist/filters/filters/definitions/rgbSplit.d.ts +2 -0
- package/dist/filters/filters/definitions/shockwave.d.ts +2 -0
- package/dist/filters/filters/definitions/simplexNoise.d.ts +2 -0
- package/dist/filters/filters/definitions/tiltShift.d.ts +2 -0
- package/dist/filters/filters/definitions/twist.d.ts +2 -0
- package/dist/filters/filters/definitions/vignette.d.ts +2 -0
- package/dist/filters/filters/definitions/zoomBlur.d.ts +2 -0
- package/dist/filters/filters/factory.d.ts +36 -0
- package/dist/filters/filters/index.d.ts +93 -0
- package/dist/filters/filters/registry.d.ts +89 -0
- package/dist/filters/index.d.ts +93 -0
- package/dist/filters/registry.d.ts +89 -0
- package/dist/filters/video-compatible.d.ts +77 -0
- package/dist/filters/video-css-filters.d.ts +153 -0
- package/dist/index-6yUGA--H.cjs +42 -0
- package/dist/index-6yUGA--H.cjs.map +1 -0
- package/dist/index-CcGWfCCV.js +7799 -0
- package/dist/index-CcGWfCCV.js.map +1 -0
- package/dist/index-DTUgsw7J.cjs +76 -0
- package/dist/index-DTUgsw7J.cjs.map +1 -0
- package/dist/index-VrUG0lmk.js +28655 -0
- package/dist/index-VrUG0lmk.js.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/js/workers/material-color-extractor.js +215 -0
- package/dist/mediables-vanilla.cjs +2 -0
- package/dist/mediables-vanilla.cjs.map +1 -0
- package/dist/mediables-vanilla.mjs +12 -0
- package/dist/mediables-vanilla.mjs.map +1 -0
- package/dist/mediables-vue.cjs +2 -0
- package/dist/mediables-vue.cjs.map +1 -0
- package/dist/mediables-vue.mjs +67 -0
- package/dist/mediables-vue.mjs.map +1 -0
- package/dist/render-page/assets/index-hBfvGPpt.js +48933 -0
- package/dist/render-page/index.html +18 -0
- package/dist/services/VideoJobClient.d.ts +79 -0
- package/dist/stores/albumStore.d.ts +4 -0
- package/dist/stores/mediaVariantStore.d.ts +1 -0
- package/dist/stores/useAdminMediaStore.d.ts +16 -0
- package/dist/stores/useMediaStore.d.ts +25 -0
- package/dist/stores/useVideoStore.d.ts +21 -0
- package/dist/stores/variantPollStore.d.ts +5 -0
- package/dist/stores/video.d.ts +1 -0
- package/dist/style.css +1 -0
- package/dist/types/adapter.d.ts +181 -0
- package/dist/types/album.d.ts +28 -0
- package/dist/types/api.d.ts +88 -0
- package/dist/types/collection.d.ts +306 -0
- package/dist/types/editor.d.ts +172 -0
- package/dist/types/image.d.ts +210 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/media.d.ts +107 -0
- package/dist/types/types/address.d.ts +66 -0
- package/dist/types/types/admin/intelligent-po.d.ts +47 -0
- package/dist/types/types/admin/products/index.d.ts +17 -0
- package/dist/types/types/admin/purchase-order.d.ts +50 -0
- package/dist/types/types/admin/receipt.d.ts +86 -0
- package/dist/types/types/admin/vendor.d.ts +61 -0
- package/dist/types/types/ai.d.ts +63 -0
- package/dist/types/types/aiActions.d.ts +42 -0
- package/dist/types/types/aiDesigner.d.ts +77 -0
- package/dist/types/types/api-errors.d.ts +6 -0
- package/dist/types/types/api.d.ts +109 -0
- package/dist/types/types/bundle.d.ts +131 -0
- package/dist/types/types/bundles/analytics.d.ts +64 -0
- package/dist/types/types/bundles.d.ts +108 -0
- package/dist/types/types/cart.d.ts +81 -0
- package/dist/types/types/checkout.d.ts +40 -0
- package/dist/types/types/component-config.d.ts +26 -0
- package/dist/types/types/components.d.ts +32 -0
- package/dist/types/types/content.d.ts +138 -0
- package/dist/types/types/coupon.d.ts +32 -0
- package/dist/types/types/customer-product-history.d.ts +210 -0
- package/dist/types/types/drag-contracts.d.ts +40 -0
- package/dist/types/types/drag-drop.d.ts +19 -0
- package/dist/types/types/editor.d.ts +127 -0
- package/dist/types/types/errors.d.ts +36 -0
- package/dist/types/types/feedback.d.ts +122 -0
- package/dist/types/types/image.d.ts +210 -0
- package/dist/types/types/index.d.ts +62 -0
- package/dist/types/types/instagram.d.ts +86 -0
- package/dist/types/types/ionic-components.d.ts +152 -0
- package/dist/types/types/layout.d.ts +127 -0
- package/dist/types/types/media-gateway.d.ts +34 -0
- package/dist/types/types/media.d.ts +178 -0
- package/dist/types/types/notifications.d.ts +123 -0
- package/dist/types/types/order-management.d.ts +245 -0
- package/dist/types/types/pageBuilder/block.d.ts +34 -0
- package/dist/types/types/pageBuilder/blocks.d.ts +82 -0
- package/dist/types/types/pageBuilder/cache.d.ts +33 -0
- package/dist/types/types/pageBuilder/editor.d.ts +15 -0
- package/dist/types/types/pageBuilder/field.d.ts +11 -0
- package/dist/types/types/pageBuilder/index.d.ts +24 -0
- package/dist/types/types/pageBuilder/revisions.d.ts +40 -0
- package/dist/types/types/pageBuilder/templates.d.ts +62 -0
- package/dist/types/types/pattern.d.ts +40 -0
- package/dist/types/types/payment.d.ts +21 -0
- package/dist/types/types/payments.d.ts +10 -0
- package/dist/types/types/pipeline.d.ts +12 -0
- package/dist/types/types/pixi/filter-args.d.ts +274 -0
- package/dist/types/types/pixi/filters-extended.d.ts +157 -0
- package/dist/types/types/pixi/filters.d.ts +38 -0
- package/dist/types/types/preview.d.ts +36 -0
- package/dist/types/types/pricing.d.ts +31 -0
- package/dist/types/types/quickbooks.d.ts +43 -0
- package/dist/types/types/receipt.d.ts +121 -0
- package/dist/types/types/rewards.d.ts +110 -0
- package/dist/types/types/saved-cart.d.ts +51 -0
- package/dist/types/types/settings.d.ts +63 -0
- package/dist/types/types/shipment.d.ts +62 -0
- package/dist/types/types/shipping.d.ts +98 -0
- package/dist/types/types/sidebar-variations.d.ts +226 -0
- package/dist/types/types/slots.d.ts +2 -0
- package/dist/types/types/specification-types.d.ts +70 -0
- package/dist/types/types/specifications.d.ts +163 -0
- package/dist/types/types/store.d.ts +64 -0
- package/dist/types/types/template.d.ts +8 -0
- package/dist/types/types/user.d.ts +47 -0
- package/dist/types/types/variant-groups.d.ts +158 -0
- package/dist/types/types/wishlist.d.ts +73 -0
- package/dist/types/types/workflow-wizard.d.ts +12 -0
- package/dist/types/video.d.ts +449 -0
- package/dist/utils/category-tree-constants.d.ts +42 -0
- package/dist/utils/cookies.d.ts +3 -0
- package/dist/utils/crypto-polyfill.d.ts +4 -0
- package/dist/utils/datetime.d.ts +43 -0
- package/dist/utils/debounce.d.ts +10 -0
- package/dist/utils/debugConsole.d.ts +69 -0
- package/dist/utils/editor/argHelpers.d.ts +6 -0
- package/dist/utils/formatters.d.ts +105 -0
- package/dist/utils/isPresignedAwsUrl.d.ts +67 -0
- package/dist/utils/media-helpers.d.ts +34 -0
- package/dist/utils/normalisePricing.d.ts +11 -0
- package/dist/utils/recipe-generator.d.ts +34 -0
- package/dist/utils/string.d.ts +29 -0
- package/dist/utils/unwrapApiResponse.d.ts +5 -0
- package/dist/utils/uuid.d.ts +30 -0
- package/dist/utils/validators.d.ts +28 -0
- package/dist/utils/video-export.d.ts +60 -0
- package/dist/v3-ionic-1-demo.html +440 -0
- package/dist/video-engine/VideoEngine.d.ts +267 -0
- package/dist/video-engine/adapters/AudioManager.d.ts +106 -0
- package/dist/video-engine/adapters/CSSFilterAdapter.d.ts +106 -0
- package/dist/video-engine/adapters/ExportManager.d.ts +88 -0
- package/dist/video-engine/adapters/FilterBridge.d.ts +96 -0
- package/dist/video-engine/adapters/MediablesCompositionAdapter.d.ts +56 -0
- package/dist/video-engine/adapters/PixiFrameExporter.d.ts +52 -0
- package/dist/video-engine/adapters/RenderQueue.d.ts +119 -0
- package/dist/video-engine/adapters/TextOverlayManager.d.ts +93 -0
- package/dist/video-engine/adapters/TimelineAdapter.d.ts +58 -0
- package/dist/video-engine/adapters/TransitionManager.d.ts +76 -0
- package/dist/video-engine/adapters/WebCodecsExport.d.ts +36 -0
- package/dist/video-engine/compositions/examples/example.d.ts +2 -0
- package/dist/video-engine/filters/CSSFilterSystem.d.ts +213 -0
- package/dist/video-engine/index.d.ts +14 -0
- package/dist/video-engine/presets/ExportPresets.d.ts +70 -0
- package/dist/video-engine/types/index.d.ts +96 -0
- package/dist/video-engine/types.d.ts +1 -0
- package/dist/video-engine/utils/EventEmitter.d.ts +12 -0
- package/dist/video-engine/utils/MediaSourceResolver.d.ts +9 -0
- package/dist/video-engine/utils/error-reporter.d.ts +159 -0
- package/dist/video-engine/utils/keyboard-shortcuts.d.ts +120 -0
- package/dist/video-engine/utils/pixi-video-fallback.d.ts +2 -0
- package/docs/video-subsystem/README.md +490 -0
- package/docs/video-subsystem/api-reference.md +747 -0
- package/docs/video-subsystem/component-examples.md +1477 -0
- package/docs/video-subsystem/integration-guide.md +1021 -0
- package/package.json +102 -0
|
@@ -0,0 +1,1021 @@
|
|
|
1
|
+
# Video Subsystem Integration Guide
|
|
2
|
+
|
|
3
|
+
This guide provides step-by-step instructions for integrating the Mediables video subsystem into your Laravel application.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
1. [Prerequisites](#prerequisites)
|
|
7
|
+
2. [Installation](#installation)
|
|
8
|
+
3. [Backend Setup](#backend-setup)
|
|
9
|
+
4. [Frontend Integration](#frontend-integration)
|
|
10
|
+
5. [Provider Configuration](#provider-configuration)
|
|
11
|
+
6. [Queue Configuration](#queue-configuration)
|
|
12
|
+
7. [Testing](#testing)
|
|
13
|
+
8. [Production Deployment](#production-deployment)
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
### System Requirements
|
|
18
|
+
- PHP 8.1 or higher
|
|
19
|
+
- Laravel 11.x or 12.x
|
|
20
|
+
- PostgreSQL 13+ (with UUID support)
|
|
21
|
+
- Redis for queues
|
|
22
|
+
- FFmpeg 4.4+ (for video processing)
|
|
23
|
+
- Node.js 18+ and npm 9+
|
|
24
|
+
|
|
25
|
+
### Required PHP Extensions
|
|
26
|
+
- `ext-gd` or `ext-imagick`
|
|
27
|
+
- `ext-redis`
|
|
28
|
+
- `ext-pcntl` (for queue workers)
|
|
29
|
+
- `ext-exif` (for media metadata)
|
|
30
|
+
|
|
31
|
+
### FFmpeg Installation
|
|
32
|
+
|
|
33
|
+
**macOS**:
|
|
34
|
+
```bash
|
|
35
|
+
brew install ffmpeg
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Ubuntu/Debian**:
|
|
39
|
+
```bash
|
|
40
|
+
sudo apt update
|
|
41
|
+
sudo apt install ffmpeg
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**CentOS/RHEL**:
|
|
45
|
+
```bash
|
|
46
|
+
sudo yum install epel-release
|
|
47
|
+
sudo yum install ffmpeg
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Verify installation:
|
|
51
|
+
```bash
|
|
52
|
+
ffmpeg -version
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
### Step 1: Install Package
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Install via Composer
|
|
61
|
+
composer require mediables/mediables:^2.0
|
|
62
|
+
|
|
63
|
+
# Install Vue components
|
|
64
|
+
npm install @mediables/vue@^2.0
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 2: Publish Configuration
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Publish config file
|
|
71
|
+
php artisan vendor:publish --provider="Mediables\MediablesServiceProvider" --tag="config"
|
|
72
|
+
|
|
73
|
+
# Publish migrations
|
|
74
|
+
php artisan vendor:publish --provider="Mediables\MediablesServiceProvider" --tag="migrations"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Step 3: Run Migrations
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Run the migrations
|
|
81
|
+
php artisan migrate
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Backend Setup
|
|
85
|
+
|
|
86
|
+
### Step 1: Configure Bunny Provider (Built-In)
|
|
87
|
+
|
|
88
|
+
The Mediables package ships with a Bunny video provider. No custom provider class is required.
|
|
89
|
+
|
|
90
|
+
Add the following to your `.env`:
|
|
91
|
+
|
|
92
|
+
```env
|
|
93
|
+
VIDEO_PROVIDER=bunny
|
|
94
|
+
BUNNY_VIDEO_API_KEY=your-api-key
|
|
95
|
+
BUNNY_VIDEO_LIBRARY_ID=your-library-id
|
|
96
|
+
BUNNY_VIDEO_CDN_HOSTNAME=your-cdn.b-cdn.net
|
|
97
|
+
BUNNY_VIDEO_WEBHOOK_SECRET=your-secret
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Step 2: (Optional) Override the Provider Binding
|
|
101
|
+
|
|
102
|
+
If you want to override the provider binding (e.g. for a custom provider), bind `Mediables\Contracts\VideoProviderInterface` in your application:
|
|
103
|
+
|
|
104
|
+
```php
|
|
105
|
+
use Mediables\Contracts\VideoProviderInterface;
|
|
106
|
+
use Mediables\Services\Video\BunnyVideoProvider;
|
|
107
|
+
|
|
108
|
+
$this->app->singleton(VideoProviderInterface::class, function () {
|
|
109
|
+
return new BunnyVideoProvider();
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Step 3: Create API Controllers
|
|
114
|
+
|
|
115
|
+
Create `app/Http/Controllers/Api/V1/VideoUploadController.php`:
|
|
116
|
+
|
|
117
|
+
```php
|
|
118
|
+
<?php
|
|
119
|
+
|
|
120
|
+
namespace App\Http\Controllers\Api\V1;
|
|
121
|
+
|
|
122
|
+
use App\Http\Controllers\Controller;
|
|
123
|
+
use App\Http\Requests\VideoUploadRequest;
|
|
124
|
+
use App\Services\Video\Contracts\VideoProviderInterface;
|
|
125
|
+
use Mediables\Facades\MediaService;
|
|
126
|
+
use Illuminate\Http\JsonResponse;
|
|
127
|
+
|
|
128
|
+
class VideoUploadController extends Controller
|
|
129
|
+
{
|
|
130
|
+
public function __construct(
|
|
131
|
+
private VideoProviderInterface $provider
|
|
132
|
+
) {}
|
|
133
|
+
|
|
134
|
+
public function createUpload(VideoUploadRequest $request): JsonResponse
|
|
135
|
+
{
|
|
136
|
+
// Create pending media record
|
|
137
|
+
$media = MediaService::createFromRequest($request, [
|
|
138
|
+
'collection' => $request->input('collection', 'videos'),
|
|
139
|
+
'disk' => 'videos',
|
|
140
|
+
'status' => 'pending',
|
|
141
|
+
'meta' => [
|
|
142
|
+
'original_name' => $request->input('filename'),
|
|
143
|
+
'mime_type' => $request->input('mime_type'),
|
|
144
|
+
'size' => $request->input('size')
|
|
145
|
+
]
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
// Get upload URL from provider
|
|
149
|
+
$upload = $this->provider->createUploadUrl([
|
|
150
|
+
'metadata' => [
|
|
151
|
+
'media_uuid' => $media->uuid
|
|
152
|
+
]
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
// Update media with upload info
|
|
156
|
+
$media->update([
|
|
157
|
+
'upload_id' => $upload->uploadId,
|
|
158
|
+
'upload_expires_at' => $upload->expiresAt
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
return response()->json([
|
|
162
|
+
'upload_url' => $upload->uploadUrl,
|
|
163
|
+
'upload_id' => $upload->uploadId,
|
|
164
|
+
'media_uuid' => $media->uuid,
|
|
165
|
+
'expires_at' => $upload->expiresAt->toIso8601String()
|
|
166
|
+
], 201);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public function completeUpload(string $uuid): JsonResponse
|
|
170
|
+
{
|
|
171
|
+
$media = MediaService::findByUuid($uuid);
|
|
172
|
+
|
|
173
|
+
if (!$media) {
|
|
174
|
+
return response()->json(['error' => 'Media not found'], 404);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Mark as processing
|
|
178
|
+
$media->update(['status' => 'processing']);
|
|
179
|
+
|
|
180
|
+
// Dispatch job to check processing status
|
|
181
|
+
ProcessVideoAsset::dispatch($media)->delay(now()->addSeconds(10));
|
|
182
|
+
|
|
183
|
+
return response()->json([
|
|
184
|
+
'message' => 'Upload completed, processing started',
|
|
185
|
+
'media' => $media
|
|
186
|
+
]);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Step 4: Create Webhook Handler
|
|
192
|
+
|
|
193
|
+
The Mediables package includes a built-in Bunny webhook endpoint:
|
|
194
|
+
|
|
195
|
+
- **Endpoint**: `POST /api/v1/video/webhooks/bunny`
|
|
196
|
+
- **Security**: `?secret=...` query parameter (recommended) or `X-Webhook-Signature` header (deployment-specific)
|
|
197
|
+
|
|
198
|
+
No additional controller is required unless you want custom behavior.
|
|
199
|
+
|
|
200
|
+
### Step 5: Create Form Requests
|
|
201
|
+
|
|
202
|
+
Create `app/Http/Requests/VideoUploadRequest.php`:
|
|
203
|
+
|
|
204
|
+
```php
|
|
205
|
+
<?php
|
|
206
|
+
|
|
207
|
+
namespace App\Http\Requests;
|
|
208
|
+
|
|
209
|
+
use Illuminate\Foundation\Http\FormRequest;
|
|
210
|
+
|
|
211
|
+
class VideoUploadRequest extends FormRequest
|
|
212
|
+
{
|
|
213
|
+
public function authorize(): bool
|
|
214
|
+
{
|
|
215
|
+
return true; // Add your authorization logic
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public function rules(): array
|
|
219
|
+
{
|
|
220
|
+
return [
|
|
221
|
+
'filename' => 'required|string|max:255',
|
|
222
|
+
'size' => 'required|integer|min:1|max:5368709120', // 5GB max
|
|
223
|
+
'mime_type' => 'required|string|in:video/mp4,video/quicktime,video/x-msvideo,video/x-matroska,video/webm',
|
|
224
|
+
'collection' => 'nullable|string|max:50'
|
|
225
|
+
];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
public function messages(): array
|
|
229
|
+
{
|
|
230
|
+
return [
|
|
231
|
+
'size.max' => 'Video file must not exceed 5GB',
|
|
232
|
+
'mime_type.in' => 'Unsupported video format. Supported formats: MP4, MOV, AVI, MKV, WebM'
|
|
233
|
+
];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Frontend Integration
|
|
239
|
+
|
|
240
|
+
### Step 1: Configure Vue Application
|
|
241
|
+
|
|
242
|
+
Update your Vue app's main file:
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
// main.js or main.ts
|
|
246
|
+
import { createApp } from 'vue'
|
|
247
|
+
import { createPinia } from 'pinia'
|
|
248
|
+
import { IonicVue } from '@ionic/vue'
|
|
249
|
+
import App from './App.vue'
|
|
250
|
+
|
|
251
|
+
// Import Mediables styles
|
|
252
|
+
import '@mediables/vue/dist/style.css'
|
|
253
|
+
|
|
254
|
+
const app = createApp(App)
|
|
255
|
+
const pinia = createPinia()
|
|
256
|
+
|
|
257
|
+
app.use(IonicVue)
|
|
258
|
+
app.use(pinia)
|
|
259
|
+
|
|
260
|
+
// Configure Mediables
|
|
261
|
+
import { configureMediables } from '@mediables/vue'
|
|
262
|
+
|
|
263
|
+
configureMediables({
|
|
264
|
+
apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:8000',
|
|
265
|
+
apiPrefix: '/api/v1',
|
|
266
|
+
storage: {
|
|
267
|
+
driver: 's3',
|
|
268
|
+
bucket: import.meta.env.VITE_S3_BUCKET,
|
|
269
|
+
region: import.meta.env.VITE_S3_REGION
|
|
270
|
+
},
|
|
271
|
+
video: {
|
|
272
|
+
provider: 'bunny',
|
|
273
|
+
maxUploadSize: 5 * 1024 * 1024 * 1024, // 5GB
|
|
274
|
+
allowedFormats: ['video/mp4', 'video/quicktime', 'video/webm'],
|
|
275
|
+
thumbnailQualities: ['poster', '2s', '5s', '10s']
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
app.mount('#app')
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Step 2: Create Video Upload Page
|
|
283
|
+
|
|
284
|
+
```vue
|
|
285
|
+
<template>
|
|
286
|
+
<PageShell>
|
|
287
|
+
<template #header>
|
|
288
|
+
<IonHeader>
|
|
289
|
+
<IonToolbar>
|
|
290
|
+
<IonTitle>Upload Video</IonTitle>
|
|
291
|
+
</IonToolbar>
|
|
292
|
+
</IonHeader>
|
|
293
|
+
</template>
|
|
294
|
+
|
|
295
|
+
<div class="p-4">
|
|
296
|
+
<VideoUploader
|
|
297
|
+
:collection="'user-videos'"
|
|
298
|
+
:max-size="5 * 1024 * 1024 * 1024"
|
|
299
|
+
@uploaded="handleVideoUploaded"
|
|
300
|
+
@error="handleUploadError"
|
|
301
|
+
>
|
|
302
|
+
<template #trigger>
|
|
303
|
+
<IonButton expand="block" fill="outline">
|
|
304
|
+
<IonIcon :icon="videocamOutline" slot="start" />
|
|
305
|
+
Select Video
|
|
306
|
+
</IonButton>
|
|
307
|
+
</template>
|
|
308
|
+
</VideoUploader>
|
|
309
|
+
|
|
310
|
+
<div v-if="uploadedVideos.length > 0" class="mt-6">
|
|
311
|
+
<h3 class="text-lg font-semibold mb-4">Uploaded Videos</h3>
|
|
312
|
+
|
|
313
|
+
<div class="space-y-4">
|
|
314
|
+
<VideoCard
|
|
315
|
+
v-for="video in uploadedVideos"
|
|
316
|
+
:key="video.uuid"
|
|
317
|
+
:media="video"
|
|
318
|
+
@play="playVideo(video)"
|
|
319
|
+
@edit="editVideo(video)"
|
|
320
|
+
/>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
</PageShell>
|
|
325
|
+
</template>
|
|
326
|
+
|
|
327
|
+
<script setup lang="ts">
|
|
328
|
+
import { ref } from 'vue'
|
|
329
|
+
import { useRouter } from 'vue-router'
|
|
330
|
+
import {
|
|
331
|
+
IonHeader,
|
|
332
|
+
IonToolbar,
|
|
333
|
+
IonTitle,
|
|
334
|
+
IonButton,
|
|
335
|
+
IonIcon,
|
|
336
|
+
useIonToast
|
|
337
|
+
} from '@ionic/vue'
|
|
338
|
+
import { videocamOutline } from 'ionicons/icons'
|
|
339
|
+
import { VideoUploader, VideoCard } from '@mediables/vue'
|
|
340
|
+
import PageShell from '@/components/layout/PageShell.vue'
|
|
341
|
+
|
|
342
|
+
const router = useRouter()
|
|
343
|
+
const [presentToast] = useIonToast()
|
|
344
|
+
const uploadedVideos = ref([])
|
|
345
|
+
|
|
346
|
+
async function handleVideoUploaded(media) {
|
|
347
|
+
uploadedVideos.value.unshift(media)
|
|
348
|
+
|
|
349
|
+
await presentToast({
|
|
350
|
+
message: 'Video uploaded successfully!',
|
|
351
|
+
duration: 3000,
|
|
352
|
+
color: 'success'
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function handleUploadError(error) {
|
|
357
|
+
await presentToast({
|
|
358
|
+
message: error.message || 'Upload failed',
|
|
359
|
+
duration: 5000,
|
|
360
|
+
color: 'danger'
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function playVideo(video) {
|
|
365
|
+
router.push(`/video/${video.uuid}/play`)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function editVideo(video) {
|
|
369
|
+
router.push(`/video/${video.uuid}/edit`)
|
|
370
|
+
}
|
|
371
|
+
</script>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Step 3: Create Video Player Page
|
|
375
|
+
|
|
376
|
+
```vue
|
|
377
|
+
<template>
|
|
378
|
+
<PageShell :use-content-wrapper="false">
|
|
379
|
+
<template #header>
|
|
380
|
+
<IonHeader>
|
|
381
|
+
<IonToolbar>
|
|
382
|
+
<IonButtons slot="start">
|
|
383
|
+
<IonBackButton />
|
|
384
|
+
</IonButtons>
|
|
385
|
+
<IonTitle>{{ media?.title || 'Video Player' }}</IonTitle>
|
|
386
|
+
</IonToolbar>
|
|
387
|
+
</IonHeader>
|
|
388
|
+
</template>
|
|
389
|
+
|
|
390
|
+
<div class="video-player-container">
|
|
391
|
+
<VideoPlayer
|
|
392
|
+
v-if="media"
|
|
393
|
+
:media="media"
|
|
394
|
+
:controls="true"
|
|
395
|
+
:autoplay="false"
|
|
396
|
+
@play="handlePlay"
|
|
397
|
+
@ended="handleEnded"
|
|
398
|
+
/>
|
|
399
|
+
|
|
400
|
+
<div class="p-4">
|
|
401
|
+
<h2 class="text-xl font-semibold">{{ media?.title }}</h2>
|
|
402
|
+
<p class="text-gray-600 mt-2">{{ media?.description }}</p>
|
|
403
|
+
|
|
404
|
+
<div class="flex items-center gap-4 mt-4">
|
|
405
|
+
<span class="text-sm text-gray-500">
|
|
406
|
+
Duration: {{ formatDuration(media?.duration) }}
|
|
407
|
+
</span>
|
|
408
|
+
<span class="text-sm text-gray-500">
|
|
409
|
+
Size: {{ formatSize(media?.size) }}
|
|
410
|
+
</span>
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
</PageShell>
|
|
415
|
+
</template>
|
|
416
|
+
|
|
417
|
+
<script setup lang="ts">
|
|
418
|
+
import { ref, onMounted } from 'vue'
|
|
419
|
+
import { useRoute } from 'vue-router'
|
|
420
|
+
import {
|
|
421
|
+
IonHeader,
|
|
422
|
+
IonToolbar,
|
|
423
|
+
IonTitle,
|
|
424
|
+
IonButtons,
|
|
425
|
+
IonBackButton
|
|
426
|
+
} from '@ionic/vue'
|
|
427
|
+
import { VideoPlayer } from '@mediables/vue'
|
|
428
|
+
import PageShell from '@/components/layout/PageShell.vue'
|
|
429
|
+
import { useMediaStore } from '@/stores/media'
|
|
430
|
+
|
|
431
|
+
const route = useRoute()
|
|
432
|
+
const mediaStore = useMediaStore()
|
|
433
|
+
const media = ref(null)
|
|
434
|
+
|
|
435
|
+
onMounted(async () => {
|
|
436
|
+
const uuid = route.params.uuid
|
|
437
|
+
media.value = await mediaStore.fetchMedia(uuid)
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
function handlePlay() {
|
|
441
|
+
console.log('Video started playing')
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function handleEnded() {
|
|
445
|
+
console.log('Video playback ended')
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function formatDuration(seconds) {
|
|
449
|
+
if (!seconds) return 'N/A'
|
|
450
|
+
const mins = Math.floor(seconds / 60)
|
|
451
|
+
const secs = Math.floor(seconds % 60)
|
|
452
|
+
return `${mins}:${secs.toString().padStart(2, '0')}`
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function formatSize(bytes) {
|
|
456
|
+
if (!bytes) return 'N/A'
|
|
457
|
+
const mb = bytes / (1024 * 1024)
|
|
458
|
+
return `${mb.toFixed(2)} MB`
|
|
459
|
+
}
|
|
460
|
+
</script>
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### Step 4: Create Video Editor Page
|
|
464
|
+
|
|
465
|
+
```vue
|
|
466
|
+
<template>
|
|
467
|
+
<PageShell :use-content-wrapper="false">
|
|
468
|
+
<VideoEditor
|
|
469
|
+
v-if="media"
|
|
470
|
+
:media-uuid="media.uuid"
|
|
471
|
+
@export="handleExport"
|
|
472
|
+
@save-draft="handleSaveDraft"
|
|
473
|
+
/>
|
|
474
|
+
|
|
475
|
+
<IonLoading
|
|
476
|
+
:is-open="exporting"
|
|
477
|
+
message="Processing video..."
|
|
478
|
+
/>
|
|
479
|
+
</PageShell>
|
|
480
|
+
</template>
|
|
481
|
+
|
|
482
|
+
<script setup lang="ts">
|
|
483
|
+
import { ref, onMounted } from 'vue'
|
|
484
|
+
import { useRoute, useRouter } from 'vue-router'
|
|
485
|
+
import { IonLoading, useIonToast } from '@ionic/vue'
|
|
486
|
+
import { VideoEditor } from '@mediables/vue'
|
|
487
|
+
import PageShell from '@/components/layout/PageShell.vue'
|
|
488
|
+
import { useMediaStore } from '@/stores/media'
|
|
489
|
+
import api from '@/services/api'
|
|
490
|
+
|
|
491
|
+
const route = useRoute()
|
|
492
|
+
const router = useRouter()
|
|
493
|
+
const [presentToast] = useIonToast()
|
|
494
|
+
const mediaStore = useMediaStore()
|
|
495
|
+
|
|
496
|
+
const media = ref(null)
|
|
497
|
+
const exporting = ref(false)
|
|
498
|
+
|
|
499
|
+
onMounted(async () => {
|
|
500
|
+
const uuid = route.params.uuid
|
|
501
|
+
media.value = await mediaStore.fetchMedia(uuid)
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
async function handleExport(recipe) {
|
|
505
|
+
exporting.value = true
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
const response = await api.post(`/video/${media.value.uuid}/export`, {
|
|
509
|
+
recipe
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
await presentToast({
|
|
513
|
+
message: 'Export started! We\'ll notify you when it\'s ready.',
|
|
514
|
+
duration: 5000,
|
|
515
|
+
color: 'success'
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
// Navigate to export status page
|
|
519
|
+
router.push(`/video/export/${response.data.job_id}`)
|
|
520
|
+
|
|
521
|
+
} catch (error) {
|
|
522
|
+
await presentToast({
|
|
523
|
+
message: 'Export failed. Please try again.',
|
|
524
|
+
duration: 5000,
|
|
525
|
+
color: 'danger'
|
|
526
|
+
})
|
|
527
|
+
} finally {
|
|
528
|
+
exporting.value = false
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async function handleSaveDraft(recipe) {
|
|
533
|
+
try {
|
|
534
|
+
await api.post(`/video/${media.value.uuid}/draft`, { recipe })
|
|
535
|
+
|
|
536
|
+
await presentToast({
|
|
537
|
+
message: 'Draft saved successfully',
|
|
538
|
+
duration: 3000,
|
|
539
|
+
color: 'success'
|
|
540
|
+
})
|
|
541
|
+
} catch (error) {
|
|
542
|
+
console.error('Failed to save draft:', error)
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
</script>
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
## Provider Configuration
|
|
549
|
+
|
|
550
|
+
### Bunny Configuration
|
|
551
|
+
|
|
552
|
+
1. **Create a Bunny Stream Video Library**:
|
|
553
|
+
- Get your `Video Library ID`
|
|
554
|
+
- Generate an API key
|
|
555
|
+
|
|
556
|
+
2. **Configure webhooks**:
|
|
557
|
+
- Endpoint: `https://yourdomain.com/api/v1/video/webhooks/bunny?secret=your-secret`
|
|
558
|
+
|
|
559
|
+
3. **RTMP ingest**:
|
|
560
|
+
- `rtmp://ingest.bunny.live/{stream-key}`
|
|
561
|
+
|
|
562
|
+
4. **Environment variables**:
|
|
563
|
+
```env
|
|
564
|
+
VIDEO_PROVIDER=bunny
|
|
565
|
+
BUNNY_VIDEO_API_KEY=your-api-key
|
|
566
|
+
BUNNY_VIDEO_LIBRARY_ID=your-library-id
|
|
567
|
+
BUNNY_VIDEO_CDN_HOSTNAME=your-cdn.b-cdn.net
|
|
568
|
+
BUNNY_VIDEO_WEBHOOK_SECRET=your-secret
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### CloudFlare Stream Configuration
|
|
572
|
+
|
|
573
|
+
For CloudFlare Stream support, create a CloudFlareVideoProvider:
|
|
574
|
+
|
|
575
|
+
```php
|
|
576
|
+
class CloudFlareVideoProvider implements VideoProviderInterface
|
|
577
|
+
{
|
|
578
|
+
// Implementation for CloudFlare Stream API
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
## Queue Configuration
|
|
583
|
+
|
|
584
|
+
### Configure Queue Workers
|
|
585
|
+
|
|
586
|
+
Update `config/queue.php`:
|
|
587
|
+
|
|
588
|
+
```php
|
|
589
|
+
'connections' => [
|
|
590
|
+
'redis' => [
|
|
591
|
+
'driver' => 'redis',
|
|
592
|
+
'connection' => 'default',
|
|
593
|
+
'queue' => env('REDIS_QUEUE', 'default'),
|
|
594
|
+
'retry_after' => 90,
|
|
595
|
+
'block_for' => null,
|
|
596
|
+
'after_commit' => false,
|
|
597
|
+
],
|
|
598
|
+
|
|
599
|
+
'video-processing' => [
|
|
600
|
+
'driver' => 'redis',
|
|
601
|
+
'connection' => 'default',
|
|
602
|
+
'queue' => 'video-processing',
|
|
603
|
+
'retry_after' => 7200, // 2 hours for long videos
|
|
604
|
+
'block_for' => null,
|
|
605
|
+
'after_commit' => false,
|
|
606
|
+
],
|
|
607
|
+
],
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### Create Queue Jobs
|
|
611
|
+
|
|
612
|
+
Create `app/Jobs/ProcessVideoExport.php`:
|
|
613
|
+
|
|
614
|
+
```php
|
|
615
|
+
<?php
|
|
616
|
+
|
|
617
|
+
namespace App\Jobs;
|
|
618
|
+
|
|
619
|
+
use Illuminate\Bus\Queueable;
|
|
620
|
+
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
621
|
+
use Illuminate\Foundation\Bus\Dispatchable;
|
|
622
|
+
use Illuminate\Queue\InteractsWithQueue;
|
|
623
|
+
use Illuminate\Queue\SerializesModels;
|
|
624
|
+
use Mediables\Models\Media;
|
|
625
|
+
use FFMpeg\FFMpeg;
|
|
626
|
+
use FFMpeg\Format\Video\X264;
|
|
627
|
+
|
|
628
|
+
class ProcessVideoExport implements ShouldQueue
|
|
629
|
+
{
|
|
630
|
+
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
631
|
+
|
|
632
|
+
public $timeout = 7200; // 2 hours
|
|
633
|
+
public $tries = 3;
|
|
634
|
+
|
|
635
|
+
public function __construct(
|
|
636
|
+
public Media $media,
|
|
637
|
+
public array $recipe,
|
|
638
|
+
public string $jobId
|
|
639
|
+
) {}
|
|
640
|
+
|
|
641
|
+
public function handle(): void
|
|
642
|
+
{
|
|
643
|
+
$ffmpeg = FFMpeg::create([
|
|
644
|
+
'ffmpeg.binaries' => config('mediables.ffmpeg_path', '/usr/bin/ffmpeg'),
|
|
645
|
+
'ffprobe.binaries' => config('mediables.ffprobe_path', '/usr/bin/ffprobe'),
|
|
646
|
+
'timeout' => 3600,
|
|
647
|
+
'ffmpeg.threads' => 12,
|
|
648
|
+
]);
|
|
649
|
+
|
|
650
|
+
// Download source video
|
|
651
|
+
$sourcePath = $this->downloadSourceVideo();
|
|
652
|
+
|
|
653
|
+
// Open video
|
|
654
|
+
$video = $ffmpeg->open($sourcePath);
|
|
655
|
+
|
|
656
|
+
// Apply timeline edits
|
|
657
|
+
foreach ($this->recipe['timeline'] as $clip) {
|
|
658
|
+
$video->clip(
|
|
659
|
+
\FFMpeg\Coordinate\TimeCode::fromSeconds($clip['source_in']),
|
|
660
|
+
\FFMpeg\Coordinate\TimeCode::fromSeconds($clip['source_out'] - $clip['source_in'])
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Apply filters
|
|
665
|
+
foreach ($this->recipe['filters'] as $filter) {
|
|
666
|
+
$video->addFilter(new \FFMpeg\Filters\Video\CustomFilter($filter['ffmpeg']));
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Configure output format
|
|
670
|
+
$format = new X264();
|
|
671
|
+
$format->setKiloBitrate(4000)
|
|
672
|
+
->setAudioCodec('aac')
|
|
673
|
+
->setAudioKiloBitrate(128);
|
|
674
|
+
|
|
675
|
+
// Add progress listener
|
|
676
|
+
$format->on('progress', function ($video, $format, $percentage) {
|
|
677
|
+
$this->updateProgress($percentage);
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
// Export
|
|
681
|
+
$outputPath = storage_path("app/exports/{$this->jobId}.mp4");
|
|
682
|
+
$video->save($format, $outputPath);
|
|
683
|
+
|
|
684
|
+
// Upload to provider
|
|
685
|
+
$this->uploadToProvider($outputPath);
|
|
686
|
+
|
|
687
|
+
// Clean up
|
|
688
|
+
@unlink($sourcePath);
|
|
689
|
+
@unlink($outputPath);
|
|
690
|
+
|
|
691
|
+
// Update media status
|
|
692
|
+
$this->media->update([
|
|
693
|
+
'export_status' => 'completed',
|
|
694
|
+
'export_completed_at' => now()
|
|
695
|
+
]);
|
|
696
|
+
|
|
697
|
+
// Fire event
|
|
698
|
+
event(new VideoExportCompleted($this->media, $this->jobId));
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
private function updateProgress(int $percentage): void
|
|
702
|
+
{
|
|
703
|
+
cache()->put("export:{$this->jobId}:progress", $percentage, 3600);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### Run Queue Workers
|
|
709
|
+
|
|
710
|
+
```bash
|
|
711
|
+
# Development
|
|
712
|
+
php artisan queue:work --queue=video-processing
|
|
713
|
+
|
|
714
|
+
# Production with Supervisor
|
|
715
|
+
[program:video-worker]
|
|
716
|
+
process_name=%(program_name)s_%(process_num)02d
|
|
717
|
+
command=php /path/to/artisan queue:work redis --queue=video-processing --sleep=3 --tries=3 --max-time=3600
|
|
718
|
+
autostart=true
|
|
719
|
+
autorestart=true
|
|
720
|
+
stopasgroup=true
|
|
721
|
+
killasgroup=true
|
|
722
|
+
user=www-data
|
|
723
|
+
numprocs=4
|
|
724
|
+
redirect_stderr=true
|
|
725
|
+
stdout_logfile=/path/to/logs/video-worker.log
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
## Testing
|
|
729
|
+
|
|
730
|
+
### Backend Testing
|
|
731
|
+
|
|
732
|
+
Create `tests/Feature/VideoUploadTest.php`:
|
|
733
|
+
|
|
734
|
+
```php
|
|
735
|
+
<?php
|
|
736
|
+
|
|
737
|
+
namespace Tests\Feature;
|
|
738
|
+
|
|
739
|
+
use Tests\TestCase;
|
|
740
|
+
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
741
|
+
use App\Models\User;
|
|
742
|
+
use Mediables\Models\Media;
|
|
743
|
+
|
|
744
|
+
class VideoUploadTest extends TestCase
|
|
745
|
+
{
|
|
746
|
+
use RefreshDatabase;
|
|
747
|
+
|
|
748
|
+
public function test_can_request_upload_url(): void
|
|
749
|
+
{
|
|
750
|
+
$user = User::factory()->create();
|
|
751
|
+
|
|
752
|
+
$response = $this->actingAs($user)
|
|
753
|
+
->postJson('/api/v1/video/upload', [
|
|
754
|
+
'filename' => 'test-video.mp4',
|
|
755
|
+
'size' => 1048576,
|
|
756
|
+
'mime_type' => 'video/mp4',
|
|
757
|
+
'collection' => 'videos'
|
|
758
|
+
]);
|
|
759
|
+
|
|
760
|
+
$response->assertStatus(201)
|
|
761
|
+
->assertJsonStructure([
|
|
762
|
+
'upload_url',
|
|
763
|
+
'upload_id',
|
|
764
|
+
'media_uuid',
|
|
765
|
+
'expires_at'
|
|
766
|
+
]);
|
|
767
|
+
|
|
768
|
+
$this->assertDatabaseHas('media', [
|
|
769
|
+
'uuid' => $response->json('media_uuid'),
|
|
770
|
+
'status' => 'pending',
|
|
771
|
+
'collection' => 'videos'
|
|
772
|
+
]);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
public function test_webhook_updates_media_status(): void
|
|
776
|
+
{
|
|
777
|
+
config()->set('mediables.video.bunny.webhook_secret', 'test-secret');
|
|
778
|
+
|
|
779
|
+
$media = Media::factory()->create([
|
|
780
|
+
'provider' => 'bunny',
|
|
781
|
+
'provider_asset_id' => 'video-guid-123',
|
|
782
|
+
'status' => 'processing',
|
|
783
|
+
]);
|
|
784
|
+
|
|
785
|
+
$payload = [
|
|
786
|
+
'VideoLibraryId' => 12345,
|
|
787
|
+
'VideoGuid' => 'video-guid-123',
|
|
788
|
+
'Status' => 3,
|
|
789
|
+
];
|
|
790
|
+
|
|
791
|
+
$response = $this->postJson('/api/v1/video/webhooks/bunny?secret=test-secret', $payload);
|
|
792
|
+
|
|
793
|
+
$response->assertOk();
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
### Frontend Testing
|
|
799
|
+
|
|
800
|
+
Create `tests/components/VideoUploader.spec.ts`:
|
|
801
|
+
|
|
802
|
+
```typescript
|
|
803
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
804
|
+
import { mount } from '@vue/test-utils'
|
|
805
|
+
import { VideoUploader } from '@mediables/vue'
|
|
806
|
+
import axios from 'axios'
|
|
807
|
+
|
|
808
|
+
vi.mock('axios')
|
|
809
|
+
|
|
810
|
+
describe('VideoUploader', () => {
|
|
811
|
+
it('requests upload URL on file selection', async () => {
|
|
812
|
+
const mockUploadResponse = {
|
|
813
|
+
data: {
|
|
814
|
+
upload_url: 'https://video.bunnycdn.com/library/test-library-id/videos/video-guid-123',
|
|
815
|
+
upload_id: 'upload-123',
|
|
816
|
+
media_uuid: 'media-456',
|
|
817
|
+
expires_at: new Date().toISOString()
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
axios.post.mockResolvedValueOnce(mockUploadResponse)
|
|
822
|
+
|
|
823
|
+
const wrapper = mount(VideoUploader, {
|
|
824
|
+
props: {
|
|
825
|
+
collection: 'videos'
|
|
826
|
+
}
|
|
827
|
+
})
|
|
828
|
+
|
|
829
|
+
// Simulate file selection
|
|
830
|
+
const file = new File(['video content'], 'test.mp4', { type: 'video/mp4' })
|
|
831
|
+
const input = wrapper.find('input[type="file"]')
|
|
832
|
+
|
|
833
|
+
Object.defineProperty(input.element, 'files', {
|
|
834
|
+
value: [file],
|
|
835
|
+
writable: false
|
|
836
|
+
})
|
|
837
|
+
|
|
838
|
+
await input.trigger('change')
|
|
839
|
+
|
|
840
|
+
expect(axios.post).toHaveBeenCalledWith('/api/v1/video/upload', {
|
|
841
|
+
filename: 'test.mp4',
|
|
842
|
+
size: file.size,
|
|
843
|
+
mime_type: 'video/mp4',
|
|
844
|
+
collection: 'videos'
|
|
845
|
+
})
|
|
846
|
+
})
|
|
847
|
+
|
|
848
|
+
it('shows upload progress', async () => {
|
|
849
|
+
const wrapper = mount(VideoUploader)
|
|
850
|
+
|
|
851
|
+
// Start upload
|
|
852
|
+
wrapper.vm.startUpload({
|
|
853
|
+
file: new File(['content'], 'test.mp4'),
|
|
854
|
+
uploadUrl: 'https://video.bunnycdn.com/library/test-library-id/videos/video-guid-123'
|
|
855
|
+
})
|
|
856
|
+
|
|
857
|
+
// Simulate progress
|
|
858
|
+
wrapper.vm.updateProgress(50)
|
|
859
|
+
|
|
860
|
+
await wrapper.vm.$nextTick()
|
|
861
|
+
|
|
862
|
+
expect(wrapper.find('.upload-progress').exists()).toBe(true)
|
|
863
|
+
expect(wrapper.text()).toContain('50%')
|
|
864
|
+
})
|
|
865
|
+
})
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
## Production Deployment
|
|
869
|
+
|
|
870
|
+
### Server Requirements
|
|
871
|
+
|
|
872
|
+
- **CPU**: 4+ cores for video processing
|
|
873
|
+
- **RAM**: 8GB minimum, 16GB recommended
|
|
874
|
+
- **Storage**: SSD with sufficient space for temporary files
|
|
875
|
+
- **Network**: 100Mbps+ for video uploads/downloads
|
|
876
|
+
|
|
877
|
+
### Deployment Checklist
|
|
878
|
+
|
|
879
|
+
1. **Environment Configuration**:
|
|
880
|
+
```bash
|
|
881
|
+
# Set production environment
|
|
882
|
+
APP_ENV=production
|
|
883
|
+
APP_DEBUG=false
|
|
884
|
+
|
|
885
|
+
# Configure queues
|
|
886
|
+
QUEUE_CONNECTION=redis
|
|
887
|
+
REDIS_HOST=your-redis-host
|
|
888
|
+
|
|
889
|
+
# Video provider settings
|
|
890
|
+
VIDEO_PROVIDER=bunny
|
|
891
|
+
BUNNY_VIDEO_API_KEY=production-api-key
|
|
892
|
+
BUNNY_VIDEO_LIBRARY_ID=production-library-id
|
|
893
|
+
BUNNY_VIDEO_CDN_HOSTNAME=production-cdn.b-cdn.net
|
|
894
|
+
BUNNY_VIDEO_WEBHOOK_SECRET=production-secret
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
2. **Optimize Application**:
|
|
898
|
+
```bash
|
|
899
|
+
php artisan config:cache
|
|
900
|
+
php artisan route:cache
|
|
901
|
+
php artisan view:cache
|
|
902
|
+
php artisan event:cache
|
|
903
|
+
composer install --no-dev --optimize-autoloader
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
3. **Frontend Build**:
|
|
907
|
+
```bash
|
|
908
|
+
npm run build
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
4. **Database Optimization**:
|
|
912
|
+
```sql
|
|
913
|
+
-- Add indexes for video queries
|
|
914
|
+
CREATE INDEX idx_media_video_queries ON media(provider, processing_status, created_at);
|
|
915
|
+
CREATE INDEX idx_live_streams_active ON live_streams(status) WHERE status IN ('idle', 'live');
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
5. **Queue Worker Configuration**:
|
|
919
|
+
- Use Supervisor or systemd for process management
|
|
920
|
+
- Configure multiple workers for video processing
|
|
921
|
+
- Set appropriate memory limits
|
|
922
|
+
|
|
923
|
+
6. **Monitoring**:
|
|
924
|
+
- Set up error tracking (Sentry, Bugsnag)
|
|
925
|
+
- Monitor queue lengths
|
|
926
|
+
- Track video processing times
|
|
927
|
+
- Alert on webhook failures
|
|
928
|
+
|
|
929
|
+
7. **CDN Configuration**:
|
|
930
|
+
- Configure CDN for video thumbnail delivery
|
|
931
|
+
- Set appropriate cache headers
|
|
932
|
+
- Enable CORS for video playback
|
|
933
|
+
|
|
934
|
+
8. **Security**:
|
|
935
|
+
- Enable rate limiting on upload endpoints
|
|
936
|
+
- Validate webhook signatures
|
|
937
|
+
- Use HTTPS everywhere
|
|
938
|
+
- Implement CSP headers
|
|
939
|
+
|
|
940
|
+
### Performance Tuning
|
|
941
|
+
|
|
942
|
+
1. **PHP Configuration**:
|
|
943
|
+
```ini
|
|
944
|
+
; php.ini
|
|
945
|
+
upload_max_filesize = 5G
|
|
946
|
+
post_max_size = 5G
|
|
947
|
+
max_execution_time = 0
|
|
948
|
+
memory_limit = 2G
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
2. **Nginx Configuration**:
|
|
952
|
+
```nginx
|
|
953
|
+
# nginx.conf
|
|
954
|
+
client_max_body_size 5G;
|
|
955
|
+
client_body_timeout 3600;
|
|
956
|
+
proxy_read_timeout 3600;
|
|
957
|
+
proxy_connect_timeout 3600;
|
|
958
|
+
proxy_send_timeout 3600;
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
3. **Redis Configuration**:
|
|
962
|
+
```conf
|
|
963
|
+
# redis.conf
|
|
964
|
+
maxmemory 2gb
|
|
965
|
+
maxmemory-policy allkeys-lru
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
## Troubleshooting Guide
|
|
969
|
+
|
|
970
|
+
### Common Issues and Solutions
|
|
971
|
+
|
|
972
|
+
**Issue**: Upload fails with CORS error
|
|
973
|
+
```javascript
|
|
974
|
+
// Solution: Configure CORS in Laravel
|
|
975
|
+
// config/cors.php
|
|
976
|
+
'paths' => ['api/*'],
|
|
977
|
+
'allowed_origins' => ['https://yourdomain.com'],
|
|
978
|
+
'allowed_methods' => ['*'],
|
|
979
|
+
'allowed_headers' => ['*'],
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
**Issue**: Video processing never completes
|
|
983
|
+
```bash
|
|
984
|
+
# Check queue workers
|
|
985
|
+
php artisan queue:failed
|
|
986
|
+
|
|
987
|
+
# Retry failed jobs
|
|
988
|
+
php artisan queue:retry all
|
|
989
|
+
|
|
990
|
+
# Check FFmpeg installation
|
|
991
|
+
ffmpeg -version
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
**Issue**: Playback token expires during viewing
|
|
995
|
+
```javascript
|
|
996
|
+
// Solution: Implement token refresh
|
|
997
|
+
const refreshInterval = setInterval(() => {
|
|
998
|
+
if (shouldRefreshToken()) {
|
|
999
|
+
refreshPlaybackToken()
|
|
1000
|
+
}
|
|
1001
|
+
}, 30000) // Check every 30 seconds
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
**Issue**: Memory exhausted during export
|
|
1005
|
+
```php
|
|
1006
|
+
// Solution: Increase memory limit for export jobs
|
|
1007
|
+
public $timeout = 7200;
|
|
1008
|
+
public $memory = 2048; // MB
|
|
1009
|
+
|
|
1010
|
+
// Or in job handle method
|
|
1011
|
+
ini_set('memory_limit', '2G');
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
## Support
|
|
1015
|
+
|
|
1016
|
+
For additional help:
|
|
1017
|
+
|
|
1018
|
+
- **Documentation**: https://mediables.dev/docs/video
|
|
1019
|
+
- **GitHub Issues**: https://github.com/mediables/mediables/issues
|
|
1020
|
+
- **Discord Community**: https://discord.gg/mediables
|
|
1021
|
+
- **Email Support**: video-support@mediables.dev
|