| Index: webkit/media/android/webmediaplayer_android.cc
|
| diff --git a/webkit/media/android/webmediaplayer_android.cc b/webkit/media/android/webmediaplayer_android.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..4966d0a4da6778d33f66ad4b604a5ec24efe6547
|
| --- /dev/null
|
| +++ b/webkit/media/android/webmediaplayer_android.cc
|
| @@ -0,0 +1,486 @@
|
| +// 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/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/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;
|
| +
|
| +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(
|
| + WebMediaPlayerClient* client,
|
| + WebKit::WebCookieJar* cookie_jar)
|
| + : client_(client),
|
| + buffered_(1u),
|
| + video_frame_(new WebVideoFrameImpl(VideoFrame::CreateEmptyFrame())),
|
| + proxy_(new WebMediaPlayerProxyAndroid(base::MessageLoopProxy::current(),
|
| + AsWeakPtr())),
|
| + prepared_(false),
|
| + duration_(0),
|
| + pending_seek_(0),
|
| + seeking_(false),
|
| + playback_completed_(false),
|
| + buffered_bytes_(0),
|
| + cookie_jar_(cookie_jar),
|
| + pending_play_event_(false),
|
| + network_state_(WebMediaPlayer::Empty),
|
| + ready_state_(WebMediaPlayer::HaveNothing) {
|
| + video_frame_.reset(new WebVideoFrameImpl(VideoFrame::CreateEmptyFrame()));
|
| +}
|
| +
|
| +WebMediaPlayerAndroid::~WebMediaPlayerAndroid() {
|
| + if (media_player_.get()) {
|
| + media_player_->Stop();
|
| + }
|
| +}
|
| +
|
| +void WebMediaPlayerAndroid::InitIncognito(bool incognito_mode) {
|
| + incognito_mode_ = incognito_mode;
|
| +}
|
| +
|
| +void WebMediaPlayerAndroid::load(const WebURL& url) {
|
| + url_ = url;
|
| +
|
| + UpdateNetworkState(WebMediaPlayer::Loading);
|
| + UpdateReadyState(WebMediaPlayer::HaveNothing);
|
| +
|
| + // 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::HaveMetadata);
|
| + UpdateReadyState(WebMediaPlayer::HaveEnoughData);
|
| +}
|
| +
|
| +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();
|
| +}
|
| +
|
| +unsigned long long WebMediaPlayerAndroid::bytesLoaded() const {
|
| + return buffered_bytes_;
|
| +}
|
| +
|
| +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;
|
| +}
|
| +
|
| +WebMediaPlayer::MovieLoadType
|
| + WebMediaPlayerAndroid::movieLoadType() const {
|
| + // Deprecated.
|
| + // TODO(qinmin): Remove this from WebKit::WebMediaPlayer as it is never used.
|
| + return WebMediaPlayer::Unknown;
|
| +}
|
| +
|
| +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::Loaded);
|
| +
|
| + if (ready_state_ != WebMediaPlayer::HaveEnoughData) {
|
| + UpdateReadyState(WebMediaPlayer::HaveMetadata);
|
| + UpdateReadyState(WebMediaPlayer::HaveEnoughData);
|
| + } else {
|
| + // If the status is already set to HaveEnoughData, set it again to make sure
|
| + // that Videolayerchromium will get created.
|
| + UpdateReadyState(WebMediaPlayer::HaveEnoughData);
|
| + }
|
| +
|
| + 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;
|
| + // Implement a trick here to fake progress event, as WebKit checks
|
| + // consecutive bytesLoaded() to see if any progress made.
|
| + // See HTMLMediaElement::progressEventTimerFired.
|
| + // TODO(qinmin): need a method to calculate the buffered bytes.
|
| + buffered_bytes_++;
|
| +}
|
| +
|
| +void WebMediaPlayerAndroid::OnSeekComplete() {
|
| + seeking_ = false;
|
| +
|
| + UpdateReadyState(WebMediaPlayer::HaveEnoughData);
|
| +
|
| + 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 FormatError.
|
| + UpdateNetworkState(WebMediaPlayer::FormatError);
|
| + 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::DecodeError);
|
| + break;
|
| + case MediaPlayerBridge::MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:
|
| + UpdateNetworkState(WebMediaPlayer::FormatError);
|
| + 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::SetVideoSurface(jobject j_surface) {
|
| + if (media_player_.get())
|
| + media_player_->SetVideoSurface(j_surface);
|
| +}
|
| +
|
| +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 url(url_);
|
| + cookies = UTF16ToUTF8(cookie_jar_->cookies(url, url));
|
| + }
|
| + media_player_->SetDataSource(url_.spec(), cookies, incognito_mode_);
|
| +
|
| + 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 (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_));
|
| +}
|
| +
|
| +WebVideoFrame* WebMediaPlayerAndroid::getCurrentFrame() {
|
| + return video_frame_.get();
|
| +}
|
| +
|
| +void WebMediaPlayerAndroid::putCurrentFrame(
|
| + WebVideoFrame* web_video_frame) {
|
| +}
|
| +
|
| +} // namespace webkit_media
|
|
|