diff --git a/.gitattributes b/.gitattributes index a602ff7..5fcda59 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,20 @@ -build/ export-ignore +# Export ignore for release ZIP +# Development and build files +/build export-ignore +/tests export-ignore +/.github export-ignore +/.tools export-ignore + +# Configuration files +/.gitattributes export-ignore +/.gitignore export-ignore +/.php-cs-fixer.dist.php export-ignore +/.php-cs-fixer.cache export-ignore +/composer.json export-ignore +/composer.lock export-ignore +/phpstan.neon export-ignore +/psalm.xml export-ignore + +# Documentation (optional - entfernen falls README im Release sein soll) +# /README.md export-ignore +# /CHANGELOG.md export-ignore diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml new file mode 100644 index 0000000..4a07a4d --- /dev/null +++ b/.github/workflows/code-style.yml @@ -0,0 +1,53 @@ +name: PHP-CS-Fixer + +# Erstelle eine `composer.json` im Repo mit folgendem Inhalt +# { +# "require-dev": { +# "friendsofphp/php-cs-fixer": "^3.0" +# }, +# "scripts": { +# "cs-fix": "php-cs-fixer fix" +# } +# } + +on: + push: + branches: [ master, main ] + pull_request: + branches: [ master, main ] + +permissions: + contents: read + +jobs: + code-style: + if: github.event.pull_request.draft == false + + runs-on: ubuntu-latest + permissions: + contents: write # for Git to git apply + + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + extensions: gd, intl, pdo_mysql + coverage: none # disable xdebug, pcov + + # install dependencies from composer.json + - name: Install test dependencies + env: + COMPOSER: composer.json + run: composer install --prefer-dist --no-progress + + # run php-cs-fixer + - name: Run PHP CS Fixer + run: composer cs-fix + + # commit and push fixed files + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Apply php-cs-fixer changes diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache new file mode 100644 index 0000000..713ad00 --- /dev/null +++ b/.php-cs-fixer.cache @@ -0,0 +1 @@ +{"php":"8.4.11","version":"3.87.1:v3.87.1#2f5170365e2a422d0c5421f9c8818b2c078105f6","indent":" ","lineEnding":"\n","rules":{"array_indentation":true,"array_syntax":true,"cast_spaces":true,"concat_space":{"spacing":"one"},"function_declaration":true,"method_argument_space":{"on_multiline":"ignore"},"new_with_parentheses":{"anonymous_class":false},"single_line_empty_body":true,"single_space_around_construct":true,"trailing_comma_in_multiline":{"after_heredoc":true,"elements":["arguments","arrays","match","parameters"]},"binary_operator_spaces":true,"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_anonymous_functions":true,"allow_single_line_empty_anonymous_classes":true},"class_definition":{"single_line":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":{"tokens":["attribute","case","continue","curly_brace_block","default","extra","parenthesis_brace_block","square_brace_block","switch","throw","use"]},"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait","case","constant_public","constant_protected","constant_private","property","construct","phpunit","method"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"alpha"},"return_type_declaration":true,"short_scalar_cast":true,"single_import_per_statement":true,"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":true,"visibility_required":true,"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":true,"single_line_after_imports":true,"spaces_inside_parentheses":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true,"no_unreachable_default_argument_value":true,"align_multiline_comment":true,"backtick_to_shell_exec":true,"class_attributes_separation":{"elements":{"method":"one"}},"class_reference_name_casing":true,"clean_namespace":true,"declare_parentheses":true,"echo_tag_syntax":{"format":"short"},"empty_loop_body":{"style":"braces"},"empty_loop_condition":true,"fully_qualified_strict_types":{"import_symbols":true},"general_phpdoc_tag_rename":{"replacements":{"inheritDocs":"inheritDoc"}},"global_namespace_import":{"import_constants":true,"import_functions":true,"import_classes":true},"include":true,"increment_style":true,"integer_literal_case":true,"lambda_not_used_import":true,"linebreak_after_opening_tag":true,"magic_constant_casing":true,"magic_method_casing":true,"native_function_casing":true,"native_type_declaration_casing":true,"no_alias_language_construct_call":true,"no_binary_string":true,"no_empty_comment":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_leading_namespace_whitespace":true,"no_mixed_echo_print":true,"no_multiline_whitespace_around_double_arrow":true,"no_null_property_initialization":true,"no_short_bool_cast":true,"no_singleline_whitespace_before_semicolons":true,"no_spaces_around_offset":true,"no_superfluous_phpdoc_tags":{"allow_mixed":true,"remove_inheritdoc":true},"no_trailing_comma_in_singleline":true,"no_unneeded_braces":{"namespaces":true},"no_unneeded_control_parentheses":{"statements":["break","clone","continue","echo_print","others","return","switch_case","yield","yield_from"]},"no_unneeded_import_alias":true,"no_unset_cast":true,"no_unused_imports":true,"no_useless_concat_operator":true,"no_useless_nullsafe_operator":true,"no_whitespace_before_comma_in_array":{"after_heredoc":true},"normalize_index_brace":true,"nullable_type_declaration_for_default_null_value":true,"object_operator_without_whitespace":true,"operator_linebreak":{"only_booleans":true},"php_unit_fqcn_annotation":true,"php_unit_method_casing":true,"phpdoc_annotation_without_dot":true,"phpdoc_indent":true,"phpdoc_inline_tag_normalizer":true,"phpdoc_no_access":true,"phpdoc_no_alias_tag":true,"phpdoc_no_useless_inheritdoc":true,"phpdoc_order":true,"phpdoc_return_self_reference":true,"phpdoc_scalar":true,"phpdoc_single_line_var_spacing":true,"phpdoc_summary":true,"phpdoc_tag_type":{"tags":{"inheritDoc":"inline"}},"phpdoc_trim":true,"phpdoc_trim_consecutive_blank_line_separation":true,"phpdoc_types":true,"phpdoc_types_order":{"null_adjustment":"always_last","sort_algorithm":"none"},"phpdoc_var_annotation_correct_order":true,"phpdoc_var_without_name":true,"simple_to_complex_string_variable":true,"single_line_comment_spacing":true,"single_line_comment_style":{"comment_types":["hash"]},"single_quote":true,"space_after_semicolon":{"remove_in_empty_for_expressions":true},"standardize_increment":true,"standardize_not_equals":true,"switch_continue_to_break":true,"trim_array_spaces":true,"type_declaration_spaces":true,"whitespace_after_comma_in_array":true,"yoda_style":true,"nullable_type_declaration":true,"ordered_types":{"null_adjustment":"always_last","sort_algorithm":"none"},"types_spaces":true,"array_push":true,"combine_nested_dirname":true,"dir_constant":true,"ereg_to_preg":true,"error_suppression":true,"fopen_flag_order":true,"fopen_flags":{"b_mode":false},"function_to_constant":true,"get_class_to_class_keyword":true,"implode_call":true,"is_null":true,"logical_operators":true,"long_to_shorthand_operator":true,"modernize_strpos":true,"modernize_types_casting":true,"native_constant_invocation":{"scope":"namespaced","strict":false},"native_function_invocation":{"include":["@compiler_optimized"],"scope":"namespaced","strict":true},"no_alias_functions":{"sets":["@all"]},"no_homoglyph_names":true,"no_php4_constructor":true,"no_unneeded_final_method":true,"no_useless_sprintf":true,"non_printable_character":true,"ordered_traits":true,"php_unit_construct":true,"php_unit_mock_short_will_return":true,"php_unit_set_up_tear_down_visibility":true,"php_unit_test_annotation":true,"self_accessor":true,"set_type_to_cast":true,"string_length_to_empty":true,"string_line_ending":true,"ternary_to_elvis_operator":true,"pow_to_exponentiation":true,"octal_notation":true,"assign_null_coalescing_to_coalesce_equal":true,"heredoc_indentation":true,"list_syntax":true,"ternary_to_null_coalescing":true,"random_api_migration":{"replacements":{"mt_rand":"random_int","rand":"random_int"}},"php_unit_data_provider_static":{"force":true},"php_unit_assert_new_names":true,"php_unit_expectation":{"target":"8.4"},"php_unit_namespaced":{"target":"6.0"},"php_unit_dedicate_assert":{"target":"5.6"},"php_unit_mock":{"target":"5.5"},"php_unit_no_expectation_annotation":{"target":"4.3"},"php_unit_dedicate_assert_internal_type":{"target":"7.5"},"comment_to_phpdoc":true,"heredoc_to_nowdoc":true,"multiline_comment_opening_closing":true,"multiline_promoted_properties":{"keep_blank_lines":true},"no_superfluous_elseif":true,"no_useless_else":true,"no_useless_return":true,"php_unit_internal_class":true,"php_unit_test_case_static_method_calls":{"call_type":"self"},"phpdoc_array_type":true,"static_lambda":true,"string_implicit_backslashes":{"single_quoted":"ignore"},"PhpCsFixerCustomFixers\/phpdoc_single_line_var":true,"Redaxo\/no_semicolon_before_closing_tag":true,"Redaxo\/statement_indentation":true},"hashes":{"lang\/translations.php":"f629043bfd30ac33b48651256579fcf7","lib\/Translator.php":"d7f446ca0c8dfdfaaf6c3ed908b8a41e","lib\/OembedParser.php":"1bb7945b9f2dee0d3f9b3faab5582b68","lib\/AssetHelper.php":"454be1ce6a10da74ec04e31ab7ac7635","lib\/VidstackPlayer.php":"a1198133d9bb64d873f2e9b5434536da","lib\/Utilities.php":"d91ddf047e85ba2b5aa230ef1ae45201","lib\/PlatformDetector.php":"01e93a5eb448abf67cf725441e366fb4","lib\/BackendIntegration.php":"7522a270b2fa92c0cbd104e02a2c92cb","boot.php":"e8090f9b7dc05adf2fc3eddfcdf504ae"}} \ No newline at end of file diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..034718e --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,12 @@ +in(__DIR__) + ->exclude('vendor') +; + +return (new Redaxo\PhpCsFixerConfig\Config()) + ->setFinder($finder) + ; diff --git a/.tools/bootstrap.php b/.tools/bootstrap.php new file mode 100644 index 0000000..56c33b7 --- /dev/null +++ b/.tools/bootstrap.php @@ -0,0 +1,13 @@ +'; -echo ''; +```bash +# Via Composer (recommended) +composer require friendsofredaxo/vidstack -// JavaScript einbinden -echo ''; -echo ''; +# Or via REDAXO Installer +# Search for "Vidstack Player" in the addon installer ``` -Was passiert hier? Wir benutzen `rex_url::addonAssets()`, um die richtigen URLs für unsere Assets zu generieren. Das ist wie ein Zauberstab, der immer auf die korrekten Dateien in deinem REDAXO-Setup zeigt, egal wo sie sich versteckt haben. - -Die `vidstack.css` und `vidstack.js` sind die Hauptdarsteller - sie bringen den Video-Player zum Laufen. Die `*_helper`-Dateien sind wie die fleißigen Backstage-Helfer. Sie kümmern sich um Extras wie die DSGVO-Abfrage und andere nützliche Funktionen. - -Übrigens: Wenn du nur die `generate()`-Methode verwendest und auf den ganzen Schnickschnack wie Consent-Abfragen verzichten möchtest, kannst du die Helper-Dateien weglassen. Aber für das volle Programm mit `generateFull()` braucht man alle vier Dateien. +## Quick Start -So, jetzt aber! Dein REDAXO ist jetzt bereit, Videos mit Style zu servieren. 🎬🍿 - -### Source Sizes für Desktop/Mobile Videos - -Mit dem Vidstack-Addon können Sie verschiedene Video-Auflösungen für Desktop und Mobile bereitstellen: +### Basic Video ```php setResponsiveSources('video-1080p.mp4', 'video-480p.mp4'); -echo $video->generateFull(); - -// Mit benutzerdefinierten Auflösungen -$video = new Video('video-desktop.mp4', 'Custom Responsive Video'); -$video->setResponsiveSources( - 'video-high.mp4', - 'video-low.mp4', - [2560, 1440], // Desktop: 2K - [960, 540] // Mobile: Mobile HD -); -echo $video->generateFull(); - -// Mit Auflösungspresets -$video = new Video('video.mp4', 'Preset Video'); -$video->setResponsiveSourcesWithPresets('video-2k.mp4', 'video-mobile.mp4', '2k', 'mobile_hd'); -echo $video->generateFull(); - -// Automatische Erstellung aus Dateinamen-Pattern -$video = new Video('produktvideo.mp4', 'Produktvideo'); -if ($video->createAutoSources('produktvideo')) { - // Sucht automatisch nach: produktvideo-1080p.mp4, produktvideo-720p.mp4, produktvideo-480p.mp4 - echo $video->generateFull(); -} - -// Mehrere Qualitätsstufen mit manueller Kontrolle -$video = new Video('video.mp4', 'Multi-Quality Video'); -$video->setSources([ - ['src' => 'video-4k.mp4', 'width' => 3840, 'height' => 2160, 'type' => 'video/mp4'], - ['src' => 'video-1080p.mp4', 'width' => 1920, 'height' => 1080, 'type' => 'video/mp4'], - ['src' => 'video-720p.mp4', 'width' => 1280, 'height' => 720, 'type' => 'video/mp4'], - ['src' => 'video-480p.mp4', 'width' => 854, 'height' => 480, 'type' => 'video/mp4'] -]); -echo $video->generateFull(); -``` +use FriendsOfRedaxo\VidstackPlayer\VidstackPlayer; -**Verfügbare Auflösungspresets:** -- `4k` (3840×2160), `2k` (2560×1440), `1080p` (1920×1080) -- `720p` (1280×720), `480p` (854×480), `360p` (640×360) -- `mobile_hd` (960×540), `mobile_sd` (640×360), `tablet` (1024×576) +$player = (new VidstackPlayer('video.mp4')) + ->title('My Video') + ->poster('poster.jpg') + ->attributes(['controls' => true]); -**Wie es funktioniert:** Der Browser wählt automatisch die beste verfügbare Quelle basierend auf Gerätegröße und Netzwerkbedingungen. Die Quellen werden nach Qualität sortiert ausgegeben (höchste zuerst). Das Sorting wird gecacht für bessere Performance. +echo $player->render(); +``` -### Grundlegende Verwendung +### Basic Audio ```php -generateFull(); - -// Vimeo-Video -$vimeoVideo = new Video('https://vimeo.com/148751763', 'Vimeo-Beispiel'); -echo $vimeoVideo->generateFull(); +$player = (new VidstackPlayer('audio.mp3')) + ->title('Podcast Episode 1') + ->attributes(['controls' => true]); -// Lokales Video -$localVideo = new Video('video.mp4', 'Eigenes Video'); -echo $localVideo->generate(); - -// Externes Video -$externalVideo = new Video('https://somedomain.tld/video.mp4', 'Eigenes Video'); -echo $externalVideo->generate(); +echo $player->render(); ``` -### Grundlegende Beispiele für den Alltag - -#### Video mit Poster-Bild und Titel +### YouTube Video ```php -title('Never Gonna Give You Up'); -// Video aus dem Medienpool mit Poster-Bild -$video = new Video('mein_video.mp4', 'Mein tolles Video mit Vorschaubild'); -$video->setPoster('vorschaubild.jpg', 'Beschreibung des Vorschaubilds'); -echo $video->generate(); +echo $player->render(); ``` -#### Video mit Untertiteln (VTT-Format) +## Fluent API Reference -```php -addSubtitle('untertitel_de.vtt', 'captions', 'Deutsch', 'de', true); // Standard-Untertitel -$video->addSubtitle('untertitel_en.vtt', 'captions', 'Englisch', 'en'); -echo $video->generate(); +```php +$player = (new VidstackPlayer('source.mp4')) + ->title('Title') // Set player title + ->lang('de') // Set language (default: 'de') + ->poster('poster.jpg', 'Alt text') // Set poster image + ->aspectRatio('16/9') // Set aspect ratio + ->thumbnails('thumbs.vtt') // Set thumbnail track + ->attributes(['controls' => true]) // Set multiple attributes + ->attr('muted', true) // Set single attribute + ->render(); // Generate HTML ``` -#### Barrierefreies Video mit Beschreibungen +### Subtitles & Captions ```php -setA11yContent( - 'Das Video zeigt Schritt für Schritt, wie REDAXO installiert wird. Beginnend mit dem Download bis zur ersten Anmeldung im Backend.', - 'https://beispiel.de/redaxo-installation-text.html' // Alternative Text-Version +$player->track( + src: 'subtitles.vtt', + label: 'Deutsch', + srclang: 'de', + kind: 'subtitles', // subtitles|captions|descriptions|chapters|metadata + default: true ); - -// Kapitelmarken hinzufügen -$video->addSubtitle('chapters.vtt', 'chapters', 'Kapitel', 'de'); - -echo $video->generateFull(); ``` -#### YouTube mit DSGVO-konformer Zwei-Klick-Lösung +### Multiple Sources (Adaptive Quality) ```php - Für das Frontend - -// generateFull() erzeugt automatisch den DSGVO-konformen Platzhalter für YouTube und Vimeo -echo $video->generateFull(); +$player->multipleSources([ + ['src' => 'video-1080p.mp4', 'width' => 1920, 'height' => 1080, 'type' => 'video/mp4'], + ['src' => 'video-720p.mp4', 'width' => 1280, 'height' => 720, 'type' => 'video/mp4'], + ['src' => 'video-480p.mp4', 'width' => 854, 'height' => 480, 'type' => 'video/mp4'] +]); ``` -#### Video mit Vorschaubildern für die Zeitleiste (VTT-Format) - -```php -setThumbnails('thumbnails.vtt'); +## Frontend Integration -// Beispiel für eine thumbnails.vtt Datei: -// WEBVTT -// -// 00:00:00.000 --> 00:00:05.000 -// thumbnails/img1.jpg -// -// 00:00:05.000 --> 00:00:10.000 -// thumbnails/img2.jpg - -echo $video->generate(); -``` - -#### Audio-Player +### Include Assets ```php generate(); +use FriendsOfRedaxo\VidstackPlayer\AssetHelper; +?> + + + + + + + + + + + ``` -## � FFmpeg-Integration (Backend-Funktionalität) - -Wenn das [FFmpeg-AddOn](https://github.com/FriendsOfREDAXO/ffmpeg) installiert und aktiv ist, zeigt Vidstack automatisch detaillierte Video-Informationen im Medienpool an. +### Asset Helper Options -### Was wird angezeigt? - -Im Medienpool wird unter jedem Video automatisch eine kompakte Informationsbox eingeblendet mit: +```php +// CSS with custom attributes +echo AssetHelper::getCss(['media' => 'screen'], cachebuster: true); -- **Auflösung**: Breite × Höhe in Pixeln (z.B. 1920 × 1080 px) und Seitenverhältnis (z.B. 16:9) -- **Video-Codec**: Komprimierungsformat (z.B. H264, VP9, AV1) -- **Dauer**: Formatierte Videolänge (z.B. 05:42 oder 01:23:45) -- **Dateigröße**: Größe der Videodatei (z.B. 45.2 MB) -- **Bitrate**: Datenrate des Videos (z.B. 2.4 Mbps) - nur bei aussagekräftigen Werten +// JS with defer (recommended for performance) +echo AssetHelper::getJs(defer: true); -### Voraussetzungen +// JS with async (use with caution) +echo AssetHelper::getJs(async: true); -```bash -# FFmpeg muss auf dem Server installiert sein -ffmpeg -version +// Custom attributes +echo AssetHelper::getJs( + defer: true, + attributes: ['type' => 'module', 'crossorigin' => 'anonymous'], + cachebuster: true +); -# FFmpeg-AddOn in REDAXO installieren und aktivieren +// Disable cache-busting +echo AssetHelper::getCss(cachebuster: false); ``` -### Funktionsweise - -Die Integration erfolgt vollautomatisch: - -1. **Automatische Erkennung**: Vidstack prüft beim Laden einer Video-Datei im Medienpool, ob das FFmpeg-AddOn verfügbar ist -2. **Video-Analyse**: Falls verfügbar, werden die Video-Metadaten über die FFmpeg VideoInfo-Klasse ausgelesen -3. **Anzeige**: Die Informationen werden kompakt unter dem Video-Player dargestellt -4. **Action-Buttons**: Direkte Verlinkung zu FFmpeg-Tools für weitere Bearbeitung - -### Action-Buttons - -Unter den Video-Informationen werden praktische Buttons angezeigt: - -- **🔧 Trimmen**: Öffnet den FFmpeg-Trimmer zum Schneiden des Videos -- **📦 Optimieren**: Startet die Komprimierung für Web-optimierte Versionen -- **ℹ️ Details**: Zeigt ausführliche technische Video-Informationen - -Die Buttons führen direkt zu den entsprechenden FFmpeg-Tools und übertragen automatisch den Dateinamen. - -### Ohne FFmpeg-AddOn +## Advanced Examples -Ohne das FFmpeg-AddOn funktioniert Vidstack weiterhin normal, zeigt aber keine technischen Video-Informationen an. +### Complete Video Setup -## �🛠 Die Class - -### Konstruktor ```php -__construct($source, $title = '', $lang = 'de'): void -``` -- `$source`: URL oder Pfad zum Video (Pflicht) -- `$title`: Titel des Videos (Optional) -- `$lang`: Sprachcode (Optional, Standard: 'de') - -### Methoden -- `setAttributes(array $attributes): void`: Zusätzliche Player-Attribute -- `setA11yContent($description, $alternativeUrl = ''): void`: Barrierefreiheits-Infos -- `setThumbnails($thumbnailsUrl): void`: Thumbnail-Vorschaubilder (VTT-Format) -- `setPoster($posterSrc, $posterAlt): void`: Poster-Bild für das Video setzen -- `addSubtitle($src, $kind, $label, $lang, $default = false): void`: Untertitel hinzufügen -- `generateFull(): string`: Vollständiger HTML-Code mit allen Schikanen -- `generate(): string`: Einfacher Video-Player ohne Schnickschnack -- `isMedia($url): bool`: Prüft, ob es sich um eine Mediendatei handelt -- `isAudio($url): bool`: Prüft, ob es sich um eine Audiodatei handelt -- `videoOembedHelper(): void`: Registriert einen Output-Filter für oEmbed-Tags -- `parseOembedTags(string $content): string`: Parst oEmbed-Tags im Inhalt -- `show_sidebar(\rex_extension_point $ep): ?string`: Generiert Medienvorschau für die Sidebar im Medienpool -- `getSourceUrl(): string`: Gibt die URL der Videoquelle zurück -- `getAlternativeUrl(): string`: Gibt eine alternative URL für das Video zurück -- `getVideoInfo($source): array`: Gibt Informationen über das Video zurück (Plattform und ID) [Statische Methode] -- `generateAttributesString(): string`: Generiert einen String mit allen gesetzten Attributen -- `generateConsentPlaceholder(string $consentText, string $platform, string $videoId): string`: Generiert einen Platzhalter für die Consent-Abfrage - -## 📋 Optionen und Pflichtangaben - -### Pflichtangaben -- `$source` beim Erstellen des Video-Objekts - -### Optionale Angaben -- `$title` beim Erstellen des Video-Objekts -- `$lang` beim Erstellen des Video-Objekts -- Alle Attribute in `setAttributes()` -- Beschreibung und alternativer URL in `setA11yContent()` -- Thumbnail-URL in `setThumbnails()` -- Poster-Bild in `setPoster()` -- Untertitel-Informationen in `addSubtitle()` - -## 🌍 Sprachenwirrwarr - -Der Video-Player spricht mehr Sprachen als ein UNO-Dolmetscher! Aktuell im Repertoire: -- Deutsch (de) -- Englisch (en) -- Spanisch (es) -- Slowenisch (si) -- Französisch (fr) - -Sprachänderung leicht gemacht: +title('My Movie') + ->lang('de') + ->poster('poster.jpg', 'Movie poster') + ->aspectRatio('16/9') + ->thumbnails('thumbs.vtt') + ->track('subtitles_de.vtt', 'Deutsch', 'de', 'subtitles', true) + ->track('subtitles_en.vtt', 'English', 'en', 'subtitles') + ->multipleSources([ + ['src' => 'movie-1080p.mp4', 'width' => 1920, 'height' => 1080, 'type' => 'video/mp4'], + ['src' => 'movie-720p.mp4', 'width' => 1280, 'height' => 720, 'type' => 'video/mp4'] + ]) + ->attributes([ + 'controls' => true, + 'playsinline' => true, + 'preload' => 'metadata' + ]); -```php -$videoES = new Video('https://www.youtube.com/watch?v=example', 'Mi Video', 'es'); +echo $player->render(); ``` -## 🎭 Beispiele für die Dramaturgen - -### Ein YouTube-Video mit vollem Programm +### Podcast with Chapters ```php -$video = new Video('https://www.youtube.com/watch?v=dQw4w9WgXcQ', 'Never Gonna Give You Up', 'en'); -$video->setAttributes(['autoplay' => true, 'muted' => true]); -$video->setA11yContent('This is a music video by Rick Astley'); -$video->setThumbnails('/pfad/zu/thumbnails.vtt'); -$video->setPoster('/pfad/zu/poster.jpg', 'Rick Astley dancing'); -$video->addSubtitle('/untertitel/deutsch.vtt', 'captions', 'Deutsch', 'de', true); -$video->addSubtitle('/untertitel/english.vtt', 'captions', 'English', 'en'); -echo $video->generateFull(); +$podcast = (new VidstackPlayer('episode-01.mp3')) + ->title('Episode 1: Getting Started') + ->lang('en') + ->poster('cover.jpg', 'Podcast cover') + ->track('chapters.vtt', 'Chapters', 'en', 'chapters', true) + ->attributes(['controls' => true]); + +echo $podcast->render(); ``` -### Ein schlichtes lokales Video +## Utility Classes + +### Platform Detection ```php -$video = new Video('/pfad/zu/katzen_spielen_schach.mp4', 'Schachgenies'); -echo $video->generate(); -``` + 'youtube', 'id' => 'abc'] -```php -$video = new Video('https://vimeo.com/148751763', 'Vimeo-Meisterwerk', 'fr'); -$video->setThumbnails('/vimeo_thumbs.vtt'); -$video->setPoster('/vimeo_poster.jpg', 'Video thumbnail'); -$video->addSubtitle('/sous-titres.vtt', 'captions', 'Français', 'fr', true); -echo $video->generateFull(); -``` +// Check if audio +$isAudio = PlatformDetector::isAudio('podcast.mp3'); // true -### 🌟 Full Featured Beispiel - Ein bisschen Hollywood ⭐️ +// Check if valid media +$isMedia = PlatformDetector::isMedia('video.mp4'); // true +``` -**Aufwendig und zu teuer** -Hier kommt der Königsklasse-Einsatz - alle Funktionen auf einmal: +### Utilities ```php setAttributes([ - 'autoplay' => false, - 'muted' => false, - 'loop' => true, - 'playsinline' => true, - 'crossorigin' => 'anonymous', - 'preload' => 'metadata', - 'controlsList' => 'nodownload', - 'class' => 'my-custom-video-class', - 'data-custom' => 'some-value' +use FriendsOfRedaxo\VidstackPlayer\Utilities; + +// Build HTML attributes +$attrs = Utilities::buildHtmlAttributes([ + 'controls' => true, + 'muted' => true, + 'data-id' => '123' ]); +// Returns: ' controls muted data-id="123"' -// Hinzufügen von ausführlichen Barrierefreiheits-Inhalten -$video->setA11yContent( - 'This legendary music video features Rick Astley performing "Never Gonna Give You Up". The video begins with Rick, dressed in a black leather jacket, dancing in various locations. The catchy synth-pop tune and Rick\'s distinctive baritone voice have made this song an internet phenomenon.', - 'https://example.com/detailed-audio-description' -); +// Detect MIME type +$mime = Utilities::detectMimeType('video.webm'); // 'video/webm' +``` -// Setzen von Thumbnail-Vorschaubildern für den Player-Fortschritt -$video->setThumbnails('/pfad/zu/detailed-thumbnails.vtt'); +## Backend Features -// Setzen des Poster-Bildes -$video->setPoster('/pfad/zu/rickroll_poster.jpg', 'Rick Astley in his iconic pose'); +### Mediapool Integration -// Hinzufügen von Untertiteln in mehreren Sprachen -$video->addSubtitle('/untertitel/english.vtt', 'captions', 'English', 'en', true); -$video->addSubtitle('/untertitel/deutsch.vtt', 'captions', 'Deutsch', 'de'); -$video->addSubtitle('/untertitel/francais.vtt', 'captions', 'Français', 'fr'); -$video->addSubtitle('/untertitel/espanol.vtt', 'captions', 'Español', 'es'); -$video->addSubtitle('/untertitel/slovenscina.vtt', 'captions', 'Slovenščina', 'si'); +The addon automatically integrates into the REDAXO mediapool: -// Hinzufügen von Audiodeskription -$video->addSubtitle('/audio/description.vtt', 'descriptions', 'Audio Description', 'en'); +- **Preview Player** - Video/audio preview in media detail sidebar +- **Video Info** - Resolution, codec, duration, filesize, bitrate (requires FFmpeg addon) +- **Quick Tools** - Trim, optimize, and analyze videos (requires FFmpeg addon) -// Hinzufügen von Kapitelmarkierungen -$video->addSubtitle('/chapters/rickroll.vtt', 'chapters', 'Chapters', 'en'); +### FFmpeg Integration -// Generieren des vollständigen Video-Player-Codes -$fullPlayerCode = $video->generateFull(); +Install the [FFmpeg AddOn](https://github.com/FriendsOfREDAXO/ffmpeg) for enhanced features: -// Ausgabe des generierten Codes -echo $fullPlayerCode; +```bash +composer require friendsofredaxo/ffmpeg ``` -Dieses Beispiel zeigt die Hauptfunktionalität des Players mit allen verfügbaren Optionen. In den meisten Fällen wird das bereits alles sein, was Sie brauchen. +Features with FFmpeg: +- Video information display +- Quick trim tool +- Optimization tool +- Detailed video analysis -## 🛠️ Erweiterte Methoden für spezielle Anwendungsfälle +### CKE5 Integration -Die folgenden erweiterten Methoden sind für spezielle Anwendungsfälle gedacht, wenn Sie mehr Kontrolle über den Player benötigen oder eigene Implementierungen erstellen möchten. +The addon provides an oEmbed parser to convert CKEditor 5 oEmbed tags to Vidstack players. -### Beispiel 1: Eigenen DSGVO-konformen Player mit zwei-Klick-Lösung erstellen +**Manual activation required** (e.g., in your project addon's `boot.php`): ```php getSourceUrl()); +// In your project addon's boot.php +use FriendsOfRedaxo\VidstackPlayer\OembedParser; -// Nur wenn es ein YouTube oder Vimeo Video ist, DSGVO-Abfrage anzeigen -if ($videoInfo['platform'] !== 'default') { - // Angepassten Consent-Text erstellen - $consentText = "Um dieses {$videoInfo['platform']}-Video anzusehen, klicken Sie bitte auf 'Video laden'. " . - "Dadurch werden Daten an {$videoInfo['platform']} übermittelt. " . - "Weitere Informationen finden Sie in unserer Datenschutzerklärung."; - - // Container mit eigener Klasse für Styling erstellen - echo ''; -} else { - // Bei lokalen Videos direkt anzeigen - echo $video->generate(); -} +// Register oEmbed parser for CKE5 +OembedParser::register(); ``` -### Beispiel 2: Erweiterter Player mit Analytics-Integration +**How it works:** -```php -getSourceUrl()); - $platform = $videoInfo['platform']; - $videoId = $videoInfo['id']; - - // Standard HTML für den Player generieren - $playerHtml = $video->generate(); - - // Attribute für das Analytics-Tracking hinzufügen - $trackingAttributes = ' data-tracking="true" data-platform="' . htmlspecialchars($platform) . - '" data-video-id="' . htmlspecialchars($videoId) . '"'; - - // HTML-Code mit Tracking-Attributen ergänzen - $trackedHtml = str_replace(' -document.addEventListener('DOMContentLoaded', function() { - const player = document.querySelector('media-player[data-tracking="true"]'); - if (player) { - player.addEventListener('play', function() { - // Hier Tracking-Code einfügen - console.log('Video gestartet:', player.getAttribute('data-platform'), player.getAttribute('data-video-id')); - }); - - player.addEventListener('ended', function() { - // Video wurde vollständig angesehen - console.log('Video beendet:', player.getAttribute('data-platform'), player.getAttribute('data-video-id')); - }); - } -}); - -EOT; - - return $trackedHtml; -} +```html + + -// Verwendung -echo createTrackedVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ', 'Tracking-Demo'); + +... ``` -### Beispiel 3: Eigenes Player-Layout mit vorgenerierten Elementen erstellen +**Why manual activation?** +- Gives you full control over when and how videos are embedded +- Prevents conflicts with existing oEmbed handlers or custom implementations +- Opt-in approach - activate only if you need it -```php -getVideoInfo(); - $isYouTube = $videoInfo['platform'] === 'youtube'; - - // Custom Container erstellen - $output = '
'; - - // Titel und Info anzeigen, wenn gewünscht - if ($showInfo) { - $output .= '
'; - $output .= '

' . htmlspecialchars($title) . '

'; - - if ($isYouTube) { - $output .= '
Quelle: YouTube
'; - } - - $output .= '
'; - } - - // Player-Container - $output .= '
'; - - // Für YouTube wird der Consent-Platzhalter verwendet - if ($isYouTube) { - $consentText = "YouTube-Videos werden erst nach Zustimmung geladen, um Ihre Privatsphäre zu schützen."; - $output .= $video->generateConsentPlaceholder($consentText, 'youtube', $videoInfo['id']); - } else { - // Für lokale Videos normalen Player anzeigen - $output .= $video->generate(); - } - - $output .= '
'; - - // Custom Controls oder zusätzliche Informationen - if ($showInfo) { - $output .= ''; - } - - $output .= '
'; - - return $output; -} +The addon is structured into focused, single-purpose classes: -// Verwendung -echo createCustomLayoutVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ', 'Custom Layout Demo'); ``` - -### Beispiel 4: Adaptive Einbindung basierend auf Gerätetyp - -```php -setAttributes([ - 'playsinline' => true, - 'preload' => 'none', // Bandbreite sparen - 'controlsList' => 'nodownload', - 'disablePictureInPicture' => true, - 'class' => 'mobile-optimized' - ]); - - // Einfache Version für mobile Geräte - return $video->generate(); - } else { - // Auf Desktop volle Funktionalität - $video->setAttributes([ - 'class' => 'desktop-enhanced', - 'preload' => 'metadata' - ]); - - // Poster und Untertitel für Desktop hinzufügen - $video->setPoster('/pfad/zu/hq-poster.jpg', 'Video-Vorschau'); - $video->addSubtitle('/untertitel/deutsch.vtt', 'captions', 'Deutsch', 'de', true); - - return $video->generateFull(); - } -} - -// Einfache Geräteerkennung (in der Praxis würden Sie hier eine richtige Erkennung verwenden) -$isMobile = strpos($_SERVER['HTTP_USER_AGENT'], 'Mobile') !== false; - -// Verwendung -echo createResponsiveVideo('https://example.com/video.mp4', 'Responsives Video', $isMobile); +lib/ +├── VidstackPlayer.php # Main player class (fluent API) +├── PlatformDetector.php # Platform & media type detection +├── AssetHelper.php # CSS/JS loading with cache-busting +├── Utilities.php # HTML attributes, MIME types +├── Translator.php # i18n translation helper +├── OembedParser.php # CKE5 oEmbed integration +└── BackendIntegration.php # Mediapool sidebar & FFmpeg ``` -### Beispiel 5: Integration mit REX_MEDIA-Variablen - -```php -'; - echo '

Audio-Player

'; - echo $video->generate(); - echo ''; - } else { - // Video mit Standardeinstellungen anzeigen - $video->setAttributes([ - 'controls' => true, - 'playsinline' => true - ]); - - // Wenn ein Poster-Bild ausgewählt wurde - if (REX_MEDIA[2]) { - $video->setPoster(rex_url::media(REX_MEDIA[2]), 'Vorschaubild'); - } - - echo $video->generateFull(); - } -} -``` +**Key Principles:** +- ✅ Single Responsibility - Each class has one clear purpose +- ✅ No "God Classes" - Separated concerns +- ✅ Universal naming - Not just "Video", but "VidstackPlayer" (supports audio too!) -Durch diese praktischen Beispiele wird deutlich, wie die erweiterten Methoden der Video-Klasse sinnvoll in verschiedenen Szenarien eingesetzt werden können, anstatt sie nur isoliert zu demonstrieren. +## Consent Management -## 🧙‍♂️ Tipp: Die magische Default-Funktion +**Important:** This addon does NOT include consent management for YouTube/Vimeo. -Wer faul clever ist, baut sich eine Hilfsfunktion für Standardeinstellungen: +For GDPR-compliant embeds, install the [Consent Manager AddOn](https://github.com/FriendsOfREDAXO/consent_manager): -```php -function createDefaultVideo($source, $title = '', $a11yContent = null) { - $current_lang = rex_clang::getCurrent(); - $lang_code = $current_lang->getCode(); - $video = new Video($source, $title, $lang_code); - $video->setAttributes([ - 'autoplay' => false, - 'muted' => true, - 'playsinline' => true - ]); - if ($a11yContent !== null) { - $video->setA11yContent($a11yContent); - } - $video->setPoster('/pfad/zu/default_poster.jpg', 'Default video poster'); - return $video; -} - -// Verwendung -$easyVideo = createDefaultVideo('https://youtube.com/watch?v=abcdefg', 'Einfach Genial', 'Ein Video über etwas Interessantes'); -echo $easyVideo->generateFull(); +```bash +composer require friendsofredaxo/consent_manager ``` -## 🎸 Unterstützung für Audio-Dateien +The Consent Manager will automatically handle consent for YouTube and Vimeo embeds. -Das Addon unterstützt auch die Einbindung von Audio-Dateien. Genauso wie für Videos: +## Browser Support -```php -$audio = new Video('audio.mp3', 'Mein Lieblingssong'); -echo $audio->generate(); -``` +- Chrome 90+ +- Firefox 88+ +- Safari 14+ +- Edge 90+ -## ✔︎ Im Backend schon integriert +For older browsers, Vidstack will gracefully degrade to native video/audio elements. -Hier muss man nichts machen - außer Videos schauen. +## Migration from Vidstack 1.x -![Screenshot](https://github.com/FriendsOfREDAXO/vidstack/blob/assets/mediapool.png?raw=true) +See [MIGRATION.md](MIGRATION.md) for detailed migration instructions. +**Breaking Changes:** +- Package name: `vidstack` → `vidstack` +- Namespace: `FriendsOfRedaxo\VidStack\Video` → `FriendsOfRedaxo\VidstackPlayer\VidstackPlayer` +- Class name: `Video` → `VidstackPlayer` (reflects audio support) +- API: Setter methods → Fluent method chaining +- Assets: Use `AssetHelper::getCss()` and `getJs()` instead of manual inclusion -## 🍪 Consent und Kekse +## Development -Leider muss es ja sein. +Dieses Addon nutzt die [Standard GitHub Workflows für REDAXO Addons](https://github.com/FriendsOfREDAXO/github-workflows). -Hiermit kann man in einem Consent-Manager oder auch so mal zwischendurch die Erlaubnis für Vimeo oder Youtube setzen. Wer keine Cookies erlaubt bekommt halt Local-Storage 😉. +### Setup -```js - -``` +```bash +# Das Addon muss im REDAXO-Kontext entwickelt werden +# Klone REDAXO und installiere das Addon dort -oder für beide -```js - +# Install Node dependencies +cd redaxo/src/addons/vidstack/build +npm install ``` +### Lokale Quality Checks +**PHP** (im Docker Container oder REDAXO-Root): +```bash +# Im REDAXO Docker Container (empfohlen) +docker exec coreweb bash -c "cd /var/www/html/public && \ + vendor/bin/php-cs-fixer fix redaxo/src/addons/vidstack --config=redaxo/src/addons/vidstack/.php-cs-fixer.dist.php" - -## 📄 CKE5 Oembed - lässig aufgelöst -(*das Plyr-AddOn lässt grüßen*) - -CKE5 kann ja bekanntlich Videos einbinden, aber liefert nichts für die Ausgabe im Frontend mit. 👋 Hier ist die Lösung: - -Einfach im String suchen und umwanden: - -```php -echo Video::parseOembedTags($content); +docker exec coreweb bash -c "cd /var/www/html/public && \ + php redaxo/src/addons/vidstack/.tools/rexstan.php && \ + redaxo/bin/console rexstan:analyze" ``` -und schon sind die Videos da 😀 -…oder in der boot.php vom Project-AddOn (gerne auch im eigenen AddOn) den Outputfilter nutzen. - - -### Outputfilter im Frontend - -```php -if (rex::isFrontend()) { -Video::videoOembedHelper(); -} +**JavaScript:** +```bash +cd build +npm run build # Build assets +npx eslint config/*.js # Lint JavaScript +npx eslint config/*.js --fix # Auto-fix JavaScript ``` -### Outputfilter im Backend: -Es soll ja nicht nur vorne schön sein. ❤️ -Hier muss man dafür sorgen, dass es ggf. in den Blocks nicht ausgeführt wird. +### GitHub Actions -```php -if (rex::isBackend() && rex_be_controller::getCurrentPagePart(1) == 'content' && !in_array(rex_request::get('function', 'string'), ['add', 'edit'])) { -Video::videoOembedHelper(); -} -``` +Automatisch bei jedem Push/PR: +1. **Code Style** - PHP-CS-Fixer formatiert Code automatisch +2. **Rexstan** - PHPStan für REDAXO (Level 9) +3. **Build Assets** - ESLint + npm build +4. **Publish to REDAXO** - Bei GitHub Release -## 🎉 HEUREKA! - -Jetzt bist du ein Video-Einbettungs-Ninja! Geh raus und mache das Internet zu einem besseren Ort - ein Video nach dem anderen. Und denk dran: Mit großer Macht kommt große Verantwortung (und coole Videos)! - -Viel Spaß beim Coden! 🚀👩‍💻👨‍💻 - -## 👓 Für die DEVs, Nerds und Geeks - -Ihr wollt uns sicher mal bei der Weiterentwicklung helfen. Das geht so: - -### Den Vendor aktualisieren und ein frisches Build erstellen - -Im Ordner build ist alles drin was man braucht. -- Also forken, lokal runterladen. -- npm install ausführen -- npm npm run build ausführen -- Im Assets-Ordner die Dateien des Dist-Ordners austauschen (Ihr habt richtig gesehen, es gibt auch die reine JS-Variante 😉) - -PR erstellen 😀 - -### Alles andere - -…fliegt hier so im Repo rum, einfach mal reinschauen. 👀 - -## Wie es arbeitet - -### Video-Klasse Prozess mit Prüfungen - -```mermaid -flowchart TD - A[Start] --> B[Erstelle Video-Objekt mit Dateipfad] - B --> C{Ist es eine gültige Datei?} - C -->|Nein| D[Fehler: Ungültige Datei] - C -->|Ja| E{Ist es ein unterstütztes Format?} - E -->|Nein| F[Fehler: Nicht unterstütztes Format] - E -->|Ja| G[Setze grundlegende Attribute] - G --> H{Ist es ein Video?} - H -->|Ja| I[Setze Video-spezifische Attribute] - H -->|Nein| J[Setze Audio-spezifische Attribute] - I --> K{Poster-Bild angegeben?} - K -->|Ja| L{Ist Poster-Datei gültig?} - L -->|Nein| M[Warnung: Ungültiges Poster] - L -->|Ja| N[Setze Poster-Bild] - K -->|Nein| O[Verwende Standard-Poster] - J --> P[Prüfe auf Untertitel] - N --> P - O --> P - M --> P - P --> Q{Untertitel vorhanden?} - Q -->|Ja| R{Sind Untertitel-Dateien gültig?} - R -->|Nein| S[Warnung: Ungültige Untertitel] - R -->|Ja| T[Füge Untertitel hinzu] - Q -->|Nein| U[Keine Untertitel] - S --> V[Generiere Player-HTML] - T --> V - U --> V - V --> W{HTML erfolgreich generiert?} - W -->|Nein| X[Fehler: HTML-Generierung fehlgeschlagen] - W -->|Ja| Y[Zeige Video/Audio-Player] - Y --> Z[Ende] - D --> Z - F --> Z - X --> Z -``` - +[Mehr Infos zu den Workflows](https://github.com/FriendsOfREDAXO/github-workflows) -## Autor(en) +## License -**Friends Of REDAXO** +MIT License - See [LICENSE](LICENSE) file -* http://www.redaxo.org -* https://github.com/FriendsOfREDAXO -* Ein bisschen KI 😎 +## Credits +- Built with [Vidstack](https://vidstack.io) +- Maintained by [Friends Of REDAXO](https://github.com/FriendsOfREDAXO) +- For REDAXO CMS -**Projektleitung** +## Support -[Thomas Skerbis](https://github.com/skerbis) +- **Issues:** [GitHub Issues](https://github.com/FriendsOfREDAXO/vidstack/issues) +- **REDAXO Slack:** #addons channel +- **Forum:** [REDAXO Forum](https://www.redaxo.org/forum/) -**Thanks to** -[Vidstack.io](https://www.vidstack.io) diff --git a/README_de.md b/README_de.md new file mode 100644 index 0000000..efb19c1 --- /dev/null +++ b/README_de.md @@ -0,0 +1,391 @@ +# Vidstack Player für REDAXO + +Moderner Media-Player (Video & Audio) mit vollständiger [Vidstack.io](https://vidstack.io)-Unterstützung für REDAXO CMS. + +## Features + +✅ **Universelle Medienunterstützung** - Video UND Audio-Wiedergabe (nicht nur Video!) +✅ **Moderne Fluent API** - Method-Chaining für sauberen, lesbaren Code +✅ **Mehrere Plattformen** - Lokale Dateien, YouTube, Vimeo +✅ **Untertitel & Captions** - Vollständige WebVTT-Track-Unterstützung +✅ **Adaptive Qualität** - Mehrere Quellen für Qualitätswechsel +✅ **Barrierefreiheit** - WCAG-konform mit ARIA-Labels +✅ **FFmpeg-Integration** - Video-Infos und Tools im Backend-Mediapool +✅ **CKE5-Integration** - Automatisches oEmbed-Parsing +✅ **Cache-Busting** - Automatische Asset-Versionierung +✅ **Performance** - Defer/Async-Script-Loading + +## Installation + +```bash +# Via Composer (empfohlen) +composer require friendsofredaxo/vidstack + +# Oder via REDAXO Installer +# Suche nach "Vidstack Player" im AddOn-Installer +``` + +## Schnellstart + +### Einfaches Video + +```php +title('Mein Video') + ->poster('poster.jpg') + ->attributes(['controls' => true]); + +echo $player->render(); +``` + +### Einfaches Audio + +```php +$player = (new VidstackPlayer('audio.mp3')) + ->title('Podcast Episode 1') + ->attributes(['controls' => true]); + +echo $player->render(); +``` + +### YouTube-Video + +```php +$player = (new VidstackPlayer('https://youtube.com/watch?v=dQw4w9WgXcQ')) + ->title('Never Gonna Give You Up'); + +echo $player->render(); +``` + +## Fluent API Referenz + +### Kern-Methoden + +```php +$player = (new VidstackPlayer('source.mp4')) + ->title('Titel') // Titel setzen + ->lang('de') // Sprache setzen (Standard: 'de') + ->poster('poster.jpg', 'Alt-Text') // Poster-Bild setzen + ->aspectRatio('16/9') // Seitenverhältnis setzen + ->thumbnails('thumbs.vtt') // Thumbnail-Track setzen + ->attributes(['controls' => true]) // Mehrere Attribute setzen + ->attr('muted', true) // Einzelnes Attribut setzen + ->render(); // HTML generieren +``` + +### Untertitel & Captions + +```php +$player->track( + src: 'subtitles.vtt', + label: 'Deutsch', + srclang: 'de', + kind: 'subtitles', // subtitles|captions|descriptions|chapters|metadata + default: true +); +``` + +### Mehrere Quellen (Adaptive Qualität) + +```php +$player->multipleSources([ + ['src' => 'video-1080p.mp4', 'width' => 1920, 'height' => 1080, 'type' => 'video/mp4'], + ['src' => 'video-720p.mp4', 'width' => 1280, 'height' => 720, 'type' => 'video/mp4'], + ['src' => 'video-480p.mp4', 'width' => 854, 'height' => 480, 'type' => 'video/mp4'] +]); +``` + +## Frontend-Integration + +### Assets einbinden + +```php + + + + + + + + + + + + +``` + +### Asset Helper Optionen + +```php +// CSS mit benutzerdefinierten Attributen +echo AssetHelper::getCss(['media' => 'screen'], cachebuster: true); + +// JS mit defer (empfohlen für Performance) +echo AssetHelper::getJs(defer: true); + +// JS mit async (mit Vorsicht verwenden) +echo AssetHelper::getJs(async: true); + +// Benutzerdefinierte Attribute +echo AssetHelper::getJs( + defer: true, + attributes: ['type' => 'module', 'crossorigin' => 'anonymous'], + cachebuster: true +); + +// Cache-Busting deaktivieren +echo AssetHelper::getCss(cachebuster: false); +``` + +## Erweiterte Beispiele + +### Vollständiges Video-Setup + +```php +title('Mein Film') + ->lang('de') + ->poster('poster.jpg', 'Film-Poster') + ->aspectRatio('16/9') + ->thumbnails('thumbs.vtt') + ->track('subtitles_de.vtt', 'Deutsch', 'de', 'subtitles', true) + ->track('subtitles_en.vtt', 'English', 'en', 'subtitles') + ->multipleSources([ + ['src' => 'movie-1080p.mp4', 'width' => 1920, 'height' => 1080, 'type' => 'video/mp4'], + ['src' => 'movie-720p.mp4', 'width' => 1280, 'height' => 720, 'type' => 'video/mp4'] + ]) + ->attributes([ + 'controls' => true, + 'playsinline' => true, + 'preload' => 'metadata' + ]); + +echo $player->render(); +``` + +### Podcast mit Kapiteln + +```php +$podcast = (new VidstackPlayer('episode-01.mp3')) + ->title('Episode 1: Der Einstieg') + ->lang('de') + ->poster('cover.jpg', 'Podcast-Cover') + ->track('chapters.vtt', 'Kapitel', 'de', 'chapters', true) + ->attributes(['controls' => true]); + +echo $podcast->render(); +``` + +## Utility-Klassen + +### Plattform-Erkennung + +```php + 'youtube', 'id' => 'abc'] + +// Prüfen ob Audio +$isAudio = PlatformDetector::isAudio('podcast.mp3'); // true + +// Prüfen ob gültiges Medium +$isMedia = PlatformDetector::isMedia('video.mp4'); // true +``` + +### Utilities + +```php + true, + 'muted' => true, + 'data-id' => '123' +]); +// Gibt zurück: ' controls muted data-id="123"' + +// MIME-Type erkennen +$mime = Utilities::detectMimeType('video.webm'); // 'video/webm' +``` + +## Backend-Features + +### Mediapool-Integration + +Das AddOn integriert sich automatisch in den REDAXO-Mediapool: + +- **Vorschau-Player** - Video/Audio-Vorschau in der Medien-Detail-Sidebar +- **Video-Info** - Auflösung, Codec, Dauer, Dateigröße, Bitrate (benötigt FFmpeg-AddOn) +- **Schnell-Tools** - Trimmen, Optimieren und Analysieren von Videos (benötigt FFmpeg-AddOn) + +### FFmpeg-Integration + +Installiere das [FFmpeg AddOn](https://github.com/FriendsOfREDAXO/ffmpeg) für erweiterte Features: + +```bash +composer require friendsofredaxo/ffmpeg +``` + +Features mit FFmpeg: +- Video-Informationsanzeige +- Schnell-Trimm-Tool +- Optimierungs-Tool +- Detaillierte Video-Analyse + +### CKE5-Integration + +Das AddOn bietet einen oEmbed-Parser, um CKEditor 5 oEmbed-Tags in Vidstack-Player umzuwandeln. + +**Manuelle Aktivierung erforderlich** (z.B. in der `boot.php` deines Projekt-AddOns): + +```php + + + + +... +``` + +**Warum manuelle Aktivierung?** +- Gibt dir volle Kontrolle über wann und wie Videos eingebettet werden +- Verhindert Konflikte mit bestehenden oEmbed-Handlern oder eigenen Implementierungen +- Opt-in-Ansatz - aktiviere nur wenn du es brauchst + +## Architektur + +Das AddOn ist in fokussierte, zweckgebundene Klassen strukturiert: + +``` +lib/ +├── VidstackPlayer.php # Haupt-Player-Klasse (Fluent API) +├── PlatformDetector.php # Plattform- & Medientyp-Erkennung +├── AssetHelper.php # CSS/JS-Laden mit Cache-Busting +├── Utilities.php # HTML-Attribute, MIME-Types +├── Translator.php # i18n-Übersetzungs-Helper +├── OembedParser.php # CKE5 oEmbed-Integration +└── BackendIntegration.php # Mediapool-Sidebar & FFmpeg +``` + +**Kernprinzipien:** +- ✅ Single Responsibility - Jede Klasse hat einen klaren Zweck +- ✅ Keine "God Classes" - Getrennte Zuständigkeiten +- ✅ Universelle Benennung - Nicht nur "Video", sondern "VidstackPlayer" (unterstützt auch Audio!) + +## Consent Management + +**Wichtig:** Dieses AddOn enthält KEIN Consent-Management für YouTube/Vimeo. + +Für DSGVO-konforme Embeds installiere den [Consent Manager](https://github.com/FriendsOfREDAXO/consent_manager): + +```bash +composer require friendsofredaxo/consent_manager +``` + +Der Consent Manager übernimmt automatisch die Einwilligung für YouTube- und Vimeo-Embeds. + +## Browser-Unterstützung + +- Chrome 90+ +- Firefox 88+ +- Safari 14+ +- Edge 90+ + +Für ältere Browser degradiert Vidstack elegant zu nativen Video/Audio-Elementen. + +## Migration von Vidstack 1.x + +Siehe [MIGRATION.md](MIGRATION.md) für detaillierte Migrations-Anweisungen. + +**Breaking Changes:** +- Package-Name: `vidstack` → `vidstack` +- Namespace: `FriendsOfRedaxo\VidStack\Video` → `FriendsOfRedaxo\VidstackPlayer\VidstackPlayer` +- Klassen-Name: `Video` → `VidstackPlayer` (reflektiert Audio-Unterstützung) +- API: Setter-Methoden → Fluent Method Chaining +- Assets: Nutze `AssetHelper::getCss()` und `getJs()` statt manueller Einbindung + +## Entwicklung + +Dieses AddOn nutzt die [Standard GitHub Workflows für REDAXO AddOns](https://github.com/FriendsOfREDAXO/github-workflows). + +### Setup + +```bash +# Das AddOn muss im REDAXO-Kontext entwickelt werden +# Klone REDAXO und installiere das AddOn dort + +# Node-Dependencies installieren +cd redaxo/src/addons/vidstack/build +npm install +``` + +### Lokale Quality Checks + +**PHP** (im Docker Container oder REDAXO-Root): +```bash +# Im REDAXO Docker Container (empfohlen) +docker exec coreweb bash -c "cd /var/www/html/public && \ + vendor/bin/php-cs-fixer fix redaxo/src/addons/vidstack --config=redaxo/src/addons/vidstack/.php-cs-fixer.dist.php" + +docker exec coreweb bash -c "cd /var/www/html/public && \ + php redaxo/src/addons/vidstack/.tools/rexstan.php && \ + redaxo/bin/console rexstan:analyze" +``` + +**JavaScript:** +```bash +cd build +npm run build # Assets bauen +npx eslint config/*.js # JavaScript linten +npx eslint config/*.js --fix # JavaScript auto-fixen +``` + +### GitHub Actions + +Automatisch bei jedem Push/PR: + +1. **Code Style** - PHP-CS-Fixer formatiert Code automatisch +2. **Rexstan** - PHPStan für REDAXO (Level 9) +3. **Build Assets** - ESLint + npm build +4. **Publish to REDAXO** - Bei GitHub Release + +[Mehr Infos zu den Workflows](https://github.com/FriendsOfREDAXO/github-workflows) + +## Lizenz + +MIT License - Siehe [LICENSE](LICENSE)-Datei + +## Credits + +- Entwickelt mit [Vidstack](https://vidstack.io) +- Maintained by [Friends Of REDAXO](https://github.com/FriendsOfREDAXO) +- Für REDAXO CMS + +## Support + +- **Issues:** [GitHub Issues](https://github.com/FriendsOfREDAXO/vidstack/issues) +- **REDAXO Slack:** #addons Channel +- **Forum:** [REDAXO Forum](https://www.redaxo.org/forum/) diff --git a/assets/vidstack_helper.css b/assets/src/vidstack_helper.css similarity index 62% rename from assets/vidstack_helper.css rename to assets/src/vidstack_helper.css index 2faac32..a1a5814 100644 --- a/assets/vidstack_helper.css +++ b/assets/src/vidstack_helper.css @@ -1,4 +1,13 @@ -.video-container { +/** + * Vidstack Helper Styles + * Core styles for Vidstack player, accessibility, and FFmpeg integration + */ + +/* =================================== + MEDIA PLAYER BASE + =================================== */ + +.vidstack-video-container { margin-bottom: 0px; } @@ -6,88 +15,33 @@ contain: layout; } -.video-container media-player { +.vidstack-video-container media-player { width: 100%; } -.video-description, .video-transcript, .alternative-links { - margin-top: 10px; - display: none; -} -.js .a11y-content { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} -.no-js .a11y-content { - display: block; -} -.skip-link { - position: absolute; - top: -40px; - left: 0; - background: #000; - color: white; - padding: 8px; - z-index: 100; -} -.skip-link:focus { - top: 0; -} -.video-wrapper { + +/* =================================== + RESPONSIVE VIDEO WRAPPER + =================================== */ + +.vidstack-video-wrapper { position: relative; padding-bottom: 56.25%; /* 16:9 Aspect Ratio */ height: 0; overflow: hidden; } -.video-wrapper media-player { + +.vidstack-video-wrapper media-player { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } -.consent-placeholder { - background-color: #f0f0f0; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - text-align: center; - padding: 20px; - box-sizing: border-box; -} - -.consent-placeholder button { - margin-top: 10px; - padding: 10px 20px; - background-color: #007bff; - color: white; - border: none; - border-radius: 5px; - cursor: pointer; -} -.consent-placeholder button:hover { - background-color: #0056b3; -} -noscript .js .a11y-content { - position: static; - width: auto; - height: auto; - padding: initial; - margin: initial; - overflow: visible; - clip: auto; - white-space: normal; -} +/* =================================== + FFMPEG VIDEO INFO (BACKEND) + =================================== */ -/* Video-Informationen Styling */ .vidstack-video-info { margin-top: 15px; padding: 12px; @@ -123,23 +77,23 @@ noscript .js .a11y-content { display: inline-block; } -@media (max-width: 768px) { - .vidstack-video-info { - font-size: 11px; - padding: 10px; - } - - .vidstack-video-info h4 { - font-size: 12px; - } - - .vidstack-video-info .btn { - font-size: 10px; - padding: 2px 6px; - } +/* FFmpeg Action Buttons */ +.vidstack-action-buttons { + margin-top: 10px; + padding-top: 8px; + border-top: 1px solid #dee2e6; +} + +.vidstack-action-buttons-label { + margin-bottom: 6px; +} + +.vidstack-action-buttons-group { + display: flex; + flex-wrap: wrap; + gap: 6px; } -/* Button-Styles für FFmpeg-Tools */ .vidstack-video-info .btn { display: inline-flex; align-items: center; @@ -188,11 +142,40 @@ noscript .js .a11y-content { color: white; } -/* CSP-compliant styles to replace inline styles */ +/* =================================== + VIDSTACK FIXES + =================================== */ + +/* Fix for controls pointer events */ .vds-controls-group { pointer-events: none; } +/* Fix for video slider thumbnails */ media-slider-video video { max-width: unset; } + +/* =================================== + RESPONSIVE ADJUSTMENTS + =================================== */ + +@media (max-width: 768px) { + .vidstack-video-info { + font-size: 11px; + padding: 10px; + } + + .vidstack-video-info h4 { + font-size: 12px; + } + + .vidstack-video-info .btn { + font-size: 10px; + padding: 2px 6px; + } + + .vidstack-action-buttons-group { + flex-direction: column; + } +} diff --git a/assets/src/vidstack_helper.js b/assets/src/vidstack_helper.js new file mode 100644 index 0000000..117099e --- /dev/null +++ b/assets/src/vidstack_helper.js @@ -0,0 +1,119 @@ +/** + * Vidstack Helper - Translations and Event Handling + * + * Handles loading and applying translations for Vidstack player + * Consent management should be handled by an external addon (e.g., Consent Manager) + */ +console.log('Vidstack Helper: Script loaded and executing!'); + +(function () { + console.log('Vidstack Helper: IIFE starting'); + let vidstackTranslations = {}; + + /** + * Load translations from JSON file + */ + async function vidstackLoadTranslations() { + try { + vidstackTranslations = await (await fetch('/assets/addons/vidstack_player/translations.json')).json(); + } catch (error) { + console.error('Vidstack: Error loading translations:', error); + vidstackTranslations = { de: {}, en: {} }; + } + } + + /** + * Apply translations to all media players + */ + function vidstackApplyTranslations() { + console.log('Vidstack: Applying translations...'); + ['media-video-layout', 'media-audio-layout'].forEach(selector => { + const layouts = document.querySelectorAll(selector); + console.log('Vidstack: Found', layouts.length, selector, 'elements'); + layouts.forEach(layout => { + const player = layout.closest('media-player'); + const lang = player?.getAttribute('lang') || document.documentElement.lang || 'en'; + + // Apply translations from loaded JSON + if (vidstackTranslations[lang]) { + layout.translations = vidstackTranslations[lang]; + } else if (vidstackTranslations['en']) { + // Fallback to English + layout.translations = vidstackTranslations['en']; + } + }); + }); + } + + /** + * Initialize media players with translations + */ + function vidstackInitializeMediaPlayers() { + document.querySelectorAll('media-player').forEach(player => { + // Translations are applied automatically via vidstackApplyTranslations() + // No consent handling here - that should be managed by external addon + }); + + vidstackApplyTranslations(); + } + + /** + * Main initialization function + */ + async function vidstackInitialize() { + console.log('Vidstack: Starting initialization...'); + await vidstackLoadTranslations(); + console.log('Vidstack: Translations loaded, found', Object.keys(vidstackTranslations).length, 'languages'); + vidstackInitializeMediaPlayers(); + + // Watch for dynamically added players (AJAX, etc.) + const observer = new MutationObserver((mutations) => { + let needsUpdate = false; + + mutations.forEach((mutation) => { + if (mutation.type === 'childList') { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === Node.ELEMENT_NODE) { + if (node.tagName?.toLowerCase() === 'media-player' || + node.querySelector?.('media-player')) { + needsUpdate = true; + } + } + }); + } + }); + + if (needsUpdate) { + vidstackApplyTranslations(); + } + }); + + observer.observe(document.body, { childList: true, subtree: true }); + } + + // Initialize on various events (cover different CMS scenarios) + ['DOMContentLoaded', 'load', 'vsrun'].forEach(event => + document.addEventListener(event, vidstackInitialize) + ); + + // REDAXO backend support - multiple events to ensure initialization + if (typeof jQuery !== 'undefined') { + jQuery(document).on('rex:ready', function() { + vidstackInitialize(); + }); + + // Also initialize on pjax updates (mediapool, content edit) + jQuery(document).on('pjax:end', function() { + vidstackInitialize(); + }); + } + + // If DOM is already ready or script loads late (e.g., with defer), initialize immediately + if (document.readyState === 'complete' || + (document.readyState === 'interactive' && document.body)) { + console.log('Vidstack: Initializing immediately (readyState:', document.readyState + ')'); + vidstackInitialize(); + } else { + console.log('Vidstack: Waiting for DOM (readyState:', document.readyState + ')'); + } +})(); diff --git a/assets/vidstack.css b/assets/vidstack.min.css similarity index 100% rename from assets/vidstack.css rename to assets/vidstack.min.css diff --git a/assets/vidstack.js b/assets/vidstack.min.js similarity index 100% rename from assets/vidstack.js rename to assets/vidstack.min.js diff --git a/assets/vidstack_helper.js b/assets/vidstack_helper.js deleted file mode 100644 index d511445..0000000 --- a/assets/vidstack_helper.js +++ /dev/null @@ -1,211 +0,0 @@ -(function () { - let translations = {}; - - async function loadTranslations() { - try { - translations = await (await fetch('/assets/addons/vidstack/translations.json')).json(); - } catch (error) { - console.error('Fehler beim Laden der Übersetzungen:', error); - translations = { de: {}, en: {} }; - } - } - - function getText(key, lang = 'en') { - return (translations[lang] && translations[lang][key]) || key; - } - - const consentKey = 'video_consent'; - let videoConsent = JSON.parse(localStorage.getItem(consentKey) || '{}'); - - function setConsent(platform) { - // Vidstack eigenes Consent-System - videoConsent[platform] = true; - localStorage.setItem(consentKey, JSON.stringify(videoConsent)); - document.cookie = `${platform}_consent=true; path=/; max-age=${30 * 24 * 60 * 60}; SameSite=Lax; Secure`; - - // Optional: Consent Manager Logging (falls installiert) - if (typeof consent_manager_util !== 'undefined') { - // Nutze Standard-UIDs: 'youtube' oder 'vimeo' - try { - // API Call für DSGVO-konformes Logging - fetch('?rex-api-call=consent_manager_inline_log', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: new URLSearchParams({ - service: platform, - consent_type: 'inline', - source: 'vidstack' - }) - }).catch(err => console.warn('Vidstack: Consent Manager Logging fehlgeschlagen', err)); - - // Consent Manager Cookie setzen (parallel zu Vidstack) - consent_manager_util.set_consent(platform); - } catch (e) { - console.warn('Vidstack: Consent Manager Integration Fehler', e); - } - } - } - - function hasConsent(platform) { - return videoConsent[platform] === true || document.cookie.includes(`${platform}_consent=true`); - } - - function rebuildMediaPlayer(existingPlayer, platform, videoId, placeholder) { - // Store the original attributes and innerHTML, including the style attribute - const attributes = {}; - for (let i = 0; i < existingPlayer.attributes.length; i++) { - const attr = existingPlayer.attributes[i]; - if (attr.name !== 'src') { // Preserve all attributes except 'src' - attributes[attr.name] = attr.value; - } - } - const innerHTML = existingPlayer.innerHTML; - - // Remove the existing media player - existingPlayer.remove(); - - // Create a new media player element with the correct src from the start - const newPlayer = document.createElement('media-player'); - - // Set all attributes including the new src - for (const [name, value] of Object.entries(attributes)) { - newPlayer.setAttribute(name, value); - } - newPlayer.setAttribute('src', `${platform}/${videoId}`); - - // Set inner HTML - newPlayer.innerHTML = innerHTML; - - // Insert the new player - if (placeholder?.classList.contains('consent-placeholder')) { - placeholder.insertAdjacentElement('afterend', newPlayer); - placeholder.style.display = 'none'; - } else { - // If no placeholder, insert at the end of the container - const container = placeholder?.parentNode || document.body; - container.appendChild(newPlayer); - } - - return newPlayer; - } - - function loadVideo(placeholder) { - const { platform, videoId } = placeholder.dataset; - const mediaPlayer = placeholder.nextElementSibling; - - if (mediaPlayer?.tagName.toLowerCase() === 'media-player') { - rebuildMediaPlayer(mediaPlayer, platform, videoId, placeholder); - } else { - console.error('Media player element not found'); - } - } - - function updatePlaceholder(placeholder, consented) { - const button = placeholder.querySelector('.consent-button'); - const lang = document.documentElement.lang || 'en'; - - if (button) { - button.textContent = getText(consented ? 'Consent given' : 'Load Video', lang); - button.disabled = consented; - } - } - - function initializePlaceholder(placeholder) { - const platform = placeholder.dataset.platform; - const consented = hasConsent(platform); - - updatePlaceholder(placeholder, consented); - - if (consented) { - loadVideo(placeholder); - } else { - const consentButton = placeholder.querySelector('.consent-button'); - if (consentButton) { - consentButton.addEventListener('click', () => { - setConsent(platform); - updatePlaceholder(placeholder, true); - loadVideo(placeholder); - }); - } else { - console.error('Consent button not found in placeholder'); - } - } - } - - function initializeMediaPlayer(player) { - const platform = player.dataset.videoPlatform; - if (hasConsent(platform)) { - const videoId = player.dataset.videoId; - - // Check if src is already set - if so, the player is already properly configured - if (player.hasAttribute('src')) { - // Make sure the player is visible and the placeholder is hidden - player.style.display = ''; - const placeholder = player.previousElementSibling; - if (placeholder?.classList.contains('consent-placeholder')) { - placeholder.style.display = 'none'; - } - return; - } - - // Only recreate the player if no src is set - const placeholder = player.previousElementSibling; - rebuildMediaPlayer(player, platform, videoId, placeholder); - } - } - - function applyTranslations() { - ['media-video-layout', 'media-audio-layout'].forEach(selector => { - document.querySelectorAll(selector).forEach(layout => { - const player = layout.closest('media-player'); - const lang = player?.getAttribute('lang') || 'en'; - layout.translations = translations[lang] || translations['en']; - }); - }); - } - - function initializeElements() { - document.querySelectorAll('.consent-placeholder').forEach(initializePlaceholder); - document.querySelectorAll('media-player[data-video-platform]').forEach(initializeMediaPlayer); - applyTranslations(); - } - - async function initializeScript() { - await loadTranslations(); - initializeElements(); - - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (mutation.type === 'childList') { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - if (node.classList.contains('consent-placeholder')) { - initializePlaceholder(node); - } - if (node.tagName.toLowerCase() === 'media-player') { - initializeMediaPlayer(node); - applyTranslations(); - } - } - }); - } - }); - }); - - observer.observe(document.body, { childList: true, subtree: true }); - } - - ['DOMContentLoaded', 'vsrun'].forEach(event => - document.addEventListener(event, initializeScript) - ); - - if (typeof jQuery !== 'undefined') { - jQuery(document).on('rex:ready', initializeScript); - } - - if (['complete', 'interactive'].includes(document.readyState)) { - initializeScript(); - } -})(); diff --git a/assets/vs_js_out.css b/assets/vs_js_out.css deleted file mode 100644 index ff58a30..0000000 --- a/assets/vs_js_out.css +++ /dev/null @@ -1,2167 +0,0 @@ -/* node_modules/vidstack/player/styles/default/theme.css */ -[data-media-player] { - width: 100%; - display: inline-flex; - align-items: center; - position: relative; - contain: style; - box-sizing: border-box; - user-select: none; -} -[data-media-player] * { - box-sizing: border-box; -} -:where([data-media-player][data-view-type=video]) { - aspect-ratio: 16 / 9; -} -[data-media-player]:focus, -[data-media-player]:focus-visible { - outline: none; -} -[data-media-player][data-view-type=video][data-started]:not([data-controls]) { - pointer-events: auto; - cursor: none; -} -[data-media-player] slot { - display: contents; -} -[data-media-provider] { - display: flex; - position: relative; - box-sizing: border-box; - align-items: center; - border-radius: inherit; - width: 100%; - aspect-ratio: inherit; - overflow: hidden; -} -[data-media-player]:not([data-view-type=audio]) [data-media-provider], -[data-media-player][data-fullscreen] [data-media-provider] { - height: 100%; -} -[data-media-player][data-view-type=audio] [data-media-provider] { - display: contents; - background-color: unset; -} -[data-media-provider] audio { - width: 100%; -} -:where(video:not([width]):not([height]), iframe:not([width]):not([height])) { - width: 100%; - aspect-ratio: 16 / 9; -} -:where([data-media-provider] video), -:where([data-media-provider] iframe) { - aspect-ratio: inherit; - display: inline-block; - height: auto; - object-fit: contain; - touch-action: manipulation; - border-radius: inherit; - width: 100%; -} -[data-media-provider] iframe { - height: 100%; -} -[data-media-player][data-view-type=audio] video, -[data-media-player][data-view-type=audio] iframe { - display: none; -} -[data-media-player][data-fullscreen] video { - height: 100%; -} -[data-media-provider] iframe:not([src]) { - display: none; -} -iframe.vds-youtube[data-no-controls] { - height: 1000%; -} -.vds-blocker { - position: absolute; - inset: 0; - width: 100%; - height: auto; - aspect-ratio: inherit; - pointer-events: auto; - border-radius: inherit; - z-index: 1; -} -[data-ended] .vds-blocker { - background-color: black; -} -.vds-icon:focus { - outline: none; -} -.vds-google-cast { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - color: #dedede; - font-family: sans-serif; - font-weight: 500; -} -.vds-google-cast svg { - --size: max(18%, 40px); - width: var(--size); - height: var(--size); - margin-bottom: 8px; -} -.vds-google-cast-info { - font-size: calc(var(--media-height) / 100 * 6); -} -:where(.vds-buffering-indicator) { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - pointer-events: none; - z-index: 1; -} -:where(.vds-buffering-indicator) :where(.vds-buffering-icon, .vds-buffering-spinner) { - opacity: 0; - pointer-events: none; - transition: var(--media-buffering-transition, opacity 200ms ease); -} -:where(.vds-buffering-indicator) :where(.vds-buffering-icon, svg.vds-buffering-spinner, .vds-buffering-spinner svg) { - width: var(--media-buffering-size, 96px); - height: var(--media-buffering-size, 96px); -} -:where(.vds-buffering-indicator) :where(.vds-buffering-track, circle[data-part=track]) { - color: var(--media-buffering-track-color, #f5f5f5); - opacity: var(--media-buffering-track-opacity, 0.25); - stroke-width: var(--media-buffering-track-width, 8); -} -:where(.vds-buffering-indicator) :where(.vds-buffering-track-fill, circle[data-part=track-fill]) { - color: var(--media-buffering-track-fill-color, var(--media-brand)); - opacity: var(--media-buffering-track-fill-opacity, 0.75); - stroke-width: var(--media-buffering-track-fill-width, 9); - stroke-dasharray: 100; - stroke-dashoffset: var(--media-buffering-track-fill-offset, 50); -} -:where([data-buffering]) :where(.vds-buffering-icon, .vds-buffering-spinner) { - opacity: 1; - animation: var(--media-buffering-animation, vds-buffering-spin 1s linear infinite); -} -@keyframes vds-buffering-spin { - to { - transform: rotate(360deg); - } -} -@media (prefers-reduced-motion) { - :where([data-buffering]) :where(.vds-buffering-icon, .vds-buffering-spinner) { - animation-duration: 8s; - } -} -:where(.vds-button) { - -webkit-tap-highlight-color: transparent; - position: relative; - display: inline-flex; - justify-content: center; - align-items: center; - user-select: none; - appearance: none; - background: none; - outline: none; - border: none; - border-radius: var(--media-button-border-radius, 8px); - width: var(--media-button-size, 40px); - height: var(--media-button-size, 40px); - transition: transform 0.2s ease-out; - contain: layout style; - cursor: pointer; - -webkit-user-select: none; - -webkit-tap-highlight-color: transparent; - touch-action: manipulation; - flex-shrink: 0; -} -.vds-button { - border: var(--media-button-border); - color: var(--media-button-color, var(--media-controls-color, #f5f5f5)); - padding: var(--media-button-padding, 0px); -} -:where([data-fullscreen] .vds-button) { - width: var(--media-fullscreen-button-size, 42px); - height: var(--media-fullscreen-button-size, 42px); -} -@media screen and (max-width: 599px) { - :where([data-fullscreen] .vds-button) { - width: var(--media-sm-fullscreen-button-size, 42px); - height: var(--media-sm-fullscreen-button-size, 42px); - } -} -:where(.vds-button .vds-icon) { - width: var(--media-button-icon-size, 80%); - height: var(--media-button-icon-size, 80%); - border-radius: var(--media-button-border-radius, 8px); -} -:where(.vds-menu-button .vds-icon) { - display: flex !important; -} -:where(.vds-button[aria-hidden=true]) { - display: none !important; -} -@media (hover: hover) and (pointer: fine) { - .vds-button:hover { - background-color: var(--media-button-hover-bg, rgb(255 255 255 / 0.2)); - } - .vds-button:hover { - transform: var(--media-button-hover-transform, scale(1.05)); - transition: var(--media-button-hover-transition, transform 0.2s ease-in); - } - .vds-button[aria-expanded=true] { - transform: unset; - } -} -@media (pointer: coarse) { - .vds-button:hover { - border-radius: var(--media-button-touch-hover-border-radius, 100%); - background-color: var(--media-button-touch-hover-bg, rgb(255 255 255 / 0.2)); - } -} -:where(.vds-button:focus) { - outline: none; -} -:where(.vds-button[data-focus], .vds-button:focus-visible) { - box-shadow: var(--media-focus-ring); -} -:where(.vds-live-button) { - min-width: auto; - min-height: auto; - width: var(--media-live-button-width, 40px); - height: var(--media-live-button-height, 40px); - padding: 0; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - user-select: none; - appearance: none; - background: none; - outline: none; - border: none; -} -:where(.vds-live-button-text) { - font-family: var(--media-font-family, sans-serif); - font-size: var(--media-live-button-font-size, 12px); - font-weight: var(--media-live-button-font-weight, 600); - letter-spacing: var(--media-live-button-letter-spacing, 1.5px); - transition: color 0.3s ease; -} -.vds-live-button-text { - background-color: var(--media-live-button-bg, #8a8a8a); - border-radius: var(--media-live-button-border-radius, 2px); - color: var(--media-live-button-color, #161616); - padding: var(--media-live-button-padding, 1px 4px); -} -:where(.vds-live-button[data-focus] .vds-live-button-text) { - box-shadow: var(--media-focus-ring); -} -:where(.vds-live-button[data-edge]) { - cursor: unset; -} -.vds-live-button[data-edge] .vds-live-button-text { - background-color: var(--media-live-button-edge-bg, #dc2626); - color: var(--media-live-button-edge-color, #f5f5f5); -} -@media (pointer: fine) { - :where(.vds-live-button:hover) { - background-color: unset; - } -} -.vds-button:not([data-paused]) .vds-play-icon, -.vds-button[data-ended] .vds-play-icon, -.vds-button[data-paused] .vds-pause-icon, -.vds-button[data-ended] .vds-pause-icon, -.vds-button:not([data-ended]) .vds-replay-icon, -.vds-button[data-active] .vds-pip-enter-icon, -.vds-button:not([data-active]) .vds-pip-exit-icon, -.vds-button[data-active] .vds-fs-enter-icon, -.vds-button:not([data-active]) .vds-fs-exit-icon, -.vds-button:not([data-active]) .vds-cc-on-icon, -.vds-button[data-active] .vds-cc-off-icon, -.vds-button:not([data-muted]) .vds-mute-icon, -.vds-button:not([data-state=low]) .vds-volume-low-icon, -.vds-button:not([data-state=high]) .vds-volume-high-icon { - display: none; -} -:where(.vds-captions) { - --overlay-padding: var(--media-captions-padding, 1%); - --cue-color: var(--media-user-text-color, var(--media-cue-color, white)); - --cue-bg-color: var(--media-user-text-bg, var(--media-cue-bg, rgba(0, 0, 0, 0.7))); - --cue-default-font-size: var(--media-cue-font-size, calc(var(--overlay-height) / 100 * 4.5)); - --cue-font-size: calc(var(--cue-default-font-size) * var(--media-user-font-size, 1)); - --cue-line-height: var(--media-cue-line-height, calc(var(--cue-font-size) * 1.2)); - --cue-padding-x: var(--media-cue-padding-x, calc(var(--cue-font-size) * 0.6)); - --cue-padding-y: var(--media-cue-padding-x, calc(var(--cue-font-size) * 0.4)); - --cue-padding: var(--cue-padding-y) var(--cue-padding-x); - position: absolute; - inset: 0; - z-index: 1; - contain: layout style; - margin: var(--overlay-padding); - font-size: var(--cue-font-size); - font-family: var(--media-user-font-family, sans-serif); - box-sizing: border-box; - pointer-events: none; - user-select: none; - word-spacing: normal; - word-break: break-word; -} -:where([data-fullscreen][data-orientation=portrait] .vds-captions) { - --cue-default-font-size: var(--media-cue-font-size, calc(var(--overlay-width) / 100 * 4.5)); -} -:where([data-view-type=audio] .vds-captions) { - position: relative; - margin: 0; -} -:where(.vds-captions[aria-hidden=true]) { - opacity: 0; - visibility: hidden; -} -.vds-captions[data-example] { - opacity: 1 !important; - visibility: visible !important; -} -:where([data-view-type=video] .vds-captions [data-part=cue-display][data-example]) { - --cue-text-align: center; - --cue-width: 100%; - --cue-top: 90%; - --cue-left: 0%; -} -:where([data-view-type=audio] .vds-captions [data-part=cue-display]) { - --cue-width: 100%; - position: relative !important; -} -:where(.vds-captions [data-part=cue-display]) { - position: absolute; - direction: ltr; - overflow: visible; - contain: content; - top: var(--cue-top); - left: var(--cue-left); - right: var(--cue-right); - bottom: var(--cue-bottom); - width: var(--cue-width, auto); - height: var(--cue-height, auto); - box-sizing: border-box; - transform: var(--cue-transform); - text-align: var(--cue-text-align); - writing-mode: var(--cue-writing-mode, unset); - white-space: pre-line; - unicode-bidi: plaintext; - min-width: min-content; - min-height: min-content; - background-color: var(--media-user-display-bg, var(--media-cue-display-bg)); - border-radius: var(--media-cue-display-border-radius); -} -.vds-captions [data-part=cue-display] { - padding: var(--media-cue-display-padding); -} -:where(.vds-captions[data-dir=rtl] [data-part=cue-display]) { - direction: rtl; -} -:where(.vds-captions [data-part=cue]) { - display: inline-block; - contain: content; - font-variant: var(--media-user-font-variant); - border: var(--media-cue-border, unset); - border-radius: var(--media-cue-border-radius, 2px); - backdrop-filter: var(--media-cue-backdrop, blur(8px)); - line-height: var(--cue-line-height); - box-sizing: border-box; - box-shadow: var(--media-cue-box-shadow, var(--cue-box-shadow)); - white-space: var(--cue-white-space, pre-wrap); - outline: var(--cue-outline); - text-shadow: var(--media-user-text-shadow, var(--cue-text-shadow)); -} -.vds-captions [data-part=cue] { - background-color: var(--cue-bg-color); - color: var(--cue-color); - padding: var(--cue-padding); -} -:where(.vds-captions [data-part=cue-display][data-vertical] [data-part=cue]) { - --cue-padding: var(--cue-padding-x) var(--cue-padding-y); -} -:where(.vds-captions [data-part=region]) { - --anchor-x-percent: calc(var(--region-anchor-x) / 100); - --anchor-x: calc(var(--region-width) * var(--anchor-x-percent)); - --anchor-y-percent: calc(var(--region-anchor-y) / 100); - --anchor-y: calc(var(--region-height) * var(--anchor-y-percent)); - --vp-anchor-x: calc(var(--region-viewport-anchor-x) * 1%); - --vp-anchor-y-percent: calc(var(--region-viewport-anchor-y) / 100); - --vp-anchor-y: calc(var(--overlay-height) * var(--vp-anchor-y-percent)); - position: absolute; - display: inline-flex; - flex-flow: column; - justify-content: flex-start; - width: var(--region-width); - height: var(--region-height); - min-height: 0px; - max-height: var(--region-height); - writing-mode: horizontal-tb; - top: var(--region-top, calc(var(--vp-anchor-y) - var(--anchor-y))); - left: var(--region-left, calc(var(--vp-anchor-x) - var(--anchor-x))); - right: var(--region-right); - bottom: var(--region-bottom); - overflow: hidden; - overflow-wrap: break-word; - box-sizing: border-box; -} -:where(.vds-captions [data-part=region][data-active]) { -} -:where(.vds-captions [data-part=region][data-scroll=up]) { - justify-content: end; -} -:where(.vds-captions [data-part=region][data-active][data-scroll=up]) { - transition: top 0.433s; -} -:where(.vds-captions [data-part=region] > [data-part=cue-display]) { - position: relative; - width: auto; - left: var(--cue-offset); - height: var(--cue-height, auto); - text-align: var(--cue-text-align); - unicode-bidi: plaintext; - margin-top: 2px; -} -:where(.vds-captions [data-part=region] [data-part=cue]) { - position: relative; - border-radius: 0px; -} -:where(.vds-chapter-title) { - --color: var(--media-chapter-title-color, rgba(255 255 255 / 0.64)); - display: inline-block; - font-family: var(--media-font-family, sans-serif); - font-size: var(--media-chapter-title-font-size, 16px); - font-weight: var(--media-chapter-title-font-weight, 400); - color: var(--color); - flex: 1 1 0%; - padding-inline: 6px; - overflow: hidden; - text-align: start; - white-space: nowrap; - text-overflow: ellipsis; -} -.vds-chapter-title::before { - content: var(--media-chapter-title-separator, "\2022"); - display: inline-block; - margin-right: var(--media-chapter-title-separator-gap, 6px); - color: var(--media-chapter-title-separator-color, var(--color)); -} -.vds-chapter-title:empty::before { - content: ""; - margin: 0; -} -:where(.vds-controls), -:where(.vds-controls-group) { - position: relative; - display: inline-block; - width: 100%; - box-sizing: border-box; -} -:where([data-view-type=audio] .vds-controls) { - display: inline-block; - max-width: 100%; -} -:where([data-view-type=video] .vds-controls) { - display: flex; - position: absolute; - flex-direction: column; - inset: 0; - width: 100%; - height: 100%; - z-index: 10; - opacity: 0; - visibility: hidden; - pointer-events: none; - padding: var(--media-controls-padding, 0px); - transition: var(--media-controls-out-transition, opacity 0.2s ease-out); -} -:where([data-view-type=video] .vds-controls[data-visible]) { - opacity: 1; - visibility: visible; - transition: var(--media-controls-in-transition, opacity 0.2s ease-in); -} -:where(.vds-controls-spacer) { - flex: 1 1 0%; - pointer-events: none; -} -:where(.vds-gestures) { - display: contents; -} -:where(.vds-gesture) { - position: absolute; - display: block; - contain: content; - z-index: 0; - opacity: 0; - visibility: hidden; - pointer-events: none !important; -} -:where(.vds-icon svg) { - display: block; - width: 100%; - height: 100%; - vertical-align: middle; -} -:where(.vds-kb-action.hidden) { - opacity: 0; -} -:where(.vds-kb-text-wrapper) { - text-align: center; - position: absolute; - left: 0; - right: 0; - top: var(--media-kb-text-top, 10%); - z-index: 20; - pointer-events: none; -} -:where(.vds-kb-text) { - display: inline-block; - font-size: var(--media-kb-text-size, 150%); - font-family: var(--media-font-family, sans-serif); - backdrop-filter: blur(2px); - border-radius: var(--media-kb-border-radius, 2.5px); - pointer-events: none; -} -.vds-kb-text { - color: var(--media-kb-text-color, var(--default-color)); - background-color: var(--media-kb-text-bg, var(--default-bg)); - padding: var(--media-kb-text-padding, 10px 20px); -} -.light .vds-kb-text { - --default-color: #1a1a1a; - --default-bg: rgb(240 240 240 / 0.6); -} -.dark .vds-kb-text { - --default-color: #f5f5f5; - --default-bg: rgb(10 10 10 / 0.6); -} -:where(.vds-kb-text:empty) { - display: none; -} -:where(.vds-kb-bezel) { - --size: var(--media-kb-bezel-size, 52px); - position: absolute; - left: 50%; - top: 45%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: var(--size); - height: var(--size); - margin-left: calc(-1 * calc(var(--size) / 2)); - margin-right: calc(-1 * calc(var(--size) / 2)); - z-index: 20; - backdrop-filter: blur(2px); - background-color: var(--media-kb-bezel-bg, var(--default-bg)); - animation: var(--media-kb-bezel-animation, vds-bezel-fade 0.35s linear 1 normal forwards); - border-radius: var(--media-kb-bezel-border-radius, calc(var(--size) / 2)); - pointer-events: none; -} -.vds-kb-bezel:not(:has(svg)) { - display: none !important; -} -.light .vds-kb-bezel { - --default-bg: rgb(255 255 255 / 0.6); -} -.dark .vds-kb-bezel { - --default-bg: rgb(10 10 10 / 0.6); -} -@media (prefers-reduced-motion) { - :where(.vds-kb-bezel) { - animation: none; - } -} -:where(.vds-kb-bezel:has(slot:empty)) { - opacity: 0; -} -:where(.vds-kb-action[data-action=seek-forward] .vds-kb-bezel) { - top: 45%; - left: unset; - right: 10%; -} -:where(.vds-kb-action[data-action=seek-backward] .vds-kb-bezel) { - top: 45%; - left: 10%; -} -:where(.vds-kb-icon) { - --size: var(--media-kb-icon-size, 38px); - width: var(--size); - height: var(--size); -} -.vds-kb-icon { - color: var(--media-kb-icon-color, var(--default-color)); -} -.light .vds-kb-icon { - --default-color: #1a1a1a; -} -.dark .vds-kb-icon { - --default-color: #f5f5f5; -} -@keyframes vds-bezel-fade { - 0% { - opacity: 1; - } - 100% { - opacity: 0; - transform: scale(2); - } -} -:where(.vds-menu-items) { - --color-inverse: var(--media-menu-color-inverse, var(--default-inverse)); - --color-gray-50: var(--media-menu-color-gray-50, var(--default-gray-50)); - --color-gray-100: var(--media-menu-color-gray-100, var(--default-gray-100)); - --color-gray-200: var(--media-menu-color-gray-200, var(--default-gray-200)); - --color-gray-300: var(--media-menu-color-gray-300, var(--default-gray-300)); - --color-gray-400: var(--media-menu-color-gray-400, var(--default-gray-400)); - --text-color: var(--media-menu-text-color, var(--default-text)); - --text-secondary-color: var(--media-menu-text-secondary-color, var(--default-text-secondary)); - --root-border: var(--media-menu-border, var(--default-root-border)); -} -.light .vds-menu-items { - --default-inverse: black; - --default-gray-50: rgb(80 80 80 / 0.15); - --default-gray-100: rgb(80 80 80 / 0.45); - --default-gray-200: rgb(235 235 235 / 0.6); - --default-gray-300: rgb(238 238 238); - --default-gray-400: rgb(250 250 250); - --default-text: #1a1a1a; - --default-text-secondary: #6b6b6b; - --default-root-border: 1px solid rgb(10 10 10 / 0.1); -} -.dark .vds-menu-items { - --default-inverse: white; - --default-gray-50: rgb(245 245 245 / 0.1); - --default-gray-100: rgb(245 245 245 / 0.45); - --default-gray-200: rgb(10 10 10 / 0.6); - --default-gray-300: rgb(27 27 27); - --default-gray-400: rgb(10 10 10); - --default-text: #f5f5f5; - --default-text-secondary: #8a8a8a; - --default-root-border: 1px solid rgb(255 255 255 / 0.1); -} -:where(.vds-menu-items) { - --font-family: var(--media-font-family, sans-serif); - --font-size: var(--media-menu-font-size, 14px); - --font-weight: var(--media-menu-font-weight, 500); - --root-bg: var(--media-menu-bg, var(--color-gray-400)); - --root-padding: var(--media-menu-padding, 12px); - --root-border-radius: var(--media-menu-border-radius, 4px); - --divider: var(--media-menu-divider, 1px solid var(--color-gray-50)); - --section-bg: var(--media-menu-section-bg, var(--color-gray-300)); - --section-border: var(--media-menu-section-border); - --section-divider: var(--media-menu-section-divider, var(--divider)); - --top-bar-bg: var(--media-menu-top-bar-bg, var(--color-gray-200)); - --top-bar-divider: var(--media-menu-divider, transparent); - --text-hint-color: var(--media-menu-hint-color, var(--text-secondary-color)); - --chapter-divider: var(--media-chapters-divider, var(--divider)); - --chapter-active-bg: var(--media-chapters-item-active-bg, var(--color-gray-50)); - --chapter-active-border-left: var(--media-chapters-item-active-border-left); - --chapter-progress-bg: var(--media-chapters-progress-bg, var(--color-inverse)); - --chapter-time-font-size: var(--media-chapters-time-font-size, 12px); - --chapter-time-font-weight: var(--media-chapters-time-font-weight, 500); - --chapter-time-gap: var(--media-chapters-time-gap, 6px); - --chapter-duration-bg: var(--media-chapters-duration-bg); - --item-border: var(--media-menu-item-border, 0); - --item-bg: var(--media-menu-item-bg, transparent); - --item-hover-bg: var(--media-menu-item-hover-bg, var(--color-gray-50)); - --item-icon-size: var(--media-menu-item-icon-size, 18px); - --item-padding: var(--media-menu-item-padding, 10px); - --item-min-height: var(--media-menu-item-height, 40px); - --item-border-radius: var(--media-menu-item-border-radius, 2px); - --scrollbar-track-bg: var(--media-menu-scrollbar-track-bg, transparent); - --scrollbar-thumb-bg: var(--media-menu-scrollbar-thumb-bg, var(--color-gray-50)); - --webkit-scrollbar-bg: var(--color-gray-400); - --webkit-scrollbar-track-bg: var(--media-menu-scrollbar-track-bg, var(--color-gray-50)); - --checkbox-bg: var(--media-menu-checkbox-bg, var(--color-gray-100)); - --checkbox-active-bg: var(--media-menu-checkbox-bg-active, #1ba13f); - --checkbox-handle-bg: var(--media-menu-checkbox-handle-bg, #f5f5f5); - --checkbox-handle-border: var(--media-menu-checkbox-handle-border); - --radio-icon-color: var(--media-menu-radio-icon-color, var(--text-color)); -} -:where(.vds-menu[data-root] media-menu[data-root]) { - display: contents; -} -:where(.vds-menu) { - font-family: var(--font-family); - font-size: var(--font-size); - font-weight: var(--font-weight); -} -:where(.vds-menu[data-disabled][data-root]) { - display: none; -} -:where(.vds-menu[data-submenu]) { - display: inline-block; -} -:where(.vds-menu-items:focus) { - outline: none; -} -:where(.vds-menu-item:focus, .vds-radio:focus) { - outline: none; -} -:where(.vds-menu-item:focus-visible, .vds-menu-item[data-focus], .vds-radio:focus-visible, .vds-radio[data-focus]) { - outline: none; - box-shadow: var(--media-focus-ring); -} -:where(.vds-menu[data-open] .vds-tooltip-content) { - display: none !important; -} -.vds-menu-items [data-hidden] { - display: none !important; -} -@media (prefers-reduced-motion: no-preference) { - :where(.vds-menu-items) { - scroll-behavior: smooth; - } -} -:where(.vds-menu-items) { - box-sizing: border-box; - min-width: var(--media-menu-min-width, 280px); - scrollbar-width: thin; - scrollbar-color: var(--scrollbar-thumb-bg) var(--scrollbar-track-bg); - transform: translate3d(0, 0, 0); -} -:where(.vds-menu-items)::-webkit-scrollbar { - background-color: var(--webkit-scrollbar-bg); - border-radius: var(--root-border-radius); - height: 6px; - width: 5px; -} -:where(.vds-menu-items)::-webkit-scrollbar-track { - background-color: var(--webkit-scrollbar-track-bg); - border-radius: 4px; -} -:where(.vds-menu-items)::-webkit-scrollbar-thumb { - background-color: var(--scrollbar-thumb-bg); - border-radius: 4px; -} -:where(.vds-menu-items)::-webkit-scrollbar-corner { - background-color: var(--scrollbar-thumb-bg); -} -:where(.vds-menu-button) { - outline: none; - box-sizing: border-box; -} -:where(.vds-menu-button .vds-rotate-icon) { - transition: transform 0.2s ease-out; -} -:where(.vds-menu-button[aria-expanded=true] .vds-rotate-icon) { - transform: rotate(var(--media-menu-icon-rotate-deg, 90deg)); - transition: transform 0.2s ease-in; -} -:where(.vds-menu-button) { - display: inline-flex; - align-items: center; - justify-content: center; -} -@media (prefers-reduced-motion) { - :where(.vds-menu-button .vds-rotate-icon) { - transition: unset; - } -} -:where(.vds-menu-items) { - display: flex; - align-items: center; - flex-direction: column; - font-family: var(--font-family); - font-size: var(--font-size); - font-weight: var(--font-weight); - transition: height 0.35s ease; -} -@media (prefers-reduced-motion) { - :where(.vds-menu-items) { - transition: unset; - } -} -:where(.vds-menu-items[data-root]) { - background-color: var(--root-bg); - border-radius: var(--root-border-radius); - box-shadow: var(--media-menu-box-shadow); - backdrop-filter: blur(4px); - height: var(--menu-height, auto); - will-change: width, height; - overflow-y: auto; - overscroll-behavior: contain; - opacity: 0; - z-index: 9999999; - box-sizing: border-box; - max-height: var(--media-menu-max-height, 250px); - filter: var( --media-menu-filter, drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06)) ); -} -.vds-menu-items[data-root] { - border: var(--root-border); - padding: var(--root-padding); -} -:where([data-view-type=video]) :where(.vds-menu-items[data-root]) { - max-height: var(--media-menu-video-max-height, calc(var(--player-height) * 0.7)); -} -:where(.vds-menu-items[data-transition=height]) { - --scrollbar-thumb-bg: rgba(0, 0, 0, 0); - pointer-events: none; - overflow: hidden; -} -.vds-menu-button[aria-disabled=true], -.vds-menu-item[aria-disabled=true], -.vds-menu-item[data-disabled] { - display: none; -} -:where(.vds-menu-items[data-root]) { - --enter-transform: translateY(0px); - --exit-transform: translateY(12px); -} -:where(.vds-menu-items[data-root]:not([data-placement])) { - --enter-transform: translateY(-24px); -} -:where(.vds-menu-items[data-root][aria-hidden=true]) { - animation: var(--media-menu-exit-animation, vds-menu-exit 0.2s ease-out); -} -:where(.vds-menu-items[data-root][aria-hidden=false]) { - animation: var(--media-menu-enter-animation, vds-menu-enter 0.3s ease-out); - animation-fill-mode: forwards; -} -:where(.vds-menu-items[data-placement~=bottom]) { - --enter-transform: translateY(0); - --exit-transform: translateY(-12px); -} -@keyframes vds-menu-enter { - from { - opacity: 0; - transform: var(--exit-transform); - } - to { - opacity: 1; - transform: var(--enter-transform); - } -} -@keyframes vds-menu-exit { - from { - opacity: 1; - transform: var(--enter-transform); - } - to { - opacity: 0; - transform: var(--exit-transform); - } -} -@media (prefers-reduced-motion) { - :where(.vds-menu-items) { - animation: none; - opacity: 1; - } -} -:where(media-menu-portal) { - display: contents; -} -:where(.vds-menu-items[data-root]:not([data-placement])) { - position: fixed; - left: 16px; - right: 16px; - top: unset; - bottom: 0; - max-height: var(--media-sm-menu-portrait-max-height, 40vh); - max-height: var(--media-sm-menu-portrait-max-height, 40dvh); -} -:where(.vds-menu-items[data-root]:not([data-placement])) { - max-width: 480px; - margin: 0 auto; -} -@media (orientation: landscape) and (pointer: coarse) { - :where(.vds-menu-items[data-root]:not([data-placement])) { - max-height: var(--media-sm-menu-landscape-max-height, min(70vh, 400px)); - max-height: var(--media-sm-menu-landscape-max-height, min(70dvh, 400px)); - } -} -:where(.vds-menu[data-submenu] .vds-menu-button) { - display: flex; - align-items: center; - justify-content: flex-start; -} -:where(.vds-menu-items[data-submenu]) { - width: 100%; -} -:where(.vds-menu[aria-hidden=true]), -:where(.vds-menu-items[data-submenu][aria-hidden=true]) { - display: none; -} -:where(.vds-menu-item, .vds-radio) { - position: relative; - -webkit-tap-highlight-color: transparent; - user-select: none; - display: flex; - align-items: center; - justify-content: left; - width: 100%; - appearance: none; - border: 0; - border-radius: var(--item-border-radius); - box-sizing: border-box; - min-height: var(--item-min-height); - font-size: var(--font-size); - outline: none; -} -.vds-menu-item, -.vds-radio { - color: var(--text-color); - background-color: var(--item-bg); - padding: var(--item-padding); -} -.vds-menu-item:focus-visible, -.vds-menu-item[data-focus], -.vds-radio:focus-visible, -.vds-radio[data-focus] { - cursor: pointer; - background-color: var(--item-hover-bg); -} -@media (hover: hover) and (pointer: fine) { - .vds-menu-item[role]:hover, - .vds-radio:hover { - cursor: pointer; - background-color: var(--item-hover-bg); - } -} -:where(.vds-menu-items[data-submenu]) { - align-items: flex-start; - justify-content: center; - flex-direction: column; -} -:where(.vds-menu-item[aria-expanded=true]) { - font-weight: bold; - border-radius: 0; - border-top-left-radius: var(--item-border-radius); - border-top-right-radius: var(--item-border-radius); -} -.vds-menu-item[aria-expanded=true] { - border-bottom: var(--top-bar-divider); -} -:where(.vds-menu-item[aria-expanded=true]) { - position: sticky; - top: calc(-1 * var(--root-padding)); - left: 0; - width: 100%; - z-index: 10; - backdrop-filter: blur(4px); - margin-bottom: 4px; -} -.vds-menu-item[aria-expanded=true] { - background-color: var(--top-bar-bg); -} -:where(.vds-menu-item-label) { - flex: 1 0 0%; - text-align: start; -} -:where(.vds-menu-item .vds-icon, .vds-radio .vds-icon) { - --size: var(--item-icon-size); - width: var(--size); - height: var(--size); - margin-right: var(--media-menu-item-icon-spacing, 6px); -} -:where(.vds-menu-open-icon, .vds-menu-close-icon) { - --size: var(--media-menu-arrow-icon-size, 18px); - width: var(--size); - height: var(--size); -} -:where(.vds-menu-item-hint, .vds-menu-open-icon, .vds-radio-hint) { - color: var(--text-hint-color); - font-size: var(--media-menu-hint-font-size, 13px); - font-weight: var(--media-menu-hint-font-weight, 400); -} -:where(.vds-menu-items .vds-menu-open-icon) { - margin-right: 0; -} -:where(.vds-menu-items) :where(.vds-menu-item-hint, .vds-menu-open-icon) { - margin-left: auto; -} -:where(.vds-menu-items) :where(.vds-menu-item-hint + .vds-menu-open-icon), -:where(.vds-menu-item-hint + media-icon .vds-menu-open-icon), -:where(.vds-menu-item-hint + slot > .vds-menu-open-icon) { - margin-left: 2px; -} -:where(.vds-menu-item[aria-hidden=true]), -:where(.vds-menu-item[aria-expanded=true] .vds-menu-open-icon) { - display: none !important; -} -:where(.vds-menu-items) :where(.vds-menu-item[aria-disabled=true], .vds-menu-item[data-disabled]) :where(.vds-menu-open-icon) { - opacity: 0; -} -:where(.vds-menu-close-icon), -:where(.vds-menu-item[aria-expanded=true] > .vds-icon) { - display: none !important; -} -:where(.vds-menu-item[aria-expanded=true] .vds-menu-close-icon) { - display: inline !important; - margin-left: calc(-1 * var(--item-padding) / 2); -} -:where(.vds-menu-checkbox) { - --checkbox-width: var(--media-menu-checkbox-width, 40px); - --checkbox-height: var(--media-menu-checkbox-height, 18px); - --checkbox-top: calc((var(--checkbox-height) - var(--checkbox-diameter)) / 2); - --checkbox-diameter: var( --media-menu-checkbox-handle-diameter, calc(var(--checkbox-height) - 2px) ); - --checkbox-gap: var(--media-menu-checkbox-gap, 2.5px); - position: relative; - display: inline-block; - width: var(--checkbox-width); - height: var(--checkbox-height); - border-radius: calc(var(--checkbox-height) / 2); - transition: 0.3s all ease-in-out; - box-sizing: border-box; - cursor: pointer; - pointer-events: auto; -} -.vds-menu-checkbox { - background-color: var(--checkbox-bg); -} -:where(.vds-menu-checkbox:focus-visible) { - outline: none; - box-shadow: var(--media-focus-ring); -} -.vds-menu-checkbox[aria-checked=true] { - background-color: var(--checkbox-active-bg); -} -:where(.vds-menu-checkbox)::after { - content: ""; - display: inline-block; - width: var(--checkbox-diameter); - height: var(--checkbox-diameter); - border-radius: calc(var(--checkbox-diameter) / 2); - position: absolute; - top: var(--checkbox-top); - transform: translateX(var(--checkbox-gap)); - transition: 0.3s all ease-in-out; - border: var(--checkbox-handle-border); - box-sizing: border-box; -} -.vds-menu-checkbox::after { - background-color: var(--checkbox-handle-bg); -} -:where(.vds-menu-checkbox[aria-checked=true])::after { - transform: translateX(calc(var(--checkbox-width) - var(--checkbox-diameter) - var(--checkbox-gap))); -} -@media (prefers-reduced-motion: no-preference) { - :where(.vds-menu-checkbox[data-active])::after { - width: calc(var(--checkbox-width) - calc(var(--checkbox-gap) * 2)); - } -} -:where(.vds-menu-checkbox[aria-checked=true][data-active])::after { - transform: translateX(var(--checkbox-gap)); -} -:where(.vds-menu-items .vds-slider) { - --media-slider-track-bg: var(--media-menu-slider-track-bg, var(--color-gray-50)); - --media-slider-track-fill-bg: var(--media-menu-slider-track-fill-bg, var(--color-inverse)); - --media-slider-height: var(--media-menu-slider-height, 32px); - --track-focus-height: var(--track-height) !important; -} -:where(.vds-menu-items .vds-slider-thumb) { - opacity: 1 !important; -} -:where(.vds-menu-slider-item.group) { - flex-direction: column; -} -:where(.vds-menu-slider-title) { - margin-top: 4px; -} -:where(.vds-menu-slider-body) { - width: 100%; - display: flex; - align-items: center; - margin-top: 6px; -} -:where(.vds-menu-slider-item .vds-icon) { - margin: 0; - color: var(--text-hint-color); -} -:where(.vds-menu-slider-item[data-min] .vds-icon.down, .vds-menu-slider-item[data-max] .vds-icon.up) { - color: var(--text-color); - animation: 0.6s ease-in-out vds-slider-icon; - transition: all 1.2s ease; -} -@keyframes vds-slider-icon { - 0% { - transform: scale(1); - } - 50% { - transform: scale(1.25); - } - 100% { - transform: scale(1); - } -} -:where(.vds-menu-items .vds-slider-track-fill) { - transition: opacity 0.3s ease; -} -:where(.vds-menu-items .vds-slider[data-active] .vds-slider-track-fill) { - opacity: 0; -} -:where(.vds-radio-group) { - box-sizing: border-box; - width: 100%; - display: flex; - flex-direction: column; -} -.vds-radio { - cursor: pointer; - contain: content; - padding-left: calc(var(--item-icon-size) + var(--item-padding)); -} -.vds-radio[aria-checked=true] { - padding-left: 0; -} -.vds-radio .vds-icon { - display: none; - color: var(--radio-icon-color); -} -.vds-radio[aria-checked=true] .vds-icon { - display: inline-block; - margin-left: 6px; -} -:where(.vds-radio-hint) { - margin-left: auto; -} -.vds-color-picker { - width: 32px; - height: 32px; - border: 0; - background-color: transparent; - outline: none; -} -.vds-color-picker::-webkit-color-swatch { - border-radius: 2px; -} -.vds-color-picker::-moz-color-swatch { - border-radius: 2px; -} -.vds-color-picker:focus-visible::-webkit-color-swatch { - box-shadow: var(--media-focus-ring); -} -.vds-color-picker:focus-visible::-moz-color-swatch { - box-shadow: var(--media-focus-ring); -} -:where(.vds-menu-section) { - width: 100%; -} -:where(.vds-menu-item + .vds-menu-section) { - margin-top: 8px; -} -:where(.vds-menu-section + .vds-menu-section) { - margin-top: 24px; -} -:where(.vds-menu-section:first-child) { - margin-top: 8px; -} -:where(.vds-menu-section:last-child) { - margin-bottom: 8px; -} -:where(.vds-menu-section-title), -:where(.vds-menu-slider-title) { - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - color: var(--text-secondary-color); - font-size: var(--media-menu-section-header-font-size, 12px); - font-weight: var(--media-menu-section-header-font-weight, 500); - padding-inline: 2px; -} -:where(.vds-menu-section-body) { - width: 100%; -} -:where(.vds-menu-section-title + .vds-menu-section-body) { - margin-top: var(--media-menu-section-gap, 8px); -} -.vds-menu-section-body { - background-color: var(--section-bg); - border: var(--section-border); - border-radius: var(--media-menu-section-border-radius, 2px); -} -:where(.vds-menu-section:not([data-open]) .vds-menu-item:not(:last-child)) { - border-bottom: var(--section-divider); -} -:where(.vds-menu-section-body .vds-menu:last-child > .vds-menu-item) { - border-bottom: unset; -} -.vds-menu-section[data-open], -.vds-menu-section[data-open] > .vds-menu-section-body { - display: contents !important; - background-color: transparent !important; -} -.vds-menu-section[data-open] > .vds-menu-section-title, -.vds-menu-section[data-open] > .vds-menu-section-body > :not([data-open]) { - display: none; -} -:where(.vds-chapters-menu-items) { - min-width: var(--media-chapters-min-width, var(--media-menu-min-width, 220px)); -} -.vds-chapters-menu-items { - padding: var(--media-chapters-padding, 0); -} -:where(.vds-menu-items:has(.vds-chapters-radio-group[data-thumbnails])) { - min-width: var(--media-chapters-with-thumbnails-min-width, 300px); -} -:where(.vds-chapter-radio) { - border-radius: 0; -} -.vds-chapter-radio { - border-bottom: var(--chapter-divider); - padding: var(--item-padding); -} -.vds-chapter-radio[aria-checked=true] { - padding-left: var(--item-padding); -} -:where(.vds-chapter-radio:last-child) { - border-bottom: 0; -} -.vds-chapter-radio[aria-checked=true] { - background-color: var(--chapter-active-bg); - border-left: var(--chapter-active-border-left); -} -:where(.vds-chapter-radio[aria-checked=true]):after { - content: " "; - width: var(--progress); - height: var(--media-chapters-progress-height, 4px); - position: absolute; - bottom: 0; - left: 0; -} -.vds-chapter-radio[aria-checked=true]:after { - border-radius: var(--media-chapters-progress-border-radius, 0); - background-color: var(--chapter-progress-bg); -} -.vds-chapters-radio-group :where(.vds-thumbnail) { - margin-right: var(--media-chapters-thumbnail-gap, 12px); - flex-shrink: 0; - min-width: var(--media-chapters-thumbnail-min-width, 100px); - min-height: var(--media-chapters-thumbnail-min-height, 56px); - max-width: var(--media-chapters-thumbnail-max-width, 120px); - max-height: var(--media-chapters-thumbnail-max-height, 68px); -} -.vds-chapters-radio-group .vds-thumbnail { - border: var(--media-chapters-thumbnail-border, 0); -} -:where(.vds-chapters-radio-group .vds-chapter-radio-label) { - color: var(--text-secondary-color); - font-size: var(--font-size); - font-weight: var(--font-weight); - white-space: nowrap; -} -:where(.vds-chapter-radio[aria-checked=true] .vds-chapter-radio-label) { - color: var(--text-color); -} -:where(.vds-chapters-radio-group .vds-chapter-radio-start-time) { - display: inline-block; - letter-spacing: var(--media-chapters-start-time-letter-spacing, 0.4px); - border-radius: var(--media-chapters-start-time-border-radius, 2px); - font-size: var(--chapter-time-font-size); - font-weight: var(--chapter-time-font-weight); - margin-top: var(--chapter-time-gap); -} -.vds-chapters-radio-group .vds-chapter-radio-start-time { - color: var(--text-secondary-color); - background-color: var(--section-bg); - padding: var(--media-chapters-start-time-padding, 1px 4px); -} -:where(.vds-chapters-radio-group .vds-chapter-radio-duration) { - color: var(--text-hint-color); - font-size: var(--chapter-time-font-size); - font-weight: var(--chapter-time-font-weight); - margin-top: var(--chapter-time-gap); -} -.vds-chapters-radio-group .vds-chapter-radio-duration { - background-color: var(--chapter-duration-bg); - border-radius: var(--media-chapters-duration-border-radius, 2px); -} -.vds-chapters-radio-group:not([data-thumbnails]) :where(.vds-thumbnail, media-thumbnail) { - display: none; -} -:where(.vds-chapter-radio-content) { - display: flex; - align-items: flex-start; - flex-direction: column; -} -:where(.vds-chapters-radio-group:not([data-thumbnails]) .vds-chapter-radio-content) { - width: 100%; - flex-direction: row; - display: flex; - flex-wrap: wrap; - align-items: center; -} -:where(.vds-chapters-radio-group:not([data-thumbnails]) .vds-chapter-radio-start-time) { - margin-top: 0; - margin-left: auto; -} -:where(.vds-chapters-radio-group:not([data-thumbnails]) .vds-chapter-radio-duration) { - margin-top: 4px; - flex-basis: 100%; -} -.vds-menu-items[data-keyboard] .vds-chapters-radio-group:focus-within { - padding: var(--media-chapters-focus-padding, 4px); -} -:where(.vds-poster) { - display: block; - contain: content; - position: absolute; - top: 50%; - transform: translateY(-50%); - left: 0; - opacity: 0; - width: 100%; - height: 100%; - z-index: 1; - border: 0; - pointer-events: none; - box-sizing: border-box; - transition: opacity 0.2s ease-out; - background-color: var(--media-poster-bg, black); -} -:where(.vds-poster img) { - object-fit: inherit; - object-position: inherit; - pointer-events: none; - user-select: none; - -webkit-user-select: none; - box-sizing: border-box; -} -.vds-poster :where(img) { - border: 0; - width: 100%; - height: 100%; - object-fit: contain; -} -:where(.vds-poster[data-hidden]) { - display: none; -} -:where(.vds-poster[data-visible]) { - opacity: 1; -} -.vds-poster:not(:defined), -.vds-poster img:not([src]) { - display: none; -} -:where(.vds-slider) { - --width: var(--media-slider-width, 100%); - --height: var(--media-slider-height, 48px); - --thumb-size: var(--media-slider-thumb-size, 15px); - --thumb-focus-size: var(--media-slider-focused-thumb-size, calc(var(--thumb-size) * 1.1)); - --track-width: var(--media-slider-track-width, 100%); - --track-height: var(--media-slider-track-height, 5px); - --track-focus-width: var(--media-slider-focused-track-width, var(--track-width)); - --track-focus-height: var(--media-slider-focused-track-height, calc(var(--track-height) * 1.25)); - display: inline-flex; - align-items: center; - width: var(--width); - height: var(--height); - margin: 0 calc(var(--thumb-size) / 2); - position: relative; - contain: layout style; - outline: none; - pointer-events: auto; - cursor: pointer; - user-select: none; - touch-action: none; - -webkit-user-select: none; - -webkit-tap-highlight-color: transparent; -} -:where(.vds-slider[aria-hidden=true]) { - display: none !important; -} -:where(.vds-slider[aria-disabled=true]) { - cursor: unset; -} -:where(.vds-slider:focus) { - outline: none; -} -:where(.vds-slider:not([data-chapters])[data-focus], .vds-slider:not([data-chapters]):focus-visible) :where(.vds-slider-track) { - box-shadow: var(--media-focus-ring); -} -:where(.vds-slider .vds-slider-track) { - z-index: 0; - position: absolute; - width: var(--track-width); - height: var(--track-height); - top: 50%; - left: 0; - border-radius: var(--media-slider-track-border-radius, 2px); - transform: translateY(-50%) translateZ(0); - background-color: var(--media-slider-track-bg, rgb(255 255 255 / 0.3)); - contain: strict; -} -:where(.vds-slider[data-focus], .vds-slider:focus-visible) :where(.vds-slider-track) { - outline-offset: var(--thumb-size); -} -:where(.vds-slider:not([data-chapters])[data-active] .vds-slider-track) { - width: var(--track-focus-width); - height: var(--track-focus-height); -} -:where(.vds-slider .vds-slider-track-fill) { - z-index: 2; - background-color: var(--media-slider-track-fill-bg, var(--media-brand)); - width: var(--slider-fill, 0%); - will-change: width; -} -:where(.vds-slider .vds-slider-thumb) { - position: absolute; - top: 50%; - left: var(--slider-fill); - opacity: 0; - contain: layout size style; - width: var(--thumb-size); - height: var(--thumb-size); - border: var(--media-slider-thumb-border, 1px solid #cacaca); - border-radius: var(--media-slider-thumb-border-radius, 9999px); - background-color: var(--media-slider-thumb-bg, #fff); - transform: translate(-50%, -50%) translateZ(0); - transition: opacity 0.15s ease-in; - pointer-events: none; - will-change: left; - z-index: 2; -} -:where(.vds-slider[data-dragging], .vds-slider[data-focus], .vds-slider:focus-visible) :where(.vds-slider-thumb) { - box-shadow: var(--media-slider-focused-thumb-shadow, 0 0 0 4px hsla(0, 0%, 100%, 0.4)); -} -:where(.vds-slider[data-active] .vds-slider-thumb) { - opacity: 1; - transition: var(--media-slider-thumb-transition, opacity 0.2s ease-in, box-shadow 0.2s ease); -} -:where(.vds-slider[data-dragging] .vds-slider-thumb) { - width: var(--thumb-focus-size); - height: var(--thumb-focus-size); -} -:where(.vds-slider-value) { - display: inline-block; - contain: content; - font-size: 14px; - font-family: var(--media-font-family, sans-serif); -} -:where(.vds-slider-thumbnail) { - display: block; - contain: content; - box-sizing: border-box; -} -:where(.vds-slider-video) { - background-color: black; - box-sizing: border-box; - contain: content; - display: inline-block; - border: var(--media-thumbnail-border, 1px solid white); -} -:where(.vds-slider-video video) { - display: block; - height: auto; - width: 156px; -} -:where(.vds-slider-video[data-loading]) { - opacity: 0; -} -:where(.vds-slider-video[data-hidden], .vds-slider-video[data-hidden] video) { - display: none; - width: 0px; -} -:where(.vds-slider .vds-slider-preview) { - display: flex; - flex-direction: column; - align-items: center; - opacity: 0; - background-color: var(--media-slider-preview-bg); - border-radius: var(--media-slider-preview-border-radius, 2px); - pointer-events: none; - transition: opacity 0.2s ease-out; - will-change: left, opacity; - contain: layout paint style; -} -:where(.vds-slider-preview[data-visible]) { - opacity: 1; - transition: opacity 0.2s ease-in; -} -.vds-slider-value { - background-color: var(--media-slider-value-bg, black); - border-radius: var(--media-slider-value-border-radius, 2px); - border: var(--media-slider-value-border); - color: var(--media-slider-value-color, white); - padding: var(--media-slider-value-padding, 1px 10px); -} -:where(.vds-slider-video:not([data-hidden]) + .vds-slider-chapter-title, .vds-slider-thumbnail:not([data-hidden]) + .vds-slider-chapter-title) { - margin-top: var(--media-slider-chapter-title-gap, 6px); -} -:where(.vds-slider-video:not([data-hidden]) + .vds-slider-value, .vds-slider-thumbnail:not([data-hidden]) + .vds-slider-value, .vds-slider-chapter-title + .vds-slider-value) { - margin-top: var(--media-slider-value-gap, 2px); -} -:where(.vds-slider[aria-orientation=vertical]) { - --width: var(--media-slider-width, 48px); - --height: var(--media-slider-height, 100%); - --track-width: var(--media-slider-track-width, 4px); - --track-height: var(--media-slider-track-height, 100%); - --track-focus-width: var(--media-slider-focused-track-width, calc(var(--track-width) * 1.25)); - --track-focus-height: var(--media-slider-focused-track-height, var(--track-height)); - margin: calc(var(--thumb-size) / 2) 0; -} -:where(.vds-slider[aria-orientation=vertical] .vds-slider-track) { - top: unset; - bottom: 0; - left: 50%; - transform: translateX(-50%) translateZ(0); -} -:where(.vds-slider[aria-orientation=vertical] .vds-slider-track-fill) { - width: var(--track-width); - height: var(--slider-fill); - will-change: height; - transform: translateX(-50%) translateZ(0); -} -:where(.vds-slider[aria-orientation=vertical] .vds-slider-progress) { - top: unset; - bottom: 0; - width: var(--track-width); - height: var(--slider-progress, 0%); - will-change: height; -} -:where(.vds-slider[aria-orientation=vertical] .vds-slider-thumb) { - top: unset; - bottom: var(--slider-fill); - left: 50%; - will-change: bottom; - transform: translate(-50%, 50%) translateZ(0); -} -:where(.vds-slider[aria-orientation=vertical] .vds-slider-preview) { - will-change: bottom, opacity; -} -:where([data-live] .vds-time-slider .vds-slider-track-fill) { - background-color: var(--media-slider-track-fill-live-bg, #dc2626); -} -:where(.vds-time-slider .vds-slider-progress) { - z-index: 1; - left: 0; - width: var(--slider-progress, 0%); - will-change: width; - background-color: var(--media-slider-track-progress-bg, rgb(255 255 255 / 0.5)); -} -:where([data-media-player]:not([data-can-play]) .vds-time-slider .vds-slider-value) { - display: none; -} -:where(.vds-slider-steps) { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - height: 100%; - position: absolute; - top: 0; - left: 0; -} -:where(.vds-slider-step) { - width: var(--media-slider-step-width, 2.5px); - height: calc(var(--track-height) + 1px); - background-color: var(--media-slider-step-color, rgb(124, 124, 124)); - opacity: 0; - transition: opacity 0.3s ease; -} -:where(.vds-slider[data-active] .vds-slider-step) { - opacity: 1; -} -:where(.vds-time-slider .vds-slider-chapters) { - position: relative; - display: flex; - align-items: center; - width: 100%; - height: 100%; - contain: layout style; - border-radius: var(--media-slider-track-border-radius, 1px); -} -:where(.vds-slider[data-focus], .vds-slider:focus-visible) :where(.vds-slider-chapters) { - box-shadow: var(--media-focus-ring); - height: var(--track-height); -} -:where(.vds-time-slider .vds-slider-chapter) { - margin-right: 2px; -} -:where(.vds-time-slider .vds-slider-chapter:last-child) { - margin-right: 0; -} -:where(.vds-time-slider .vds-slider-chapter) { - position: relative; - display: flex; - align-items: center; - width: 100%; - height: 100%; - will-change: height, transform; - contain: layout style; - border-radius: var(--media-slider-track-border-radius, 1px); -} -:where(.vds-time-slider .vds-slider-chapter .vds-slider-track-fill) { - width: var(--chapter-fill, 0%); - will-change: width; -} -:where(.vds-time-slider .vds-slider-chapter .vds-slider-progress) { - width: var(--chapter-progress, 0%); - will-change: width; -} -@media (hover: hover) and (pointer: fine) { - :where(.vds-time-slider:hover .vds-slider-chapters) { - contain: strict; - } - :where(.vds-time-slider .vds-slider-chapter:hover:not(:only-of-type)) { - transform: var(--media-slider-chapter-hover-transform, scaleY(2)); - transition: var( --media-slider-chapter-hover-transition, transform 0.1s cubic-bezier(0.4, 0, 1, 1) ); - } -} -:where(.vds-time-slider .vds-slider-chapter-title) { - font-family: var(--media-font-family, sans-serif); - font-size: var(--media-slider-chapter-title-font-size, 14px); - color: var(--media-slider-chapter-title-color, #f5f5f5); - background-color: var(--media-slider-chapter-title-bg); -} -:where(.vds-thumbnail) { - --min-width: var(--media-thumbnail-min-width, 140px); - --max-width: var(--media-thumbnail-max-width, 180px); - --aspect-ratio: var(--media-thumbnail-aspect-ratio, var(--thumbnail-aspect-ratio)); - display: block; - width: var(--thumbnail-width); - height: var(--thumbnail-height); - background-color: var(--media-thumbnail-bg, black); - contain: strict; - overflow: hidden; - box-sizing: border-box; - min-width: var(--min-width); - min-height: var(--media-thumbnail-min-height, calc(var(--min-width) / var(--aspect-ratio))); - max-width: var(--max-width); - max-height: var(--media-thumbnail-max-height, calc(var(--max-width) / var(--aspect-ratio))); -} -.vds-thumbnail { - border: var(--media-thumbnail-border, 1px solid white); -} -:where(.vds-thumbnail img) { - min-width: unset !important; - max-width: unset !important; - will-change: - width, - height, - transform; -} -:where(.vds-thumbnail[data-loading] img) { - opacity: 0; -} -:where(.vds-thumbnail[aria-hidden=true]) { - display: none !important; -} -:where(.vds-time-group) { - display: flex; - align-items: center; -} -.vds-time-divider { - margin: 0 var(--media-time-divider-gap, 2.5px); - color: var(--media-time-divider-color, #e0e0e0); -} -:where(.vds-time) { - display: inline-block; - contain: content; - font-size: var(--media-time-font-size, 15px); - font-weight: var(--media-time-font-weight, 400); - font-family: var(--media-font-family, sans-serif); - border-radius: var(--media-time-border-radius, 2px); - letter-spacing: var(--media-time-letter-spacing, 0.025em); -} -.vds-time { - outline: 0; - color: var(--media-time-color, var(--default-color)); - background-color: var(--media-time-bg); - border: var(--media-time-border); - padding: var(--media-time-padding, 2px); -} -:where(.vds-time:focus-visible) { - box-shadow: var(--media-focus-ring); -} -.light .vds-time { - --default-color: rgb(10 10 10); -} -.dark .vds-time { - --default-color: #f5f5f5; -} -:where(.vds-tooltip, media-tooltip) { - display: contents; -} -:where(.vds-tooltip-content) { - display: inline-block; - box-sizing: border-box; - font-family: var(--media-font-family, sans-serif); - font-size: var(--media-tooltip-font-size, 13px); - font-weight: var(--media-tooltip-font-weight, 500); - opacity: 0; - pointer-events: none; - white-space: nowrap; - z-index: 10; - will-change: transform, opacity; -} -.vds-tooltip-content { - border-radius: var(--media-tooltip-border-radius, 2px); - background-color: var(--media-tooltip-bg-color, var(--default-bg)); - border: var(--media-tooltip-border, var(--default-border)); - color: var(--media-tooltip-color, var(--default-color)); - padding: var(--media-tooltip-padding, 2px 8px); -} -.light .vds-tooltip-content { - --default-color: #1a1a1a; - --default-bg: white; - --default-border: 1px solid rgb(0 0 0 / 0.2); -} -.dark .vds-tooltip-content { - --default-color: #f5f5f5; - --default-bg: black; - --default-border: 1px solid rgb(255 255 255 / 0.1); -} -:where(.vds-menu .vds-menu-button[role=button][data-pressed] .vds-tooltip-content) { - opacity: 0; - display: none; -} -:where(.vds-tooltip-content) { - --enter-transform: translateY(0px) scale(1); - --exit-transform: translateY(12px) scale(0.8); -} -:where(.vds-tooltip-content:not([data-visible])) { - animation: var(--media-tooltip-exit-animation, vds-tooltip-exit 0.2s ease-out); -} -:where(.vds-tooltip-content[data-visible]) { - animation: var(--media-tooltip-enter-animation, vds-tooltip-enter 0.2s ease-in); - animation-fill-mode: forwards; -} -:where(.vds-tooltip-content[data-placement~=bottom]) { - --enter-transform: translateY(0) scale(1); - --exit-transform: translateY(-12px) scale(0.8); -} -:where(.vds-tooltip-content[data-placement~=left]) { - --enter-transform: translateX(0) scale(1); - --exit-transform: translateX(12px) scale(0.8); -} -:where(.vds-tooltip-content[data-placement~=right]) { - --enter-transform: translateX(0) scale(1); - --exit-transform: translateX(-12px) scale(0.8); -} -@keyframes vds-tooltip-enter { - from { - opacity: 0; - transform: var(--exit-transform); - } - to { - opacity: 1; - transform: var(--enter-transform); - } -} -@keyframes vds-tooltip-exit { - from { - opacity: 1; - transform: var(--enter-transform); - } - to { - opacity: 0; - transform: var(--exit-transform); - } -} -@media (prefers-reduced-motion) { - :where(.vds-tooltip-content) { - animation: none; - } - :where(.vds-tooltip-content[data-visible]) { - opacity: 1; - } -} -[data-media-player]:not([data-paused]) .vds-play-tooltip-text, -[data-media-player][data-paused] .vds-pause-tooltip-text, -[data-media-player][data-pip] .vds-pip-enter-tooltip-text, -[data-media-player]:not([data-pip]) .vds-pip-exit-tooltip-text, -[data-media-player][data-fullscreen] .vds-fs-enter-tooltip-text, -[data-media-player]:not([data-fullscreen]) .vds-fs-exit-tooltip-text, -[data-media-player]:not([data-captions]) .vds-cc-on-tooltip-text, -[data-media-player][data-captions] .vds-cc-off-tooltip-text, -[data-media-player]:not([data-muted]) .vds-mute-tooltip-text, -[data-media-player][data-muted] .vds-unmute-tooltip-text { - display: none; -} - -/* node_modules/vidstack/player/styles/default/layouts/video.css */ -[data-media-player] .vds-video-layout:not([data-match]) { - display: none !important; -} -[data-media-player][data-layout=video] { - background-color: var(--video-bg, black); -} -[data-media-player][data-layout=video]:not([data-fullscreen]) { - border-radius: var(--video-border-radius, 6px); - border: var(--video-border, 1px solid rgb(255 255 255 / 0.1)); -} -:where(.vds-video-layout) { - --media-brand: var(--video-brand, #f5f5f5); - --media-font-family: var(--video-font-family, sans-serif); - --media-controls-color: var(--video-controls-color, #f5f5f5); - --media-tooltip-y-offset: 6px; - --media-menu-y-offset: 6px; - --media-focus-ring-color: var(--video-focus-ring-color, rgb(78 156 246)); - --media-focus-ring: var(--video-focus-ring, 0 0 0 3px var(--media-focus-ring-color)); - color: var(--video-controls-color, #f5f5f5); - display: contents; -} -:where([data-media-player][data-focus]:not([data-playing]) .vds-video-layout .vds-controls) { - border-radius: var(--video-border-radius, 6px); - box-shadow: var(--media-focus-ring); -} -:where(.vds-video-layout .vds-controls[data-visible]) { - border-radius: var(--video-border-radius, 6px); - background-image: - linear-gradient( - to top, - rgb(0 0 0 / 0.6), - 10%, - transparent, - 95%, - rgb(0 0 0 / 0.3)); -} -.vds-video-layout .vds-controls-group { - align-items: center; - display: flex; - pointer-events: auto; - z-index: 0; - padding: 4px 6px; -} -.vds-video-layout .vds-controls-group:first-child { - z-index: 50; -} -.vds-video-layout .vds-controls-group:nth-last-child(2) { - padding: 0 12px; - z-index: 11; - margin-bottom: -16px; -} -.vds-video-layout:not([data-sm]) .vds-controls-group:last-child { - --media-menu-y-offset: 26px; - --media-tooltip-y-offset: 26px; - --media-slider-preview-offset: 26px; - z-index: 10; -} -:where(.vds-video-layout .vds-button) { - margin-right: 2.5px; -} -:where(.vds-video-layout[data-sm] .vds-chapter-title) { - font-size: var(--video-sm-chapter-title-font-size, 15px); -} -:where([data-fullscreen] .vds-video-layout .vds-chapter-title) { - font-size: var(--video-fullscreen-chapter-title-font-size, 16px); -} -:where(.vds-video-layout:not([data-sm]) .vds-mute-button) { - margin-left: -2.5px; - margin-right: -5px; -} -:where(.vds-video-layout[data-sm]) { - --media-button-size: var(--video-sm-button-size, 36px); -} -:where(.vds-video-layout .vds-time-slider) { - --media-slider-height: 45px; - flex-grow: 0; -} -:where(.vds-video-layout .vds-slider-thumbnail) { - --media-thumbnail-border: var(--video-slider-thumbnail-border, 1px solid #f5f5f5); - border-radius: var(--video-slider-thumbnail-border-radius, 2px); -} -.vds-video-layout .vds-time-slider .vds-slider-value { - background-color: var(--video-time-bg, unset); - text-shadow: - -1px -1px 0 #333333, - 1px -1px 0 #333333, - -1px 1px 0 #333333, - 1px 1px 0 #333333; -} -:where(.vds-video-layout[data-sm] .vds-time) { - text-shadow: unset; -} -:where(.vds-video-layout[data-lg] .vds-volume) { - --gap: var(--video-volume-gap, 10px); - display: contents; -} -:where(.vds-video-layout[data-lg] .vds-volume-popup) { - display: contents; -} -:where(.vds-video-layout[data-lg] .vds-volume-slider) { - margin: 0; - max-width: 0; - transition: all 0.15s ease; -} -:where(.vds-video-layout[data-lg] .vds-volume[data-active] .vds-volume-slider), -:where(.vds-video-layout[data-lg] .vds-volume:has([data-active]) .vds-volume-slider) { - margin-left: var(--gap); - opacity: 1; - visibility: visible; - max-width: var(--video-volume-slider-max-width, 72px); -} -.vds-video-layout[data-lg] .vds-volume-slider::after { - content: ""; - position: fixed; - top: 0; - left: calc(-1 * var(--gap)); - width: var(--gap); - height: 100%; - z-index: 1; - pointer-events: auto; -} -:where(.vds-video-layout[data-sm] .vds-volume) { - --media-slider-height: var(--video-volume-height, 96px); - --media-slider-preview-offset: calc(-200% - 6px); - --gap: var(--video-volume-gap, 10px); - position: relative; - display: flex; - align-items: center; - justify-content: center; -} -:where(.vds-video-layout[data-sm] .vds-volume-popup) { - display: block; - position: absolute; - top: calc(100% + var(--gap)); - left: 50%; - opacity: 0; - transform: translateX(-50%); - transition: opacity 150ms ease-out, visibility 150ms ease-out; - border-radius: var(--video-volume-border-radius, 8px); - filter: var(--media-volume-filter, drop-shadow(0 1px 1px rgb(0 0 0 / 0.05))); - visibility: hidden; -} -.vds-video-layout[data-sm] .vds-mute-button::after { - content: ""; - position: fixed; - bottom: calc(-1 * var(--gap)); - right: 0; - width: 100%; - height: var(--gap); - z-index: 1; - pointer-events: auto; -} -.vds-video-layout .vds-volume-popup { - background-color: var(--video-volume-bg, var(--media-menu-bg, var(--default-bg))); - border: var(--video-volume-border, var(--default-border)); -} -.light .vds-video-layout .vds-volume-popup, -.vds-video-layout.light .vds-volume-popup { - --default-bg: rgb(250 250 250); - --default-border: 1px solid rgb(10 10 10 / 0.1); -} -.dark .vds-video-layout .vds-volume-popup, -.vds-video-layout.dark .vds-volume-popup { - --default-bg: rgb(10 10 10); - --default-border: 1px solid rgb(255 255 255 / 0.1); -} -:where(.vds-video-layout[data-sm] .vds-volume[data-active] .vds-volume-popup), -:where(.vds-video-layout[data-sm] .vds-volume:has([data-active]) .vds-volume-popup) { - transition: opacity 150ms ease-in, visibility 150ms ease-in; - opacity: 1; - visibility: visible; -} -:where(.vds-video-layout[data-sm] .vds-volume[data-active] .vds-tooltip-content) { - display: none !important; -} -:where(.vds-video-layout .vds-time[data-type=current]) { - margin-right: 2px; -} -:where(.vds-video-layout .vds-time[data-type=current][remainder]) { - margin-left: 2px; -} -.vds-video-layout .vds-time { - --default-color: #f5f5f5 !important; -} -:where([data-preview] .vds-video-layout .vds-captions) { - opacity: 0; -} -:where(.vds-video-layout .vds-captions) { - z-index: 10; - transition: var(--video-captions-transition, bottom 0.3s ease-in-out); -} -@media (min-width: 980px) { - :where([data-fullscreen] .vds-video-layout .vds-captions) { - bottom: var(--video-lg-fullscreen-captions-offset, 54px); - } -} -:where([data-media-player][data-controls] .vds-video-layout .vds-captions) { - bottom: var(--video-captions-offset, 78px); -} -:where([data-media-player][data-controls] .vds-video-layout[data-sm] .vds-captions) { - bottom: var(--video-sm-captions-offset, 48px); -} -:where(.vds-video-layout .vds-time-slider .vds-slider-chapter-title) { - width: 100%; - text-align: center; - text-shadow: - -1px -1px 0 #212121, - 1px -1px 0 #212121, - -1px 1px 0 #212121, - 1px 1px 0 #212121; -} -:where(.vds-video-layout .vds-gesture) { - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 0; -} -:where(.vds-video-layout .vds-gesture[action="seek:-10"]) { - width: var(--video-gesture-seek-width, 20%); - z-index: 1; -} -:where(.vds-video-layout .vds-gesture[action="seek:10"]) { - left: unset; - right: 0; - width: var(--video-gesture-seek-width, 20%); - z-index: 1; -} -@media (pointer: coarse) { - :where(.vds-video-layout .vds-gesture[action="toggle:paused"]) { - display: none; - } -} -@media not (pointer: coarse) { - :where([data-media-player] .vds-video-layout .vds-gesture[action="toggle:controls"]) { - display: none; - } -} -:where(.vds-video-layout .vds-live-button) { - margin-left: 12px; -} -:where(.vds-video-layout:not([data-sm]) .vds-time-group) { - margin-left: 10px; -} -:where(.vds-video-layout[data-sm] .vds-time) { - font-size: var(--video-sm-time-font-size, 14px); -} -:where([data-fullscreen] .vds-video-layout .vds-time) { - font-size: var(--video-fullscreen-time-font-size, 16px); -} -:where(.vds-video-layout .vds-load-container) { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - display: none; - align-items: center; - justify-content: center; - pointer-events: none; - z-index: 99; -} -:where([data-media-player][data-load=play]:not([data-started]) .vds-video-layout[data-match] .vds-load-container) { - display: flex; -} -:where(.vds-video-layout .vds-load-container .vds-play-button) { - --size: var(--video-load-button-size, 56px); - --color: var(--video-load-button-color, rgb(0 0 0 / 0.8)); - --bg-color: var(--video-load-button-bg, var(--media-brand)); - --media-button-hover-transform: 0; - --media-button-border: var(--video-load-button-border, var(--color)); - --media-button-hover-bg: var(--video-load-button-bg, var(--media-brand)); - width: var(--size); - height: var(--size); - pointer-events: auto; - margin-bottom: 2px; - overflow: hidden; -} -.vds-video-layout .vds-load-container .vds-play-button { - border-radius: var(--video-load-button-border-radius, 100%); - color: var(--color); -} -.vds-video-layout .vds-load-container .vds-play-button { - background-color: var(--bg-color); -} -:where(.vds-video-layout[data-sm] .vds-load-container .vds-play-button) { - --size: var(--video-sm-load-button-size, 48px); - --media-button-hover-transform: translateY(0%); - width: var(--size); - height: var(--size); - transform: translateY(0%); -} -:where(.vds-video-layout[data-sm] .vds-controls-group:nth-last-child(2)) { - pointer-events: none; -} -:where(.vds-video-layout[data-sm] .vds-controls-group:last-child) { - z-index: 2; - margin-top: -2.5px; - margin-bottom: -6px; -} -:where([data-fullscreen] .vds-video-layout[data-sm] .vds-controls-group:last-child) { - margin-bottom: 0; -} -.vds-video-layout[data-sm] .vds-controls-group { - padding: 2px; -} -:where(.vds-video-layout[data-sm]) :where(.vds-button, .vds-slider:not(.vds-time-slider), .vds-time, .vds-time-divider, .vds-chapter-title) { - transition: opacity 0.15s ease; -} -:where([data-media-player]:not([data-started]) .vds-video-layout[data-sm]) :where(.vds-button .vds-slider, .vds-time-group) { - opacity: 0; - visibility: hidden; -} -:where(.vds-video-layout[data-sm] .vds-time-slider) { - transition: transform 0.1s linear; -} -@media (pointer: coarse) { - :where([data-preview] .vds-video-layout:not([data-no-scrub-gesture])) :where(.vds-button, .vds-slider:not(.vds-time-slider), .vds-time, .vds-chapter-title, .vds-time-divider, .vds-captions, .vds-live-button) { - opacity: 0; - } - :where([data-preview] .vds-video-layout:not([data-no-scrub-gesture]) .vds-time-slider) { - --track-height: var(--video-sm-slider-focus-track-height, 12px); - transform: translateY(-6px); - transition: transform 0.1s linear; - } -} -:where(.vds-video-layout[data-sm] .vds-controls .vds-play-button) { - --size: var(--video-sm-play-button-size, 45px); - --media-button-hover-transform: translateY(25%); - width: var(--size); - height: var(--size); - transform: translateY(25%); - border-radius: 100%; - pointer-events: auto; - margin-bottom: 2px; - overflow: hidden; -} -.vds-video-layout[data-sm] .vds-controls .vds-play-button { - background-color: var(--video-sm-play-button-bg, rgba(0 0 0 / 0.6)); -} -:where([data-media-player]:not([data-started]) .vds-video-layout[data-sm] .vds-controls-group:not(:nth-child(3))) { - opacity: 0; - visibility: hidden; -} -:where(.vds-video-layout[data-sm] .vds-buffering-indicator) { - --media-buffering-size: 64px; - transform: translate(-2px, -4px); -} -:where(.vds-video-layout .vds-start-duration .vds-time) { - position: absolute; - right: 8px; - bottom: 8px; - margin-right: 8px; - margin-bottom: 8px; - z-index: 10; -} -.vds-video-layout .vds-start-duration .vds-time { - padding: var(--video-sm-start-duration-padding, 3px 6px); - color: var(--video-sm-start-duration-color, var(--video-controls-color)); - background-color: var(--video-sm-start-duration-bg, rgba(0 0 0 / 0.64)); -} -:where([data-started] .vds-video-layout .vds-start-duration .vds-time) { - display: none; -} -:where([data-media-player]:not([data-can-play]) .vds-video-layout .vds-start-duration .vds-time) { - opacity: 0; -} -:where(.vds-video-layout[data-sm] .vds-time[data-type=current]) { - margin-left: 8px; -} -:where([data-fullscreen] .vds-video-layout .vds-controls-group:nth-last-child(2)) { - margin-bottom: -16px; -} -@media (orientation: portrait) { - :where([data-fullscreen] .vds-video-layout .vds-captions) { - bottom: 30lvh; - bottom: 10dvh; - } -} -@media (orientation: landscape) { - :where([data-fullscreen] .vds-video-layout .vds-controls-group:nth-last-child(2)) { - margin-bottom: -12px; - } -} diff --git a/assets/vs_js_out.js b/assets/vs_js_out.js deleted file mode 100644 index 75a771c..0000000 --- a/assets/vs_js_out.js +++ /dev/null @@ -1,21783 +0,0 @@ -(() => { - var __defProp = Object.defineProperty; - var __getOwnPropNames = Object.getOwnPropertyNames; - var __esm = (fn, res) => function __init() { - return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; - }; - var __export = (target, all) => { - for (var name in all) - __defProp(target, name, { get: all[name], enumerable: true }); - }; - - // node_modules/vidstack/prod/chunks/vidstack-CRlI3Mh7.js - function flushEffects() { - scheduledEffects = true; - queueMicrotask(runEffects); - } - function runEffects() { - if (!effects.length) { - scheduledEffects = false; - return; - } - runningEffects = true; - for (let i4 = 0; i4 < effects.length; i4++) { - if (effects[i4].$st !== STATE_CLEAN) - runTop(effects[i4]); - } - effects = []; - scheduledEffects = false; - runningEffects = false; - } - function runTop(node) { - let ancestors = [node]; - while (node = node[SCOPE]) { - if (node.$e && node.$st !== STATE_CLEAN) - ancestors.push(node); - } - for (let i4 = ancestors.length - 1; i4 >= 0; i4--) { - updateCheck(ancestors[i4]); - } - } - function root(init) { - const scope = createScope(); - return compute(scope, !init.length ? init : init.bind(null, dispose.bind(scope)), null); - } - function peek(fn) { - return compute(currentScope, fn, null); - } - function untrack(fn) { - return compute(null, fn, null); - } - function tick() { - if (!runningEffects) - runEffects(); - } - function getScope() { - return currentScope; - } - function scoped(run, scope) { - try { - return compute(scope, run, null); - } catch (error) { - handleError(scope, error); - return; - } - } - function getContext(key2, scope = currentScope) { - return scope?.$cx[key2]; - } - function setContext(key2, value, scope = currentScope) { - if (scope) - scope.$cx = { ...scope.$cx, [key2]: value }; - } - function onDispose(disposable) { - if (!disposable || !currentScope) - return disposable || NOOP; - const node = currentScope; - if (!node.$d) { - node.$d = disposable; - } else if (Array.isArray(node.$d)) { - node.$d.push(disposable); - } else { - node.$d = [node.$d, disposable]; - } - return function removeDispose() { - if (node.$st === STATE_DISPOSED) - return; - disposable.call(null); - if (isFunction$1(node.$d)) { - node.$d = null; - } else if (Array.isArray(node.$d)) { - node.$d.splice(node.$d.indexOf(disposable), 1); - } - }; - } - function dispose(self = true) { - if (this.$st === STATE_DISPOSED) - return; - if (this.$h) { - if (Array.isArray(this.$h)) { - for (let i4 = this.$h.length - 1; i4 >= 0; i4--) { - dispose.call(this.$h[i4]); - } - } else { - dispose.call(this.$h); - } - } - if (self) { - const parent = this[SCOPE]; - if (parent) { - if (Array.isArray(parent.$h)) { - parent.$h.splice(parent.$h.indexOf(this), 1); - } else { - parent.$h = null; - } - } - disposeNode(this); - } - } - function disposeNode(node) { - node.$st = STATE_DISPOSED; - if (node.$d) - emptyDisposal(node); - if (node.$s) - removeSourceObservers(node, 0); - node[SCOPE] = null; - node.$s = null; - node.$o = null; - node.$h = null; - node.$cx = defaultContext; - node.$eh = null; - } - function emptyDisposal(scope) { - try { - if (Array.isArray(scope.$d)) { - for (let i4 = scope.$d.length - 1; i4 >= 0; i4--) { - const callable = scope.$d[i4]; - callable.call(callable); - } - } else { - scope.$d.call(scope.$d); - } - scope.$d = null; - } catch (error) { - handleError(scope, error); - } - } - function compute(scope, compute2, observer) { - const prevScope = currentScope, prevObserver = currentObserver; - currentScope = scope; - currentObserver = observer; - try { - return compute2.call(scope); - } finally { - currentScope = prevScope; - currentObserver = prevObserver; - } - } - function handleError(scope, error) { - if (!scope || !scope.$eh) - throw error; - let i4 = 0, len = scope.$eh.length, currentError = error; - for (i4 = 0; i4 < len; i4++) { - try { - scope.$eh[i4](currentError); - break; - } catch (error2) { - currentError = error2; - } - } - if (i4 === len) - throw currentError; - } - function read() { - if (this.$st === STATE_DISPOSED) - return this.$v; - if (currentObserver && !this.$e) { - if (!currentObservers && currentObserver.$s && currentObserver.$s[currentObserversIndex] == this) { - currentObserversIndex++; - } else if (!currentObservers) - currentObservers = [this]; - else - currentObservers.push(this); - } - if (this.$c) - updateCheck(this); - return this.$v; - } - function write(newValue) { - const value = isFunction$1(newValue) ? newValue(this.$v) : newValue; - if (this.$ch(this.$v, value)) { - this.$v = value; - if (this.$o) { - for (let i4 = 0; i4 < this.$o.length; i4++) { - notify(this.$o[i4], STATE_DIRTY); - } - } - } - return this.$v; - } - function createScope() { - return new ScopeNode(); - } - function createComputation(initialValue, compute2, options) { - return new ComputeNode(initialValue, compute2, options); - } - function isNotEqual(a3, b2) { - return a3 !== b2; - } - function isFunction$1(value) { - return typeof value === "function"; - } - function updateCheck(node) { - if (node.$st === STATE_CHECK) { - for (let i4 = 0; i4 < node.$s.length; i4++) { - updateCheck(node.$s[i4]); - if (node.$st === STATE_DIRTY) { - break; - } - } - } - if (node.$st === STATE_DIRTY) - update(node); - else - node.$st = STATE_CLEAN; - } - function cleanup(node) { - if (node.$h) - dispose.call(node, false); - if (node.$d) - emptyDisposal(node); - node.$eh = node[SCOPE] ? node[SCOPE].$eh : null; - } - function update(node) { - let prevObservers = currentObservers, prevObserversIndex = currentObserversIndex; - currentObservers = null; - currentObserversIndex = 0; - try { - cleanup(node); - const result = compute(node, node.$c, node); - updateObservers(node); - if (!node.$e && node.$i) { - write.call(node, result); - } else { - node.$v = result; - node.$i = true; - } - } catch (error) { - updateObservers(node); - handleError(node, error); - } finally { - currentObservers = prevObservers; - currentObserversIndex = prevObserversIndex; - node.$st = STATE_CLEAN; - } - } - function updateObservers(node) { - if (currentObservers) { - if (node.$s) - removeSourceObservers(node, currentObserversIndex); - if (node.$s && currentObserversIndex > 0) { - node.$s.length = currentObserversIndex + currentObservers.length; - for (let i4 = 0; i4 < currentObservers.length; i4++) { - node.$s[currentObserversIndex + i4] = currentObservers[i4]; - } - } else { - node.$s = currentObservers; - } - let source; - for (let i4 = currentObserversIndex; i4 < node.$s.length; i4++) { - source = node.$s[i4]; - if (!source.$o) - source.$o = [node]; - else - source.$o.push(node); - } - } else if (node.$s && currentObserversIndex < node.$s.length) { - removeSourceObservers(node, currentObserversIndex); - node.$s.length = currentObserversIndex; - } - } - function notify(node, state) { - if (node.$st >= state) - return; - if (node.$e && node.$st === STATE_CLEAN) { - effects.push(node); - if (!scheduledEffects) - flushEffects(); - } - node.$st = state; - if (node.$o) { - for (let i4 = 0; i4 < node.$o.length; i4++) { - notify(node.$o[i4], STATE_CHECK); - } - } - } - function removeSourceObservers(node, index) { - let source, swap; - for (let i4 = index; i4 < node.$s.length; i4++) { - source = node.$s[i4]; - if (source.$o) { - swap = source.$o.indexOf(node); - source.$o[swap] = source.$o[source.$o.length - 1]; - source.$o.pop(); - } - } - } - function noop(...args) { - } - function isNull(value) { - return value === null; - } - function isUndefined(value) { - return typeof value === "undefined"; - } - function isNil(value) { - return isNull(value) || isUndefined(value); - } - function isObject(value) { - return value?.constructor === Object; - } - function isNumber(value) { - return typeof value === "number" && !Number.isNaN(value); - } - function isString(value) { - return typeof value === "string"; - } - function isBoolean(value) { - return typeof value === "boolean"; - } - function isFunction(value) { - return typeof value === "function"; - } - function isArray(value) { - return Array.isArray(value); - } - function isDOMEvent(event2) { - return !!event2?.[DOM_EVENT]; - } - function listenEvent(target, type, handler, options) { - target.addEventListener(type, handler, options); - return onDispose(() => target.removeEventListener(type, handler, options)); - } - function anySignal(...signals) { - const controller = new AbortController(), options = { signal: controller.signal }; - function onAbort(event2) { - controller.abort(event2.target.reason); - } - for (const signal2 of signals) { - if (signal2.aborted) { - controller.abort(signal2.reason); - break; - } - signal2.addEventListener("abort", onAbort, options); - } - return controller.signal; - } - function isPointerEvent(event2) { - return !!event2?.type.startsWith("pointer"); - } - function isTouchEvent(event2) { - return !!event2?.type.startsWith("touch"); - } - function isMouseEvent(event2) { - return /^(click|mouse)/.test(event2?.type ?? ""); - } - function isKeyboardEvent(event2) { - return !!event2?.type.startsWith("key"); - } - function wasEnterKeyPressed(event2) { - return isKeyboardEvent(event2) && event2.key === "Enter"; - } - function isKeyboardClick(event2) { - return isKeyboardEvent(event2) && (event2.key === "Enter" || event2.key === " "); - } - function isDOMNode(node) { - return node instanceof Node; - } - function setAttribute(host, name, value) { - if (!host) return; - else if (!value && value !== "" && value !== 0) { - host.removeAttribute(name); - } else { - const attrValue = value === true ? "" : value + ""; - if (host.getAttribute(name) !== attrValue) { - host.setAttribute(name, attrValue); - } - } - } - function setStyle(host, property, value) { - if (!host) return; - else if (!value && value !== 0) { - host.style.removeProperty(property); - } else { - host.style.setProperty(property, value + ""); - } - } - function toggleClass(host, name, value) { - host.classList[value ? "add" : "remove"](name); - } - function signal(initialValue, options) { - const node = createComputation(initialValue, null, options), signal2 = read.bind(node); - signal2[SCOPE] = true; - signal2.set = write.bind(node); - return signal2; - } - function isReadSignal(fn) { - return isFunction$1(fn) && SCOPE in fn; - } - function computed(compute2, options) { - const node = createComputation( - options?.initial, - compute2, - options - ), signal2 = read.bind(node); - signal2[SCOPE] = true; - return signal2; - } - function effect$1(effect2, options) { - const signal2 = createComputation( - null, - function runEffect() { - let effectResult = effect2(); - isFunction$1(effectResult) && onDispose(effectResult); - return null; - }, - void 0 - ); - signal2.$e = true; - update(signal2); - return dispose.bind(signal2, true); - } - function isWriteSignal(fn) { - return isReadSignal(fn) && "set" in fn; - } - function createContext(provide) { - return { id: Symbol(), provide }; - } - function provideContext(context, value, scope = getScope()) { - const hasProvidedValue = !isUndefined(value); - setContext(context.id, hasProvidedValue ? value : context.provide?.(), scope); - } - function useContext(context) { - const value = getContext(context.id); - return value; - } - function hasProvidedContext(context) { - return !isUndefined(getContext(context.id)); - } - function createInstanceProps(props) { - const $props = {}; - for (const name of Object.keys(props)) { - const def = props[name]; - $props[name] = signal(def, def); - } - return $props; - } - function createComponent(Component2, init) { - return root(() => { - currentInstance.$$ = new Instance(Component2, getScope(), init); - const component = new Component2(); - currentInstance.$$.component = component; - currentInstance.$$ = null; - return component; - }); - } - function prop(target, propertyKey, descriptor) { - if (!target[PROPS]) target[PROPS] = /* @__PURE__ */ new Set(); - target[PROPS].add(propertyKey); - } - function method(target, propertyKey, descriptor) { - if (!target[METHODS]) target[METHODS] = /* @__PURE__ */ new Set(); - target[METHODS].add(propertyKey); - } - function useState(state) { - return useContext(state); - } - function runAll(fns, arg) { - for (const fn of fns) fn(arg); - } - function camelToKebabCase(str) { - return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); - } - function kebabToCamelCase(str) { - return str.replace(/-./g, (x2) => x2[1].toUpperCase()); - } - function uppercaseFirstChar(str) { - return str.charAt(0).toUpperCase() + str.slice(1); - } - function unwrap(fn) { - return isFunction(fn) ? fn() : fn; - } - function ariaBool(value) { - return value ? "true" : "false"; - } - function keysOf(obj) { - return Object.keys(obj); - } - function deferredPromise() { - let resolve, reject; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - return { promise, resolve, reject }; - } - function waitTimeout(delay) { - return new Promise((resolve) => setTimeout(resolve, delay)); - } - function animationFrameThrottle(func) { - let id3 = -1, lastArgs; - function throttle2(...args) { - lastArgs = args; - if (id3 >= 0) return; - id3 = window.requestAnimationFrame(() => { - func.apply(this, lastArgs); - id3 = -1; - lastArgs = void 0; - }); - } - return throttle2; - } - function waitIdlePeriod(callback, options) { - return new Promise((resolve) => { - requestIdleCallback((deadline) => { - callback?.(deadline); - resolve(); - }, options); - }); - } - function throttle(fn, interval, options) { - var timeoutId = null; - var throttledFn = null; - var leading = options && options.leading; - var trailing = options && options.trailing; - if (leading == null) { - leading = true; - } - if (trailing == null) { - trailing = !leading; - } - if (leading == true) { - trailing = false; - } - var cancel = function() { - if (timeoutId) { - clearTimeout(timeoutId); - timeoutId = null; - } - }; - var flush = function() { - var call = throttledFn; - cancel(); - if (call) { - call(); - } - }; - var throttleWrapper = function() { - var callNow = leading && !timeoutId; - var context = this; - var args = arguments; - throttledFn = function() { - return fn.apply(context, args); - }; - if (!timeoutId) { - timeoutId = setTimeout(function() { - timeoutId = null; - if (trailing) { - return throttledFn(); - } - }, interval); - } - if (callNow) { - callNow = false; - return throttledFn(); - } - }; - throttleWrapper.cancel = cancel; - throttleWrapper.flush = flush; - return throttleWrapper; - } - function debounce(fn, wait, callFirst) { - var timeout = null; - var debouncedFn = null; - var clear = function() { - if (timeout) { - clearTimeout(timeout); - debouncedFn = null; - timeout = null; - } - }; - var flush = function() { - var call = debouncedFn; - clear(); - if (call) { - call(); - } - }; - var debounceWrapper = function() { - if (!wait) { - return fn.apply(this, arguments); - } - var context = this; - var args = arguments; - var callNow = callFirst && !timeout; - clear(); - debouncedFn = function() { - fn.apply(context, args); - }; - timeout = setTimeout(function() { - timeout = null; - if (!callNow) { - var call = debouncedFn; - debouncedFn = null; - return call(); - } - }, wait); - if (callNow) { - return debouncedFn(); - } - }; - debounceWrapper.cancel = clear; - debounceWrapper.flush = flush; - return debounceWrapper; - } - function inferAttributeConverter(value) { - if (value === null) return NULLABLE_STRING; - switch (typeof value) { - case "undefined": - return STRING; - case "string": - return STRING; - case "boolean": - return BOOLEAN; - case "number": - return NUMBER; - case "function": - return FUNCTION; - case "object": - return isArray(value) ? ARRAY : OBJECT; - default: - return STRING; - } - } - function Host(Super, Component2) { - class MaverickElement extends Super { - static attrs; - static [ATTRS] = null; - static get observedAttributes() { - if (!this[ATTRS] && Component2.props) { - const map = /* @__PURE__ */ new Map(); - for (const propName of Object.keys(Component2.props)) { - let attr = this.attrs?.[propName], attrName = isString(attr) ? attr : !attr ? attr : attr?.attr; - if (attrName === false) continue; - if (!attrName) attrName = camelToKebabCase(propName); - map.set(attrName, { - prop: propName, - converter: attr && !isString(attr) && attr?.converter || inferAttributeConverter(Component2.props[propName]) - }); - } - this[ATTRS] = map; - } - return this[ATTRS] ? Array.from(this[ATTRS].keys()) : []; - } - $; - [SETUP_STATE] = SetupState.Idle; - [SETUP_CALLBACKS] = null; - keepAlive = false; - forwardKeepAlive = true; - get scope() { - return this.$.$$.scope; - } - get attachScope() { - return this.$.$$.attachScope; - } - get connectScope() { - return this.$.$$.connectScope; - } - get $props() { - return this.$.$$.props; - } - get $state() { - return this.$.$$.$state; - } - get state() { - return this.$.state; - } - constructor(...args) { - super(...args); - this.$ = scoped(() => createComponent(Component2), null); - this.$.$$.addHooks(this); - if (Component2.props) { - const props = this.$props, descriptors = Object.getOwnPropertyDescriptors(this); - for (const prop2 of Object.keys(descriptors)) { - if (prop2 in Component2.props) { - props[prop2].set(this[prop2]); - delete this[prop2]; - } - } - } - } - attributeChangedCallback(name, _2, newValue) { - const Ctor = this.constructor; - if (!Ctor[ATTRS]) { - super.attributeChangedCallback?.(name, _2, newValue); - return; - } - const def = Ctor[ATTRS].get(name); - if (def) this[def.prop] = def.converter(newValue); - } - connectedCallback() { - const instance = this.$?.$$; - if (!instance || instance.destroyed) return; - if (this[SETUP_STATE] !== SetupState.Ready) { - setup.call(this); - return; - } - if (!this.isConnected) return; - if (this.hasAttribute("keep-alive")) { - this.keepAlive = true; - } - instance.connect(); - if (isArray(this[SETUP_CALLBACKS])) runAll(this[SETUP_CALLBACKS], this); - this[SETUP_CALLBACKS] = null; - const callback = super.connectedCallback; - if (callback) scoped(() => callback.call(this), this.connectScope); - return; - } - disconnectedCallback() { - const instance = this.$?.$$; - if (!instance || instance.destroyed) return; - instance.disconnect(); - const callback = super.disconnectedCallback; - if (callback) callback.call(this); - if (!this.keepAlive && !this.hasAttribute("keep-alive")) { - setTimeout(() => { - requestAnimationFrame(() => { - if (!this.isConnected) instance.destroy(); - }); - }, 0); - } - } - [SETUP]() { - const instance = this.$.$$, Ctor = this.constructor; - if (instance.destroyed) return; - const attrs = Ctor[ATTRS]; - if (attrs) { - for (const attr of this.attributes) { - let def = attrs.get(attr.name); - if (def && def.converter) { - instance.props[def.prop].set(def.converter(this.getAttribute(attr.name))); - } - } - } - instance.setup(); - instance.attach(this); - this[SETUP_STATE] = SetupState.Ready; - this.connectedCallback(); - } - // @ts-expect-error - subscribe(callback) { - return this.$.subscribe(callback); - } - destroy() { - this.disconnectedCallback(); - this.$.destroy(); - } - } - extendProto(MaverickElement, Component2); - return MaverickElement; - } - function extendProto(Element2, Component2) { - const ElementProto = Element2.prototype, ComponentProto = Component2.prototype; - if (Component2.props) { - for (const prop2 of Object.keys(Component2.props)) { - Object.defineProperty(ElementProto, prop2, { - enumerable: true, - configurable: true, - get() { - return this.$props[prop2](); - }, - set(value) { - this.$props[prop2].set(value); - } - }); - } - } - if (ComponentProto[PROPS]) { - for (const name of ComponentProto[PROPS]) { - Object.defineProperty(ElementProto, name, { - enumerable: true, - configurable: true, - get() { - return this.$[name]; - }, - set(value) { - this.$[name] = value; - } - }); - } - } - if (ComponentProto[METHODS]) { - for (const name of ComponentProto[METHODS]) { - ElementProto[name] = function(...args) { - return this.$[name](...args); - }; - } - } - } - function setup() { - if (this[SETUP_STATE] !== SetupState.Idle) return; - this[SETUP_STATE] = SetupState.Pending; - const parent = findParent(this), isParentRegistered = parent && window.customElements.get(parent.localName), isParentSetup = parent && parent[SETUP_STATE] === SetupState.Ready; - if (parent && (!isParentRegistered || !isParentSetup)) { - waitForParent.call(this, parent); - return; - } - attach.call(this, parent); - } - async function waitForParent(parent) { - await window.customElements.whenDefined(parent.localName); - if (parent[SETUP_STATE] !== SetupState.Ready) { - await new Promise((res) => (parent[SETUP_CALLBACKS] ??= []).push(res)); - } - attach.call(this, parent); - } - function attach(parent) { - if (!this.isConnected) return; - if (parent) { - if (parent.keepAlive && parent.forwardKeepAlive) { - this.keepAlive = true; - this.setAttribute("keep-alive", ""); - } - const scope = this.$.$$.scope; - if (scope) parent.$.$$.attachScope.append(scope); - } - this[SETUP](); - } - function findParent(host) { - let node = host.parentNode, prefix = host.localName.split("-", 1)[0] + "-"; - while (node) { - if (node.nodeType === 1 && node.localName.startsWith(prefix)) { - return node; - } - node = node.parentNode; - } - return null; - } - function defineCustomElement(element, throws = false) { - if (throws || !window.customElements.get(element.tagName)) { - window.customElements.define(element.tagName, element); - } - } - var SCOPE, scheduledEffects, runningEffects, currentScope, currentObserver, currentObservers, currentObserversIndex, effects, defaultContext, NOOP, STATE_CLEAN, STATE_CHECK, STATE_DIRTY, STATE_DISPOSED, ScopeNode, ScopeProto, ComputeNode, ComputeProto, EVENT, DOM_EVENT, DOMEvent, EventTriggers, EventsTarget, EventsController, effect, PROPS, METHODS, ON_DISPATCH, EMPTY_PROPS, Instance, currentInstance, ViewController, Component, State, requestIdleCallback, key, webkit, moz, ms, document$1, vendor, fscreen, functionThrottle, functionDebounce, t, e, n, o, l, r, STRING, NULLABLE_STRING, NUMBER, BOOLEAN, FUNCTION, ARRAY, OBJECT, ATTRS, SETUP, SETUP_STATE, SETUP_CALLBACKS, SetupState, Icon$24, Icon$0, Icon$5, Icon$8, Icon$11, Icon$13, Icon$16, Icon$19, Icon$22, Icon$26, Icon$27, Icon$31, Icon$33, Icon$34, Icon$35, Icon$39, Icon$40, Icon$53, Icon$54, Icon$56, Icon$59, Icon$60, Icon$61, Icon$62, Icon$63, Icon$74, Icon$77, Icon$81, Icon$88, Icon$104, Icon$105; - var init_vidstack_CRlI3Mh7 = __esm({ - "node_modules/vidstack/prod/chunks/vidstack-CRlI3Mh7.js"() { - SCOPE = Symbol(0); - scheduledEffects = false; - runningEffects = false; - currentScope = null; - currentObserver = null; - currentObservers = null; - currentObserversIndex = 0; - effects = []; - defaultContext = {}; - NOOP = () => { - }; - STATE_CLEAN = 0; - STATE_CHECK = 1; - STATE_DIRTY = 2; - STATE_DISPOSED = 3; - ScopeNode = function Scope() { - this[SCOPE] = null; - this.$h = null; - if (currentScope) - currentScope.append(this); - }; - ScopeProto = ScopeNode.prototype; - ScopeProto.$cx = defaultContext; - ScopeProto.$eh = null; - ScopeProto.$c = null; - ScopeProto.$d = null; - ScopeProto.append = function(child) { - child[SCOPE] = this; - if (!this.$h) { - this.$h = child; - } else if (Array.isArray(this.$h)) { - this.$h.push(child); - } else { - this.$h = [this.$h, child]; - } - child.$cx = child.$cx === defaultContext ? this.$cx : { ...this.$cx, ...child.$cx }; - if (this.$eh) { - child.$eh = !child.$eh ? this.$eh : [...child.$eh, ...this.$eh]; - } - }; - ScopeProto.dispose = function() { - dispose.call(this); - }; - ComputeNode = function Computation(initialValue, compute2, options) { - ScopeNode.call(this); - this.$st = compute2 ? STATE_DIRTY : STATE_CLEAN; - this.$i = false; - this.$e = false; - this.$s = null; - this.$o = null; - this.$v = initialValue; - if (compute2) - this.$c = compute2; - if (options && options.dirty) - this.$ch = options.dirty; - }; - ComputeProto = ComputeNode.prototype; - Object.setPrototypeOf(ComputeProto, ScopeProto); - ComputeProto.$ch = isNotEqual; - ComputeProto.call = read; - EVENT = Event; - DOM_EVENT = Symbol("DOM_EVENT"); - DOMEvent = class extends EVENT { - [DOM_EVENT] = true; - /** - * The event detail. - */ - detail; - /** - * The event trigger chain. - */ - triggers = new EventTriggers(); - /** - * The preceding event that was responsible for this event being fired. - */ - get trigger() { - return this.triggers.source; - } - /** - * The origin event that lead to this event being fired. - */ - get originEvent() { - return this.triggers.origin; - } - /** - * Whether the origin event was triggered by the user. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted} - */ - get isOriginTrusted() { - return this.triggers.origin?.isTrusted ?? false; - } - constructor(type, ...init) { - super(type, init[0]); - this.detail = init[0]?.detail; - const trigger = init[0]?.trigger; - if (trigger) this.triggers.add(trigger); - } - }; - EventTriggers = class { - chain = []; - get source() { - return this.chain[0]; - } - get origin() { - return this.chain[this.chain.length - 1]; - } - /** - * Appends the event to the end of the chain. - */ - add(event2) { - this.chain.push(event2); - if (isDOMEvent(event2)) { - this.chain.push(...event2.triggers); - } - } - /** - * Removes the event from the chain and returns it (if found). - */ - remove(event2) { - return this.chain.splice(this.chain.indexOf(event2), 1)[0]; - } - /** - * Returns whether the chain contains the given `event`. - */ - has(event2) { - return this.chain.some((e6) => e6 === event2); - } - /** - * Returns whether the chain contains the given event type. - */ - hasType(type) { - return !!this.findType(type); - } - /** - * Returns the first event with the given `type` found in the chain. - */ - findType(type) { - return this.chain.find((e6) => e6.type === type); - } - /** - * Walks an event chain on a given `event`, and invokes the given `callback` for each trigger event. - */ - walk(callback) { - for (const event2 of this.chain) { - const returnValue = callback(event2); - if (returnValue) return [event2, returnValue]; - } - } - [Symbol.iterator]() { - return this.chain.values(); - } - }; - EventsTarget = class extends EventTarget { - /** @internal type only */ - $ts__events; - addEventListener(type, callback, options) { - return super.addEventListener(type, callback, options); - } - removeEventListener(type, callback, options) { - return super.removeEventListener(type, callback, options); - } - }; - EventsController = class { - #target; - #controller; - get signal() { - return this.#controller.signal; - } - constructor(target) { - this.#target = target; - this.#controller = new AbortController(); - onDispose(this.abort.bind(this)); - } - add(type, handler, options) { - if (this.signal.aborted) throw Error("aborted"); - this.#target.addEventListener(type, handler, { - ...options, - signal: options?.signal ? anySignal(this.signal, options.signal) : this.signal - }); - return this; - } - remove(type, handler) { - this.#target.removeEventListener(type, handler); - return this; - } - abort(reason) { - this.#controller.abort(reason); - } - }; - effect = effect$1; - PROPS = /* @__PURE__ */ Symbol(0); - METHODS = /* @__PURE__ */ Symbol(0); - ON_DISPATCH = /* @__PURE__ */ Symbol(0); - EMPTY_PROPS = {}; - Instance = class { - /** @internal type only */ - $ts__events; - /** @internal type only */ - $ts__vars; - /* @internal */ - [ON_DISPATCH] = null; - $el = signal(null); - el = null; - scope = null; - attachScope = null; - connectScope = null; - component = null; - destroyed = false; - props = EMPTY_PROPS; - attrs = null; - styles = null; - state; - $state; - #setupCallbacks = []; - #attachCallbacks = []; - #connectCallbacks = []; - #destroyCallbacks = []; - constructor(Component2, scope, init) { - this.scope = scope; - if (init?.scope) init.scope.append(scope); - let stateFactory = Component2.state, props = Component2.props; - if (stateFactory) { - this.$state = stateFactory.create(); - this.state = new Proxy(this.$state, { - get: (_2, prop2) => this.$state[prop2]() - }); - provideContext(stateFactory, this.$state); - } - if (props) { - this.props = createInstanceProps(props); - if (init?.props) { - for (const prop2 of Object.keys(init.props)) { - this.props[prop2]?.set(init.props[prop2]); - } - } - } - onDispose(this.destroy.bind(this)); - } - setup() { - scoped(() => { - for (const callback of this.#setupCallbacks) callback(); - }, this.scope); - } - attach(el) { - if (this.el) return; - this.el = el; - this.$el.set(el); - scoped(() => { - this.attachScope = createScope(); - scoped(() => { - for (const callback of this.#attachCallbacks) callback(this.el); - this.#attachAttrs(); - this.#attachStyles(); - }, this.attachScope); - }, this.scope); - el.dispatchEvent(new Event("attached")); - } - detach() { - this.attachScope?.dispose(); - this.attachScope = null; - this.connectScope = null; - this.el = null; - this.$el.set(null); - } - connect() { - if (!this.el || !this.attachScope || !this.#connectCallbacks.length) return; - scoped(() => { - this.connectScope = createScope(); - scoped(() => { - for (const callback of this.#connectCallbacks) callback(this.el); - }, this.connectScope); - }, this.attachScope); - } - disconnect() { - this.connectScope?.dispose(); - this.connectScope = null; - } - destroy() { - if (this.destroyed) return; - this.destroyed = true; - scoped(() => { - for (const callback of this.#destroyCallbacks) callback(this.el); - }, this.scope); - const el = this.el; - this.detach(); - this.scope.dispose(); - this.#setupCallbacks.length = 0; - this.#attachCallbacks.length = 0; - this.#connectCallbacks.length = 0; - this.#destroyCallbacks.length = 0; - this.component = null; - this.attrs = null; - this.styles = null; - this.props = EMPTY_PROPS; - this.scope = null; - this.state = EMPTY_PROPS; - this.$state = null; - if (el) delete el.$; - } - addHooks(target) { - if (target.onSetup) this.#setupCallbacks.push(target.onSetup.bind(target)); - if (target.onAttach) this.#attachCallbacks.push(target.onAttach.bind(target)); - if (target.onConnect) this.#connectCallbacks.push(target.onConnect.bind(target)); - if (target.onDestroy) this.#destroyCallbacks.push(target.onDestroy.bind(target)); - } - #attachAttrs() { - if (!this.attrs) return; - for (const name of Object.keys(this.attrs)) { - if (isFunction(this.attrs[name])) { - effect(this.#setAttr.bind(this, name)); - } else { - setAttribute(this.el, name, this.attrs[name]); - } - } - } - #attachStyles() { - if (!this.styles) return; - for (const name of Object.keys(this.styles)) { - if (isFunction(this.styles[name])) { - effect(this.#setStyle.bind(this, name)); - } else { - setStyle(this.el, name, this.styles[name]); - } - } - } - #setAttr(name) { - setAttribute(this.el, name, this.attrs[name].call(this.component)); - } - #setStyle(name) { - setStyle(this.el, name, this.styles[name].call(this.component)); - } - }; - currentInstance = { $$: null }; - ViewController = class extends EventTarget { - /** @internal */ - $$; - get el() { - return this.$$.el; - } - get $el() { - return this.$$.$el(); - } - get scope() { - return this.$$.scope; - } - get attachScope() { - return this.$$.attachScope; - } - get connectScope() { - return this.$$.connectScope; - } - /** @internal */ - get $props() { - return this.$$.props; - } - /** @internal */ - get $state() { - return this.$$.$state; - } - get state() { - return this.$$.state; - } - constructor() { - super(); - if (currentInstance.$$) this.attach(currentInstance); - } - attach({ $$ }) { - this.$$ = $$; - $$.addHooks(this); - return this; - } - addEventListener(type, callback, options) { - this.listen(type, callback, options); - } - removeEventListener(type, callback, options) { - this.el?.removeEventListener(type, callback, options); - } - /** - * The given callback is invoked when the component is ready to be set up. - * - * - This hook will run once. - * - This hook is called both client-side and server-side. - * - It's safe to use context inside this hook. - * - The host element has not attached yet - wait for `onAttach`. - */ - /** - * This method can be used to specify attributes that should be set on the host element. Any - * attributes that are assigned to a function will be considered a signal and updated accordingly. - */ - setAttributes(attributes) { - if (!this.$$.attrs) this.$$.attrs = {}; - Object.assign(this.$$.attrs, attributes); - } - /** - * This method can be used to specify styles that should set be set on the host element. Any - * styles that are assigned to a function will be considered a signal and updated accordingly. - */ - setStyles(styles) { - if (!this.$$.styles) this.$$.styles = {}; - Object.assign(this.$$.styles, styles); - } - /** - * This method is used to satisfy the CSS variables contract specified on the current - * component. Other CSS variables can be set via the `setStyles` method. - */ - setCSSVars(vars) { - this.setStyles(vars); - } - /** - * Type-safe utility for creating component DOM events. - */ - createEvent(type, ...init) { - return new DOMEvent(type, init[0]); - } - /** - * Creates a `DOMEvent` and dispatches it from the host element. This method is typed to - * match all component events. - */ - dispatch(type, ...init) { - if (!this.el) return false; - const event2 = type instanceof Event ? type : new DOMEvent(type, init[0]); - Object.defineProperty(event2, "target", { - get: () => this.$$.component - }); - return untrack(() => { - this.$$[ON_DISPATCH]?.(event2); - return this.el.dispatchEvent(event2); - }); - } - dispatchEvent(event2) { - return this.dispatch(event2); - } - /** - * Adds an event listener for the given `type` and returns a function which can be invoked to - * remove the event listener. - * - * - The listener is removed if the current scope is disposed. - * - This method is safe to use on the server (noop). - */ - listen(type, handler, options) { - if (!this.el) return noop; - return listenEvent(this.el, type, handler, options); - } - }; - Component = class extends ViewController { - subscribe(callback) { - return scoped(() => effect(() => callback(this.state)), this.$$.scope); - } - destroy() { - this.$$.destroy(); - } - }; - State = class { - id = Symbol(0); - record; - #descriptors; - constructor(record) { - this.record = record; - this.#descriptors = Object.getOwnPropertyDescriptors(record); - } - create() { - const store = {}, state = new Proxy(store, { get: (_2, prop2) => store[prop2]() }); - for (const name of Object.keys(this.record)) { - const getter = this.#descriptors[name].get; - store[name] = getter ? computed(getter.bind(state)) : signal(this.record[name]); - } - return store; - } - reset(record, filter) { - for (const name of Object.keys(record)) { - if (!this.#descriptors[name].get && (!filter || filter(name))) { - record[name].set(this.record[name]); - } - } - } - }; - requestIdleCallback = typeof window !== "undefined" ? "requestIdleCallback" in window ? window.requestIdleCallback : (cb) => window.setTimeout(cb, 1) : noop; - key = { - fullscreenEnabled: 0, - fullscreenElement: 1, - requestFullscreen: 2, - exitFullscreen: 3, - fullscreenchange: 4, - fullscreenerror: 5, - fullscreen: 6 - }; - webkit = [ - "webkitFullscreenEnabled", - "webkitFullscreenElement", - "webkitRequestFullscreen", - "webkitExitFullscreen", - "webkitfullscreenchange", - "webkitfullscreenerror", - "-webkit-full-screen" - ]; - moz = [ - "mozFullScreenEnabled", - "mozFullScreenElement", - "mozRequestFullScreen", - "mozCancelFullScreen", - "mozfullscreenchange", - "mozfullscreenerror", - "-moz-full-screen" - ]; - ms = [ - "msFullscreenEnabled", - "msFullscreenElement", - "msRequestFullscreen", - "msExitFullscreen", - "MSFullscreenChange", - "MSFullscreenError", - "-ms-fullscreen" - ]; - document$1 = typeof window !== "undefined" && typeof window.document !== "undefined" ? window.document : {}; - vendor = "fullscreenEnabled" in document$1 && Object.keys(key) || webkit[0] in document$1 && webkit || moz[0] in document$1 && moz || ms[0] in document$1 && ms || []; - fscreen = { - requestFullscreen: function(element) { - return element[vendor[key.requestFullscreen]](); - }, - requestFullscreenFunction: function(element) { - return element[vendor[key.requestFullscreen]]; - }, - get exitFullscreen() { - return document$1[vendor[key.exitFullscreen]].bind(document$1); - }, - get fullscreenPseudoClass() { - return ":" + vendor[key.fullscreen]; - }, - addEventListener: function(type, handler, options) { - return document$1.addEventListener(vendor[key[type]], handler, options); - }, - removeEventListener: function(type, handler, options) { - return document$1.removeEventListener(vendor[key[type]], handler, options); - }, - get fullscreenEnabled() { - return Boolean(document$1[vendor[key.fullscreenEnabled]]); - }, - set fullscreenEnabled(val) { - }, - get fullscreenElement() { - return document$1[vendor[key.fullscreenElement]]; - }, - set fullscreenElement(val) { - }, - get onfullscreenchange() { - return document$1[("on" + vendor[key.fullscreenchange]).toLowerCase()]; - }, - set onfullscreenchange(handler) { - return document$1[("on" + vendor[key.fullscreenchange]).toLowerCase()] = handler; - }, - get onfullscreenerror() { - return document$1[("on" + vendor[key.fullscreenerror]).toLowerCase()]; - }, - set onfullscreenerror(handler) { - return document$1[("on" + vendor[key.fullscreenerror]).toLowerCase()] = handler; - } - }; - functionThrottle = throttle; - functionDebounce = debounce; - t = (t22) => "object" == typeof t22 && null != t22 && 1 === t22.nodeType; - e = (t22, e22) => (!e22 || "hidden" !== t22) && ("visible" !== t22 && "clip" !== t22); - n = (t22, n22) => { - if (t22.clientHeight < t22.scrollHeight || t22.clientWidth < t22.scrollWidth) { - const o22 = getComputedStyle(t22, null); - return e(o22.overflowY, n22) || e(o22.overflowX, n22) || ((t32) => { - const e22 = ((t42) => { - if (!t42.ownerDocument || !t42.ownerDocument.defaultView) return null; - try { - return t42.ownerDocument.defaultView.frameElement; - } catch (t5) { - return null; - } - })(t32); - return !!e22 && (e22.clientHeight < t32.scrollHeight || e22.clientWidth < t32.scrollWidth); - })(t22); - } - return false; - }; - o = (t22, e22, n22, o22, l22, r22, i4, s4) => r22 < t22 && i4 > e22 || r22 > t22 && i4 < e22 ? 0 : r22 <= t22 && s4 <= n22 || i4 >= e22 && s4 >= n22 ? r22 - t22 - o22 : i4 > e22 && s4 < n22 || r22 < t22 && s4 > n22 ? i4 - e22 + l22 : 0; - l = (t22) => { - const e22 = t22.parentElement; - return null == e22 ? t22.getRootNode().host || null : e22; - }; - r = (e22, r22) => { - var i4, s4, d2, h4; - if ("undefined" == typeof document) return []; - const { scrollMode: c3, block: f2, inline: u2, boundary: a3, skipOverflowHiddenElements: g2 } = r22, p2 = "function" == typeof a3 ? a3 : (t22) => t22 !== a3; - if (!t(e22)) throw new TypeError("Invalid target"); - const m2 = document.scrollingElement || document.documentElement, w2 = []; - let W = e22; - for (; t(W) && p2(W); ) { - if (W = l(W), W === m2) { - w2.push(W); - break; - } - null != W && W === document.body && n(W) && !n(document.documentElement) || null != W && n(W, g2) && w2.push(W); - } - const b2 = null != (s4 = null == (i4 = window.visualViewport) ? void 0 : i4.width) ? s4 : innerWidth, H2 = null != (h4 = null == (d2 = window.visualViewport) ? void 0 : d2.height) ? h4 : innerHeight, { scrollX: y2, scrollY: M2 } = window, { height: v2, width: E2, top: x2, right: C2, bottom: I2, left: R2 } = e22.getBoundingClientRect(), { top: T2, right: B2, bottom: F, left: V2 } = ((t22) => { - const e32 = window.getComputedStyle(t22); - return { top: parseFloat(e32.scrollMarginTop) || 0, right: parseFloat(e32.scrollMarginRight) || 0, bottom: parseFloat(e32.scrollMarginBottom) || 0, left: parseFloat(e32.scrollMarginLeft) || 0 }; - })(e22); - let k2 = "start" === f2 || "nearest" === f2 ? x2 - T2 : "end" === f2 ? I2 + F : x2 + v2 / 2 - T2 + F, D2 = "center" === u2 ? R2 + E2 / 2 - V2 + B2 : "end" === u2 ? C2 + B2 : R2 - V2; - const L2 = []; - for (let t22 = 0; t22 < w2.length; t22++) { - const e32 = w2[t22], { height: n22, width: l22, top: r32, right: i22, bottom: s22, left: d22 } = e32.getBoundingClientRect(); - if ("if-needed" === c3 && x2 >= 0 && R2 >= 0 && I2 <= H2 && C2 <= b2 && x2 >= r32 && I2 <= s22 && R2 >= d22 && C2 <= i22) return L2; - const h22 = getComputedStyle(e32), a22 = parseInt(h22.borderLeftWidth, 10), g22 = parseInt(h22.borderTopWidth, 10), p22 = parseInt(h22.borderRightWidth, 10), W2 = parseInt(h22.borderBottomWidth, 10); - let T22 = 0, B22 = 0; - const F2 = "offsetWidth" in e32 ? e32.offsetWidth - e32.clientWidth - a22 - p22 : 0, V22 = "offsetHeight" in e32 ? e32.offsetHeight - e32.clientHeight - g22 - W2 : 0, S2 = "offsetWidth" in e32 ? 0 === e32.offsetWidth ? 0 : l22 / e32.offsetWidth : 0, X = "offsetHeight" in e32 ? 0 === e32.offsetHeight ? 0 : n22 / e32.offsetHeight : 0; - if (m2 === e32) T22 = "start" === f2 ? k2 : "end" === f2 ? k2 - H2 : "nearest" === f2 ? o(M2, M2 + H2, H2, g22, W2, M2 + k2, M2 + k2 + v2, v2) : k2 - H2 / 2, B22 = "start" === u2 ? D2 : "center" === u2 ? D2 - b2 / 2 : "end" === u2 ? D2 - b2 : o(y2, y2 + b2, b2, a22, p22, y2 + D2, y2 + D2 + E2, E2), T22 = Math.max(0, T22 + M2), B22 = Math.max(0, B22 + y2); - else { - T22 = "start" === f2 ? k2 - r32 - g22 : "end" === f2 ? k2 - s22 + W2 + V22 : "nearest" === f2 ? o(r32, s22, n22, g22, W2 + V22, k2, k2 + v2, v2) : k2 - (r32 + n22 / 2) + V22 / 2, B22 = "start" === u2 ? D2 - d22 - a22 : "center" === u2 ? D2 - (d22 + l22 / 2) + F2 / 2 : "end" === u2 ? D2 - i22 + p22 + F2 : o(d22, i22, l22, a22, p22 + F2, D2, D2 + E2, E2); - const { scrollLeft: t32, scrollTop: h32 } = e32; - T22 = 0 === X ? 0 : Math.max(0, Math.min(h32 + T22 / X, e32.scrollHeight - n22 / X + V22)), B22 = 0 === S2 ? 0 : Math.max(0, Math.min(t32 + B22 / S2, e32.scrollWidth - l22 / S2 + F2)), k2 += h32 - T22, D2 += t32 - B22; - } - L2.push({ el: e32, top: T22, left: B22 }); - } - return L2; - }; - STRING = (v2) => v2 === null ? "" : v2 + ""; - NULLABLE_STRING = (v2) => v2 === null ? null : v2 + ""; - NUMBER = (v2) => v2 === null ? 0 : Number(v2); - BOOLEAN = (v2) => v2 !== null; - FUNCTION = () => null; - ARRAY = (v2) => v2 === null ? [] : JSON.parse(v2); - OBJECT = (v2) => v2 === null ? {} : JSON.parse(v2); - ATTRS = /* @__PURE__ */ Symbol(0); - SETUP = /* @__PURE__ */ Symbol(0); - SETUP_STATE = /* @__PURE__ */ Symbol(0); - SETUP_CALLBACKS = /* @__PURE__ */ Symbol(0); - (function(SetupState2) { - const Idle = 0; - SetupState2[SetupState2["Idle"] = Idle] = "Idle"; - const Pending = 1; - SetupState2[SetupState2["Pending"] = Pending] = "Pending"; - const Ready = 2; - SetupState2[SetupState2["Ready"] = Ready] = "Ready"; - })(SetupState || (SetupState = {})); - Icon$24 = ` `; - Icon$0 = ` `; - Icon$5 = ` `; - Icon$8 = ``; - Icon$11 = ``; - Icon$13 = ``; - Icon$16 = ` `; - Icon$19 = ``; - Icon$22 = ``; - Icon$26 = ` `; - Icon$27 = ``; - Icon$31 = ` `; - Icon$33 = ` `; - Icon$34 = ``; - Icon$35 = ``; - Icon$39 = ` `; - Icon$40 = ` `; - Icon$53 = ``; - Icon$54 = ` `; - Icon$56 = ` `; - Icon$59 = ` `; - Icon$60 = ` `; - Icon$61 = ` `; - Icon$62 = ``; - Icon$63 = ` `; - Icon$74 = ``; - Icon$77 = ` `; - Icon$81 = ` `; - Icon$88 = ``; - Icon$104 = ` `; - Icon$105 = ` `; - } - }); - - // node_modules/vidstack/prod/chunks/vidstack-Cpte_fRf.js - function useMediaContext() { - return useContext(mediaContext); - } - function useMediaState() { - return useMediaContext().$state; - } - var mediaContext; - var init_vidstack_Cpte_fRf = __esm({ - "node_modules/vidstack/prod/chunks/vidstack-Cpte_fRf.js"() { - init_vidstack_CRlI3Mh7(); - mediaContext = createContext(); - } - }); - - // node_modules/vidstack/prod/chunks/vidstack-DwhHIY5e.js - function canOrientScreen() { - return canRotateScreen() && isFunction(screen.orientation.unlock); - } - function canRotateScreen() { - return !isUndefined(window.screen.orientation) && !isUndefined(window.screen.orientation.lock); - } - function canPlayAudioType(audio, type) { - if (!audio) audio = document.createElement("audio"); - return audio.canPlayType(type).length > 0; - } - function canPlayVideoType(video, type) { - if (!video) video = document.createElement("video"); - return video.canPlayType(type).length > 0; - } - function canPlayHLSNatively(video) { - if (!video) video = document.createElement("video"); - return video.canPlayType("application/vnd.apple.mpegurl").length > 0; - } - function canUsePictureInPicture(video) { - return !!document.pictureInPictureEnabled && !video?.disablePictureInPicture; - } - function canUseVideoPresentation(video) { - return isFunction(video?.webkitSupportsPresentationMode) && isFunction(video?.webkitSetPresentationMode); - } - async function canChangeVolume() { - const video = document.createElement("video"); - video.volume = 0.5; - await waitTimeout(0); - return video.volume === 0.5; - } - function getMediaSource() { - return window?.ManagedMediaSource ?? window?.MediaSource ?? window?.WebKitMediaSource; - } - function getSourceBuffer() { - return window?.SourceBuffer ?? window?.WebKitSourceBuffer; - } - function isHLSSupported() { - const MediaSource = getMediaSource(); - if (isUndefined(MediaSource)) return false; - const isTypeSupported = MediaSource && isFunction(MediaSource.isTypeSupported) && MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"'); - const SourceBuffer = getSourceBuffer(); - const isSourceBufferValid = isUndefined(SourceBuffer) || !isUndefined(SourceBuffer.prototype) && isFunction(SourceBuffer.prototype.appendBuffer) && isFunction(SourceBuffer.prototype.remove); - return !!isTypeSupported && !!isSourceBufferValid; - } - function isDASHSupported() { - return isHLSSupported(); - } - function isAudioSrc({ src, type }) { - return isString(src) ? AUDIO_EXTENSIONS.test(src) || AUDIO_TYPES.has(type) || src.startsWith("blob:") && type === "audio/object" : type === "audio/object"; - } - function isVideoSrc(src) { - return isString(src.src) ? VIDEO_EXTENSIONS.test(src.src) || VIDEO_TYPES.has(src.type) || src.src.startsWith("blob:") && src.type === "video/object" || isHLSSrc(src) && canPlayHLSNatively() : src.type === "video/object"; - } - function isHLSSrc({ src, type }) { - return isString(src) && HLS_VIDEO_EXTENSIONS.test(src) || HLS_VIDEO_TYPES.has(type); - } - function isDASHSrc({ src, type }) { - return isString(src) && DASH_VIDEO_EXTENSIONS.test(src) || DASH_VIDEO_TYPES.has(type); - } - function canGoogleCastSrc(src) { - return isString(src.src) && (isAudioSrc(src) || isVideoSrc(src) || isHLSSrc(src)); - } - function isMediaStream(src) { - return typeof window.MediaStream !== "undefined" && src instanceof window.MediaStream; - } - var UA, IS_IOS, IS_IPHONE, IS_CHROME, IS_SAFARI, AUDIO_EXTENSIONS, AUDIO_TYPES, VIDEO_EXTENSIONS, VIDEO_TYPES, HLS_VIDEO_EXTENSIONS, DASH_VIDEO_EXTENSIONS, HLS_VIDEO_TYPES, DASH_VIDEO_TYPES; - var init_vidstack_DwhHIY5e = __esm({ - "node_modules/vidstack/prod/chunks/vidstack-DwhHIY5e.js"() { - init_vidstack_CRlI3Mh7(); - UA = navigator?.userAgent.toLowerCase() || ""; - IS_IOS = /iphone|ipad|ipod|ios|crios|fxios/i.test(UA); - IS_IPHONE = /(iphone|ipod)/gi.test(navigator?.platform || ""); - IS_CHROME = !!window.chrome; - IS_SAFARI = !!window.safari || IS_IOS; - AUDIO_EXTENSIONS = /\.(m4a|m4b|mp4a|mpga|mp2|mp2a|mp3|m2a|m3a|wav|weba|aac|oga|spx|flac)($|\?)/i; - AUDIO_TYPES = /* @__PURE__ */ new Set([ - "audio/mpeg", - "audio/ogg", - "audio/3gp", - "audio/mp3", - "audio/webm", - "audio/flac", - "audio/m4a", - "audio/m4b", - "audio/mp4a", - "audio/mp4" - ]); - VIDEO_EXTENSIONS = /\.(mp4|og[gv]|webm|mov|m4v)(#t=[,\d+]+)?($|\?)/i; - VIDEO_TYPES = /* @__PURE__ */ new Set([ - "video/mp4", - "video/webm", - "video/3gp", - "video/ogg", - "video/avi", - "video/mpeg" - ]); - HLS_VIDEO_EXTENSIONS = /\.(m3u8)($|\?)/i; - DASH_VIDEO_EXTENSIONS = /\.(mpd)($|\?)/i; - HLS_VIDEO_TYPES = /* @__PURE__ */ new Set([ - // Apple sanctioned - "application/vnd.apple.mpegurl", - // Apple sanctioned for backwards compatibility - "audio/mpegurl", - // Very common - "audio/x-mpegurl", - // Very common - "application/x-mpegurl", - // Included for completeness - "video/x-mpegurl", - "video/mpegurl", - "application/mpegurl" - ]); - DASH_VIDEO_TYPES = /* @__PURE__ */ new Set(["application/dash+xml"]); - } - }); - - // node_modules/vidstack/prod/chunks/vidstack-BmMUBVGQ.js - function getTimeRangesStart(range) { - if (!range.length) return null; - let min2 = range.start(0); - for (let i4 = 1; i4 < range.length; i4++) { - const value = range.start(i4); - if (value < min2) min2 = value; - } - return min2; - } - function getTimeRangesEnd(range) { - if (!range.length) return null; - let max2 = range.end(0); - for (let i4 = 1; i4 < range.length; i4++) { - const value = range.end(i4); - if (value > max2) max2 = value; - } - return max2; - } - function normalizeTimeIntervals(intervals) { - if (intervals.length <= 1) { - return intervals; - } - intervals.sort((a3, b2) => a3[0] - b2[0]); - let normalized = [], current = intervals[0]; - for (let i4 = 1; i4 < intervals.length; i4++) { - const next = intervals[i4]; - if (current[1] >= next[0] - 1) { - current = [current[0], Math.max(current[1], next[1])]; - } else { - normalized.push(current); - current = next; - } - } - normalized.push(current); - return normalized; - } - function updateTimeIntervals(intervals, interval, value) { - let start = interval[0], end = interval[1]; - if (value < start) { - return [value, -1]; - } else if (value === start) { - return interval; - } else if (start === -1) { - interval[0] = value; - return interval; - } else if (value > start) { - interval[1] = value; - if (end === -1) intervals.push(interval); - } - normalizeTimeIntervals(intervals); - return interval; - } - var TimeRange; - var init_vidstack_BmMUBVGQ = __esm({ - "node_modules/vidstack/prod/chunks/vidstack-BmMUBVGQ.js"() { - init_vidstack_CRlI3Mh7(); - TimeRange = class { - #ranges; - get length() { - return this.#ranges.length; - } - constructor(start, end) { - if (isArray(start)) { - this.#ranges = start; - } else if (!isUndefined(start) && !isUndefined(end)) { - this.#ranges = [[start, end]]; - } else { - this.#ranges = []; - } - } - start(index) { - return this.#ranges[index][0] ?? Infinity; - } - end(index) { - return this.#ranges[index][1] ?? Infinity; - } - }; - } - }); - - // node_modules/vidstack/prod/chunks/vidstack-A9j--j6J.js - function appendParamsToURL(baseUrl, params) { - const url = new URL(baseUrl); - for (const key2 of Object.keys(params)) { - url.searchParams.set(key2, params[key2] + ""); - } - return url.toString(); - } - function preconnect(url, rel = "preconnect") { - const exists = document.querySelector(`link[href="${url}"]`); - if (!isNull(exists)) return true; - const link = document.createElement("link"); - link.rel = rel; - link.href = url; - link.crossOrigin = "true"; - document.head.append(link); - return true; - } - function loadScript(src) { - if (pendingRequests[src]) return pendingRequests[src].promise; - const promise = deferredPromise(), exists = document.querySelector(`script[src="${src}"]`); - if (!isNull(exists)) { - promise.resolve(); - return promise.promise; - } - pendingRequests[src] = promise; - const script = document.createElement("script"); - script.src = src; - script.onload = () => { - promise.resolve(); - delete pendingRequests[src]; - }; - script.onerror = () => { - promise.reject(); - delete pendingRequests[src]; - }; - setTimeout(() => document.head.append(script), 0); - return promise.promise; - } - function getRequestCredentials(crossOrigin) { - return crossOrigin === "use-credentials" ? "include" : isString(crossOrigin) ? "same-origin" : void 0; - } - function getDownloadFile({ - title, - src, - download - }) { - const url = isBoolean(download) || download === "" ? src.src : isString(download) ? download : download?.url; - if (!isValidFileDownload({ url, src, download })) return null; - return { - url, - name: !isBoolean(download) && !isString(download) && download?.filename || title.toLowerCase() || "media" - }; - } - function isValidFileDownload({ - url, - src, - download - }) { - return isString(url) && (download && download !== true || isAudioSrc(src) || isVideoSrc(src)); - } - var pendingRequests; - var init_vidstack_A9j_j6J = __esm({ - "node_modules/vidstack/prod/chunks/vidstack-A9j--j6J.js"() { - init_vidstack_CRlI3Mh7(); - init_vidstack_DwhHIY5e(); - pendingRequests = {}; - } - }); - - // node_modules/vidstack/prod/chunks/vidstack-lwuXewh7.js - function isCueActive(cue, time) { - return time >= cue.startTime && time < cue.endTime; - } - function watchActiveTextTrack(tracks, kind, onChange) { - let currentTrack = null, scope = getScope(); - function onModeChange() { - const kinds = isString(kind) ? [kind] : kind, track = tracks.toArray().find((track2) => kinds.includes(track2.kind) && track2.mode === "showing"); - if (track === currentTrack) return; - if (!track) { - onChange(null); - currentTrack = null; - return; - } - if (track.readyState == 2) { - onChange(track); - } else { - onChange(null); - scoped(() => { - const off = listenEvent( - track, - "load", - () => { - onChange(track); - off(); - }, - { once: true } - ); - }, scope); - } - currentTrack = track; - } - onModeChange(); - return listenEvent(tracks, "mode-change", onModeChange); - } - function watchCueTextChange(tracks, kind, callback) { - watchActiveTextTrack(tracks, kind, (track) => { - if (!track) { - callback(""); - return; - } - const onCueChange = () => { - const activeCue = track?.activeCues[0]; - callback(activeCue?.text || ""); - }; - onCueChange(); - listenEvent(track, "cue-change", onCueChange); - }); - } - var init_vidstack_lwuXewh7 = __esm({ - "node_modules/vidstack/prod/chunks/vidstack-lwuXewh7.js"() { - init_vidstack_CRlI3Mh7(); - } - }); - - // node_modules/media-captions/dist/prod/srt-parser.js - var srt_parser_exports = {}; - __export(srt_parser_exports, { - SRTParser: () => SRTParser, - default: () => createSRTParser - }); - function createSRTParser() { - return new SRTParser(); - } - var MILLISECOND_SEP_RE, TIMESTAMP_SEP, SRTParser; - var init_srt_parser = __esm({ - "node_modules/media-captions/dist/prod/srt-parser.js"() { - init_prod(); - MILLISECOND_SEP_RE = /,/g; - TIMESTAMP_SEP = "-->"; - SRTParser = class extends VTTParser { - parse(line, lineCount) { - if (line === "") { - if (this.c) { - this.l.push(this.c); - this.h.onCue?.(this.c); - this.c = null; - } - this.e = VTTBlock.None; - } else if (this.e === VTTBlock.Cue) { - this.c.text += (this.c.text ? "\n" : "") + line; - } else if (line.includes(TIMESTAMP_SEP)) { - const result = this.q(line, lineCount); - if (result) { - this.c = new VTTCue(result[0], result[1], result[2].join(" ")); - this.c.id = this.n; - this.e = VTTBlock.Cue; - } - } - this.n = line; - } - q(line, lineCount) { - return super.q(line.replace(MILLISECOND_SEP_RE, "."), lineCount); - } - }; - } - }); - - // node_modules/media-captions/dist/prod/errors.js - var errors_exports = {}; - __export(errors_exports, { - ParseErrorBuilder: () => ParseErrorBuilder - }); - var ParseErrorBuilder; - var init_errors = __esm({ - "node_modules/media-captions/dist/prod/errors.js"() { - init_prod(); - ParseErrorBuilder = { - r() { - return new ParseError({ - code: ParseErrorCode.BadSignature, - reason: "missing WEBVTT file header", - line: 1 - }); - }, - s(startTime, line) { - return new ParseError({ - code: ParseErrorCode.BadTimestamp, - reason: `cue start timestamp \`${startTime}\` is invalid on line ${line}`, - line - }); - }, - t(endTime, line) { - return new ParseError({ - code: ParseErrorCode.BadTimestamp, - reason: `cue end timestamp \`${endTime}\` is invalid on line ${line}`, - line - }); - }, - u(startTime, endTime, line) { - return new ParseError({ - code: ParseErrorCode.BadTimestamp, - reason: `cue end timestamp \`${endTime}\` is greater than start \`${startTime}\` on line ${line}`, - line - }); - }, - y(name, value, line) { - return new ParseError({ - code: ParseErrorCode.BadSettingValue, - reason: `invalid value for cue setting \`${name}\` on line ${line} (value: ${value})`, - line - }); - }, - x(name, value, line) { - return new ParseError({ - code: ParseErrorCode.UnknownSetting, - reason: `unknown cue setting \`${name}\` on line ${line} (value: ${value})`, - line - }); - }, - w(name, value, line) { - return new ParseError({ - code: ParseErrorCode.BadSettingValue, - reason: `invalid value for region setting \`${name}\` on line ${line} (value: ${value})`, - line - }); - }, - v(name, value, line) { - return new ParseError({ - code: ParseErrorCode.UnknownSetting, - reason: `unknown region setting \`${name}\` on line ${line} (value: ${value})`, - line - }); - }, - // SSA-specific errors - T(type, line) { - return new ParseError({ - code: ParseErrorCode.BadFormat, - reason: `format missing for \`${type}\` block on line ${line}`, - line - }); - } - }; - } - }); - - // node_modules/media-captions/dist/prod/ssa-parser.js - var ssa_parser_exports = {}; - __export(ssa_parser_exports, { - SSAParser: () => SSAParser, - default: () => createSSAParser - }); - function parseColor(color) { - const abgr = parseInt(color.replace("&H", ""), 16); - if (abgr >= 0) { - const a3 = abgr >> 24 & 255 ^ 255; - const alpha = a3 / 255; - const b2 = abgr >> 16 & 255; - const g2 = abgr >> 8 & 255; - const r4 = abgr & 255; - return "rgba(" + [r4, g2, b2, alpha].join(",") + ")"; - } - return null; - } - function buildTextShadow(x2, y2, color) { - const noOfShadows = Math.ceil(2 * Math.PI * x2); - let textShadow = ""; - for (let i4 = 0; i4 < noOfShadows; i4++) { - const theta = 2 * Math.PI * i4 / noOfShadows; - textShadow += x2 * Math.cos(theta) + "px " + y2 * Math.sin(theta) + "px 0 " + color + (i4 == noOfShadows - 1 ? "" : ","); - } - return textShadow; - } - function createSSAParser() { - return new SSAParser(); - } - var FORMAT_START_RE, STYLE_START_RE, DIALOGUE_START_RE, FORMAT_SPLIT_RE, STYLE_FUNCTION_RE, NEW_LINE_RE, STYLES_SECTION_START_RE, EVENTS_SECTION_START_RE, SSAParser; - var init_ssa_parser = __esm({ - "node_modules/media-captions/dist/prod/ssa-parser.js"() { - init_prod(); - FORMAT_START_RE = /^Format:[\s\t]*/; - STYLE_START_RE = /^Style:[\s\t]*/; - DIALOGUE_START_RE = /^Dialogue:[\s\t]*/; - FORMAT_SPLIT_RE = /[\s\t]*,[\s\t]*/; - STYLE_FUNCTION_RE = /\{[^}]+\}/g; - NEW_LINE_RE = /\\N/g; - STYLES_SECTION_START_RE = /^\[(.*)[\s\t]?Styles\]$/; - EVENTS_SECTION_START_RE = /^\[(.*)[\s\t]?Events\]$/; - SSAParser = class { - h; - O = 0; - c = null; - l = []; - m = []; - N = null; - f; - P = {}; - async init(init) { - this.h = init; - if (init.errors) - this.f = (await Promise.resolve().then(() => (init_errors(), errors_exports))).ParseErrorBuilder; - } - parse(line, lineCount) { - if (this.O) { - switch (this.O) { - case 1: - if (line === "") { - this.O = 0; - } else if (STYLE_START_RE.test(line)) { - if (this.N) { - const styles = line.replace(STYLE_START_RE, "").split(FORMAT_SPLIT_RE); - this.S(styles); - } else { - this.g(this.f?.T("Style", lineCount)); - } - } else if (FORMAT_START_RE.test(line)) { - this.N = line.replace(FORMAT_START_RE, "").split(FORMAT_SPLIT_RE); - } else if (EVENTS_SECTION_START_RE.test(line)) { - this.N = null; - this.O = 2; - } - break; - case 2: - if (line === "") { - this.Q(); - } else if (DIALOGUE_START_RE.test(line)) { - this.Q(); - if (this.N) { - const dialogue = line.replace(DIALOGUE_START_RE, "").split(FORMAT_SPLIT_RE), cue = this.U(dialogue, lineCount); - if (cue) - this.c = cue; - } else { - this.g(this.f?.T("Dialogue", lineCount)); - } - } else if (this.c) { - this.c.text += "\n" + line.replace(STYLE_FUNCTION_RE, "").replace(NEW_LINE_RE, "\n"); - } else if (FORMAT_START_RE.test(line)) { - this.N = line.replace(FORMAT_START_RE, "").split(FORMAT_SPLIT_RE); - } else if (STYLES_SECTION_START_RE.test(line)) { - this.N = null; - this.O = 1; - } else if (EVENTS_SECTION_START_RE.test(line)) { - this.N = null; - } - } - } else if (line === "") ; - else if (STYLES_SECTION_START_RE.test(line)) { - this.N = null; - this.O = 1; - } else if (EVENTS_SECTION_START_RE.test(line)) { - this.N = null; - this.O = 2; - } - } - done() { - return { - metadata: {}, - cues: this.l, - regions: [], - errors: this.m - }; - } - Q() { - if (!this.c) - return; - this.l.push(this.c); - this.h.onCue?.(this.c); - this.c = null; - } - S(values) { - let name = "Default", styles = {}, outlineX, align = "center", vertical = "bottom", marginV, outlineY = 1.2, outlineColor, bgColor, borderStyle = 3, transform = []; - for (let i4 = 0; i4 < this.N.length; i4++) { - const field = this.N[i4], value = values[i4]; - switch (field) { - case "Name": - name = value; - break; - case "Fontname": - styles["font-family"] = value; - break; - case "Fontsize": - styles["font-size"] = `calc(${value} / var(--overlay-height))`; - break; - case "PrimaryColour": - const color = parseColor(value); - if (color) - styles["--cue-color"] = color; - break; - case "BorderStyle": - borderStyle = parseInt(value, 10); - break; - case "BackColour": - bgColor = parseColor(value); - break; - case "OutlineColour": - const _outlineColor = parseColor(value); - if (_outlineColor) - outlineColor = _outlineColor; - break; - case "Bold": - if (parseInt(value)) - styles["font-weight"] = "bold"; - break; - case "Italic": - if (parseInt(value)) - styles["font-style"] = "italic"; - break; - case "Underline": - if (parseInt(value)) - styles["text-decoration"] = "underline"; - break; - case "StrikeOut": - if (parseInt(value)) - styles["text-decoration"] = "line-through"; - break; - case "Spacing": - styles["letter-spacing"] = value + "px"; - break; - case "AlphaLevel": - styles["opacity"] = parseFloat(value); - break; - case "ScaleX": - transform.push(`scaleX(${parseFloat(value) / 100})`); - break; - case "ScaleY": - transform.push(`scaleY(${parseFloat(value) / 100})`); - break; - case "Angle": - transform.push(`rotate(${value}deg)`); - break; - case "Shadow": - outlineY = parseInt(value, 10) * 1.2; - break; - case "MarginL": - styles["--cue-width"] = "auto"; - styles["--cue-left"] = parseFloat(value) + "px"; - break; - case "MarginR": - styles["--cue-width"] = "auto"; - styles["--cue-right"] = parseFloat(value) + "px"; - break; - case "MarginV": - marginV = parseFloat(value); - break; - case "Outline": - outlineX = parseInt(value, 10); - break; - case "Alignment": - const alignment = parseInt(value, 10); - if (alignment >= 4) - vertical = alignment >= 7 ? "top" : "center"; - switch (alignment % 3) { - case 1: - align = "start"; - break; - case 2: - align = "center"; - break; - case 3: - align = "end"; - break; - } - } - } - styles.R = vertical; - styles["--cue-white-space"] = "normal"; - styles["--cue-line-height"] = "normal"; - styles["--cue-text-align"] = align; - if (vertical === "center") { - styles[`--cue-top`] = "50%"; - transform.push("translateY(-50%)"); - } else { - styles[`--cue-${vertical}`] = (marginV || 0) + "px"; - } - if (borderStyle === 1) { - styles["--cue-padding-y"] = "0"; - } - if (borderStyle === 1 || bgColor) { - styles["--cue-bg-color"] = borderStyle === 1 ? "none" : bgColor; - } - if (borderStyle === 3 && outlineColor) { - styles["--cue-outline"] = `${outlineX}px solid ${outlineColor}`; - } - if (borderStyle === 1 && typeof outlineX === "number") { - const color = bgColor ?? "#000"; - styles["--cue-text-shadow"] = [ - outlineColor && buildTextShadow(outlineX * 1.2, outlineY * 1.2, outlineColor), - outlineColor ? buildTextShadow(outlineX * (outlineX / 2), outlineY * (outlineX / 2), color) : buildTextShadow(outlineX, outlineY, color) - ].filter(Boolean).join(", "); - } - if (transform.length) - styles["--cue-transform"] = transform.join(" "); - this.P[name] = styles; - } - U(values, lineCount) { - const fields = this.V(values); - const timestamp = this.q(fields.Start, fields.End, lineCount); - if (!timestamp) - return; - const cue = new VTTCue(timestamp[0], timestamp[1], ""), styles = { ...this.P[fields.Style] || {} }, voice = fields.Name ? `` : ""; - const vertical = styles.R, marginLeft = fields.MarginL && parseFloat(fields.MarginL), marginRight = fields.MarginR && parseFloat(fields.MarginR), marginV = fields.MarginV && parseFloat(fields.MarginV); - if (marginLeft) { - styles["--cue-width"] = "auto"; - styles["--cue-left"] = marginLeft + "px"; - } - if (marginRight) { - styles["--cue-width"] = "auto"; - styles["--cue-right"] = marginRight + "px"; - } - if (marginV && vertical !== "center") { - styles[`--cue-${vertical}`] = marginV + "px"; - } - cue.text = voice + values.slice(this.N.length - 1).join(", ").replace(STYLE_FUNCTION_RE, "").replace(NEW_LINE_RE, "\n"); - delete styles.R; - if (Object.keys(styles).length) - cue.style = styles; - return cue; - } - V(values) { - const fields = {}; - for (let i4 = 0; i4 < this.N.length; i4++) { - fields[this.N[i4]] = values[i4]; - } - return fields; - } - q(startTimeText, endTimeText, lineCount) { - const startTime = parseVTTTimestamp(startTimeText), endTime = parseVTTTimestamp(endTimeText); - if (startTime !== null && endTime !== null && endTime > startTime) { - return [startTime, endTime]; - } else { - if (startTime === null) { - this.g(this.f?.s(startTimeText, lineCount)); - } - if (endTime === null) { - this.g(this.f?.t(endTimeText, lineCount)); - } - if (startTime != null && endTime !== null && endTime > startTime) { - this.g(this.f?.u(startTime, endTime, lineCount)); - } - } - } - g(error) { - if (!error) - return; - this.m.push(error); - if (this.h.strict) { - this.h.cancel(); - throw error; - } else { - this.h.onError?.(error); - } - } - }; - } - }); - - // node_modules/media-captions/dist/prod/index.js - async function parseText(text, options) { - const stream = new ReadableStream({ - start(controller) { - const lines = text.split(LINE_TERMINATOR_RE); - for (const line of lines) - controller.enqueue(line); - controller.close(); - } - }); - return parseTextStream(stream, options); - } - async function parseTextStream(stream, options) { - const type = options?.type ?? "vtt"; - let factory; - if (typeof type === "string") { - switch (type) { - case "srt": - factory = (await Promise.resolve().then(() => (init_srt_parser(), srt_parser_exports))).default; - break; - case "ssa": - case "ass": - factory = (await Promise.resolve().then(() => (init_ssa_parser(), ssa_parser_exports))).default; - break; - default: - factory = (await Promise.resolve().then(function() { - return vttParser; - })).default; - } - } else { - factory = type; - } - let result; - const reader = stream.getReader(), parser = factory(), errors = !!options?.strict || !!options?.errors; - await parser.init({ - strict: false, - ...options, - errors, - type, - cancel() { - reader.cancel(); - result = parser.done(true); - } - }); - let i4 = 1; - while (true) { - const { value, done } = await reader.read(); - if (done) { - parser.parse("", i4); - result = parser.done(false); - break; - } - parser.parse(value, i4); - i4++; - } - return result; - } - async function parseResponse(response, options) { - const res = await response; - if (!res.ok || !res.body) { - let error; - return { - metadata: {}, - cues: [], - regions: [], - errors: [error] - }; - } - const contentType = res.headers.get("content-type") || "", type = contentType.match(/text\/(.*?)(?:;|$)/)?.[1], encoding = contentType.match(/charset=(.*?)(?:;|$)/)?.[1]; - return parseByteStream(res.body, { type, encoding, ...options }); - } - async function parseByteStream(stream, { encoding = "utf-8", ...options } = {}) { - const textStream = stream.pipeThrough(new TextLineTransformStream(encoding)); - return parseTextStream(textStream, options); - } - function toNumber(text) { - const num = parseInt(text, 10); - return !Number.isNaN(num) ? num : null; - } - function toPercentage(text) { - const num = parseInt(text.replace(PERCENT_SIGN$1, ""), 10); - return !Number.isNaN(num) && num >= 0 && num <= 100 ? num : null; - } - function toCoords(text) { - if (!text.includes(COMMA$1)) - return null; - const [x2, y2] = text.split(COMMA$1).map(toPercentage); - return x2 !== null && y2 !== null ? [x2, y2] : null; - } - function toFloat(text) { - const num = parseFloat(text); - return !Number.isNaN(num) ? num : null; - } - function parseVTTTimestamp(timestamp) { - const match = timestamp.match(TIMESTAMP_RE); - if (!match) - return null; - const hours = match[1] ? parseInt(match[1], 10) : 0, minutes = parseInt(match[2], 10), seconds = parseInt(match[3], 10), milliseconds = match[4] ? parseInt(match[4].padEnd(3, "0"), 10) : 0, total = hours * 3600 + minutes * 60 + seconds + milliseconds / 1e3; - if (hours < 0 || minutes < 0 || seconds < 0 || milliseconds < 0 || minutes > 59 || seconds > 59) { - return null; - } - return total; - } - function createVTTParser() { - return new VTTParser(); - } - function tokenizeVTTCue(cue) { - let buffer = "", mode = 1, result = [], stack = [], node; - for (let i4 = 0; i4 < cue.text.length; i4++) { - const char = cue.text[i4]; - switch (mode) { - case 1: - if (char === "<") { - addText(); - mode = 2; - } else { - buffer += char; - } - break; - case 2: - switch (char) { - case "\n": - case " ": - case " ": - addNode(); - mode = 4; - break; - case ".": - addNode(); - mode = 3; - break; - case "/": - mode = 5; - break; - case ">": - addNode(); - mode = 1; - break; - default: - if (!buffer && DIGIT_RE.test(char)) - mode = 6; - buffer += char; - break; - } - break; - case 3: - switch (char) { - case " ": - case " ": - case "\n": - addClass(); - if (node) - node.class?.trim(); - mode = 4; - break; - case ".": - addClass(); - break; - case ">": - addClass(); - if (node) - node.class?.trim(); - mode = 1; - break; - default: - buffer += char; - } - break; - case 4: - if (char === ">") { - buffer = buffer.replace(MULTI_SPACE_RE, " "); - if (node?.type === "v") - node.voice = replaceHTMLEntities(buffer); - else if (node?.type === "lang") - node.lang = replaceHTMLEntities(buffer); - buffer = ""; - mode = 1; - } else { - buffer += char; - } - break; - case 5: - if (char === ">") { - buffer = ""; - node = stack.pop(); - mode = 1; - } - break; - case 6: - if (char === ">") { - const time = parseVTTTimestamp(buffer); - if (time !== null && time >= cue.startTime && time <= cue.endTime) { - buffer = "timestamp"; - addNode(); - node.time = time; - } - buffer = ""; - mode = 1; - } else { - buffer += char; - } - break; - } - } - function addNode() { - if (BLOCK_TYPES.has(buffer)) { - const parent = node; - node = createBlockNode(buffer); - if (parent) { - if (stack[stack.length - 1] !== parent) - stack.push(parent); - parent.children.push(node); - } else - result.push(node); - } - buffer = ""; - mode = 1; - } - function addClass() { - if (node && buffer) { - const color = buffer.replace("bg_", ""); - if (COLORS.has(color)) { - node[buffer.startsWith("bg_") ? "bgColor" : "color"] = color; - } else { - node.class = !node.class ? buffer : node.class + " " + buffer; - } - } - buffer = ""; - } - function addText() { - if (!buffer) - return; - const text = { type: "text", data: replaceHTMLEntities(buffer) }; - node ? node.children.push(text) : result.push(text); - buffer = ""; - } - if (mode === 1) - addText(); - return result; - } - function createBlockNode(type) { - return { - tagName: TAG_NAME[type], - type, - children: [] - }; - } - function replaceHTMLEntities(text) { - return text.replace(HTML_ENTITY_RE, (entity) => HTML_ENTITIES[entity] || "'"); - } - function setCSSVar(el, name, value) { - el.style.setProperty(`--${name}`, value + ""); - } - function setDataAttr(el, name, value = true) { - el.setAttribute(`data-${name}`, value === true ? "" : value + ""); - } - function setPartAttr(el, name) { - el.setAttribute("data-part", name); - } - function getLineHeight(el) { - return parseFloat(getComputedStyle(el).lineHeight) || 0; - } - function createVTTCueTemplate(cue) { - if (IS_SERVER) { - throw Error( - "[media-captions] called `createVTTCueTemplate` on the server - use `renderVTTCueString`" - ); - } - const template = document.createElement("template"); - template.innerHTML = renderVTTCueString(cue); - return { cue, content: template.content }; - } - function renderVTTCueString(cue, currentTime = 0) { - return renderVTTTokensString(tokenizeVTTCue(cue), currentTime); - } - function renderVTTTokensString(tokens, currentTime = 0) { - let attrs, result = ""; - for (const token of tokens) { - if (token.type === "text") { - result += token.data; - } else { - const isTimestamp = token.type === "timestamp"; - attrs = {}; - attrs.class = token.class; - attrs.title = token.type === "v" && token.voice; - attrs.lang = token.type === "lang" && token.lang; - attrs["data-part"] = token.type === "v" && "voice"; - if (isTimestamp) { - attrs["data-part"] = "timed"; - attrs["data-time"] = token.time; - attrs["data-future"] = token.time > currentTime; - attrs["data-past"] = token.time < currentTime; - } - attrs.style = `${token.color ? `color: ${token.color};` : ""}${token.bgColor ? `background-color: ${token.bgColor};` : ""}`; - const attributes = Object.entries(attrs).filter((v2) => v2[1]).map((v2) => `${v2[0]}="${v2[1] === true ? "" : v2[1]}"`).join(" "); - result += `<${token.tagName}${attributes ? " " + attributes : ""}>${renderVTTTokensString( - token.children - )}`; - } - } - return result; - } - function updateTimedVTTCueNodes(root2, currentTime) { - if (IS_SERVER) - return; - for (const el of root2.querySelectorAll('[data-part="timed"]')) { - const time = Number(el.getAttribute("data-time")); - if (Number.isNaN(time)) - continue; - if (time > currentTime) - setDataAttr(el, "future"); - else - el.removeAttribute("data-future"); - if (time < currentTime) - setDataAttr(el, "past"); - else - el.removeAttribute("data-past"); - } - } - function debounce2(fn, delay) { - let timeout = null, args; - function run() { - clear(); - fn(...args); - args = void 0; - } - function clear() { - clearTimeout(timeout); - timeout = null; - } - function debounce22() { - args = [].slice.call(arguments); - clear(); - timeout = setTimeout(run, delay); - } - return debounce22; - } - function createBox(box) { - if (box instanceof HTMLElement) { - return { - top: box.offsetTop, - width: box.clientWidth, - height: box.clientHeight, - left: box.offsetLeft, - right: box.offsetLeft + box.clientWidth, - bottom: box.offsetTop + box.clientHeight - }; - } - return { ...box }; - } - function moveBox(box, axis, delta) { - switch (axis) { - case "+x": - box.left += delta; - box.right += delta; - break; - case "-x": - box.left -= delta; - box.right -= delta; - break; - case "+y": - box.top += delta; - box.bottom += delta; - break; - case "-y": - box.top -= delta; - box.bottom -= delta; - break; - } - } - function isBoxCollision(a3, b2) { - return a3.left <= b2.right && a3.right >= b2.left && a3.top <= b2.bottom && a3.bottom >= b2.top; - } - function isAnyBoxCollision(box, boxes) { - for (let i4 = 0; i4 < boxes.length; i4++) - if (isBoxCollision(box, boxes[i4])) - return boxes[i4]; - return null; - } - function isWithinBox(container, box) { - return box.top >= 0 && box.bottom <= container.height && box.left >= 0 && box.right <= container.width; - } - function isBoxOutOfBounds(container, box, axis) { - switch (axis) { - case "+x": - return box.left < 0; - case "-x": - return box.right > container.width; - case "+y": - return box.top < 0; - case "-y": - return box.bottom > container.height; - } - } - function calcBoxIntersectPercentage(container, box) { - const x2 = Math.max(0, Math.min(container.width, box.right) - Math.max(0, box.left)), y2 = Math.max(0, Math.min(container.height, box.bottom) - Math.max(0, box.top)), intersectArea = x2 * y2; - return intersectArea / (container.height * container.width); - } - function createCSSBox(container, box) { - return { - top: box.top / container.height, - left: box.left / container.width, - right: (container.width - box.right) / container.width, - bottom: (container.height - box.bottom) / container.height - }; - } - function resolveRelativeBox(container, box) { - box.top = box.top * container.height; - box.left = box.left * container.width; - box.right = container.width - box.right * container.width; - box.bottom = container.height - box.bottom * container.height; - return box; - } - function setBoxCSSVars(el, container, box, prefix) { - const cssBox = createCSSBox(container, box); - for (const side of BOX_SIDES) { - setCSSVar(el, `${prefix}-${side}`, cssBox[side] * 100 + "%"); - } - } - function avoidBoxCollisions(container, box, boxes, axis) { - let percentage = 1, positionedBox, startBox = { ...box }; - for (let i4 = 0; i4 < axis.length; i4++) { - while (isBoxOutOfBounds(container, box, axis[i4]) || isWithinBox(container, box) && isAnyBoxCollision(box, boxes)) { - moveBox(box, axis[i4], 1); - } - if (isWithinBox(container, box)) - return box; - const intersection = calcBoxIntersectPercentage(container, box); - if (percentage > intersection) { - positionedBox = { ...box }; - percentage = intersection; - } - box = { ...startBox }; - } - return positionedBox || startBox; - } - function positionCue(container, cue, displayEl, boxes) { - let cueEl = displayEl.firstElementChild, line = computeCueLine(cue), displayBox, axis = []; - if (!displayEl[STARTING_BOX]) { - displayEl[STARTING_BOX] = createStartingBox(container, displayEl); - } - displayBox = resolveRelativeBox(container, { ...displayEl[STARTING_BOX] }); - if (displayEl[POSITION_OVERRIDE]) { - axis = [displayEl[POSITION_OVERRIDE] === "top" ? "+y" : "-y", "+x", "-x"]; - } else if (cue.snapToLines) { - let size2; - switch (cue.vertical) { - case "": - axis = ["+y", "-y"]; - size2 = "height"; - break; - case "rl": - axis = ["+x", "-x"]; - size2 = "width"; - break; - case "lr": - axis = ["-x", "+x"]; - size2 = "width"; - break; - } - let step = getLineHeight(cueEl), position = step * Math.round(line), maxPosition = container[size2] + step, initialAxis = axis[0]; - if (Math.abs(position) > maxPosition) { - position = position < 0 ? -1 : 1; - position *= Math.ceil(maxPosition / step) * step; - } - if (line < 0) { - position += cue.vertical === "" ? container.height : container.width; - axis = axis.reverse(); - } - moveBox(displayBox, initialAxis, position); - } else { - const isHorizontal = cue.vertical === "", posAxis = isHorizontal ? "+y" : "+x", size2 = isHorizontal ? displayBox.height : displayBox.width; - moveBox( - displayBox, - posAxis, - (isHorizontal ? container.height : container.width) * line / 100 - ); - moveBox( - displayBox, - posAxis, - cue.lineAlign === "center" ? size2 / 2 : cue.lineAlign === "end" ? size2 : 0 - ); - axis = isHorizontal ? ["-y", "+y", "-x", "+x"] : ["-x", "+x", "-y", "+y"]; - } - displayBox = avoidBoxCollisions(container, displayBox, boxes, axis); - setBoxCSSVars(displayEl, container, displayBox, "cue"); - return displayBox; - } - function createStartingBox(container, cueEl) { - const box = createBox(cueEl), pos = getStyledPositions(cueEl); - cueEl[POSITION_OVERRIDE] = false; - if (pos.top) { - box.top = pos.top; - box.bottom = pos.top + box.height; - cueEl[POSITION_OVERRIDE] = "top"; - } - if (pos.bottom) { - const bottom = container.height - pos.bottom; - box.top = bottom - box.height; - box.bottom = bottom; - cueEl[POSITION_OVERRIDE] = "bottom"; - } - if (pos.left) - box.left = pos.left; - if (pos.right) - box.right = container.width - pos.right; - return createCSSBox(container, box); - } - function getStyledPositions(el) { - const positions = {}; - for (const side of BOX_SIDES) { - positions[side] = parseFloat(el.style.getPropertyValue(`--cue-${side}`)); - } - return positions; - } - function computeCueLine(cue) { - if (cue.line === "auto") { - if (!cue.snapToLines) { - return 100; - } else { - return -1; - } - } - return cue.line; - } - function computeCuePosition(cue) { - if (cue.position === "auto") { - switch (cue.align) { - case "start": - case "left": - return 0; - case "right": - case "end": - return 100; - default: - return 50; - } - } - return cue.position; - } - function computeCuePositionAlignment(cue, dir) { - if (cue.positionAlign === "auto") { - switch (cue.align) { - case "start": - return dir === "ltr" ? "line-left" : "line-right"; - case "end": - return dir === "ltr" ? "line-right" : "line-left"; - case "center": - return "center"; - default: - return `line-${cue.align}`; - } - } - return cue.positionAlign; - } - function positionRegion(container, region, regionEl, boxes) { - let cues = Array.from(regionEl.querySelectorAll('[data-part="cue-display"]')), height = 0, limit = Math.max(0, cues.length - region.lines); - for (let i4 = cues.length - 1; i4 >= limit; i4--) { - height += cues[i4].offsetHeight; - } - setCSSVar(regionEl, "region-height", height + "px"); - if (!regionEl[STARTING_BOX]) { - regionEl[STARTING_BOX] = createCSSBox(container, createBox(regionEl)); - } - let box = { ...regionEl[STARTING_BOX] }; - box = resolveRelativeBox(container, box); - box.width = regionEl.clientWidth; - box.height = height; - box.right = box.left + box.width; - box.bottom = box.top + height; - box = avoidBoxCollisions(container, box, boxes, REGION_AXIS); - setBoxCSSVars(regionEl, container, box, "region"); - return box; - } - var ParseErrorCode, ParseError, LINE_TERMINATOR_RE, TextLineTransformStream, TextStreamLineIterator, TextCue, IS_SERVER, CueBase, VTTCue, VTTRegion, COMMA$1, PERCENT_SIGN$1, HEADER_MAGIC, COMMA, PERCENT_SIGN, SETTING_SEP_RE, SETTING_LINE_RE, NOTE_BLOCK_START, REGION_BLOCK_START, REGION_BLOCK_START_RE, SPACE_RE, TIMESTAMP_SEP2, TIMESTAMP_SEP_RE, ALIGN_RE, LINE_ALIGN_RE, POS_ALIGN_RE, TIMESTAMP_RE, VTTBlock, VTTParser, vttParser, DIGIT_RE, MULTI_SPACE_RE, TAG_NAME, HTML_ENTITIES, HTML_ENTITY_RE, COLORS, BLOCK_TYPES, STARTING_BOX, BOX_SIDES, POSITION_OVERRIDE, REGION_AXIS, CaptionsRenderer; - var init_prod = __esm({ - "node_modules/media-captions/dist/prod/index.js"() { - ParseErrorCode = { - LoadFail: 0, - BadSignature: 1, - BadTimestamp: 2, - BadSettingValue: 3, - BadFormat: 4, - UnknownSetting: 5 - }; - ParseError = class extends Error { - code; - line; - constructor(init) { - super(init.reason); - this.code = init.code; - this.line = init.line; - } - }; - LINE_TERMINATOR_RE = /\r?\n|\r/gm; - TextLineTransformStream = class { - writable; - readable; - constructor(encoding) { - const transformer = new TextStreamLineIterator(encoding); - this.writable = new WritableStream({ - write(chunk) { - transformer.transform(chunk); - }, - close() { - transformer.close(); - } - }); - this.readable = new ReadableStream({ - start(controller) { - transformer.onLine = (line) => controller.enqueue(line); - transformer.onClose = () => controller.close(); - } - }); - } - }; - TextStreamLineIterator = class { - a = ""; - b; - onLine; - onClose; - constructor(encoding) { - this.b = new TextDecoder(encoding); - } - transform(chunk) { - this.a += this.b.decode(chunk, { stream: true }); - const lines = this.a.split(LINE_TERMINATOR_RE); - this.a = lines.pop() || ""; - for (let i4 = 0; i4 < lines.length; i4++) - this.onLine(lines[i4].trim()); - } - close() { - if (this.a) - this.onLine(this.a.trim()); - this.a = ""; - this.onClose(); - } - }; - TextCue = class extends EventTarget { - /** - * A string that identifies the cue. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/TextTrackCue/id} - */ - id = ""; - /** - * A `double` that represents the video time that the cue will start being displayed, in seconds. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/TextTrackCue/startTime} - */ - startTime; - /** - * A `double` that represents the video time that the cue will stop being displayed, in seconds. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/TextTrackCue/endTime} - */ - endTime; - /** - * Returns a string with the contents of the cue. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/text} - */ - text; - /** - * A `boolean` for whether the video will pause when this cue stops being displayed. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/TextTrackCue/pauseOnExit} - */ - pauseOnExit = false; - constructor(startTime, endTime, text) { - super(); - this.startTime = startTime; - this.endTime = endTime; - this.text = text; - } - addEventListener(type, listener, options) { - super.addEventListener(type, listener, options); - } - removeEventListener(type, listener, options) { - super.removeEventListener(type, listener, options); - } - }; - IS_SERVER = typeof document === "undefined"; - CueBase = IS_SERVER ? TextCue : window.VTTCue; - VTTCue = class extends CueBase { - /** - * A `VTTRegion` object describing the video's sub-region that the cue will be drawn onto, - * or `null` if none is assigned. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/region} - */ - region = null; - /** - * The cue writing direction. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/vertical} - */ - vertical = ""; - /** - * Returns `true` if the `VTTCue.line` attribute is an integer number of lines or a percentage - * of the video size. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/snapToLines} - */ - snapToLines = true; - /** - * Returns the line positioning of the cue. This can be the string `'auto'` or a number whose - * interpretation depends on the value of `VTTCue.snapToLines`. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/line} - */ - line = "auto"; - /** - * Returns an enum representing the alignment of the `VTTCue.line`. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/lineAlign} - */ - lineAlign = "start"; - /** - * Returns the indentation of the cue within the line. This can be the string `'auto'` or a - * number representing the percentage of the `VTTCue.region`, or the video size if `VTTCue`.region` - * is `null`. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/position} - */ - position = "auto"; - /** - * Returns an enum representing the alignment of the cue. This is used to determine what - * the `VTTCue.position` is anchored to. The default is `'auto'`. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/positionAlign} - */ - positionAlign = "auto"; - /** - * Returns a double representing the size of the cue, as a percentage of the video size. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/size} - */ - size = 100; - /** - * Returns an enum representing the alignment of all the lines of text within the cue box. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/VTTCue/align} - */ - align = "center"; - /** - * Additional styles associated with the cue. - */ - style; - }; - VTTRegion = class { - /** - * A string that identifies the region. - */ - id = ""; - /** - * A `double` representing the width of the region, as a percentage of the video. - */ - width = 100; - /** - * A `double` representing the height of the region, in number of lines. - */ - lines = 3; - /** - * A `double` representing the region anchor X offset, as a percentage of the region. - */ - regionAnchorX = 0; - /** - * A `double` representing the region anchor Y offset, as a percentage of the region. - */ - regionAnchorY = 100; - /** - * A `double` representing the viewport anchor X offset, as a percentage of the video. - */ - viewportAnchorX = 0; - /** - * A `double` representing the viewport anchor Y offset, as a percentage of the video. - */ - viewportAnchorY = 100; - /** - * An enum representing how adding new cues will move existing cues. - */ - scroll = ""; - }; - COMMA$1 = ","; - PERCENT_SIGN$1 = "%"; - HEADER_MAGIC = "WEBVTT"; - COMMA = ","; - PERCENT_SIGN = "%"; - SETTING_SEP_RE = /[:=]/; - SETTING_LINE_RE = /^[\s\t]*(region|vertical|line|position|size|align)[:=]/; - NOTE_BLOCK_START = "NOTE"; - REGION_BLOCK_START = "REGION"; - REGION_BLOCK_START_RE = /^REGION:?[\s\t]+/; - SPACE_RE = /[\s\t]+/; - TIMESTAMP_SEP2 = "-->"; - TIMESTAMP_SEP_RE = /[\s\t]*-->[\s\t]+/; - ALIGN_RE = /start|center|end|left|right/; - LINE_ALIGN_RE = /start|center|end/; - POS_ALIGN_RE = /line-(?:left|right)|center|auto/; - TIMESTAMP_RE = /^(?:(\d{1,2}):)?(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/; - VTTBlock = /* @__PURE__ */ ((VTTBlock2) => { - VTTBlock2[VTTBlock2["None"] = 0] = "None"; - VTTBlock2[VTTBlock2["Header"] = 1] = "Header"; - VTTBlock2[VTTBlock2["Cue"] = 2] = "Cue"; - VTTBlock2[VTTBlock2["Region"] = 3] = "Region"; - VTTBlock2[VTTBlock2["Note"] = 4] = "Note"; - return VTTBlock2; - })(VTTBlock || {}); - VTTParser = class { - h; - e = 0; - i = {}; - j = {}; - l = []; - c = null; - d = null; - m = []; - f; - n = ""; - async init(init) { - this.h = init; - if (init.strict) - this.e = 1; - if (init.errors) - this.f = (await Promise.resolve().then(() => (init_errors(), errors_exports))).ParseErrorBuilder; - } - parse(line, lineCount) { - if (line === "") { - if (this.c) { - this.l.push(this.c); - this.h.onCue?.(this.c); - this.c = null; - } else if (this.d) { - this.j[this.d.id] = this.d; - this.h.onRegion?.(this.d); - this.d = null; - } else if (this.e === 1) { - this.k(line, lineCount); - this.h.onHeaderMetadata?.(this.i); - } - this.e = 0; - } else if (this.e) { - switch (this.e) { - case 1: - this.k(line, lineCount); - break; - case 2: - if (this.c) { - const hasText = this.c.text.length > 0; - if (!hasText && SETTING_LINE_RE.test(line)) { - this.o(line.split(SPACE_RE), lineCount); - } else { - this.c.text += (hasText ? "\n" : "") + line; - } - } - break; - case 3: - this.p(line.split(SPACE_RE), lineCount); - break; - } - } else if (line.startsWith(NOTE_BLOCK_START)) { - this.e = 4; - } else if (line.startsWith(REGION_BLOCK_START)) { - this.e = 3; - this.d = new VTTRegion(); - this.p(line.replace(REGION_BLOCK_START_RE, "").split(SPACE_RE), lineCount); - } else if (line.includes(TIMESTAMP_SEP2)) { - const result = this.q(line, lineCount); - if (result) { - this.c = new VTTCue(result[0], result[1], ""); - this.c.id = this.n; - this.o(result[2], lineCount); - } - this.e = 2; - } else if (lineCount === 1) { - this.k(line, lineCount); - } - this.n = line; - } - done() { - return { - metadata: this.i, - cues: this.l, - regions: Object.values(this.j), - errors: this.m - }; - } - k(line, lineCount) { - if (lineCount > 1) { - if (SETTING_SEP_RE.test(line)) { - const [key2, value] = line.split(SETTING_SEP_RE); - if (key2) - this.i[key2] = (value || "").replace(SPACE_RE, ""); - } - } else if (line.startsWith(HEADER_MAGIC)) { - this.e = 1; - } else { - this.g(this.f?.r()); - } - } - q(line, lineCount) { - const [startTimeText, trailingText = ""] = line.split(TIMESTAMP_SEP_RE), [endTimeText, ...settingsText] = trailingText.split(SPACE_RE), startTime = parseVTTTimestamp(startTimeText), endTime = parseVTTTimestamp(endTimeText); - if (startTime !== null && endTime !== null && endTime > startTime) { - return [startTime, endTime, settingsText]; - } else { - if (startTime === null) { - this.g(this.f?.s(startTimeText, lineCount)); - } - if (endTime === null) { - this.g(this.f?.t(endTimeText, lineCount)); - } - if (startTime != null && endTime !== null && endTime > startTime) { - this.g(this.f?.u(startTime, endTime, lineCount)); - } - } - } - /** - * @see {@link https://www.w3.org/TR/webvtt1/#region-settings-parsing} - */ - p(settings, line) { - let badValue; - for (let i4 = 0; i4 < settings.length; i4++) { - if (SETTING_SEP_RE.test(settings[i4])) { - badValue = false; - const [name, value] = settings[i4].split(SETTING_SEP_RE); - switch (name) { - case "id": - this.d.id = value; - break; - case "width": - const width = toPercentage(value); - if (width !== null) - this.d.width = width; - else - badValue = true; - break; - case "lines": - const lines = toNumber(value); - if (lines !== null) - this.d.lines = lines; - else - badValue = true; - break; - case "regionanchor": - const region = toCoords(value); - if (region !== null) { - this.d.regionAnchorX = region[0]; - this.d.regionAnchorY = region[1]; - } else - badValue = true; - break; - case "viewportanchor": - const viewport = toCoords(value); - if (viewport !== null) { - this.d.viewportAnchorX = viewport[0]; - this.d.viewportAnchorY = viewport[1]; - } else - badValue = true; - break; - case "scroll": - if (value === "up") - this.d.scroll = "up"; - else - badValue = true; - break; - default: - this.g(this.f?.v(name, value, line)); - } - if (badValue) { - this.g(this.f?.w(name, value, line)); - } - } - } - } - /** - * @see {@link https://www.w3.org/TR/webvtt1/#cue-timings-and-settings-parsing} - */ - o(settings, line) { - let badValue; - for (let i4 = 0; i4 < settings.length; i4++) { - badValue = false; - if (SETTING_SEP_RE.test(settings[i4])) { - const [name, value] = settings[i4].split(SETTING_SEP_RE); - switch (name) { - case "region": - const region = this.j[value]; - if (region) - this.c.region = region; - break; - case "vertical": - if (value === "lr" || value === "rl") { - this.c.vertical = value; - this.c.region = null; - } else - badValue = true; - break; - case "line": - const [linePos, lineAlign] = value.split(COMMA); - if (linePos.includes(PERCENT_SIGN)) { - const percentage = toPercentage(linePos); - if (percentage !== null) { - this.c.line = percentage; - this.c.snapToLines = false; - } else - badValue = true; - } else { - const number = toFloat(linePos); - if (number !== null) - this.c.line = number; - else - badValue = true; - } - if (LINE_ALIGN_RE.test(lineAlign)) { - this.c.lineAlign = lineAlign; - } else if (lineAlign) { - badValue = true; - } - if (this.c.line !== "auto") - this.c.region = null; - break; - case "position": - const [colPos, colAlign] = value.split(COMMA), position = toPercentage(colPos); - if (position !== null) - this.c.position = position; - else - badValue = true; - if (colAlign && POS_ALIGN_RE.test(colAlign)) { - this.c.positionAlign = colAlign; - } else if (colAlign) { - badValue = true; - } - break; - case "size": - const size2 = toPercentage(value); - if (size2 !== null) { - this.c.size = size2; - if (size2 < 100) - this.c.region = null; - } else { - badValue = true; - } - break; - case "align": - if (ALIGN_RE.test(value)) { - this.c.align = value; - } else { - badValue = true; - } - break; - default: - this.g(this.f?.x(name, value, line)); - } - if (badValue) { - this.g(this.f?.y(name, value, line)); - } - } - } - } - g(error) { - if (!error) - return; - this.m.push(error); - if (this.h.strict) { - this.h.cancel(); - throw error; - } else { - this.h.onError?.(error); - } - } - }; - vttParser = /* @__PURE__ */ Object.freeze({ - __proto__: null, - VTTBlock, - VTTParser, - default: createVTTParser, - parseVTTTimestamp - }); - DIGIT_RE = /[0-9]/; - MULTI_SPACE_RE = /[\s\t]+/; - TAG_NAME = { - c: "span", - i: "i", - b: "b", - u: "u", - ruby: "ruby", - rt: "rt", - v: "span", - lang: "span", - timestamp: "span" - }; - HTML_ENTITIES = { - "&": "&", - "<": "<", - ">": ">", - """: '"', - "'": "'", - " ": "\xA0", - "‎": "\u200E", - "‏": "\u200F" - }; - HTML_ENTITY_RE = /&(?:amp|lt|gt|quot|#(0+)?39|nbsp|lrm|rlm);/g; - COLORS = /* @__PURE__ */ new Set([ - "white", - "lime", - "cyan", - "red", - "yellow", - "magenta", - "blue", - "black" - ]); - BLOCK_TYPES = /* @__PURE__ */ new Set(Object.keys(TAG_NAME)); - STARTING_BOX = Symbol(0); - BOX_SIDES = ["top", "left", "right", "bottom"]; - POSITION_OVERRIDE = Symbol(0); - REGION_AXIS = ["-y", "+y", "-x", "+x"]; - CaptionsRenderer = class { - overlay; - z; - A = 0; - C = "ltr"; - B = []; - D = false; - E; - j = /* @__PURE__ */ new Map(); - l = /* @__PURE__ */ new Map(); - /* Text direction. */ - get dir() { - return this.C; - } - set dir(dir) { - this.C = dir; - setDataAttr(this.overlay, "dir", dir); - } - get currentTime() { - return this.A; - } - set currentTime(time) { - this.A = time; - this.update(); - } - constructor(overlay, init) { - this.overlay = overlay; - this.dir = init?.dir ?? "ltr"; - overlay.setAttribute("translate", "yes"); - overlay.setAttribute("aria-live", "off"); - overlay.setAttribute("aria-atomic", "true"); - setPartAttr(overlay, "captions"); - this.G(); - this.E = new ResizeObserver(this.I.bind(this)); - this.E.observe(overlay); - } - changeTrack({ regions, cues }) { - this.reset(); - this.J(regions); - for (const cue of cues) - this.l.set(cue, null); - this.update(); - } - addCue(cue) { - this.l.set(cue, null); - this.update(); - } - removeCue(cue) { - this.l.delete(cue); - this.update(); - } - update(forceUpdate = false) { - this.H(forceUpdate); - } - reset() { - this.l.clear(); - this.j.clear(); - this.B = []; - this.overlay.textContent = ""; - } - destroy() { - this.reset(); - this.E.disconnect(); - } - I() { - this.D = true; - this.K(); - } - K = debounce2(() => { - this.D = false; - this.G(); - for (const el of this.j.values()) { - el[STARTING_BOX] = null; - } - for (const el of this.l.values()) { - if (el) - el[STARTING_BOX] = null; - } - this.H(true); - }, 50); - G() { - this.z = createBox(this.overlay); - setCSSVar(this.overlay, "overlay-width", this.z.width + "px"); - setCSSVar(this.overlay, "overlay-height", this.z.height + "px"); - } - H(forceUpdate = false) { - if (!this.l.size || this.D) - return; - let cue, activeCues = [...this.l.keys()].filter((cue2) => this.A >= cue2.startTime && this.A <= cue2.endTime).sort( - (cueA, cueB) => cueA.startTime !== cueB.startTime ? cueA.startTime - cueB.startTime : cueA.endTime - cueB.endTime - ), activeRegions = activeCues.map((cue2) => cue2.region); - for (let i4 = 0; i4 < this.B.length; i4++) { - cue = this.B[i4]; - if (activeCues[i4] === cue) - continue; - if (cue.region && !activeRegions.includes(cue.region)) { - const regionEl = this.j.get(cue.region.id); - if (regionEl) { - regionEl.removeAttribute("data-active"); - forceUpdate = true; - } - } - const cueEl = this.l.get(cue); - if (cueEl) { - cueEl.remove(); - forceUpdate = true; - } - } - for (let i4 = 0; i4 < activeCues.length; i4++) { - cue = activeCues[i4]; - let cueEl = this.l.get(cue); - if (!cueEl) - this.l.set(cue, cueEl = this.L(cue)); - const regionEl = this.F(cue) && this.j.get(cue.region.id); - if (regionEl && !regionEl.hasAttribute("data-active")) { - requestAnimationFrame(() => setDataAttr(regionEl, "active")); - forceUpdate = true; - } - if (!cueEl.isConnected) { - (regionEl || this.overlay).append(cueEl); - forceUpdate = true; - } - } - if (forceUpdate) { - const boxes = [], seen = /* @__PURE__ */ new Set(); - for (let i4 = activeCues.length - 1; i4 >= 0; i4--) { - cue = activeCues[i4]; - if (seen.has(cue.region || cue)) - continue; - const isRegion = this.F(cue), el = isRegion ? this.j.get(cue.region.id) : this.l.get(cue); - if (isRegion) { - boxes.push(positionRegion(this.z, cue.region, el, boxes)); - } else { - boxes.push(positionCue(this.z, cue, el, boxes)); - } - seen.add(isRegion ? cue.region : cue); - } - } - updateTimedVTTCueNodes(this.overlay, this.A); - this.B = activeCues; - } - J(regions) { - if (!regions) - return; - for (const region of regions) { - const el = this.M(region); - this.j.set(region.id, el); - this.overlay.append(el); - } - } - M(region) { - const el = document.createElement("div"); - setPartAttr(el, "region"); - setDataAttr(el, "id", region.id); - setDataAttr(el, "scroll", region.scroll); - setCSSVar(el, "region-width", region.width + "%"); - setCSSVar(el, "region-anchor-x", region.regionAnchorX); - setCSSVar(el, "region-anchor-y", region.regionAnchorY); - setCSSVar(el, "region-viewport-anchor-x", region.viewportAnchorX); - setCSSVar(el, "region-viewport-anchor-y", region.viewportAnchorY); - setCSSVar(el, "region-lines", region.lines); - return el; - } - L(cue) { - const display = document.createElement("div"), position = computeCuePosition(cue), positionAlignment = computeCuePositionAlignment(cue, this.C); - setPartAttr(display, "cue-display"); - if (cue.vertical !== "") - setDataAttr(display, "vertical"); - setCSSVar(display, "cue-text-align", cue.align); - if (cue.style) { - for (const prop2 of Object.keys(cue.style)) { - display.style.setProperty(prop2, cue.style[prop2]); - } - } - if (!this.F(cue)) { - setCSSVar( - display, - "cue-writing-mode", - cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl" - ); - if (!cue.style?.["--cue-width"]) { - let maxSize = position; - if (positionAlignment === "line-left") { - maxSize = 100 - position; - } else if (positionAlignment === "center" && position <= 50) { - maxSize = position * 2; - } else if (positionAlignment === "center" && position > 50) { - maxSize = (100 - position) * 2; - } - const size2 = cue.size < maxSize ? cue.size : maxSize; - if (cue.vertical === "") - setCSSVar(display, "cue-width", size2 + "%"); - else - setCSSVar(display, "cue-height", size2 + "%"); - } - } else { - setCSSVar( - display, - "cue-offset", - `${position - (positionAlignment === "line-right" ? 100 : positionAlignment === "center" ? 50 : 0)}%` - ); - } - const el = document.createElement("div"); - setPartAttr(el, "cue"); - if (cue.id) - setDataAttr(el, "id", cue.id); - el.innerHTML = renderVTTCueString(cue); - display.append(el); - return display; - } - F(cue) { - return cue.region && cue.size === 100 && cue.vertical === "" && cue.line === "auto"; - } - }; - } - }); - - // node_modules/media-captions/dist/prod.js - var prod_exports = {}; - __export(prod_exports, { - CaptionsRenderer: () => CaptionsRenderer, - ParseError: () => ParseError, - ParseErrorCode: () => ParseErrorCode, - TextCue: () => TextCue, - VTTCue: () => VTTCue, - VTTRegion: () => VTTRegion, - createVTTCueTemplate: () => createVTTCueTemplate, - parseByteStream: () => parseByteStream, - parseResponse: () => parseResponse, - parseText: () => parseText, - parseTextStream: () => parseTextStream, - parseVTTTimestamp: () => parseVTTTimestamp, - renderVTTCueString: () => renderVTTCueString, - renderVTTTokensString: () => renderVTTTokensString, - tokenizeVTTCue: () => tokenizeVTTCue, - updateTimedVTTCueNodes: () => updateTimedVTTCueNodes - }); - var init_prod2 = __esm({ - "node_modules/media-captions/dist/prod.js"() { - init_prod(); - } - }); - - // node_modules/vidstack/prod/chunks/vidstack-CP9ACpRU.js - function isTrackCaptionKind(track) { - return captionRE.test(track.kind); - } - function parseJSONCaptionsFile(json, Cue, Region) { - const content = isString(json) ? JSON.parse(json) : json; - let regions = [], cues = []; - if (content.regions && Region) { - regions = content.regions.map((region) => Object.assign(new Region(), region)); - } - if (content.cues || isArray(content)) { - cues = (isArray(content) ? content : content.cues).filter((content2) => isNumber(content2.startTime) && isNumber(content2.endTime)).map((cue) => Object.assign(new Cue(0, 0, ""), cue)); - } - return { regions, cues }; - } - var CROSS_ORIGIN, READY_STATE, UPDATE_ACTIVE_CUES, CAN_LOAD, ON_MODE_CHANGE, NATIVE, NATIVE_HLS, TextTrackSymbol, TextTrack, captionRE; - var init_vidstack_CP9ACpRU = __esm({ - "node_modules/vidstack/prod/chunks/vidstack-CP9ACpRU.js"() { - init_vidstack_CRlI3Mh7(); - init_vidstack_A9j_j6J(); - init_vidstack_lwuXewh7(); - CROSS_ORIGIN = Symbol(0); - READY_STATE = Symbol(0); - UPDATE_ACTIVE_CUES = Symbol(0); - CAN_LOAD = Symbol(0); - ON_MODE_CHANGE = Symbol(0); - NATIVE = Symbol(0); - NATIVE_HLS = Symbol(0); - TextTrackSymbol = { - crossOrigin: CROSS_ORIGIN, - readyState: READY_STATE, - updateActiveCues: UPDATE_ACTIVE_CUES, - canLoad: CAN_LOAD, - onModeChange: ON_MODE_CHANGE, - native: NATIVE, - nativeHLS: NATIVE_HLS - }; - TextTrack = class extends EventsTarget { - static createId(track) { - return `vds-${track.type}-${track.kind}-${track.src ?? track.label ?? "?"}`; - } - src; - content; - type; - encoding; - id = ""; - label = ""; - language = ""; - kind; - default = false; - #canLoad = false; - #currentTime = 0; - #mode = "disabled"; - #metadata = {}; - #regions = []; - #cues = []; - #activeCues = []; - /** @internal */ - [TextTrackSymbol.readyState] = 0; - /** @internal */ - [TextTrackSymbol.crossOrigin]; - /** @internal */ - [TextTrackSymbol.onModeChange] = null; - /** @internal */ - [TextTrackSymbol.native] = null; - get metadata() { - return this.#metadata; - } - get regions() { - return this.#regions; - } - get cues() { - return this.#cues; - } - get activeCues() { - return this.#activeCues; - } - /** - * - 0: Not Loading - * - 1: Loading - * - 2: Ready - * - 3: Error - */ - get readyState() { - return this[TextTrackSymbol.readyState]; - } - get mode() { - return this.#mode; - } - set mode(mode) { - this.setMode(mode); - } - constructor(init) { - super(); - for (const prop2 of Object.keys(init)) this[prop2] = init[prop2]; - if (!this.type) this.type = "vtt"; - if (init.content) { - this.#parseContent(init); - } else if (!init.src) { - this[TextTrackSymbol.readyState] = 2; - } - } - addCue(cue, trigger) { - let i4 = 0, length = this.#cues.length; - for (i4 = 0; i4 < length; i4++) if (cue.endTime <= this.#cues[i4].startTime) break; - if (i4 === length) this.#cues.push(cue); - else this.#cues.splice(i4, 0, cue); - if (!(cue instanceof TextTrackCue)) { - this[TextTrackSymbol.native]?.track.addCue(cue); - } - this.dispatchEvent(new DOMEvent("add-cue", { detail: cue, trigger })); - if (isCueActive(cue, this.#currentTime)) { - this[TextTrackSymbol.updateActiveCues](this.#currentTime, trigger); - } - } - removeCue(cue, trigger) { - const index = this.#cues.indexOf(cue); - if (index >= 0) { - const isActive = this.#activeCues.includes(cue); - this.#cues.splice(index, 1); - this[TextTrackSymbol.native]?.track.removeCue(cue); - this.dispatchEvent(new DOMEvent("remove-cue", { detail: cue, trigger })); - if (isActive) { - this[TextTrackSymbol.updateActiveCues](this.#currentTime, trigger); - } - } - } - setMode(mode, trigger) { - if (this.#mode === mode) return; - this.#mode = mode; - if (mode === "disabled") { - this.#activeCues = []; - this.#activeCuesChanged(); - } else if (this.readyState === 2) { - this[TextTrackSymbol.updateActiveCues](this.#currentTime, trigger); - } else { - this.#load(); - } - this.dispatchEvent(new DOMEvent("mode-change", { detail: this, trigger })); - this[TextTrackSymbol.onModeChange]?.(); - } - /** @internal */ - [TextTrackSymbol.updateActiveCues](currentTime, trigger) { - this.#currentTime = currentTime; - if (this.mode === "disabled" || !this.#cues.length) return; - const activeCues = []; - for (let i4 = 0, length = this.#cues.length; i4 < length; i4++) { - const cue = this.#cues[i4]; - if (isCueActive(cue, currentTime)) activeCues.push(cue); - } - let changed = activeCues.length !== this.#activeCues.length; - if (!changed) { - for (let i4 = 0; i4 < activeCues.length; i4++) { - if (!this.#activeCues.includes(activeCues[i4])) { - changed = true; - break; - } - } - } - this.#activeCues = activeCues; - if (changed) this.#activeCuesChanged(trigger); - } - /** @internal */ - [TextTrackSymbol.canLoad]() { - this.#canLoad = true; - if (this.#mode !== "disabled") this.#load(); - } - #parseContent(init) { - Promise.resolve().then(() => (init_prod2(), prod_exports)).then(({ parseText: parseText2, VTTCue: VTTCue2, VTTRegion: VTTRegion2 }) => { - if (!isString(init.content) || init.type === "json") { - this.#parseJSON(init.content, VTTCue2, VTTRegion2); - if (this.readyState !== 3) this.#ready(); - } else { - parseText2(init.content, { type: init.type }).then(({ cues, regions }) => { - this.#cues = cues; - this.#regions = regions; - this.#ready(); - }); - } - }); - } - async #load() { - if (!this.#canLoad || this[TextTrackSymbol.readyState] > 0) return; - this[TextTrackSymbol.readyState] = 1; - this.dispatchEvent(new DOMEvent("load-start")); - if (!this.src) { - this.#ready(); - return; - } - try { - const { parseResponse: parseResponse2, VTTCue: VTTCue2, VTTRegion: VTTRegion2 } = await Promise.resolve().then(() => (init_prod2(), prod_exports)), crossOrigin = this[TextTrackSymbol.crossOrigin]?.(); - const response = fetch(this.src, { - headers: this.type === "json" ? { "Content-Type": "application/json" } : void 0, - credentials: getRequestCredentials(crossOrigin) - }); - if (this.type === "json") { - this.#parseJSON(await (await response).text(), VTTCue2, VTTRegion2); - } else { - const { errors, metadata, regions, cues } = await parseResponse2(response, { - type: this.type, - encoding: this.encoding - }); - if (errors[0]?.code === 0) { - throw errors[0]; - } else { - this.#metadata = metadata; - this.#regions = regions; - this.#cues = cues; - } - } - this.#ready(); - } catch (error) { - this.#error(error); - } - } - #ready() { - this[TextTrackSymbol.readyState] = 2; - if (!this.src || this.type !== "vtt") { - const native = this[TextTrackSymbol.native]; - if (native && !native.managed) { - for (const cue of this.#cues) native.track.addCue(cue); - } - } - const loadEvent = new DOMEvent("load"); - this[TextTrackSymbol.updateActiveCues](this.#currentTime, loadEvent); - this.dispatchEvent(loadEvent); - } - #error(error) { - this[TextTrackSymbol.readyState] = 3; - this.dispatchEvent(new DOMEvent("error", { detail: error })); - } - #parseJSON(json, VTTCue2, VTTRegion2) { - try { - const { regions, cues } = parseJSONCaptionsFile(json, VTTCue2, VTTRegion2); - this.#regions = regions; - this.#cues = cues; - } catch (error) { - this.#error(error); - } - } - #activeCuesChanged(trigger) { - this.dispatchEvent(new DOMEvent("cue-change", { trigger })); - } - }; - captionRE = /captions|subtitles/; - } - }); - - // node_modules/vidstack/prod/chunks/vidstack-D5EzK014.js - var ADD, REMOVE, RESET, SELECT, READONLY, SET_READONLY, ON_RESET, ON_REMOVE, ON_USER_SELECT, ListSymbol; - var init_vidstack_D5EzK014 = __esm({ - "node_modules/vidstack/prod/chunks/vidstack-D5EzK014.js"() { - ADD = Symbol(0); - REMOVE = Symbol(0); - RESET = Symbol(0); - SELECT = Symbol(0); - READONLY = Symbol(0); - SET_READONLY = Symbol(0); - ON_RESET = Symbol(0); - ON_REMOVE = Symbol(0); - ON_USER_SELECT = Symbol(0); - ListSymbol = { - add: ADD, - remove: REMOVE, - reset: RESET, - select: SELECT, - readonly: READONLY, - setReadonly: SET_READONLY, - onReset: ON_RESET, - onRemove: ON_REMOVE, - onUserSelect: ON_USER_SELECT - }; - } - }); - - // node_modules/vidstack/prod/chunks/vidstack-B01xzxC4.js - var SET_AUTO, ENABLE_AUTO, QualitySymbol; - var init_vidstack_B01xzxC4 = __esm({ - "node_modules/vidstack/prod/chunks/vidstack-B01xzxC4.js"() { - SET_AUTO = Symbol(0); - ENABLE_AUTO = Symbol(0); - QualitySymbol = { - setAuto: SET_AUTO, - enableAuto: ENABLE_AUTO - }; - } - }); - - // node_modules/vidstack/prod/chunks/vidstack-C9vIqaYT.js - function coerceToError(error) { - return error instanceof Error ? error : Error(typeof error === "string" ? error : JSON.stringify(error)); - } - function assert(condition, message) { - if (!condition) { - throw Error("Assertion failed."); - } - } - var init_vidstack_C9vIqaYT = __esm({ - "node_modules/vidstack/prod/chunks/vidstack-C9vIqaYT.js"() { - } - }); - - // node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs - function clamp(start, value, end) { - return max(start, min(value, end)); - } - function evaluate(value, param) { - return typeof value === "function" ? value(param) : value; - } - function getSide(placement) { - return placement.split("-")[0]; - } - function getAlignment(placement) { - return placement.split("-")[1]; - } - function getOppositeAxis(axis) { - return axis === "x" ? "y" : "x"; - } - function getAxisLength(axis) { - return axis === "y" ? "height" : "width"; - } - function getSideAxis(placement) { - return ["top", "bottom"].includes(getSide(placement)) ? "y" : "x"; - } - function getAlignmentAxis(placement) { - return getOppositeAxis(getSideAxis(placement)); - } - function getAlignmentSides(placement, rects, rtl) { - if (rtl === void 0) { - rtl = false; - } - const alignment = getAlignment(placement); - const alignmentAxis = getAlignmentAxis(placement); - const length = getAxisLength(alignmentAxis); - let mainAlignmentSide = alignmentAxis === "x" ? alignment === (rtl ? "end" : "start") ? "right" : "left" : alignment === "start" ? "bottom" : "top"; - if (rects.reference[length] > rects.floating[length]) { - mainAlignmentSide = getOppositePlacement(mainAlignmentSide); - } - return [mainAlignmentSide, getOppositePlacement(mainAlignmentSide)]; - } - function getExpandedPlacements(placement) { - const oppositePlacement = getOppositePlacement(placement); - return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)]; - } - function getOppositeAlignmentPlacement(placement) { - return placement.replace(/start|end/g, (alignment) => oppositeAlignmentMap[alignment]); - } - function getSideList(side, isStart, rtl) { - const lr = ["left", "right"]; - const rl = ["right", "left"]; - const tb = ["top", "bottom"]; - const bt = ["bottom", "top"]; - switch (side) { - case "top": - case "bottom": - if (rtl) return isStart ? rl : lr; - return isStart ? lr : rl; - case "left": - case "right": - return isStart ? tb : bt; - default: - return []; - } - } - function getOppositeAxisPlacements(placement, flipAlignment, direction, rtl) { - const alignment = getAlignment(placement); - let list = getSideList(getSide(placement), direction === "start", rtl); - if (alignment) { - list = list.map((side) => side + "-" + alignment); - if (flipAlignment) { - list = list.concat(list.map(getOppositeAlignmentPlacement)); - } - } - return list; - } - function getOppositePlacement(placement) { - return placement.replace(/left|right|bottom|top/g, (side) => oppositeSideMap[side]); - } - function expandPaddingObject(padding) { - return { - top: 0, - right: 0, - bottom: 0, - left: 0, - ...padding - }; - } - function getPaddingObject(padding) { - return typeof padding !== "number" ? expandPaddingObject(padding) : { - top: padding, - right: padding, - bottom: padding, - left: padding - }; - } - function rectToClientRect(rect) { - const { - x: x2, - y: y2, - width, - height - } = rect; - return { - width, - height, - top: y2, - left: x2, - right: x2 + width, - bottom: y2 + height, - x: x2, - y: y2 - }; - } - var min, max, round, floor, createCoords, oppositeSideMap, oppositeAlignmentMap; - var init_floating_ui_utils = __esm({ - "node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs"() { - min = Math.min; - max = Math.max; - round = Math.round; - floor = Math.floor; - createCoords = (v2) => ({ - x: v2, - y: v2 - }); - oppositeSideMap = { - left: "right", - right: "left", - bottom: "top", - top: "bottom" - }; - oppositeAlignmentMap = { - start: "end", - end: "start" - }; - } - }); - - // node_modules/@floating-ui/core/dist/floating-ui.core.mjs - function computeCoordsFromPlacement(_ref, placement, rtl) { - let { - reference, - floating - } = _ref; - const sideAxis = getSideAxis(placement); - const alignmentAxis = getAlignmentAxis(placement); - const alignLength = getAxisLength(alignmentAxis); - const side = getSide(placement); - const isVertical = sideAxis === "y"; - const commonX = reference.x + reference.width / 2 - floating.width / 2; - const commonY = reference.y + reference.height / 2 - floating.height / 2; - const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2; - let coords; - switch (side) { - case "top": - coords = { - x: commonX, - y: reference.y - floating.height - }; - break; - case "bottom": - coords = { - x: commonX, - y: reference.y + reference.height - }; - break; - case "right": - coords = { - x: reference.x + reference.width, - y: commonY - }; - break; - case "left": - coords = { - x: reference.x - floating.width, - y: commonY - }; - break; - default: - coords = { - x: reference.x, - y: reference.y - }; - } - switch (getAlignment(placement)) { - case "start": - coords[alignmentAxis] -= commonAlign * (rtl && isVertical ? -1 : 1); - break; - case "end": - coords[alignmentAxis] += commonAlign * (rtl && isVertical ? -1 : 1); - break; - } - return coords; - } - async function detectOverflow(state, options) { - var _await$platform$isEle; - if (options === void 0) { - options = {}; - } - const { - x: x2, - y: y2, - platform: platform2, - rects, - elements, - strategy - } = state; - const { - boundary = "clippingAncestors", - rootBoundary = "viewport", - elementContext = "floating", - altBoundary = false, - padding = 0 - } = evaluate(options, state); - const paddingObject = getPaddingObject(padding); - const altContext = elementContext === "floating" ? "reference" : "floating"; - const element = elements[altBoundary ? altContext : elementContext]; - const clippingClientRect = rectToClientRect(await platform2.getClippingRect({ - element: ((_await$platform$isEle = await (platform2.isElement == null ? void 0 : platform2.isElement(element))) != null ? _await$platform$isEle : true) ? element : element.contextElement || await (platform2.getDocumentElement == null ? void 0 : platform2.getDocumentElement(elements.floating)), - boundary, - rootBoundary, - strategy - })); - const rect = elementContext === "floating" ? { - x: x2, - y: y2, - width: rects.floating.width, - height: rects.floating.height - } : rects.reference; - const offsetParent = await (platform2.getOffsetParent == null ? void 0 : platform2.getOffsetParent(elements.floating)); - const offsetScale = await (platform2.isElement == null ? void 0 : platform2.isElement(offsetParent)) ? await (platform2.getScale == null ? void 0 : platform2.getScale(offsetParent)) || { - x: 1, - y: 1 - } : { - x: 1, - y: 1 - }; - const elementClientRect = rectToClientRect(platform2.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform2.convertOffsetParentRelativeRectToViewportRelativeRect({ - elements, - rect, - offsetParent, - strategy - }) : rect); - return { - top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y, - bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y, - left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x, - right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x - }; - } - var computePosition, flip, shift; - var init_floating_ui_core = __esm({ - "node_modules/@floating-ui/core/dist/floating-ui.core.mjs"() { - init_floating_ui_utils(); - init_floating_ui_utils(); - computePosition = async (reference, floating, config) => { - const { - placement = "bottom", - strategy = "absolute", - middleware = [], - platform: platform2 - } = config; - const validMiddleware = middleware.filter(Boolean); - const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(floating)); - let rects = await platform2.getElementRects({ - reference, - floating, - strategy - }); - let { - x: x2, - y: y2 - } = computeCoordsFromPlacement(rects, placement, rtl); - let statefulPlacement = placement; - let middlewareData = {}; - let resetCount = 0; - for (let i4 = 0; i4 < validMiddleware.length; i4++) { - const { - name, - fn - } = validMiddleware[i4]; - const { - x: nextX, - y: nextY, - data, - reset - } = await fn({ - x: x2, - y: y2, - initialPlacement: placement, - placement: statefulPlacement, - strategy, - middlewareData, - rects, - platform: platform2, - elements: { - reference, - floating - } - }); - x2 = nextX != null ? nextX : x2; - y2 = nextY != null ? nextY : y2; - middlewareData = { - ...middlewareData, - [name]: { - ...middlewareData[name], - ...data - } - }; - if (reset && resetCount <= 50) { - resetCount++; - if (typeof reset === "object") { - if (reset.placement) { - statefulPlacement = reset.placement; - } - if (reset.rects) { - rects = reset.rects === true ? await platform2.getElementRects({ - reference, - floating, - strategy - }) : reset.rects; - } - ({ - x: x2, - y: y2 - } = computeCoordsFromPlacement(rects, statefulPlacement, rtl)); - } - i4 = -1; - } - } - return { - x: x2, - y: y2, - placement: statefulPlacement, - strategy, - middlewareData - }; - }; - flip = function(options) { - if (options === void 0) { - options = {}; - } - return { - name: "flip", - options, - async fn(state) { - var _middlewareData$arrow, _middlewareData$flip; - const { - placement, - middlewareData, - rects, - initialPlacement, - platform: platform2, - elements - } = state; - const { - mainAxis: checkMainAxis = true, - crossAxis: checkCrossAxis = true, - fallbackPlacements: specifiedFallbackPlacements, - fallbackStrategy = "bestFit", - fallbackAxisSideDirection = "none", - flipAlignment = true, - ...detectOverflowOptions - } = evaluate(options, state); - if ((_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) { - return {}; - } - const side = getSide(placement); - const initialSideAxis = getSideAxis(initialPlacement); - const isBasePlacement = getSide(initialPlacement) === initialPlacement; - const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating)); - const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [getOppositePlacement(initialPlacement)] : getExpandedPlacements(initialPlacement)); - const hasFallbackAxisSideDirection = fallbackAxisSideDirection !== "none"; - if (!specifiedFallbackPlacements && hasFallbackAxisSideDirection) { - fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl)); - } - const placements2 = [initialPlacement, ...fallbackPlacements]; - const overflow = await detectOverflow(state, detectOverflowOptions); - const overflows = []; - let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || []; - if (checkMainAxis) { - overflows.push(overflow[side]); - } - if (checkCrossAxis) { - const sides2 = getAlignmentSides(placement, rects, rtl); - overflows.push(overflow[sides2[0]], overflow[sides2[1]]); - } - overflowsData = [...overflowsData, { - placement, - overflows - }]; - if (!overflows.every((side2) => side2 <= 0)) { - var _middlewareData$flip2, _overflowsData$filter; - const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1; - const nextPlacement = placements2[nextIndex]; - if (nextPlacement) { - return { - data: { - index: nextIndex, - overflows: overflowsData - }, - reset: { - placement: nextPlacement - } - }; - } - let resetPlacement = (_overflowsData$filter = overflowsData.filter((d2) => d2.overflows[0] <= 0).sort((a3, b2) => a3.overflows[1] - b2.overflows[1])[0]) == null ? void 0 : _overflowsData$filter.placement; - if (!resetPlacement) { - switch (fallbackStrategy) { - case "bestFit": { - var _overflowsData$filter2; - const placement2 = (_overflowsData$filter2 = overflowsData.filter((d2) => { - if (hasFallbackAxisSideDirection) { - const currentSideAxis = getSideAxis(d2.placement); - return currentSideAxis === initialSideAxis || // Create a bias to the `y` side axis due to horizontal - // reading directions favoring greater width. - currentSideAxis === "y"; - } - return true; - }).map((d2) => [d2.placement, d2.overflows.filter((overflow2) => overflow2 > 0).reduce((acc, overflow2) => acc + overflow2, 0)]).sort((a3, b2) => a3[1] - b2[1])[0]) == null ? void 0 : _overflowsData$filter2[0]; - if (placement2) { - resetPlacement = placement2; - } - break; - } - case "initialPlacement": - resetPlacement = initialPlacement; - break; - } - } - if (placement !== resetPlacement) { - return { - reset: { - placement: resetPlacement - } - }; - } - } - return {}; - } - }; - }; - shift = function(options) { - if (options === void 0) { - options = {}; - } - return { - name: "shift", - options, - async fn(state) { - const { - x: x2, - y: y2, - placement - } = state; - const { - mainAxis: checkMainAxis = true, - crossAxis: checkCrossAxis = false, - limiter = { - fn: (_ref) => { - let { - x: x3, - y: y3 - } = _ref; - return { - x: x3, - y: y3 - }; - } - }, - ...detectOverflowOptions - } = evaluate(options, state); - const coords = { - x: x2, - y: y2 - }; - const overflow = await detectOverflow(state, detectOverflowOptions); - const crossAxis = getSideAxis(getSide(placement)); - const mainAxis = getOppositeAxis(crossAxis); - let mainAxisCoord = coords[mainAxis]; - let crossAxisCoord = coords[crossAxis]; - if (checkMainAxis) { - const minSide = mainAxis === "y" ? "top" : "left"; - const maxSide = mainAxis === "y" ? "bottom" : "right"; - const min2 = mainAxisCoord + overflow[minSide]; - const max2 = mainAxisCoord - overflow[maxSide]; - mainAxisCoord = clamp(min2, mainAxisCoord, max2); - } - if (checkCrossAxis) { - const minSide = crossAxis === "y" ? "top" : "left"; - const maxSide = crossAxis === "y" ? "bottom" : "right"; - const min2 = crossAxisCoord + overflow[minSide]; - const max2 = crossAxisCoord - overflow[maxSide]; - crossAxisCoord = clamp(min2, crossAxisCoord, max2); - } - const limitedCoords = limiter.fn({ - ...state, - [mainAxis]: mainAxisCoord, - [crossAxis]: crossAxisCoord - }); - return { - ...limitedCoords, - data: { - x: limitedCoords.x - x2, - y: limitedCoords.y - y2, - enabled: { - [mainAxis]: checkMainAxis, - [crossAxis]: checkCrossAxis - } - } - }; - } - }; - }; - } - }); - - // node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.mjs - function hasWindow() { - return typeof window !== "undefined"; - } - function getNodeName(node) { - if (isNode(node)) { - return (node.nodeName || "").toLowerCase(); - } - return "#document"; - } - function getWindow(node) { - var _node$ownerDocument; - return (node == null || (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window; - } - function getDocumentElement(node) { - var _ref; - return (_ref = (isNode(node) ? node.ownerDocument : node.document) || window.document) == null ? void 0 : _ref.documentElement; - } - function isNode(value) { - if (!hasWindow()) { - return false; - } - return value instanceof Node || value instanceof getWindow(value).Node; - } - function isElement(value) { - if (!hasWindow()) { - return false; - } - return value instanceof Element || value instanceof getWindow(value).Element; - } - function isHTMLElement(value) { - if (!hasWindow()) { - return false; - } - return value instanceof HTMLElement || value instanceof getWindow(value).HTMLElement; - } - function isShadowRoot(value) { - if (!hasWindow() || typeof ShadowRoot === "undefined") { - return false; - } - return value instanceof ShadowRoot || value instanceof getWindow(value).ShadowRoot; - } - function isOverflowElement(element) { - const { - overflow, - overflowX, - overflowY, - display - } = getComputedStyle2(element); - return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !["inline", "contents"].includes(display); - } - function isTableElement(element) { - return ["table", "td", "th"].includes(getNodeName(element)); - } - function isTopLayer(element) { - return [":popover-open", ":modal"].some((selector) => { - try { - return element.matches(selector); - } catch (e6) { - return false; - } - }); - } - function isContainingBlock(elementOrCss) { - const webkit2 = isWebKit(); - const css = isElement(elementOrCss) ? getComputedStyle2(elementOrCss) : elementOrCss; - return css.transform !== "none" || css.perspective !== "none" || (css.containerType ? css.containerType !== "normal" : false) || !webkit2 && (css.backdropFilter ? css.backdropFilter !== "none" : false) || !webkit2 && (css.filter ? css.filter !== "none" : false) || ["transform", "perspective", "filter"].some((value) => (css.willChange || "").includes(value)) || ["paint", "layout", "strict", "content"].some((value) => (css.contain || "").includes(value)); - } - function getContainingBlock(element) { - let currentNode = getParentNode(element); - while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) { - if (isContainingBlock(currentNode)) { - return currentNode; - } else if (isTopLayer(currentNode)) { - return null; - } - currentNode = getParentNode(currentNode); - } - return null; - } - function isWebKit() { - if (typeof CSS === "undefined" || !CSS.supports) return false; - return CSS.supports("-webkit-backdrop-filter", "none"); - } - function isLastTraversableNode(node) { - return ["html", "body", "#document"].includes(getNodeName(node)); - } - function getComputedStyle2(element) { - return getWindow(element).getComputedStyle(element); - } - function getNodeScroll(element) { - if (isElement(element)) { - return { - scrollLeft: element.scrollLeft, - scrollTop: element.scrollTop - }; - } - return { - scrollLeft: element.scrollX, - scrollTop: element.scrollY - }; - } - function getParentNode(node) { - if (getNodeName(node) === "html") { - return node; - } - const result = ( - // Step into the shadow DOM of the parent of a slotted node. - node.assignedSlot || // DOM Element detected. - node.parentNode || // ShadowRoot detected. - isShadowRoot(node) && node.host || // Fallback. - getDocumentElement(node) - ); - return isShadowRoot(result) ? result.host : result; - } - function getNearestOverflowAncestor(node) { - const parentNode = getParentNode(node); - if (isLastTraversableNode(parentNode)) { - return node.ownerDocument ? node.ownerDocument.body : node.body; - } - if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) { - return parentNode; - } - return getNearestOverflowAncestor(parentNode); - } - function getOverflowAncestors(node, list, traverseIframes) { - var _node$ownerDocument2; - if (list === void 0) { - list = []; - } - if (traverseIframes === void 0) { - traverseIframes = true; - } - const scrollableAncestor = getNearestOverflowAncestor(node); - const isBody = scrollableAncestor === ((_node$ownerDocument2 = node.ownerDocument) == null ? void 0 : _node$ownerDocument2.body); - const win = getWindow(scrollableAncestor); - if (isBody) { - const frameElement = getFrameElement(win); - return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [], frameElement && traverseIframes ? getOverflowAncestors(frameElement) : []); - } - return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes)); - } - function getFrameElement(win) { - return win.parent && Object.getPrototypeOf(win.parent) ? win.frameElement : null; - } - var init_floating_ui_utils_dom = __esm({ - "node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.mjs"() { - } - }); - - // node_modules/@floating-ui/dom/dist/floating-ui.dom.mjs - function getCssDimensions(element) { - const css = getComputedStyle2(element); - let width = parseFloat(css.width) || 0; - let height = parseFloat(css.height) || 0; - const hasOffset = isHTMLElement(element); - const offsetWidth = hasOffset ? element.offsetWidth : width; - const offsetHeight = hasOffset ? element.offsetHeight : height; - const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight; - if (shouldFallback) { - width = offsetWidth; - height = offsetHeight; - } - return { - width, - height, - $: shouldFallback - }; - } - function unwrapElement(element) { - return !isElement(element) ? element.contextElement : element; - } - function getScale(element) { - const domElement = unwrapElement(element); - if (!isHTMLElement(domElement)) { - return createCoords(1); - } - const rect = domElement.getBoundingClientRect(); - const { - width, - height, - $: $2 - } = getCssDimensions(domElement); - let x2 = ($2 ? round(rect.width) : rect.width) / width; - let y2 = ($2 ? round(rect.height) : rect.height) / height; - if (!x2 || !Number.isFinite(x2)) { - x2 = 1; - } - if (!y2 || !Number.isFinite(y2)) { - y2 = 1; - } - return { - x: x2, - y: y2 - }; - } - function getVisualOffsets(element) { - const win = getWindow(element); - if (!isWebKit() || !win.visualViewport) { - return noOffsets; - } - return { - x: win.visualViewport.offsetLeft, - y: win.visualViewport.offsetTop - }; - } - function shouldAddVisualOffsets(element, isFixed, floatingOffsetParent) { - if (isFixed === void 0) { - isFixed = false; - } - if (!floatingOffsetParent || isFixed && floatingOffsetParent !== getWindow(element)) { - return false; - } - return isFixed; - } - function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) { - if (includeScale === void 0) { - includeScale = false; - } - if (isFixedStrategy === void 0) { - isFixedStrategy = false; - } - const clientRect = element.getBoundingClientRect(); - const domElement = unwrapElement(element); - let scale = createCoords(1); - if (includeScale) { - if (offsetParent) { - if (isElement(offsetParent)) { - scale = getScale(offsetParent); - } - } else { - scale = getScale(element); - } - } - const visualOffsets = shouldAddVisualOffsets(domElement, isFixedStrategy, offsetParent) ? getVisualOffsets(domElement) : createCoords(0); - let x2 = (clientRect.left + visualOffsets.x) / scale.x; - let y2 = (clientRect.top + visualOffsets.y) / scale.y; - let width = clientRect.width / scale.x; - let height = clientRect.height / scale.y; - if (domElement) { - const win = getWindow(domElement); - const offsetWin = offsetParent && isElement(offsetParent) ? getWindow(offsetParent) : offsetParent; - let currentWin = win; - let currentIFrame = getFrameElement(currentWin); - while (currentIFrame && offsetParent && offsetWin !== currentWin) { - const iframeScale = getScale(currentIFrame); - const iframeRect = currentIFrame.getBoundingClientRect(); - const css = getComputedStyle2(currentIFrame); - const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x; - const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y; - x2 *= iframeScale.x; - y2 *= iframeScale.y; - width *= iframeScale.x; - height *= iframeScale.y; - x2 += left; - y2 += top; - currentWin = getWindow(currentIFrame); - currentIFrame = getFrameElement(currentWin); - } - } - return rectToClientRect({ - width, - height, - x: x2, - y: y2 - }); - } - function getWindowScrollBarX(element, rect) { - const leftScroll = getNodeScroll(element).scrollLeft; - if (!rect) { - return getBoundingClientRect(getDocumentElement(element)).left + leftScroll; - } - return rect.left + leftScroll; - } - function getHTMLOffset(documentElement, scroll, ignoreScrollbarX) { - if (ignoreScrollbarX === void 0) { - ignoreScrollbarX = false; - } - const htmlRect = documentElement.getBoundingClientRect(); - const x2 = htmlRect.left + scroll.scrollLeft - (ignoreScrollbarX ? 0 : ( - // RTL scrollbar. - getWindowScrollBarX(documentElement, htmlRect) - )); - const y2 = htmlRect.top + scroll.scrollTop; - return { - x: x2, - y: y2 - }; - } - function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) { - let { - elements, - rect, - offsetParent, - strategy - } = _ref; - const isFixed = strategy === "fixed"; - const documentElement = getDocumentElement(offsetParent); - const topLayer = elements ? isTopLayer(elements.floating) : false; - if (offsetParent === documentElement || topLayer && isFixed) { - return rect; - } - let scroll = { - scrollLeft: 0, - scrollTop: 0 - }; - let scale = createCoords(1); - const offsets = createCoords(0); - const isOffsetParentAnElement = isHTMLElement(offsetParent); - if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) { - if (getNodeName(offsetParent) !== "body" || isOverflowElement(documentElement)) { - scroll = getNodeScroll(offsetParent); - } - if (isHTMLElement(offsetParent)) { - const offsetRect = getBoundingClientRect(offsetParent); - scale = getScale(offsetParent); - offsets.x = offsetRect.x + offsetParent.clientLeft; - offsets.y = offsetRect.y + offsetParent.clientTop; - } - } - const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll, true) : createCoords(0); - return { - width: rect.width * scale.x, - height: rect.height * scale.y, - x: rect.x * scale.x - scroll.scrollLeft * scale.x + offsets.x + htmlOffset.x, - y: rect.y * scale.y - scroll.scrollTop * scale.y + offsets.y + htmlOffset.y - }; - } - function getClientRects(element) { - return Array.from(element.getClientRects()); - } - function getDocumentRect(element) { - const html = getDocumentElement(element); - const scroll = getNodeScroll(element); - const body = element.ownerDocument.body; - const width = max(html.scrollWidth, html.clientWidth, body.scrollWidth, body.clientWidth); - const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight); - let x2 = -scroll.scrollLeft + getWindowScrollBarX(element); - const y2 = -scroll.scrollTop; - if (getComputedStyle2(body).direction === "rtl") { - x2 += max(html.clientWidth, body.clientWidth) - width; - } - return { - width, - height, - x: x2, - y: y2 - }; - } - function getViewportRect(element, strategy) { - const win = getWindow(element); - const html = getDocumentElement(element); - const visualViewport = win.visualViewport; - let width = html.clientWidth; - let height = html.clientHeight; - let x2 = 0; - let y2 = 0; - if (visualViewport) { - width = visualViewport.width; - height = visualViewport.height; - const visualViewportBased = isWebKit(); - if (!visualViewportBased || visualViewportBased && strategy === "fixed") { - x2 = visualViewport.offsetLeft; - y2 = visualViewport.offsetTop; - } - } - return { - width, - height, - x: x2, - y: y2 - }; - } - function getInnerBoundingClientRect(element, strategy) { - const clientRect = getBoundingClientRect(element, true, strategy === "fixed"); - const top = clientRect.top + element.clientTop; - const left = clientRect.left + element.clientLeft; - const scale = isHTMLElement(element) ? getScale(element) : createCoords(1); - const width = element.clientWidth * scale.x; - const height = element.clientHeight * scale.y; - const x2 = left * scale.x; - const y2 = top * scale.y; - return { - width, - height, - x: x2, - y: y2 - }; - } - function getClientRectFromClippingAncestor(element, clippingAncestor, strategy) { - let rect; - if (clippingAncestor === "viewport") { - rect = getViewportRect(element, strategy); - } else if (clippingAncestor === "document") { - rect = getDocumentRect(getDocumentElement(element)); - } else if (isElement(clippingAncestor)) { - rect = getInnerBoundingClientRect(clippingAncestor, strategy); - } else { - const visualOffsets = getVisualOffsets(element); - rect = { - x: clippingAncestor.x - visualOffsets.x, - y: clippingAncestor.y - visualOffsets.y, - width: clippingAncestor.width, - height: clippingAncestor.height - }; - } - return rectToClientRect(rect); - } - function hasFixedPositionAncestor(element, stopNode) { - const parentNode = getParentNode(element); - if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) { - return false; - } - return getComputedStyle2(parentNode).position === "fixed" || hasFixedPositionAncestor(parentNode, stopNode); - } - function getClippingElementAncestors(element, cache2) { - const cachedResult = cache2.get(element); - if (cachedResult) { - return cachedResult; - } - let result = getOverflowAncestors(element, [], false).filter((el) => isElement(el) && getNodeName(el) !== "body"); - let currentContainingBlockComputedStyle = null; - const elementIsFixed = getComputedStyle2(element).position === "fixed"; - let currentNode = elementIsFixed ? getParentNode(element) : element; - while (isElement(currentNode) && !isLastTraversableNode(currentNode)) { - const computedStyle = getComputedStyle2(currentNode); - const currentNodeIsContaining = isContainingBlock(currentNode); - if (!currentNodeIsContaining && computedStyle.position === "fixed") { - currentContainingBlockComputedStyle = null; - } - const shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === "static" && !!currentContainingBlockComputedStyle && ["absolute", "fixed"].includes(currentContainingBlockComputedStyle.position) || isOverflowElement(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode); - if (shouldDropCurrentNode) { - result = result.filter((ancestor) => ancestor !== currentNode); - } else { - currentContainingBlockComputedStyle = computedStyle; - } - currentNode = getParentNode(currentNode); - } - cache2.set(element, result); - return result; - } - function getClippingRect(_ref) { - let { - element, - boundary, - rootBoundary, - strategy - } = _ref; - const elementClippingAncestors = boundary === "clippingAncestors" ? isTopLayer(element) ? [] : getClippingElementAncestors(element, this._c) : [].concat(boundary); - const clippingAncestors = [...elementClippingAncestors, rootBoundary]; - const firstClippingAncestor = clippingAncestors[0]; - const clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => { - const rect = getClientRectFromClippingAncestor(element, clippingAncestor, strategy); - accRect.top = max(rect.top, accRect.top); - accRect.right = min(rect.right, accRect.right); - accRect.bottom = min(rect.bottom, accRect.bottom); - accRect.left = max(rect.left, accRect.left); - return accRect; - }, getClientRectFromClippingAncestor(element, firstClippingAncestor, strategy)); - return { - width: clippingRect.right - clippingRect.left, - height: clippingRect.bottom - clippingRect.top, - x: clippingRect.left, - y: clippingRect.top - }; - } - function getDimensions(element) { - const { - width, - height - } = getCssDimensions(element); - return { - width, - height - }; - } - function getRectRelativeToOffsetParent(element, offsetParent, strategy) { - const isOffsetParentAnElement = isHTMLElement(offsetParent); - const documentElement = getDocumentElement(offsetParent); - const isFixed = strategy === "fixed"; - const rect = getBoundingClientRect(element, true, isFixed, offsetParent); - let scroll = { - scrollLeft: 0, - scrollTop: 0 - }; - const offsets = createCoords(0); - if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) { - if (getNodeName(offsetParent) !== "body" || isOverflowElement(documentElement)) { - scroll = getNodeScroll(offsetParent); - } - if (isOffsetParentAnElement) { - const offsetRect = getBoundingClientRect(offsetParent, true, isFixed, offsetParent); - offsets.x = offsetRect.x + offsetParent.clientLeft; - offsets.y = offsetRect.y + offsetParent.clientTop; - } else if (documentElement) { - offsets.x = getWindowScrollBarX(documentElement); - } - } - const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0); - const x2 = rect.left + scroll.scrollLeft - offsets.x - htmlOffset.x; - const y2 = rect.top + scroll.scrollTop - offsets.y - htmlOffset.y; - return { - x: x2, - y: y2, - width: rect.width, - height: rect.height - }; - } - function isStaticPositioned(element) { - return getComputedStyle2(element).position === "static"; - } - function getTrueOffsetParent(element, polyfill) { - if (!isHTMLElement(element) || getComputedStyle2(element).position === "fixed") { - return null; - } - if (polyfill) { - return polyfill(element); - } - let rawOffsetParent = element.offsetParent; - if (getDocumentElement(element) === rawOffsetParent) { - rawOffsetParent = rawOffsetParent.ownerDocument.body; - } - return rawOffsetParent; - } - function getOffsetParent(element, polyfill) { - const win = getWindow(element); - if (isTopLayer(element)) { - return win; - } - if (!isHTMLElement(element)) { - let svgOffsetParent = getParentNode(element); - while (svgOffsetParent && !isLastTraversableNode(svgOffsetParent)) { - if (isElement(svgOffsetParent) && !isStaticPositioned(svgOffsetParent)) { - return svgOffsetParent; - } - svgOffsetParent = getParentNode(svgOffsetParent); - } - return win; - } - let offsetParent = getTrueOffsetParent(element, polyfill); - while (offsetParent && isTableElement(offsetParent) && isStaticPositioned(offsetParent)) { - offsetParent = getTrueOffsetParent(offsetParent, polyfill); - } - if (offsetParent && isLastTraversableNode(offsetParent) && isStaticPositioned(offsetParent) && !isContainingBlock(offsetParent)) { - return win; - } - return offsetParent || getContainingBlock(element) || win; - } - function isRTL(element) { - return getComputedStyle2(element).direction === "rtl"; - } - function observeMove(element, onMove) { - let io = null; - let timeoutId; - const root2 = getDocumentElement(element); - function cleanup2() { - var _io; - clearTimeout(timeoutId); - (_io = io) == null || _io.disconnect(); - io = null; - } - function refresh(skip, threshold) { - if (skip === void 0) { - skip = false; - } - if (threshold === void 0) { - threshold = 1; - } - cleanup2(); - const { - left, - top, - width, - height - } = element.getBoundingClientRect(); - if (!skip) { - onMove(); - } - if (!width || !height) { - return; - } - const insetTop = floor(top); - const insetRight = floor(root2.clientWidth - (left + width)); - const insetBottom = floor(root2.clientHeight - (top + height)); - const insetLeft = floor(left); - const rootMargin = -insetTop + "px " + -insetRight + "px " + -insetBottom + "px " + -insetLeft + "px"; - const options = { - rootMargin, - threshold: max(0, min(1, threshold)) || 1 - }; - let isFirstUpdate = true; - function handleObserve(entries) { - const ratio = entries[0].intersectionRatio; - if (ratio !== threshold) { - if (!isFirstUpdate) { - return refresh(); - } - if (!ratio) { - timeoutId = setTimeout(() => { - refresh(false, 1e-7); - }, 1e3); - } else { - refresh(false, ratio); - } - } - isFirstUpdate = false; - } - try { - io = new IntersectionObserver(handleObserve, { - ...options, - // Handle )?(?:

.*

)?~ix'; - if (preg_match($vimeoPattern, $source, $match)) { - return ['platform' => 'vimeo', 'id' => $match[2]]; - } - return ['platform' => 'default', 'id' => '']; - } - - public static function isPlayable($source): bool - { - // Check for local media files - if (self::isMedia($source)) { - return true; - } - - // Check for YouTube or Vimeo videos - $videoInfo = self::getVideoInfo($source); - if ($videoInfo['platform'] === 'youtube' || $videoInfo['platform'] === 'vimeo') { - return true; - } - - // If none of the above conditions are met, it's not a playable media - return false; - } - - public function generateFull(): string - { - $videoInfo = self::getVideoInfo($this->source); - $isAudio = self::isAudio($this->source); - $mediaType = $isAudio ? 'audio' : 'video'; - - $code = "
getText("a11y_{$mediaType}_player")) . "\">"; - - if (!$isAudio && $videoInfo['platform'] !== 'default') { - $consentTextKey = "consent_text_{$videoInfo['platform']}"; - $consentText = $this->getText($consentTextKey); - if ($consentText === "[[{$consentTextKey}]]") { - $consentText = $this->getText('consent_text_default'); - } - - $code .= $this->generateConsentPlaceholder($consentText, $videoInfo['platform'], $videoInfo['id']); - } - - $code .= $this->generate(); - - if (!$isAudio && $this->a11yContent) { - $code .= "
getText('a11y_additional_information')) . "\">" - . $this->a11yContent - . "
"; - } - - $code .= "
"; - return $code; - } - - public function generate(): string - { - $attributesString = $this->generateAttributesString(); - $titleAttr = $this->title ? " title=\"" . rex_escape($this->title) . "\"" : ''; - $sourceUrl = $this->getSourceUrl(); - $isAudio = self::isAudio($this->source); - $mediaType = $isAudio ? 'audio' : 'video'; - $videoInfo = self::getVideoInfo($this->source); - - $code = "getText('a11y_video_from')) . " " . rex_escape($videoInfo['platform']) . "\""; - // Never set src attribute for YouTube/Vimeo - this should be done by JavaScript after consent - } else { - $code .= " src=\"" . rex_escape($sourceUrl) . "\""; - // Add aria-label for local videos/audio to ensure proper accessibility - $ariaLabel = $this->title ?: $this->getText("a11y_{$mediaType}_player"); - $code .= " aria-label=\"" . rex_escape($ariaLabel) . "\""; - } - - $code .= " crossorigin>"; - - $code .= ""; - - if (!$isAudio && !empty($this->poster)) { - // Use title as fallback alt text if poster alt is empty for accessibility - $posterAlt = !empty($this->poster['alt']) ? $this->poster['alt'] : (!empty($this->title) ? $this->title : $this->getText('a11y_video_poster')); - $code .= "poster['src']) . "\" alt=\"" . rex_escape($posterAlt) . "\">"; - } - - if ($videoInfo['platform'] === 'default') { - $code .= $this->generateSourceElements(); - } - - // Move subtitles inside - if (!$isAudio) { - foreach ($this->subtitles as $subtitle) { - $defaultAttr = $subtitle['default'] ? ' default' : ''; - $code .= ""; - } - } - - $code .= ""; - - $code .= $isAudio ? "" : - "thumbnails ? " thumbnails=\"" . rex_escape($this->thumbnails) . "\"" : "") . ">"; - $code .= ""; - return $code; - } - - public function generateAttributesString(): string - { - return array_reduce(array_keys($this->attributes), function ($carry, $key) { - $value = $this->attributes[$key]; - return $carry . (is_bool($value) ? ($value ? " " . rex_escape($key) : '') : " " . rex_escape($key) . "=\"" . rex_escape($value) . "\""); - }, ''); - } - - public function generateConsentPlaceholder(string $consentText, string $platform, string $videoId): string - { - $buttonText = $this->getText('Load Video'); - return "
" - . "

" . rex_escape($consentText) . "

" - . "" - . "
"; - } - - public static function videoOembedHelper(): void - { - rex_extension::register('OUTPUT_FILTER', static function (rex_extension_point $ep) { - $content = $ep->getSubject(); - return self::parseOembedTags($content); - }, rex_extension::LATE); - } - - public static function parseOembedTags(string $content): string - { - return preg_replace_callback('/<\/oembed>/is', static function ($match) { - $video = new self($match[1]); - $videoInfo = self::getVideoInfo($match[1]); - - // For YouTube/Vimeo videos, don't use native controls - let Vidstack handle the UI - if ($videoInfo['platform'] === 'youtube' || $videoInfo['platform'] === 'vimeo') { - $video->setAttributes([ - 'crossorigin' => '', - 'playsinline' => true, - 'controls' => false // Important: disable native controls for embed videos - ]); - } else { - // For regular video files, use native controls - $video->setAttributes([ - 'crossorigin' => '', - 'playsinline' => true, - 'controls' => true - ]); - } - - return $video->generateFull(); - }, $content); - } - - // Fixed method with correct type hint - public static function show_sidebar(rex_extension_point $ep): ?string - { - $params = $ep->getParams(); - $file = $params['filename']; - - $existingContent = $ep->getSubject(); - - if (self::isMedia($file)) { - $isAudio = self::isAudio($file); - $mediaUrl = rex_url::media($file); - - if ($isAudio) { - $newContent = "" - . "" - . "" - . ""; - } else { - $media = new self($file); - $media->setAttributes([ - 'crossorigin' => '', - 'playsinline' => true, - 'controls' => true - ]); - $newContent = $media->generate(); - } - - // Video-Informationen hinzufügen, wenn FFmpeg-AddOn verfügbar ist - $videoInfo = self::getFFmpegVideoInfo($file); - if ($videoInfo) { - $newContent .= $videoInfo; - } - - return $existingContent . $newContent; - } - - return $existingContent; - } - - /** - * Video-Informationen für die Sidebar abrufen und formatieren - * - * @param string $filename Dateiname der Video-Datei - * @return string|null HTML mit Video-Informationen oder null - */ - private static function getFFmpegVideoInfo(string $filename): ?string - { - // Prüfen ob FFmpeg-AddOn verfügbar ist - $ffmpegAddon = \rex_addon::get('ffmpeg'); - if (!$ffmpegAddon->isAvailable()) { - return null; - } - - // Nur für Video-Dateien Informationen anzeigen - if (self::isAudio($filename)) { - return null; - } - - try { - // Video-Informationen über FFmpeg-AddOn abrufen - $videoInfo = \FriendsOfRedaxo\FFmpeg\VideoInfo::getBasicInfo($filename); - - if (!$videoInfo) { - return null; - } - - // Kompakte HTML-Ausgabe der Video-Informationen - $html = '
'; - $html .= '

' . rex_i18n::msg('vidstack_video_info_title') . '

'; - - // Dimensionen - if ($videoInfo['width'] && $videoInfo['height']) { - $html .= '
' . rex_i18n::msg('vidstack_video_info_resolution') . ': ' . - rex_escape($videoInfo['width']) . ' × ' . rex_escape($videoInfo['height']) . ' px'; - if ($videoInfo['aspect_ratio']) { - $html .= ' (' . rex_escape($videoInfo['aspect_ratio']) . ')'; - } - $html .= '
'; - } - - // Codec - if ($videoInfo['video_codec']) { - $html .= '
' . rex_i18n::msg('vidstack_video_info_codec') . ': ' . - rex_escape(strtoupper($videoInfo['video_codec'])) . '
'; - } - - // Dauer - if ($videoInfo['duration_formatted']) { - $html .= '
' . rex_i18n::msg('vidstack_video_info_duration') . ': ' . - rex_escape($videoInfo['duration_formatted']) . '
'; - } - - // Dateigröße - if ($videoInfo['filesize_formatted']) { - $html .= '
' . rex_i18n::msg('vidstack_video_info_filesize') . ': ' . - rex_escape($videoInfo['filesize_formatted']) . '
'; - } - - // Bitrate (optional, nur bei sinnvollen Werten) - if ($videoInfo['bitrate_formatted'] && $videoInfo['bitrate'] > 0) { - $html .= '
' . rex_i18n::msg('vidstack_video_info_bitrate') . ': ' . - rex_escape($videoInfo['bitrate_formatted']) . '
'; - } - - // Action-Buttons für FFmpeg-Tools hinzufügen - $html .= self::generateFFmpegActionButtons($filename); - - $html .= '
'; - - return $html; - - } catch (\Exception $e) { - // Bei Fehlern stillschweigend ignorieren - return null; - } - } - - /** - * Generiert Action-Buttons für FFmpeg-Tools - * - * @param string $filename Dateiname der Video-Datei - * @return string HTML mit Action-Buttons - */ - private static function generateFFmpegActionButtons(string $filename): string - { - // Zusätzliche Prüfung: Sind die FFmpeg-Seiten verfügbar? - $ffmpegAddon = \rex_addon::get('ffmpeg'); - if (!$ffmpegAddon->isAvailable()) { - return ''; - } - - $buttons = '
'; - $buttons .= '
' . rex_i18n::msg('vidstack_video_tools') . ':
'; - $buttons .= '
'; - - // Trimmer-Button - $trimmerUrl = rex_url::backendPage('mediapool/ffmpeg/trimmer') . '&video=' . urlencode($filename); - $buttons .= ''; - $buttons .= ' ' . rex_i18n::msg('vidstack_tool_trim') . ''; - - // Optimieren-Button (zur Haupt-FFmpeg-Seite) - $optimizeUrl = rex_url::backendPage('mediapool/ffmpeg') . '&video=' . urlencode($filename); - $buttons .= ''; - $buttons .= ' ' . rex_i18n::msg('vidstack_tool_optimize') . ''; - - // Info-Button - $infoUrl = rex_url::backendPage('mediapool/ffmpeg/info') . '&video=' . urlencode($filename); - $buttons .= ''; - $buttons .= ' ' . rex_i18n::msg('vidstack_tool_details') . ''; - - $buttons .= '
'; - - return $buttons; - } - - /** - * Vordefinierte Auflösungspresets für gängige Anwendungsfälle - */ - public static function getResolutionPresets(): array - { - return [ - '4k' => [3840, 2160], - '2k' => [2560, 1440], - '1080p' => [1920, 1080], - '720p' => [1280, 720], - '480p' => [854, 480], - '360p' => [640, 360], - '240p' => [426, 240], - 'mobile_hd' => [960, 540], - 'mobile_sd' => [640, 360], - 'tablet' => [1024, 576], - ]; - } - - /** - * Erweiterte responsive Sources mit Presets - * - * @param string $desktopSource Desktop Video - * @param string $mobileSource Mobile Video - * @param string $desktopPreset Preset-Name für Desktop (z.B. '1080p') - * @param string $mobilePreset Preset-Name für Mobile (z.B. '480p') - */ - public function setResponsiveSourcesWithPresets( - string $desktopSource, - string $mobileSource, - string $desktopPreset = '1080p', - string $mobilePreset = '480p' - ): void { - $presets = self::getResolutionPresets(); - - $desktopResolution = $presets[$desktopPreset] ?? $presets['1080p']; - $mobileResolution = $presets[$mobilePreset] ?? $presets['480p']; - - $this->setResponsiveSources($desktopSource, $mobileSource, $desktopResolution, $mobileResolution); - } - - /** - * Erstellt Sources automatisch aus einem Basis-Dateinamen - * - * @param string $baseFilename Basis-Dateiname ohne Suffix (z.B. 'video') - * @param array $qualityLevels Array von Qualitätsstufen mit Suffixen - * @param string $extension Dateierweiterung (default: 'mp4') - * - * Beispiel: createAutoSources('video', ['1080p' => [1920, 1080], '720p' => [1280, 720]]) - * Sucht nach: video-1080p.mp4, video-720p.mp4 - */ - public function createAutoSources( - string $baseFilename, - array $qualityLevels = null, - string $extension = 'mp4' - ): bool { - if ($qualityLevels === null) { - $qualityLevels = [ - '1080p' => [1920, 1080], - '720p' => [1280, 720], - '480p' => [854, 480] - ]; - } - - $sources = []; - $foundAny = false; - - foreach ($qualityLevels as $suffix => $resolution) { - $filename = $baseFilename . '-' . $suffix . '.' . $extension; - - // Prüfen ob Datei existiert (für REDAXO Media) - if (rex_media::get($filename)) { - $sources[] = [ - 'src' => $filename, - 'width' => $resolution[0], - 'height' => $resolution[1], - 'type' => $this->getMediaType($filename) - ]; - $foundAny = true; - } - } - - if ($foundAny) { - $this->setSources($sources); - return true; - } - - return false; - } -} diff --git a/package.yml b/package.yml index 8c0d4ce..6b15ad5 100644 --- a/package.yml +++ b/package.yml @@ -1,5 +1,5 @@ -package: vidstack -version: '1.7.2' +package: vidstack_player +version: '1.0.0-beta1' author: 'Friends Of REDAXO' supportpage: https://github.com/FriendsOfREDAXO/vidstack requires: diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..d3c9eef --- /dev/null +++ b/tests/README.md @@ -0,0 +1,46 @@ +# Tests (Coming Soon) + +This directory will contain PHPUnit tests for the Vidstack Player addon. + +## Planned Test Structure + +``` +tests/ +├── Unit/ +│ ├── VidstackPlayerTest.php +│ ├── PlatformDetectorTest.php +│ ├── UtilitiesTest.php +│ └── AssetHelperTest.php +├── Integration/ +│ ├── BackendIntegrationTest.php +│ └── OembedParserTest.php +└── bootstrap.php +``` + +## Test Coverage Goals + +- **VidstackPlayer**: All fluent methods, rendering logic +- **PlatformDetector**: YouTube/Vimeo/Local detection, media type detection +- **Utilities**: HTML attribute building, MIME type detection +- **AssetHelper**: CSS/JS generation, cache-busting +- **BackendIntegration**: Mediapool sidebar rendering (with mocked FFmpeg) +- **OembedParser**: oEmbed tag parsing and conversion + +## Running Tests + +```bash +# Install PHPUnit (when implemented) +composer require --dev phpunit/phpunit + +# Run tests +vendor/bin/phpunit tests/ + +# With coverage +vendor/bin/phpunit --coverage-html coverage/ tests/ +``` + +## Test Requirements + +- PHP 8.2+ +- REDAXO 5.17+ +- PHPUnit 10+