mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-12 15:50:01 -08:00
feat(mpv-ipc): basic support for media registry
This commit is contained in:
@@ -136,7 +136,9 @@ def downloads_player_controls(
|
||||
query=media_item.title.english or media_item.title.romaji or "",
|
||||
episode=current_episode_num,
|
||||
start_time=current_start_time,
|
||||
)
|
||||
),
|
||||
media_item=media_item,
|
||||
local=True,
|
||||
)
|
||||
|
||||
# Track watch history after playing
|
||||
|
||||
@@ -133,7 +133,9 @@ class Context:
|
||||
if not self._player:
|
||||
from ..service.player import PlayerService
|
||||
|
||||
self._player = PlayerService(self.config, self.provider)
|
||||
self._player = PlayerService(
|
||||
self.config, self.provider, self.media_registry
|
||||
)
|
||||
return self._player
|
||||
|
||||
@property
|
||||
|
||||
@@ -8,6 +8,7 @@ from .....libs.player.params import PlayerParams
|
||||
from .....libs.player.types import PlayerResult
|
||||
from .....libs.provider.anime.base import BaseAnimeProvider
|
||||
from .....libs.provider.anime.types import Anime
|
||||
from ....service.registry import MediaRegistryService
|
||||
|
||||
|
||||
class BaseIPCPlayer(ABC):
|
||||
@@ -23,8 +24,9 @@ class BaseIPCPlayer(ABC):
|
||||
self,
|
||||
player: BasePlayer,
|
||||
player_params: PlayerParams,
|
||||
provider: BaseAnimeProvider,
|
||||
anime: Anime,
|
||||
provider: Optional[BaseAnimeProvider] = None,
|
||||
anime: Optional[Anime] = None,
|
||||
registry: Optional[MediaRegistryService] = None,
|
||||
media_item: Optional[MediaItem] = None,
|
||||
) -> PlayerResult:
|
||||
"""
|
||||
|
||||
@@ -25,6 +25,8 @@ from .....libs.player.types import PlayerResult
|
||||
from .....libs.provider.anime.base import BaseAnimeProvider
|
||||
from .....libs.provider.anime.params import EpisodeStreamsParams
|
||||
from .....libs.provider.anime.types import Anime, ProviderServer, Server
|
||||
from ....service.registry.models import DownloadStatus
|
||||
from ...registry import MediaRegistryService
|
||||
from .base import BaseIPCPlayer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -260,9 +262,11 @@ class MpvIPCPlayer(BaseIPCPlayer):
|
||||
property_observers: Dict[str, List[Callable]] = {}
|
||||
key_bindings: Dict[str, Callable] = {}
|
||||
message_handlers: Dict[str, Callable] = {}
|
||||
provider: BaseAnimeProvider
|
||||
anime: Anime
|
||||
media_item: Optional[MediaItem]
|
||||
provider: Optional[BaseAnimeProvider] = None
|
||||
anime: Optional[Anime] = None
|
||||
media_item: Optional[MediaItem] = None
|
||||
|
||||
registry: Optional[MediaRegistryService] = None
|
||||
|
||||
def __init__(self, stream_config: StreamConfig):
|
||||
super().__init__(stream_config)
|
||||
@@ -271,11 +275,18 @@ class MpvIPCPlayer(BaseIPCPlayer):
|
||||
self._fetch_result_queue: Queue = Queue()
|
||||
|
||||
def play(
|
||||
self, player, player_params, provider, anime, media_item=None
|
||||
self,
|
||||
player: BasePlayer,
|
||||
player_params: PlayerParams,
|
||||
provider: Optional[BaseAnimeProvider] = None,
|
||||
anime: Optional[Anime] = None,
|
||||
registry: Optional[MediaRegistryService] = None,
|
||||
media_item: Optional[MediaItem] = None,
|
||||
) -> PlayerResult:
|
||||
self.provider = provider
|
||||
self.anime = anime
|
||||
self.media_item = media_item
|
||||
self.registry = registry
|
||||
self.player_state = PlayerState(
|
||||
self.stream_config,
|
||||
player_params.query,
|
||||
@@ -429,7 +440,7 @@ class MpvIPCPlayer(BaseIPCPlayer):
|
||||
self._handle_property_change(message)
|
||||
elif event == "client-message":
|
||||
self._handle_client_message(message)
|
||||
elif event == "file-loaded":
|
||||
elif event == "file-loaded" and not self.registry:
|
||||
self._configure_player()
|
||||
elif event:
|
||||
logger.debug(f"MPV event: {event}")
|
||||
@@ -497,52 +508,101 @@ class MpvIPCPlayer(BaseIPCPlayer):
|
||||
):
|
||||
"""This function runs in a background thread to fetch episode streams."""
|
||||
try:
|
||||
available_episodes = getattr(
|
||||
self.anime.episodes, self.stream_config.translation_type
|
||||
)
|
||||
if not available_episodes:
|
||||
raise ValueError(
|
||||
f"No {self.stream_config.translation_type} episodes available."
|
||||
if self.anime and self.provider:
|
||||
available_episodes = getattr(
|
||||
self.anime.episodes, self.stream_config.translation_type
|
||||
)
|
||||
|
||||
current_index = available_episodes.index(self.player_state.episode)
|
||||
|
||||
if episode_type == "next":
|
||||
if current_index >= len(available_episodes) - 1:
|
||||
raise ValueError("Already at the last episode.")
|
||||
target_episode = available_episodes[current_index + 1]
|
||||
elif episode_type == "previous":
|
||||
if current_index <= 0:
|
||||
raise ValueError("Already at first episode")
|
||||
target_episode = available_episodes[current_index - 1]
|
||||
elif episode_type == "reload":
|
||||
target_episode = self.player_state.episode
|
||||
elif episode_type == "custom":
|
||||
if not ep_no or ep_no not in available_episodes:
|
||||
if not available_episodes:
|
||||
raise ValueError(
|
||||
f"Invalid episode. Available: {', '.join(available_episodes)}"
|
||||
f"No {self.stream_config.translation_type} episodes available."
|
||||
)
|
||||
target_episode = ep_no
|
||||
else:
|
||||
return
|
||||
|
||||
stream_params = EpisodeStreamsParams(
|
||||
anime_id=self.anime.id,
|
||||
query=self.player_state.query,
|
||||
episode=target_episode,
|
||||
translation_type=self.stream_config.translation_type,
|
||||
)
|
||||
# This is the blocking network call, now safely in a thread
|
||||
episode_streams = list(self.provider.episode_streams(stream_params) or [])
|
||||
if not episode_streams:
|
||||
raise ValueError(f"No streams found for episode {target_episode}")
|
||||
current_index = available_episodes.index(self.player_state.episode)
|
||||
|
||||
result = {
|
||||
"type": "success",
|
||||
"target_episode": target_episode,
|
||||
"servers": {ProviderServer(s.name): s for s in episode_streams},
|
||||
}
|
||||
self._fetch_result_queue.put(result)
|
||||
if episode_type == "next":
|
||||
if current_index >= len(available_episodes) - 1:
|
||||
raise ValueError("Already at the last episode.")
|
||||
target_episode = available_episodes[current_index + 1]
|
||||
elif episode_type == "previous":
|
||||
if current_index <= 0:
|
||||
raise ValueError("Already at first episode")
|
||||
target_episode = available_episodes[current_index - 1]
|
||||
elif episode_type == "reload":
|
||||
target_episode = self.player_state.episode
|
||||
elif episode_type == "custom":
|
||||
if not ep_no or ep_no not in available_episodes:
|
||||
raise ValueError(
|
||||
f"Invalid episode. Available: {', '.join(available_episodes)}"
|
||||
)
|
||||
target_episode = ep_no
|
||||
else:
|
||||
return
|
||||
|
||||
stream_params = EpisodeStreamsParams(
|
||||
anime_id=self.anime.id,
|
||||
query=self.player_state.query,
|
||||
episode=target_episode,
|
||||
translation_type=self.stream_config.translation_type,
|
||||
)
|
||||
# This is the blocking network call, now safely in a thread
|
||||
episode_streams = list(
|
||||
self.provider.episode_streams(stream_params) or []
|
||||
)
|
||||
if not episode_streams:
|
||||
raise ValueError(f"No streams found for episode {target_episode}")
|
||||
|
||||
result = {
|
||||
"type": "success",
|
||||
"target_episode": target_episode,
|
||||
"servers": {ProviderServer(s.name): s for s in episode_streams},
|
||||
}
|
||||
self._fetch_result_queue.put(result)
|
||||
elif self.registry and self.media_item:
|
||||
record = self.registry.get_media_record(self.media_item.id)
|
||||
if not record or not record.media_episodes:
|
||||
logger.warning("No downloaded episodes found for this anime.")
|
||||
return
|
||||
|
||||
downloaded_episodes = {
|
||||
ep.episode_number: ep.file_path
|
||||
for ep in record.media_episodes
|
||||
if ep.download_status == DownloadStatus.COMPLETED
|
||||
and ep.file_path
|
||||
and ep.file_path.exists()
|
||||
}
|
||||
available_episodes = list(sorted(downloaded_episodes.keys(), key=float))
|
||||
current_index = available_episodes.index(self.player_state.episode)
|
||||
|
||||
if episode_type == "next":
|
||||
if current_index >= len(available_episodes) - 1:
|
||||
raise ValueError("Already at the last episode.")
|
||||
target_episode = available_episodes[current_index + 1]
|
||||
elif episode_type == "previous":
|
||||
if current_index <= 0:
|
||||
raise ValueError("Already at first episode")
|
||||
target_episode = available_episodes[current_index - 1]
|
||||
elif episode_type == "reload":
|
||||
target_episode = self.player_state.episode
|
||||
elif episode_type == "custom":
|
||||
if not ep_no or ep_no not in available_episodes:
|
||||
raise ValueError(
|
||||
f"Invalid episode. Available: {', '.join(available_episodes)}"
|
||||
)
|
||||
target_episode = ep_no
|
||||
else:
|
||||
return
|
||||
file_path = downloaded_episodes[target_episode]
|
||||
|
||||
self.player_state.reset()
|
||||
self.player_state.episode = target_episode
|
||||
self.ipc_client.send_command(["loadfile", str(file_path)])
|
||||
time.sleep(1)
|
||||
self.ipc_client.send_command(["seek", 0, "absolute"])
|
||||
self.ipc_client.send_command(
|
||||
["set_property", "title", self.player_state.episode_title]
|
||||
)
|
||||
self._show_text(f"Fetched {file_path}")
|
||||
self.player_fetching = False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Episode fetch task failed: {e}")
|
||||
|
||||
@@ -10,6 +10,7 @@ from ....libs.player.player import create_player
|
||||
from ....libs.player.types import PlayerResult
|
||||
from ....libs.provider.anime.base import BaseAnimeProvider
|
||||
from ....libs.provider.anime.types import Anime
|
||||
from ..registry import MediaRegistryService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -18,10 +19,18 @@ class PlayerService:
|
||||
app_config: AppConfig
|
||||
provider: BaseAnimeProvider
|
||||
player: BasePlayer
|
||||
registry: Optional[MediaRegistryService] = None
|
||||
local: bool = False
|
||||
|
||||
def __init__(self, app_config: AppConfig, provider: BaseAnimeProvider):
|
||||
def __init__(
|
||||
self,
|
||||
app_config: AppConfig,
|
||||
provider: BaseAnimeProvider,
|
||||
registry: Optional[MediaRegistryService] = None,
|
||||
):
|
||||
self.app_config = app_config
|
||||
self.provider = provider
|
||||
self.registry = registry
|
||||
self.player = create_player(app_config)
|
||||
|
||||
def play(
|
||||
@@ -29,9 +38,11 @@ class PlayerService:
|
||||
params: PlayerParams,
|
||||
anime: Optional[Anime] = None,
|
||||
media_item: Optional[MediaItem] = None,
|
||||
local: bool = False,
|
||||
) -> PlayerResult:
|
||||
self.local = local
|
||||
if self.app_config.stream.use_ipc:
|
||||
if anime:
|
||||
if anime or self.registry:
|
||||
return self._play_with_ipc(params, anime, media_item)
|
||||
else:
|
||||
logger.warning(
|
||||
@@ -40,13 +51,17 @@ class PlayerService:
|
||||
return self.player.play(params)
|
||||
|
||||
def _play_with_ipc(
|
||||
self, params: PlayerParams, anime: Anime, media_item: Optional[MediaItem] = None
|
||||
self,
|
||||
params: PlayerParams,
|
||||
anime: Optional[Anime] = None,
|
||||
media_item: Optional[MediaItem] = None,
|
||||
) -> PlayerResult:
|
||||
if self.app_config.stream.player == "mpv":
|
||||
from .ipc.mpv import MpvIPCPlayer
|
||||
|
||||
registry = self.registry if self.local else None
|
||||
return MpvIPCPlayer(self.app_config.stream).play(
|
||||
self.player, params, self.provider, anime, media_item
|
||||
self.player, params, self.provider, anime, registry, media_item
|
||||
)
|
||||
else:
|
||||
raise FastAnimeError("Not implemented")
|
||||
|
||||
Reference in New Issue
Block a user