新增模块覆盖: 系统层 (14): system, security, print, opengl, renderscript, transition, test, service_notification, service_controls, service_quicksettings, service_wallpaper, service_autofill, service_dreams, service_vr 应用层 (13): app_admin, app_backup, app_job, app_usage, app_role, app_people, appwidget, companion, credentials, mtp, sip, telecom, speech 硬件媒体层 (15): hardware_display, hardware_fingerprint, hardware_usb, hardware_input, media_audiofx, media_browse, media_session, media_tv, media_midi, media_projection, uwb, se, view_autofill, view_textclassifier, view_translation 总计 94 个模块,75,342 行代码
310 lines
12 KiB
Python
310 lines
12 KiB
Python
"""
|
|
Android media.browse 组件 — MediaBrowser, MediaItem, MediaDescription 等
|
|
方法签名完全对齐 Android SDK
|
|
"""
|
|
|
|
from typing import Any, Callable, Dict, List, Optional, Set
|
|
|
|
from .._core import Stub, AndroidOnlyError
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# MediaBrowser
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class MediaBrowser(Stub):
|
|
"""MediaBrowser — 媒体浏览器"""
|
|
|
|
EXTRA_PAGE = "android.media.browse.extra.PAGE"
|
|
EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE"
|
|
|
|
# ═══════════════════════════════════════════════════════════
|
|
# ConnectionCallback
|
|
# ═══════════════════════════════════════════════════════════
|
|
|
|
class ConnectionCallback(Stub):
|
|
"""MediaBrowser.ConnectionCallback — 连接回调"""
|
|
|
|
def onConnected(self):
|
|
pass
|
|
|
|
def onConnectionSuspended(self):
|
|
pass
|
|
|
|
def onConnectionFailed(self):
|
|
pass
|
|
|
|
# ═══════════════════════════════════════════════════════════
|
|
# SubscriptionCallback
|
|
# ═══════════════════════════════════════════════════════════
|
|
|
|
class SubscriptionCallback(Stub):
|
|
"""MediaBrowser.SubscriptionCallback — 订阅回调"""
|
|
|
|
def onChildrenLoaded(self, parentId: str, children: List["MediaBrowser.MediaItem"]):
|
|
pass
|
|
|
|
def onError(self, parentId: str):
|
|
pass
|
|
|
|
def onChildrenLoadedWithOptions(self, parentId: str,
|
|
children: List["MediaBrowser.MediaItem"],
|
|
options: Any):
|
|
pass
|
|
|
|
# ═══════════════════════════════════════════════════════════
|
|
# ItemCallback
|
|
# ═══════════════════════════════════════════════════════════
|
|
|
|
class ItemCallback(Stub):
|
|
"""MediaBrowser.ItemCallback — 媒体项回调"""
|
|
|
|
def onItemLoaded(self, item: "MediaBrowser.MediaItem"):
|
|
pass
|
|
|
|
def onError(self, mediaId: str):
|
|
pass
|
|
|
|
# ═══════════════════════════════════════════════════════════
|
|
# MediaItem
|
|
# ═══════════════════════════════════════════════════════════
|
|
|
|
class MediaItem(Stub):
|
|
"""MediaBrowser.MediaItem — 媒体项"""
|
|
|
|
FLAG_BROWSABLE = 1
|
|
FLAG_PLAYABLE = 2
|
|
|
|
def __init__(self, description: "MediaDescription" = None, flags: int = FLAG_PLAYABLE):
|
|
super().__init__()
|
|
self._description = description or MediaDescription()
|
|
self._flags = flags
|
|
|
|
def getMediaId(self) -> Optional[str]:
|
|
return self._description.getMediaId()
|
|
|
|
def getDescription(self) -> "MediaDescription":
|
|
return self._description
|
|
|
|
def getFlags(self) -> int:
|
|
return self._flags
|
|
|
|
def isBrowsable(self) -> bool:
|
|
return bool(self._flags & MediaBrowser.MediaItem.FLAG_BROWSABLE)
|
|
|
|
def isPlayable(self) -> bool:
|
|
return bool(self._flags & MediaBrowser.MediaItem.FLAG_PLAYABLE)
|
|
|
|
def toString(self) -> str:
|
|
return f"MediaItem(mediaId={self.getMediaId()}, flags={self._flags})"
|
|
|
|
def __init__(self, context: Any = None, serviceComponent: Any = None,
|
|
connectionCallback: Optional[ConnectionCallback] = None,
|
|
rootHints: Any = None):
|
|
super().__init__()
|
|
self._connected = False
|
|
self._service_component = serviceComponent
|
|
self._root: str = ""
|
|
self._session_token: Any = None
|
|
self._connection_callback = connectionCallback
|
|
self._subscriptions: Dict[str, List[MediaBrowser.SubscriptionCallback]] = {}
|
|
self._media_tree: Dict[str, List[MediaBrowser.MediaItem]] = {}
|
|
self._item_cache: Dict[str, MediaBrowser.MediaItem] = {}
|
|
|
|
def connect(self):
|
|
self._connected = True
|
|
self._root = "__ROOT__"
|
|
# Build a simple in-memory media tree
|
|
self._media_tree["__ROOT__"] = [
|
|
MediaBrowser.MediaItem(
|
|
MediaDescription.Builder()
|
|
.setMediaId("__RECENT__")
|
|
.setTitle("Recently Played")
|
|
.setSubtitle("Your recently played media")
|
|
.build(),
|
|
flags=MediaBrowser.MediaItem.FLAG_BROWSABLE
|
|
),
|
|
MediaBrowser.MediaItem(
|
|
MediaDescription.Builder()
|
|
.setMediaId("__ALL__")
|
|
.setTitle("All Music")
|
|
.setSubtitle("All your music library")
|
|
.build(),
|
|
flags=MediaBrowser.MediaItem.FLAG_BROWSABLE
|
|
),
|
|
]
|
|
self._media_tree["__RECENT__"] = [
|
|
MediaBrowser.MediaItem(
|
|
MediaDescription.Builder()
|
|
.setMediaId("song_1")
|
|
.setTitle("Sample Song 1")
|
|
.setSubtitle("Artist 1")
|
|
.build(),
|
|
flags=MediaBrowser.MediaItem.FLAG_PLAYABLE
|
|
),
|
|
]
|
|
self._item_cache["song_1"] = self._media_tree["__RECENT__"][0]
|
|
|
|
if self._connection_callback:
|
|
self._connection_callback.onConnected()
|
|
|
|
def disconnect(self):
|
|
self._connected = False
|
|
self._subscriptions.clear()
|
|
if self._connection_callback:
|
|
self._connection_callback.onConnectionSuspended()
|
|
|
|
def getConnected(self) -> bool:
|
|
return self._connected
|
|
|
|
def getServiceComponent(self) -> Any:
|
|
return self._service_component
|
|
|
|
def getRoot(self) -> str:
|
|
return self._root
|
|
|
|
def isConnected(self) -> bool:
|
|
return self._connected
|
|
|
|
def getSessionToken(self) -> Any:
|
|
return self._session_token
|
|
|
|
def getItem(self, mediaId: str) -> Optional[MediaItem]:
|
|
return self._item_cache.get(mediaId)
|
|
|
|
def subscribe(self, parentId: str, callback: SubscriptionCallback):
|
|
if parentId not in self._subscriptions:
|
|
self._subscriptions[parentId] = []
|
|
self._subscriptions[parentId].append(callback)
|
|
|
|
children = self._media_tree.get(parentId, [])
|
|
if children:
|
|
callback.onChildrenLoaded(parentId, children)
|
|
else:
|
|
callback.onError(parentId)
|
|
|
|
def unsubscribe(self, parentId: str, callback: Optional[SubscriptionCallback] = None):
|
|
if parentId in self._subscriptions:
|
|
if callback:
|
|
self._subscriptions[parentId] = [
|
|
cb for cb in self._subscriptions[parentId]
|
|
if cb is not callback
|
|
]
|
|
if not self._subscriptions[parentId]:
|
|
del self._subscriptions[parentId]
|
|
else:
|
|
del self._subscriptions[parentId]
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# MediaDescription
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class MediaDescription(Stub):
|
|
"""MediaDescription — 媒体描述"""
|
|
|
|
class Builder(Stub):
|
|
"""MediaDescription.Builder — 媒体描述构建器"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._media_id: Optional[str] = None
|
|
self._title: Optional[str] = None
|
|
self._subtitle: Optional[str] = None
|
|
self._description: Optional[str] = None
|
|
self._icon_bitmap: Any = None
|
|
self._icon_uri: Optional[str] = None
|
|
self._extras: Any = None
|
|
self._media_uri: Optional[str] = None
|
|
|
|
def setMediaId(self, mediaId: str) -> "MediaDescription.Builder":
|
|
self._media_id = mediaId
|
|
return self
|
|
|
|
def setTitle(self, title: str) -> "MediaDescription.Builder":
|
|
self._title = title
|
|
return self
|
|
|
|
def setSubtitle(self, subtitle: str) -> "MediaDescription.Builder":
|
|
self._subtitle = subtitle
|
|
return self
|
|
|
|
def setDescription(self, description: str) -> "MediaDescription.Builder":
|
|
self._description = description
|
|
return self
|
|
|
|
def setIconBitmap(self, icon: Any) -> "MediaDescription.Builder":
|
|
self._icon_bitmap = icon
|
|
return self
|
|
|
|
def setIconUri(self, iconUri: str) -> "MediaDescription.Builder":
|
|
self._icon_uri = iconUri
|
|
return self
|
|
|
|
def setExtras(self, extras: Any) -> "MediaDescription.Builder":
|
|
self._extras = extras
|
|
return self
|
|
|
|
def setMediaUri(self, mediaUri: str) -> "MediaDescription.Builder":
|
|
self._media_uri = mediaUri
|
|
return self
|
|
|
|
def build(self) -> "MediaDescription":
|
|
md = MediaDescription()
|
|
md._media_id = self._media_id
|
|
md._title = self._title
|
|
md._subtitle = self._subtitle
|
|
md._description = self._description
|
|
md._icon_bitmap = self._icon_bitmap
|
|
md._icon_uri = self._icon_uri
|
|
md._extras = self._extras
|
|
md._media_uri = self._media_uri
|
|
return md
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._media_id: Optional[str] = None
|
|
self._title: Optional[str] = None
|
|
self._subtitle: Optional[str] = None
|
|
self._description: Optional[str] = None
|
|
self._icon_bitmap: Any = None
|
|
self._icon_uri: Optional[str] = None
|
|
self._extras: Any = None
|
|
self._media_uri: Optional[str] = None
|
|
|
|
def getMediaId(self) -> Optional[str]:
|
|
return self._media_id
|
|
|
|
def getTitle(self) -> Optional[str]:
|
|
return self._title
|
|
|
|
def getSubtitle(self) -> Optional[str]:
|
|
return self._subtitle
|
|
|
|
def getDescription(self) -> Optional[str]:
|
|
return self._description
|
|
|
|
def getIconBitmap(self) -> Any:
|
|
return self._icon_bitmap
|
|
|
|
def getIconUri(self) -> Optional[str]:
|
|
return self._icon_uri
|
|
|
|
def getExtras(self) -> Any:
|
|
return self._extras
|
|
|
|
def getMediaUri(self) -> Optional[str]:
|
|
return self._media_uri
|
|
|
|
def toString(self) -> str:
|
|
return f"MediaDescription(id={self._media_id}, title={self._title})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# __all__
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
__all__ = [
|
|
"MediaBrowser",
|
|
"MediaDescription",
|
|
] |