Chrome · Chromium 公共管线
控制:WebMediaPlayerImpl · PipelineController
- 加载 URL 字节
MultiBufferDataSource - 浏览器侧解析容器
FFmpegDemuxer - 解码 → 视频帧 → 合成
Renderer / Decoder → VideoFrame
URL 数据进入 Chromium 的解析与解码组件。
Chrome 公共管线与本地 tizen 分支对照:URL 交给原生播放器,MSE 在 Chromium 解析后通过 Mojo 向平台提供样本。
src:网页提供媒体 URL,播放器负责获取与解析。
MSE:网页获取媒体分片,SourceBuffer 接收字节,demuxer 解析后向下游提供压缩样本。
控制:WebMediaPlayerImpl · PipelineController
MultiBufferDataSourceFFmpegDemuxerRenderer / Decoder → VideoFrameURL 数据进入 Chromium 的解析与解码组件。
控制:WebMediaPlayerImpl ⇄ Mojo ⇄ TizenRendererImpl
MediaUrlDemuxer / MediaPlayerRegistryMediaPlayerBridgeCapiTVplayer_set_uri() · player_start()主要交付 URL,由平台播放器管理媒体处理。
控制:WebMediaPlayerImpl · PipelineController
MediaSource / SourceBuffer.appendBuffer()WebSourceBufferImpl → ChunkDemuxerRenderer / Decoder → VideoFrame应用控制分片供给,后续处理仍由 Chromium 管线承接。
控制:WebMediaPlayerImpl ⇄ Mojo ⇄ TizenRendererImpl
WebSourceBufferImpl / ChunkDemuxerMojoDemuxerStreamImpl ⇄ AdapterMediaPlayerESPlusPlayerTV交付解析后的压缩样本,而不是直接把 URL 交给平台。
框图按职责与数据交接简化;控制 IPC 单独列示。Chrome 展示普通 URL / MSE 的公共实现,Decoder 由平台选择;Tizen 展示本地 tizen 分支的固定快照,其他版本与构建可不同。平台 API 以下的实现未展开。
网页负责能力选择与许可证消息交换;CDM 处理许可证和解密能力。EME 是接口规范,具体内容保护由所选 Key System 实现。
探测 Key System 与编码支持,创建 MediaKeys 并关联 video。
requestMediaKeySystemAccess() → createMediaKeys() → setMediaKeys()从 encrypted 事件取得 initData,创建 session,让 CDM 生成许可证请求。
createSession() → generateRequest()处理 session 的 message 事件,将请求交给许可证服务器,再把响应交回 CDM。
message → 许可证服务 → session.update()观察 keystatuseschange;所需密钥可用后,媒体路径可继续解密、解码与呈现。
keyStatuses → CDM / 平台安全媒体路径网页 ⇄ EME / CDM
网页 ⇄ 许可证服务器
加密分片 → demux / 压缩样本 → 解密 → 解码 → 呈现
初始化数据、KID 与许可证消息不等于内容密钥。网页转发消息,不直接获得商业 DRM 明文密钥;具体解密可以由 CDM 或平台安全路径完成。
固定到已核对的本地分支提交,不宣称已同步远端。Chrome 参考图使用独立源码快照。
从函数入口展开。箭头上的标签区分调用、任务、Mojo IPC 与回调;初始化与数据读取分阶段展示。
URL 与 MSE 共用控制桥接;资源分别以 URL 参数或 DemuxerStream endpoints 交接。
WebMediaPlayerImpl::Load() → DoLoad()加载入口;后续创建 demuxer 和启动 pipeline。
737: }
738:
739: WebMediaPlayer::LoadTiming WebMediaPlayerImpl::Load(
740: LoadType load_type,
741: const WebMediaPlayerSource& source,
742: CorsMode cors_mode,
743: bool is_cache_disabled) {
744: // Only URL or MSE blob URL is supported.
745: DCHECK(source.IsURL());
746: WebURL url = source.GetAsURL();
747: DVLOG(1) << __func__ << "(" << load_type << ", " << GURL(url) << ", "WebMediaPlayerImpl::StartPipeline()调用 DemuxerManager::CreateDemuxer();PipelineController 管理状态。
3821: }
3822:
3823: void WebMediaPlayerImpl::StartPipeline() {
3824: DCHECK(main_task_runner_->BelongsToCurrentThread());
3825:
3826: vfc_task_runner_->PostTask(
3827: FROM_HERE,
3828: base::BindOnce(&VideoFrameCompositor::SetOnNewProcessedFrameCallback,
3829: base::Unretained(compositor_.get()),
3830: base::BindPostTaskToCurrentDefault(base::BindOnce(
3831: &WebMediaPlayerImpl::OnFirstFrame, weak_this_))));MediaPlayerRendererClientFactory::CreateRenderer()建立 Mojo renderer 及双向扩展接口;由 MediaFactory 选择此工厂。
21:
22: std::unique_ptr<media::Renderer>
23: MediaPlayerRendererClientFactory::CreateRenderer(
24: const scoped_refptr<base::SequencedTaskRunner>& media_task_runner,
25: const scoped_refptr<base::TaskRunner>& worker_task_runner,
26: media::AudioRendererSink* audio_renderer_sink,
27: media::VideoRendererSink* video_renderer_sink,
28: media::RequestOverlayInfoCB request_overlay_info_cb,
29: const gfx::ColorSpace& target_color_space) {
30: // Used to send messages from the MPRC (Renderer process), to the MPR (Browser
31: // process). The |renderer_extension_request| will be bound inMediaInterfaceProxy::CreateMediaPlayerRenderer()browser 创建 TizenRendererImpl,交给 MojoRendererService 托管。
433: media::MojoRendererService::Create(
434: nullptr,
435: std::make_unique<content::TizenRendererImpl>(
436: render_frame_host().GetProcess()->GetID(),
437: render_frame_host().GetRoutingID(),
438: WebContents::FromRenderFrameHost(&render_frame_host()),
439: std::move(renderer_extension_receiver),
440: std::move(client_extension_remote)),
441: std::move(receiver));
442: }
443: #endifMediaPlayerRendererClient::Initialize() → MojoRendererWrapper::Initialize()renderer 侧包装层将初始化转给 MojoRenderer。
44: }
45:
46: void MediaPlayerRendererClient::Initialize(
47: media::MediaResource* media_resource,
48: media::RendererClient* client,
49: media::PipelineStatusCallback init_cb) {
50: DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
51: DCHECK(!init_cb_);
52:
53: LOG(INFO) << "MediaPlayerRendererClient Initialize:" << (void*)this;
54: MojoRenderer::InitializeRendererFromUrl() / InitializeRendererFromStreams()URL 发送 MediaUrlParams;stream 绑定 MojoDemuxerStreamImpl 并发送 endpoints。
61: init_cb_ = std::move(init_cb);
62:
63: switch (media_resource_->GetType()) {
64: case MediaResource::Type::kStream:
65: InitializeRendererFromStreams(client);
66: break;
67: case MediaResource::Type::KUrl:
68: InitializeRendererFromUrl(client);
69: break;
70: }
71: }MojoRendererService::Initialize() / OnAllStreamsReady()URL 重建 MediaUrlDemuxer;stream 建立 MediaResourceShim,流就绪后调用 renderer。
54: MojoRendererService::~MojoRendererService() = default;
55:
56: void MojoRendererService::Initialize(
57: mojo::PendingAssociatedRemote<mojom::RendererClient> client,
58: std::optional<std::vector<mojo::PendingRemote<mojom::DemuxerStream>>>
59: streams,
60: mojom::MediaUrlParamsPtr media_url_params,
61: InitializeCallback callback) {
62: LOG(INFO) << "MojoRendererService Initialize:" << (void*)this;
63: DCHECK_EQ(state_, STATE_UNINITIALIZED);
64: TizenRendererImpl::Initialize() → MediaPlayerRegistry::CreateMediaPlayer()按资源类型选择平台工厂,创建并初始化原生播放器。
200:
201: // Create a media player and get player id for the created media player.
202: media_player_ = media::MediaPlayerRegistry::GetInstance()->CreateMediaPlayer(
203: media_resource_, user_agent, client_, volume_, player_id_);
204:
205: #if BUILDFLAG(IS_TIZEN_TV)
206: client_extension_->UpdateVideoId(player_id_);
207: media_player_->SetContentMimeType(mime_type_);
208: #endif
209:
210: if (!media_player_->CreatePlayer(player_id_)) {展示 Tizen TV 的普通平台 URL 路径;音频、upstream 模式和 HbbTV 等条件分支另列。
ContentRendererClient::OverrideDemuxerForUrl()TV / 非 upstream 模式下可用 MediaUrlDemuxer;MSE 或音频条件直接返回 nullptr。
186: #endif
187:
188: std::unique_ptr<media::Demuxer> ContentRendererClient::OverrideDemuxerForUrl(
189: RenderFrame* render_frame,
190: const GURL& url,
191: #if BUILDFLAG(IS_TIZEN_TV)
192: bool is_media_source,
193: #endif
194: scoped_refptr<base::SequencedTaskRunner> task_runner) {
195: #if defined(TIZEN_MULTIMEDIA) && BUILDFLAG(IS_TIZEN_TV)
196: if (media::IsUpstreamArchitectureEnabled()) {DemuxerManager::CreateDemuxer()管理 URL demuxer / override;这里的 MediaUrlDemuxer 是 URL 资源桥,不负责拆出平台媒体包。
391: }
392:
393: PipelineStatus DemuxerManager::CreateDemuxer(
394: bool load_media_source,
395: #if defined(SAMSUNG_ELEMENTARY_MEDIA_STREAM_SOURCE)
396: bool load_elementary_media_stream_source,
397: #endif // defined(SAMSUNG_ELEMENTARY_MEDIA_STREAM_SOURCE)
398: DataSource::Preload preload,
399: bool needs_first_frame,
400: DemuxerManager::DemuxerCreatedCB on_demuxer_created,
401: base::flat_map<std::string, std::string> headers) {MojoRenderer::InitializeRendererFromUrl()将媒体 URL 与相关请求参数发送到 browser;共用初始化链见上一节。
107: }
108:
109: void MojoRenderer::InitializeRendererFromUrl(media::RendererClient* client) {
110: DVLOG(2) << __func__;
111: DCHECK(task_runner_->RunsTasksInCurrentSequence());
112:
113: BindRemoteRendererIfNeeded();
114:
115: const MediaUrlParams& url_params = media_resource_->GetMediaUrlParams();
116:
117: // Using base::Unretained(this) is safe because |this| ownsMediaPlayerRegistry::CreateMediaPlayer()URL 默认选择 BridgeCapi 工厂;启用 PlusPlayer 的 HbbTV 构建存在独立分支。
61: }
62:
63: std::unique_ptr<MediaPlayerTizen> MediaPlayerRegistry::CreateMediaPlayer(
64: MediaResource* media_resource,
65: const std::string& user_agent,
66: media::RendererClient* client,
67: double volume,
68: int& player_id) {
69: LOG(INFO) << __func__;
70: std::unique_ptr<MediaPlayerFactory> factory;
71: if (media_resource->GetType() == MediaResource::Type::kStream) {MediaPlayerBridgeCapiFactory::Create()IS_TIZEN_TV 创建 MediaPlayerBridgeCapiTV,其他构建使用 common 类。
26: << media_resource->GetMediaUrlParams().media_url.spec()
27: << ", clean url: " << clean_url.spec();
28: return std::make_unique<MediaPlayerBridgeCapiTV>(clean_url, user_agent,
29: volume);
30: #else
31: return std::make_unique<MediaPlayerBridgeCapi>(
32: media_resource->GetMediaUrlParams().media_url, user_agent, volume);
33: #endif
34: }
35:
36: } // namespace mediaMediaPlayerBridgeCapi::Prepare() → player_set_uri()普通 TV URL 在 Prepare 中交给平台;HbbTV 可使用 TV 子类的 URL 配置分支。
240: LOG(INFO) << "Blob URL detected, using temp file: " << url_str;
241: }
242: player_set_uri(player_, url_str.c_str());
243: }
244: #else
245: player_set_uri(player_, url_.spec().c_str());
246: #endif
247:
248: #if !defined(EWK_BRINGUP)
249: player_set_sound_type(player_, SOUND_TYPE_MEDIA);
250: #endifPrepare() → player_prepare_async()准备过程异步完成;不是配置 URI 后立即同步播放。
346: LOG_ID(INFO, player_id_) << "Call player_prepare_async";
347:
348: int ret = player_prepare_async(player_, PlayerPreparedCb, this);
349: if (ret != PLAYER_ERROR_NONE) {
350: is_preparing_ = false;
351: OnHandlePlayerError(ret, FROM_HERE);
352: return false;
353: }
354:
355: is_preparing_ = true;
356: return true;MediaPlayerBridgeCapi::Play() → player_start()在准备、状态与播放请求满足条件时启动平台播放。
486: }
487:
488: bool MediaPlayerBridgeCapi::Play() {
489: LOG_ID(INFO, player_id_) << "(" << static_cast<void*>(this) << ") "
490: << __func__;
491:
492: if (IsPlayerSuspended()) {
493: LOG(INFO) << "(" << static_cast<void*>(this) << ") " << __func__
494: << " Player is suspended...";
495: is_play_pending_ = true;
496: delayed_player_state_ = PLAYER_STATE_DELAYED_PLAY;此链在 renderer 处理分片并形成压缩样本。HEVC 是 codec 条件分支,不代表所有 MSE 内容都经过 HEVC。
SourceBuffer.appendBuffer() → AppendBufferInternal()检查追加状态并接收 JS 提供的媒体字节。
600: }
601:
602: void SourceBuffer::appendBuffer(DOMArrayBuffer* data,
603: ExceptionState& exception_state) {
604: DVLOG(2) << __func__ << " this=" << this << " size=" << data->ByteLength();
605: // Section 3.2 appendBuffer()
606: // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-appendBuffer-void-ArrayBufferView-data
607: AppendBufferInternal(data->ByteSpan(), exception_state);
608: }
609:
610: void SourceBuffer::appendBuffer(NotShared<DOMArrayBufferView> data,WebSourceBufferImpl::AppendToParseBuffer()先把追加字节交给 ChunkDemuxer 的解析缓冲。
135: }
136:
137: bool WebSourceBufferImpl::AppendToParseBuffer(
138: base::span<const unsigned char> data) {
139: return demuxer_->AppendToParseBuffer(id_, data);
140: }
141:
142: media::StreamParser::ParseStatus WebSourceBufferImpl::RunSegmentParserLoop(
143: double* timestamp_offset) {
144: base::TimeDelta old_offset = timestamp_offset_;
145: media::StreamParser::ParseStatus parse_result =SourceBuffer::AppendBufferAsyncPart_Locked()排队任务运行 segment parser loop;可能分多轮处理。
2128: }
2129:
2130: void SourceBuffer::AppendBufferAsyncPart_Locked(
2131: MediaSourceAttachmentSupplement::ExclusiveKey pass_key) {
2132: DCHECK(source_);
2133: source_->AssertAttachmentsMutexHeldIfCrossThreadForDebugging();
2134: DCHECK(updating_);
2135:
2136: // Section 3.5.4 Buffer Append Algorithm
2137: // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-buffer-append
2138: WebSourceBufferImpl::RunSegmentParserLoop() → ChunkDemuxer::RunSegmentParserLoop()把 append window 与 timestamp offset 交给 demuxer。
140: }
141:
142: media::StreamParser::ParseStatus WebSourceBufferImpl::RunSegmentParserLoop(
143: double* timestamp_offset) {
144: base::TimeDelta old_offset = timestamp_offset_;
145: media::StreamParser::ParseStatus parse_result =
146: demuxer_->RunSegmentParserLoop(id_, append_window_start_,
147: append_window_end_, ×tamp_offset_);
148:
149: // Coded frame processing may update the timestamp offset. If the caller
150: // provides a non-nullptr |timestamp_offset| and frame processing changes theSourceBufferState::RunSegmentParserLoop() → MP4StreamParser::Parse()MP4 parser 分析 box、track run 与媒体样本。
232: // and timestamp offset pointer. See http://crbug.com/351454.
233: StreamParser::ParseStatus result =
234: stream_parser_->Parse(StreamParser::kMaxPendingBytesPerParse);
235:
236: if (result == StreamParser::ParseStatus::kFailed) {
237: MEDIA_LOG(ERROR, media_log_)
238: << __func__ << ": stream parsing failed. append_window_start="
239: << append_window_start.InSecondsF()
240: << " append_window_end=" << append_window_end.InSecondsF();
241: }
242: MP4StreamParser::EnqueueSample() → TrackRunIterator::GetDecryptConfig()加密样本取得 KID / IV / subsamples;样本输出阶段附加时间戳与配置。
1246: std::vector<SubsampleEntry> subsamples;
1247: if (runs_->is_encrypted()) {
1248: decrypt_config = runs_->GetDecryptConfig();
1249: if (!decrypt_config)
1250: return ParseResult::kError;
1251: subsamples = decrypt_config->subsamples();
1252: }
1253:
1254: // This may change if analysis results indicate runs_->is_keyframe() is
1255: // opposite of what the coded frame contains.
1256: bool is_keyframe = runs_->is_keyframe();HEVCBitstreamConverter::ConvertAndAnalyzeFrame()长度前缀转 Annex B,分析关键帧并按需插入参数集;同步维护 subsamples。
647: }
648:
649: bool HEVCBitstreamConverter::ConvertAndAnalyzeFrame(
650: std::vector<uint8_t>* frame_buf,
651: bool is_keyframe,
652: std::vector<SubsampleEntry>* subsamples,
653: AnalysisResult* analysis_result) const {
654: RCHECK(AVC::ConvertFrameToAnnexB(hevc_config_->lengthSizeMinusOne + 1,
655: frame_buf, subsamples));
656: // |is_keyframe| may be incorrect. Analyze the frame to see if it is a
657: // keyframe. |is_keyframe| will be used if the analysis is inconclusive.HEVC::AnalyzeAnnexB() / InsertParamSetsAnnexB() → H265NaluParser分析与参数集插入路径调用 NAL parser,属于解密前的码流结构处理。
459: // units
460: while (true) {
461: H265NaluParser::Result h265_result = parser.AdvanceToNextNALU(&nalu);
462: if (h265_result == H265NaluParser::kEOStream) {
463: break;
464: }
465:
466: if (h265_result != H265NaluParser::kOk) {
467: DCHECK_NE(h265_result, H265NaluParser::kUnsupportedStream)
468: << "AdvanceToNextNALU() returned kUnsupportedStream!";
469: return result;H265NaluParser::AdvanceToNextNALU()读取 NAL 头;此快照中的具体检查以代码片段为准,不套用 M94 或历史故障提交。
126: }
127:
128: H265NaluParser::Result H265NaluParser::AdvanceToNextNALU(H265NALU* nalu) {
129: off_t start_code_size;
130: off_t nalu_size_with_start_code;
131: if (!LocateNALU(&nalu_size_with_start_code, &start_code_size)) {
132: DVLOG(4) << "Could not find next NALU, bytes left in stream: "
133: << bytes_left_;
134: return kEOStream;
135: }
136: MP4StreamParser → StreamParserBuffer / buffers形成带压缩样本和元数据的输出,交给后续缓冲及异步读取;append 不直接调用平台 submit。
1408:
1409: if (decrypt_config)
1410: stream_buf->set_decrypt_config(std::move(decrypt_config));
1411:
1412: if (runs_->duration() != kNoTimestamp) {
1413: stream_buf->set_duration(runs_->duration());
1414: } else {
1415: MEDIA_LOG(ERROR, media_log_) << "Frame duration exceeds representable "
1416: << "limit";
1417: return ParseResult::kError;
1418: }读取方向为 browser → renderer,数据回传方向相反;异步回调后才进入平台样本队列。
MojoRenderer::InitializeRendererFromStreams()renderer 将本地 DemuxerStream 暴露为 Mojo 接口。
83: for (media::DemuxerStream* stream : streams) {
84: mojo::PendingRemote<mojom::DemuxerStream> stream_proxy;
85: auto mojo_stream = std::make_unique<MojoDemuxerStreamImpl>(
86: stream, stream_proxy.InitWithNewPipeAndPassReceiver());
87:
88: // Using base::Unretained(this) is safe because |this| owns |mojo_stream|,
89: // and the error handler can't be invoked once |mojo_stream| is destroyed.
90: mojo_stream->set_disconnect_handler(
91: base::BindOnce(&MojoRenderer::OnDemuxerStreamConnectionError,
92: base::Unretained(this), mojo_stream.get()));
93: MediaResourceShim → MojoDemuxerStreamAdapterbrowser 把远程 streams 包装为本地可读的 DemuxerStream。
21:
22: for (auto& s : streams) {
23: streams_.emplace_back(std::make_unique<MojoDemuxerStreamAdapter>(
24: std::move(s), base::BindOnce(&MediaResourceShim::OnStreamReady,
25: weak_factory_.GetWeakPtr())));
26: }
27: }
28:
29: MediaResourceShim::~MediaResourceShim() = default;
30:
31: std::vector<DemuxerStream*> MediaResourceShim::GetAllStreams() {MediaPlayerRegistry → MediaPlayerESPlusPlayerFactory::Create()kStream 资源选择 ESPlusPlayer,TV 构建创建 TV 子类。
19: double volume) {
20: #if BUILDFLAG(IS_TIZEN_TV)
21: return std::make_unique<MediaPlayerESPlusPlayerTV>();
22: #else
23: return std::make_unique<MediaPlayerESPlusPlayerCommon>();
24: #endif
25: }
26:
27: } // namespace mediaMediaPlayerESPlusPlayer::ReadBuffer()先消耗已有队列;检查背压、暂停、EOS 和 pending read,再请求一批样本。
996: }
997:
998: void MediaPlayerESPlusPlayer::ReadBuffer(DemuxerStream::Type type) {
999: if (!ReadFromBufferQueue(type))
1000: return;
1001:
1002: // Avoid unnecessary or redundant read requests.
1003: if (!ShouldFeed(type) || ReadRequested(type) || IsEos(type) ||
1004: (is_prepared_ && is_paused_ && !is_seeking_)) {
1005: return;
1006: }MojoDemuxerStreamAdapter::Read() ⇢ MojoDemuxerStreamImpl::Read()读取请求通过 Mojo 发往 renderer 的 stream 实现。
41:
42: read_cb_ = std::move(read_cb);
43: demuxer_stream_->Read(count,
44: base::BindOnce(&MojoDemuxerStreamAdapter::OnBufferReady,
45: weak_factory_.GetWeakPtr()));
46: }
47:
48: AudioDecoderConfig MojoDemuxerStreamAdapter::audio_decoder_config() {
49: DCHECK_EQ(type_, AUDIO);
50: return audio_config_;
51: }MojoDemuxerStreamImpl::Read() → stream_->Read()异步读取本地压缩样本,并在 OnBufferReady 中序列化返回。
53: }
54:
55: void MojoDemuxerStreamImpl::Read(uint32_t count, ReadCallback callback) {
56: DVLOG(3) << __func__ << ": count=" << count;
57: stream_->Read(
58: count, base::BindOnce(&MojoDemuxerStreamImpl::OnBufferReady,
59: weak_factory_.GetWeakPtr(), std::move(callback)));
60: }
61:
62: void MojoDemuxerStreamImpl::EnableBitstreamConverter() {
63: stream_->EnableBitstreamConverter();MojoDemuxerStreamAdapter::OnBufferReady() → OnBufferRead()通过 buffer reader 还原数据;回调完成后向平台读取者交付 buffer。
132:
133: batch_buffers_ = std::move(batch_buffers);
134: mojo_decoder_buffer_reader_->ReadDecoderBuffer(
135: std::move(batch_buffers_[0]),
136: base::BindOnce(&MojoDemuxerStreamAdapter::OnBufferRead,
137: weak_factory_.GetWeakPtr()));
138: }
139:
140: void MojoDemuxerStreamAdapter::OnBufferRead(
141: scoped_refptr<DecoderBuffer> buffer) {
142: if (!buffer) {MediaPlayerESPlusPlayer::OnBufferReady() → ReadBuffer()检查状态与 flush generation,将有效样本放入队列后继续喂入。
1044: }
1045:
1046: void MediaPlayerESPlusPlayer::OnBufferReady(
1047: uint64_t posted_flush_id,
1048: DemuxerStream::Type type,
1049: DemuxerStream::Status status,
1050: DemuxerStream::DecoderBufferVector buffers) {
1051: // Defensive check: drop buffers if player has been released.
1052: // After Release(), esplayer_ handle is closed and player state is cleaned up.
1053: // OnBufferReady may still be called by pending DemuxerStream::Read() callbacks.
1054: if (IsReleased()) {MediaPlayerESPlusPlayer::ReadFromBufferQueue() → SubmitEsPacket()提交队首样本;FULL / NOT_PREPARED 等状态保留队列并停止继续喂入。
1116: status = SubmitEosPacket(type);
1117: else
1118: status = SubmitEsPacket(type, buffer);
1119:
1120: if (status == ESPLUSPLAYER_SUBMIT_STATUS_FULL) {
1121: return false;
1122: }
1123:
1124: if ((status == ESPLUSPLAYER_SUBMIT_STATUS_NOT_PREPARED) ||
1125: (status == ESPLUSPLAYER_SUBMIT_STATUS_OUT_OF_MEMORY))
1126: return false;MediaPlayerESPlusPlayerTV::SubmitEsPacket()按 DRM / handle 分支提交 encrypted packet、trust-zone packet,或调用普通父类提交。
642: }
643:
644: esplusplayer_submit_status MediaPlayerESPlusPlayerTV::SubmitEsPacket(
645: DemuxerStream::Type type,
646: scoped_refptr<DecoderBuffer> buffer) {
647: // Defensive check: prevent submitting packets after Release() has been called.
648: // This guards against race conditions where OnBufferReady may still execute
649: // after the player has been released and esplayer_ handle has been closed.
650: if (IsReleased()) {
651: LOG_ID(WARNING, player_id_)
652: << "Player is released, skip submit "分支条件:TIZEN_MULTIMEDIA 且未启用 upstream architecture 的平台集成;TV、音频、HbbTV / PlusPlayer、EMSS 可走不同分支。平台 API 以下不推断芯片实现。MSE 的样本解析与拉取是两条异步链路,不是一条同步栈。
完整函数定位、原始片段与文件哈希 ↗Chromium 参考快照:8c7b7ff9514f38006379aa1ae3115eae9faae71c。普通 URL 图选取 FFmpeg 分支,不覆盖 HLS、override 等所有加载方式。
本地 tizen 快照:f5439772622daf5a2f513e4c305f690b9d49ba9d。chrome/VERSION:130.0.6723.116。这里只核对源码,不代表硬件运行 trace。
此版本已使用 WebMediaPlayerImpl / Mojo / TizenRendererImpl;旧 M94 的 WebMediaPlayerEfl、RendererDemuxerEfl / BrowserDemuxerEfl 不作为当前图依据。