""" Android media.tv 扩展组件 — TvContentRating, TvTrackInfo, TvView, TvStreamConfig, TvContract, PreviewProgram, PreviewChannel 等 方法签名完全对齐 Android SDK """ from __future__ import annotations from typing import Any, Dict, List, Optional, Tuple from .._core import Stub, AndroidOnlyError from ..java.lang import RuntimeException IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) __all__ = [ "TvInputManager", "TvContentRating", "TvTrackInfo", "TvInputInfo", "TvInputService", "TvRecordingClient", "TvView", "TvStreamConfig", "TvContract", "PreviewProgram", "PreviewChannel", ] # ═══════════════════════════════════════════════════════════════ # TvContentRating # ═══════════════════════════════════════════════════════════════ class TvContentRating(Stub): """TvContentRating — 电视内容分级""" RATING_TV_G = "US_TV:US_TV_G" RATING_TV_PG = "US_TV:US_TV_PG" RATING_TV_14 = "US_TV:US_TV_14" RATING_TV_MA = "US_TV:US_TV_MA" RATING_TV_Y = "US_TV:US_TV_Y" RATING_TV_Y7 = "US_TV:US_TV_Y7" RATING_TV_Y7_FV = "US_TV:US_TV_Y7_FV" RATING_MOVIE_G = "US_MOVIE:US_MOVIE_G" RATING_MOVIE_PG = "US_MOVIE:US_MOVIE_PG" RATING_MOVIE_PG13 = "US_MOVIE:US_MOVIE_PG13" RATING_MOVIE_R = "US_MOVIE:US_MOVIE_R" RATING_MOVIE_NC17 = "US_MOVIE:US_MOVIE_NC17" def __init__(self, ratingString: str = ""): super().__init__() self._rating_string = ratingString parts = ratingString.split(":") if ratingString else [] self._domain = parts[0] if len(parts) > 0 else "" self._rating_system = parts[1] if len(parts) > 1 else "" self._rating_value = parts[2] if len(parts) > 2 else "" self._sub_ratings: List[str] = parts[3:] if len(parts) > 3 else [] @staticmethod def create(ratingSystemDomain: str, ratingSystem: str, ratedRating: str, subRatings: List[str] = None) -> "TvContentRating": parts = [ratingSystemDomain, ratingSystem, ratedRating] if subRatings: parts.extend(subRatings) return TvContentRating(":".join(parts)) @staticmethod def createRatingSystemString(ratingSystemDomain: str, ratingSystem: str) -> str: return f"{ratingSystemDomain}:{ratingSystem}" @staticmethod def unflattenFromString(ratingString: str) -> "TvContentRating": return TvContentRating(ratingString) def flattenToString(self) -> str: return self._rating_string def getDomain(self) -> str: return self._domain def getRatingSystem(self) -> str: return self._rating_system def getMainRating(self) -> str: return self._rating_value def getSubRatings(self) -> List[str]: return list(self._sub_ratings) def getRatingSystemDomain(self) -> str: return self._domain def contains(self, other: "TvContentRating") -> bool: return self._rating_string == other._rating_string def toString(self) -> str: return self._rating_string def equals(self, other: Any) -> bool: if not isinstance(other, TvContentRating): return False return self._rating_string == other._rating_string def hashCode(self) -> int: return hash(self._rating_string) & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # TvTrackInfo # ═══════════════════════════════════════════════════════════════ class TvTrackInfo(Stub): """TvTrackInfo — 电视轨道信息""" TYPE_AUDIO = 0 TYPE_VIDEO = 1 TYPE_SUBTITLE = 2 def __init__(self, type_: int = TYPE_VIDEO, trackId: str = "", language: str = "", description: str = "", audioChannelCount: int = 0, audioSampleRate: int = 0, videoWidth: int = 0, videoHeight: int = 0, videoFrameRate: float = 0.0, videoPixelAspectRatioWidth: int = 0, videoPixelAspectRatioHeight: int = 0, videoActiveFormatDescription: int = 0, subtitleType: int = 0, subtitleRoleFlags: int = 0, encoding: str = ""): super().__init__() self._type = type_ self._track_id = trackId self._language = language self._description = description self._audio_channel_count = audioChannelCount self._audio_sample_rate = audioSampleRate self._video_width = videoWidth self._video_height = videoHeight self._video_frame_rate = videoFrameRate self._video_pixel_aspect_ratio_width = videoPixelAspectRatioWidth self._video_pixel_aspect_ratio_height = videoPixelAspectRatioHeight self._video_active_format_description = videoActiveFormatDescription self._subtitle_type = subtitleType self._subtitle_role_flags = subtitleRoleFlags self._encoding = encoding def getType(self) -> int: return self._type def getTrackId(self) -> str: return self._track_id def getLanguage(self) -> str: return self._language def getDescription(self) -> str: return self._description def getAudioChannelCount(self) -> int: return self._audio_channel_count if self._type == TvTrackInfo.TYPE_AUDIO else 0 def getAudioSampleRate(self) -> int: return self._audio_sample_rate if self._type == TvTrackInfo.TYPE_AUDIO else 0 def getVideoWidth(self) -> int: return self._video_width if self._type == TvTrackInfo.TYPE_VIDEO else 0 def getVideoHeight(self) -> int: return self._video_height if self._type == TvTrackInfo.TYPE_VIDEO else 0 def getVideoFrameRate(self) -> float: return self._video_frame_rate if self._type == TvTrackInfo.TYPE_VIDEO else 0.0 def getVideoPixelAspectRatioWidth(self) -> int: return self._video_pixel_aspect_ratio_width def getVideoPixelAspectRatioHeight(self) -> int: return self._video_pixel_aspect_ratio_height def getVideoActiveFormatDescription(self) -> int: return self._video_active_format_description def getSubtitleType(self) -> int: return self._subtitle_type if self._type == TvTrackInfo.TYPE_SUBTITLE else 0 def getSubtitleRoleFlags(self) -> int: return self._subtitle_role_flags def getEncoding(self) -> str: return self._encoding class Builder(Stub): """TvTrackInfo.Builder — 轨道信息构建器""" def __init__(self, type_: int): super().__init__() self._type = type_ self._track_id = "" self._language = "" self._description = "" self._audio_channel_count = 0 self._audio_sample_rate = 0 self._video_width = 0 self._video_height = 0 self._video_frame_rate = 0.0 self._video_pixel_aspect_ratio_width = 0 self._video_pixel_aspect_ratio_height = 0 self._video_active_format_description = 0 self._subtitle_type = 0 self._subtitle_role_flags = 0 self._encoding = "" def setTrackId(self, trackId: str) -> "TvTrackInfo.Builder": self._track_id = trackId return self def setLanguage(self, language: str) -> "TvTrackInfo.Builder": self._language = language return self def setDescription(self, description: str) -> "TvTrackInfo.Builder": self._description = description return self def setAudioChannelCount(self, audioChannelCount: int) -> "TvTrackInfo.Builder": self._audio_channel_count = audioChannelCount return self def setAudioSampleRate(self, audioSampleRate: int) -> "TvTrackInfo.Builder": self._audio_sample_rate = audioSampleRate return self def setVideoWidth(self, videoWidth: int) -> "TvTrackInfo.Builder": self._video_width = videoWidth return self def setVideoHeight(self, videoHeight: int) -> "TvTrackInfo.Builder": self._video_height = videoHeight return self def setVideoFrameRate(self, videoFrameRate: float) -> "TvTrackInfo.Builder": self._video_frame_rate = videoFrameRate return self def setVideoPixelAspectRatioWidth(self, aspectRatioWidth: int) -> "TvTrackInfo.Builder": self._video_pixel_aspect_ratio_width = aspectRatioWidth return self def setVideoPixelAspectRatioHeight(self, aspectRatioHeight: int) -> "TvTrackInfo.Builder": self._video_pixel_aspect_ratio_height = aspectRatioHeight return self def setVideoActiveFormatDescription(self, afd: int) -> "TvTrackInfo.Builder": self._video_active_format_description = afd return self def setSubtitleType(self, subtitleType: int) -> "TvTrackInfo.Builder": self._subtitle_type = subtitleType return self def setSubtitleRoleFlags(self, subtitleRoleFlags: int) -> "TvTrackInfo.Builder": self._subtitle_role_flags = subtitleRoleFlags return self def setEncoding(self, encoding: str) -> "TvTrackInfo.Builder": self._encoding = encoding return self def build(self) -> "TvTrackInfo": return TvTrackInfo( type_=self._type, trackId=self._track_id, language=self._language, description=self._description, audioChannelCount=self._audio_channel_count, audioSampleRate=self._audio_sample_rate, videoWidth=self._video_width, videoHeight=self._video_height, videoFrameRate=self._video_frame_rate, videoPixelAspectRatioWidth=self._video_pixel_aspect_ratio_width, videoPixelAspectRatioHeight=self._video_pixel_aspect_ratio_height, videoActiveFormatDescription=self._video_active_format_description, subtitleType=self._subtitle_type, subtitleRoleFlags=self._subtitle_role_flags, encoding=self._encoding, ) def toString(self) -> str: type_names = {0: "AUDIO", 1: "VIDEO", 2: "SUBTITLE"} return f"TvTrackInfo(id={self._track_id}, type={type_names.get(self._type, str(self._type))})" def equals(self, other: Any) -> bool: if not isinstance(other, TvTrackInfo): return False return self._track_id == other._track_id and self._type == other._type def hashCode(self) -> int: return hash((self._track_id, self._type)) & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # TvInputInfo (扩展) # ═══════════════════════════════════════════════════════════════ class TvInputInfo(Stub): """TvInputInfo — 电视输入信息 (完整版)""" TYPE_COMPONENT = 1005 TYPE_COMPOSITE = 1004 TYPE_HDMI = 1007 TYPE_OTHER = 1001 TYPE_SCART = 1003 TYPE_SVIDEO = 1006 TYPE_TUNER = 0 TYPE_DISPLAY_LINK = 1008 TYPE_VGA = 1002 def __init__(self, id: str = "", serviceInfo: Any = None, parentId: str = "", type_: int = TYPE_TUNER, hdmiDeviceInfo: Any = None, isHidden: bool = False, canRecord: bool = False, canPauseRecording: bool = False, passthroughMode: int = 0, tunerCount: int = 0): super().__init__() self._id = id self._service_info = serviceInfo self._parent_id = parentId self._type = type_ self._hdmi_device_info = hdmiDeviceInfo self._is_hidden = isHidden self._can_record = canRecord self._can_pause_recording = canPauseRecording self._passthrough_mode = passthroughMode self._tuner_count = tunerCount def getId(self) -> str: return self._id def getServiceInfo(self) -> Any: return self._service_info def getParentId(self) -> str: return self._parent_id def getType(self) -> int: return self._type def getHdmiDeviceInfo(self) -> Any: return self._hdmi_device_info def loadIcon(self, context: Any) -> Any: return None def loadLogo(self, context: Any) -> Any: return None def createIntentForSetupScreen(self) -> Any: return None def isHidden(self) -> bool: return self._is_hidden def canRecord(self) -> bool: return self._can_record def canPauseRecording(self) -> bool: return self._can_pause_recording def getPassthroughMode(self) -> int: return self._passthrough_mode def getTunerCount(self) -> int: return self._tuner_count def toString(self) -> str: type_names = { TvInputInfo.TYPE_TUNER: "TUNER", TvInputInfo.TYPE_HDMI: "HDMI", TvInputInfo.TYPE_COMPOSITE: "COMPOSITE", TvInputInfo.TYPE_COMPONENT: "COMPONENT", } return f"TvInputInfo(id={self._id}, type={type_names.get(self._type, str(self._type))})" def equals(self, other: Any) -> bool: if not isinstance(other, TvInputInfo): return False return self._id == other._id def hashCode(self) -> int: return hash(self._id) & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # TvInputService (扩展) # ═══════════════════════════════════════════════════════════════ class TvInputService(Stub): """TvInputService — 电视输入服务 (完整版)""" RECORDING_ERROR_RECORDING_LOST = 0 RECORDING_ERROR_TIMEOUT = 1 RECORDING_ERROR_OTHER = 2 RECORDING_ERROR_INSUFFICIENT_SPACE = 3 RECORDING_ERROR_SERVICE_UNAVAILABLE = 4 class Session(Stub): """TvInputService.Session — 电视会话""" def __init__(self): super().__init__() self._main = False self._stream_volume = 1.0 self._caption_enabled = False self._surface: Any = None self._overlay_view: Any = None self._recording_enabled = False def onSetSurface(self, surface: Any) -> bool: self._surface = surface return True def onSurfaceChanged(self, format: int, width: int, height: int): pass def onSurfaceReleased(self): self._surface = None def onAppPrivateCommand(self, action: str, data: Any): pass def onCreateOverlayView(self) -> Any: return self._overlay_view def onRelayoutContent(self): pass def onSetCaptionEnabled(self, enabled: bool): self._caption_enabled = enabled def onSetStreamVolume(self, volume: float): self._stream_volume = volume def onTimeShiftSetCurrentPositionRequest(self, positionMs: int): pass def notifyTimeShiftStatusChanged(self, status: int): pass def scheduleOverlayView(self, view: Any): self._overlay_view = view def timeShiftPlay(self, uri: str): pass def timeShiftPause(self): pass def timeShiftResume(self): pass def timeShiftSeekTo(self, positionMs: int): pass def timeShiftSetPlaybackParams(self, params: Any): pass def timeShiftEnablePositionTracking(self, enable: bool): pass def notifyVideoAvailable(self): pass def notifyVideoUnavailable(self, reason: int): pass def notifySessionEvent(self, eventType: str, eventArgs: Any): pass def notifyTracksChanged(self, tracks: List[Any]): pass def notifyTrackSelected(self, type_: str, trackId: str): pass def notifyContentAllowed(self): pass def notifyContentBlocked(self, rating: str): pass def setMain(self, main: bool): self._main = main def setStreamVolume(self, volume: float): self._stream_volume = volume def notifyRecordingStarted(self): self._recording_enabled = True def notifyRecordingStopped(self): self._recording_enabled = False def notifyRecordingPaused(self): pass def notifyRecordingResumed(self): pass def notifyRecordingError(self, errorCode: int): pass def getSurface(self) -> Any: return self._surface def isCaptionEnabled(self) -> bool: return self._caption_enabled def getStreamVolume(self) -> float: return self._stream_volume def toString(self) -> str: return f"Session(main={self._main}, volume={self._stream_volume})" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF class RecordingSession(Stub): """TvInputService.RecordingSession — 录制会话""" def __init__(self): super().__init__() self._input_id = "" self._recording_enabled = False self._tune_uri: Any = None def onTune(self, uri: Any): self._tune_uri = uri def onStartRecording(self, uri: Any): self._recording_enabled = True def onStopRecording(self): self._recording_enabled = False def onPauseRecording(self): pass def onResumeRecording(self): pass def notifyRecordingStarted(self): pass def notifyRecordingStopped(self): pass def notifyRecordingPaused(self): pass def notifyRecordingResumed(self): pass def notifyRecordingError(self, errorCode: int): pass def notifyTuned(self, uri: Any): pass def getTuneUri(self) -> Optional[Any]: return self._tune_uri def toString(self) -> str: return f"RecordingSession(recording={self._recording_enabled})" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF class HardwareSession(Stub): """TvInputService.HardwareSession — 硬件会话""" def __init__(self): super().__init__() self._session = TvInputService.Session() def onHardwareVideoAvailable(self): pass def onHardwareVideoUnavailable(self, reason: int): pass def toString(self) -> str: return f"HardwareSession@{hex(id(self))}" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF def __init__(self): super().__init__() self._sessions: List[TvInputService.Session] = [] self._recording_sessions: List[TvInputService.RecordingSession] = [] def onCreateSession(self, inputId: str) -> Session: session = TvInputService.Session() self._sessions.append(session) return session def onCreateRecordingSession(self, inputId: str) -> RecordingSession: session = TvInputService.RecordingSession() self._recording_sessions.append(session) return session def onSetupFinished(self, inputId: str): pass def sessionCreated(self, session: Session): pass def notifyHardwareFailed(self, deviceId: int): pass def getSessions(self) -> List[Session]: return list(self._sessions) def getRecordingSessions(self) -> List[RecordingSession]: return list(self._recording_sessions) def toString(self) -> str: return f"TvInputService(sessions={len(self._sessions)})" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # TvRecordingClient (扩展) # ═══════════════════════════════════════════════════════════════ class TvRecordingClient(Stub): """TvRecordingClient — 电视录制客户端""" def __init__(self, context: Any = None, inputId: str = "", callback: Any = None): super().__init__() self._context = context self._input_id = inputId self._callback = callback self._recording_uri: Optional[str] = None self._is_recording = False self._is_paused = False def startRecording(self, uri: str): self._recording_uri = uri self._is_recording = True self._is_paused = False def stopRecording(self): self._is_recording = False self._is_paused = False def pauseRecording(self): if self._is_recording: self._is_paused = True def resumeRecording(self): if self._is_paused: self._is_paused = False def release(self): self._recording_uri = None self._is_recording = False self._is_paused = False def isRecording(self) -> bool: return self._is_recording def isPaused(self) -> bool: return self._is_paused def getRecordingUri(self) -> Optional[str]: return self._recording_uri def getInputId(self) -> str: return self._input_id class Callback(Stub): """TvRecordingClient.Callback — 录制回调""" def onRecordingStarted(self): pass def onRecordingStopped(self): pass def onRecordingPaused(self): pass def onRecordingResumed(self): pass def onError(self, errorCode: int): pass def onTuned(self, channelUri: Any): pass def onConnectionFailed(self, inputId: str): pass def onDisconnected(self, inputId: str): pass def toString(self) -> str: return f"RecordingCallback@{hex(id(self))}" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF def toString(self) -> str: return f"TvRecordingClient(input={self._input_id}, recording={self._is_recording})" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # TvView # ═══════════════════════════════════════════════════════════════ class TvView(Stub): """TvView — 电视视图""" def __init__(self, context: Any = None): super().__init__() self._context = context self._input_id: Optional[str] = None self._callback: Optional[TvView.TvInputCallback] = None self._stream_configs: List[TvStreamConfig] = [] self._is_overlay = False self._time_shift_enabled = False class TvInputCallback(Stub): """TvView.TvInputCallback — 输入回调""" def onConnectionFailed(self, inputId: str): pass def onDisconnected(self, inputId: str): pass def onChannelRetuned(self, channelUri: Any): pass def onTrackSelected(self, type_: int, trackId: str): pass def onTracksChanged(self, tracks: List[Any]): pass def onVideoAvailable(self, reason: int): pass def onVideoUnavailable(self, reason: int): pass def onContentAllowed(self): pass def onContentBlocked(self, rating: str): pass def onTimeShiftStatusChanged(self, status: int): pass def toString(self) -> str: return f"TvInputCallback@{hex(id(self))}" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF def tune(self, inputId: str, channelUri: Any): self._input_id = inputId def reset(self): self._input_id = None def setCallback(self, callback: TvInputCallback): self._callback = callback def setCaptionEnabled(self, enabled: bool): pass def setStreamVolume(self, volume: float): pass def selectTrack(self, type_: int, trackId: str): pass def getSelectedTrack(self, type_: int) -> Optional[str]: return None def getTracks(self, type_: int) -> List[Any]: return [] def timeShiftPlay(self, uri: str): self._time_shift_enabled = True def timeShiftPause(self): pass def timeShiftResume(self): pass def timeShiftSeekTo(self, positionMs: int): pass def timeShiftSetPlaybackParams(self, params: Any): pass def timeShiftEnablePositionTracking(self, enable: bool): pass def timeShiftGetCurrentPosition(self) -> int: return 0 def timeShiftGetStartPosition(self) -> int: return 0 def sendAppPrivateCommand(self, action: str, data: Any): pass def dispatchKeyEvent(self, event: Any) -> bool: return False def dispatchUnhandledInputEvent(self, event: Any) -> bool: return False def streamOverlay(self, isOverlay: bool): self._is_overlay = isOverlay def getInputId(self) -> Optional[str]: return self._input_id def toString(self) -> str: return f"TvView(input={self._input_id})" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # TvStreamConfig # ═══════════════════════════════════════════════════════════════ class TvStreamConfig(Stub): """TvStreamConfig — 电视流配置""" STREAM_TYPE_INDEPENDENT_VIDEO = 0 STREAM_TYPE_INDEPENDENT_SOURCE = 1 STREAM_TYPE_SESSION = 2 def __init__(self, streamId: int = 0, type_: int = STREAM_TYPE_INDEPENDENT_VIDEO, maxWidth: int = 1920, maxHeight: int = 1080, generation: int = 0): super().__init__() self._stream_id = streamId self._type = type_ self._max_width = maxWidth self._max_height = maxHeight self._generation = generation def getStreamId(self) -> int: return self._stream_id def getType(self) -> int: return self._type def getMaxWidth(self) -> int: return self._max_width def getMaxHeight(self) -> int: return self._max_height def getGeneration(self) -> int: return self._generation class Builder(Stub): """TvStreamConfig.Builder — 流配置构建器""" def __init__(self): super().__init__() self._stream_id = 0 self._type = TvStreamConfig.STREAM_TYPE_INDEPENDENT_VIDEO self._max_width = 1920 self._max_height = 1080 self._generation = 0 def setStreamId(self, streamId: int) -> "TvStreamConfig.Builder": self._stream_id = streamId return self def setType(self, type_: int) -> "TvStreamConfig.Builder": self._type = type_ return self def setMaxWidth(self, maxWidth: int) -> "TvStreamConfig.Builder": self._max_width = maxWidth return self def setMaxHeight(self, maxHeight: int) -> "TvStreamConfig.Builder": self._max_height = maxHeight return self def setGeneration(self, generation: int) -> "TvStreamConfig.Builder": self._generation = generation return self def build(self) -> "TvStreamConfig": return TvStreamConfig( streamId=self._stream_id, type_=self._type, maxWidth=self._max_width, maxHeight=self._max_height, generation=self._generation, ) def toString(self) -> str: return f"TvStreamConfig(id={self._stream_id}, type={self._type})" def equals(self, other: Any) -> bool: if not isinstance(other, TvStreamConfig): return False return self._stream_id == other._stream_id def hashCode(self) -> int: return self._stream_id & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # TvContract # ═══════════════════════════════════════════════════════════════ class TvContract(Stub): """TvContract — TV 内容提供者合约""" AUTHORITY = "android.media.tv" CONTENT_URI_BASE = "content://android.media.tv" # ═══════════════════════════════════════════════════════════ # Channels # ═══════════════════════════════════════════════════════════ class Channels(Stub): """TvContract.Channels — 频道表""" CONTENT_URI = "content://android.media.tv/channel" CONTENT_URI_PASSTHROUGH = "content://android.media.tv/passthrough_channel" _ID = "_id" COLUMN_BROWSABLE = "browsable" COLUMN_DESCRIPTION = "description" COLUMN_DISPLAY_NAME = "display_name" COLUMN_DISPLAY_NUMBER = "display_number" COLUMN_INPUT_ID = "input_id" COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data" COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1" COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2" COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3" COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4" COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id" COLUMN_NETWORK_AFFILIATION = "network_affiliation" COLUMN_ORIGINAL_NETWORK_ID = "original_network_id" COLUMN_SEARCHABLE = "searchable" COLUMN_SERVICE_ID = "service_id" COLUMN_SERVICE_TYPE = "service_type" COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id" COLUMN_TYPE = "type" COLUMN_VIDEO_FORMAT = "video_format" COLUMN_VIDEO_HEIGHT = "video_height" COLUMN_VIDEO_WIDTH = "video_width" COLUMN_WATCH_NEXT_PROGRAM_COUNT = "watch_next_program_count" BROWSABLE_NO = 0 BROWSABLE_YES = 1 TYPE_1SEG = "1SEG" TYPE_ATSC_C = "ATSC_C" TYPE_ATSC_M = "ATSC_M" TYPE_ATSC_T = "ATSC_T" TYPE_DTMB = "DTMB" TYPE_DVB_C = "DVB_C" TYPE_DVB_C2 = "DVB_C2" TYPE_DVB_H = "DVB_H" TYPE_DVB_S = "DVB_S" TYPE_DVB_S2 = "DVB_S2" TYPE_DVB_SH = "DVB_SH" TYPE_DVB_T = "DVB_T" TYPE_DVB_T2 = "DVB_T2" TYPE_ISDB_C = "ISDB_C" TYPE_ISDB_S = "ISDB_S" TYPE_ISDB_T = "ISDB_T" TYPE_ISDB_TB = "ISDB_TB" TYPE_NTSC = "NTSC" TYPE_OTHER = "OTHER" TYPE_PAL = "PAL" TYPE_PREVIEW = "PREVIEW" TYPE_SECAM = "SECAM" TYPE_S_DMB = "S_DMB" TYPE_T_DMB = "T_DMB" VIDEO_FORMAT_1080I = "1080i" VIDEO_FORMAT_1080P = "1080p" VIDEO_FORMAT_2160P = "2160p" VIDEO_FORMAT_240P = "240p" VIDEO_FORMAT_360P = "360p" VIDEO_FORMAT_4320P = "4320p" VIDEO_FORMAT_480I = "480i" VIDEO_FORMAT_480P = "480p" VIDEO_FORMAT_576I = "576i" VIDEO_FORMAT_576P = "576p" VIDEO_FORMAT_720P = "720p" SERVICE_TYPE_AUDIO = "AUDIO" SERVICE_TYPE_AUDIO_VIDEO = "AUDIO_VIDEO" SERVICE_TYPE_OTHER = "OTHER" # ═══════════════════════════════════════════════════════════ # Programs # ═══════════════════════════════════════════════════════════ class Programs(Stub): """TvContract.Programs — 节目表""" CONTENT_URI = "content://android.media.tv/program" _ID = "_id" COLUMN_AUDIO_LANGUAGE = "audio_language" COLUMN_BROADCAST_GENRE = "broadcast_genre" COLUMN_CHANNEL_ID = "channel_id" COLUMN_CONTENT_RATING = "content_rating" COLUMN_DESCRIPTION = "description" COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis" COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number" COLUMN_EPISODE_TITLE = "episode_title" COLUMN_INTENT_URI = "intent_uri" COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data" COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1" COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2" COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3" COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4" COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id" COLUMN_LONG_DESCRIPTION = "long_description" COLUMN_POSTER_ART_URI = "poster_art_uri" COLUMN_REVIEW_RATING_SCORE = "review_rating_score" COLUMN_REVIEW_RATING_STYLE = "review_rating_style" COLUMN_SEARCHABLE = "searchable" COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number" COLUMN_SEASON_TITLE = "season_title" COLUMN_SHORT_DESCRIPTION = "short_description" COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis" COLUMN_THUMBNAIL_URI = "thumbnail_uri" COLUMN_TITLE = "title" COLUMN_VERSION_NUMBER = "version_number" COLUMN_VIDEO_HEIGHT = "video_height" COLUMN_VIDEO_WIDTH = "video_width" REVIEW_RATING_STYLE_PERCENTAGE = 0 REVIEW_RATING_STYLE_STARS = 1 REVIEW_RATING_STYLE_THUMBS_UP_DOWN = 2 # ═══════════════════════════════════════════════════════════ # RecordedPrograms # ═══════════════════════════════════════════════════════════ class RecordedPrograms(Stub): """TvContract.RecordedPrograms — 录制节目表""" CONTENT_URI = "content://android.media.tv/recorded_program" _ID = "_id" COLUMN_AUDIO_LANGUAGE = "audio_language" COLUMN_BROADCAST_GENRE = "broadcast_genre" COLUMN_CHANNEL_ID = "channel_id" COLUMN_CONTENT_RATING = "content_rating" COLUMN_DESCRIPTION = "description" COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis" COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number" COLUMN_EPISODE_TITLE = "episode_title" COLUMN_INPUT_ID = "input_id" COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data" COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id" COLUMN_LONG_DESCRIPTION = "long_description" COLUMN_POSTER_ART_URI = "poster_art_uri" COLUMN_RECORDING_DURATION_MILLIS = "recording_duration_millis" COLUMN_RECORDING_DATA_BYTES = "recording_data_bytes" COLUMN_RECORDING_DATA_URI = "recording_data_uri" COLUMN_RECORDING_DURATION_MS = "recording_duration_ms" COLUMN_REVIEW_RATING_SCORE = "review_rating_score" COLUMN_REVIEW_RATING_STYLE = "review_rating_style" COLUMN_SEARCHABLE = "searchable" COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number" COLUMN_SEASON_TITLE = "season_title" COLUMN_SHORT_DESCRIPTION = "short_description" COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis" COLUMN_THUMBNAIL_URI = "thumbnail_uri" COLUMN_TITLE = "title" COLUMN_VERSION_NUMBER = "version_number" # ═══════════════════════════════════════════════════════════ # WatchNextPrograms # ═══════════════════════════════════════════════════════════ class WatchNextPrograms(Stub): """TvContract.WatchNextPrograms — 待看节目表""" CONTENT_URI = "content://android.media.tv/watch_next_program" _ID = "_id" COLUMN_AUDIO_LANGUAGE = "audio_language" COLUMN_BROADCAST_GENRE = "broadcast_genre" COLUMN_CHANNEL_ID = "channel_id" COLUMN_CONTENT_RATING = "content_rating" COLUMN_DESCRIPTION = "description" COLUMN_DURATION_MILLIS = "duration_millis" COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis" COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number" COLUMN_EPISODE_TITLE = "episode_title" COLUMN_INTENT_URI = "intent_uri" COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data" COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id" COLUMN_LAST_PLAYBACK_POSITION_MILLIS = "last_playback_position_millis" COLUMN_LONG_DESCRIPTION = "long_description" COLUMN_POSTER_ART_URI = "poster_art_uri" COLUMN_REVIEW_RATING_SCORE = "review_rating_score" COLUMN_REVIEW_RATING_STYLE = "review_rating_style" COLUMN_SEARCHABLE = "searchable" COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number" COLUMN_SEASON_TITLE = "season_title" COLUMN_SHORT_DESCRIPTION = "short_description" COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis" COLUMN_THUMBNAIL_URI = "thumbnail_uri" COLUMN_TITLE = "title" COLUMN_VERSION_NUMBER = "version_number" COLUMN_WATCH_NEXT_TYPE = "watch_next_type" WATCH_NEXT_TYPE_CONTINUE = 0 WATCH_NEXT_TYPE_NEXT = 1 WATCH_NEXT_TYPE_NEW = 2 WATCH_NEXT_TYPE_WATCHLIST = 3 # ═══════════════════════════════════════════════════════════ # PreviewPrograms # ═══════════════════════════════════════════════════════════ class PreviewPrograms(Stub): """TvContract.PreviewPrograms — 预览节目表""" CONTENT_URI = "content://android.media.tv/preview_program" _ID = "_id" COLUMN_AUDIO_LANGUAGE = "audio_language" COLUMN_BROADCAST_GENRE = "broadcast_genre" COLUMN_CHANNEL_ID = "channel_id" COLUMN_CONTENT_RATING = "content_rating" COLUMN_DESCRIPTION = "description" COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis" COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number" COLUMN_EPISODE_TITLE = "episode_title" COLUMN_INTENT_URI = "intent_uri" COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data" COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id" COLUMN_ITEM_COUNT = "item_count" COLUMN_LAST_PLAYBACK_POSITION_MILLIS = "last_playback_position_millis" COLUMN_LONG_DESCRIPTION = "long_description" COLUMN_POSTER_ART_URI = "poster_art_uri" COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri" COLUMN_REVIEW_RATING_SCORE = "review_rating_score" COLUMN_REVIEW_RATING_STYLE = "review_rating_style" COLUMN_SEARCHABLE = "searchable" COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number" COLUMN_SEASON_TITLE = "season_title" COLUMN_SHORT_DESCRIPTION = "short_description" COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis" COLUMN_THUMBNAIL_URI = "thumbnail_uri" COLUMN_TITLE = "title" COLUMN_TYPE = "type" COLUMN_VERSION_NUMBER = "version_number" COLUMN_WEIGHT = "weight" ASPECT_RATIO_16_9 = 0 ASPECT_RATIO_1_1 = 1 ASPECT_RATIO_2_3 = 2 ASPECT_RATIO_3_2 = 3 ASPECT_RATIO_4_3 = 4 ASPECT_RATIO_MOVIE = 5 TYPE_CLIP = "CLIP" TYPE_EPISODE = "EPISODE" TYPE_MOVIE = "MOVIE" TYPE_PLAYLIST = "PLAYLIST" TYPE_SERIES = "SERIES" TYPE_TRAILER = "TRAILER" TYPE_EVENT = "EVENT" def __init__(self): super().__init__() def buildChannelUri(self, channelId: int) -> str: return f"{TvContract.Channels.CONTENT_URI}/{channelId}" def buildProgramUri(self, programId: int) -> str: return f"{TvContract.Programs.CONTENT_URI}/{programId}" def buildRecordedProgramUri(self, recordedProgramId: int) -> str: return f"{TvContract.RecordedPrograms.CONTENT_URI}/{recordedProgramId}" def buildWatchNextProgramUri(self, watchNextProgramId: int) -> str: return f"{TvContract.WatchNextPrograms.CONTENT_URI}/{watchNextProgramId}" def buildPreviewProgramUri(self, previewProgramId: int) -> str: return f"{TvContract.PreviewPrograms.CONTENT_URI}/{previewProgramId}" def buildChannelsUriForInput(self, inputId: str) -> str: return f"{TvContract.Channels.CONTENT_URI}?inputId={inputId}" def buildInputId(serviceComponentName: str) -> str: return serviceComponentName def toString(self) -> str: return f"TvContract@{hex(id(self))}" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # PreviewProgram # ═══════════════════════════════════════════════════════════════ class PreviewProgram(Stub): """PreviewProgram — 预览节目""" def __init__(self, id: int = 0, channelId: int = 0, title: str = "", description: str = "", longDescription: str = "", posterArtUri: str = "", thumbnailUri: str = "", startTimeUtcMillis: int = 0, endTimeUtcMillis: int = 0, weight: int = 0, type_: str = "CLIP"): super().__init__() self._id = id self._channel_id = channelId self._title = title self._description = description self._long_description = longDescription self._poster_art_uri = posterArtUri self._thumbnail_uri = thumbnailUri self._start_time_utc_millis = startTimeUtcMillis self._end_time_utc_millis = endTimeUtcMillis self._weight = weight self._type = type_ def getId(self) -> int: return self._id def getChannelId(self) -> int: return self._channel_id def getTitle(self) -> str: return self._title def getDescription(self) -> str: return self._description def getLongDescription(self) -> str: return self._long_description def getPosterArtUri(self) -> str: return self._poster_art_uri def getThumbnailUri(self) -> str: return self._thumbnail_uri def getStartTimeUtcMillis(self) -> int: return self._start_time_utc_millis def getEndTimeUtcMillis(self) -> int: return self._end_time_utc_millis def getWeight(self) -> int: return self._weight def getType(self) -> str: return self._type def toString(self) -> str: return f"PreviewProgram(id={self._id}, title={self._title})" def equals(self, other: Any) -> bool: if not isinstance(other, PreviewProgram): return False return self._id == other._id def hashCode(self) -> int: return self._id & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # PreviewChannel # ═══════════════════════════════════════════════════════════════ class PreviewChannel(Stub): """PreviewChannel — 预览频道""" def __init__(self, id: int = 0, type_: str = "PREVIEW", displayName: str = "", description: str = "", displayNumber: str = "", browsable: bool = True): super().__init__() self._id = id self._type = type_ self._display_name = displayName self._description = description self._display_number = displayNumber self._browsable = browsable def getId(self) -> int: return self._id def getType(self) -> str: return self._type def getDisplayName(self) -> str: return self._display_name def getDescription(self) -> str: return self._description def getDisplayNumber(self) -> str: return self._display_number def isBrowsable(self) -> bool: return self._browsable def toString(self) -> str: return f"PreviewChannel(id={self._id}, name={self._display_name})" def equals(self, other: Any) -> bool: if not isinstance(other, PreviewChannel): return False return self._id == other._id def hashCode(self) -> int: return self._id & 0x7FFFFFFF # ═══════════════════════════════════════════════════════════════ # TvInputManager (扩展完整版) # ═══════════════════════════════════════════════════════════════ class TvInputManager(Stub): """TvInputManager — 电视输入管理器 (完整版)""" ACTION_VIEW_RECORDING_SCHEDULES = "android.tv.action.VIEW_RECORDING_SCHEDULES" ACTION_WATCH_NEXT = "android.tv.action.WATCH_NEXT" ACTION_PREVIEW_PROGRAM_BROWSABLE = "android.tv.action.PREVIEW_PROGRAM_BROWSABLE" TIME_SHIFT_STATUS_AVAILABLE = 0 TIME_SHIFT_STATUS_UNAVAILABLE = 1 TIME_SHIFT_STATUS_UNKNOWN = 2 TIME_SHIFT_STATUS_UNSUPPORTED = 3 VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = 8 VIDEO_UNAVAILABLE_REASON_BUFFERING = 3 VIDEO_UNAVAILABLE_REASON_CAS = 7 VIDEO_UNAVAILABLE_REASON_INSUFFICIENT_RESOURCE = 5 VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED = 6 VIDEO_UNAVAILABLE_REASON_PREEMPTED = 4 VIDEO_UNAVAILABLE_REASON_TUNING = 1 VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0 VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL = 2 INPUT_STATE_CONNECTED = 0 INPUT_STATE_CONNECTED_STANDBY = 4 INPUT_STATE_DISCONNECTED = 2 INPUT_STATE_UNKNOWN = 1 class TvInputCallback(Stub): """TvInputManager.TvInputCallback — 电视输入回调""" def onInputAdded(self, inputId: str): pass def onInputRemoved(self, inputId: str): pass def onInputChanged(self, inputId: str): pass def onInputStateChanged(self, inputId: str, state: int): pass def onTvInputInfoUpdated(self, inputInfo: "TvInputInfo"): pass def toString(self) -> str: return f"TvInputCallback@{hex(id(self))}" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF def __init__(self): super().__init__() self._inputs: Dict[str, TvInputInfo] = {} self._callbacks: Dict[int, TvInputManager.TvInputCallback] = {} self._input_states: Dict[str, int] = {} def getTvInputList(self) -> List["TvInputInfo"]: return list(self._inputs.values()) def getTvInputInfo(self, inputId: str) -> Optional["TvInputInfo"]: return self._inputs.get(inputId) def getCurrentTvInputInfo(self) -> Optional["TvInputInfo"]: for inp in self._inputs.values(): return inp return None def updateTvInputInfo(self, inputInfo: "TvInputInfo"): if inputInfo: self._inputs[inputInfo.getId()] = inputInfo def registerCallback(self, callback: TvInputCallback, handler: Any = None): lid = id(callback) self._callbacks[lid] = callback def unregisterCallback(self, callback: TvInputCallback): lid = id(callback) self._callbacks.pop(lid, None) def createRecordingSession(self, recordingService: str, inputId: str): raise AndroidOnlyError("TvInputManager.createRecordingSession() is not available on desktop") def sendTvInputNotifyIntent(self, intent: Any, inputId: str): pass def isParentalControlsEnabled(self) -> bool: return False def isRatingBlocked(self, rating: str) -> bool: return False def getBlockedRatings(self) -> List[str]: return [] def getAvailableTvInputs(self) -> List[str]: return list(self._inputs.keys()) def getTvContentRatingSystems(self) -> List[str]: return ["US_TV", "US_MOVIE"] def getInputState(self, inputId: str) -> int: return self._input_states.get(inputId, TvInputManager.INPUT_STATE_UNKNOWN) def setInputState(self, inputId: str, state: int): self._input_states[inputId] = state def addInput(self, inputInfo: TvInputInfo): if inputInfo: self._inputs[inputInfo.getId()] = inputInfo def removeInput(self, inputId: str): self._inputs.pop(inputId, None) def toString(self) -> str: return f"TvInputManager(inputs={len(self._inputs)})" def equals(self, other: Any) -> bool: return self is other def hashCode(self) -> int: return id(self) & 0x7FFFFFFF