""" Android media 异步组件 — MediaCodec, MediaExtractor, MediaMuxer, MediaPlayer 扩展等 方法签名完全对齐 Android SDK """ from __future__ import annotations import os import io import math import time import struct import threading from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union from enum import IntEnum from .._core import Stub, AndroidOnlyError from ..java.lang import RuntimeException # ═══════════════════════════════════════════════════════════════ # MediaCodec 常量 # ═══════════════════════════════════════════════════════════════ BUFFER_FLAG_KEY_FRAME = 1 BUFFER_FLAG_CODEC_CONFIG = 2 BUFFER_FLAG_END_OF_STREAM = 4 BUFFER_FLAG_PARTIAL_FRAME = 8 BUFFER_FLAG_SYNC_FRAME = 1 CRYPTO_MODE_UNENCRYPTED = 0 CRYPTO_MODE_AES_CTR = 1 CRYPTO_MODE_AES_WV = 2 CRYPTO_MODE_AES_CBC = 3 INFO_TRY_AGAIN_LATER = -1 INFO_OUTPUT_FORMAT_CHANGED = -2 INFO_OUTPUT_BUFFERS_CHANGED = -3 MEDIA_CODEC_UNKNOWN = 0 MEDIA_CODEC_DECODER = 1 MEDIA_CODEC_ENCODER = 2 COLOR_FormatUnsupported = 0x7FFFFFFF COLOR_FormatYUV420Planar = 19 COLOR_FormatYUV420SemiPlanar = 21 COLOR_FormatYUV420PackedPlanar = 20 COLOR_FormatYUV420PackedSemiPlanar = 22 COLOR_FormatYUV422Planar = 16 COLOR_FormatYUV422SemiPlanar = 41 COLOR_FormatYUV422PackedPlanar = 17 COLOR_FormatYUV422PackedSemiPlanar = 18 COLOR_FormatYUV444Planar = 31 COLOR_FormatYUV444SemiPlanar = 42 COLOR_FormatYUV444PackedPlanar = 32 COLOR_FormatYUV444PackedSemiPlanar = 33 COLOR_TI_FormatYUV420PackedSemiPlanar = 0x7F000100 COLOR_FormatSurface = 0x7F000789 COLOR_FormatYUV420Flexible = 0x7F420888 COLOR_Format24BitBGR = 5 COLOR_Format32BitARGB8888 = 9 COLOR_Format32BitBGRA8888 = 10 BITRATE_MODE_VBR = 0 BITRATE_MODE_CBR = 1 BITRATE_MODE_CQ = 2 CODEC_LEVEL_UNSPECIFIED = 0 CODEC_PROFILE_UNSPECIFIED = 0 AUDIO_CHANNEL_OUT_MONO = 4 AUDIO_CHANNEL_OUT_STEREO = 12 AUDIO_CHANNEL_OUT_5POINT1 = 252 AUDIO_CHANNEL_OUT_7POINT1 = 6396 AUDIO_CHANNEL_IN_MONO = 16 AUDIO_CHANNEL_IN_STEREO = 12 PCM_ENCODING_PCM_16BIT = 2 PCM_ENCODING_PCM_8BIT = 3 PCM_ENCODING_PCM_FLOAT = 4 AUDIO_OUTPUT_FLAG_NONE = 0 AUDIO_OUTPUT_FLAG_DIRECT = 1 AUDIO_OUTPUT_FLAG_HW_AV_SYNC = 8 # MediaFormat KEY 常量 KEY_MIME = "mime" KEY_WIDTH = "width" KEY_HEIGHT = "height" KEY_FRAME_RATE = "frame-rate" KEY_BIT_RATE = "bitrate" KEY_COLOR_FORMAT = "color-format" KEY_I_FRAME_INTERVAL = "i-frame-interval" KEY_CHANNEL_COUNT = "channel-count" KEY_SAMPLE_RATE = "sample-rate" KEY_MAX_INPUT_SIZE = "max-input-size" KEY_DURATION = "durationUs" KEY_IS_ADTS = "is-adts" KEY_CHANNEL_MASK = "channel-mask" KEY_ENCODING = "encoding" KEY_PCM_ENCODING = "pcm-encoding" KEY_PROFILE = "profile" KEY_LEVEL = "level" KEY_LANGUAGE = "language" KEY_PRIORITY = "priority" KEY_OPERATING_RATE = "operating-rate" KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period" KEY_LATENCY = "latency" KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder" KEY_MAX_PTS_GAP_TO_ENCODER = "max-pts-gap-to-encoder" KEY_PREPEND_HEADER_TO_SYNC_FRAMES = "prepend-sps-pps-to-idr-frames" KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after" KEY_TEMPORAL_LAYERING = "temporal-layering" KEY_HDR10_PLUS_INFO = "hdr10-plus-info" KEY_PIXEL_ASPECT_RATIO_WIDTH = "pixel-aspect-ratio-width" KEY_PIXEL_ASPECT_RATIO_HEIGHT = "pixel-aspect-ratio-height" KEY_AAC_DRC_BOOST_FACTOR = "aac-drc-boost-factor" KEY_AAC_DRC_ATTENUATION_FACTOR = "aac-drc-attenuation-factor" KEY_AAC_DRC_HEAVY_COMPRESSION = "aac-drc-heavy-compression" KEY_AAC_DRC_TARGET_REFERENCE_LEVEL = "aac-target-ref-level" KEY_AAC_ENCODED_TARGET_LEVEL = "aac-encoded-target-level" KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT = "aac-max-output-channel-count" KEY_AAC_PROFILE = "aac-profile" KEY_AAC_SBR_MODE = "aac-sbr-mode" KEY_ENCODER_DELAY_FRAME_COUNT = "encoder-delay" KEY_ENCODER_PADDING_FRAME_COUNT = "encoder-padding" KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level" KEY_MAX_BIT_RATE = "max-bitrate" KEY_QUALITY = "quality" KEY_SAMPLE_MIME_TYPE = "sample-mime-type" KEY_TRACK_ID = "track-id" KEY_IS_AUTOSELECT = "is-autoselect" KEY_IS_DEFAULT = "is-default" KEY_IS_FORCED_SUBTITLE = "is-forced-subtitle" KEY_ROTATION = "rotation" KEY_CAPTURE_RATE = "capture-rate" KEY_FEATURE_ = "feature-" KEY_HDR_STATIC_INFO = "hdr-static-info" KEY_HDR10_PLUS_INFO = "hdr10-plus-info" KEY_COLOR_TRANSFER_REQUEST = "color-transfer-request" KEY_OFFSET_TIME = "offset-time" KEY_PCM_ENCODING = "pcm-encoding" KEY_SAMPLE_RATE = "sample-rate" KEY_SUPPORTED_PCM_ENCODINGS = "supported-pcm-encodings" KEY_SUPPORTED_PCM_USAGES = "supported-pcm-usages" KEY_TRANSFER_TIMEOUT = "transfer-timeout" KEY_VIDEO_QP_B_MAX = "video-qp-b-max" KEY_VIDEO_QP_B_MIN = "video-qp-b-min" KEY_VIDEO_QP_I_MAX = "video-qp-i-max" KEY_VIDEO_QP_I_MIN = "video-qp-i-min" KEY_VIDEO_QP_P_MAX = "video-qp-p-max" KEY_VIDEO_QP_P_MIN = "video-qp-p-min" # MIME 类型 MIMETYPE_VIDEO_AV1 = "video/av01" MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9" MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8" MIMETYPE_VIDEO_H265 = "video/hevc" MIMETYPE_VIDEO_H264 = "video/avc" MIMETYPE_VIDEO_MPEG2 = "video/mpeg2" MIMETYPE_VIDEO_MPEG4 = "video/mp4v-es" MIMETYPE_VIDEO_VC1 = "video/wvc1" MIMETYPE_VIDEO_DIVX = "video/divx" MIMETYPE_VIDEO_DIVX3 = "video/divx3" MIMETYPE_VIDEO_FLV = "video/flv1" MIMETYPE_VIDEO_H263 = "video/3gpp" MIMETYPE_VIDEO_RAW = "video/raw" MIMETYPE_VIDEO_DOLBY_VISION = "video/dolby-vision" MIMETYPE_VIDEO_SCRAMBLED = "video/scrambled" MIMETYPE_AUDIO_AAC = "audio/mp4a-latm" MIMETYPE_AUDIO_MP3 = "audio/mpeg" MIMETYPE_AUDIO_MPEG = "audio/mpeg" MIMETYPE_AUDIO_AMR_NB = "audio/3gpp" MIMETYPE_AUDIO_AMR_WB = "audio/amr-wb" MIMETYPE_AUDIO_FLAC = "audio/flac" MIMETYPE_AUDIO_VORBIS = "audio/vorbis" MIMETYPE_AUDIO_OPUS = "audio/opus" MIMETYPE_AUDIO_RAW = "audio/raw" MIMETYPE_AUDIO_ALAC = "audio/alac" MIMETYPE_AUDIO_AC3 = "audio/ac3" MIMETYPE_AUDIO_EAC3 = "audio/eac3" MIMETYPE_AUDIO_WMA = "audio/x-ms-wma" MIMETYPE_AUDIO_MSGSM = "audio/gsm" MIMETYPE_TEXT_VTT = "text/vtt" MIMETYPE_TEXT_CEA_608 = "text/cea-608" MIMETYPE_TEXT_CEA_708 = "text/cea-708" MIMETYPE_SUBTITLE_SRT = "application/x-subrip" MIMETYPE_SUBTITLE_TTML = "application/ttml+xml" # ═══════════════════════════════════════════════════════════════ # MediaCodec.BufferInfo # ═══════════════════════════════════════════════════════════════ class CodecBufferInfo(Stub): """MediaCodec.BufferInfo — 编解码缓冲区信息""" BUFFER_FLAG_KEY_FRAME = BUFFER_FLAG_KEY_FRAME BUFFER_FLAG_CODEC_CONFIG = BUFFER_FLAG_CODEC_CONFIG BUFFER_FLAG_END_OF_STREAM = BUFFER_FLAG_END_OF_STREAM BUFFER_FLAG_PARTIAL_FRAME = BUFFER_FLAG_PARTIAL_FRAME BUFFER_FLAG_SYNC_FRAME = BUFFER_FLAG_SYNC_FRAME def __init__(self): super().__init__() self.offset: int = 0 self.size: int = 0 self.presentationTimeUs: int = 0 self.flags: int = 0 def set(self, new_offset: int, new_size: int, new_time_us: int, new_flags: int) -> None: self.offset = new_offset self.size = new_size self.presentationTimeUs = new_time_us self.flags = new_flags def duplicate(self) -> "CodecBufferInfo": info = CodecBufferInfo() info.offset = self.offset info.size = self.size info.presentationTimeUs = self.presentationTimeUs info.flags = self.flags return info def toString(self) -> str: return (f"BufferInfo(offset={self.offset}, size={self.size}, " f"timeUs={self.presentationTimeUs}, flags={self.flags})") # ═══════════════════════════════════════════════════════════════ # MediaCodec.CryptoInfo # ═══════════════════════════════════════════════════════════════ class CryptoInfo(Stub): """CryptoInfo — 加密信息 (MediaCodec)""" def __init__(self): super().__init__() self.numSubSamples = 0 self.numBytesOfClearData: List[int] = [] self.numBytesOfEncryptedData: List[int] = [] self.key: Optional[bytes] = None self.iv: Optional[bytes] = None self.mode: int = 0 class Pattern(Stub): """Pattern — 加密模式""" def __init__(self, encrypt_blocks: int = 0, skip_blocks: int = 0): super().__init__() self.encryptBlocks = encrypt_blocks self.skipBlocks = skip_blocks def toString(self) -> str: return f"Pattern(encrypt={self.encryptBlocks}, skip={self.skipBlocks})" def hashCode(self) -> int: return hash((self.encryptBlocks, self.skipBlocks)) & 0x7FFFFFFF def equals(self, other) -> bool: if not isinstance(other, CryptoInfo.Pattern): return False return (self.encryptBlocks == other.encryptBlocks and self.skipBlocks == other.skipBlocks) def setPattern(self, pattern: "CryptoInfo.Pattern"): self._pattern = pattern def getPattern(self) -> Optional["CryptoInfo.Pattern"]: return getattr(self, '_pattern', None) def toString(self) -> str: return (f"CryptoInfo(subsamples={self.numSubSamples}, " f"mode={self.mode})") # ═══════════════════════════════════════════════════════════════ # MediaCodec.CodecException # ═══════════════════════════════════════════════════════════════ class CodecException(RuntimeException): """MediaCodec.CodecException — 编解码器异常""" ERROR_RECOVERABLE = 1 ERROR_INSUFFICIENT_RESOURCE = 2 def __init__(self, error_code: int = 0, diagnostic_info: str = "", is_recoverable: bool = False, is_transient: bool = False): super().__init__(f"CodecException: {diagnostic_info}") self._error_code = error_code self._diagnostic_info = diagnostic_info self._is_recoverable = is_recoverable self._is_transient = is_transient def getErrorCode(self) -> int: return self._error_code def getDiagnosticInfo(self) -> str: return self._diagnostic_info def isRecoverable(self) -> bool: return self._is_recoverable def isTransient(self) -> bool: return self._is_transient def toString(self) -> str: return (f"CodecException(errorCode={self._error_code}, " f"recoverable={self._is_recoverable}, " f"transient={self._is_transient})") # ═══════════════════════════════════════════════════════════════ # MediaCodec # ═══════════════════════════════════════════════════════════════ class MediaCodec(Stub): """MediaCodec — 媒体编解码器""" # 内部类引用 BufferInfo = CodecBufferInfo CryptoInfo = CryptoInfo CodecException = CodecException BUFFER_FLAG_KEY_FRAME = BUFFER_FLAG_KEY_FRAME BUFFER_FLAG_CODEC_CONFIG = BUFFER_FLAG_CODEC_CONFIG BUFFER_FLAG_END_OF_STREAM = BUFFER_FLAG_END_OF_STREAM BUFFER_FLAG_PARTIAL_FRAME = BUFFER_FLAG_PARTIAL_FRAME BUFFER_FLAG_SYNC_FRAME = BUFFER_FLAG_SYNC_FRAME CRYPTO_MODE_UNENCRYPTED = CRYPTO_MODE_UNENCRYPTED CRYPTO_MODE_AES_CTR = CRYPTO_MODE_AES_CTR CRYPTO_MODE_AES_WV = CRYPTO_MODE_AES_WV CRYPTO_MODE_AES_CBC = CRYPTO_MODE_AES_CBC INFO_TRY_AGAIN_LATER = INFO_TRY_AGAIN_LATER INFO_OUTPUT_FORMAT_CHANGED = INFO_OUTPUT_FORMAT_CHANGED INFO_OUTPUT_BUFFERS_CHANGED = INFO_OUTPUT_BUFFERS_CHANGED CONFIGURE_FLAG_ENCODE = 1 def __init__(self, name: str = ""): super().__init__() self._name = name self._configured = False self._started = False self._format: Optional["MediaFormat"] = None self._input_buffers: Dict[int, Optional[bytes]] = {} self._output_buffers: Dict[int, Optional[bytes]] = {} self._input_buffer_count = 0 self._output_buffer_count = 0 self._input_lock = threading.Lock() self._output_lock = threading.Lock() self._callback: Optional[MediaCodec.Callback] = None self._handler: Optional[Any] = None @staticmethod def createByCodecName(name: str) -> "MediaCodec": return MediaCodec(name) @staticmethod def createDecoderByType(type_str: str) -> "MediaCodec": return MediaCodec(f"decoder.{type_str}") @staticmethod def createEncoderByType(type_str: str) -> "MediaCodec": return MediaCodec(f"encoder.{type_str}") def configure(self, format: "MediaFormat", surface: Any = None, crypto: Any = None, flags: int = 0) -> None: self._format = format self._configured = True self._input_buffers.clear() self._output_buffers.clear() def start(self) -> None: if not self._configured: raise RuntimeException("MediaCodec 未配置") self._started = True self._input_buffer_count = 8 self._output_buffer_count = 8 def stop(self) -> None: self._started = False def flush(self) -> None: if not self._started: return with self._input_lock: self._input_buffers.clear() with self._output_lock: self._output_buffers.clear() def release(self) -> None: self.stop() self._configured = False self._format = None def getInputBuffers(self) -> List[Optional[bytes]]: return [self._input_buffers.get(i) for i in range(self._input_buffer_count)] def getOutputBuffers(self) -> List[Optional[bytes]]: return [self._output_buffers.get(i) for i in range(self._output_buffer_count)] def getInputBuffer(self, idx: int) -> Optional[bytes]: with self._input_lock: if idx not in self._input_buffers: self._input_buffers[idx] = bytearray(1024 * 1024) return self._input_buffers[idx] def getOutputBuffer(self, idx: int) -> Optional[bytes]: with self._output_lock: return self._output_buffers.get(idx) def queueInputBuffer(self, index: int, offset: int, size: int, presentation_time_us: int, flags: int) -> None: pass def queueSecureInputBuffer(self, index: int, offset: int, info: CryptoInfo, presentation_time_us: int, flags: int) -> None: pass def dequeueInputBuffer(self, timeout_us: int) -> int: return 0 def dequeueOutputBuffer(self, info: CodecBufferInfo, timeout_us: int) -> int: return INFO_TRY_AGAIN_LATER def releaseOutputBuffer(self, index: int, render: bool) -> None: pass def getOutputFormat(self) -> Optional["MediaFormat"]: return self._format def getCodecInfo(self) -> Optional["MediaCodecInfo"]: return None def getName(self) -> str: return self._name def setCallback(self, cb: Optional["MediaCodec.Callback"], handler: Any = None) -> None: self._callback = cb self._handler = handler def createInputSurface(self) -> Any: raise AndroidOnlyError("createInputSurface() 仅在 Android 上可用") def signalEndOfInputStream(self) -> None: pass def getMetrics(self) -> Dict[str, Any]: return {} def setVideoScalingMode(self, mode: int) -> None: pass def getCodecName(self) -> str: return self._name def isConfigured(self) -> bool: return self._configured def isStarted(self) -> bool: return self._started def toString(self) -> str: return f"MediaCodec(name={self._name}, started={self._started})" class MediaCodecCallback(Stub): """MediaCodec.Callback — 编解码器回调""" def onInputBufferAvailable(self, codec: MediaCodec, index: int) -> None: pass def onOutputBufferAvailable(self, codec: MediaCodec, index: int, info: CodecBufferInfo) -> None: pass def onError(self, codec: MediaCodec, e: CodecException) -> None: pass def onOutputFormatChanged(self, codec: MediaCodec, format: "MediaFormat") -> None: pass MediaCodec.Callback = MediaCodecCallback class MediaCodecInfo(Stub): """MediaCodecInfo — 编解码器信息""" class CodecCapabilities(Stub): """MediaCodecInfo.CodecCapabilities — 编解码能力""" FEATURE_AdaptivePlayback = "adaptive-playback" FEATURE_SecurePlayback = "secure-playback" FEATURE_TunneledPlayback = "tunneled-playback" FEATURE_PartialFrame = "partial-frame" def __init__(self): super().__init__() self.colorFormats: List[int] = [] self.profileLevels: List[Any] = [] self.profileEntries: List[Any] = [] self._flags: int = 0 self._max_instances: int = 0 self._bitrate_range: Optional[Tuple[int, int]] = None self._width_range: Optional[Tuple[int, int]] = None self._height_range: Optional[Tuple[int, int]] = None self._frame_rate_range: Optional[Tuple[int, int]] = None def getColorFormats(self) -> List[int]: return list(self.colorFormats) def getProfileLevels(self) -> List[Any]: return list(self.profileLevels) def getSupportedFeatures(self) -> List[str]: features = [] return features def isFeatureSupported(self, name: str) -> bool: return False def getVideoCapabilities(self) -> Optional[Any]: return None def getAudioCapabilities(self) -> Optional[Any]: return None def toString(self) -> str: return (f"CodecCapabilities(formats={len(self.colorFormats)}, " f"profiles={len(self.profileLevels)})") class CodecProfileLevel(Stub): """MediaCodecInfo.CodecProfileLevel — 配置等级""" def __init__(self): super().__init__() self.profile: int = 0 self.level: int = 0 def __init__(self, name: str = "", canonical_name: str = ""): super().__init__() self._name = name self._canonical_name = canonical_name or name self._is_encoder: bool = False self._is_vendor: bool = False self._capabilities: Optional[MediaCodecInfo.CodecCapabilities] = None self._supported_types: List[str] = [] def getName(self) -> str: return self._name def getCanonicalName(self) -> str: return self._canonical_name def isEncoder(self) -> bool: return self._is_encoder def setEncoder(self, is_enc: bool) -> None: self._is_encoder = is_enc def isVendor(self) -> bool: return self._is_vendor def getSupportedTypes(self) -> List[str]: return list(self._supported_types) def getCapabilitiesForType(self, type_str: str) -> Optional[CodecCapabilities]: if type_str in self._supported_types: return self._capabilities return None def toString(self) -> str: return (f"MediaCodecInfo(name={self._name}, " f"encoder={self._is_encoder}, " f"types={len(self._supported_types)})") class MediaCodecList(Stub): """MediaCodecList — 编解码器列表""" REGULAR_CODECS = 0 ALL_CODECS = 1 def __init__(self, kind: int = REGULAR_CODECS): super().__init__() self._kind = kind self._codecs: List[MediaCodecInfo] = [] def getCodecInfos(self) -> List[MediaCodecInfo]: return list(self._codecs) def findDecoderForFormat(self, format: "MediaFormat") -> Optional[str]: return None def findEncoderForFormat(self, format: "MediaFormat") -> Optional[str]: return None @staticmethod def getCodecCount() -> int: return 0 @staticmethod def getCodecInfoAt(index: int) -> Optional[MediaCodecInfo]: return None def toString(self) -> str: return f"MediaCodecList(codecs={len(self._codecs)})" # ═══════════════════════════════════════════════════════════════ # MediaExtractor # ═══════════════════════════════════════════════════════════════ class MediaExtractor(Stub): """MediaExtractor — 媒体提取器""" SEEK_TO_PREVIOUS_SYNC = 0 SEEK_TO_NEXT_SYNC = 1 SEEK_TO_CLOSEST_SYNC = 2 SAMPLE_FLAG_SYNC = 1 SAMPLE_FLAG_ENCRYPTED = 2 def __init__(self): super().__init__() self._data_source: Optional[str] = None self._fd: Optional[int] = None self._track_count: int = 0 self._selected_tracks: Set[int] = set() self._current_track: int = -1 self._sample_time: int = 0 self._sample_flags: int = 0 self._sample_size: int = 0 self._sample_track_index: int = -1 self._cached_info: Dict[int, "MediaFormat"] = {} self._released = False self._lock = threading.Lock() def setDataSource(self, path: str) -> None: self._data_source = path self._fd = None self._parse_source() def setDataSource(self, path: str, headers: Dict[str, str] = None) -> None: self._data_source = path self._fd = None self._headers = headers or {} self._parse_source() def setDataSource(self, fd: int) -> None: self._fd = fd self._data_source = None if fd > 0: self._track_count = 1 def _parse_source(self) -> None: if self._data_source and os.path.exists(self._data_source): self._track_count = 1 def getTrackCount(self) -> int: return self._track_count def getTrackFormat(self, index: int) -> Optional["MediaFormat"]: return self._cached_info.get(index) def selectTrack(self, index: int) -> None: with self._lock: if 0 <= index < self._track_count: self._selected_tracks.add(index) def unselectTrack(self, index: int) -> None: with self._lock: self._selected_tracks.discard(index) def getSampleTrackIndex(self) -> int: return self._sample_track_index def readSampleData(self, byte_buf: Any, offset: int) -> int: return -1 def getSampleTime(self) -> int: return self._sample_time def getSampleFlags(self) -> int: return self._sample_flags def getSampleSize(self) -> int: return self._sample_size def advance(self) -> bool: return False def seekTo(self, time_us: int, mode: int = SEEK_TO_CLOSEST_SYNC) -> None: self._sample_time = time_us def release(self) -> None: self._released = True self._cached_info.clear() self._selected_tracks.clear() def getCachedDuration(self) -> int: return -1 def hasCacheReachedEndOfStream(self) -> bool: return False def getSampleCryptoInfo(self, info: CryptoInfo) -> bool: return False def toString(self) -> str: return (f"MediaExtractor(source={self._data_source}, " f"tracks={self._track_count})") # ═══════════════════════════════════════════════════════════════ # MediaMuxer # ═══════════════════════════════════════════════════════════════ class MediaMuxer(Stub): """MediaMuxer — 媒体复用器""" OUTPUT_FORMAT_MPEG4 = 0 OUTPUT_FORMAT_WEBM = 1 OUTPUT_FORMAT_3GPP = 2 OUTPUT_FORMAT_HEIF = 3 OUTPUT_FORMAT_OGG = 4 def __init__(self, path: str, format: int = OUTPUT_FORMAT_MPEG4): super().__init__() self._path = path self._format = format self._started = False self._track_count = 0 self._tracks: Dict[int, "MediaFormat"] = {} self._next_track_index = 0 self._released = False def addTrack(self, format: "MediaFormat") -> int: idx = self._next_track_index self._next_track_index += 1 self._tracks[idx] = format self._track_count = len(self._tracks) return idx def start(self) -> None: if self._started: return self._started = True def stop(self) -> None: self._started = False def release(self) -> None: self._released = True self._tracks.clear() self._started = False def writeSampleData(self, track_index: int, byte_buf: Any, offset: int, buffer_info: CodecBufferInfo) -> None: pass def setOrientationHint(self, degrees: int) -> None: pass def setLocation(self, latitude: float, longitude: float) -> None: pass def getTrackCount(self) -> int: return self._track_count def getOutputFormat(self) -> int: return self._format def getPath(self) -> str: return self._path def isStarted(self) -> bool: return self._started def toString(self) -> str: return (f"MediaMuxer(path={self._path}, format={self._format}, " f"tracks={self._track_count})") # ═══════════════════════════════════════════════════════════════ # MediaFormat # ═══════════════════════════════════════════════════════════════ class MediaFormat(Stub): """MediaFormat — 媒体格式""" # MIME 类型 MIMETYPE_VIDEO_AV1 = MIMETYPE_VIDEO_AV1 MIMETYPE_VIDEO_VP9 = MIMETYPE_VIDEO_VP9 MIMETYPE_VIDEO_VP8 = MIMETYPE_VIDEO_VP8 MIMETYPE_VIDEO_H265 = MIMETYPE_VIDEO_H265 MIMETYPE_VIDEO_H264 = MIMETYPE_VIDEO_H264 MIMETYPE_VIDEO_MPEG2 = MIMETYPE_VIDEO_MPEG2 MIMETYPE_VIDEO_MPEG4 = MIMETYPE_VIDEO_MPEG4 MIMETYPE_VIDEO_VC1 = MIMETYPE_VIDEO_VC1 MIMETYPE_VIDEO_DIVX = MIMETYPE_VIDEO_DIVX MIMETYPE_VIDEO_DIVX3 = MIMETYPE_VIDEO_DIVX3 MIMETYPE_VIDEO_FLV = MIMETYPE_VIDEO_FLV MIMETYPE_VIDEO_H263 = MIMETYPE_VIDEO_H263 MIMETYPE_VIDEO_RAW = MIMETYPE_VIDEO_RAW MIMETYPE_VIDEO_DOLBY_VISION = MIMETYPE_VIDEO_DOLBY_VISION MIMETYPE_VIDEO_SCRAMBLED = MIMETYPE_VIDEO_SCRAMBLED MIMETYPE_AUDIO_AAC = MIMETYPE_AUDIO_AAC MIMETYPE_AUDIO_MP3 = MIMETYPE_AUDIO_MP3 MIMETYPE_AUDIO_AMR_NB = MIMETYPE_AUDIO_AMR_NB MIMETYPE_AUDIO_AMR_WB = MIMETYPE_AUDIO_AMR_WB MIMETYPE_AUDIO_FLAC = MIMETYPE_AUDIO_FLAC MIMETYPE_AUDIO_VORBIS = MIMETYPE_AUDIO_VORBIS MIMETYPE_AUDIO_OPUS = MIMETYPE_AUDIO_OPUS MIMETYPE_AUDIO_RAW = MIMETYPE_AUDIO_RAW MIMETYPE_AUDIO_ALAC = MIMETYPE_AUDIO_ALAC MIMETYPE_AUDIO_AC3 = MIMETYPE_AUDIO_AC3 MIMETYPE_AUDIO_EAC3 = MIMETYPE_AUDIO_EAC3 MIMETYPE_TEXT_VTT = MIMETYPE_TEXT_VTT MIMETYPE_TEXT_CEA_608 = MIMETYPE_TEXT_CEA_608 MIMETYPE_TEXT_CEA_708 = MIMETYPE_TEXT_CEA_708 MIMETYPE_SUBTITLE_SRT = MIMETYPE_SUBTITLE_SRT MIMETYPE_SUBTITLE_TTML = MIMETYPE_SUBTITLE_TTML # KEY 常量 KEY_MIME = KEY_MIME KEY_WIDTH = KEY_WIDTH KEY_HEIGHT = KEY_HEIGHT KEY_FRAME_RATE = KEY_FRAME_RATE KEY_BIT_RATE = KEY_BIT_RATE KEY_COLOR_FORMAT = KEY_COLOR_FORMAT KEY_I_FRAME_INTERVAL = KEY_I_FRAME_INTERVAL KEY_CHANNEL_COUNT = KEY_CHANNEL_COUNT KEY_SAMPLE_RATE = KEY_SAMPLE_RATE KEY_MAX_INPUT_SIZE = KEY_MAX_INPUT_SIZE KEY_DURATION = KEY_DURATION KEY_IS_ADTS = KEY_IS_ADTS KEY_CHANNEL_MASK = KEY_CHANNEL_MASK KEY_ENCODING = KEY_ENCODING KEY_PROFILE = KEY_PROFILE KEY_LEVEL = KEY_LEVEL KEY_LANGUAGE = KEY_LANGUAGE KEY_PRIORITY = KEY_PRIORITY KEY_OPERATING_RATE = KEY_OPERATING_RATE KEY_INTRA_REFRESH_PERIOD = KEY_INTRA_REFRESH_PERIOD KEY_LATENCY = KEY_LATENCY KEY_ROTATION = KEY_ROTATION KEY_CAPTURE_RATE = KEY_CAPTURE_RATE KEY_QUALITY = KEY_QUALITY KEY_MAX_BIT_RATE = KEY_MAX_BIT_RATE KEY_FLAC_COMPRESSION_LEVEL = KEY_FLAC_COMPRESSION_LEVEL KEY_TRACK_ID = KEY_TRACK_ID KEY_IS_AUTOSELECT = KEY_IS_AUTOSELECT KEY_IS_DEFAULT = KEY_IS_DEFAULT KEY_IS_FORCED_SUBTITLE = KEY_IS_FORCED_SUBTITLE # 色彩格式 COLOR_FormatUnsupported = COLOR_FormatUnsupported COLOR_FormatYUV420Planar = COLOR_FormatYUV420Planar COLOR_FormatYUV420SemiPlanar = COLOR_FormatYUV420SemiPlanar COLOR_FormatYUV420Flexible = COLOR_FormatYUV420Flexible COLOR_FormatSurface = COLOR_FormatSurface COLOR_Format32BitARGB8888 = COLOR_Format32BitARGB8888 def __init__(self): super().__init__() self._properties: Dict[str, Any] = {} @staticmethod def createVideoFormat(mime: str, width: int, height: int) -> "MediaFormat": fmt = MediaFormat() fmt.setString(KEY_MIME, mime) fmt.setInteger(KEY_WIDTH, width) fmt.setInteger(KEY_HEIGHT, height) return fmt @staticmethod def createAudioFormat(mime: str, sample_rate: int, channel_count: int) -> "MediaFormat": fmt = MediaFormat() fmt.setString(KEY_MIME, mime) fmt.setInteger(KEY_SAMPLE_RATE, sample_rate) fmt.setInteger(KEY_CHANNEL_COUNT, channel_count) return fmt @staticmethod def createSubtitleFormat(mime: str, language: str) -> "MediaFormat": fmt = MediaFormat() fmt.setString(KEY_MIME, mime) fmt.setString(KEY_LANGUAGE, language) return fmt def containsKey(self, key: str) -> bool: return key in self._properties def getInteger(self, key: str, default_value: int = 0) -> int: val = self._properties.get(key, default_value) return int(val) if isinstance(val, (int, float)) else default_value def getFloat(self, key: str, default_value: float = 0.0) -> float: val = self._properties.get(key, default_value) return float(val) if isinstance(val, (int, float)) else default_value def getLong(self, key: str, default_value: int = 0) -> int: return self.getInteger(key, default_value) def getString(self, key: str, default_value: Optional[str] = None) -> Optional[str]: val = self._properties.get(key, default_value) return str(val) if val is not None else None def getByteBuffer(self, key: str) -> Optional[bytes]: val = self._properties.get(key) if isinstance(val, (bytes, bytearray)): return bytes(val) return None def setInteger(self, key: str, value: int) -> None: self._properties[key] = value def setFloat(self, key: str, value: float) -> None: self._properties[key] = value def setLong(self, key: str, value: int) -> None: self._properties[key] = value def setString(self, key: str, value: str) -> None: self._properties[key] = value def setByteBuffer(self, key: str, buf: bytes) -> None: self._properties[key] = buf def getMap(self) -> Dict[str, Any]: return dict(self._properties) def toString(self) -> str: parts = [f"{k}={v}" for k, v in self._properties.items()] return f"MediaFormat({', '.join(parts)})" # ═══════════════════════════════════════════════════════════════ # MediaCrypto # ═══════════════════════════════════════════════════════════════ class MediaCrypto(Stub): """MediaCrypto — 媒体加密""" def __init__(self, uuid: str = ""): super().__init__() self._uuid = uuid self._init_data: Optional[bytes] = None def requiresSecureDecoderComponent(self, mime: str) -> bool: return False def setMediaDrmSession(self, session_id: bytes) -> None: pass def getUUID(self) -> str: return self._uuid def toString(self) -> str: return f"MediaCrypto(uuid={self._uuid})" # ═══════════════════════════════════════════════════════════════ # MediaDrm # ═══════════════════════════════════════════════════════════════ class MediaDrm(Stub): """MediaDrm — 数字版权管理""" KEY_TYPE_STREAMING = 1 KEY_TYPE_OFFLINE = 2 KEY_TYPE_RELEASE = 3 EVENT_PROVISION_REQUIRED = 1 EVENT_KEY_REQUIRED = 2 EVENT_KEY_EXPIRED = 3 EVENT_VENDOR_DEFINED = 4 EVENT_SESSION_RECLAIMED = 5 KEY_EXPIRED = 0 KEY_STATUS_OK = 1 KEY_STATUS_PENDING = 2 KEY_STATUS_USABLE = 3 KEY_STATUS_USABLE_IN_FUTURE = 4 def __init__(self, uuid: str = ""): super().__init__() self._uuid = uuid self._sessions: Dict[str, bytes] = {} self._event_listener: Optional[Callable] = None self._expiration_update_listener: Optional[Callable] = None self._key_status_listener: Optional[Callable] = None def openSession(self) -> Optional[bytes]: import os session_id = os.urandom(16) self._sessions[session_id.hex()] = session_id return session_id def closeSession(self, session_id: bytes) -> None: key = session_id.hex() if isinstance(session_id, bytes) else session_id self._sessions.pop(key, None) def getKeyRequest(self, session_id: bytes, init_data: bytes, mime_type: str, key_type: int, optional_parameters: Optional[Dict[str, str]] = None) -> Any: return None def provideKeyResponse(self, session_id: bytes, response: bytes) -> Optional[bytes]: return None def restoreKeys(self, session_id: bytes, key_set_id: bytes) -> None: pass def removeKeys(self, session_id: bytes) -> None: pass def queryKeyStatus(self, session_id: bytes) -> Optional[Dict[str, str]]: return {"status": "usable"} def getProvisionRequest(self) -> Optional[Tuple[bytes, str]]: return None def provideProvisionResponse(self, response: bytes) -> None: pass def getSecureStops(self) -> List[bytes]: return [] def getSecureStop(self, ssid: bytes) -> Optional[bytes]: return None def releaseSecureStops(self, ssid: bytes) -> None: pass def releaseAllSecureStops(self) -> None: pass def getPropertyString(self, name: str) -> str: return "" def setPropertyString(self, name: str, value: str) -> None: pass def getPropertyByteArray(self, name: str) -> Optional[bytes]: return None def setPropertyByteArray(self, name: str, value: bytes) -> None: pass def getMetrics(self) -> Dict[str, Any]: return {} def getMaxSessionCount(self) -> int: return 128 def getOpenSessionCount(self) -> int: return len(self._sessions) def getTotalSessionCount(self) -> int: return len(self._sessions) def release(self) -> None: self._sessions.clear() @staticmethod def isCryptoSchemeSupported(uuid: str) -> bool: return False def toString(self) -> str: return f"MediaDrm(uuid={self._uuid})" # ═══════════════════════════════════════════════════════════════ # MediaDescription / MediaMetadata # ═══════════════════════════════════════════════════════════════ class MediaDescription(Stub): """MediaDescription — 媒体描述""" BT_FOLDER_TYPE_ALBUMS = 1 BT_FOLDER_TYPE_ARTISTS = 2 BT_FOLDER_TYPE_GENRES = 3 BT_FOLDER_TYPE_PLAYLISTS = 4 BT_FOLDER_TYPE_YEARS = 5 EXTRA_BT_FOLDER_TYPE = "android.media.extra.BT_FOLDER_TYPE" EXTRA_DOWNLOAD_STATUS = "android.media.extra.DOWNLOAD_STATUS" def __init__(self): super().__init__() self._media_id: str = "" self._title: Optional[str] = None self._subtitle: Optional[str] = None self._description: Optional[str] = None self._icon_uri: Optional[str] = None self._extras: Dict[str, Any] = {} self._media_uri: Optional[str] = None self._flags: int = 0 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 getIconUri(self) -> Optional[str]: return self._icon_uri def getExtras(self) -> Optional[Dict[str, Any]]: return dict(self._extras) if self._extras else None def getMediaUri(self) -> Optional[str]: return self._media_uri def toString(self) -> str: return (f"MediaDescription(id={self._media_id}, " f"title={self._title})") class MediaMetadata(Stub): """MediaMetadata — 媒体元数据""" METADATA_KEY_ALBUM = "android.media.metadata.ALBUM" METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART" METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST" METADATA_KEY_ART = "android.media.metadata.ART" METADATA_KEY_ARTIST = "android.media.metadata.ARTIST" METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR" METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION" METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER" METADATA_KEY_DATE = "android.media.metadata.DATE" METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER" METADATA_KEY_DURATION = "android.media.metadata.DURATION" METADATA_KEY_GENRE = "android.media.metadata.GENRE" METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS" METADATA_KEY_RATING = "android.media.metadata.RATING" METADATA_KEY_TITLE = "android.media.metadata.TITLE" METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER" METADATA_KEY_WRITER = "android.media.metadata.WRITER" METADATA_KEY_YEAR = "android.media.metadata.YEAR" METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID" METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE" METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE" METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION" METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON" METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING" def __init__(self): super().__init__() self._values: Dict[str, Any] = {} def containsKey(self, key: str) -> bool: return key in self._values def getString(self, key: str) -> Optional[str]: val = self._values.get(key) return str(val) if val is not None else None def getLong(self, key: str) -> int: return int(self._values.get(key, 0)) def getFloat(self, key: str) -> float: return float(self._values.get(key, 0.0)) def getBitmap(self, key: str) -> Optional[Any]: return None def putString(self, key: str, value: str) -> None: self._values[key] = value def putLong(self, key: str, value: int) -> None: self._values[key] = value def putFloat(self, key: str, value: float) -> None: self._values[key] = value def putBitmap(self, key: str, bitmap: Any) -> None: pass def size(self) -> int: return len(self._values) def keySet(self) -> Set[str]: return set(self._values.keys()) def toString(self) -> str: return f"MediaMetadata(entries={len(self._values)})" # ═══════════════════════════════════════════════════════════════ # MediaMetadataRetriever # ═══════════════════════════════════════════════════════════════ class MediaMetadataRetriever(Stub): """MediaMetadataRetriever — 媒体元数据检索器""" OPTION_PREVIOUS_SYNC = 0 OPTION_NEXT_SYNC = 1 OPTION_CLOSEST_SYNC = 2 OPTION_CLOSEST = 3 METADATA_KEY_ALBUM = 1 METADATA_KEY_ALBUM_ARTIST = 13 METADATA_KEY_ARTIST = 2 METADATA_KEY_AUTHOR = 3 METADATA_KEY_BITRATE = 20 METADATA_KEY_CD_TRACK_NUMBER = 0 METADATA_KEY_COMPILATION = 15 METADATA_KEY_COMPOSER = 4 METADATA_KEY_DATE = 5 METADATA_KEY_DISC_NUMBER = 14 METADATA_KEY_DURATION = 9 METADATA_KEY_GENRE = 6 METADATA_KEY_HAS_AUDIO = 16 METADATA_KEY_HAS_VIDEO = 17 METADATA_KEY_IS_DRM = 22 METADATA_KEY_LOCATION = 23 METADATA_KEY_MIMETYPE = 12 METADATA_KEY_NUM_TRACKS = 10 METADATA_KEY_TITLE = 7 METADATA_KEY_VIDEO_HEIGHT = 19 METADATA_KEY_VIDEO_WIDTH = 18 METADATA_KEY_WRITER = 11 METADATA_KEY_YEAR = 8 def __init__(self): super().__init__() self._data_source: Optional[str] = None self._extracted: Dict[int, str] = {} self._released = False def setDataSource(self, path: str) -> None: self._data_source = path self._extract() def setDataSource(self, path: str, headers: Dict[str, str]) -> None: self._data_source = path self._headers = headers self._extract() def setDataSource(self, uri: Any, headers: Dict[str, str]) -> None: self._data_source = str(uri) self._extract() def setDataSource(self, fd: int) -> None: self._data_source = f"fd:{fd}" self._extract() def _extract(self) -> None: if self._data_source and os.path.exists(str(self._data_source)): self._extracted[METADATA_KEY_HAS_AUDIO] = "yes" self._extracted[METADATA_KEY_HAS_VIDEO] = "yes" def extractMetadata(self, key_code: int) -> Optional[str]: return self._extracted.get(key_code) def getEmbeddedPicture(self) -> Optional[bytes]: return None def getFrameAtTime(self, time_us: int = -1, option: int = OPTION_CLOSEST_SYNC) -> Optional[Any]: return None def getScaledFrameAtTime(self, time_us: int, option: int, width: int, height: int) -> Optional[Any]: return None def getPrimaryImage(self) -> Optional[Any]: return None def release(self) -> None: self._released = True self._extracted.clear() def toString(self) -> str: return f"MediaMetadataRetriever(source={self._data_source})" # ═══════════════════════════════════════════════════════════════ # MediaPlayer 扩展 # ═══════════════════════════════════════════════════════════════ class MediaPlayer(Stub): """MediaPlayer — 媒体播放器(扩展)""" MEDIA_ERROR_UNKNOWN = 1 MEDIA_ERROR_SERVER_DIED = 100 MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200 MEDIA_ERROR_IO = -1004 MEDIA_ERROR_MALFORMED = -1007 MEDIA_ERROR_TIMED_OUT = -110 MEDIA_ERROR_UNSUPPORTED = -1010 MEDIA_INFO_UNKNOWN = 1 MEDIA_INFO_VIDEO_TRACK_LAGGING = 700 MEDIA_INFO_VIDEO_RENDERING_START = 3 MEDIA_INFO_BUFFERING_START = 701 MEDIA_INFO_BUFFERING_END = 702 MEDIA_INFO_BAD_INTERLEAVING = 800 MEDIA_INFO_NOT_SEEKABLE = 801 MEDIA_INFO_METADATA_UPDATE = 802 MEDIA_INFO_AUDIO_NOT_PLAYING = 804 MEDIA_INFO_VIDEO_NOT_PLAYING = 805 MEDIA_INFO_SUBTITLE_TIMED_OUT = 902 MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901 SEEK_CLOSEST = 3 SEEK_CLOSEST_SYNC = 2 SEEK_NEXT_SYNC = 1 SEEK_PREVIOUS_SYNC = 0 VIDEO_SCALING_MODE_SCALE_TO_FIT = 1 VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2 def __init__(self): super().__init__() self._data_source: Optional[str] = None self._isplaying = False self._isloop = False self._volume: float = 1.0 self._position: int = 0 self._duration: int = 0 self._isplayback = False self._isprepared = False self._on_completion: Optional[Callable] = None self._on_error: Optional[Callable] = None self._on_prepared: Optional[Callable] = None self._on_info: Optional[Callable] = None self._lock = threading.Lock() self._surface: Optional[Any] = None @staticmethod def create(context: Any, uri: Any) -> "MediaPlayer": mp = MediaPlayer() mp.setDataSource(str(uri)) mp.prepare() return mp def setDataSource(self, path: str) -> None: self._data_source = path def setDataSource(self, path: str, headers: Dict[str, str]) -> None: self._data_source = path def prepare(self) -> None: self._isprepared = True self._duration = 10000 if self._on_prepared: self._on_prepared(self) def prepareAsync(self) -> None: self._isprepared = True if self._on_prepared: self._on_prepared(self) def start(self) -> None: self._isplaying = True self._isplayback = True def stop(self) -> None: self._isplaying = False self._isplayback = False self._position = 0 def pause(self) -> None: self._isplaying = False def seekTo(self, msec: int, mode: int = SEEK_CLOSEST) -> None: self._position = max(0, msec) def getCurrentPosition(self) -> int: return self._position def getDuration(self) -> int: return self._duration def isPlaying(self) -> bool: return self._isplaying def isLooping(self) -> bool: return self._isloop def setLooping(self, looping: bool) -> None: self._isloop = looping def setVolume(self, left_volume: float, right_volume: float) -> None: self._volume = (left_volume + right_volume) / 2.0 def getVolume(self) -> float: return self._volume def setSurface(self, surface: Any) -> None: self._surface = surface def setDisplay(self, holder: Any) -> None: pass def setScreenOnWhilePlaying(self, screen_on: bool) -> None: pass def setWakeMode(self, context: Any, mode: int) -> None: pass def setAudioStreamType(self, stream_type: int) -> None: pass def reset(self) -> None: self.stop() self._isprepared = False def release(self) -> None: self.reset() def getVideoWidth(self) -> int: return 0 def getVideoHeight(self) -> int: return 0 def setOnCompletionListener(self, listener: Optional[Callable]) -> None: self._on_completion = listener def setOnErrorListener(self, listener: Optional[Callable]) -> None: self._on_error = listener def setOnPreparedListener(self, listener: Optional[Callable]) -> None: self._on_prepared = listener def setOnInfoListener(self, listener: Optional[Callable]) -> None: self._on_info = listener def setOnBufferingUpdateListener(self, listener: Optional[Callable]) -> None: pass def setOnSeekCompleteListener(self, listener: Optional[Callable]) -> None: pass def setOnVideoSizeChangedListener(self, listener: Optional[Callable]) -> None: pass def attachAuxEffect(self, effect_id: int) -> None: pass def toString(self) -> str: return (f"MediaPlayer(source={self._data_source}, " f"playing={self._isplaying})") # ═══════════════════════════════════════════════════════════════ # MediaSync / MediaTimeProvider / TimedMetaData / WebVttParser # ═══════════════════════════════════════════════════════════════ class MediaSync(Stub): """MediaSync — 媒体同步器""" def __init__(self): super().__init__() self._surface: Optional[Any] = None self._playback_params: Optional[Any] = None self._callback: Optional[Callable] = None self._timestamp: int = 0 self._playback_rate: float = 1.0 def setSurface(self, surface: Any) -> None: self._surface = surface def setCallback(self, cb: Optional[Callable], handler: Any = None) -> None: self._callback = cb def getPlaybackParams(self) -> Any: return self._playback_params def setPlaybackParams(self, params: Any) -> None: self._playback_params = params def queueAudioBuffer(self, buffer_info: CodecBufferInfo, data: bytes, offset: int, size: int, presentation_time_us: int) -> None: pass def queueVideoBuffer(self, data: bytes, buffer_info: CodecBufferInfo, presentation_time_us: int) -> None: pass def render(self, presentation_time_us: int) -> bool: return True def flush(self) -> None: pass def getTimestamp(self) -> int: return self._timestamp def release(self) -> None: pass def toString(self) -> str: return f"MediaSync(rate={self._playback_rate})" class MediaTimeProvider(Stub): """MediaTimeProvider — 媒体时间提供器""" def getTimeUs(self) -> int: return int(time.time() * 1000000) class TimedMetaData(Stub): """TimedMetaData — 定时元数据""" def __init__(self, timestamp_us: int = 0, meta_data: bytes = b""): super().__init__() self._timestamp_us = timestamp_us self._meta_data = meta_data def getTimestamp(self) -> int: return self._timestamp_us def getMetaData(self) -> bytes: return self._meta_data def toString(self) -> str: return (f"TimedMetaData(timestamp={self._timestamp_us}, " f"size={len(self._meta_data)})") class WebVttParser(Stub): """WebVttParser — WebVTT 字幕解析器""" def __init__(self): super().__init__() self._cues: List["WebVttCue"] = [] def parse(self, data: str) -> List["WebVttCue"]: self._cues.clear() lines = data.split("\n") current_cue: Optional["WebVttCue"] = None for line in lines: line = line.strip() if line.startswith("WEBVTT"): continue if line == "": if current_cue is not None: self._cues.append(current_cue) current_cue = None continue if "-->" in line: parts = line.split("-->") start = self._parse_time(parts[0].strip()) end = self._parse_time(parts[1].strip()) current_cue = WebVttCue(start, end) elif current_cue is not None: current_cue.add_text(line) if current_cue is not None: self._cues.append(current_cue) return list(self._cues) def _parse_time(self, time_str: str) -> int: """将 HH:MM:SS.mmm 格式转换为微秒""" if ":" in time_str: parts = time_str.split(":") if len(parts) == 3: h, m, s = parts else: h = "0" m, s = parts sec_parts = s.split(".") seconds = int(sec_parts[0]) millis = int(sec_parts[1]) if len(sec_parts) > 1 else 0 total_ms = int(h) * 3600000 + int(m) * 60000 + seconds * 1000 + millis return total_ms * 1000 return 0 def getCues(self) -> List["WebVttCue"]: return list(self._cues) def clear(self) -> None: self._cues.clear() class WebVttCue(Stub): """WebVttCue — WebVTT 字幕提示""" def __init__(self, start_us: int = 0, end_us: int = 0, text: str = ""): super().__init__() self._start_us = start_us self._end_us = end_us self._text = text def add_text(self, text: str) -> None: if self._text: self._text += "\n" + text else: self._text = text def getStartTime(self) -> int: return self._start_us def getEndTime(self) -> int: return self._end_us def getText(self) -> str: return self._text def toString(self) -> str: return (f"WebVttCue(start={self._start_us}, " f"end={self._end_us}, text={self._text[:30]})")