diff --git a/build/config/BUILD.gn b/build/config/BUILD.gn index bbd7c4e6f0d..ca3d6cf9289 100644 --- a/build/config/BUILD.gn +++ b/build/config/BUILD.gn @@ -62,6 +62,7 @@ config("feature_flags") { defines = [ "V8_DEPRECATION_WARNINGS" ] if (enable_castanets) { defines += [ "CASTANETS" ] + defines += [ "VIDEO_HOLE" ] } if (dcheck_always_on) { defines += [ "DCHECK_ALWAYS_ON=1" ] diff --git a/cc/layers/video_frame_provider.h b/cc/layers/video_frame_provider.h index af413093231..712b978a3b4 100644 --- a/cc/layers/video_frame_provider.h +++ b/cc/layers/video_frame_provider.h @@ -9,12 +9,23 @@ #include "base/time/time.h" #include "cc/cc_export.h" +#if defined(VIDEO_HOLE) +namespace gfx { +class Rect; +} +#endif + namespace media { class VideoFrame; } namespace cc { +#if defined(VIDEO_HOLE) +using DrawableContentRectChangedCallback = + base::RepeatingCallback; +#endif + // VideoFrameProvider and VideoFrameProvider::Client define the relationship by // which video frames are exchanged between a provider and client. // @@ -93,6 +104,13 @@ class CC_EXPORT VideoFrameProvider { // frame missed its intended deadline. virtual void PutCurrentFrame() = 0; +#if defined(VIDEO_HOLE) + virtual void SetDrawableContentRectChangedCallback( + DrawableContentRectChangedCallback cb) = 0; + + // Notifies the client of video plane geometry to be use. + virtual void OnDrawableContentRectChanged(const gfx::Rect&) = 0; +#endif protected: virtual ~VideoFrameProvider() {} }; diff --git a/cc/layers/video_frame_provider_client_impl.cc b/cc/layers/video_frame_provider_client_impl.cc index 1f70db25eba..960f7210ca1 100644 --- a/cc/layers/video_frame_provider_client_impl.cc +++ b/cc/layers/video_frame_provider_client_impl.cc @@ -89,6 +89,15 @@ void VideoFrameProviderClientImpl::PutCurrentFrame() { provider_->PutCurrentFrame(); needs_put_current_frame_ = false; } +#if defined(VIDEO_HOLE) +void VideoFrameProviderClientImpl::OnDrawableContentRectChanged( + const gfx::Rect rect) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (provider_) + provider_->OnDrawableContentRectChanged(rect); +} +#endif void VideoFrameProviderClientImpl::ReleaseLock() { DCHECK(thread_checker_.CalledOnValidThread()); diff --git a/cc/layers/video_frame_provider_client_impl.h b/cc/layers/video_frame_provider_client_impl.h index 6c11f72b0a0..87794096265 100644 --- a/cc/layers/video_frame_provider_client_impl.h +++ b/cc/layers/video_frame_provider_client_impl.h @@ -58,6 +58,10 @@ class CC_EXPORT VideoFrameProviderClientImpl void DidReceiveFrame() override; bool IsDrivingFrameUpdates() const override; +#if defined(VIDEO_HOLE) + void OnDrawableContentRectChanged(const gfx::Rect); +#endif + const VideoFrameProvider* get_provider_for_testing() const { return provider_; } diff --git a/cc/layers/video_layer_impl.cc b/cc/layers/video_layer_impl.cc index d4a18b06d92..0245e86680f 100644 --- a/cc/layers/video_layer_impl.cc +++ b/cc/layers/video_layer_impl.cc @@ -157,6 +157,15 @@ void VideoLayerImpl::AppendQuads(viz::RenderPass* render_pass, visible_layer_rect(), clip_rect(), is_clipped(), contents_opaque(), draw_opacity(), GetSortingContextId(), visible_quad_rect); + +#if defined(VIDEO_HOLE) + const gfx::Rect video_rect = + cc::MathUtil::MapEnclosingClippedRect(transform, gfx::Rect(rotated_size)); + if (previous_visible_rect_ != video_rect) { + previous_visible_rect_ = video_rect; + provider_client_impl_->OnDrawableContentRectChanged(video_rect); + } +#endif } void VideoLayerImpl::DidDraw(viz::ClientResourceProvider* resource_provider) { diff --git a/cc/layers/video_layer_impl.h b/cc/layers/video_layer_impl.h index 4b3646b0e16..fd0d009d889 100644 --- a/cc/layers/video_layer_impl.h +++ b/cc/layers/video_layer_impl.h @@ -13,6 +13,10 @@ #include "components/viz/common/resources/release_callback.h" #include "media/base/video_rotation.h" +#if defined(VIDEO_HOLE) +#include "ui/gfx/geometry/rect.h" +#endif + namespace media { class VideoFrame; class VideoResourceUpdater; @@ -56,6 +60,10 @@ class CC_EXPORT VideoLayerImpl : public LayerImpl { const char* LayerTypeAsString() const override; +#if defined(VIDEO_HOLE) + gfx::Rect previous_visible_rect_; +#endif + scoped_refptr provider_client_impl_; scoped_refptr frame_; diff --git a/content/browser/media/android/browser_media_player_manager.cc b/content/browser/media/android/browser_media_player_manager.cc index 75f94f6902d..81cdcbebda9 100644 --- a/content/browser/media/android/browser_media_player_manager.cc +++ b/content/browser/media/android/browser_media_player_manager.cc @@ -121,6 +121,8 @@ BrowserMediaPlayerManager::CreateMediaPlayer( } return std::move(media_player_bridge); } + default: + break; } NOTREACHED(); diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn index 54b719fe7fc..043f5669182 100644 --- a/content/common/BUILD.gn +++ b/content/common/BUILD.gn @@ -395,6 +395,14 @@ source_set("common") { "//content/public/common:interfaces", ] + if (enable_castanets) { + sources += [ + "media/castanets_media_param_traits.cc", + "media/castanets_media_param_traits.h", + "media/castanets_media_player_messages.h", + ] + } + if (is_android && use_seccomp_bpf) { set_sources_assignment_filter([]) sources += [ diff --git a/content/common/content_message_generator.h b/content/common/content_message_generator.h index c895849490e..709f57413dd 100644 --- a/content/common/content_message_generator.h +++ b/content/common/content_message_generator.h @@ -125,3 +125,12 @@ #error "Failed to include content/common/media/media_player_messages_android.h" #endif #endif // defined(OS_ANDROID) + +#if defined(CASTANETS) +#undef CONTENT_COMMON_MEDIA_CASTANETS_MEDIA_PLAYER_MESSAGES_H_ +#include "content/common/media/castanets_media_player_messages.h" +#ifndef CONTENT_COMMON_MEDIA_CASTANETS_MEDIA_PLAYER_MESSAGES_H_ +#error \ + "Failed to include content/common/media/castanets_media_player_messages.h" +#endif +#endif diff --git a/content/common/media/castanets_media_param_traits.cc b/content/common/media/castanets_media_param_traits.cc new file mode 100644 index 00000000000..85c6615ba1a --- /dev/null +++ b/content/common/media/castanets_media_param_traits.cc @@ -0,0 +1,52 @@ +// Copyright 2019 Samsung Electronics Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/common/media/castanets_media_param_traits.h" + +#include "base/strings/stringprintf.h" +#include "ipc/ipc_message_utils.h" + +namespace IPC { + +void ParamTraits>::Write( + base::Pickle* pickle, + const media::Ranges& range) { + WriteParam(pickle, static_cast(range.size())); + for (size_t i = 0; i < range.size(); ++i) { + WriteParam(pickle, range.start(i)); + WriteParam(pickle, range.end(i)); + } +} + +bool ParamTraits>::Read( + const base::Pickle* pickle, + base::PickleIterator* iter, + media::Ranges* range) { + int size = 0; + + // ReadLength() checks for < 0 itself. + if (!iter->ReadLength(&size)) + return false; + for (int i = 0; i < size; i++) { + base::TimeDelta start, end; + if (!ReadParam(pickle, iter, &start) || !ReadParam(pickle, iter, &end)) + return false; + range->Add(start, end); + } + return true; +} + +void ParamTraits>::Log( + const media::Ranges& pickle, + std::string* str) { + str->append("TimeRanges:["); + for (size_t i = 0u; i < pickle.size(); ++i) { + str->append(base::StringPrintf("{%zu:{%lf,%lf}}, ", i, + pickle.start(i).InSecondsF(), + pickle.end(i).InSecondsF())); + } + str->append("]"); +} + +} // namespace IPC diff --git a/content/common/media/castanets_media_param_traits.h b/content/common/media/castanets_media_param_traits.h new file mode 100644 index 00000000000..aefe64c291d --- /dev/null +++ b/content/common/media/castanets_media_param_traits.h @@ -0,0 +1,23 @@ +#ifndef CONTENT_COMMON_MEDIA_CASTANETS_MEDIA_PARAM_TRAITS_H_ +#define CONTENT_COMMON_MEDIA_CASTANETS_MEDIA_PARAM_TRAITS_H_ + +#include "base/pickle.h" +#include "content/common/content_export.h" +#include "ipc/ipc_param_traits.h" +#include "media/base/ranges.h" + +namespace IPC { + +template <> +struct CONTENT_EXPORT ParamTraits> { + typedef media::Ranges param_type; + static void Write(base::Pickle* pickle, const param_type& ptype); + static bool Read(const base::Pickle* pickle, + base::PickleIterator* iter, + param_type* ptype); + static void Log(const param_type& ptype, std::string* str); +}; + +} // namespace IPC + +#endif // CONTENT_COMMON_MEDIA_CASTANETS_MEDIA_PARAM_TRAITS_H_ diff --git a/content/common/media/castanets_media_player_init_config.h b/content/common/media/castanets_media_player_init_config.h new file mode 100644 index 00000000000..a4754ab7cef --- /dev/null +++ b/content/common/media/castanets_media_player_init_config.h @@ -0,0 +1,24 @@ +// Copyright 2019 Samsung Electronics Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_COMMON_MEDIA_CASTANETS_MEDIA_PLAYER_INIT_CONFIG_H_ +#define CONTENT_COMMON_MEDIA_CASTANETS_MEDIA_PLAYER_INIT_CONFIG_H_ + +#include +#include "media/blink/renderer_media_player_interface.h" +#include "url/gurl.h" + +namespace content { + +struct MediaPlayerInitConfig { + MediaPlayerHostMsg_Initialize_Type type; + GURL url; + std::string mime_type; + int demuxer_client_id; + bool has_encrypted_listener_or_cdm; +}; + +} // namespace content + +#endif // CONTENT_COMMON_MEDIA_CASTANETS_MEDIA_PLAYER_INIT_CONFIG_H_ diff --git a/content/common/media/castanets_media_player_messages.h b/content/common/media/castanets_media_player_messages.h new file mode 100644 index 00000000000..eac026cf45f --- /dev/null +++ b/content/common/media/castanets_media_player_messages.h @@ -0,0 +1,148 @@ +// Copyright 2019 Samsung Electronics Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// IPC messages for castanets player. +#ifndef CONTENT_COMMON_MEDIA_CASTANETS_MEDIA_PLAYER_MESSAGES_H_ +#define CONTENT_COMMON_MEDIA_CASTANETS_MEDIA_PLAYER_MESSAGES_H_ + +#include "content/common/media/castanets_media_param_traits.h" +#include "content/common/media/castanets_media_player_init_config.h" +#include "ipc/ipc_message_macros.h" +#include "media/blink/renderer_media_player_interface.h" +#include "ui/gfx/geometry/rect_f.h" + +#undef IPC_MESSAGE_EXPORT +#define IPC_MESSAGE_EXPORT CONTENT_EXPORT +#define IPC_MESSAGE_START MediaPlayerCastanetsMsgStart + +IPC_ENUM_TRAITS(blink::WebMediaPlayer::ReadyState) +IPC_ENUM_TRAITS(blink::WebMediaPlayer::NetworkState) +#if !defined(OS_ANDROID) +IPC_ENUM_TRAITS(MediaPlayerHostMsg_Initialize_Type) +#endif + +// Should be same as castanets player. +IPC_STRUCT_TRAITS_BEGIN(content::MediaPlayerInitConfig) +IPC_STRUCT_TRAITS_MEMBER(type) +IPC_STRUCT_TRAITS_MEMBER(url) +IPC_STRUCT_TRAITS_MEMBER(mime_type) +IPC_STRUCT_TRAITS_MEMBER(demuxer_client_id) +IPC_STRUCT_TRAITS_MEMBER(has_encrypted_listener_or_cdm) +IPC_STRUCT_TRAITS_END() + +// TODO (sm.venugopal): Rename the IPCs. +// Initialize Efl player. +IPC_MESSAGE_ROUTED2(MediaPlayerEflHostMsg_Init, + int /* player_id */, + content::MediaPlayerInitConfig /* config */) + +// Requests the player to enter fullscreen. +IPC_MESSAGE_ROUTED1(MediaPlayerEflHostMsg_EnteredFullscreen, + int /* player_id */) + +// Requests the player to exit fullscreen. +IPC_MESSAGE_ROUTED1(MediaPlayerEflHostMsg_ExitedFullscreen, int /* player_id */) + +// Deinitialize Gst player. +IPC_MESSAGE_ROUTED1(MediaPlayerEflHostMsg_DeInit, int /* player_id */) + +// Start playback. +IPC_MESSAGE_ROUTED1(MediaPlayerEflHostMsg_Play, int /* player_id */) + +// Pause playback. +IPC_MESSAGE_ROUTED2(MediaPlayerEflHostMsg_Pause, + int /* player_id */, + bool /* is_media_related_action */) + +// Suspend media player. +IPC_MESSAGE_ROUTED1(MediaPlayerEflHostMsg_Suspend, int /* player_id */) + +// Resume media player. +IPC_MESSAGE_ROUTED1(MediaPlayerEflHostMsg_Resume, int /* player_id*/) + +// Player was activated by an user or an app. +IPC_MESSAGE_ROUTED1(MediaPlayerEflHostMsg_Activate, int /* player_id*/) + +// Player should deactivate (ex. save power). +IPC_MESSAGE_ROUTED1(MediaPlayerEflHostMsg_Deactivate, int /* player_id*/) + +// Set volume. +IPC_MESSAGE_ROUTED2(MediaPlayerEflHostMsg_SetVolume, + int /* player_id */, + double /* volume */) + +// Set playback rate. +IPC_MESSAGE_ROUTED2(MediaPlayerEflHostMsg_SetRate, + int /* player_id */, + double /* rate */) + +// Playback duration. +IPC_MESSAGE_ROUTED2(MediaPlayerEflMsg_DurationChanged, + int /* player_id */, + base::TimeDelta /* time */) + +// Current duration. +IPC_MESSAGE_ROUTED2(MediaPlayerEflMsg_TimeUpdate, + int /* player_id */, + base::TimeDelta /* time */) + +// Pause state. +IPC_MESSAGE_ROUTED2(MediaPlayerEflMsg_PauseStateChanged, + int /* player_id */, + bool /* state */) + +// Seek state. +IPC_MESSAGE_ROUTED1(MediaPlayerEflMsg_OnSeekComplete, int /* player_id */) + +// Current buffer range. +IPC_MESSAGE_ROUTED2(MediaPlayerEflMsg_BufferUpdate, + int /* player_id */, + int /* buffering_percentage */) + +// Playback completed. +IPC_MESSAGE_ROUTED1(MediaPlayerEflMsg_TimeChanged, int /* player_id */) + +IPC_MESSAGE_ROUTED1(MediaPlayerEflMsg_PlayerDestroyed, int /* player_id */) + +// Ready state change. +IPC_MESSAGE_ROUTED2(MediaPlayerEflMsg_ReadyStateChange, + int /* player_id */, + blink::WebMediaPlayer::ReadyState /* state */) + +// Network state change. +IPC_MESSAGE_ROUTED2(MediaPlayerEflMsg_NetworkStateChange, + int /* player_id */, + blink::WebMediaPlayer::NetworkState /* state */) + +// Gst media data has changed. +IPC_MESSAGE_ROUTED4(MediaPlayerEflMsg_MediaDataChanged, + int /* player_id */, + int /* width */, + int /* height */, + int /* media */) + +// Set geometry. +IPC_MESSAGE_ROUTED2(MediaPlayerEflHostMsg_SetGeometry, + int /* player_id */, + gfx::RectF /* position and size */) +// Seek. +IPC_MESSAGE_ROUTED2(MediaPlayerEflHostMsg_Seek, + int /* player_id */, + base::TimeDelta /* time */) + +IPC_MESSAGE_ROUTED2(MediaPlayerEflMsg_SeekRequest, + int /* player_id */, + base::TimeDelta /* time_to_seek */) + +// Player has begun suspend procedure +IPC_MESSAGE_ROUTED2(MediaPlayerEflMsg_PlayerSuspend, + int /* player_id */, + bool /* is_preempted */) + +// Player has resumed +IPC_MESSAGE_ROUTED2(MediaPlayerEflMsg_PlayerResumed, + int /* player_id */, + bool /* is_preempted */) + +#endif // CONTENT_COMMON_MEDIA_CASTANETS_MEDIA_PLAYER_MESSAGES_H_ diff --git a/content/public/common/web_preferences.h b/content/public/common/web_preferences.h index b7e51788e11..9055a91677b 100644 --- a/content/public/common/web_preferences.h +++ b/content/public/common/web_preferences.h @@ -212,6 +212,8 @@ struct CONTENT_EXPORT WebPreferences { #if defined(CASTANETS) bool use_native_scrollbars = false; +#endif +#if defined(VIDEO_HOLE) bool video_hole_enabled = false; #endif @@ -239,7 +241,7 @@ struct CONTENT_EXPORT WebPreferences { // Cues will not be placed in this margin area. float text_track_margin_percentage; -#if defined(CASTANETS) +#if defined(CASTANETS) bool node_integration = false; #endif diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn index d97b7082177..f7483cf7930 100644 --- a/content/renderer/BUILD.gn +++ b/content/renderer/BUILD.gn @@ -625,6 +625,15 @@ target(link_target_type, "renderer") { "worker_thread_registry.h", ] + if (enable_castanets) { + sources += [ + "media/castanets/castanets_renderer_media_player_manager.cc", + "media/castanets/castanets_renderer_media_player_manager.h", + "media/castanets/castanets_webmediaplayer_impl.cc", + "media/castanets/castanets_webmediaplayer_impl.h", + ] + } + if (!is_component_build) { if (is_win && is_official_build) { split_count = 2 # In certain configurations a full renderer.lib can diff --git a/content/renderer/media/castanets/castanets_renderer_media_player_manager.cc b/content/renderer/media/castanets/castanets_renderer_media_player_manager.cc new file mode 100644 index 00000000000..9246d354d75 --- /dev/null +++ b/content/renderer/media/castanets/castanets_renderer_media_player_manager.cc @@ -0,0 +1,224 @@ +// Copyright 2019 Samsung Electronics Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/renderer/media/castanets/castanets_renderer_media_player_manager.h" + +#include "content/common/media/castanets_media_player_messages.h" + +namespace content { + +CastanetsRendererMediaPlayerManager::CastanetsRendererMediaPlayerManager( + RenderFrame* render_frame) + : RenderFrameObserver(render_frame) {} + +CastanetsRendererMediaPlayerManager::~CastanetsRendererMediaPlayerManager() { + DCHECK(media_players_.empty()) + << "CastanetsRendererMediaPlayerManager is owned by RenderFrameImpl and " + "is " + "destroyed only after all media players are destroyed."; +} + +int CastanetsRendererMediaPlayerManager::RegisterMediaPlayer( + media::RendererMediaPlayerInterface* player) { + // Note : For the unique player id among the all renderer process, + // generate player id based on renderer process id. + static int next_media_player_id_ = base::GetCurrentProcId() << 16; + next_media_player_id_ = (next_media_player_id_ & 0xFFFF0000) | + ((next_media_player_id_ + 1) & 0x0000FFFF); + media_players_[next_media_player_id_] = player; + return next_media_player_id_; +} + +void CastanetsRendererMediaPlayerManager::UnregisterMediaPlayer(int player_id) { + media_players_.erase(player_id); +} + +media::RendererMediaPlayerInterface* +CastanetsRendererMediaPlayerManager::GetMediaPlayer(int player_id) { + std::map::iterator iter = + media_players_.find(player_id); + if (iter != media_players_.end()) + return iter->second; + return NULL; +} + +void CastanetsRendererMediaPlayerManager::OnDestruct() { + delete this; +} + +bool CastanetsRendererMediaPlayerManager::OnMessageReceived( + const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(CastanetsRendererMediaPlayerManager, message) + IPC_MESSAGE_HANDLER(MediaPlayerEflMsg_MediaDataChanged, OnMediaDataChange) + IPC_MESSAGE_HANDLER(MediaPlayerEflMsg_DurationChanged, OnDurationChange) + IPC_MESSAGE_HANDLER(MediaPlayerEflMsg_TimeUpdate, OnTimeUpdate) + IPC_MESSAGE_HANDLER(MediaPlayerEflMsg_BufferUpdate, OnBufferUpdate) + IPC_MESSAGE_HANDLER(MediaPlayerEflMsg_ReadyStateChange, OnReadyStateChange) + IPC_MESSAGE_HANDLER(MediaPlayerEflMsg_NetworkStateChange, + OnNetworkStateChange) + IPC_MESSAGE_HANDLER(MediaPlayerEflMsg_TimeChanged, OnTimeChanged) + IPC_MESSAGE_HANDLER(MediaPlayerEflMsg_PauseStateChanged, OnPauseStateChange) + IPC_MESSAGE_HANDLER(MediaPlayerEflMsg_OnSeekComplete, OnSeekComplete) + IPC_MESSAGE_HANDLER(MediaPlayerEflMsg_SeekRequest, OnRequestSeek) + IPC_MESSAGE_HANDLER(MediaPlayerEflMsg_PlayerSuspend, OnPlayerSuspend) + IPC_MESSAGE_HANDLER(MediaPlayerEflMsg_PlayerResumed, OnPlayerResumed) + IPC_MESSAGE_HANDLER(MediaPlayerEflMsg_PlayerDestroyed, OnPlayerDestroyed) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void CastanetsRendererMediaPlayerManager::Initialize( + int player_id, + MediaPlayerHostMsg_Initialize_Type type, + const GURL& url, + const std::string& mime_type, + int demuxer_client_id) { + bool has_encrypted_listener_or_cdm = false; + MediaPlayerInitConfig config{type, url, mime_type, demuxer_client_id, + has_encrypted_listener_or_cdm}; + Send(new MediaPlayerEflHostMsg_Init(routing_id(), player_id, config)); +} + +void CastanetsRendererMediaPlayerManager::OnMediaDataChange(int player_id, + int width, + int height, + int media) { + media::RendererMediaPlayerInterface* player = GetMediaPlayer(player_id); + if (player) + player->OnMediaDataChange(width, height, media); +} + +void CastanetsRendererMediaPlayerManager::OnPlayerDestroyed(int player_id) { + media::RendererMediaPlayerInterface* player = GetMediaPlayer(player_id); + if (player) + player->OnPlayerDestroyed(); +} + +void CastanetsRendererMediaPlayerManager::OnDurationChange( + int player_id, + base::TimeDelta duration) { + media::RendererMediaPlayerInterface* player = GetMediaPlayer(player_id); + if (player) + player->OnDurationChange(duration); +} + +void CastanetsRendererMediaPlayerManager::OnTimeUpdate( + int player_id, + base::TimeDelta current_time) { + media::RendererMediaPlayerInterface* player = GetMediaPlayer(player_id); + if (player) + player->OnTimeUpdate(current_time); +} + +void CastanetsRendererMediaPlayerManager::OnBufferUpdate(int player_id, + int percentage) { + media::RendererMediaPlayerInterface* player = GetMediaPlayer(player_id); + if (player) + player->OnBufferingUpdate(percentage); +} + +void CastanetsRendererMediaPlayerManager::OnReadyStateChange( + int player_id, + blink::WebMediaPlayer::ReadyState state) { + media::RendererMediaPlayerInterface* player = GetMediaPlayer(player_id); + if (player) + player->SetReadyState( + static_cast(state)); +} + +void CastanetsRendererMediaPlayerManager::OnNetworkStateChange( + int player_id, + blink::WebMediaPlayer::NetworkState state) { + media::RendererMediaPlayerInterface* player = GetMediaPlayer(player_id); + if (player) + player->SetNetworkState( + static_cast(state)); +} + +void CastanetsRendererMediaPlayerManager::OnTimeChanged(int player_id) { + media::RendererMediaPlayerInterface* player = GetMediaPlayer(player_id); + if (player) + player->OnTimeChanged(); +} + +void CastanetsRendererMediaPlayerManager::OnSeekComplete(int player_id) { + media::RendererMediaPlayerInterface* player = GetMediaPlayer(player_id); + if (player) + player->OnSeekComplete(); +} + +void CastanetsRendererMediaPlayerManager::OnPauseStateChange(int player_id, + bool state) { + media::RendererMediaPlayerInterface* player = GetMediaPlayer(player_id); + if (player) + player->OnPauseStateChange(state); +} + +void CastanetsRendererMediaPlayerManager::OnRequestSeek( + int player_id, + base::TimeDelta seek_time) { + media::RendererMediaPlayerInterface* player = GetMediaPlayer(player_id); + if (player) + player->OnSeekRequest(seek_time); +} + +void CastanetsRendererMediaPlayerManager::OnPlayerSuspend(int player_id, + bool is_preempted) { + if (media::RendererMediaPlayerInterface* player = GetMediaPlayer(player_id)) + player->OnPlayerSuspend(is_preempted); +} + +void CastanetsRendererMediaPlayerManager::OnPlayerResumed(int player_id, + bool is_preempted) { + if (media::RendererMediaPlayerInterface* player = GetMediaPlayer(player_id)) + player->OnPlayerResumed(is_preempted); +} + +void CastanetsRendererMediaPlayerManager::Start(int player_id) { + Send(new MediaPlayerEflHostMsg_Play(routing_id(), player_id)); +} + +void CastanetsRendererMediaPlayerManager::Pause(int player_id, + bool is_media_related_action) { + Send(new MediaPlayerEflHostMsg_Pause(routing_id(), player_id, + is_media_related_action)); +} + +void CastanetsRendererMediaPlayerManager::Seek(int player_id, + base::TimeDelta time) { + Send(new MediaPlayerEflHostMsg_Seek(routing_id(), player_id, time)); +} + +void CastanetsRendererMediaPlayerManager::SetVolume(int player_id, + double volume) { + Send(new MediaPlayerEflHostMsg_SetVolume(routing_id(), player_id, volume)); +} + +void CastanetsRendererMediaPlayerManager::SetRate(int player_id, double rate) { + Send(new MediaPlayerEflHostMsg_SetRate(routing_id(), player_id, rate)); +} + +void CastanetsRendererMediaPlayerManager::DestroyPlayer(int player_id) { + Send(new MediaPlayerEflHostMsg_DeInit(routing_id(), player_id)); +} + +void CastanetsRendererMediaPlayerManager::EnteredFullscreen(int player_id) { + Send(new MediaPlayerEflHostMsg_EnteredFullscreen(routing_id(), player_id)); +} + +void CastanetsRendererMediaPlayerManager::ExitedFullscreen(int player_id) { + Send(new MediaPlayerEflHostMsg_ExitedFullscreen(routing_id(), player_id)); +} + +void CastanetsRendererMediaPlayerManager::SetMediaGeometry( + int player_id, + const gfx::RectF& rect) { + gfx::RectF video_rect = rect; + Send(new MediaPlayerEflHostMsg_SetGeometry(routing_id(), player_id, + video_rect)); +} + +} // namespace content diff --git a/content/renderer/media/castanets/castanets_renderer_media_player_manager.h b/content/renderer/media/castanets/castanets_renderer_media_player_manager.h new file mode 100644 index 00000000000..7914cc15627 --- /dev/null +++ b/content/renderer/media/castanets/castanets_renderer_media_player_manager.h @@ -0,0 +1,107 @@ +// Copyright 2019 Samsung Electronics Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_RENDERER_MEDIA_CASTANETS_CASTANETS_RENDERER_MEDIA_PLAYER_MANAGER_H_ +#define CONTENT_RENDERER_MEDIA_CASTANETS_CASTANETS_RENDERER_MEDIA_PLAYER_MANAGER_H_ + +#include +#include "content/public/renderer/render_frame_observer.h" +#include "media/blink/renderer_media_player_interface.h" + +namespace content { + +class CastanetsRendererMediaPlayerManager + : public RenderFrameObserver, + public media::RendererMediaPlayerManagerInterface { + public: + // Constructs a CastanetsRendererMediaPlayerManager object for the + // |render_frame|. + explicit CastanetsRendererMediaPlayerManager(RenderFrame* render_frame); + ~CastanetsRendererMediaPlayerManager() override; + + // RendererMediaPlayerManagerInterface overrides. + void Initialize(int player_id, + MediaPlayerHostMsg_Initialize_Type type, + const GURL& url, + const std::string& mime_type, + int demuxer_client_id) override; + void Start(int player_id) override; + + // Pauses the player. + // is_media_related_action should be true if this pause is coming from an + // an action that explicitly pauses the video (user pressing pause, JS, etc.) + // Otherwise it should be false if Pause is being called due to other reasons + // (cleanup, freeing resources, etc.) + void Pause(int player_id, bool is_media_related_action) override; + void Seek(int player_id, base::TimeDelta time) override; + void SetVolume(int player_id, double volume) override; + void SetRate(int player_id, double rate) override; + + // Registers and unregisters a WebMediaPlayerEfl object. + int RegisterMediaPlayer(media::RendererMediaPlayerInterface* player) override; + void UnregisterMediaPlayer(int player_id) override; + + void DestroyPlayer(int player_id) override; + void Suspend(int player_id) override {} + void Resume(int player_id) override {} + void Activate(int player_id) override {} + void Deactivate(int player_id) override {} + + // Requests the player to enter/exit fullscreen. + void EnteredFullscreen(int player_id) override; + void ExitedFullscreen(int player_id) override; + + void SetMediaGeometry(int player_id, const gfx::RectF& rect) override; + + void Initialize(MediaPlayerHostMsg_Initialize_Type type, + int player_id, + const GURL& url, + const GURL& site_for_cookies, + const GURL& frame_url, + bool allow_credentials, + int delegate_id) override {} + + void SetPoster(int player_id, const GURL& poster) override {} + void SuspendAndReleaseResources(int player_id) override {} + void RequestRemotePlayback(int player_id) override {} + void RequestRemotePlaybackControl(int player_id) override {} + void RequestRemotePlaybackStop(int player_id) override {} + + // RenderFrameObserver overrides. + void OnDestruct() override; + bool OnMessageReceived(const IPC::Message& message) override; + void WasHidden() override {} + void WasShown() override {} + void OnStop() override {} + + private: + void OnPlayerDestroyed(int player_id); + void OnMediaDataChange(int player_id, int width, int height, int media); + void OnDurationChange(int player_id, base::TimeDelta duration); + void OnTimeUpdate(int player_id, base::TimeDelta current_time); + void OnBufferUpdate(int player_id, int percentage); + void OnTimeChanged(int player_id); + void OnPauseStateChange(int player_id, bool state); + void OnSeekComplete(int player_id); + void OnRequestSeek(int player_id, base::TimeDelta seek_time); + void OnPlayerSuspend(int player_id, bool is_preempted); + void OnPlayerResumed(int player_id, bool is_preempted); + void OnReadyStateChange(int player_id, + blink::WebMediaPlayer::ReadyState state); + void OnNetworkStateChange(int player_id, + blink::WebMediaPlayer::NetworkState state); + + media::RendererMediaPlayerInterface* GetMediaPlayer(int player_id); + + // Pause the playing media players when tab/webpage goes to background + void PausePlayingPlayers(); + + std::map media_players_; + + DISALLOW_COPY_AND_ASSIGN(CastanetsRendererMediaPlayerManager); +}; + +} // namespace content + +#endif // CONTENT_RENDERER_MEDIA_CASTANETS_CASTANETS_RENDERER_MEDIA_PLAYER_MANAGER_H_ diff --git a/content/renderer/media/castanets/castanets_webmediaplayer_impl.cc b/content/renderer/media/castanets/castanets_webmediaplayer_impl.cc new file mode 100644 index 00000000000..ee29fcd3647 --- /dev/null +++ b/content/renderer/media/castanets/castanets_webmediaplayer_impl.cc @@ -0,0 +1,737 @@ +// Copyright 2019 Samsung Electronics Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/renderer/media/castanets/castanets_webmediaplayer_impl.h" + +#include "cc/layers/video_layer.h" +#include "media/base/bind_to_current_loop.h" +#include "media/base/media_content_type.h" +#include "third_party/blink/public/platform/web_media_player_client.h" +#include "third_party/blink/public/platform/web_media_player_source.h" +#include "third_party/blink/public/web/web_local_frame.h" +#include "third_party/blink/public/web/web_view.h" + +namespace { +using media::VideoFrame; + +GURL GetCleanURL(std::string url) { + // FIXME: Need to consider "app://" scheme. + CHECK(url.compare(0, 6, "app://")); + if (!url.compare(0, 7, "file://")) { + int position = url.find("?"); + if (position != -1) + url.erase(url.begin() + position, url.end()); + } + GURL url_(url); + return url_; +} + +const base::TimeDelta kLayerBoundUpdateInterval = + base::TimeDelta::FromMilliseconds(50); +} // namespace + +namespace content { +WebMediaPlayerCastanets::WebMediaPlayerCastanets( + blink::WebLocalFrame* frame, + blink::WebMediaPlayerClient* client, + blink::WebMediaPlayerEncryptedMediaClient* encrypted_client, + media::WebMediaPlayerDelegate* delegate, + media::UrlIndex* url_index, + std::unique_ptr compositor, + std::unique_ptr params) + : frame_(frame), + network_state_(blink::WebMediaPlayer::kNetworkStateEmpty), + ready_state_(blink::WebMediaPlayer::kReadyStateHaveNothing), + main_task_runner_(base::ThreadTaskRunnerHandle::Get()), + client_(client), + media_log_(params->take_media_log()), + delegate_(delegate), + defer_load_cb_(params->defer_load_cb()), + compositor_task_runner_(params->video_frame_compositor_task_runner()), + compositor_(std::move(compositor)), + player_type_(MEDIA_PLAYER_TYPE_NONE), + video_width_(0), + video_height_(0), + audio_(false), + video_(false), + is_paused_(true), + is_seeking_(false), + pending_seek_(false), + opaque_(false), + is_fullscreen_(false), + is_draw_ready_(false), + pending_play_(false), + natural_size_(0, 0), + buffered_(static_cast(1)), + did_loading_progress_(false), + volume_(1.0), + weak_factory_(this) { + if (delegate_) + delegate_id_ = delegate_->AddObserver(this); + + compositor_task_runner_->PostTask( + FROM_HERE, + base::BindOnce( + &media::VideoFrameCompositor::SetDrawableContentRectChangedCallback, + base::Unretained(compositor_.get()), + media::BindToCurrentLoop( + base::Bind(&WebMediaPlayerCastanets::OnDrawableContentRectChanged, + AsWeakPtr())))); + + media_log_->AddEvent( + media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_CREATED)); +} + +WebMediaPlayerCastanets::~WebMediaPlayerCastanets() { + if (manager_) { + manager_->DestroyPlayer(player_id_); + manager_->UnregisterMediaPlayer(player_id_); + } + + compositor_->SetVideoFrameProviderClient(NULL); + client_->SetCcLayer(NULL); + + if (delegate_) { + delegate_->PlayerGone(delegate_id_); + delegate_->RemoveObserver(delegate_id_); + } + + compositor_task_runner_->DeleteSoon(FROM_HERE, std::move(compositor_)); +} + +void WebMediaPlayerCastanets::SetMediaPlayerManager( + media::RendererMediaPlayerManagerInterface* media_player_manager) { + manager_ = media_player_manager; + player_id_ = manager_->RegisterMediaPlayer(this); +} + +blink::WebMediaPlayer::LoadTiming WebMediaPlayerCastanets::Load( + LoadType load_type, + const blink::WebMediaPlayerSource& source, + CORSMode /* cors_mode */) { + // Only URL is supported. + DCHECK(source.IsURL()); + blink::WebURL url = source.GetAsURL(); + + bool is_deferred = false; + if (!defer_load_cb_.is_null()) { + is_deferred = defer_load_cb_.Run(base::Bind( + &WebMediaPlayerCastanets::DoLoad, AsWeakPtr(), load_type, url)); + } else { + DoLoad(load_type, url); + } + return is_deferred ? LoadTiming::kDeferred : LoadTiming::kImmediate; +} + +void WebMediaPlayerCastanets::DoLoad(LoadType load_type, + const blink::WebURL& url) { + switch (load_type) { + case kLoadTypeURL: + player_type_ = MEDIA_PLAYER_TYPE_URL_WITH_VIDEO_HOLE; + break; + default: + LOG(ERROR) << "Unsupported load type #" << load_type; + return; + } + + int demuxer_client_id = 0; + blink::WebString content_mime_type = + blink::WebString(client_->GetContentMIMEType()); + + manager_->Initialize(player_id_, player_type_, + GetCleanURL(url.GetString().Utf8()), + content_mime_type.Utf8(), demuxer_client_id); +} + +void WebMediaPlayerCastanets::Play() { + LOG(INFO) << __FUNCTION__ << " [" << player_id_ << "]"; + + if (HasVideo() && !is_draw_ready_) { + pending_play_ = true; + return; + } + pending_play_ = false; + + manager_->Start(player_id_); + // Has to be updated from |MediaPlayerCastanets| but IPC causes delay. + // There are cases were play - pause are fired successively and would fail. + is_paused_ = false; + if (delegate_) + delegate_->DidPlay(delegate_id_, HasVideo(), HasAudio(), + media::DurationToMediaContentType(duration_)); +} + +void WebMediaPlayerCastanets::PauseInternal(bool is_media_related_action) { + LOG(INFO) << __FUNCTION__ << " [" << player_id_ << "]" + << " media_related:" << is_media_related_action; + + pending_play_ = false; + manager_->Pause(player_id_, is_media_related_action); + + // Has to be updated from |MediaPlayerCastanets| but IPC causes delay. + // There are cases were play - pause are fired successively and would fail. + is_paused_ = true; + if (delegate_) + delegate_->DidPause(delegate_id_); +} + +void WebMediaPlayerCastanets::Pause() { + PauseInternal(false); +} + +void WebMediaPlayerCastanets::ReleaseMediaResource() { + LOG(INFO) << __FUNCTION__ << " Player[" << player_id_ << "]"; + manager_->Suspend(player_id_); +} + +void WebMediaPlayerCastanets::InitializeMediaResource() { + LOG(INFO) << __FUNCTION__ << " Player[" << player_id_ + << "] suspend_time : " << current_time_; + manager_->Resume(player_id_); +} + +void WebMediaPlayerCastanets::RequestPause() { + LOG(INFO) << __FUNCTION__ << " Player[" << player_id_ << "]"; + switch (network_state_) { + // Pause the media player and inform Blink if the player is in a good + // shape. + case blink::WebMediaPlayer::kNetworkStateIdle: + case blink::WebMediaPlayer::kNetworkStateLoading: + case blink::WebMediaPlayer::kNetworkStateLoaded: + PauseInternal(false); + client_->RequestPause(); + break; + // If a WebMediaPlayer instance has entered into other then above states, + // the internal network state in HTMLMediaElement could be set to empty. + default: + break; + } +} + +void WebMediaPlayerCastanets::Seek(double seconds) { + LOG(INFO) << __FUNCTION__ << " Player[" << player_id_ << "]" + << " seconds :" << seconds; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + base::TimeDelta new_seek_time = base::TimeDelta::FromSecondsD(seconds); + if (is_seeking_) { + if (new_seek_time == seek_time_) { + pending_seek_ = false; + return; + } + + pending_seek_ = true; + pending_seek_time_ = new_seek_time; + + // Later, OnSeekComplete will trigger the pending seek. + return; + } + + is_seeking_ = true; + seek_time_ = new_seek_time; + manager_->Seek(player_id_, seek_time_); +} + +void WebMediaPlayerCastanets::SetRate(double rate) { + manager_->SetRate(player_id_, rate); +} + +void WebMediaPlayerCastanets::SetVolume(double volume) { + manager_->SetVolume(player_id_, volume); +} + +blink::WebTimeRanges WebMediaPlayerCastanets::Buffered() const { + return buffered_; +} + +blink::WebTimeRanges WebMediaPlayerCastanets::Seekable() const { + if (ready_state_ < WebMediaPlayer::kReadyStateHaveMetadata) + return blink::WebTimeRanges(); + + const blink::WebTimeRange seekable_range(0.0, Duration()); + return blink::WebTimeRanges(&seekable_range, 1); +} + +void WebMediaPlayerCastanets::Paint(cc::PaintCanvas* canvas, + const blink::WebRect& rect, + cc::PaintFlags& flags, + int already_uploaded_id, + VideoFrameUploadMetadata* out_metadata) { + NOTIMPLEMENTED(); +} + +void WebMediaPlayerCastanets::OnFrameHidden() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + SuspendAndReleaseResources(); +} + +void WebMediaPlayerCastanets::OnFrameClosed() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + NOTIMPLEMENTED(); +} + +void WebMediaPlayerCastanets::OnFrameShown() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + Resume(); +} + +void WebMediaPlayerCastanets::OnIdleTimeout() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + NOTIMPLEMENTED(); +} + +void WebMediaPlayerCastanets::OnPlay() { + client_->RequestPlay(); +} + +void WebMediaPlayerCastanets::OnPause() { + client_->RequestPause(); +} + +void WebMediaPlayerCastanets::OnSeekForward(double seconds) { + DCHECK_GE(seconds, 0) << "Attempted to seek by a negative number of seconds"; + client_->RequestSeek(CurrentTime() + seconds); +} + +void WebMediaPlayerCastanets::OnSeekBackward(double seconds) { + DCHECK_GE(seconds, 0) << "Attempted to seek by a negative number of seconds"; + client_->RequestSeek(CurrentTime() - seconds); +} + +void OnSeekRequest(base::TimeDelta time_to_seek) {} + +void WebMediaPlayerCastanets::OnVolumeMultiplierUpdate(double multiplier) { + SetVolume(volume_); +} + +void WebMediaPlayerCastanets::OnBecamePersistentVideo(bool value) { + client_->OnBecamePersistentVideo(value); +} + +void WebMediaPlayerCastanets::OnPictureInPictureModeEnded() { + NOTIMPLEMENTED(); +} + +void WebMediaPlayerCastanets::OnPictureInPictureControlClicked( + const std::string& control_id) { + NOTIMPLEMENTED(); +} + +void WebMediaPlayerCastanets::EnterPictureInPicture( + blink::WebMediaPlayer::PipWindowOpenedCallback callback) { + NOTIMPLEMENTED(); +} + +void WebMediaPlayerCastanets::ExitPictureInPicture( + blink::WebMediaPlayer::PipWindowClosedCallback callback) { + NOTIMPLEMENTED(); +} + +void WebMediaPlayerCastanets::RegisterPictureInPictureWindowResizeCallback( + blink::WebMediaPlayer::PipWindowResizedCallback callback) { + NOTIMPLEMENTED(); +} + +void WebMediaPlayerCastanets::SetSinkId( + const blink::WebString& sink_id, + blink::WebSetSinkIdCallbacks* web_callback) { + NOTIMPLEMENTED(); +} + +bool WebMediaPlayerCastanets::HasVideo() const { + return video_; +} + +bool WebMediaPlayerCastanets::HasAudio() const { + return audio_; +} + +blink::WebSize WebMediaPlayerCastanets::NaturalSize() const { + return blink::WebSize(natural_size_); +} + +blink::WebSize WebMediaPlayerCastanets::VisibleRect() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + scoped_refptr video_frame = GetCurrentFrameFromCompositor(); + if (!video_frame) + return blink::WebSize(); + + const gfx::Rect& visible_rect = video_frame->visible_rect(); + return blink::WebSize(visible_rect.width(), visible_rect.height()); +} + +bool WebMediaPlayerCastanets::Paused() const { + return is_paused_; +} + +bool WebMediaPlayerCastanets::Seeking() const { + return is_seeking_; +} + +double WebMediaPlayerCastanets::Duration() const { + return duration_.InSecondsF(); +} + +double WebMediaPlayerCastanets::CurrentTime() const { + if (Seeking()) + return (pending_seek_ ? pending_seek_time_.InSecondsF() + : seek_time_.InSecondsF()); + return current_time_.InSecondsF(); +} + +void WebMediaPlayerCastanets::SuspendAndReleaseResources() { + LOG(INFO) << __FUNCTION__ << " Player[" << player_id_ << "]"; + if (player_type_ == MEDIA_PLAYER_TYPE_NONE) { + // TODO(m.debski): This should not happen as HTMLMediaElement is handling a + // load deferral. + LOG(ERROR) << "Player type is not set, load has not occured and there is " + "no player yet the player should suspend."; + return; + } + + if (!is_paused_) + OnPauseStateChange(true); + + ReleaseMediaResource(); +} + +void WebMediaPlayerCastanets::Resume() { + LOG(INFO) << __FUNCTION__ << "Player[" << player_id_ << "]"; + InitializeMediaResource(); +} + +blink::WebMediaPlayer::NetworkState WebMediaPlayerCastanets::GetNetworkState() + const { + return network_state_; +} + +blink::WebMediaPlayer::ReadyState WebMediaPlayerCastanets::GetReadyState() + const { + return ready_state_; +} + +blink::WebString WebMediaPlayerCastanets::GetErrorMessage() const { + return blink::WebString::FromUTF8(media_log_->GetErrorMessage()); +} + +bool WebMediaPlayerCastanets::DidLoadingProgress() { + if (did_loading_progress_) { + did_loading_progress_ = false; + return true; + } + return false; +} + +bool WebMediaPlayerCastanets::DidGetOpaqueResponseFromServiceWorker() const { + NOTIMPLEMENTED(); + return false; +} + +bool WebMediaPlayerCastanets::HasSingleSecurityOrigin() const { + NOTIMPLEMENTED(); + return true; +} + +bool WebMediaPlayerCastanets::DidPassCORSAccessCheck() const { + NOTIMPLEMENTED(); + return false; +} + +double WebMediaPlayerCastanets::MediaTimeForTimeValue(double timeValue) const { + return base::TimeDelta::FromSecondsD(timeValue).InSecondsF(); +} + +unsigned WebMediaPlayerCastanets::DecodedFrameCount() const { + NOTIMPLEMENTED(); + return 0; +} + +unsigned WebMediaPlayerCastanets::DroppedFrameCount() const { + NOTIMPLEMENTED(); + return 0; +} + +size_t WebMediaPlayerCastanets::AudioDecodedByteCount() const { + NOTIMPLEMENTED(); + return 0; +} + +size_t WebMediaPlayerCastanets::VideoDecodedByteCount() const { + NOTIMPLEMENTED(); + return 0; +}; + +bool WebMediaPlayerCastanets::CopyVideoTextureToPlatformTexture( + gpu::gles2::GLES2Interface* gl, + unsigned int target, + unsigned int texture, + unsigned internal_format, + unsigned format, + unsigned type, + int level, + bool premultiply_alpha, + bool flip_y, + int already_uploaded_id, + VideoFrameUploadMetadata* out_metadata) { + NOTIMPLEMENTED(); + return false; +} + +void WebMediaPlayerCastanets::SetReadyState( + blink::WebMediaPlayer::ReadyState state) { + ready_state_ = state; + client_->ReadyStateChanged(); +} + +void WebMediaPlayerCastanets::SetNetworkState( + blink::WebMediaPlayer::NetworkState state) { + network_state_ = state; + client_->NetworkStateChanged(); +} + +void WebMediaPlayerCastanets::SetContentDecryptionModule( + blink::WebContentDecryptionModule* cdm, + blink::WebContentDecryptionModuleResult result) {} + +void WebMediaPlayerCastanets::EnteredFullscreen() { + if (is_fullscreen_) + return; + + is_fullscreen_ = true; + + manager_->EnteredFullscreen(player_id_); + if (HasVideo()) { + CreateVideoHoleFrame(); + } +} + +void WebMediaPlayerCastanets::ExitedFullscreen() { + if (!is_fullscreen_) + return; + + is_fullscreen_ = false; + + if (HasVideo()) { + gfx::Size size(video_width_, video_height_); + scoped_refptr video_frame = VideoFrame::CreateBlackFrame(size); + FrameReady(video_frame); + } + + manager_->ExitedFullscreen(player_id_); + client_->Repaint(); +} + +void WebMediaPlayerCastanets::FrameReady( + const scoped_refptr& frame) { + compositor_->PaintSingleFrame(frame); +} + +void WebMediaPlayerCastanets::CreateVideoHoleFrame() { + gfx::Size size(video_width_, video_height_); + + scoped_refptr video_frame = VideoFrame::CreateHoleFrame(size); + if (video_frame) + FrameReady(video_frame); +} + +void WebMediaPlayerCastanets::OnDrawableContentRectChanged(gfx::Rect rect, + bool is_video) { + LOG(INFO) << __FUNCTION__ << "Player[" << player_id_ + << "] rect :" << rect.ToString(); + is_draw_ready_ = true; + + StopLayerBoundUpdateTimer(); + gfx::RectF rect_f = static_cast(rect); + if (manager_) + manager_->SetMediaGeometry(player_id_, rect_f); + + if (pending_play_) + Play(); +} + +bool WebMediaPlayerCastanets::UpdateBoundaryRectangle() { + if (!video_layer_) + return false; + + // Compute the geometry of video frame layer. + cc::Layer* layer = video_layer_.get(); + gfx::RectF rect(gfx::SizeF(layer->bounds())); + while (layer) { + rect.Offset(layer->position().OffsetFromOrigin()); + rect.Offset(layer->CurrentScrollOffset().x() * (-1), + layer->CurrentScrollOffset().y() * (-1)); + layer = layer->parent(); + } + + // Compute the real pixs if frame scaled. + rect.Scale(frame_->View()->PageScaleFactor()); + + // Return false when the geometry hasn't been changed from the last time. + if (last_computed_rect_ == rect) + return false; + + // Store the changed geometry information when it is actually changed. + last_computed_rect_ = rect; + return true; +} + +const gfx::RectF WebMediaPlayerCastanets::GetBoundaryRectangle() { + LOG(INFO) << __FUNCTION__ << "Player[" << player_id_ + << "] rect :" << last_computed_rect_.ToString(); + return last_computed_rect_; +} + +void WebMediaPlayerCastanets::StartLayerBoundUpdateTimer() { + if (layer_bound_update_timer_.IsRunning()) + return; + + layer_bound_update_timer_.Start( + FROM_HERE, kLayerBoundUpdateInterval, this, + &WebMediaPlayerCastanets::OnLayerBoundUpdateTimerFired); +} + +void WebMediaPlayerCastanets::StopLayerBoundUpdateTimer() { + if (layer_bound_update_timer_.IsRunning()) + layer_bound_update_timer_.Stop(); +} + +void WebMediaPlayerCastanets::OnLayerBoundUpdateTimerFired() { + if (UpdateBoundaryRectangle()) { + if (manager_) { + manager_->SetMediaGeometry(player_id_, GetBoundaryRectangle()); + StopLayerBoundUpdateTimer(); + } + } +} + +void WebMediaPlayerCastanets::OnMediaDataChange(int width, + int height, + int media) { + video_height_ = height; + video_width_ = width; + audio_ = media & static_cast(MediaType::Audio) ? true : false; + video_ = media & static_cast(MediaType::Video) ? true : false; + natural_size_ = gfx::Size(width, height); + if (HasVideo() && !video_layer_) { + video_layer_ = + cc::VideoLayer::Create(compositor_.get(), media::VIDEO_ROTATION_0); + video_layer_->SetContentsOpaque(opaque_); + client_->SetCcLayer(video_layer_.get()); + } + + CreateVideoHoleFrame(); + StartLayerBoundUpdateTimer(); +} + +void WebMediaPlayerCastanets::OnTimeChanged() { + client_->TimeChanged(); +} + +void WebMediaPlayerCastanets::OnDurationChange(base::TimeDelta Duration) { + duration_ = Duration; + client_->DurationChanged(); +} + +void WebMediaPlayerCastanets::OnNaturalSizeChanged(gfx::Size size) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK_NE(ready_state_, blink::WebMediaPlayer::kReadyStateHaveNothing); + media_log_->AddEvent( + media_log_->CreateVideoSizeSetEvent(size.width(), size.height())); + natural_size_ = size; + + client_->SizeChanged(); +} + +void WebMediaPlayerCastanets::OnOpacityChanged(bool opaque) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK_NE(ready_state_, blink::WebMediaPlayer::kReadyStateHaveNothing); + + opaque_ = opaque; + if (video_layer_) + video_layer_->SetContentsOpaque(opaque_); +} + +scoped_refptr +WebMediaPlayerCastanets::GetCurrentFrameFromCompositor() const { + // Can be null. + scoped_refptr video_frame = + compositor_->GetCurrentFrameOnAnyThread(); + + compositor_task_runner_->PostTask( + FROM_HERE, + base::Bind(&media::VideoFrameCompositor::UpdateCurrentFrameIfStale, + base::Unretained(compositor_.get()))); + + return video_frame; +} + +void WebMediaPlayerCastanets::OnTimeUpdate(base::TimeDelta current_time) { + current_time_ = current_time; +} + +void WebMediaPlayerCastanets::OnBufferingUpdate(int percentage) { + buffered_[0].end = Duration() * percentage / 100; + did_loading_progress_ = true; +} + +void WebMediaPlayerCastanets::OnPauseStateChange(bool state) { + if (is_paused_ == state) + return; + + is_paused_ = state; + if (is_paused_) + client_->RequestPause(); + else + client_->RequestPlay(); + + if (!delegate_) + return; + + if (is_paused_) { + delegate_->DidPause(delegate_id_); + } else { + delegate_->DidPlay(delegate_id_, HasVideo(), HasAudio(), + media::DurationToMediaContentType(duration_)); + } +} + +void WebMediaPlayerCastanets::OnPlayerSuspend(bool is_preempted) { + if (!is_paused_ && is_preempted) { + OnPauseStateChange(true); + } + + if (!delegate_) + return; + delegate_->PlayerGone(delegate_id_); +} + +void WebMediaPlayerCastanets::OnPlayerResumed(bool is_preempted) { + if (!delegate_) + return; + + if (is_paused_) + delegate_->DidPause(delegate_id_); + else + delegate_->DidPlay(delegate_id_, HasVideo(), HasAudio(), + media::DurationToMediaContentType(duration_)); +} + +void WebMediaPlayerCastanets::OnPlayerDestroyed() { + NOTIMPLEMENTED(); +} + +void WebMediaPlayerCastanets::OnSeekComplete() { + LOG(INFO) << __FUNCTION__ << "Player[" << player_id_ << "]" + << " seconds :" << seek_time_.InSecondsF(); + is_seeking_ = false; + seek_time_ = base::TimeDelta(); + + CreateVideoHoleFrame(); + client_->TimeChanged(); +} + +void WebMediaPlayerCastanets::OnSeekRequest(base::TimeDelta seek_time) { + client_->RequestSeek(seek_time.InSecondsF()); +} + +} // namespace content diff --git a/content/renderer/media/castanets/castanets_webmediaplayer_impl.h b/content/renderer/media/castanets/castanets_webmediaplayer_impl.h new file mode 100644 index 00000000000..7e3072e5ffb --- /dev/null +++ b/content/renderer/media/castanets/castanets_webmediaplayer_impl.h @@ -0,0 +1,351 @@ +// Copyright 2019 Samsung Electronics Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_RENDERER_MEDIA_CASTANETS_CASTANETS_WEBMEDIAPLAYER_IMPL_H_ +#define CONTENT_RENDERER_MEDIA_CASTANETS_CASTANETS_WEBMEDIAPLAYER_IMPL_H_ + +#include "media/blink/renderer_media_player_interface.h" +#include "media/blink/video_frame_compositor.h" +#include "media/renderers/paint_canvas_video_renderer.h" + +namespace blink { +class WebLocalFrame; +class WebMediaPlayerClient; +class WebMediaPlayerEncryptedMediaClient; +} // namespace blink + +namespace base { +class SingleThreadTaskRunner; +} // namespace base + +namespace cc { +class VideoLayer; +} + +namespace media { +class MediaLog; +class RendererMediaPlayerManagerInterface; +class UrlIndex; +class WebAudioSourceProviderImpl; +class WebMediaPlayerDelegate; +} // namespace media + +namespace content { +class RendererMediaPlayerManager; + +// This class implements blink::WebMediaPlayer by keeping the castanets +// media player in the browser process. It listens to all the status changes +// sent from the browser process and sends playback controls to the media +// player. +class WebMediaPlayerCastanets + : public blink::WebMediaPlayer, + public media::RendererMediaPlayerInterface, + public media::WebMediaPlayerDelegate::Observer, + public base::SupportsWeakPtr { + public: + // Construct a WebMediaPlayerCastanets object. This class communicates + // with the WebMediaPlayerCastanets object in the browser process through + // |proxy|. + WebMediaPlayerCastanets( + blink::WebLocalFrame* frame, + blink::WebMediaPlayerClient* client, + blink::WebMediaPlayerEncryptedMediaClient* encrypted_client, + media::WebMediaPlayerDelegate* delegate, + media::UrlIndex* url_index, + std::unique_ptr compositor, + std::unique_ptr params); + ~WebMediaPlayerCastanets() override; + + LoadTiming Load(LoadType, + const blink::WebMediaPlayerSource&, + CORSMode) override; + + // Playback controls. + void Play() override; + void Pause() override; + void Seek(double seconds) override; + void SetRate(double rate) override; + void SetVolume(double volume) override; + + // Enter Picture-in-Picture and notifies Blink with window size + // when video successfully enters Picture-in-Picture. + void EnterPictureInPicture(PipWindowOpenedCallback) override; + + // Exit Picture-in-Picture and notifies Blink when it's done. + void ExitPictureInPicture(PipWindowClosedCallback) override; + + // Register a callback that will be run when the Picture-in-Picture window + // is resized. + void RegisterPictureInPictureWindowResizeCallback( + PipWindowResizedCallback) override; + + blink::WebTimeRanges Buffered() const override; + blink::WebTimeRanges Seekable() const override; + + // Attempts to switch the audio output device. + // Implementations of SetSinkId take ownership of the WebSetSinkCallbacks + // object. + // Note also that SetSinkId implementations must make sure that all + // methods of the WebSetSinkCallbacks object, including constructors and + // destructors, run in the same thread where the object is created + // (i.e., the blink thread). + void SetSinkId(const blink::WebString& sink_id, + blink::WebSetSinkIdCallbacks*) override; + + // True if the loaded media has a playable video/audio track. + bool HasVideo() const override; + bool HasAudio() const override; + + // Dimension of the video. + blink::WebSize NaturalSize() const override; + + blink::WebSize VisibleRect() const override; + + // Getters of playback state. + bool Paused() const override; + bool Seeking() const override; + double Duration() const override; + double CurrentTime() const override; + + // Internal states of loading and network. + NetworkState GetNetworkState() const override; + ReadyState GetReadyState() const override; + + // Returns an implementation-specific human readable error message, or an + // empty string if no message is available. The message should begin with a + // UA-specific-error-code (without any ':'), optionally followed by ': ' and + // further description of the error. + blink::WebString GetErrorMessage() const override; + + bool DidLoadingProgress() override; + + bool DidGetOpaqueResponseFromServiceWorker() const override; + bool HasSingleSecurityOrigin() const override; + bool DidPassCORSAccessCheck() const override; + + double MediaTimeForTimeValue(double time_value) const override; + + unsigned DecodedFrameCount() const override; + unsigned DroppedFrameCount() const override; + size_t AudioDecodedByteCount() const override; + size_t VideoDecodedByteCount() const override; + + bool CopyVideoTextureToPlatformTexture( + gpu::gles2::GLES2Interface* gl, + unsigned int target, + unsigned int texture, + unsigned internal_format, + unsigned format, + unsigned type, + int level, + bool premultiply_alpha, + bool flip_y, + int already_uploaded_id, + VideoFrameUploadMetadata* out_metadata) override; + + // |out_metadata|, if set, is used to return metadata about the frame that is + // uploaded during this call. |already_uploaded_id| indicates the unique_id of + // the frame last uploaded to this destination. It should only be set by the + // caller if the contents of the destination are known not to have changed + // since that upload. - If |out_metadata| is not null, |already_uploaded_id| + // is compared with the unique_id of the frame being uploaded. If it's the + // same, the upload may be skipped and considered to be successful. + void Paint(cc::PaintCanvas* canvas, + const blink::WebRect& rect, + cc::PaintFlags& flags, + int already_uploaded_id, + VideoFrameUploadMetadata* out_metadata) override; + + void SetContentDecryptionModule( + blink::WebContentDecryptionModule* cdm, + blink::WebContentDecryptionModuleResult result) override; + + void EnteredFullscreen() override; + void ExitedFullscreen() override; + + // WebMediaPlayerDelegate::Observer implementation. + void OnFrameHidden() override; + void OnFrameClosed() override; + void OnFrameShown() override; + void OnIdleTimeout() override; + void OnPlay() override; + void OnPause() override; + void OnSeekForward(double seconds) override; + void OnSeekBackward(double seconds) override; + void OnVolumeMultiplierUpdate(double multiplier) override; + void OnBecamePersistentVideo(bool value) override; + void OnPictureInPictureModeEnded() override; + void OnPictureInPictureControlClicked(const std::string& control_id) override; + + // RendererMediaPlayerInterface implementation + void OnMediaMetadataChanged(base::TimeDelta duration, + int width, + int height, + bool success) override {} + void OnPlaybackComplete() override {} + void OnSeekComplete(base::TimeDelta current_time) override {} + void OnMediaError(int error_type) override {} + void OnVideoSizeChanged(int width, int height) override {} + void OnTimeUpdate(base::TimeDelta current_timestamp, + base::TimeTicks current_time_ticks) override {} + void OnPlayerReleased() override {} + void OnConnectedToRemoteDevice( + const std::string& remote_playback_message) override {} + void OnDisconnectedFromRemoteDevice() override {} + void OnCancelledRemotePlaybackRequest() override {} + void OnRemotePlaybackStarted() override {} + void OnDidExitFullscreen() override {} + void OnMediaPlayerPlay() override {} + void OnMediaPlayerPause() override {} + void OnRemoteRouteAvailabilityChanged( + blink::WebRemotePlaybackAvailability availability) override {} + + void OnMediaDataChange(int, int, int) override; + void OnDurationChange(base::TimeDelta) override; + + // Called after a seek request is complete. Current time can be different + // from the requested seek time. + void OnTimeChanged() override; + void OnTimeUpdate(base::TimeDelta) override; + void OnBufferingUpdate(int) override; + void OnPauseStateChange(bool) override; + + void OnSeekRequest(base::TimeDelta time_to_seek) override; + + // Internal seeks can happen. So don't include time as argument. + void OnSeekComplete() override; + + void OnPlayerSuspend(bool) override; + void OnPlayerResumed(bool) override; + void OnPlayerDestroyed() override; + + void SetReadyState(WebMediaPlayer::ReadyState) override; + void SetNetworkState(WebMediaPlayer::NetworkState) override; + + void SuspendAndReleaseResources() override; + + void SetMediaPlayerManager( + media::RendererMediaPlayerManagerInterface* media_player_manager); + + void RequestPause(); + void ReleaseMediaResource(); + void InitializeMediaResource(); + + void CreateVideoHoleFrame(); + void OnDrawableContentRectChanged(gfx::Rect rect, bool is_video); + + void StartLayerBoundUpdateTimer(); + void StopLayerBoundUpdateTimer(); + void OnLayerBoundUpdateTimerFired(); + + private: + // Called after |defer_load_cb_| has decided to allow the load. If + // |defer_load_cb_| is null this is called immediately. + void DoLoad(LoadType load_type, const blink::WebURL& url); + void PauseInternal(bool is_media_related_action); + + void OnNaturalSizeChanged(gfx::Size size); + void OnOpacityChanged(bool opaque); + + // Returns the current video frame from |compositor_|. Blocks until the + // compositor can return the frame. + scoped_refptr GetCurrentFrameFromCompositor() const; + + // Called whenever there is new frame to be painted. + void FrameReady(const scoped_refptr& frame); + + // Calculate the boundary rectangle of the media player (i.e. location and + // size of the video frame). + // Returns true if the geometry has been changed since the last call. + bool UpdateBoundaryRectangle(); + const gfx::RectF GetBoundaryRectangle(); + + // TODO: Fix the scope! + void Resume(); + + blink::WebLocalFrame* frame_; + + blink::WebMediaPlayer::NetworkState network_state_; + blink::WebMediaPlayer::ReadyState ready_state_; + + // Message loops for posting tasks on Chrome's main thread. Also used + // for DCHECKs so methods calls won't execute in the wrong thread. + const scoped_refptr main_task_runner_; + + // Manager for managing this object and for delegating method calls on + // Render Thread. + media::RendererMediaPlayerManagerInterface* manager_; + + blink::WebMediaPlayerClient* client_; + + std::unique_ptr media_log_; + + media::WebMediaPlayerDelegate* const delegate_; + int delegate_id_; + + media::WebMediaPlayerParams::DeferLoadCB defer_load_cb_; + + // Video rendering members. + // The |compositor_| runs on the compositor thread, or if + // kEnableSurfaceLayerForVideo is enabled, the media thread. This task runner + // posts tasks for the |compositor_| on the correct thread. + scoped_refptr compositor_task_runner_; + + // Deleted on |compositor_task_runner_|. + std::unique_ptr compositor_; + + // The compositor layer for displaying the video content when using composited + // playback. + scoped_refptr video_layer_; + + MediaPlayerHostMsg_Initialize_Type player_type_; + + // Player ID assigned by the |manager_|. + int player_id_; + + int video_width_; + int video_height_; + + bool audio_; + bool video_; + + base::TimeDelta current_time_; + base::TimeDelta duration_; + bool is_paused_; + + bool is_seeking_; + base::TimeDelta seek_time_; + bool pending_seek_; + base::TimeDelta pending_seek_time_; + + // Whether the video is known to be opaque or not. + bool opaque_; + bool is_fullscreen_; + + bool is_draw_ready_; + bool pending_play_; + + // A rectangle represents the geometry of video frame, when computed last + // time. + gfx::RectF last_computed_rect_; + base::RepeatingTimer layer_bound_update_timer_; + + gfx::Size natural_size_; + blink::WebTimeRanges buffered_; + mutable bool did_loading_progress_; + + // The last volume received by setVolume() and the last volume multiplier from + // OnVolumeMultiplierUpdate(). The multiplier is typical 1.0, but may be less + // if the WebMediaPlayerDelegate has requested a volume reduction (ducking) + // for a transient sound. Playout volume is derived by volume * multiplier. + double volume_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(WebMediaPlayerCastanets); +}; + +} // namespace content + +#endif // CONTENT_RENDERER_MEDIA_CASTANETS_CASTANETS_WEBMEDIAPLAYER_IMPL_H_ diff --git a/content/renderer/media/media_factory.cc b/content/renderer/media/media_factory.cc index fe25c6b87ab..cee65bd2fe9 100644 --- a/content/renderer/media/media_factory.cc +++ b/content/renderer/media/media_factory.cc @@ -79,6 +79,12 @@ #include "media/remoting/renderer_controller.h" // nogncheck #endif +#if defined(CASTANETS) +#include "base/base_switches.h" +#include "content/renderer/media/castanets/castanets_renderer_media_player_manager.h" +#include "content/renderer/media/castanets/castanets_webmediaplayer_impl.h" +#endif + namespace { class FrameFetchContext : public media::ResourceFetchContext { public: @@ -206,7 +212,12 @@ blink::WebMediaPlayer* MediaFactory::CreateMediaPlayer( blink::WebContentDecryptionModule* initial_cdm, const blink::WebString& sink_id, blink::WebLayerTreeView* layer_tree_view, - const cc::LayerTreeSettings& settings) { + const cc::LayerTreeSettings& settings +#if defined(VIDEO_HOLE) + , + bool is_video_hole +#endif + ) { blink::WebLocalFrame* web_frame = render_frame_->GetWebFrame(); blink::WebSecurityOrigin security_origin = render_frame_->GetWebFrame()->GetSecurityOrigin(); @@ -346,6 +357,18 @@ blink::WebMediaPlayer* MediaFactory::CreateMediaPlayer( std::make_unique( params->video_frame_compositor_task_runner(), std::move(submitter)); +#if defined(CASTANETS) && defined(VIDEO_HOLE) + if (!base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableForking) && + is_video_hole) { + WebMediaPlayerCastanets* player = new WebMediaPlayerCastanets( + web_frame, client, encrypted_client, GetWebMediaPlayerDelegate(), + url_index_.get(), std::move(vfc), std::move(params)); + player->SetMediaPlayerManager(GetCastanetsMediaPlayerManager()); + return player; + } +#endif + media::WebMediaPlayerImpl* media_player = new media::WebMediaPlayerImpl( web_frame, client, encrypted_client, GetWebMediaPlayerDelegate(), std::move(factory_selector), url_index_.get(), std::move(vfc), @@ -560,6 +583,16 @@ media::DecoderFactory* MediaFactory::GetDecoderFactory() { return decoder_factory_.get(); } +#if defined(CASTANETS) +CastanetsRendererMediaPlayerManager* +MediaFactory::GetCastanetsMediaPlayerManager() { + if (!castanets_media_player_manager_) + castanets_media_player_manager_ = + new CastanetsRendererMediaPlayerManager(render_frame_); + return castanets_media_player_manager_; +} +#endif + #if defined(OS_ANDROID) RendererMediaPlayerManager* MediaFactory::GetMediaPlayerManager() { if (!media_player_manager_) diff --git a/content/renderer/media/media_factory.h b/content/renderer/media/media_factory.h index 7c4ea973a11..66031e2926e 100644 --- a/content/renderer/media/media_factory.h +++ b/content/renderer/media/media_factory.h @@ -68,6 +68,10 @@ class MediaStreamRendererFactory; class RendererMediaPlayerManager; #endif +#if defined(CASTANETS) +class CastanetsRendererMediaPlayerManager; +#endif + // Assist to RenderFrameImpl in creating various media clients. class MediaFactory { public: @@ -103,7 +107,12 @@ class MediaFactory { blink::WebContentDecryptionModule* initial_cdm, const blink::WebString& sink_id, blink::WebLayerTreeView* layer_tree_view, - const cc::LayerTreeSettings& settings); + const cc::LayerTreeSettings& settings +#if defined(VIDEO_HOLE) + , + bool video_hole +#endif + ); // Provides an EncryptedMediaClient to connect blink's EME layer to media's // implementation of requestMediaKeySystemAccess. Will always return the same @@ -138,6 +147,9 @@ class MediaFactory { #if defined(OS_ANDROID) RendererMediaPlayerManager* GetMediaPlayerManager(); #endif +#if defined(CASTANETS) + CastanetsRendererMediaPlayerManager* GetCastanetsMediaPlayerManager(); +#endif #if BUILDFLAG(ENABLE_MEDIA_REMOTING) media::mojom::RemoterFactory* GetRemoterFactory(); @@ -174,6 +186,11 @@ class MediaFactory { RendererMediaPlayerManager* media_player_manager_ = nullptr; #endif +#if defined(CASTANETS) + CastanetsRendererMediaPlayerManager* castanets_media_player_manager_ = + nullptr; +#endif + // Manages play, pause notifications for WebMediaPlayer implementations; its // lifetime is tied to the RenderFrame via the RenderFrameObserver interface. media::RendererWebMediaPlayerDelegate* media_player_delegate_ = nullptr; diff --git a/content/renderer/media/stream/webmediaplayer_ms_compositor.h b/content/renderer/media/stream/webmediaplayer_ms_compositor.h index 002b4c413fe..713eeff3777 100644 --- a/content/renderer/media/stream/webmediaplayer_ms_compositor.h +++ b/content/renderer/media/stream/webmediaplayer_ms_compositor.h @@ -79,6 +79,12 @@ class CONTENT_EXPORT WebMediaPlayerMSCompositor scoped_refptr GetCurrentFrame() override; void PutCurrentFrame() override; +#if defined(VIDEO_HOLE) + void SetDrawableContentRectChangedCallback( + cc::DrawableContentRectChangedCallback) override {} + void OnDrawableContentRectChanged(const gfx::Rect&) override {} +#endif + // Return the current frame being rendered. // Difference between GetCurrentFrame(): GetCurrentFrame() is designed for // chrome compositor to pull frame from WebMediaPlayerMSCompositor, and thus diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index 9571ea0233e..20f43d48110 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc @@ -1444,6 +1444,9 @@ RenderFrameImpl::RenderFrameImpl(CreateParams params) selection_range_(gfx::Range::InvalidRange()), handling_select_range_(false), web_user_media_client_(nullptr), +#if defined(VIDEO_HOLE) + contains_media_player_(false), +#endif push_messaging_client_(nullptr), render_accessibility_(nullptr), previews_state_(PREVIEWS_UNSPECIFIED), @@ -1531,6 +1534,11 @@ RenderFrameImpl::~RenderFrameImpl() { base::trace_event::TraceLog::GetInstance()->RemoveProcessLabel(routing_id_); +#if defined(VIDEO_HOLE) + if (contains_media_player_) + render_view_->UnregisterVideoHoleFrame(this); +#endif + if (auto* factory = AudioOutputIPCFactory::get()) factory->MaybeDeregisterRemoteFactory(GetRoutingID()); @@ -3493,11 +3501,27 @@ blink::WebMediaPlayer* RenderFrameImpl::CreateMediaPlayer( WebContentDecryptionModule* initial_cdm, const blink::WebString& sink_id, blink::WebLayerTreeView* layer_tree_view) { +#if defined(VIDEO_HOLE) + bool is_video_hole = false; + if (frame_ && frame_->View()) + is_video_hole = frame_->View()->IsVideoHoleForRender(); + + if (!contains_media_player_) { + render_view_->RegisterVideoHoleFrame(this); + contains_media_player_ = true; + } +#endif + const cc::LayerTreeSettings& settings = GetRenderWidget()->layer_tree_view()->GetLayerTreeSettings(); return media_factory_.CreateMediaPlayer(source, client, encrypted_client, initial_cdm, sink_id, layer_tree_view, - settings); + settings +#if defined(VIDEO_HOLE) + , + is_video_hole +#endif + ); } std::unique_ptr diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h index cb5ffc56b00..5369762915d 100644 --- a/content/renderer/render_frame_impl.h +++ b/content/renderer/render_frame_impl.h @@ -1467,6 +1467,11 @@ class CONTENT_EXPORT RenderFrameImpl // The media permission dispatcher attached to this frame. std::unique_ptr media_permission_dispatcher_; +#if defined(VIDEO_HOLE) + // Used to register as an observer for video-hole-specific events. + bool contains_media_player_; +#endif + // The PushMessagingClient attached to this frame, lazily initialized. PushMessagingClient* push_messaging_client_; diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc index 03d9cda9f95..fcc6b0812a3 100644 --- a/content/renderer/render_view_impl.cc +++ b/content/renderer/render_view_impl.cc @@ -828,7 +828,8 @@ void RenderView::ApplyWebPreferences(const WebPreferences& prefs, settings->SetTextAutosizingEnabled(prefs.text_autosizing_enabled); settings->SetDoubleTapToZoomEnabled(prefs.double_tap_to_zoom_enabled); -#if defined(OS_ANDROID) +// TODO (sm.venugopal): Should be enabled based on target running the browser. +#if defined(OS_ANDROID) && !defined(CASTANETS) settings->SetAllowCustomScrollbarInMainFrame(false); settings->SetAccessibilityFontScaleFactor(prefs.font_scale_factor); settings->SetDeviceScaleAdjustment(prefs.device_scale_adjustment); @@ -983,6 +984,10 @@ void RenderView::ApplyWebPreferences(const WebPreferences& prefs, } } +#if defined(VIDEO_HOLE) + settings->SetVideoHoleEnabled(prefs.video_hole_enabled); +#endif + #if defined(OS_MACOSX) settings->SetDoubleTapToZoomEnabled(true); web_view->SetMaximumLegibleScale(prefs.default_maximum_page_scale_factor); diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc index 560c7caaa74..b7365a56dfb 100644 --- a/content/renderer/render_widget.cc +++ b/content/renderer/render_widget.cc @@ -2919,6 +2919,16 @@ void RenderWidget::UnregisterBrowserPlugin(BrowserPlugin* browser_plugin) { browser_plugins_.RemoveObserver(browser_plugin); } +#if defined(VIDEO_HOLE) +void RenderWidget::RegisterVideoHoleFrame(RenderFrameImpl* frame) { + video_hole_frames_.AddObserver(frame); +} + +void RenderWidget::UnregisterVideoHoleFrame(RenderFrameImpl* frame) { + video_hole_frames_.RemoveObserver(frame); +} +#endif + void RenderWidget::OnWaitNextFrameForTests(int routing_id) { QueueMessage(new ViewHostMsg_WaitForNextFrameForTests_ACK(routing_id), MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE); diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h index ab52a4efb1c..09280297c79 100644 --- a/content/renderer/render_widget.h +++ b/content/renderer/render_widget.h @@ -249,6 +249,11 @@ class CONTENT_EXPORT RenderWidget void RegisterBrowserPlugin(BrowserPlugin* browser_plugin); void UnregisterBrowserPlugin(BrowserPlugin* browser_plugin); +#if defined(VIDEO_HOLE) + void RegisterVideoHoleFrame(RenderFrameImpl* frame); + void UnregisterVideoHoleFrame(RenderFrameImpl* frame); +#endif + // IPC::Listener bool OnMessageReceived(const IPC::Message& msg) override; @@ -854,6 +859,10 @@ class CONTENT_EXPORT RenderWidget // compositing-related events (e.g. DidCommitCompositorFrame). base::ObserverList render_frame_proxies_; +#if defined(VIDEO_HOLE) + base::ObserverList video_hole_frames_; +#endif + // A list of RenderFrames associated with this RenderWidget. Notifications // are sent to each frame in the list for events such as changing // visibility state for example. diff --git a/ipc/ipc_message_start.h b/ipc/ipc_message_start.h index 9c829d3cfea..8f2dea5b7cc 100644 --- a/ipc/ipc_message_start.h +++ b/ipc/ipc_message_start.h @@ -42,6 +42,9 @@ enum IPCMessageStart { BrowserPluginMsgStart, AndroidWebViewMsgStart, MediaPlayerMsgStart, +#if defined(CASTANETS) + MediaPlayerCastanetsMsgStart, +#endif TracingMsgStart, PeerConnectionTrackerMsgStart, AppShimMsgStart, @@ -71,6 +74,7 @@ enum IPCMessageStart { SurfaceViewManagerMsgStart, ExtensionWorkerMsgStart, SubresourceFilterMsgStart, + LastIPCMsgStart // Must come last. }; diff --git a/media/base/video_frame.cc b/media/base/video_frame.cc index 54a59242e50..f51e859efbe 100644 --- a/media/base/video_frame.cc +++ b/media/base/video_frame.cc @@ -67,6 +67,10 @@ static std::string StorageTypeToString( #if defined(OS_LINUX) case VideoFrame::STORAGE_DMABUFS: return "DMABUFS"; +#endif +#if defined(VIDEO_HOLE) + case VideoFrame::STORAGE_HOLE: + return "HOLE"; #endif case VideoFrame::STORAGE_MOJO_SHARED_BUFFER: return "MOJO_SHARED_BUFFER"; @@ -560,6 +564,23 @@ scoped_refptr VideoFrame::CreateTransparentFrame( return frame; } +#if defined(VIDEO_HOLE) +// static +scoped_refptr VideoFrame::CreateHoleFrame(const gfx::Size& size) { + const VideoPixelFormat format = PIXEL_FORMAT_UNKNOWN; + const StorageType storage = STORAGE_HOLE; + const gfx::Rect visible_rect = gfx::Rect(size); + if (!IsValidConfig(format, storage, size, visible_rect, size)) { + LOG(DFATAL) << __func__ << " Invalid config." + << ConfigToString(format, storage, size, visible_rect, size); + return nullptr; + } + scoped_refptr frame(new VideoFrame( + format, storage, size, gfx::Rect(size), size, base::TimeDelta())); + return frame; +} +#endif + // static size_t VideoFrame::NumPlanes(VideoPixelFormat format) { switch (format) { diff --git a/media/base/video_frame.h b/media/base/video_frame.h index d70e19cc7a0..7ca075ebe12 100644 --- a/media/base/video_frame.h +++ b/media/base/video_frame.h @@ -77,7 +77,13 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe { STORAGE_DMABUFS = 5, // Each plane is stored into a DmaBuf. #endif STORAGE_MOJO_SHARED_BUFFER = 6, +#if defined(VIDEO_HOLE) + // Indicates protected media that needs to be directly rendered to HW. + STORAGE_HOLE = 7, + STORAGE_LAST = STORAGE_HOLE, +#else STORAGE_LAST = STORAGE_MOJO_SHARED_BUFFER, +#endif }; // CB to be called on the mailbox backing this frame when the frame is @@ -286,6 +292,11 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe { static scoped_refptr CreateTransparentFrame( const gfx::Size& size); +#if defined(VIDEO_HOLE) + // Allocates a hole frame. + static scoped_refptr CreateHoleFrame(const gfx::Size& size); +#endif + static size_t NumPlanes(VideoPixelFormat format); // Returns the required allocation size for a (tightly packed) frame of the diff --git a/media/blink/renderer_media_player_interface.h b/media/blink/renderer_media_player_interface.h index d95338d17fd..139cfef3f87 100644 --- a/media/blink/renderer_media_player_interface.h +++ b/media/blink/renderer_media_player_interface.h @@ -23,11 +23,25 @@ enum class WebRemotePlaybackAvailability; // Dictates which type of media playback is being initialized. enum MediaPlayerHostMsg_Initialize_Type { +#if defined(CASTANETS) + MEDIA_PLAYER_TYPE_NONE, + MEDIA_PLAYER_TYPE_MEDIA_SOURCE, + MEDIA_PLAYER_TYPE_URL_WITH_VIDEO_HOLE, + MEDIA_PLAYER_TYPE_MEDIA_SOURCE_WITH_VIDEO_HOLE, +#endif MEDIA_PLAYER_TYPE_URL, MEDIA_PLAYER_TYPE_REMOTE_ONLY, MEDIA_PLAYER_TYPE_LAST = MEDIA_PLAYER_TYPE_REMOTE_ONLY }; +#if defined(CASTANETS) +enum class MediaType { + Video = 0x1, + Audio = Video << 1, + Text = Video << 2, +}; +#endif + namespace media { class RendererMediaPlayerInterface { @@ -65,6 +79,27 @@ class RendererMediaPlayerInterface { // video and release the media player and surface texture when we switch tabs. // However, the actual GlTexture is not released to keep the video screenshot. virtual void SuspendAndReleaseResources() = 0; + +#if defined(CASTANETS) + virtual void OnMediaDataChange(int, int, int) {} + virtual void OnDurationChange(base::TimeDelta) {} + + // Called after a seek request is complete. Current time can be different + // from the requested seek time. + virtual void OnTimeChanged() {} + virtual void OnTimeUpdate(base::TimeDelta) {} + + // TODO(sm.venugopal): Check and remove. + // void OnBufferUpdate(base::TimeDelta current_time){} + virtual void OnPauseStateChange(bool) {} + virtual void OnSeekComplete() {} + virtual void OnPlayerSuspend(bool) {} + virtual void OnPlayerResumed(bool) {} + virtual void OnPlayerDestroyed() {} + + virtual void SetReadyState(blink::WebMediaPlayer::ReadyState) {} + virtual void SetNetworkState(blink::WebMediaPlayer::NetworkState) {} +#endif }; class RendererMediaPlayerManagerInterface { @@ -115,6 +150,22 @@ class RendererMediaPlayerManagerInterface { // Registers and unregisters a RendererMediaPlayerInterface object. virtual int RegisterMediaPlayer(RendererMediaPlayerInterface* player) = 0; virtual void UnregisterMediaPlayer(int player_id) = 0; + +#if defined(CASTANETS) + virtual void Initialize(int player_id, + MediaPlayerHostMsg_Initialize_Type type, + const GURL& url, + const std::string& mime_type, + int demuxer_client_id) {} + virtual void SetRate(int player_id, double rate) {} + virtual void Suspend(int player_id) {} + virtual void Resume(int player_id) {} + virtual void Activate(int player_id) {} + virtual void Deactivate(int player_id) {} + virtual void EnteredFullscreen(int player_id) {} + virtual void ExitedFullscreen(int player_id) {} + virtual void SetMediaGeometry(int player_id, const gfx::RectF& rect) {} +#endif }; } // namespace media diff --git a/media/blink/video_frame_compositor.cc b/media/blink/video_frame_compositor.cc index dae188a5d4b..80dddc8a8bf 100644 --- a/media/blink/video_frame_compositor.cc +++ b/media/blink/video_frame_compositor.cc @@ -343,4 +343,16 @@ void VideoFrameCompositor::UpdateIsOpaque(bool is_opaque) { submitter_->SetIsOpaque(is_opaque); } +#if defined(VIDEO_HOLE) +void VideoFrameCompositor::SetDrawableContentRectChangedCallback( + cc::DrawableContentRectChangedCallback cb) { + drawable_content_rect_changed_cb_ = std::move(cb); +} + +void VideoFrameCompositor::OnDrawableContentRectChanged(const gfx::Rect& rect) { + if (!drawable_content_rect_changed_cb_.is_null()) + drawable_content_rect_changed_cb_.Run(rect, true); +} +#endif + } // namespace media diff --git a/media/blink/video_frame_compositor.h b/media/blink/video_frame_compositor.h index bf61a04afe3..a56a8d7edb0 100644 --- a/media/blink/video_frame_compositor.h +++ b/media/blink/video_frame_compositor.h @@ -153,6 +153,12 @@ class MEDIA_BLINK_EXPORT VideoFrameCompositor : public VideoRendererSink, background_rendering_enabled_ = enabled; } +#if defined(VIDEO_HOLE) + void SetDrawableContentRectChangedCallback( + cc::DrawableContentRectChangedCallback cb) override; + void OnDrawableContentRectChanged(const gfx::Rect&) override; +#endif + void set_submitter_for_test( std::unique_ptr submitter) { submitter_ = std::move(submitter); @@ -191,6 +197,10 @@ class MEDIA_BLINK_EXPORT VideoFrameCompositor : public VideoRendererSink, base::TimeTicks deadline_max, bool background_rendering); +#if defined(VIDEO_HOLE) + cc::DrawableContentRectChangedCallback drawable_content_rect_changed_cb_; +#endif + // This will run tasks on the compositor thread. If // kEnableSurfaceLayerForVideo is enabled, it will instead run tasks on the // media thread. diff --git a/media/renderers/video_resource_updater.cc b/media/renderers/video_resource_updater.cc index 0a3c0e673b2..5d1049d5592 100644 --- a/media/renderers/video_resource_updater.cc +++ b/media/renderers/video_resource_updater.cc @@ -47,6 +47,10 @@ #include "ui/gl/gl_enums.h" #include "ui/gl/trace_util.h" +#if defined(VIDEO_HOLE) +#include "components/viz/common/quads/solid_color_draw_quad.h" +#endif + #if defined(CASTANETS) #include "mojo/public/cpp/system/sync.h" #endif @@ -519,6 +523,20 @@ void VideoResourceUpdater::AppendQuads(viz::RenderPass* render_pass, } break; } +#if defined(VIDEO_HOLE) + case VideoFrameResourceType::HOLE: { + DCHECK_EQ(frame_resources_.size(), 0u); + auto* solid_color_draw_quad = + render_pass->CreateAndAppendDrawQuad(); + + // Create a solid color quad with transparent black and force no + // blending / no anti-aliasing. + solid_color_draw_quad->SetAll(shared_quad_state, quad_rect, + visible_quad_rect, false, + SK_ColorTRANSPARENT, true); + break; + } +#endif case VideoFrameResourceType::STREAM_TEXTURE: { DCHECK_EQ(frame_resources_.size(), 1u); if (frame_resources_.size() < 1u) @@ -544,6 +562,14 @@ void VideoResourceUpdater::AppendQuads(viz::RenderPass* render_pass, VideoFrameExternalResources VideoResourceUpdater::CreateExternalResourcesFromVideoFrame( scoped_refptr video_frame) { +#if defined(VIDEO_HOLE) + if (video_frame->storage_type() == media::VideoFrame::STORAGE_HOLE) { + VideoFrameExternalResources external_resources; + external_resources.type = VideoFrameResourceType::HOLE; + return external_resources; + } +#endif + if (video_frame->format() == PIXEL_FORMAT_UNKNOWN) return VideoFrameExternalResources(); DCHECK(video_frame->HasTextures() || video_frame->IsMappable()); diff --git a/media/renderers/video_resource_updater.h b/media/renderers/video_resource_updater.h index 9ea3f503ca9..716c86b74cc 100644 --- a/media/renderers/video_resource_updater.h +++ b/media/renderers/video_resource_updater.h @@ -49,6 +49,9 @@ enum class VideoFrameResourceType { RGB, RGBA_PREMULTIPLIED, RGBA, +#if defined(VIDEO_HOLE) + HOLE, +#endif STREAM_TEXTURE, }; diff --git a/third_party/blink/public/platform/web_media_player_client.h b/third_party/blink/public/platform/web_media_player_client.h index 092556d5872..58c1fa2e26e 100644 --- a/third_party/blink/public/platform/web_media_player_client.h +++ b/third_party/blink/public/platform/web_media_player_client.h @@ -179,6 +179,10 @@ class BLINK_PLATFORM_EXPORT WebMediaPlayerClient { // Request the player to pause playback. virtual void RequestPause() = 0; +#if defined(CASTANETS) + virtual WebString GetContentMIMEType() = 0; +#endif + protected: ~WebMediaPlayerClient() = default; }; diff --git a/third_party/blink/public/web/web_settings.h b/third_party/blink/public/web/web_settings.h index 5c7b6cb74ae..f4dba385aec 100644 --- a/third_party/blink/public/web/web_settings.h +++ b/third_party/blink/public/web/web_settings.h @@ -307,6 +307,10 @@ class WebSettings { virtual void SetLazyFrameLoadingDistanceThresholdPx3G(int) = 0; virtual void SetLazyFrameLoadingDistanceThresholdPx4G(int) = 0; +#if defined(VIDEO_HOLE) + virtual void SetVideoHoleEnabled(bool) = 0; +#endif + protected: ~WebSettings() = default; }; diff --git a/third_party/blink/public/web/web_view.h b/third_party/blink/public/web/web_view.h index fcba3388093..01b1531820f 100644 --- a/third_party/blink/public/web/web_view.h +++ b/third_party/blink/public/web/web_view.h @@ -450,6 +450,11 @@ class WebView : protected WebWidget { // Pausing and unpausing current scheduled tasks. virtual void PausePageScheduledTasks(bool paused) = 0; +#if defined(VIDEO_HOLE) + virtual void SetVideoHoleForRender(bool enable) = 0; + virtual bool IsVideoHoleForRender() const = 0; +#endif + // TODO(lfg): Remove this once the refactor of WebView/WebWidget is // completed. WebWidget* GetWidget() { return this; } diff --git a/third_party/blink/renderer/core/exported/web_settings_impl.cc b/third_party/blink/renderer/core/exported/web_settings_impl.cc index 4760b2c18b4..d2dfcc29ad7 100644 --- a/third_party/blink/renderer/core/exported/web_settings_impl.cc +++ b/third_party/blink/renderer/core/exported/web_settings_impl.cc @@ -758,4 +758,10 @@ void WebSettingsImpl::SetLazyFrameLoadingDistanceThresholdPx4G( settings_->SetLazyFrameLoadingDistanceThresholdPx4G(distance_px); } +#if defined(VIDEO_HOLE) +void WebSettingsImpl::SetVideoHoleEnabled(bool enabled) { + settings_->SetVideoHoleEnabled(enabled); +} +#endif + } // namespace blink diff --git a/third_party/blink/renderer/core/exported/web_settings_impl.h b/third_party/blink/renderer/core/exported/web_settings_impl.h index 8dc554aa52b..c61cb51a9f5 100644 --- a/third_party/blink/renderer/core/exported/web_settings_impl.h +++ b/third_party/blink/renderer/core/exported/web_settings_impl.h @@ -214,6 +214,10 @@ class CORE_EXPORT WebSettingsImpl final : public WebSettings { void SetLazyFrameLoadingDistanceThresholdPx3G(int) override; void SetLazyFrameLoadingDistanceThresholdPx4G(int) override; +#if defined(VIDEO_HOLE) + void SetVideoHoleEnabled(bool) override; +#endif + bool ShowFPSCounter() const { return show_fps_counter_; } bool ShowPaintRects() const { return show_paint_rects_; } bool RenderVSyncNotificationEnabled() const { diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc index 2acc5921abe..72cf5752ef9 100644 --- a/third_party/blink/renderer/core/exported/web_view_impl.cc +++ b/third_party/blink/renderer/core/exported/web_view_impl.cc @@ -3606,4 +3606,19 @@ int32_t WebViewImpl::AutoplayFlagsForTest() { return page_->AutoplayFlags(); } +#if defined(VIDEO_HOLE) +void WebViewImpl::SetVideoHoleForRender(bool enable) { + if (!GetPage()) + return; + + GetPage()->GetSettings().SetVideoHoleEnabled(enable); +} + +bool WebViewImpl::IsVideoHoleForRender() const { + if (!GetPage()) + return false; + return GetPage()->GetSettings().GetVideoHoleEnabled(); +} +#endif + } // namespace blink diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h index 9055bbe708a..d478d7f6eb9 100644 --- a/third_party/blink/renderer/core/exported/web_view_impl.h +++ b/third_party/blink/renderer/core/exported/web_view_impl.h @@ -456,6 +456,11 @@ class CORE_EXPORT WebViewImpl final : public WebView, const IntRect& caret_bounds_in_document, bool zoom_into_legible_scale); +#if defined(VIDEO_HOLE) + void SetVideoHoleForRender(bool enable) override; + bool IsVideoHoleForRender() const override; +#endif + private: FRIEND_TEST_ALL_PREFIXES(WebFrameTest, DivScrollIntoEditableTest); FRIEND_TEST_ALL_PREFIXES(WebFrameTest, diff --git a/third_party/blink/renderer/core/frame/settings.json5 b/third_party/blink/renderer/core/frame/settings.json5 index 24f03d2a651..aef78715ea3 100644 --- a/third_party/blink/renderer/core/frame/settings.json5 +++ b/third_party/blink/renderer/core/frame/settings.json5 @@ -1000,5 +1000,9 @@ initial: 800, type: "int", }, + { + name: "videoHoleEnabled", + initial: false, + }, ], } diff --git a/third_party/blink/renderer/core/html/media/html_media_element.cc b/third_party/blink/renderer/core/html/media/html_media_element.cc index a7cec27411c..6e53f0132e0 100644 --- a/third_party/blink/renderer/core/html/media/html_media_element.cc +++ b/third_party/blink/renderer/core/html/media/html_media_element.cc @@ -1171,6 +1171,10 @@ void HTMLMediaElement::LoadResource(const WebMediaPlayerSource& source, // media element API. current_src_ = url; +#if defined(CASTANETS) + content_mime_type_ = ContentType(content_type).GetType().DeprecatedLower(); +#endif + if (audio_source_node_) audio_source_node_->OnCurrentSrcChanged(current_src_); @@ -1548,6 +1552,31 @@ void HTMLMediaElement::WaitForSourceChange() { GetLayoutObject()->UpdateFromElement(); } +#if defined(CASTANETS) +WebString HTMLMediaElement::GetContentMIMEType() { + // If the MIME type is missing or is not meaningful, try to figure it out + // from the URL. + if (content_mime_type_.IsEmpty() || + content_mime_type_ == "application/octet-stream" || + content_mime_type_ == "text/plain") { + if (current_src_.ProtocolIsData()) + content_mime_type_ = MimeTypeFromDataURL(current_src_.GetString()); + else { + String last_path_component = current_src_.LastPathComponent(); + size_t pos = last_path_component.ReverseFind('.'); + if (pos != kNotFound) { + String extension = last_path_component.Substring(pos + 1); + String media_type = + MIMETypeRegistry::GetMIMETypeForExtension(extension); + if (!media_type.IsEmpty()) + content_mime_type_ = media_type; + } + } + } + return WebString(content_mime_type_); +} +#endif + void HTMLMediaElement::NoneSupported(const String& input_message) { BLINK_MEDIA_LOG << "NoneSupported(" << (void*)this << ", message='" << input_message << "')"; diff --git a/third_party/blink/renderer/core/html/media/html_media_element.h b/third_party/blink/renderer/core/html/media/html_media_element.h index c9b109245ca..54168ba1951 100644 --- a/third_party/blink/renderer/core/html/media/html_media_element.h +++ b/third_party/blink/renderer/core/html/media/html_media_element.h @@ -335,6 +335,10 @@ class CORE_EXPORT HTMLMediaElement bool HasMediaSource() const { return media_source_; } + #if defined(CASTANETS) + WebString GetContentMIMEType() override; + #endif + protected: HTMLMediaElement(const QualifiedName&, Document&); ~HTMLMediaElement() override; @@ -575,6 +579,10 @@ class CORE_EXPORT HTMLMediaElement KURL current_src_; Member src_object_; +#if defined(CASTANETS) + String content_mime_type_; +#endif + Member error_; double volume_;