Files
Android-pyApi/android_pyapi/android/media_async.py
T
shuiyu1123 a51854455b feat: 大规模扩充至212个模块/175K行代码 — 覆盖android/java/javax/org/dalvik全包
- 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关键字冲突
2026-06-24 14:15:56 +00:00

1621 lines
53 KiB
Python

"""
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]})")