| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/filters/demuxer_manager.h" |
| |
| #include "base/feature_list.h" |
| #include "base/functional/callback.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_util.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/task_runner.h" |
| #include "media/base/cross_origin_data_source.h" |
| #include "media/base/data_source.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/media_url_demuxer.h" |
| #include "media/filters/chunk_demuxer.h" |
| #include "media/filters/ffmpeg_demuxer.h" |
| #include "url/gurl.h" |
| |
| #if BUILDFLAG(ENABLE_HLS_DEMUXER) |
| #include "media/filters/hls_manifest_demuxer_engine.h" |
| #include "media/filters/manifest_demuxer.h" |
| #endif // BUILDFLAG(ENABLE_HLS_DEMUXER) |
| |
| namespace media { |
| |
| namespace { |
| |
| #if BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID) |
| |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class MimeType { |
| kOtherMimeType = 0, |
| kApplicationDashXml = 1, |
| kApplicationOgg = 2, |
| kApplicationMpegUrl = 3, |
| kApplicationVndAppleMpegUrl = 4, |
| kApplicationXMpegUrl = 5, |
| kAudioMpegUrl = 6, |
| kAudioXMpegUrl = 7, |
| kNonspecificAudio = 8, |
| kNonspecificImage = 9, |
| kNonspecificVideo = 10, |
| kTextVtt = 11, |
| kMaxValue = kTextVtt, // For UMA histograms. |
| }; |
| |
| MimeType TranslateMimeTypeToHistogramEnum(const base::StringPiece& mime_type) { |
| constexpr auto kCaseInsensitive = base::CompareCase::INSENSITIVE_ASCII; |
| if (base::StartsWith(mime_type, "application/dash+xml", kCaseInsensitive)) { |
| return MimeType::kApplicationDashXml; |
| } |
| if (base::StartsWith(mime_type, "application/ogg", kCaseInsensitive)) { |
| return MimeType::kApplicationOgg; |
| } |
| if (base::StartsWith(mime_type, "application/mpegurl", kCaseInsensitive)) { |
| return MimeType::kApplicationMpegUrl; |
| } |
| if (base::StartsWith(mime_type, "application/vnd.apple.mpegurl", |
| kCaseInsensitive)) { |
| return MimeType::kApplicationVndAppleMpegUrl; |
| } |
| if (base::StartsWith(mime_type, "application/x-mpegurl", kCaseInsensitive)) { |
| return MimeType::kApplicationXMpegUrl; |
| } |
| |
| if (base::StartsWith(mime_type, "audio/mpegurl", kCaseInsensitive)) { |
| return MimeType::kAudioMpegUrl; |
| } |
| if (base::StartsWith(mime_type, "audio/x-mpegurl", kCaseInsensitive)) { |
| return MimeType::kAudioXMpegUrl; |
| } |
| |
| if (base::StartsWith(mime_type, "audio/", kCaseInsensitive)) { |
| return MimeType::kNonspecificAudio; |
| } |
| if (base::StartsWith(mime_type, "image/", kCaseInsensitive)) { |
| return MimeType::kNonspecificImage; |
| } |
| if (base::StartsWith(mime_type, "video/", kCaseInsensitive)) { |
| return MimeType::kNonspecificVideo; |
| } |
| |
| if (base::StartsWith(mime_type, "text/vtt", kCaseInsensitive)) { |
| return MimeType::kTextVtt; |
| } |
| |
| return MimeType::kOtherMimeType; |
| } |
| |
| HlsFallbackImplementation SelectHlsFallbackImplementation() { |
| #if !BUILDFLAG(IS_ANDROID) |
| // TODO(crbug/1266991): This should return kBuiltinHlsPlayer when we launch |
| // on non-mobile. For now, do not support it. |
| return HlsFallbackImplementation::kNone; |
| #elif !BUILDFLAG(ENABLE_HLS_DEMUXER) |
| // Android build supporting only media player. |
| if (base::FeatureList::IsEnabled(kHlsPlayer)) { |
| return HlsFallbackImplementation::kMediaPlayer; |
| } |
| return HlsFallbackImplementation::kNone; |
| #else |
| // Android build with both builtin & media player implementations. |
| // Prefer builtin if it is enabled. |
| if (base::FeatureList::IsEnabled(kBuiltInHlsPlayer)) { |
| // TODO(crbug/1266991): This should return kBuiltinHlsPlayer when the |
| // HlsDemuxer is able to be created. |
| return HlsFallbackImplementation::kNone; |
| } |
| if (base::FeatureList::IsEnabled(kHlsPlayer)) { |
| return HlsFallbackImplementation::kMediaPlayer; |
| } |
| return HlsFallbackImplementation::kNone; |
| #endif |
| } |
| |
| #endif // BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID) |
| |
| #if BUILDFLAG(ENABLE_FFMPEG) |
| // Returns true if `url` represents (or is likely to) a local file. |
| bool IsLocalFile(const GURL& url) { |
| return url.SchemeIsFile() || url.SchemeIsFileSystem() || |
| url.SchemeIs(url::kContentScheme) || |
| url.SchemeIs(url::kContentIDScheme) || |
| url.SchemeIs("chrome-extension"); |
| } |
| #endif |
| |
| } // namespace |
| |
| DemuxerManager::DemuxerManager( |
| Client* client, |
| scoped_refptr<base::SequencedTaskRunner> media_task_runner, |
| MediaLog* log, |
| net::SiteForCookies site_for_cookies, |
| url::Origin top_frame_origin, |
| bool has_storage_access, |
| bool enable_instant_source_buffer_gc, |
| std::unique_ptr<Demuxer> demuxer_override) |
| : client_(client), |
| media_task_runner_(std::move(media_task_runner)), |
| media_log_(log->Clone()), |
| site_for_cookies_(std::move(site_for_cookies)), |
| top_frame_origin_(std::move(top_frame_origin)), |
| #if BUILDFLAG(IS_ANDROID) |
| has_storage_access_(has_storage_access), |
| #endif // BUILDFLAG(IS_ANDROID) |
| enable_instant_source_buffer_gc_(enable_instant_source_buffer_gc), |
| demuxer_override_(std::move(demuxer_override)) { |
| DCHECK(client_); |
| } |
| |
| DemuxerManager::~DemuxerManager() { |
| // ManifestDemuxer has multiple outstanding weak pointers bound to the media |
| // thread, and needs to be deleted there. |
| if (GetDemuxerType() == DemuxerType::kManifestDemuxer) { |
| media_task_runner_->DeleteSoon(FROM_HERE, std::move(demuxer_)); |
| } |
| } |
| |
| void DemuxerManager::InvalidateWeakPtrs() { |
| weak_factory_.InvalidateWeakPtrs(); |
| } |
| |
| void DemuxerManager::RestartClientForHLS() { |
| if (client_ && fallback_allowed_) { |
| client_->RestartForHls(); |
| } |
| } |
| |
| void DemuxerManager::OnPipelineError(PipelineStatus error) { |
| DCHECK(client_); |
| |
| if (!fallback_allowed_) { |
| return client_->OnError(std::move(error)); |
| } |
| |
| #if BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID) |
| bool can_play_hls = |
| SelectHlsFallbackImplementation() != HlsFallbackImplementation::kNone; |
| if (can_play_hls && error == DEMUXER_ERROR_DETECTED_HLS) { |
| PipelineStatus reset_status = |
| SelectHlsFallbackMechanism(client_->IsSecurityOriginCryptographic()); |
| if (!reset_status.is_ok()) { |
| client_->OnError(std::move(reset_status).AddCause(std::move(error))); |
| return; |
| } |
| |
| // The data source must be stopped after the client, after which the |
| // old demuxer and data source can be freed. |
| client_->StopForDemuxerReset(); |
| data_source_->Stop(); |
| FreeResourcesAfterMediaThreadWait(base::BindOnce( |
| &DemuxerManager::RestartClientForHLS, weak_factory_.GetWeakPtr())); |
| |
| return; |
| } |
| #endif // BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID) |
| |
| client_->OnError(std::move(error)); |
| } |
| |
| void DemuxerManager::FreeResourcesAfterMediaThreadWait(base::OnceClosure cb) { |
| // The demuxer and data source must be freed on the main thread, but we have |
| // to make sure nothing is using them on the media thread first. So we have |
| // to post to the media thread and back. |
| media_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindPostTaskToCurrentDefault(base::BindOnce( |
| [](std::unique_ptr<Demuxer> demuxer, |
| std::unique_ptr<DataSource> data_source, |
| base::OnceClosure done_cb) { |
| demuxer.reset(); |
| data_source.reset(); |
| std::move(done_cb).Run(); |
| }, |
| std::move(demuxer_), std::move(data_source_), std::move(cb)))); |
| } |
| |
| void DemuxerManager::DisallowFallback() { |
| fallback_allowed_ = false; |
| } |
| |
| void DemuxerManager::SetLoadedUrl(GURL url) { |
| // The URL might be a rather large data:// url, so move it to prevent a |
| // copy. |
| loaded_url_ = std::move(url); |
| } |
| |
| #if BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID) |
| |
| void DemuxerManager::PopulateHlsHistograms(bool cryptographic_url) { |
| DCHECK(data_source_); |
| |
| if (auto* co_data_source = data_source_->GetAsCrossOriginDataSource()) { |
| MimeType mime_type = |
| TranslateMimeTypeToHistogramEnum(co_data_source->GetMimeType()); |
| base::UmaHistogramEnumeration("Media.WebMediaPlayerImpl.HLS.MimeType", |
| mime_type); |
| |
| bool is_cross_origin = co_data_source->IsCorsCrossOrigin(); |
| UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.IsCorsCrossOrigin", |
| is_cross_origin); |
| if (is_cross_origin) { |
| UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.HasAccessControl", |
| co_data_source->HasAccessControl()); |
| base::UmaHistogramEnumeration( |
| "Media.WebMediaPlayerImpl.HLS.CorsCrossOrigin.MimeType", mime_type); |
| } |
| } else { |
| UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.IsCorsCrossOrigin", |
| false); |
| } |
| |
| bool is_mixed_content = |
| cryptographic_url && |
| (!loaded_url_.SchemeIsCryptographic() || |
| GetDataSourceUrlAfterRedirects()->SchemeIsCryptographic()); |
| UMA_HISTOGRAM_BOOLEAN("Media.WebMediaPlayerImpl.HLS.IsMixedContent", |
| is_mixed_content); |
| } |
| |
| PipelineStatus DemuxerManager::SelectHlsFallbackMechanism( |
| bool cryptographic_url) { |
| hls_fallback_ = SelectHlsFallbackImplementation(); |
| if (hls_fallback_ == HlsFallbackImplementation::kNone) { |
| return DEMUXER_ERROR_DETECTED_HLS; |
| } |
| |
| // If we've gotten a request to start HLS fallback and logging, we can assert |
| // that data source has been set. |
| CHECK(data_source_); |
| |
| // |data_source_| might be a MemoryDataSource if our URL is a data:// url. |
| // Since MediaPlayer doesn't support this type of URL, we can't fall back to |
| // android's HLS implementation. Since HLS is enabled, we should report a |
| // failed external renderer, since we know MediaPlayerRenderer would fail |
| // anyway here. |
| bool is_mp = hls_fallback_ == HlsFallbackImplementation::kMediaPlayer; |
| if (!data_source_->GetAsCrossOriginDataSource() && is_mp) { |
| // Media player requires that the data source not be a data:// url. |
| return PIPELINE_ERROR_EXTERNAL_RENDERER_FAILED; |
| } |
| |
| loaded_url_ = GetDataSourceUrlAfterRedirects().value(); |
| |
| // We do not support using blob and filesystem schemes with the Android |
| // MediaPlayer. Fail now rather than during MediaPlayerRender initialization. |
| if (is_mp && |
| (loaded_url_.SchemeIsBlob() || loaded_url_.SchemeIsFileSystem())) { |
| return PIPELINE_ERROR_EXTERNAL_RENDERER_FAILED; |
| } |
| |
| PopulateHlsHistograms(cryptographic_url); |
| |
| if (client_) { |
| client_->UpdateLoadedUrl(loaded_url_); |
| } |
| |
| return OkStatus(); |
| } |
| |
| #endif // BUILDFLAG(ENABLE_HLS_DEMUXER) || BUILDFLAG(IS_ANDROID) |
| |
| absl::optional<double> DemuxerManager::GetDemuxerDuration() { |
| if (!demuxer_) { |
| return absl::nullopt; |
| } |
| if (demuxer_->GetDemuxerType() != DemuxerType::kChunkDemuxer) { |
| return absl::nullopt; |
| } |
| |
| // Use duration from ChunkDemuxer when present. MSE allows users to specify |
| // duration as a double. This propagates to the rest of the pipeline as a |
| // TimeDelta with potentially reduced precision (limited to Microseconds). |
| // ChunkDemuxer returns the full-precision user-specified double. This ensures |
| // users can "get" the exact duration they "set". |
| // TODO(crbug/1377053) Get rid of this static cast. |
| return static_cast<ChunkDemuxer*>(demuxer_.get())->GetDuration(); |
| } |
| |
| absl::optional<DemuxerType> DemuxerManager::GetDemuxerType() { |
| if (!demuxer_) { |
| return absl::nullopt; |
| } |
| return demuxer_->GetDemuxerType(); |
| } |
| |
| absl::optional<container_names::MediaContainerName> |
| DemuxerManager::GetContainerForMetrics() { |
| if (!demuxer_) { |
| return absl::nullopt; |
| } |
| return demuxer_->GetContainerForMetrics(); |
| } |
| |
| void DemuxerManager::RespondToDemuxerMemoryUsageReport( |
| base::OnceCallback<void(int64_t)> cb) { |
| if (!demuxer_) { |
| return std::move(cb).Run(0); |
| } |
| switch (demuxer_->GetDemuxerType()) { |
| case DemuxerType::kChunkDemuxer: |
| // ChunkDemuxer locks while getting the memory size, so we don't have |
| // to post cross thread. |
| return std::move(cb).Run(demuxer_->GetMemoryUsage()); |
| case DemuxerType::kMediaUrlDemuxer: |
| // MediaUrlDemuxer always returns a constant. |
| return std::move(cb).Run(demuxer_->GetMemoryUsage()); |
| default: |
| // FFmpegDemuxer is single threaded and only runs on the media thread, |
| // so we have to post there and wait for the reply. We can't be sure what |
| // other demuxers do. |
| // base::Unretained is safe here because |this| is posted for destruction |
| // and |this| strongly owns |demuxer_|. See WMPI::ReportMemoryUsage() for |
| // more information about destruction order. |
| media_task_runner_->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&Demuxer::GetMemoryUsage, |
| base::Unretained(demuxer_.get())), |
| std::move(cb)); |
| break; |
| } |
| } |
| |
| void DemuxerManager::DisableDemuxerCanChangeType() { |
| demuxer_->DisableCanChangeType(); |
| } |
| |
| PipelineStatus DemuxerManager::CreateDemuxer( |
| bool load_media_source, |
| DataSource::Preload preload, |
| bool has_poster, |
| DemuxerManager::DemuxerCreatedCB on_demuxer_created) { |
| // TODO(crbug/1377053) return a better error |
| if (!client_) { |
| return DEMUXER_ERROR_COULD_NOT_OPEN; |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| const bool media_player_hls = |
| hls_fallback_ == HlsFallbackImplementation::kMediaPlayer; |
| if (media_player_hls || client_->IsMediaPlayerRendererClient()) { |
| SetDemuxer(CreateMediaUrlDemuxer(media_player_hls)); |
| return std::move(on_demuxer_created) |
| .Run(demuxer_.get(), Pipeline::StartType::kNormal, |
| /*is_streaming = */ false, |
| /*is_static = */ false); |
| } |
| #endif |
| |
| #if BUILDFLAG(ENABLE_HLS_DEMUXER) |
| if (hls_fallback_ == HlsFallbackImplementation::kBuiltinHlsPlayer) { |
| SetDemuxer(CreateHlsDemuxer()); |
| return std::move(on_demuxer_created) |
| .Run(demuxer_.get(), Pipeline::StartType::kNormal, |
| /*is_streaming=*/false, /*is_static=*/false); |
| } |
| #endif // BUILDFLAG(ENABLE_HLS_DEMUXER) |
| |
| // TODO(sandersd): FileSystem objects may also be non-static, but due to our |
| // caching layer such situations are broken already. http://crbug.com/593159 |
| bool is_static = true; |
| |
| if (demuxer_override_) { |
| // TODO(https://crbug.com/1076267): Should everything else after this block |
| // run in the demuxer override case? |
| SetDemuxer(std::move(demuxer_override_)); |
| } else if (!load_media_source) { |
| #if BUILDFLAG(ENABLE_FFMPEG) |
| SetDemuxer(CreateFFmpegDemuxer()); |
| #else |
| return DEMUXER_ERROR_COULD_NOT_OPEN; |
| #endif |
| } else { |
| DCHECK(!HasDataSource()); |
| SetDemuxer(CreateChunkDemuxer()); |
| is_static = false; |
| } |
| |
| if (!demuxer_) { |
| return DEMUXER_ERROR_COULD_NOT_OPEN; |
| } |
| |
| // A myriad of reasons exists that prevent us from entering a suspended state |
| // after metadata is reached - in this case we'll have to do a normal startup. |
| if (demuxer_->GetDemuxerType() == DemuxerType::kChunkDemuxer || |
| preload != DataSource::METADATA || client_->CouldPlayIfEnoughData() || |
| IsStreaming()) { |
| return std::move(on_demuxer_created) |
| .Run(demuxer_.get(), Pipeline::StartType::kNormal, IsStreaming(), |
| is_static); |
| } |
| |
| // We can only do a universal suspend for posters, unless the flag is enabled. |
| auto suspended_mode = Pipeline::StartType::kSuspendAfterMetadataForAudioOnly; |
| if (has_poster || base::FeatureList::IsEnabled(kPreloadMetadataLazyLoad)) { |
| suspended_mode = Pipeline::StartType::kSuspendAfterMetadata; |
| } |
| return std::move(on_demuxer_created) |
| .Run(demuxer_.get(), suspended_mode, IsStreaming(), is_static); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| void DemuxerManager::SetAllowMediaPlayerRendererCredentials(bool allow) { |
| allow_media_player_renderer_credentials_ = allow; |
| } |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| const DataSource* DemuxerManager::GetDataSourceForTesting() const { |
| return data_source_.get(); |
| } |
| |
| void DemuxerManager::SetDataSource(std::unique_ptr<DataSource> data_source) { |
| data_source_ = std::move(data_source); |
| } |
| |
| void DemuxerManager::OnBufferingHaveEnough(bool enough) { |
| CHECK(data_source_); |
| data_source_->OnBufferingHaveEnough(enough); |
| } |
| |
| void DemuxerManager::SetPreload(DataSource::Preload preload) { |
| if (data_source_) { |
| data_source_->SetPreload(preload); |
| } |
| } |
| |
| void DemuxerManager::StopAndResetClient(Client* client) { |
| if (data_source_) { |
| data_source_->Stop(); |
| } |
| client_ = client; |
| } |
| |
| int64_t DemuxerManager::GetDataSourceMemoryUsage() { |
| return data_source_ ? data_source_->GetMemoryUsage() : 0; |
| } |
| |
| void DemuxerManager::OnDataSourcePlaybackRateChange(double rate, bool paused) { |
| if (!data_source_) { |
| return; |
| } |
| data_source_->OnMediaPlaybackRateChanged(rate); |
| if (!paused) { |
| data_source_->OnMediaIsPlaying(); |
| } |
| } |
| |
| bool DemuxerManager::WouldTaintOrigin() const { |
| if (hls_fallback_ != HlsFallbackImplementation::kNone) { |
| // HLS manifests might pull segments from a different origin. We can't know |
| // for sure, so we conservatively say yes here. |
| // TODO (crbug/1266991) We will be able to know for sure with the builtin |
| // player, when that's implemented. |
| return true; |
| } |
| |
| // TODO(crbug/1377053): The default |false| value might have to be |
| // re-considered for MediaPlayerRenderer, but for now, leave behavior the |
| // same as it was. |
| return data_source_ ? data_source_->WouldTaintOrigin() : false; |
| } |
| |
| bool DemuxerManager::HasDataSource() const { |
| return data_source_ != nullptr; |
| } |
| |
| bool DemuxerManager::HasDemuxer() const { |
| return !!demuxer_; |
| } |
| |
| bool DemuxerManager::HasDemuxerOverride() const { |
| return !!demuxer_override_; |
| } |
| |
| absl::optional<GURL> DemuxerManager::GetDataSourceUrlAfterRedirects() const { |
| if (data_source_) { |
| return data_source_->GetUrlAfterRedirects(); |
| } |
| return absl::nullopt; |
| } |
| |
| bool DemuxerManager::DataSourceFullyBuffered() const { |
| return data_source_ && data_source_->AssumeFullyBuffered(); |
| } |
| |
| bool DemuxerManager::IsStreaming() const { |
| return (data_source_ && data_source_->IsStreaming()) || |
| (demuxer_ && !demuxer_->IsSeekable()); |
| } |
| |
| bool DemuxerManager::PassedDataSourceTimingAllowOriginCheck() const { |
| // If there is no MultiBuffer, then there are no HTTP responses, and so this |
| // can safely return true. Specifically for the MSE case, the app itself |
| // sources the ArrayBuffer[Views], possibly not even from HTTP responses. Any |
| // TAO checks which are present to prevent deduction of the resource content |
| // can be assumed to have passed, as the content is already readable by the |
| // app. TAO checks which would be used to determine other network timing |
| // info, such as DNS lookup time, are not relevant as the media data is far |
| // removed from the network itself at this point, and so that info cannot be |
| // revealed via the MediaSource or WebMediaPlayer that's using MSE. |
| // TODO(1266991): Ensure that this returns the correct value for HLS media, |
| // based on the TAO checks performed on those resources. |
| return data_source_ ? data_source_->PassedTimingAllowOriginCheck() : true; |
| } |
| |
| std::unique_ptr<Demuxer> DemuxerManager::CreateChunkDemuxer() { |
| if (base::FeatureList::IsEnabled(kMemoryPressureBasedSourceBufferGC)) { |
| memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>( |
| FROM_HERE, base::BindRepeating(&DemuxerManager::OnMemoryPressure, |
| base::Unretained(this))); |
| } |
| |
| return std::make_unique<ChunkDemuxer>( |
| base::BindPostTaskToCurrentDefault(base::BindOnce( |
| &DemuxerManager::OnChunkDemuxerOpened, weak_factory_.GetWeakPtr())), |
| base::BindPostTaskToCurrentDefault(base::BindRepeating( |
| &DemuxerManager::OnProgress, weak_factory_.GetWeakPtr())), |
| base::BindPostTaskToCurrentDefault( |
| base::BindRepeating(&DemuxerManager::OnEncryptedMediaInitData, |
| weak_factory_.GetWeakPtr())), |
| media_log_.get()); |
| } |
| |
| #if BUILDFLAG(ENABLE_FFMPEG) |
| std::unique_ptr<Demuxer> DemuxerManager::CreateFFmpegDemuxer() { |
| DCHECK(data_source_); |
| return std::make_unique<FFmpegDemuxer>( |
| media_task_runner_, data_source_.get(), |
| base::BindPostTaskToCurrentDefault( |
| base::BindRepeating(&DemuxerManager::OnEncryptedMediaInitData, |
| weak_factory_.GetWeakPtr())), |
| base::BindPostTaskToCurrentDefault( |
| base::BindRepeating(&DemuxerManager::OnFFmpegMediaTracksUpdated, |
| weak_factory_.GetWeakPtr())), |
| media_log_.get(), IsLocalFile(loaded_url_)); |
| } |
| #endif // BUILDFLAG(ENABLE_FFMPEG) |
| |
| #if BUILDFLAG(ENABLE_HLS_DEMUXER) |
| std::unique_ptr<Demuxer> DemuxerManager::CreateHlsDemuxer() { |
| auto engine = std::make_unique<HlsManifestDemuxerEngine>( |
| client_->GetHlsDataSourceProvider(), media_task_runner_, loaded_url_, |
| media_log_.get()); |
| return std::make_unique<ManifestDemuxer>(media_task_runner_, |
| std::move(engine), media_log_.get()); |
| } |
| #endif |
| |
| #if BUILDFLAG(IS_ANDROID) |
| std::unique_ptr<Demuxer> DemuxerManager::CreateMediaUrlDemuxer( |
| bool expect_hls_content) { |
| return std::make_unique<MediaUrlDemuxer>( |
| media_task_runner_, loaded_url_, site_for_cookies_, top_frame_origin_, |
| has_storage_access_, allow_media_player_renderer_credentials_, |
| expect_hls_content); |
| } |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| void DemuxerManager::SetDemuxer(std::unique_ptr<Demuxer> demuxer) { |
| DCHECK(!demuxer_); |
| CHECK(demuxer); |
| |
| demuxer_ = std::move(demuxer); |
| if (client_) { |
| client_->MakeDemuxerThreadDumper(demuxer_.get()); |
| } |
| } |
| |
| void DemuxerManager::OnEncryptedMediaInitData( |
| EmeInitDataType init_data_type, |
| const std::vector<uint8_t>& init_data) { |
| if (client_) { |
| client_->OnEncryptedMediaInitData(init_data_type, init_data); |
| } |
| } |
| |
| void DemuxerManager::OnMemoryPressure( |
| base::MemoryPressureListener::MemoryPressureLevel level) { |
| DVLOG(2) << __func__ << " level=" << level; |
| DCHECK(base::FeatureList::IsEnabled(kMemoryPressureBasedSourceBufferGC)); |
| DCHECK(GetDemuxerType() == DemuxerType::kChunkDemuxer); |
| |
| if (level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) { |
| return; |
| } |
| |
| // The new value of `level` will take effect on the next |
| // garbage collection. Typically this means the next SourceBuffer append() |
| // operation, since per MSE spec, the garbage collection must only occur |
| // during SourceBuffer append(). But if memory pressure is critical it might |
| // be better to perform GC immediately rather than wait for the next append |
| // and potentially get killed due to out-of-memory. |
| // So if this experiment is enabled and pressure level is critical, we'll pass |
| // down force_instant_gc==true, which will force immediate GC on |
| // SourceBufferStreams. |
| bool force_instant_gc = |
| (enable_instant_source_buffer_gc_ && |
| level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL); |
| |
| if (!client_) { |
| return; |
| } |
| |
| // base::Unretained is safe, since `demuxer_` is actually owned by |
| // `this` via this->demuxer_. Note the destruction of `demuxer_` is done |
| // from ~WMPI by first hopping to `media_task_runner_` to prevent race with |
| // this task. |
| // TODO(crbug/1377053) Get rid of this static cast. |
| media_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &ChunkDemuxer::OnMemoryPressure, |
| base::Unretained(static_cast<ChunkDemuxer*>(demuxer_.get())), |
| base::Seconds(client_->CurrentTime()), level, force_instant_gc)); |
| } |
| |
| void DemuxerManager::OnChunkDemuxerOpened() { |
| CHECK(demuxer_); |
| CHECK(demuxer_->GetDemuxerType() == DemuxerType::kChunkDemuxer); |
| // TODO(crbug/1377053) Get rid of this static cast. |
| if (client_) { |
| client_->OnChunkDemuxerOpened(static_cast<ChunkDemuxer*>(demuxer_.get())); |
| } |
| } |
| |
| void DemuxerManager::OnProgress() { |
| if (client_) { |
| client_->OnProgress(); |
| } |
| } |
| |
| #if BUILDFLAG(ENABLE_FFMPEG) |
| void DemuxerManager::OnFFmpegMediaTracksUpdated( |
| std::unique_ptr<MediaTracks> tracks) { |
| DCHECK(demuxer_); |
| |
| // For MSE/chunk_demuxer case the media track updates are handled by |
| // WebSourceBufferImpl. |
| DCHECK(GetDemuxerType() != DemuxerType::kChunkDemuxer); |
| |
| // we might be in the process of being destroyed when this happens. |
| if (!client_) { |
| return; |
| } |
| |
| // Only the first audio track and the first video track are enabled by |
| // default to match blink logic. |
| bool is_first_audio_track = true; |
| bool is_first_video_track = true; |
| for (const auto& track : tracks->tracks()) { |
| if (track->type() == MediaTrack::Audio) { |
| client_->AddAudioTrack(track->id().value(), track->label().value(), |
| track->language().value(), is_first_audio_track); |
| is_first_audio_track = false; |
| } else if (track->type() == MediaTrack::Video) { |
| client_->AddVideoTrack(track->id().value(), track->label().value(), |
| track->language().value(), is_first_video_track); |
| is_first_video_track = false; |
| } else { |
| // Text tracks are not supported through this code path. |
| NOTREACHED(); |
| } |
| } |
| } |
| #endif // BUILDFLAG(ENABLE_FFMPEG) |
| |
| } // namespace media |