""" Android Media Service 组件 — MediaBrowserService, MediaSessionService, BrowserRoot, Result 等 方法签名完全对齐 Android SDK (android.service.media 包) """ from __future__ import annotations import time from typing import Any, Callable, Dict, List, Optional, Set, Tuple from .._core import Stub, AndroidOnlyError from ..java.lang import RuntimeException # ═══════════════════════════════════════════════════════════════ # Result # ═══════════════════════════════════════════════════════════════ class Result(Stub): """Result — 异步操作结果""" FLAG_OPTION_NOT_KNOWN = 0 FLAG_OPTION_FOUND = 1 FLAG_OPTION_NOT_FOUND = 2 FLAG_OPTION_ERROR = 4 def __init__(self, result_type: Any = None): super().__init__() self._result_type = result_type self._result: Any = None self._flags: int = Result.FLAG_OPTION_NOT_KNOWN self._is_done: bool = False self._is_sent: bool = False self._error: Optional[str] = None def getResult(self) -> Any: return self._result def setResult(self, result: Any): self._result = result def getFlags(self) -> int: return self._flags def setFlags(self, flags: int): self._flags = flags def isDone(self) -> bool: return self._is_done def isSent(self) -> bool: return self._is_sent def getError(self) -> Optional[str]: return self._error def sendResult(self, result: Any): """发送结果""" self._result = result self._is_done = True self._is_sent = True def sendError(self, error: str): """发送错误""" self._error = error self._is_done = True self._is_sent = True self._flags = Result.FLAG_OPTION_ERROR def onResultSent(self, result: Any, flags: int = 0): """结果已发送回调""" self._result = result self._flags = flags self._is_done = True def toString(self) -> str: return f"Result(done={self._is_done}, sent={self._is_sent})" # ═══════════════════════════════════════════════════════════════ # BrowserRoot # ═══════════════════════════════════════════════════════════════ class BrowserRoot(Stub): """BrowserRoot — 媒体浏览根节点""" EXTRA_RECENT = "android.service.media.extra.RECENT" EXTRA_OFFLINE = "android.service.media.extra.OFFLINE" EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED" EXTRA_EMPTY_ROOT_ALLOWED = "android.service.media.extra.EMPTY_ROOT_ALLOWED" EXTRA_RECYCLERVIEW = "android.service.media.extra.RECYCLERVIEW" def __init__(self, root_id: str = "", extras: Any = None): super().__init__() self._root_id = root_id self._extras = extras self._title: str = "" self._is_visible: bool = True self._is_playable: bool = False self._is_browsable: bool = True self._children: List[MediaBrowserService.BrowserRoot] = [] self._icon_uri: str = "" self._subtitle: str = "" self._description: str = "" def getRootId(self) -> str: return self._root_id def setRootId(self, root_id: str): self._root_id = root_id def getExtras(self) -> Any: return self._extras def setExtras(self, extras: Any): self._extras = extras def getTitle(self) -> str: return self._title def setTitle(self, title: str): self._title = title def isVisible(self) -> bool: return self._is_visible def setVisible(self, visible: bool): self._is_visible = visible def isPlayable(self) -> bool: return self._is_playable def setPlayable(self, playable: bool): self._is_playable = playable def isBrowsable(self) -> bool: return self._is_browsable def setBrowsable(self, browsable: bool): self._is_browsable = browsable def getIconUri(self) -> str: return self._icon_uri def setIconUri(self, uri: str): self._icon_uri = uri def getSubtitle(self) -> str: return self._subtitle def setSubtitle(self, subtitle: str): self._subtitle = subtitle def getDescription(self) -> str: return self._description def setDescription(self, desc: str): self._description = desc def toString(self) -> str: return f"BrowserRoot(rootId={self._root_id})" # ═══════════════════════════════════════════════════════════════ # MediaBrowserService # ═══════════════════════════════════════════════════════════════ class MediaBrowserService(Stub): """MediaBrowserService — 媒体浏览服务""" SERVICE_INTERFACE = "android.media.browse.MediaBrowserService" SERVICE_META_DATA = "android.media.browse" KEY_MEDIA_ITEM = "media_item" KEY_SEARCH_RESULTS = "search_results" KEY_ERROR = "error" KEY_MEDIA_ROOT = "media_root" KEY_RECENT = "recent" KEY_OFFLINE = "offline" KEY_SUGGESTED = "suggested" KEY_EMPTY_ROOT_ALLOWED = "empty_root_allowed" KEY_PAGE = "page" KEY_PAGE_SIZE = "page_size" # ═══════════════════════════════════════════════════════ # BrowserRoot (inner class) # ═══════════════════════════════════════════════════════ class BrowserRoot(Stub): """MediaBrowserService.BrowserRoot — 浏览根节点""" EXTRA_RECENT = "android.service.media.extra.RECENT" EXTRA_OFFLINE = "android.service.media.extra.OFFLINE" EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED" EXTRA_EMPTY_ROOT_ALLOWED = "android.service.media.extra.EMPTY_ROOT_ALLOWED" def __init__(self, root_id: str = "", extras: Any = None): super().__init__() self._root_id = root_id self._extras = extras def getRootId(self) -> str: return self._root_id def getExtras(self) -> Any: return self._extras # ═══════════════════════════════════════════════════════ # Result (inner class) # ═══════════════════════════════════════════════════════ class Result(Stub): """MediaBrowserService.Result — 服务结果""" FLAG_OPTION_NOT_KNOWN = 0 FLAG_OPTION_FOUND = 1 FLAG_OPTION_NOT_FOUND = 2 FLAG_OPTION_ERROR = 4 def __init__(self): super().__init__() self._result: Any = None self._flags: int = MediaBrowserService.Result.FLAG_OPTION_NOT_KNOWN self._is_done: bool = False self._error: Optional[str] = None def getResult(self) -> Any: return self._result def getFlags(self) -> int: return self._flags def isDone(self) -> bool: return self._is_done def sendResult(self, result: Any, flags: int = 0): self._result = result self._flags = flags self._is_done = True def sendError(self, error: str): self._error = error self._flags = MediaBrowserService.Result.FLAG_OPTION_ERROR self._is_done = True def onResultSent(self, result: Any, flags: int = 0): self._result = result self._flags = flags self._is_done = True def __init__(self): super().__init__() self._root: Optional[MediaBrowserService.BrowserRoot] = None self._session: Optional[MediaSessionService.MediaSession] = None self._is_connected: bool = False self._media_items: Dict[str, Any] = {} self._children_cache: Dict[str, List[Any]] = {} self._search_results: Dict[str, List[Any]] = {} self._package_name: str = "" self._on_load_children_callbacks: Dict[str, Callable] = {} self._on_load_item_callbacks: Dict[str, Callable] = {} def onGetRoot(self, client_package_name: str, client_uid: int, root_hints: Any = None) -> Optional[BrowserRoot]: """获取根节点回调""" return self._root def onLoadChildren(self, parent_id: str, result: Result): """加载子节点回调""" children = self._children_cache.get(parent_id, []) result.sendResult(children) def onLoadChildrenWithOptions(self, parent_id: str, result: Result, options: Any = None): """加载子节点 (带选项) 回调""" self.onLoadChildren(parent_id, result) def onLoadItem(self, item_id: str, result: Result): """加载媒体项回调""" item = self._media_items.get(item_id) if item: result.sendResult(item) else: result.sendError(f"Item not found: {item_id}") def onSearch(self, query: str, extras: Any, result: Result): """搜索回调""" results = self._search_results.get(query, []) result.sendResult(results) def onCustomAction(self, action: str, extras: Any, result: Result): """自定义动作回调""" result.sendResult(None) def setRoot(self, root: BrowserRoot): self._root = root def getRoot(self) -> Optional[BrowserRoot]: return self._root def setSession(self, session: MediaSessionService.MediaSession): self._session = session def getSession(self) -> Optional[MediaSessionService.MediaSession]: return self._session def setMediaItems(self, items: Dict[str, Any]): self._media_items = dict(items) def addMediaItem(self, item_id: str, item: Any): self._media_items[item_id] = item def setChildren(self, parent_id: str, children: List[Any]): self._children_cache[parent_id] = list(children) def addChild(self, parent_id: str, child: Any): if parent_id not in self._children_cache: self._children_cache[parent_id] = [] self._children_cache[parent_id].append(child) def setSearchResults(self, query: str, results: List[Any]): self._search_results[query] = list(results) def isConnected(self) -> bool: return self._is_connected def setConnected(self, connected: bool): self._is_connected = connected def getPackageName(self) -> str: return self._package_name def setPackageName(self, pkg: str): self._package_name = pkg def notifyChildrenChanged(self, parent_id: str): """通知子节点已变更""" pass def notifyChildrenChangedWithOptions(self, parent_id: str, options: Any = None): """通知子节点已变更 (带选项)""" pass def toString(self) -> str: return f"MediaBrowserService(root={self._root})" # ═══════════════════════════════════════════════════════════════ # MediaSessionService # ═══════════════════════════════════════════════════════════════ class MediaSessionService(Stub): """MediaSessionService — 媒体会话服务""" SERVICE_INTERFACE = "android.media.session.MediaSessionService" SERVICE_META_DATA = "android.media.session" KEY_MEDIA_SESSION = "media_session" KEY_MEDIA_SESSION_TOKEN = "media_session_token" KEY_MEDIA_CONTROLLER = "media_controller" KEY_PLAYBACK_STATE = "playback_state" KEY_METADATA = "metadata" KEY_QUEUE = "queue" KEY_QUEUE_TITLE = "queue_title" KEY_ACTIONS = "actions" KEY_ERROR = "error" KEY_EXTRAS = "extras" KEY_RATING = "rating" KEY_REPEAT_MODE = "repeat_mode" KEY_SHUFFLE_MODE = "shuffle_mode" # ═══════════════════════════════════════════════════════ # MediaSession # ═══════════════════════════════════════════════════════ class MediaSession(Stub): """MediaSessionService.MediaSession — 媒体会话""" FLAG_HANDLES_MEDIA_BUTTONS = 1 FLAG_HANDLES_TRANSPORT_CONTROLS = 2 FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 4 PLAYBACK_STATE_NONE = 0 PLAYBACK_STATE_STOPPED = 1 PLAYBACK_STATE_PAUSED = 2 PLAYBACK_STATE_PLAYING = 3 PLAYBACK_STATE_FAST_FORWARDING = 4 PLAYBACK_STATE_REWINDING = 5 PLAYBACK_STATE_BUFFERING = 6 PLAYBACK_STATE_ERROR = 7 PLAYBACK_STATE_SKIPPING_TO_PREVIOUS = 8 PLAYBACK_STATE_SKIPPING_TO_NEXT = 9 PLAYBACK_STATE_SKIPPING_TO_QUEUE_ITEM = 10 PLAYBACK_STATE_CONNECTING = 11 REPEAT_MODE_NONE = 0 REPEAT_MODE_ONE = 1 REPEAT_MODE_ALL = 2 REPEAT_MODE_GROUP = 3 SHUFFLE_MODE_NONE = 0 SHUFFLE_MODE_ALL = 1 SHUFFLE_MODE_GROUP = 2 ACTION_PLAY = 1 ACTION_PAUSE = 2 ACTION_PLAY_PAUSE = 4 ACTION_STOP = 8 ACTION_SEEK_TO = 16 ACTION_FAST_FORWARD = 32 ACTION_REWIND = 64 ACTION_SKIP_TO_NEXT = 128 ACTION_SKIP_TO_PREVIOUS = 256 ACTION_SKIP_TO_QUEUE_ITEM = 512 ACTION_SET_RATING = 1024 ACTION_SET_REPEAT_MODE = 2048 ACTION_SET_SHUFFLE_MODE = 4096 ACTION_SET_CAPTIONING_ENABLED = 8192 ACTION_PLAY_FROM_MEDIA_ID = 16384 ACTION_PLAY_FROM_SEARCH = 32768 ACTION_PLAY_FROM_URI = 65536 ACTION_PREPARE = 131072 ACTION_PREPARE_FROM_MEDIA_ID = 262144 ACTION_PREPARE_FROM_SEARCH = 524288 ACTION_PREPARE_FROM_URI = 1048576 class Callback(Stub): """MediaSessionService.MediaSession.Callback — 会话回调""" def onPlay(self): pass def onPause(self): pass def onStop(self): pass def onSeekTo(self, pos: int): pass def onFastForward(self): pass def onRewind(self): pass def onSkipToNext(self): pass def onSkipToPrevious(self): pass def onSkipToQueueItem(self, id: int): pass def onSetRating(self, rating: Any): pass def onSetRepeatMode(self, repeat_mode: int): pass def onSetShuffleMode(self, shuffle_mode: int): pass def onPlayFromMediaId(self, media_id: str, extras: Any): pass def onPlayFromSearch(self, query: str, extras: Any): pass def onPlayFromUri(self, uri: str, extras: Any): pass def onPrepare(self): pass def onPrepareFromMediaId(self, media_id: str, extras: Any): pass def onPrepareFromSearch(self, query: str, extras: Any): pass def onPrepareFromUri(self, uri: str, extras: Any): pass def onCommand(self, command: str, extras: Any, cb: Any): pass def onCustomAction(self, action: str, extras: Any): pass def onMediaButtonEvent(self, intent: Any) -> bool: return False def __init__(self, tag: str = ""): super().__init__() self._tag = tag self._callback: Optional[MediaSessionService.MediaSession.Callback] = None self._playback_state: int = MediaSessionService.MediaSession.PLAYBACK_STATE_NONE self._metadata: Any = None self._queue: List[Any] = [] self._queue_title: str = "" self._actions: int = 0 self._repeat_mode: int = MediaSessionService.MediaSession.REPEAT_MODE_NONE self._shuffle_mode: int = MediaSessionService.MediaSession.SHUFFLE_MODE_NONE self._active: bool = False self._flags: int = 0 self._position: int = 0 self._duration: int = 0 self._error: Optional[str] = None self._extras: Dict[str, Any] = {} def setCallback(self, callback: Callback): self._callback = callback def getCallback(self) -> Optional[Callback]: return self._callback def setActive(self, active: bool): self._active = active def isActive(self) -> bool: return self._active def setFlags(self, flags: int): self._flags = flags def getFlags(self) -> int: return self._flags def setPlaybackState(self, state: int): self._playback_state = state def getPlaybackState(self) -> int: return self._playback_state def setMetadata(self, metadata: Any): self._metadata = metadata def getMetadata(self) -> Any: return self._metadata def setQueue(self, queue: List[Any]): self._queue = list(queue) def getQueue(self) -> List[Any]: return list(self._queue) def setQueueTitle(self, title: str): self._queue_title = title def getQueueTitle(self) -> str: return self._queue_title def setActions(self, actions: int): self._actions = actions def getActions(self) -> int: return self._actions def setRepeatMode(self, mode: int): self._repeat_mode = mode def getRepeatMode(self) -> int: return self._repeat_mode def setShuffleMode(self, mode: int): self._shuffle_mode = mode def getShuffleMode(self) -> int: return self._shuffle_mode def getTag(self) -> str: return self._tag def setPosition(self, pos: int): self._position = pos def getPosition(self) -> int: return self._position def setDuration(self, duration: int): self._duration = duration def getDuration(self) -> int: return self._duration def setError(self, error: str): self._error = error def getError(self) -> Optional[str]: return self._error def getExtras(self) -> Dict[str, Any]: return dict(self._extras) def setExtras(self, extras: Dict[str, Any]): self._extras = dict(extras) def isPlaying(self) -> bool: return self._playback_state == MediaSessionService.MediaSession.PLAYBACK_STATE_PLAYING def toString(self) -> str: return f"MediaSession(tag={self._tag}, state={self._playback_state})" def __init__(self): super().__init__() self._sessions: Dict[str, MediaSessionService.MediaSession] = {} self._browser_service: Optional[MediaBrowserService] = None self._is_running: bool = False def onCreate(self): """创建服务""" self._is_running = True def onDestroy(self): """销毁服务""" self._is_running = False for session in self._sessions.values(): session.setActive(False) self._sessions.clear() def onGetSession(self, controller_info: Any) -> Optional[MediaSession]: """获取会话回调""" if self._sessions: return next(iter(self._sessions.values())) return None def getSessions(self) -> List[MediaSession]: return list(self._sessions.values()) def addSession(self, tag: str, session: MediaSession): self._sessions[tag] = session def removeSession(self, tag: str): self._sessions.pop(tag, None) def getSession(self, tag: str) -> Optional[MediaSession]: return self._sessions.get(tag) def setBrowserService(self, service: MediaBrowserService): self._browser_service = service def getBrowserService(self) -> Optional[MediaBrowserService]: return self._browser_service def isRunning(self) -> bool: return self._is_running def toString(self) -> str: return f"MediaSessionService(sessions={len(self._sessions)})" # ═══════════════════════════════════════════════════════════════ # MediaSessionServiceCallback # ═══════════════════════════════════════════════════════════════ class MediaSessionServiceCallback(Stub): """MediaSessionServiceCallback — 媒体会话服务回调""" def onSessionCreated(self, session: MediaSessionService.MediaSession): pass def onSessionDestroyed(self, session: MediaSessionService.MediaSession): pass def onSessionActive(self, session: MediaSessionService.MediaSession): pass def onSessionInactive(self, session: MediaSessionService.MediaSession): pass def onPlaybackStateChanged(self, session: MediaSessionService.MediaSession, state: int): pass def onMetadataChanged(self, session: MediaSessionService.MediaSession, metadata: Any): pass # ═══════════════════════════════════════════════════════════════ # MediaSessionUtils # ═══════════════════════════════════════════════════════════════ class MediaSessionUtils(Stub): """MediaSessionUtils — 媒体会话工具类""" @staticmethod def createMediaSession(tag: str) -> MediaSessionService.MediaSession: return MediaSessionService.MediaSession(tag) @staticmethod def createBrowserRoot(root_id: str, extras: Any = None) -> BrowserRoot: return BrowserRoot(root_id, extras) @staticmethod def createServiceResult() -> MediaBrowserService.Result: return MediaBrowserService.Result() @staticmethod def getPlaybackStateName(state: int) -> str: names = { MediaSessionService.MediaSession.PLAYBACK_STATE_NONE: "NONE", MediaSessionService.MediaSession.PLAYBACK_STATE_STOPPED: "STOPPED", MediaSessionService.MediaSession.PLAYBACK_STATE_PAUSED: "PAUSED", MediaSessionService.MediaSession.PLAYBACK_STATE_PLAYING: "PLAYING", MediaSessionService.MediaSession.PLAYBACK_STATE_FAST_FORWARDING: "FAST_FORWARDING", MediaSessionService.MediaSession.PLAYBACK_STATE_REWINDING: "REWINDING", MediaSessionService.MediaSession.PLAYBACK_STATE_BUFFERING: "BUFFERING", MediaSessionService.MediaSession.PLAYBACK_STATE_ERROR: "ERROR", } return names.get(state, f"UNKNOWN({state})") @staticmethod def isActiveSession(session: MediaSessionService.MediaSession) -> bool: return session is not None and session.isActive() @staticmethod def getSupportedActions(session: MediaSessionService.MediaSession) -> int: return session.getActions() if session else 0 # ═══════════════════════════════════════════════════════════════ # MediaSessionServiceConfig # ═══════════════════════════════════════════════════════════════ class MediaSessionServiceConfig(Stub): """MediaSessionServiceConfig — 媒体会话服务配置""" def __init__(self): super().__init__() self._max_sessions: int = 10 self._allow_multiple_sessions: bool = True self._notification_enabled: bool = True self._auto_play_on_start: bool = False self._enable_media_buttons: bool = True self._enable_queue: bool = True def getMaxSessions(self) -> int: return self._max_sessions def setMaxSessions(self, max_sessions: int): self._max_sessions = max_sessions def isAllowMultipleSessions(self) -> bool: return self._allow_multiple_sessions def setAllowMultipleSessions(self, allow: bool): self._allow_multiple_sessions = allow def isNotificationEnabled(self) -> bool: return self._notification_enabled def setNotificationEnabled(self, enabled: bool): self._notification_enabled = enabled def isAutoPlayOnStart(self) -> bool: return self._auto_play_on_start def setAutoPlayOnStart(self, auto_play: bool): self._auto_play_on_start = auto_play def isMediaButtonsEnabled(self) -> bool: return self._enable_media_buttons def setMediaButtonsEnabled(self, enabled: bool): self._enable_media_buttons = enabled def isQueueEnabled(self) -> bool: return self._enable_queue def setQueueEnabled(self, enabled: bool): self._enable_queue = enabled def toString(self) -> str: return f"MediaSessionServiceConfig(maxSessions={self._max_sessions})" # ═══════════════════════════════════════════════════════════════ # MediaSessionServiceHelper # ═══════════════════════════════════════════════════════════════ class MediaSessionServiceHelper(Stub): """MediaSessionServiceHelper — 媒体会话服务辅助类""" @staticmethod def requestMediaSessionService(context: Any, service: MediaSessionService): """请求媒体会话服务""" service.onCreate() @staticmethod def releaseMediaSessionService(service: MediaSessionService): """释放媒体会话服务""" service.onDestroy() @staticmethod def sendMediaButton(context: Any, session: MediaSessionService.MediaSession, key_code: int): """发送媒体按键""" cb = session.getCallback() if cb: cb.onMediaButtonEvent(None) @staticmethod def createDefaultSession(tag: str = "default") -> MediaSessionService.MediaSession: session = MediaSessionService.MediaSession(tag) session.setCallback(MediaSessionService.MediaSession.Callback()) return session @staticmethod def playbackStateToString(state: int) -> str: return MediaSessionUtils.getPlaybackStateName(state)