""" 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 '' 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'''
{label}
{'
Verse ' + str(from_verse) + '
' if show_indicator else ''} {'
' if show_slider else ''}
{'' if show_navigation else ''} {'' if show_navigation else ''}
''' 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, }