blob: fae25d6ffd800c028354feb528cefc1960a7c526 [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 "base/file_path.h"
#include "base/logging.h"
#include "media/base/android/media_player_bridge.h"
#include "net/base/mime_util.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaPlayerClient.h"
#include "webkit/media/android/stream_texture_factory_android.h"
#include "webkit/media/android/webmediaplayer_manager_android.h"
#include "webkit/media/webmediaplayer_util.h"
#include "webkit/media/webvideoframe_impl.h"
static const uint32 kGLTextureExternalOES = 0x8D65;
using WebKit::WebMediaPlayer;
using WebKit::WebSize;
using WebKit::WebTimeRanges;
using WebKit::WebURL;
using WebKit::WebVideoFrame;
using media::MediaPlayerBridge;
using media::VideoFrame;
namespace webkit_media {
WebMediaPlayerAndroid::WebMediaPlayerAndroid(
WebKit::WebMediaPlayerClient* client,
WebMediaPlayerManagerAndroid* manager,
StreamTextureFactory* factory)
: client_(client),
buffered_(1u),
video_frame_(new WebVideoFrameImpl(VideoFrame::CreateEmptyFrame())),
main_loop_(MessageLoop::current()),
pending_seek_(0),
seeking_(false),
did_loading_progress_(false),
manager_(manager),
network_state_(WebMediaPlayer::NetworkStateEmpty),
ready_state_(WebMediaPlayer::ReadyStateHaveNothing),
is_playing_(false),
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());
stream_id_ = stream_texture_factory_->CreateStreamTexture(&texture_id_);
}
}
WebMediaPlayerAndroid::~WebMediaPlayerAndroid() {
if (stream_id_)
stream_texture_factory_->DestroyStreamTexture(texture_id_);
if (manager_)
manager_->UnregisterMediaPlayer(player_id_);
if (main_loop_)
main_loop_->RemoveDestructionObserver(this);
}
void WebMediaPlayerAndroid::load(const WebURL& url, CORSMode cors_mode) {
if (cors_mode != CORSModeUnspecified)
NOTIMPLEMENTED() << "No CORS support";
url_ = url;
InitializeMediaPlayer(url_);
}
void WebMediaPlayerAndroid::cancelLoad() {
NOTIMPLEMENTED();
}
void WebMediaPlayerAndroid::play() {
if (hasVideo() && needs_establish_peer_)
EstablishSurfaceTexturePeer();
PlayInternal();
is_playing_ = true;
}
void WebMediaPlayerAndroid::pause() {
PauseInternal();
is_playing_ = false;
}
void WebMediaPlayerAndroid::seek(float seconds) {
pending_seek_ = seconds;
seeking_ = true;
SeekInternal(ConvertSecondsToTimestamp(seconds));
}
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) {
NOTIMPLEMENTED();
}
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 {
// If we have obtained video size information before, use it.
if (!natural_size_.isEmpty())
return true;
// 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 (!url_.has_path())
return false;
std::string mime;
if(!net::GetMimeTypeFromFile(FilePath(url_.path()), &mime))
return true;
return mime.find("audio/") == std::string::npos;
}
bool WebMediaPlayerAndroid::hasAudio() const {
// TODO(hclam): Query status of audio and return the actual value.
return true;
}
bool WebMediaPlayerAndroid::paused() const {
return !is_playing_;
}
bool WebMediaPlayerAndroid::seeking() const {
return seeking_;
}
float WebMediaPlayerAndroid::duration() const {
return static_cast<float>(duration_.InSecondsF());
}
float WebMediaPlayerAndroid::currentTime() const {
// If the player is pending for a seek, return the seek time.
if (seeking())
return pending_seek_;
return GetCurrentTimeInternal();
}
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 WebKit::WebSize& 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(base::TimeDelta duration) {
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);
}
// In we have skipped loading, we have to update webkit about the new
// duration.
if (duration_ != duration) {
duration_ = duration;
client_->durationChanged();
}
}
void WebMediaPlayerAndroid::OnPlaybackComplete() {
// 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.
OnTimeUpdate(duration_);
client_->timeChanged();
}
void WebMediaPlayerAndroid::OnBufferingUpdate(int percentage) {
buffered_[0].end = duration() * percentage / 100;
did_loading_progress_ = true;
}
void WebMediaPlayerAndroid::OnSeekComplete(base::TimeDelta current_time) {
seeking_ = false;
OnTimeUpdate(current_time);
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::OnVideoSizeChanged(int width, int height) {
if (natural_size_.width == width && natural_size_.height == height)
return;
natural_size_.width = width;
natural_size_.height = height;
if (texture_id_) {
video_frame_.reset(new WebVideoFrameImpl(VideoFrame::WrapNativeTexture(
texture_id_, kGLTextureExternalOES, natural_size_, natural_size_,
base::TimeDelta(),
base::Closure())));
}
}
void WebMediaPlayerAndroid::UpdateNetworkState(
WebMediaPlayer::NetworkState state) {
network_state_ = state;
client_->networkStateChanged();
}
void WebMediaPlayerAndroid::UpdateReadyState(
WebMediaPlayer::ReadyState state) {
ready_state_ = state;
client_->readyStateChanged();
}
void WebMediaPlayerAndroid::OnPlayerReleased() {
needs_establish_peer_ = true;
}
void WebMediaPlayerAndroid::ReleaseMediaResources() {
// Pause the media player first.
pause();
client_->playbackStateChanged();
ReleaseResourcesInternal();
OnPlayerReleased();
}
void WebMediaPlayerAndroid::WillDestroyCurrentMessageLoop() {
Destroy();
if (stream_id_) {
stream_texture_factory_->DestroyStreamTexture(texture_id_);
stream_id_ = 0;
}
video_frame_.reset(new WebVideoFrameImpl(VideoFrame::CreateEmptyFrame()));
if (manager_)
manager_->UnregisterMediaPlayer(player_id_);
manager_ = NULL;
main_loop_ = NULL;
}
WebVideoFrame* WebMediaPlayerAndroid::getCurrentFrame() {
if (stream_texture_proxy_.get() && !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) {
}
void WebMediaPlayerAndroid::setStreamTextureClient(
WebKit::WebStreamTextureClient* client) {
if (stream_texture_proxy_.get())
stream_texture_proxy_->SetClient(client);
}
void WebMediaPlayerAndroid::EstablishSurfaceTexturePeer() {
if (stream_texture_factory_.get() && stream_id_)
stream_texture_factory_->EstablishPeer(stream_id_, player_id_);
needs_establish_peer_ = false;
}
void WebMediaPlayerAndroid::UpdatePlayingState(bool is_playing) {
is_playing_ = is_playing;
}
} // namespace webkit_media