Files
FastAnime/fastanime/cli/interactive/menus/player_controls.py
2025-07-23 21:17:03 +03:00

135 lines
4.6 KiB
Python

import threading
from typing import TYPE_CHECKING, Callable, Dict
import click
from rich.console import Console
from ..session import Context, session
from ..state import InternalDirective, State
if TYPE_CHECKING:
from ....libs.providers.anime.types import Server
@session.menu
def player_controls(ctx: Context, state: State) -> State | InternalDirective:
"""
Handles post-playback options like playing the next episode,
replaying, or changing streaming options.
"""
# --- State and Context Extraction ---
config = ctx.config
player = ctx.player
selector = ctx.selector
console = Console()
console.clear()
provider_anime = state.provider.anime
anilist_anime = state.media_api.anime
current_episode_num = state.provider.episode_number
selected_server = state.provider.selected_server
all_servers = state.provider.servers
player_result = state.provider.last_player_result
if not all(
(
provider_anime,
anilist_anime,
current_episode_num,
selected_server,
all_servers,
)
):
console.print(
"[bold red]Error: Player state is incomplete. Returning.[/bold red]"
)
return InternalDirective.BACK
# --- Auto-Next Logic ---
available_episodes = getattr(
provider_anime.episodes, config.stream.translation_type, []
)
current_index = available_episodes.index(current_episode_num)
if config.stream.auto_next and current_index < len(available_episodes) - 1:
console.print("[cyan]Auto-playing next episode...[/cyan]")
next_episode_num = available_episodes[current_index + 1]
# Track next episode in unified media registry
return State(
menu_name="SERVERS",
media_api=state.media_api,
provider=state.provider.model_copy(
update={"episode_number": next_episode_num}
),
)
# --- Action Definitions ---
def next_episode() -> State | InternalDirective:
if current_index < len(available_episodes) - 1:
next_episode_num = available_episodes[current_index + 1]
# Transition back to the SERVERS menu with the new episode number.
return State(
menu_name="SERVERS",
media_api=state.media_api,
provider=state.provider.model_copy(
update={"episode_number": next_episode_num}
),
)
console.print("[bold yellow]This is the last available episode.[/bold yellow]")
return InternalDirective.CONTINUE
def replay() -> State | InternalDirective:
# We don't need to change state, just re-trigger the SERVERS menu's logic.
return State(
menu_name="SERVERS", media_api=state.media_api, provider=state.provider
)
def change_server() -> State | InternalDirective:
server_map: Dict[str, Server] = {s.name: s for s in all_servers}
new_server_name = selector.choose(
"Select a different server:", list(server_map.keys())
)
if new_server_name:
# Update the selected server and re-run the SERVERS logic.
return State(
menu_name="SERVERS",
media_api=state.media_api,
provider=state.provider.model_copy(
update={"selected_server": server_map[new_server_name]}
),
)
return InternalDirective.CONTINUE
# --- Menu Options ---
icons = config.general.icons
options: Dict[str, Callable[[], State | InternalDirective]] = {}
if current_index < len(available_episodes) - 1:
options[f"{'⏭️ ' if icons else ''}Next Episode"] = next_episode
options.update(
{
f"{'🔄 ' if icons else ''}Replay Episode": replay,
f"{'💻 ' if icons else ''}Change Server": change_server,
f"{'🎞️ ' if icons else ''}Back to Episode List": lambda: State(
menu_name="EPISODES", media_api=state.media_api, provider=state.provider
),
f"{'🏠 ' if icons else ''}Main Menu": lambda: State(menu_name="MAIN"),
f"{'' if icons else ''}Exit": lambda: InternalDirective.EXIT,
}
)
# --- Prompt and Execute ---
header = f"Finished Episode {current_episode_num} of {provider_anime.title}"
choice_str = selector.choose(
prompt="What's next?", choices=list(options.keys()), header=header
)
if choice_str and choice_str in options:
return options[choice_str]()
return InternalDirective.BACK