在Chromium中,
《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!
从前面Chromium为视频标签
Surface有两种获取方式。第一种方式是通过SurfaceTexture构造一个新的Surface。第二种方式是从SurfaceView内部获得。在非全屏模式下,Chromium就是通过第一种方式构造一个Surface,然后设置给MediaPlayer的。在全屏模式下,Chromium将会直接创建一个全屏的SurfaceView,然后再从这个SurfaceView内部获得一个Surface,并且设置给MediaPlayer。
在Android平台上,SurfaceView的本质是一个窗口。既然是窗口,那么它的UI就是由系统(SurfaceFlinger)合成在屏幕上显示的。它的UI就来源于它内部的Surface描述的GPU缓冲区队列。因此,当MediaPlayer将解码出来的视频画面写入到SurfaceView内部的Surface描述的GPU缓冲区队列去时,SurfaceFlinger就会从该GPU缓冲区队列中将新写入的视频画面提取出来,并且合成在屏幕上显示。关于SurfaceView的更多知识,可以参考前面Android视图SurfaceView的实现原理分析一文。
Surface描述的GPU缓冲区队列,是一个生产者/消息者模型。在我们这个情景中,生产者便是MediaPlayer。如果Surface是通过SurfaceTexture构造的,那么SurfaceTexture的所有者,也就是Chromium,就是消费者。消费者有责任将视频画面从GPU缓冲区队列中提取出来,并且进行渲染。渲染完成后,再交给SurfaceFlinger合成显示在屏幕中。如果Surface是从SurfaceView内部获取的,那么SurfaceView就是消费者,然后再交给SurfaceFlinger合成显示在屏幕中。
简单来说,在非全屏模式下,
Chromium支持
图1
当
接下来,我们就结合源代码,从
void HTMLMediaElement::enterFullscreen()
{
WTF_LOG(Media, "HTMLMediaElement::enterFullscreen");
FullscreenElementStack::from(document()).requestFullScreenForElement(this, 0, FullscreenElementStack::ExemptIFrameAllowFullScreenRequirement);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。
在WebKit中,网页的每一个标签都可以进入全屏模式。每一个网页都对应有一个FullscreenElementStack对象。这个FullscreenElementStack对象内部有一个栈,用来记录它对应的网页有哪些标签进入了全屏模式。
HTMLMediaElement类的成员函数enterFullscreen首先调用成员函数document获得当前正在处理的
FullscreenElementStack类的成员函数requestFullScreenForElement的实现如下所示:
void FullscreenElementStack::requestFullScreenForElement(Element* element, unsigned short flags, FullScreenCheckType checkType)
{
......
// The Mozilla Full Screen API <https://wiki.mozilla.org/Gecko:FullScreenAPI> has different requirements
// for full screen mode, and do not have the concept of a full screen element stack.
bool inLegacyMozillaMode = (flags & Element::LEGACY_MOZILLA_REQUEST);
do {
......
// 1. If any of the following conditions are true, terminate these steps and queue a task to fire
// an event named fullscreenerror with its bubbles attribute set to true on the context object's
// node document:
......
// The context object's node document fullscreen element stack is not empty and its top element
// is not an ancestor of the context object. (NOTE: Ignore this requirement if the request was
// made via the legacy Mozilla-style API.)
if (!m_fullScreenElementStack.isEmpty() && !inLegacyMozillaMode) {
Element* lastElementOnStack = m_fullScreenElementStack.last().get();
if (lastElementOnStack == element || !lastElementOnStack->contains(element))
break;
}
// A descendant browsing context's document has a non-empty fullscreen element stack.
bool descendentHasNonEmptyStack = false;
for (Frame* descendant = document()->frame() ? document()->frame()->tree().traverseNext() : 0; descendant; descendant = descendant->tree().traverseNext()) {
......
if (fullscreenElementFrom(*toLocalFrame(descendant)->document())) {
descendentHasNonEmptyStack = true;
break;
}
}
if (descendentHasNonEmptyStack && !inLegacyMozillaMode)
break;
......
// 2. Let doc be element's node document. (i.e. "this")
Document* currentDoc = document();
// 3. Let docs be all doc's ancestor browsing context's documents (if any) and doc.
Deque<Document*> docs;
do {
docs.prepend(currentDoc);
currentDoc = currentDoc->ownerElement() ? ¤tDoc->ownerElement()->document() : 0;
} while (currentDoc);
// 4. For each document in docs, run these substeps:
Deque<Document*>::iterator current = docs.begin(), following = docs.begin();
do {
++following;
// 1. Let following document be the document after document in docs, or null if there is no
// such document.
Document* currentDoc = *current;
Document* followingDoc = following != docs.end() ? *following : 0;
// 2. If following document is null, push context object on document's fullscreen element
// stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute
// set to true on the document.
if (!followingDoc) {
from(*currentDoc).pushFullscreenElementStack(element);
addDocumentToFullScreenChangeEventQueue(currentDoc);
continue;
}
// 3. Otherwise, if document's fullscreen element stack is either empty or its top element
// is not following document's browsing context container,
Element* topElement = fullscreenElementFrom(*currentDoc);
if (!topElement || topElement != followingDoc->ownerElement()) {
// ...push following document's browsing context container on document's fullscreen element
// stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute
// set to true on document.
from(*currentDoc).pushFullscreenElementStack(followingDoc->ownerElement());
addDocumentToFullScreenChangeEventQueue(currentDoc);
continue;
}
// 4. Otherwise, do nothing for this document. It stays the same.
} while (++current != docs.end());
// 5. Return, and run the remaining steps asynchronously.
// 6. Optionally, perform some animation.
......
document()->frameHost()->chrome().client().enterFullScreenForElement(element);
// 7. Optionally, display a message indicating how the user can exit displaying the context object fullscreen.
return;
} while (0);
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/FullscreenElementStack.cpp中。
FullscreenElementStack类的成员函数requestFullScreenForElement主要是用来为网页中的每一个Document建立一个Stack。这个Stack记录了Document中所有请求设置为全屏模式的标签。我们通过图2所示的例子说明FullscreenElementStack类的成员函数requestFullScreenForElement的实现:
图2 Fullscreen Stack for Document
图2所示的网页包含了两个Document:Doc1和Doc2。其中,Doc1通过
注释中提到,Mozilla定义的Fullscreen API没有Fullscreen Element Stack的概念。没有Fullscreen Element Stack,意味着网页的标签在任意情况下都可以设置为全屏模式。不过,非Mozilla定义的Fullscreen API是要求Fullscreen Element Stack的。Fullscreen Element Stack用来限制一个标签是否可以设置为全屏模式:
1. 当Stack为空时,任意标签均可设置为全屏模式。
2. 当Stack非空时,栈顶标签的子标签才可以设置为全屏模式。
FullscreenElementStack类的成员函数requestFullScreenForElement就是根据上述逻辑判断参数element描述的标签是否可以设置为全屏模式的。如果可以,那么就会更新与它相关的Stack,并且在最后调用在WebKit Glue层创建一个WebViewImpl对象的成员函数enterFullScreenForElement,用来通知WebKit Glue层有一个标签要进入全屏模式。这个WebViewImpl对象的创建过程可以参考前面Chromium网页Frame Tree创建过程分析一文。WebKit Glue层的作用,可以参考前面Chromium网页加载过程简要介绍和学习计划一文。
接下来,我们就继续分析WebViewImpl类的成员函数enterFullScreenForElement的实现,如下所示:
void WebViewImpl::enterFullScreenForElement(WebCore::Element* element)
{
m_fullscreenController->enterFullScreenForElement(element);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebViewImpl.cpp中。
WebViewImpl类的成员变量m_fullscreenController指向的是一个FullscreenController对象。WebViewImpl类的成员函数enterFullScreenForElement调用这个FullscreenController对象的成员函数enterFullScreenForElement,用来通知它将参数element描述的标签设置为全屏模式。
FullscreenController类的成员函数enterFullScreenForElement的实现如下所示:
void FullscreenController::enterFullScreenForElement(WebCore::Element* element)
{
// We are already transitioning to fullscreen for a different element.
if (m_provisionalFullScreenElement) {
m_provisionalFullScreenElement = element;
return;
}
// We are already in fullscreen mode.
if (m_fullScreenFrame) {
m_provisionalFullScreenElement = element;
willEnterFullScreen();
didEnterFullScreen();
return;
}
// We need to transition to fullscreen mode.
if (WebViewClient* client = m_webViewImpl->client()) {
if (client->enterFullScreen())
m_provisionalFullScreenElement = element;
}
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/FullscreenController.cpp中。
如果参数element描述的标签所在的网页已经有标签处于全屏模式,那么FullscreenController类的成员变量m_provisionalFullScreenElement就会指向该标签,并且FullscreenController类的另外一个成员变量m_fullScreenFrame会指向一个LocalFrame对象。这个LocalFrame对象描述的就是处于全屏模式的标签所在的网页。
我们假设参数element描述的标签所在的网页还没有标签被设置为全屏模式。这时候FullscreenController类的成员函数enterFullScreenForElement会调用成员变量m_webViewImpl指向的一个WebViewImpl对象的成员函数client获得一个WebViewClient接口,然后再调用这个WebViewClient接口的成员函数enterFullScreen,用来通知它进入全屏模式。
上述WebViewClient接口是由WebKit的使用者设置的。在我们这个情景中,WebKit的使用者即Render进程中的Content模块,它设置的WebViewClient接口指向的是一个RenderViewImpl对象。这个RenderViewImpl对象的创建过程可以参考前面Chromium网页Frame Tree创建过程分析一文,它描述的是当前正在处理的网页所加载在的一个RenderView控件。这一步实际上是通知Chromium的Content层进入全屏模式。
FullscreenController类的成员函数enterFullScreenForElement通知了Content层进入全屏模式之后,会将引发Content层进入全屏模式的标签记录在成员变量m_provisionalFullScreenElement中。在我们这个情景中,这个标签即为一个
接下来我们继续分析Chromium的Content层进入全屏模式的过程,也就是RenderViewImpl类的成员函数enterFullScreen的实现,如下所示:
bool RenderViewImpl::enterFullScreen() {
Send(new ViewHostMsg_ToggleFullscreen(routing_id_, true));
return true;
}
这个函数定义在文件external/chromium_org/content/renderer/render_view_impl.cc。
RenderViewImpl类的成员函数enterFullScreen向Browser进程发送一个类型为ViewHostMsg_ToggleFullscreen的IPC消息,用来通知它将Routing ID为routing_id_的网页所加载在的Tab设置为全屏模式。
Browser进程通过RenderViewHostImpl类的成员函数OnMessageReceived接收类型为ViewHostMsg_ToggleFullscreen的IPC消息,如下所示:
bool RenderViewHostImpl::OnMessageReceived(const IPC::Message& msg) {
......
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(RenderViewHostImpl, msg)
......
IPC_MESSAGE_HANDLER(ViewHostMsg_ToggleFullscreen, OnToggleFullscreen)
......
IPC_MESSAGE_UNHANDLED(
handled = RenderWidgetHostImpl::OnMessageReceived(msg))
IPC_END_MESSAGE_MAP()
return handled;
}
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_view_host_impl.cc中。
RenderViewHostImpl类的成员函数OnMessageReceived将类型为ViewHostMsg_ToggleFullscreen的IPC消息分发给另外一个成员函数OnToggleFullscreen处理,如下所示:
void RenderViewHostImpl::OnToggleFullscreen(bool enter_fullscreen) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
delegate_->ToggleFullscreenMode(enter_fullscreen);
// We need to notify the contents that its fullscreen state has changed. This
// is done as part of the resize message.
WasResized();
}
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_view_host_impl.cc中。
RenderViewHostImpl类的成员函数OnToggleFullscreen首先通过成员变量delegate_描述的一个RenderViewHostDelegate委托接口将浏览器窗口设置为全屏模式,然后再调用另外一个成员函数WasResized通知Render进程,浏览器窗口大小已经发生了变化,也就是它进入了全屏模式。
RenderViewHostImpl类的成员变量delegate_描述的RenderViewHostDelegate委托接口指向的是一个WebContentsImpl对象。这个WebContentsImpl对象是Content层提供给外界的一个接口,用来描述当前正在加载的一个网页。外界通过这个接口就可以访问当前正在加载的网页。
RenderViewHostImpl类的成员函数WasResized是从父类RenderWidgetHostImpl继承下来的,它的实现如下所示:
void RenderWidgetHostImpl::WasResized() {
......
is_fullscreen_ = IsFullscreen();
......
ViewMsg_Resize_Params params;
......
params.is_fullscreen = is_fullscreen_;
if (!Send(new ViewMsg_Resize(routing_id_, params))) {
resize_ack_pending_ = false;
} else {
last_requested_size_ = new_size;
}
}
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_impl.cc中。
RenderWidgetHostImpl类的成员函数WasResized主要是向Render进程发送一个类型为ViewMsg_Resize的IPC消息。这个ViewMsg_Resize的IPC消息。携带了一个is_fullscreen信息,用来告知Render进程当前正在处理的网页是否已经进入了全屏模式。
Render进程通过RenderWidget类的成员函数OnMessageReceived接收类型为ViewMsg_Resize的IPC消息,如下所示:
bool RenderWidget::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(RenderWidget, message)
......
IPC_MESSAGE_HANDLER(ViewMsg_Resize, OnResize)
......
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。
RenderWidget类的成员函数OnMessageReceived将类型为ViewMsg_Resize的IPC消息分发给另外一个成员函数OnResize处理,如下所示:
void RenderWidget::OnResize(const ViewMsg_Resize_Params& params) {
......
Resize(params.new_size, params.physical_backing_size,
params.overdraw_bottom_height, params.visible_viewport_size,
params.resizer_rect, params.is_fullscreen, SEND_RESIZE_ACK);
......
}
这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。
RenderWidget类的成员函数OnResize又调用另外一个成员函数Resize处理类型为ViewMsg_Resize的IPC消息,如下所示:
void RenderWidget::Resize(const gfx::Size& new_size,
const gfx::Size& physical_backing_size,
float overdraw_bottom_height,
const gfx::Size& visible_viewport_size,
const gfx::Rect& resizer_rect,
bool is_fullscreen,
ResizeAck resize_ack) {
......
if (compositor_) {
compositor_->setViewportSize(new_size, physical_backing_size);
......
}
......
// NOTE: We may have entered fullscreen mode without changing our size.
bool fullscreen_change = is_fullscreen_ != is_fullscreen;
if (fullscreen_change)
WillToggleFullscreen();
is_fullscreen_ = is_fullscreen;
if (size_ != new_size) {
size_ = new_size;
// When resizing, we want to wait to paint before ACK'ing the resize. This
// ensures that we only resize as fast as we can paint. We only need to
// send an ACK if we are resized to a non-empty rect.
webwidget_->resize(new_size);
}
......
if (fullscreen_change)
DidToggleFullscreen();
......
}
这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。
RenderWidget类的成员函数Resize一方面会通知网页的UI合成器,它负责渲染的网页的大小发生了变化,以便它修改网页UI的Viewport大小。这是通过调用成员变量compositor_指向的一个RenderWidgetCompositor对象的成员函数setViewportSize实现的。这个RenderWidgetCompositor对象的创建过程可以参考前面Chromium网页Layer Tree创建过程分析一文。
另一方面,RenderWidget类的成员函数Resize又会通知WebKit,它当前正在加载网页的大小发生了变化。这是通过调用成员变量webwidget_指向的一个WebViewImpl对象的成员函数resize实现的。这个WebViewImpl对象的创建过程可以参考前面Chromium网页Frame Tree创建过程分析一文。
此外,RenderWidget类的成员函数Resize还会判断网页是否从全屏模式退出,或者进入全屏模式。如果是的话,那么就在通知WebKit修改网页的大小前后,RenderWidget类的成员函数Resize还会分别调用另外两个成员函数WillToggleFullscreen和DidToggleFullscreen,用来通知WebKit网页进入或者退出了全屏模式。
在我们这个情景中,网页是进入了全屏模式。接下来我们就先分析RenderWidget类的成员函数WillToggleFullscreen的实现,然后再分析另外一个成员函数DidToggleFullscreen的实现。
RenderWidget类的成员函数WillToggleFullscreen的实现如下所示:
void RenderWidget::WillToggleFullscreen() {
......
if (is_fullscreen_) {
webwidget_->willExitFullScreen();
} else {
webwidget_->willEnterFullScreen();
}
}
这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。
从前面的分析可以知道,RenderWidget类的成员函数WillToggleFullscreen是在通知WebKit进入全屏模式之前被调用的。这时候RenderWidget类的成员变量is_fullscreen_的值为false。因此,接下来RenderWidget类的成员函数WillToggleFullscreen会调用成员变量webwidget_指向的一个WebViewImpl对象的成员函数willEnterFullScreen,用来通知WebKit它即将要进入全屏模式。
WebViewImpl类的成员函数willEnterFullScreen的实现如下所示:
void WebViewImpl::willEnterFullScreen()
{
m_fullscreenController->willEnterFullScreen();
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebViewImpl.cpp中。
WebViewImpl类的成员函数willEnterFullScreen调用成员变量m_fullscreenController指向的一个FullscreenController对象的成员函数willEnterFullScreen,用来通知WebKit即将要进入全屏模式,如下所示:
void FullscreenController::willEnterFullScreen()
{
if (!m_provisionalFullScreenElement)
return;
// Ensure that this element's document is still attached.
Document& doc = m_provisionalFullScreenElement->document();
if (doc.frame()) {
......
m_fullScreenFrame = doc.frame();
}
m_provisionalFullScreenElement.clear();
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/FullscreenController.cpp中。
从前面的分析可以知道,此时FullscreenController类的成员变量m_provisionalFullScreenElement的值不等于NULL,它指向了一个
这一步执行完成后,回到前面分析的RenderWidget类的成员函数Resize,它在通知了WebKit修改当前正在加载的网页的大小之后,会调用另外一个成员函数DidToggleFullscreen,用来通知WebKit已经进入了全屏模式,如下所示:
void RenderWidget::DidToggleFullscreen() {
......
if (is_fullscreen_) {
webwidget_->didEnterFullScreen();
} else {
webwidget_->didExitFullScreen();
}
}
这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。
从前面的分析可以知道,此时RenderWidget类的成员变量is_fullscreen_的值已经被设置为true。因此,RenderWidget类的成员函数DidToggleFullscreen接下来就会调用成员变量webwidget_指向的一个WebViewImpl对象的成员函数didEnterFullScreen,用来通知WebKit它已经进入全屏模式,如下所示:
void WebViewImpl::didEnterFullScreen()
{
m_fullscreenController->didEnterFullScreen();
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebViewImpl.cpp中。
WebViewImpl类的成员函数didEnterFullScreen调用成员变量m_fullscreenController指向的一个FullscreenController对象的成员函数didEnterFullScreen,用来通知WebKit已经进入全屏模式,如下所示:
void FullscreenController::didEnterFullScreen()
{
if (!m_fullScreenFrame)
return;
if (Document* doc = m_fullScreenFrame->document()) {
if (FullscreenElementStack::isFullScreen(*doc)) {
......
if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled()) {
Element* element = FullscreenElementStack::currentFullScreenElementFrom(*doc);
ASSERT(element);
if (isHTMLMediaElement(*element)) {
HTMLMediaElement* mediaElement = toHTMLMediaElement(element);
if (mediaElement->webMediaPlayer() && mediaElement->webMediaPlayer()->canEnterFullscreen()
// FIXME: There is no embedder-side handling in layout test mode.
&& !isRunningLayoutTest()) {
mediaElement->webMediaPlayer()->enterFullscreen();
}
.......
}
}
}
}
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/FullscreenController.cpp中。
从前面的分析可以知道,FullscreenController类的成员变量m_fullScreenFrame的值不等于NULL。它指向了一个LocalFrame对象。这个LocalFrame对象描述的就是当前被设置为全屏模式的标签所在的网页。通过这个LocalFrame对象可以获得一个Document对象。
有了这个Document对象之后,FullscreenController类的成员函数didEnterFullScreen就会检查它的Fullscreen Element Stack栈顶标签。如果这是一个
从前面Chromium为视频标签
WebMediaPlayerAndroid类的成员函数enenterFullScreen的实现如下所示:
void WebMediaPlayerAndroid::enterFullscreen() {
if (player_manager_->CanEnterFullscreen(frame_)) {
player_manager_->EnterFullscreen(player_id_, frame_);
......
}
}
这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
从前面Chromium为视频标签
WebMediaPlayerAndroid类的成员函数enenterFullScreen首先调用上述RendererMediaPlayerManager对象的成员函数CanEnterFullscreen检查与当前正在处理的播放器实例关联的
从前面的分析可以知道,与当前正在处理的播放器实例关联的
void RendererMediaPlayerManager::EnterFullscreen(int player_id,
blink::WebFrame* frame) {
pending_fullscreen_frame_ = frame;
Send(new MediaPlayerHostMsg_EnterFullscreen(routing_id(), player_id));
}
这个函数定义在文件external/chromium_org/content/renderer/media/android/renderer_media_player_manager.cc中。
RendererMediaPlayerManager类的成员函数EnterFullscreen主要是向Browser进程发送一个类型为MediaPlayerHostMsg_EnterFullscreen的IPC消息,通知它将ID为player_id的播放器设置为全屏模式。
Browser进程通过MediaWebContentsObserver类的成员函数OnMediaPlayerMessageReceived接收类型为MediaPlayerHostMsg_EnterFullscreen的IPC消息,如下所示:
bool MediaWebContentsObserver::OnMediaPlayerMessageReceived(
const IPC::Message& msg,
RenderFrameHost* render_frame_host) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(MediaWebContentsObserver, msg)
IPC_MESSAGE_FORWARD(MediaPlayerHostMsg_EnterFullscreen,
GetMediaPlayerManager(render_frame_host),
BrowserMediaPlayerManager::OnEnterFullscreen)
......
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
这个函数定义在文件external/chromium_org/content/browser/media/media_web_contents_observer.cc中。
MediaWebContentsObserver类的成员函数OnMediaPlayerMessageReceived首先调用成员函数GetMediaPlayerManager获得一个BrowserMediaPlayerManager对象,然后调用这个BrowserMediaPlayerManager对象的成员函数OnEnterFullscreen处理类型为MediaPlayerHostMsg_EnterFullscreen的IPC消息,如下所示:
void BrowserMediaPlayerManager::OnEnterFullscreen(int player_id) {
......
if (video_view_.get()) {
fullscreen_player_id_ = player_id;
video_view_->OpenVideo();
return;
} else if (!ContentVideoView::GetInstance()) {
......
video_view_.reset(new ContentVideoView(this));
base::android::ScopedJavaLocalRef<jobject> j_content_video_view =
video_view_->GetJavaObject(base::android::AttachCurrentThread());
if (!j_content_video_view.is_null()) {
fullscreen_player_id_ = player_id;
return;
}
}
.....
}
这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。
当BrowserMediaPlayerManager类的成员变量video_view_的值不等于NULL时,它指向一个ContentVideoView对象。这个ContentVideoView对象描述的是一个全屏播放器窗口。
在Browser进程中,一个网页对应有一个BrowserMediaPlayerManager对象。同一个网页中的所有
BrowserMediaPlayerManager类的成员函数OnEnterFullscreen的执行逻辑如下所示:
1. 如果当前正在处理的BrowserMediaPlayerManager对象的成员变量video_view_的值不等于NULL,也就是它指向了一个ContentVideoView对象,那么就说明Browser进程已经为当前正在处理的BrowserMediaPlayerManager对象创建过全屏播放器窗口了。这时候就会将参数player_id的值保存在另外一个成员变量fullscreen_player_id_中,表示当前正在处理的BrowserMediaPlayerManager对象所管理的全屏播放器窗口正在被ID为player_id的播放器使用。同时,会调用上述ContentVideoView对象的成员函数OpenVideo,让其全屏播放当前被设置为全屏模式的
C++层ContentVideoView对象在创建的过程中,又会在Java层创建一个对应的ContentVideoView对象。这个Java层ContentVideoView对象如果能成功创建,那么就可以通过调用其对应的C++层ContentVideoView对象的成员函数GetJavaObject获得。这时候说明Browser进程成功创建了一个全屏播放器窗口。这个全屏播放器窗口实际上就是一个SurfaceView对象。
一个SurfaceView对象刚创建出来的时候,它描述的窗口没有创建出来。Browser进程需要等它描述的窗口创建出来之后,才使用它全屏播放当前被设置为全屏模式的
接下来,我们就继续分析全屏播放器窗口的创建过程,以及Browser进程使用它来全屏播放当前设置为全屏模式的
全屏播放器窗口是在C++层ContentVideoView对象的创建过程当中创建出来的,因此,我们就从C++层ContentVideoView类的构造函数开始,分析全屏播放器窗口的创建过程,如下所示:
ContentVideoView::ContentVideoView(
BrowserMediaPlayerManager* manager)
: manager_(manager),
weak_factory_(this) {
DCHECK(!g_content_video_view);
j_content_video_view_ = CreateJavaObject();
g_content_video_view = this;
......
}
这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。
C++层ContentVideoView类的构造函数会调用另外一个成员函数CreateJavaObject在Java层创建一个对应的ContentVideoView对象,并且保存在成员变量j_content_video_view_中。
与此同时,C++层ContentVideoView类的构造函数会将当前正在创建的ContentVideoView对象保存一个静态成员变量g_content_video_view_中,表示Browser进程当前已经存在一个全屏窗口。根据我们前面的分析,这样就会阻止其它网页全屏播放它们的
ContentVideoView* ContentVideoView::GetInstance() {
return g_content_video_view;
}
这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。
回到C++层的ContentVideoView类的构造函数中,我们主要关注Java层的ContentVideoView对象的创建过程,因此接下来我们继续分析C++层的ContentVideoView类的成员函数CreateJavaObject的实现,如下所示:
JavaObjectWeakGlobalRef ContentVideoView::CreateJavaObject() {
ContentViewCoreImpl* content_view_core = manager_->GetContentViewCore();
JNIEnv* env = AttachCurrentThread();
bool legacyMode = CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableOverlayFullscreenVideoSubtitle);
return JavaObjectWeakGlobalRef(
env,
Java_ContentVideoView_createContentVideoView(
env,
content_view_core->GetContext().obj(),
reinterpret_cast<intptr_t>(this),
content_view_core->GetContentVideoViewClient().obj(),
legacyMode).obj());
}
这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。
C++层的ContentVideoView类的成员函数CreateJavaObject主要是通过JNI接口Java_ContentVideoView_createContentVideoView调用Java层的ContentVideoView类的静态成员函数createContentVideoView创建一个Java层的ContentVideoView对象,如下所示:
public class ContentVideoView extends FrameLayout
implements SurfaceHolder.Callback, ViewAndroidDelegate {
......
@CalledByNative
private static ContentVideoView createContentVideoView(
Context context, long nativeContentVideoView, ContentVideoViewClient client,
boolean legacy) {
......
ContentVideoView videoView = null;
if (legacy) {
videoView = new ContentVideoViewLegacy(context, nativeContentVideoView, client);
} else {
videoView = new ContentVideoView(context, nativeContentVideoView, client);
}
if (videoView.getContentVideoViewClient().onShowCustomView(videoView)) {
return videoView;
}
return null;
}
......
}
这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。
从前面的调用过程可以知道,当Browser进程设置了kDisableOverlayFullscreenVideoSubtitle(disable-overlay-fullscreen-video-subtitle)启动选项时,参数legacy的值就会等于true时,表示禁止在全屏播放器窗口上面显示播放控制控件以及字幕。这时候全屏播放器窗口通过一个ContentVideoViewLegacy对象描述。另一方面,如果参数legacy的值等于false,那么全屏播放器窗口将会通过一个ContentVideoView对象描述。
一个ContentVideoViewLegacy对象或者一个ContentVideoView对象描述的全屏播放器窗口实际上是一个SurfaceView。这个SurfaceView只有添加到浏览器窗口之后,才能显示在屏幕中。为了将该全屏播放器窗口显示出来,ContentVideoView类的静态成员函数createContentVideoView将会通过调用前面创建的ContentVideoViewLegacy对象或者ContentVideoView对象的成员函数getContentVideoViewClient获得一个ContentVideoViewClient接口。有了这个ContentVideoViewClient接口之后,就可以调用它的成员函数onShowCustomView将刚才创建出来的全屏播放器窗口显示出来了。
我们假设参数legacy的值等于false。这意味着全屏播放器窗口是通过一个ContentVideoView对象描述的。接下来我们就继续分析这个ContentVideoView对象的创建过程,即ContentVideoView类的构造函数的实现,以便了解它内部的SurfaceView的创建过程,如下所示:
public class ContentVideoView extends FrameLayout
implements SurfaceHolder.Callback, ViewAndroidDelegate {
......
// This view will contain the video.
private VideoSurfaceView mVideoSurfaceView;
......
protected ContentVideoView(Context context, long nativeContentVideoView,
ContentVideoViewClient client) {
super(context);
......
mVideoSurfaceView = new VideoSurfaceView(context);
showContentVideoView();
......
}
......
}
这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。
ContentVideoView类的构造函数会创建一个VideoSurfaceView对象,并且保存在成员变量mVideoSurfaceView中。
VideoSurfaceView类是从SurfaceView类继承下来的,如下所示:
public class ContentVideoView extends FrameLayout
implements SurfaceHolder.Callback, ViewAndroidDelegate {
......
private class VideoSurfaceView extends SurfaceView {
......
}
......
}
这个类定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。
ContentVideoView类的构造函数在创建了一个VideoSurfaceView对象,接下来会调用另外一个成员函数showContentVideoView将它作为当前正在创建的ContentVideoView对象的子View,如下所示:
public class ContentVideoView extends FrameLayout
implements SurfaceHolder.Callback, ViewAndroidDelegate {
......
protected void showContentVideoView() {
mVideoSurfaceView.getHolder().addCallback(this);
this.addView(mVideoSurfaceView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
Gravity.CENTER));
......
}
......
}
这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。
ContentVideoView类的成员函数showContentVideoView将前面创建的VideoSurfaceView对象作为当前正在创建的ContentVideoView对象的子View之外,还会使用后者监听前者的surfaceCreated事件,也就是它描述的窗口创建完成事件。
当前正在创建的ContentVideoView对象实现了SurfaceHolder.Callback接口,一旦前面创建的VideoSurfaceView对象发出surfaceCreated事件通知,那么前者的成员函数surfaceCreated就会被调用,如下所示:
public class ContentVideoView extends FrameLayout
implements SurfaceHolder.Callback, ViewAndroidDelegate {
......
@Override
public void surfaceCreated(SurfaceHolder holder) {
mSurfaceHolder = holder;
openVideo();
}
......
}
这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。
ContentVideoView类的成员函数surfaceCreated会将参数holder描述的一个SurfaceHolder对象保存在成员变量mSurfaceHolder中。后面可以通过这个SurfaceHolder对象获得前面创建的VideoSurfaceView对象内部的一个Surface。这个Surface将会用来接收MediaPlayer的解码输出。
接下来,ContentVideoView类的成员函数surfaceCreated就会调用另外一个成员函数openVideo将上述Surface设置为MediaPlayer的解码输出,如下所示:
public class ContentVideoView extends FrameLayout
implements SurfaceHolder.Callback, ViewAndroidDelegate {
......
@CalledByNative
protected void openVideo() {
if (mSurfaceHolder != null) {
mCurrentState = STATE_IDLE;
if (mNativeContentVideoView != 0) {
nativeRequestMediaMetadata(mNativeContentVideoView);
nativeSetSurface(mNativeContentVideoView,
mSurfaceHolder.getSurface());
}
}
}
......
}
这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。
ContentVideoView类的成员变量mNativeContentVideoView描述的是前面在C++层创建的一个ContentVideoView对象。ContentVideoView类的成员函数openVideo主要是做两件事情:
1. 获取要播放的视频的元数据,以便用来初始化全屏播放器窗口。
2. 将前面创建的VideoSurfaceView对象内部维护的Surface设置为MediaPlayer的解码输出。
这两件事情分别是通过调用成员函数nativeRequestMediaMetadata和nativeSetSurface完成的。当它们完成之后,
ContentVideoView类的成员函数nativeRequestMediaMetadata是一个JNI方法,它由C++层的函数Java_com_android_org_chromium_content_browser_ContentVideoView_nativeRequestMediaMetadata实现,如下所示:
void
Java_com_android_org_chromium_content_browser_ContentVideoView_nativeRequestMediaMetadata(JNIEnv*
env,
jobject jcaller,
jlong nativeContentVideoView) {
ContentVideoView* native =
reinterpret_cast<ContentVideoView*>(nativeContentVideoView);
CHECK_NATIVE_PTR(env, jcaller, native, "RequestMediaMetadata");
return native->RequestMediaMetadata(env, jcaller);
}
这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/content/jni/ContentVideoView_jni.h中。
从前面的调用过程可以知道,参数nativeContentVideoView描述的是一个C++层ContentVideoView对象,函数Java_com_android_org_chromium_content_browser_ContentVideoView_nativeRequestMediaMetadata调用它的成员函数RequestMediaMetadata获取要播放的视频的元数据,如下所示:
void ContentVideoView::RequestMediaMetadata(JNIEnv* env, jobject obj) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&ContentVideoView::UpdateMediaMetadata,
weak_factory_.GetWeakPtr()));
}
这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。
ContentVideoView类的成员函数RequestMediaMetadata向当前线程的消息队列发送一个Task。这个Task绑定了ContentVideoView类的另外一个成员函数UpdateMediaMetadata。这意味着接下来ContentVideoView类的成员函数UpdateMediaMetadata会在当前线程中调用,用来获取要播放的视频的元数据。
ContentVideoView类的成员函数UpdateMediaMetadata的实现如下所示:
void ContentVideoView::UpdateMediaMetadata() {
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jobject> content_video_view = GetJavaObject(env);
if (content_video_view.is_null())
return;
media::MediaPlayerAndroid* player = manager_->GetFullscreenPlayer();
if (player && player->IsPlayerReady()) {
Java_ContentVideoView_onUpdateMediaMetadata(
env, content_video_view.obj(), player->GetVideoWidth(),
player->GetVideoHeight(),
static_cast<int>(player->GetDuration().InMilliseconds()),
player->CanPause(),player->CanSeekForward(), player->CanSeekBackward());
}
}
这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。
ContentVideoView类的成员函数UpdateMediaMetadata首先调用另外一个成员函数GetJavaObject获得与当前正在处理的C++层ContentVideoView对象对应的Java层ContentVideoView对象。
ContentVideoView类的成员函数UpdateMediaMetadata接下来又通过调用成员变量manager_指向的一个BrowserMediaPlayerManager对象的成员函数GetFullscreenPlayer获得当前处于全屏模式的播放器,如下所示:
MediaPlayerAndroid* BrowserMediaPlayerManager::GetFullscreenPlayer() {
return GetPlayer(fullscreen_player_id_);
}
这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。
从前面的分析可以知道,BrowserMediaPlayerManager类的成员变量fullscreen_player_id_记录了当前处于全屏模式的播放器的ID。有了这个ID之后,就可以调用另外一个成员函数GetPlayer获得一个对应的MediaPlayerBridge对象。这个MediaPlayerBridge对象描述的就是当前处于全屏模式的播放器。
回到ContentVideoView类的成员函数UpdateMediaMetadata中,它获得的全屏播放器之后,实际上就是
有了要播放的视频的元数据之后,ContentVideoView类的成员函数UpdateMediaMetadata就通过JNI接口Java_ContentVideoView_onUpdateMediaMetadata调用前面获得的Java层ContentVideoView对象的成员函数onUpdateMediaMetadata,让其初始化全屏播放器窗口,如下所示:
public class ContentVideoView extends FrameLayout
implements SurfaceHolder.Callback, ViewAndroidDelegate {
......
@CalledByNative
protected void onUpdateMediaMetadata(
int videoWidth,
int videoHeight,
int duration,
boolean canPause,
boolean canSeekBack,
boolean canSeekForward) {
mDuration = duration;
.....
onVideoSizeChanged(videoWidth, videoHeight);
}
......
}
这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。
参数duration描述的就是要播放的视频的总时长,它会被记录在ContentVideoView类的成员变量mDuration中。
另外两个参数videoWidth和videoHeight描述的是要播放的视频的宽和高,ContentVideoView类的成员函数onUpdateMediaMetadata将它们传递给另外一个成员函数onVideoSizeChanged,用来初始化全屏播放器窗口,如下所示:
public class ContentVideoView extends FrameLayout
implements SurfaceHolder.Callback, ViewAndroidDelegate {
......
@CalledByNative
private void onVideoSizeChanged(int width, int height) {
mVideoWidth = width;
mVideoHeight = height;
// This will trigger the SurfaceView.onMeasure() call.
mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
}
......
}
这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。
ContentVideoView类的成员函数onVideoSizeChanged将视频的宽度和高度分别记录在成员变量mVideoWidth和mVideoHeight中,然后再将它们设置为用来显示视频画面的VideoSurfaceView的大小。
这一步执行完成后,全屏播放器窗口就初始化完毕。回到前面分析的ContentVideoView类的成员函数openVideo中,它接下来将上述VideoSurfaceView内部使用的Surface设置为MediaPlayer的解码输出。这是通过调用ContentVideoView类的成员函数nativeSetSurface实现的。
ContentVideoView类的成员函数nativeSetSurface是一个JNI方法,它由C++层的函数Java_com_android_org_chromium_content_browser_ContentVideoView_nativeSetSurface实现,如下所示:
void
Java_com_android_org_chromium_content_browser_ContentVideoView_nativeSetSurface(JNIEnv*
env,
jobject jcaller,
jlong nativeContentVideoView,
jobject surface) {
ContentVideoView* native =
reinterpret_cast<ContentVideoView*>(nativeContentVideoView);
CHECK_NATIVE_PTR(env, jcaller, native, "SetSurface");
return native->SetSurface(env, jcaller, surface);
}
这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/content/jni/ContentVideoView_jni.h中。
参数nativeContentVideoView描述的是一个C++层ContentVideoView对象,另外一个参数surface描述的就是前面提到的用来显示视频画面的VideoSurfaceView内部使用的Surface。
函数Java_com_android_org_chromium_content_browser_ContentVideoView_nativeSetSurface参数nativeContentVideoView描述的C++层ContentVideoView对象的成员函数SetSurface重新给当前处于全屏模式的播放器设置一个Surface,如下所示:
void ContentVideoView::SetSurface(JNIEnv* env, jobject obj,
jobject surface) {
manager_->SetVideoSurface(
gfx::ScopedJavaSurface::AcquireExternalSurface(surface));
}
这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。
ContentVideoView类的成员函数SetSurface调用成员变量manager_指向的一个BrowserMediaPlayerManager对象的成员函数SetVideoSurface重新给当前处于全屏模式的播放器设置一个Surface,如下所示:
void BrowserMediaPlayerManager::SetVideoSurface(
gfx::ScopedJavaSurface surface) {
MediaPlayerAndroid* player = GetFullscreenPlayer();
......
player->SetVideoSurface(surface.Pass());
......
}
这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。
BrowserMediaPlayerManager类的成员函数SetVideoSurface首先调用另外一个成员函数GetFullscreenPlayer获得一个MediaPlayerBridge对象。这个MediaPlayerBridge对象被一个MediaPlayerAndroid指针引用(MediaPlayerAndroid是MediaPlayerBridge的父类),它描述的便是当前处于全屏模式的播放器。
获得了当前处于全屏模式的播放器之后,就可以调用它的成员函数SetVideoSurface将参数surface描述的一个Surface设置为它的解码输出。这个设置过程,也就是MediaPlayerBridge类的成员函数SetVideoSurface的实现,可以参考前面Chromium为视频标签
回忆
这样,我们就分析完成
至此,我们也分析完成了video标签在Chromium中的实现。视频在互联网中将扮演着越来越重要的角色。以前浏览器主要是通过Flash插件来支持视频播放。Flash插件有着臭名昭著的安全问题和Crash问题。因此,随着HTML5的出现,浏览器逐步转向使用
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。