blob: 6670ded56ec68e17ad375d8a1c41bf6c8f49f0c7 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "webkit/media/android/webmediaplayer_android.h"
#include <string>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/logging.h"
#include "base/utf_string_conversions.h"
#include "media/base/android/media_player_bridge.h"
#include "net/base/mime_util.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaPlayerClient.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebCookieJar.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebRect.h"
#include "webkit/media/android/stream_texture_factory_android.h"
#include "webkit/media/android/webmediaplayer_manager_android.h"
#include "webkit/media/android/webmediaplayer_proxy_android.h"
#include "webkit/media/webmediaplayer_util.h"
#include "webkit/media/webvideoframe_impl.h"
using WebKit::WebCanvas;
using WebKit::WebMediaPlayerClient;
using WebKit::WebMediaPlayer;
using WebKit::WebRect;
using WebKit::WebSize;
using WebKit::WebTimeRanges;
using WebKit::WebURL;
using WebKit::WebVideoFrame;
using media::MediaPlayerBridge;
using media::VideoFrame;
using webkit_media::WebVideoFrameImpl;
// TODO(qinmin): Figure out where we should define this more appropriately
static const uint32 kGLTextureExternalOES = 0x8D65;
namespace webkit_media {
// Because we create the media player lazily on android, the duration of the
// media is initially unknown to us. This makes the user unable to perform
// seek. To solve this problem, we use a temporary duration of 100 seconds when
// the duration is unknown. And we scale the seek position later when duration
// is available.
// TODO(qinmin): create a thread and use android MediaMetadataRetriever
// class to extract the duration.
static const float kTemporaryDuration = 100.0f;
bool WebMediaPlayerAndroid::incognito_mode_ = false;
WebMediaPlayerAndroid::WebMediaPlayerAndroid(
WebKit::WebFrame* frame,
WebMediaPlayerClient* client,
WebKit::WebCookieJar* cookie_jar,
webkit_media::WebMediaPlayerManagerAndroid* manager,
webkit_media::StreamTextureFactory* factory)
: frame_(frame),
client_(client),
buffered_(1u),
video_frame_(new WebVideoFrameImpl(VideoFrame::CreateEmptyFrame())),
main_loop_(MessageLoop::current()),
proxy_(new WebMediaPlayerProxyAndroid(main_loop_->message_loop_proxy(),
AsWeakPtr())),
prepared_(false),
duration_(0),
pending_seek_(0),
seeking_(false),
playback_completed_(false),
did_loading_progress_(false),
cookie_jar_(cookie_jar),
manager_(manager),
pending_play_event_(false),
network_state_(WebMediaPlayer::NetworkStateEmpty),
ready_state_(WebMediaPlayer::ReadyStateHaveNothing),
texture_id_(0),
stream_id_(0),
needs_establish_peer_(true),
stream_texture_factory_(factory) {
main_loop_->AddDestructionObserver(this);
if (manager_)
player_id_ = manager_->RegisterMediaPlayer(this);
if (stream_texture_factory_.get())
stream_texture_proxy_.reset(stream_texture_factory_->CreateProxy());
}
WebMediaPlayerAndroid::~WebMediaPlayerAndroid() {
if (manager_)
manager_->UnregisterMediaPlayer(player_id_);
if (main_loop_)
main_loop_->RemoveDestructionObserver(this);
}
void WebMediaPlayerAndroid::InitIncognito(bool incognito_mode) {
incognito_mode_ = incognito_mode;
}
void WebMediaPlayerAndroid::load(const WebURL& url, CORSMode cors_mode) {
if (cors_mode != CORSModeUnspecified)
NOTIMPLEMENTED() << "No CORS support";
url_ = url;
UpdateNetworkState(WebMediaPlayer::NetworkStateLoading);
UpdateReadyState(WebMediaPlayer::ReadyStateHaveNothing);
// Calling InitializeMediaPlayer() will cause android mediaplayer to start
// buffering and decoding the data. On mobile devices, this costs a lot of
// data usage and could even introduce performance issues. So we don't
// initialize the player unless it is a local file. We will start loading
// the media only when play/seek/fullsceen button is clicked.
if (url_.SchemeIs("file")) {
InitializeMediaPlayer();
return;
}
// TODO(qinmin): we need a method to calculate the duration of the media.
// Android does not provide any function to do that.
// Set the initial duration value to kTemporaryDuration so that user can
// touch the seek bar to perform seek. We will scale the seek position later
// when we got the actual duration.
duration_ = kTemporaryDuration;
// Pretend everything has been loaded so that webkit can
// still call play() and seek().
UpdateReadyState(WebMediaPlayer::ReadyStateHaveMetadata);
UpdateReadyState(WebMediaPlayer::ReadyStateHaveEnoughData);
}
void WebMediaPlayerAndroid::cancelLoad() {
NOTIMPLEMENTED();
}
void WebMediaPlayerAndroid::play() {
if (media_player_.get()) {
if (!prepared_)
pending_play_event_ = true;
else
PlayInternal();
} else {
pending_play_event_ = true;
InitializeMediaPlayer();
}
}
void WebMediaPlayerAndroid::pause() {
if (media_player_.get()) {
if (!prepared_)
pending_play_event_ = false;
else
PauseInternal();
} else {
// We don't need to load media if pause() is called.
pending_play_event_ = false;
}
}
void WebMediaPlayerAndroid::seek(float seconds) {
// Record the time to seek when OnMediaPrepared() is called.
pending_seek_ = seconds;
// Reset |playback_completed_| so that we return the correct current time.
playback_completed_ = false;
if (media_player_.get()) {
if (prepared_)
SeekInternal(seconds);
} else {
InitializeMediaPlayer();
}
}
bool WebMediaPlayerAndroid::supportsFullscreen() const {
return true;
}
bool WebMediaPlayerAndroid::supportsSave() const {
return false;
}
void WebMediaPlayerAndroid::setEndTime(float seconds) {
// Deprecated.
// TODO(qinmin): Remove this from WebKit::WebMediaPlayer as it is never used.
}
void WebMediaPlayerAndroid::setRate(float rate) {
NOTIMPLEMENTED();
}
void WebMediaPlayerAndroid::setVolume(float volume) {
if (media_player_.get())
media_player_->SetVolume(volume, volume);
}
void WebMediaPlayerAndroid::setVisible(bool visible) {
// Deprecated.
// TODO(qinmin): Remove this from WebKit::WebMediaPlayer as it is never used.
}
bool WebMediaPlayerAndroid::totalBytesKnown() {
NOTIMPLEMENTED();
return false;
}
bool WebMediaPlayerAndroid::hasVideo() const {
// TODO(qinmin): need a better method to determine whether the current media
// content contains video. Android does not provide any function to do
// this.
// We don't know whether the current media content has video unless
// the player is prepared. If the player is not prepared, we fall back
// to the mime-type. There may be no mime-type on a redirect URL.
// In that case, we conservatively assume it contains video so that
// enterfullscreen call will not fail.
if (!prepared_) {
if (!url_.has_path())
return false;
std::string mime;
if(!net::GetMimeTypeFromFile(FilePath(url_.path()), &mime))
return true;
return mime.find("audio/") == std::string::npos;
}
return !natural_size_.isEmpty();
}
bool WebMediaPlayerAndroid::hasAudio() const {
// TODO(hclam): Query status of audio and return the actual value.
return true;
}
bool WebMediaPlayerAndroid::paused() const {
if (!prepared_)
return !pending_play_event_;
return !media_player_->IsPlaying();
}
bool WebMediaPlayerAndroid::seeking() const {
return seeking_;
}
float WebMediaPlayerAndroid::duration() const {
return duration_;
}
float WebMediaPlayerAndroid::currentTime() const {
// If the player is pending for a seek, return the seek time.
if (!prepared_ || seeking())
return pending_seek_;
// When playback is about to finish, android media player often stops
// at a time which is smaller than the duration. This makes webkit never
// know that the playback has finished. To solve this, we set the
// current time to media duration when OnPlaybackComplete() get called.
// And return the greater of the two values so that the current
// time is most updated.
if (playback_completed_)
return duration();
return static_cast<float>(media_player_->GetCurrentTime().InSecondsF());
}
int WebMediaPlayerAndroid::dataRate() const {
// Deprecated.
// TODO(qinmin): Remove this from WebKit::WebMediaPlayer as it is never used.
return 0;
}
WebSize WebMediaPlayerAndroid::naturalSize() const {
return natural_size_;
}
WebMediaPlayer::NetworkState WebMediaPlayerAndroid::networkState() const {
return network_state_;
}
WebMediaPlayer::ReadyState WebMediaPlayerAndroid::readyState() const {
return ready_state_;
}
const WebTimeRanges& WebMediaPlayerAndroid::buffered() {
return buffered_;
}
float WebMediaPlayerAndroid::maxTimeSeekable() const {
// TODO(hclam): If this stream is not seekable this should return 0.
return duration();
}
bool WebMediaPlayerAndroid::didLoadingProgress() const {
bool ret = did_loading_progress_;
did_loading_progress_ = false;
return ret;
}
unsigned long long WebMediaPlayerAndroid::totalBytes() const {
// Deprecated.
// TODO(qinmin): Remove this from WebKit::WebMediaPlayer as it is never used.
return 0;
}
void WebMediaPlayerAndroid::setSize(const WebSize& size) {
texture_size_ = size;
}
void WebMediaPlayerAndroid::paint(WebKit::WebCanvas* canvas,
const WebKit::WebRect& rect,
uint8_t alpha) {
NOTIMPLEMENTED();
}
bool WebMediaPlayerAndroid::hasSingleSecurityOrigin() const {
return false;
}
bool WebMediaPlayerAndroid::didPassCORSAccessCheck() const {
return false;
}
WebMediaPlayer::MovieLoadType
WebMediaPlayerAndroid::movieLoadType() const {
// Deprecated.
// TODO(qinmin): Remove this from WebKit::WebMediaPlayer as it is never used.
return WebMediaPlayer::MovieLoadTypeUnknown;
}
float WebMediaPlayerAndroid::mediaTimeForTimeValue(float timeValue) const {
return ConvertSecondsToTimestamp(timeValue).InSecondsF();
}
unsigned WebMediaPlayerAndroid::decodedFrameCount() const {
NOTIMPLEMENTED();
return 0;
}
unsigned WebMediaPlayerAndroid::droppedFrameCount() const {
NOTIMPLEMENTED();
return 0;
}
unsigned WebMediaPlayerAndroid::audioDecodedByteCount() const {
NOTIMPLEMENTED();
return 0;
}
unsigned WebMediaPlayerAndroid::videoDecodedByteCount() const {
NOTIMPLEMENTED();
return 0;
}
void WebMediaPlayerAndroid::OnMediaPrepared() {
if (!media_player_.get())
return;
prepared_ = true;
// Update the media duration first so that webkit will get the correct
// duration when UpdateReadyState is called.
float dur = duration_;
duration_ = media_player_->GetDuration().InSecondsF();
if (url_.SchemeIs("file"))
UpdateNetworkState(WebMediaPlayer::NetworkStateLoaded);
if (ready_state_ != WebMediaPlayer::ReadyStateHaveEnoughData) {
UpdateReadyState(WebMediaPlayer::ReadyStateHaveMetadata);
UpdateReadyState(WebMediaPlayer::ReadyStateHaveEnoughData);
} else {
// If the status is already set to ReadyStateHaveEnoughData, set it again
// to make sure that Videolayerchromium will get created.
UpdateReadyState(WebMediaPlayer::ReadyStateHaveEnoughData);
}
if (!url_.SchemeIs("file")) {
// In we have skipped loading, the duration was preset to
// kTemporaryDuration. We have to update webkit about the new duration.
if (duration_ != dur) {
// Scale the |pending_seek_| according to the new duration.
pending_seek_ = pending_seek_ * duration_ / kTemporaryDuration;
client_->durationChanged();
}
}
// If media player was recovered from a saved state, consume all the pending
// events.
seek(pending_seek_);
if (pending_play_event_)
PlayInternal();
pending_play_event_ = false;
}
void WebMediaPlayerAndroid::OnPlaybackComplete() {
// Set the current time equal to duration to let webkit know that play back
// is completed.
playback_completed_ = true;
client_->timeChanged();
}
void WebMediaPlayerAndroid::OnBufferingUpdate(int percentage) {
buffered_[0].end = duration() * percentage / 100;
did_loading_progress_ = true;
}
void WebMediaPlayerAndroid::OnSeekComplete() {
seeking_ = false;
UpdateReadyState(WebMediaPlayer::ReadyStateHaveEnoughData);
client_->timeChanged();
}
void WebMediaPlayerAndroid::OnMediaError(int error_type) {
switch (error_type) {
case MediaPlayerBridge::MEDIA_ERROR_UNKNOWN:
// When playing an bogus URL or bad file we fire a MEDIA_ERROR_UNKNOWN.
// As WebKit uses FormatError to indicate an error for bogus URL or bad
// file we default a MEDIA_ERROR_UNKNOWN to NetworkStateFormatError.
UpdateNetworkState(WebMediaPlayer::NetworkStateFormatError);
break;
case MediaPlayerBridge::MEDIA_ERROR_SERVER_DIED:
// TODO(zhenghao): Media server died. In this case, the application must
// release the MediaPlayer object and instantiate a new one.
UpdateNetworkState(WebMediaPlayer::NetworkStateDecodeError);
break;
case MediaPlayerBridge::MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:
UpdateNetworkState(WebMediaPlayer::NetworkStateFormatError);
break;
case MediaPlayerBridge::MEDIA_ERROR_INVALID_CODE:
break;
}
client_->repaint();
}
void WebMediaPlayerAndroid::OnMediaInfo(int info_type) {
NOTIMPLEMENTED();
}
void WebMediaPlayerAndroid::OnVideoSizeChanged(int width, int height) {
natural_size_.width = width;
natural_size_.height = height;
}
void WebMediaPlayerAndroid::UpdateNetworkState(
WebMediaPlayer::NetworkState state) {
network_state_ = state;
client_->networkStateChanged();
}
void WebMediaPlayerAndroid::UpdateReadyState(
WebMediaPlayer::ReadyState state) {
ready_state_ = state;
client_->readyStateChanged();
}
void WebMediaPlayerAndroid::ReleaseMediaResources() {
// Pause the media player first.
pause();
client_->playbackStateChanged();
if (media_player_.get()) {
// Save the current media player status.
pending_seek_ = currentTime();
duration_ = duration();
media_player_.reset();
needs_establish_peer_ = true;
}
prepared_ = false;
}
bool WebMediaPlayerAndroid::IsInitialized() const {
return (media_player_ != NULL);
}
void WebMediaPlayerAndroid::InitializeMediaPlayer() {
CHECK(!media_player_.get());
prepared_ = false;
media_player_.reset(new MediaPlayerBridge());
media_player_->SetStayAwakeWhilePlaying();
std::string cookies;
if (cookie_jar_ != NULL) {
WebURL first_party_url(frame_->document().firstPartyForCookies());
cookies = UTF16ToUTF8(cookie_jar_->cookies(url_, first_party_url));
}
media_player_->SetDataSource(url_.spec(), cookies, incognito_mode_);
if (manager_)
manager_->RequestMediaResources(player_id_);
media_player_->Prepare(
base::Bind(&WebMediaPlayerProxyAndroid::MediaInfoCallback, proxy_),
base::Bind(&WebMediaPlayerProxyAndroid::MediaErrorCallback, proxy_),
base::Bind(&WebMediaPlayerProxyAndroid::VideoSizeChangedCallback, proxy_),
base::Bind(&WebMediaPlayerProxyAndroid::BufferingUpdateCallback, proxy_),
base::Bind(&WebMediaPlayerProxyAndroid::MediaPreparedCallback, proxy_));
}
void WebMediaPlayerAndroid::PlayInternal() {
CHECK(prepared_);
if (hasVideo() && stream_texture_factory_.get()) {
if (!stream_id_)
CreateStreamTexture();
if (needs_establish_peer_) {
stream_texture_factory_->EstablishPeer(stream_id_, player_id_);
needs_establish_peer_ = false;
}
}
if (paused())
media_player_->Start(base::Bind(
&WebMediaPlayerProxyAndroid::PlaybackCompleteCallback, proxy_));
}
void WebMediaPlayerAndroid::PauseInternal() {
CHECK(prepared_);
media_player_->Pause();
}
void WebMediaPlayerAndroid::SeekInternal(float seconds) {
CHECK(prepared_);
seeking_ = true;
media_player_->SeekTo(ConvertSecondsToTimestamp(seconds), base::Bind(
&WebMediaPlayerProxyAndroid::SeekCompleteCallback, proxy_));
}
void WebMediaPlayerAndroid::CreateStreamTexture() {
DCHECK(!stream_id_);
DCHECK(!texture_id_);
stream_id_ = stream_texture_factory_->CreateStreamTexture(&texture_id_);
if (texture_id_)
video_frame_.reset(new WebVideoFrameImpl(VideoFrame::WrapNativeTexture(
texture_id_,
kGLTextureExternalOES,
texture_size_.width,
texture_size_.height,
base::TimeDelta(),
base::TimeDelta(),
base::Bind(&WebMediaPlayerAndroid::DestroyStreamTexture,
base::Unretained(this)))));
}
void WebMediaPlayerAndroid::DestroyStreamTexture() {
DCHECK(stream_id_);
DCHECK(texture_id_);
stream_texture_factory_->DestroyStreamTexture(texture_id_);
texture_id_ = 0;
stream_id_ = 0;
}
void WebMediaPlayerAndroid::WillDestroyCurrentMessageLoop() {
manager_ = NULL;
main_loop_ = NULL;
}
WebVideoFrame* WebMediaPlayerAndroid::getCurrentFrame() {
if (!stream_texture_proxy_->IsInitialized() && stream_id_) {
stream_texture_proxy_->Initialize(
stream_id_, video_frame_->width(), video_frame_->height());
}
return video_frame_.get();
}
void WebMediaPlayerAndroid::putCurrentFrame(
WebVideoFrame* web_video_frame) {
}
// This gets called both on compositor and main thread.
void WebMediaPlayerAndroid::setStreamTextureClient(
WebKit::WebStreamTextureClient* client) {
if (stream_texture_proxy_.get())
stream_texture_proxy_->SetClient(client);
}
} // namespace webkit_media