""" Android media.session 组件 — MediaSession, MediaController, PlaybackState, MediaMetadata 等 方法签名完全对齐 Android SDK """ import time from typing import Any, Callable, Dict, List, Optional, Set, Union from .._core import Stub, AndroidOnlyError # ═══════════════════════════════════════════════════════════════ # MediaSession # ═══════════════════════════════════════════════════════════════ class MediaSession(Stub): """MediaSession — 媒体会话""" FLAG_HANDLES_MEDIA_BUTTONS = 1 FLAG_HANDLES_TRANSPORT_CONTROLS = 2 FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 4 # ═══════════════════════════════════════════════════════════ # Callback # ═══════════════════════════════════════════════════════════ class Callback(Stub): """MediaSession.Callback — 会话回调""" def onPlay(self): pass def onPause(self): pass def onSkipToNext(self): pass def onSkipToPrevious(self): pass def onFastForward(self): pass def onRewind(self): pass def onStop(self): pass def onSeekTo(self, pos: int): pass def onSetRating(self, rating: "Rating"): pass def onSetRepeatMode(self, repeatMode: int): pass def onSetShuffleMode(self, shuffleMode: int): pass def onCommand(self, command: str, extras: Any, cb: Any): pass def onCustomAction(self, action: str, extras: Any): pass def onMediaButtonEvent(self, mediaButtonIntent: Any) -> bool: return False def onPlayFromMediaId(self, mediaId: 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, mediaId: str, extras: Any): pass def onPrepareFromSearch(self, query: str, extras: Any): pass def onPrepareFromUri(self, uri: str, extras: Any): pass def __init__(self, context: Any = None, tag: str = "", token: "SessionToken" = None): super().__init__() self._tag = tag self._token = token or SessionToken() self._active = False self._flags = 0 self._session_activity: Any = None self._callback: Optional[MediaSession.Callback] = None self._metadata: Optional["MediaMetadata"] = None self._playback_state: Optional["PlaybackState"] = None self._queue: List["QueueItem"] = [] self._queue_title: Optional[str] = None self._extras: Any = None self._rating_type = Rating.RATING_NONE self._repeat_mode = PlaybackState.REPEAT_MODE_NONE self._shuffle_mode = PlaybackState.SHUFFLE_MODE_NONE self._captioning_enabled = False self._controllers: List[MediaController] = [] def setSessionActivity(self, sessionActivity: Any): self._session_activity = sessionActivity def sendSessionEvent(self, event: str, extras: Any): for ctrl in self._controllers: ctrl._callback.onSessionEvent(event, extras) def release(self): self._active = False self._controllers.clear() def getSessionToken(self) -> "SessionToken": return self._token def setFlags(self, flags: int): self._flags = flags def setPlaybackToLocal(self, volumeProvider: Any = None): pass def setPlaybackToRemote(self, volumeProvider: Any): pass def setActive(self, active: bool): self._active = active def isActive(self) -> bool: return self._active def setMediaMetadata(self, metadata: "MediaMetadata"): self._metadata = metadata for ctrl in self._controllers: ctrl._callback.onMetadataChanged(metadata) def setPlaybackState(self, state: "PlaybackState"): self._playback_state = state for ctrl in self._controllers: ctrl._callback.onPlaybackStateChanged(state) def setQueue(self, queue: List["QueueItem"]): self._queue = queue for ctrl in self._controllers: ctrl._callback.onQueueChanged(queue) def setQueueTitle(self, title: str): self._queue_title = title for ctrl in self._controllers: ctrl._callback.onQueueTitleChanged(title) def setExtras(self, extras: Any): self._extras = extras for ctrl in self._controllers: ctrl._callback.onExtrasChanged(extras) def setMetadata(self, metadata: "MediaMetadata"): self.setMediaMetadata(metadata) def setRatingType(self, ratingType: int): self._rating_type = ratingType def setRepeatMode(self, repeatMode: int): self._repeat_mode = repeatMode for ctrl in self._controllers: ctrl._callback.onRepeatModeChanged(repeatMode) def setShuffleMode(self, shuffleMode: int): self._shuffle_mode = shuffleMode for ctrl in self._controllers: ctrl._callback.onShuffleModeChanged(shuffleMode) def setCaptioningEnabled(self, enabled: bool): self._captioning_enabled = enabled for ctrl in self._controllers: ctrl._callback.onCaptioningEnabledChanged(enabled) def getController(self) -> "MediaController": ctrl = MediaController(self) self._controllers.append(ctrl) return ctrl def getCallerPackageName(self) -> str: return "com.android.systemui" def setCallback(self, callback: Callback, handler: Any = None): self._callback = callback # ═══════════════════════════════════════════════════════════════ # MediaController # ═══════════════════════════════════════════════════════════════ class MediaController(Stub): """MediaController — 媒体控制器""" # ═══════════════════════════════════════════════════════════ # Callback # ═══════════════════════════════════════════════════════════ class Callback(Stub): """MediaController.Callback — 控制器回调""" def onSessionReady(self): pass def onSessionDestroyed(self): pass def onSessionEvent(self, event: str, extras: Any): pass def onPlaybackStateChanged(self, state: "PlaybackState"): pass def onMetadataChanged(self, metadata: "MediaMetadata"): pass def onQueueChanged(self, queue: List["QueueItem"]): pass def onQueueTitleChanged(self, title: str): pass def onExtrasChanged(self, extras: Any): pass def onAudioInfoChanged(self, info: Any): pass def onCaptioningEnabledChanged(self, enabled: bool): pass def onRepeatModeChanged(self, repeatMode: int): pass def onShuffleModeChanged(self, shuffleMode: int): pass # ═══════════════════════════════════════════════════════════ # TransportControls # ═══════════════════════════════════════════════════════════ class TransportControls(Stub): """MediaController.TransportControls — 传输控制""" def __init__(self, session: "MediaSession"): super().__init__() self._session = session def play(self): if self._session._callback: self._session._callback.onPlay() def pause(self): if self._session._callback: self._session._callback.onPause() def stop(self): if self._session._callback: self._session._callback.onStop() def fastForward(self): if self._session._callback: self._session._callback.onFastForward() def rewind(self): if self._session._callback: self._session._callback.onRewind() def skipToNext(self): if self._session._callback: self._session._callback.onSkipToNext() def skipToPrevious(self): if self._session._callback: self._session._callback.onSkipToPrevious() def seekTo(self, pos: int): if self._session._callback: self._session._callback.onSeekTo(pos) def setRating(self, rating: "Rating"): if self._session._callback: self._session._callback.onSetRating(rating) def setRepeatMode(self, repeatMode: int): if self._session._callback: self._session._callback.onSetRepeatMode(repeatMode) def setShuffleMode(self, shuffleMode: int): if self._session._callback: self._session._callback.onSetShuffleMode(shuffleMode) def sendCustomAction(self, action: str, extras: Any): if self._session._callback: self._session._callback.onCustomAction(action, extras) def playFromMediaId(self, mediaId: str, extras: Any): if self._session._callback: self._session._callback.onPlayFromMediaId(mediaId, extras) def playFromSearch(self, query: str, extras: Any): if self._session._callback: self._session._callback.onPlayFromSearch(query, extras) def playFromUri(self, uri: str, extras: Any): if self._session._callback: self._session._callback.onPlayFromUri(uri, extras) def prepare(self): if self._session._callback: self._session._callback.onPrepare() def prepareFromMediaId(self, mediaId: str, extras: Any): if self._session._callback: self._session._callback.onPrepareFromMediaId(mediaId, extras) def prepareFromSearch(self, query: str, extras: Any): if self._session._callback: self._session._callback.onPrepareFromSearch(query, extras) def prepareFromUri(self, uri: str, extras: Any): if self._session._callback: self._session._callback.onPrepareFromUri(uri, extras) def __init__(self, session: "MediaSession" = None): super().__init__() self._session = session self._callback = MediaController.Callback() self._transport_controls = MediaController.TransportControls(session) def getSessionToken(self) -> "SessionToken": return self._session.getSessionToken() if self._session else SessionToken() def getTransportControls(self) -> TransportControls: return self._transport_controls def getPlaybackState(self) -> Optional["PlaybackState"]: return self._session._playback_state if self._session else None def getMetadata(self) -> Optional["MediaMetadata"]: return self._session._metadata if self._session else None def getQueueTitle(self) -> Optional[str]: return self._session._queue_title if self._session else None def getQueue(self) -> List["QueueItem"]: return self._session._queue if self._session else [] def getRepeatMode(self) -> int: return self._session._repeat_mode if self._session else PlaybackState.REPEAT_MODE_NONE def getShuffleMode(self) -> int: return self._session._shuffle_mode if self._session else PlaybackState.SHUFFLE_MODE_NONE def getRatingType(self) -> int: return self._session._rating_type if self._session else Rating.RATING_NONE def getExtras(self) -> Any: return self._session._extras if self._session else None def getFlags(self) -> int: return self._session._flags if self._session else 0 def getPackageName(self) -> str: return self._session.getCallerPackageName() if self._session else "" def getSessionActivity(self) -> Any: return self._session._session_activity if self._session else None def registerCallback(self, callback: Callback, handler: Any = None): self._callback = callback def unregisterCallback(self, callback: Callback): self._callback = MediaController.Callback() def sendCommand(self, command: str, extras: Any, cb: Any): if self._session and self._session._callback: self._session._callback.onCommand(command, extras, cb) def adjustVolume(self, direction: int, flags: int): pass def setVolumeTo(self, value: int, flags: int): pass def isSessionReady(self) -> bool: return True # ═══════════════════════════════════════════════════════════════ # PlaybackState # ═══════════════════════════════════════════════════════════════ class PlaybackState(Stub): """PlaybackState — 播放状态""" STATE_NONE = 0 STATE_STOPPED = 1 STATE_PAUSED = 2 STATE_PLAYING = 3 STATE_FAST_FORWARDING = 4 STATE_REWINDING = 5 STATE_BUFFERING = 6 STATE_ERROR = 7 STATE_CONNECTING = 8 STATE_SKIPPING_TO_PREVIOUS = 9 STATE_SKIPPING_TO_NEXT = 10 STATE_SKIPPING_TO_QUEUE_ITEM = 11 ACTION_PLAY = 1 << 0 ACTION_PAUSE = 1 << 1 ACTION_PLAY_PAUSE = 1 << 2 ACTION_STOP = 1 << 3 ACTION_SEEK_TO = 1 << 4 ACTION_FAST_FORWARD = 1 << 5 ACTION_REWIND = 1 << 6 ACTION_SKIP_TO_NEXT = 1 << 7 ACTION_SKIP_TO_PREVIOUS = 1 << 8 ACTION_SET_RATING = 1 << 9 ACTION_SET_REPEAT_MODE = 1 << 10 ACTION_SET_SHUFFLE_MODE = 1 << 11 ACTION_SET_CAPTIONING_ENABLED = 1 << 12 ACTION_SKIP_TO_QUEUE_ITEM = 1 << 13 ACTION_PLAY_FROM_MEDIA_ID = 1 << 14 ACTION_PLAY_FROM_SEARCH = 1 << 15 ACTION_PREPARE = 1 << 16 ACTION_PREPARE_FROM_MEDIA_ID = 1 << 17 ACTION_PREPARE_FROM_SEARCH = 1 << 18 ACTION_PREPARE_FROM_URI = 1 << 19 ACTION_PLAY_FROM_URI = 1 << 20 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 class Builder(Stub): """PlaybackState.Builder — 播放状态构建器""" def __init__(self): super().__init__() self._state = PlaybackState.STATE_NONE self._position = 0 self._speed = 1.0 self._actions = 0 self._custom_actions: List[Any] = [] self._active_queue_item_id = -1 self._error_message: Optional[str] = None self._extras: Any = None self._buffered_position = 0 def setState(self, state: int, position: int, speed: float) -> "PlaybackState.Builder": self._state = state self._position = position self._speed = speed return self def setActions(self, actions: int) -> "PlaybackState.Builder": self._actions = actions return self def setCustomAction(self, action: str, name: str, icon: Any = None) -> "PlaybackState.Builder": self._custom_actions.append((action, name, icon)) return self def setActiveQueueItemId(self, id: int) -> "PlaybackState.Builder": self._active_queue_item_id = id return self def setErrorMessage(self, error: str) -> "PlaybackState.Builder": self._error_message = error return self def setExtras(self, extras: Any) -> "PlaybackState.Builder": self._extras = extras return self def setBufferedPosition(self, bufferedPosition: int) -> "PlaybackState.Builder": self._buffered_position = bufferedPosition return self def setPosition(self, position: int) -> "PlaybackState.Builder": self._position = position return self def setSpeed(self, speed: float) -> "PlaybackState.Builder": self._speed = speed return self def addCustomAction(self, action: "PlaybackState.CustomAction") -> "PlaybackState.Builder": self._custom_actions.append(action) return self def build(self) -> "PlaybackState": ps = PlaybackState() ps._state = self._state ps._position = self._position ps._speed = self._speed ps._actions = self._actions ps._custom_actions = self._custom_actions ps._active_queue_item_id = self._active_queue_item_id ps._error_message = self._error_message ps._extras = self._extras ps._buffered_position = self._buffered_position return ps class CustomAction(Stub): """PlaybackState.CustomAction — 自定义操作""" def __init__(self, action: str, name: str, icon: Any = None): super().__init__() self._action = action self._name = name self._icon = icon def getAction(self) -> str: return self._action def getName(self) -> str: return self._name def getIcon(self) -> Any: return self._icon def __init__(self): super().__init__() self._state = PlaybackState.STATE_NONE self._position = 0 self._speed = 1.0 self._actions = 0 self._custom_actions: List[Any] = [] self._active_queue_item_id = -1 self._error_message: Optional[str] = None self._extras: Any = None self._buffered_position = 0 self._last_position_update_time = int(time.time() * 1000) def getState(self) -> int: return self._state def getPosition(self) -> int: return self._position def getPlaybackSpeed(self) -> float: return self._speed def getActions(self) -> int: return self._actions def getCustomActions(self) -> List[Any]: return self._custom_actions def getActiveQueueItemId(self) -> int: return self._active_queue_item_id def getErrorMessage(self) -> Optional[str]: return self._error_message def getExtras(self) -> Any: return self._extras def getBufferedPosition(self) -> int: return self._buffered_position def getLastPositionUpdateTime(self) -> int: return self._last_position_update_time def toString(self) -> str: state_names = { PlaybackState.STATE_NONE: "NONE", PlaybackState.STATE_STOPPED: "STOPPED", PlaybackState.STATE_PAUSED: "PAUSED", PlaybackState.STATE_PLAYING: "PLAYING", } return f"PlaybackState(state={state_names.get(self._state, str(self._state))}," \ f" pos={self._position})" # ═══════════════════════════════════════════════════════════════ # MediaMetadata # ═══════════════════════════════════════════════════════════════ class MediaMetadata(Stub): """MediaMetadata — 媒体元数据""" METADATA_KEY_ALBUM = "android.media.metadata.ALBUM" METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART" METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST" METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI" METADATA_KEY_ART = "android.media.metadata.ART" METADATA_KEY_ARTIST = "android.media.metadata.ARTIST" METADATA_KEY_ART_URI = "android.media.metadata.ART_URI" METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR" METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION" METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER" METADATA_KEY_DATE = "android.media.metadata.DATE" METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER" METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION" METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON" METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI" METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE" METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE" METADATA_KEY_DURATION = "android.media.metadata.DURATION" METADATA_KEY_GENRE = "android.media.metadata.GENRE" METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID" METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS" METADATA_KEY_RATING = "android.media.metadata.RATING" METADATA_KEY_TITLE = "android.media.metadata.TITLE" METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER" METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING" METADATA_KEY_WRITER = "android.media.metadata.WRITER" METADATA_KEY_YEAR = "android.media.metadata.YEAR" class Builder(Stub): """MediaMetadata.Builder — 元数据构建器""" def __init__(self): super().__init__() self._data: Dict[str, Any] = {} def putString(self, key: str, value: str) -> "MediaMetadata.Builder": self._data[key] = value return self def putLong(self, key: str, value: int) -> "MediaMetadata.Builder": self._data[key] = value return self def putBitmap(self, key: str, value: Any) -> "MediaMetadata.Builder": self._data[key] = value return self def putText(self, key: str, value: str) -> "MediaMetadata.Builder": self._data[key] = value return self def putRating(self, key: str, value: "Rating") -> "MediaMetadata.Builder": self._data[key] = value return self def build(self) -> "MediaMetadata": mm = MediaMetadata() mm._data = dict(self._data) return mm def __init__(self): super().__init__() self._data: Dict[str, Any] = {} def getString(self, key: str) -> Optional[str]: v = self._data.get(key) return str(v) if v is not None else None def getLong(self, key: str) -> int: v = self._data.get(key, 0) try: return int(v) except (ValueError, TypeError): return 0 def getBitmap(self, key: str) -> Any: return self._data.get(key) def getText(self, key: str) -> Optional[str]: return self.getString(key) def getDescription(self) -> "MediaDescription": from .media_browse import MediaDescription builder = MediaDescription.Builder() title = self.getString(MediaMetadata.METADATA_KEY_TITLE) if title: builder.setTitle(title) artist = self.getString(MediaMetadata.METADATA_KEY_ARTIST) if artist: builder.setSubtitle(artist) media_id = self.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) if media_id: builder.setMediaId(media_id) return builder.build() def getRating(self, key: str) -> Optional["Rating"]: v = self._data.get(key) return v if isinstance(v, Rating) else None def getBitmapDescription(self) -> Any: return None def containsKey(self, key: str) -> bool: return key in self._data def keySet(self) -> Set[str]: return set(self._data.keys()) def getMediaId(self) -> Optional[str]: return self.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) def toString(self) -> str: title = self.getString(MediaMetadata.METADATA_KEY_TITLE) return f"MediaMetadata(title={title})" # ═══════════════════════════════════════════════════════════════ # QueueItem # ═══════════════════════════════════════════════════════════════ class QueueItem(Stub): """QueueItem — 队列项""" def __init__(self, description: "MediaDescription" = None, queueId: int = 0): super().__init__() self._description = description self._queue_id = queueId def getDescription(self) -> "MediaDescription": return self._description def getQueueId(self) -> int: return self._queue_id def toString(self) -> str: return f"QueueItem(id={self._queue_id}, desc={self._description})" # ═══════════════════════════════════════════════════════════════ # Rating # ═══════════════════════════════════════════════════════════════ class Rating(Stub): """Rating — 评分""" RATING_NONE = 0 RATING_HEART = 1 RATING_THUMB_UP_DOWN = 2 RATING_3_STARS = 3 RATING_4_STARS = 4 RATING_5_STARS = 5 RATING_PERCENTAGE = 6 def __init__(self, ratingStyle: int = RATING_NONE): super().__init__() self._rating_style = ratingStyle self._rated = False self._heart = False self._thumb_up = False self._star_rating = 0.0 self._percent_rating = 0.0 @staticmethod def newHeartRating(hasHeart: bool) -> "Rating": r = Rating(Rating.RATING_HEART) r._rated = True r._heart = hasHeart return r @staticmethod def newThumbRating(thumbUp: bool) -> "Rating": r = Rating(Rating.RATING_THUMB_UP_DOWN) r._rated = True r._thumb_up = thumbUp return r @staticmethod def newStarRating(starRatingStyle: int, starRating: float) -> "Rating": r = Rating(starRatingStyle) r._rated = True r._star_rating = starRating return r @staticmethod def newPercentageRating(percent: float) -> "Rating": r = Rating(Rating.RATING_PERCENTAGE) r._rated = True r._percent_rating = percent return r @staticmethod def newUnratedRating(ratingStyle: int) -> "Rating": return Rating(ratingStyle) def isRated(self) -> bool: return self._rated def getRatingStyle(self) -> int: return self._rating_style def hasHeart(self) -> bool: return self._heart def isThumbUp(self) -> bool: return self._thumb_up def getStarRating(self) -> float: return self._star_rating def getPercentRating(self) -> float: return self._percent_rating def toString(self) -> str: return f"Rating(style={self._rating_style}, rated={self._rated})" # ═══════════════════════════════════════════════════════════════ # SessionToken # ═══════════════════════════════════════════════════════════════ class SessionToken(Stub): """SessionToken — 会话令牌""" TYPE_SESSION = 0 def __init__(self, type_: int = TYPE_SESSION): super().__init__() self._type = type_ self._binder = None def getType(self) -> int: return self._type def getSessionBinder(self) -> Any: return self._binder def toString(self) -> str: return f"SessionToken(type={self._type})" # ═══════════════════════════════════════════════════════════════ # __all__ # ═══════════════════════════════════════════════════════════════ __all__ = [ "MediaSession", "MediaController", "PlaybackState", "MediaMetadata", "QueueItem", "Rating", "SessionToken", ]