最近在修改一些问题,涉及到了网页播放视频的相关东西,分析了一下流程,把它记录下来,不然可能一个星期就忘掉了.
在chromium网页切换小屏至全屏的过程中,每一个视频url对应一个WebMediaPlayer(有唯一id), 从而对应一个Browser进程中的MediaPlayerBridge. HTMLMediaElement的src变化时都会调用到HTMLMediaElement::startPlayerLoad(),进而去创建一个新的WebMediaPlayer, 然后去load. 如果该Video已经处于全屏状态,则调用ChromeClient::enterFullScreenForElement保证新创建的WebMediaPlayer也进入全屏.
全屏时, 用的java MediaPlayer公用小屏时的MediaPlayer, 只是更换了MediaPlayer的Surface. 小屏时MediaPlayer的Surface为SurfaceTexure, 全屏是为ContentVideoView中的SurfaceView
播放完毕WebMediaPlayer会被release, 进而MediaPlayerBridge也会被release. 同时如果此时是全屏播放, 那么全屏id(fullscreen_player_id_, 保存在BrowserMediaPlayerManager中)会随着MediaPlayerBridge的消失而变成无效(赋值为kInvalidMediaPlayerId, BrowserMediaPlayerManager会使用fullscreen_player_id_来查找相应的MediaPlayerBridge,如果fullscreen_player_id_无效就说明此时没有全屏的播放器)
每次进入全屏时会将新的MediaPlayer的id设置为全屏的id, 这部分逻辑在BrowserMediaPlayerManager::OnEnterFullscreen(int player_id)中处理
简单说就是: 每个src对应一个WebMediaPlayer,也就对应一个MediaPlayerBridge. src变化后前一个的MediaPlayer(WebMediaPlayer和MediaPlayerBridge)就会被release, 创建一个新的MediaPlayer(同上), 相应的player_id也会加1. 如果此时video标签处于全屏,那么新建的MediaPlayer也进入全屏.
具体代码如下
void HTMLMediaElement::startPlayerLoad()
{
ASSERT(!m_webMediaPlayer);
m_webMediaPlayer = frame->loader().client()->createWebMediaPlayer(*this, kurl, this);
...
m_webMediaPlayer->setVolume(effectiveMediaVolume());
m_webMediaPlayer->setPoster(posterImageURL());
m_webMediaPlayer->setPreload(effectivePreloadType());
m_webMediaPlayer->load(loadType(), kurl, corsMode());
if (isFullscreen()) { // true说明video标签处于全屏
// This handles any transition to or from fullscreen overlay mode.
document().frame()->chromeClient().enterFullScreenForElement(this);
// 会调用到BrowserMediaPlayerManager::OnEnterFullscreen(int player_id),并把该player_id设置为全屏id(fullscreen_player_id_)
}
}
Issue-1: 全屏activity启动后,chromeactivity会被隐藏,进而触发一些列的hidden操作, 期间会调用到RenderFrameObserver::WasHidden.
调用流程如下
RenderFrameObserver::WasHidden
RenderMediaPlayerManager::WasHidden
RenderMediaPlayerManager::ReleaseVideoResources // 遍历所有player,如果player->paused() || player->hasVideo(), 即该player已经暂停或是video,则release该player
WebMediaPlayerAndroid::ReleaseMediaResources()
| --- Pause(false);
| 最后调到getLocalPlayer().pause()
| --- client_->playbackStateChanged();
| --- RendererMediaPlayerManager::ReleaseResources(int player_id)
Send(new MediaPlayerHostMsg_Release(routing_id(), player_id))
================
BrowserMediaPlayerManager::OnReleaseResources(int player_id)
| --- if (player->GetVideoWidth() > 0) // 如果是video
| | --- MediaSession::Get(web_contents())->RemovePlayer(this, player_id);
| --- BrowserMediaPlayerManager::ReleasePlayer(MediaPlayerAndroid* player)
| --- MediaPlayerBridge::Release()
| MediaPlayerBridge.release // java code
| getLocalPlayer().release() // 释放java层的MediaPlayer
| --- BrowserMediaPlayerManager::ReleaseMediaResources(int player_id)
先在的逻辑是这样的:
Hide时只有当player暂停或是video时才释放java层的player, 也就是说如果是正在播放音频,则不释放,这符合用户习惯. 如果是视频不管是在暂停状态还是在播放状态都会被释放,这样符合用户习惯,毕竟视频在后台继续播放没有意义.(但如果是听mtv的歌曲呢, 所有还需改进)
Hide的过程只是释放了java层的MediaPlayer, 也就无法继续播放音视频了, 但是并没有释放c++层的WebMediaPlayerAndroid和MediaPlayeBridge. 当activity重新可见时,用户继续点击播放, 会调用WebMediaPlayerAndroid::play(), 发IPC后调用MediaPlayerBridge::Start(), 再调用MediaPlayerBridge::Prepare(), 在Prepare中建立java层的MediaPlayer来继续播放.
附1:BrowserMediaPlayerManager生命周期
创建: MediaWebContentsObserver::GetMediaPlayerManager(RenderFrameHost* render_frame_host)
BrowserMediaPlayerManager与RenderFrameHost一一对应, 建立一个map
typedef base::ScopedPtrHashMap> // uintptr_t为RenderFrameHost*
销毁: MediaWebContentsObserver::RenderFrameDeleted(RenderFrameHost* render_frame_host)
RenderFrameDeleted时从map中erase
附2: