Spaces:
Running
on
Zero
Running
on
Zero
| """ | |
| Reference audio player component using native Gradio components. | |
| Provides the reference reciter audio player with multi-verse navigation. | |
| Supports: | |
| - Single verse: Just audio player | |
| - 2+ verses: Audio player with navigation arrows | |
| - 3+ verses: Audio player with arrows and slider | |
| Uses native Gradio components (gr.Audio, gr.Slider, gr.Button) instead of | |
| custom HTML/JavaScript for HF Spaces compatibility. | |
| """ | |
| from typing import Optional | |
| import gradio as gr | |
| from i8n import t | |
| def create_reference_audio_components(initial_audio_path: str | None = None) -> dict: | |
| """ | |
| Create native Gradio components for reference audio playback. | |
| Args: | |
| initial_audio_path: Optional path to initial audio file. Must be set during | |
| component creation (not after) to properly render the waveform. | |
| Returns a dict with all components needed for reference audio: | |
| - nav_row: Row containing navigation buttons and slider | |
| - prev_btn: Previous verse button | |
| - slider: Slider for verse navigation (embedded in nav row) | |
| - next_btn: Next verse button | |
| - audio_player: Native Gradio audio player with "Reference Reciter" label | |
| - verse_index: State tracking current index | |
| - verse_keys: State tracking list of verse keys | |
| Returns: | |
| Dict with component references | |
| """ | |
| with gr.Column(elem_classes=["reference-audio-section"]): | |
| # Navigation row with slider (hidden for single verse) | |
| # Layout: [◀ 15%] [slider 70%] [▶ 15%] | |
| with gr.Row(visible=False, elem_classes=["ref-audio-nav-row"]) as nav_row: | |
| prev_btn = gr.Button( | |
| "◀", | |
| scale=1, | |
| min_width=40, | |
| elem_classes=["ref-audio-nav-btn"] | |
| ) | |
| verse_slider = gr.Slider( | |
| minimum=0, | |
| maximum=0, | |
| step=1, | |
| value=0, | |
| label="", | |
| show_label=False, | |
| scale=5, | |
| elem_id="ref-audio-verse-slider", | |
| elem_classes=["ref-audio-slider"] | |
| ) | |
| next_btn = gr.Button( | |
| "▶", | |
| scale=1, | |
| min_width=40, | |
| elem_classes=["ref-audio-nav-btn"] | |
| ) | |
| # Audio player (always visible) | |
| audio_player = gr.Audio( | |
| value=initial_audio_path, | |
| label=t("audio.reference_reciter"), | |
| show_label=False, | |
| type="filepath", | |
| interactive=False, | |
| elem_classes=["ref-audio-player"] | |
| ) | |
| # State components | |
| verse_index = gr.State(0) | |
| verse_keys = gr.State([]) | |
| return { | |
| "nav_row": nav_row, | |
| "prev_btn": prev_btn, | |
| "slider": verse_slider, | |
| "next_btn": next_btn, | |
| "audio_player": audio_player, | |
| "verse_index": verse_index, | |
| "verse_keys": verse_keys, | |
| } | |
| # ============================================================================ | |
| # Legacy functions (kept for backward compatibility during transition) | |
| # ============================================================================ | |
| def get_placeholder_audio_html() -> str: | |
| """ | |
| Return placeholder HTML to prevent empty-state Gradio bug. | |
| DEPRECATED: Use create_reference_audio_components() instead. | |
| Kept for backward compatibility. | |
| """ | |
| return '<div id="ref-audio-container" style="display:none;"></div>' | |
| def build_reference_audio_html( | |
| data_uris: list[str] | str | None = None, | |
| urls: list[str] | str | None = None, | |
| verse_keys: list[str] | None = None, | |
| from_verse: int | None = None, | |
| to_verse: int | None = None, | |
| chapter: int | None = None, | |
| label: str = "Reference Reciter", | |
| lazy_load: bool = False | |
| ) -> str: | |
| """ | |
| Build HTML for audio player with multi-verse navigation. | |
| DEPRECATED: Use create_reference_audio_components() instead. | |
| Kept for backward compatibility during transition. | |
| """ | |
| # Determine which source to use (prefer urls for lazy loading) | |
| sources = urls if urls is not None else data_uris | |
| # Handle backward compatibility: convert single string to list | |
| if isinstance(sources, str): | |
| if not sources: | |
| return "" | |
| sources = [sources] | |
| if from_verse is None: | |
| from_verse = 1 | |
| if to_verse is None: | |
| to_verse = 1 | |
| # In lazy_load mode, use verse_keys to determine verse count | |
| if lazy_load and verse_keys: | |
| verse_count = len(verse_keys) | |
| elif sources: | |
| verse_count = len(sources) | |
| else: | |
| return "" | |
| # Ensure we have at least one source for the first verse | |
| if not sources or len(sources) == 0: | |
| return "" | |
| # Determine verse range | |
| if from_verse is None or to_verse is None: | |
| from_verse = 1 | |
| to_verse = verse_count | |
| # Determine what UI elements to show | |
| show_navigation = verse_count >= 2 | |
| show_slider = verse_count >= 3 | |
| show_indicator = verse_count >= 2 | |
| # Serialize sources (URLs or data URIs) to JSON for JavaScript | |
| import json | |
| sources_json = json.dumps(sources) | |
| verse_keys_json = json.dumps(verse_keys) if verse_keys else "[]" | |
| # CSS styles | |
| container_style = ''' | |
| position: relative; | |
| padding: 12px 14px; | |
| border: 1px solid var(--border-color-primary, #e5e7eb); | |
| border-radius: 10px; | |
| background: var(--background-fill-secondary, #f3f4f6); | |
| ''' | |
| header_style = ''' | |
| display: flex; | |
| justify-content: flex-start; | |
| align-items: center; | |
| margin-bottom: 8px; | |
| ''' | |
| title_style = ''' | |
| font-weight: 600; | |
| font-size: 14px; | |
| color: var(--body-text-color, inherit); | |
| ''' | |
| verse_indicator_style = ''' | |
| position: absolute; | |
| top: 8px; | |
| right: 14px; | |
| background: #fb8c00; | |
| color: white; | |
| padding: 4px 10px; | |
| border-radius: 6px; | |
| font-size: 12px; | |
| font-weight: 600; | |
| ''' | |
| slider_row_style = ''' | |
| margin-bottom: 10px; | |
| padding: 0 30px; | |
| ''' | |
| slider_style = ''' | |
| width: 100%; | |
| height: 6px; | |
| cursor: pointer; | |
| accent-color: #fb8c00; | |
| ''' | |
| player_row_style = ''' | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| ''' | |
| nav_button_style = ''' | |
| background: var(--button-secondary-background-fill, #f3f4f6); | |
| border: 1px solid var(--border-color-primary, #e5e7eb); | |
| border-radius: 6px; | |
| padding: 8px 12px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| font-weight: 600; | |
| transition: background 0.2s; | |
| color: var(--body-text-color, inherit); | |
| ''' | |
| audio_style = ''' | |
| flex: 1; | |
| height: 36px; | |
| min-width: 0; | |
| ''' | |
| # Build final HTML | |
| html = f''' | |
| <div id="ref-audio-container" | |
| style="{container_style}" | |
| data-verse-count="{verse_count}" | |
| data-current-verse="0" | |
| data-uris='{sources_json}' | |
| data-verse-keys='{verse_keys_json}' | |
| data-from-verse="{from_verse}" | |
| data-chapter="{chapter or 1}" | |
| data-lazy-load="{str(lazy_load).lower()}"> | |
| <div style="{header_style}"> | |
| <span style="{title_style}">{label}</span> | |
| </div> | |
| {'<div style="' + verse_indicator_style + '" id="verse-indicator">Verse ' + str(from_verse) + '</div>' if show_indicator else ''} | |
| {'<div style="' + slider_row_style + '"><input type="range" id="verse-slider" min="0" max="' + str(verse_count - 1) + '" value="0" style="' + slider_style + '"></div>' if show_slider else ''} | |
| <div style="{player_row_style}"> | |
| {'<button class="ref-audio-nav-btn" data-direction="-1" style="' + nav_button_style + '">◀</button>' if show_navigation else ''} | |
| <audio id="ref-audio-player" controls style="{audio_style}"> | |
| <source src="{sources[0]}" type="audio/mpeg"> | |
| Your browser does not support the audio element. | |
| </audio> | |
| {'<button class="ref-audio-nav-btn" data-direction="1" style="' + nav_button_style + '">▶</button>' if show_navigation else ''} | |
| </div> | |
| </div> | |
| ''' | |
| return html | |
| def create_reference_audio_section(init_audio_html: str) -> dict: | |
| """ | |
| Create reference audio section with just the audio player. | |
| DEPRECATED: Use create_reference_audio_components() instead. | |
| Kept for backward compatibility during transition. | |
| """ | |
| # Main audio player row | |
| with gr.Row(equal_height=True) as audio_row: | |
| with gr.Column(scale=1, min_width=80, visible=False) as dropdown_col: | |
| dropdown = gr.Dropdown(visible=False) | |
| with gr.Column(scale=4) as audio_col: | |
| audio_html = gr.HTML( | |
| value=init_audio_html, | |
| elem_id="reference-audio-container" | |
| ) | |
| return { | |
| "audio_row": audio_row, | |
| "audio_html": audio_html, | |
| "dropdown_col": dropdown_col, | |
| "dropdown": dropdown, | |
| } | |