- android/ 167个模块 (app/content/os/view/widget/graphics/media/hardware/net/telephony/provider等) - java/ 35个模块 (lang/io/util/net/nio/security/crypto/sql/beans/rmi/awt等) - javax/ 4个模块 (net/crypto/security/sql) - org/ 5个模块 (json/w3c_dom/xml_sax/xml_pull/apache_http) - dalvik/ 1个模块 (system) - ~3053个类/接口,方法签名完全对齐Android SDK - 桌面跨平台运行 (Windows/macOS/Linux) - 修复所有导入错误和Python关键字冲突
1500 lines
51 KiB
Python
1500 lines
51 KiB
Python
"""
|
|
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 |