summaryrefslogtreecommitdiff
path: root/chromium/media/base/android/media_source_player.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/media/base/android/media_source_player.cc')
-rw-r--r--chromium/media/base/android/media_source_player.cc412
1 files changed, 323 insertions, 89 deletions
diff --git a/chromium/media/base/android/media_source_player.cc b/chromium/media/base/android/media_source_player.cc
index 223515e9992..ee84528a87c 100644
--- a/chromium/media/base/android/media_source_player.cc
+++ b/chromium/media/base/android/media_source_player.cc
@@ -11,12 +11,16 @@
#include "base/barrier_closure.h"
#include "base/basictypes.h"
#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/debug/trace_event.h"
#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
#include "media/base/android/audio_decoder_job.h"
#include "media/base/android/media_drm_bridge.h"
#include "media/base/android/media_player_manager.h"
#include "media/base/android/video_decoder_job.h"
#include "media/base/audio_timestamp_helper.h"
+#include "media/base/buffers.h"
namespace {
@@ -59,36 +63,36 @@ bool MediaSourcePlayer::IsTypeSupported(
MediaSourcePlayer::MediaSourcePlayer(
int player_id,
MediaPlayerManager* manager,
- int demuxer_client_id,
- DemuxerAndroid* demuxer)
+ scoped_ptr<DemuxerAndroid> demuxer)
: MediaPlayerAndroid(player_id, manager),
- demuxer_client_id_(demuxer_client_id),
- demuxer_(demuxer),
+ demuxer_(demuxer.Pass()),
pending_event_(NO_EVENT_PENDING),
- seek_request_id_(0),
width_(0),
height_(0),
audio_codec_(kUnknownAudioCodec),
video_codec_(kUnknownVideoCodec),
num_channels_(0),
sampling_rate_(0),
- audio_finished_(true),
- video_finished_(true),
+ reached_audio_eos_(false),
+ reached_video_eos_(false),
playing_(false),
is_audio_encrypted_(false),
is_video_encrypted_(false),
volume_(-1.0),
clock_(&default_tick_clock_),
+ next_video_data_is_iframe_(true),
+ doing_browser_seek_(false),
+ pending_seek_(false),
reconfig_audio_decoder_(false),
reconfig_video_decoder_(false),
weak_this_(this),
drm_bridge_(NULL),
is_waiting_for_key_(false) {
- demuxer_->AddDemuxerClient(demuxer_client_id_, this);
+ demuxer_->Initialize(this);
+ clock_.SetMaxTime(base::TimeDelta());
}
MediaSourcePlayer::~MediaSourcePlayer() {
- demuxer_->RemoveDemuxerClient(demuxer_client_id_);
Release();
}
@@ -107,31 +111,53 @@ void MediaSourcePlayer::SetVideoSurface(gfx::ScopedJavaSurface surface) {
// processed.
if (IsEventPending(SURFACE_CHANGE_EVENT_PENDING))
return;
+
+ // Eventual processing of surface change will take care of feeding the new
+ // video decoder initially with I-frame. See b/8950387.
SetPendingEvent(SURFACE_CHANGE_EVENT_PENDING);
- if (IsEventPending(SEEK_EVENT_PENDING)) {
- // Waiting for the seek to finish.
+
+ // If seek is already pending, processing of the pending surface change
+ // event will occur in OnDemuxerSeekDone().
+ if (IsEventPending(SEEK_EVENT_PENDING))
+ return;
+
+ // If video config change is already pending, processing of the pending
+ // surface change event will occur in OnDemuxerConfigsAvailable().
+ if (reconfig_video_decoder_ && IsEventPending(CONFIG_CHANGE_EVENT_PENDING))
return;
- }
- // Setting a new surface will require a new MediaCodec to be created.
- // Request a seek so that the new decoder will decode an I-frame first.
- // Or otherwise, the new MediaCodec might crash. See b/8950387.
- ScheduleSeekEventAndStopDecoding();
+ // Otherwise we need to trigger pending event processing now.
+ ProcessPendingEvents();
}
-void MediaSourcePlayer::ScheduleSeekEventAndStopDecoding() {
+void MediaSourcePlayer::ScheduleSeekEventAndStopDecoding(
+ const base::TimeDelta& seek_time) {
+ DVLOG(1) << __FUNCTION__ << "(" << seek_time.InSecondsF() << ")";
+ DCHECK(!IsEventPending(SEEK_EVENT_PENDING));
+
+ pending_seek_ = false;
+
+ clock_.SetTime(seek_time, seek_time);
+ if (audio_timestamp_helper_)
+ audio_timestamp_helper_->SetBaseTimestamp(seek_time);
+
if (audio_decoder_job_ && audio_decoder_job_->is_decoding())
audio_decoder_job_->StopDecode();
if (video_decoder_job_ && video_decoder_job_->is_decoding())
video_decoder_job_->StopDecode();
- if (IsEventPending(SEEK_EVENT_PENDING))
- return;
-
SetPendingEvent(SEEK_EVENT_PENDING);
ProcessPendingEvents();
}
+void MediaSourcePlayer::BrowserSeekToCurrentTime() {
+ DVLOG(1) << __FUNCTION__;
+
+ DCHECK(!IsEventPending(SEEK_EVENT_PENDING));
+ doing_browser_seek_ = true;
+ ScheduleSeekEventAndStopDecoding(GetCurrentTime());
+}
+
bool MediaSourcePlayer::Seekable() {
// If the duration TimeDelta, converted to milliseconds from microseconds,
// is >= 2^31, then the media is assumed to be unbounded and unseekable.
@@ -176,13 +202,23 @@ int MediaSourcePlayer::GetVideoHeight() {
return height_;
}
-void MediaSourcePlayer::SeekTo(base::TimeDelta timestamp) {
+void MediaSourcePlayer::SeekTo(const base::TimeDelta& timestamp) {
DVLOG(1) << __FUNCTION__ << "(" << timestamp.InSecondsF() << ")";
- clock_.SetTime(timestamp, timestamp);
- if (audio_timestamp_helper_)
- audio_timestamp_helper_->SetBaseTimestamp(timestamp);
- ScheduleSeekEventAndStopDecoding();
+ if (IsEventPending(SEEK_EVENT_PENDING)) {
+ DCHECK(doing_browser_seek_) << "SeekTo while SeekTo in progress";
+ DCHECK(!pending_seek_) << "SeekTo while SeekTo pending browser seek";
+
+ // There is a browser seek currently in progress to obtain I-frame to feed
+ // a newly constructed video decoder. Remember this real seek request so
+ // it can be initiated once OnDemuxerSeekDone() occurs for the browser seek.
+ pending_seek_ = true;
+ pending_seek_time_ = timestamp;
+ return;
+ }
+
+ doing_browser_seek_ = false;
+ ScheduleSeekEventAndStopDecoding(timestamp);
}
base::TimeDelta MediaSourcePlayer::GetCurrentTime() {
@@ -195,15 +231,39 @@ base::TimeDelta MediaSourcePlayer::GetDuration() {
void MediaSourcePlayer::Release() {
DVLOG(1) << __FUNCTION__;
+
+ // Allow pending seeks and config changes to survive this Release().
+ // If previously pending a prefetch done event, or a job was still decoding,
+ // then at end of Release() we need to ProcessPendingEvents() to process any
+ // seek or config change that was blocked by the prefetch or decode.
+ // TODO(qinmin/wolenetz): Maintain channel state to not double-request data
+ // or drop data received across Release()+Start(). See http://crbug.com/306314
+ // and http://crbug.com/304234.
+ bool process_pending_events = false;
+ process_pending_events = IsEventPending(PREFETCH_DONE_EVENT_PENDING) ||
+ (audio_decoder_job_ && audio_decoder_job_->is_decoding()) ||
+ (video_decoder_job_ && video_decoder_job_->is_decoding());
+
+ // Clear all the pending events except seeks and config changes.
+ pending_event_ &= (SEEK_EVENT_PENDING | CONFIG_CHANGE_EVENT_PENDING);
+
audio_decoder_job_.reset();
- video_decoder_job_.reset();
+ ResetVideoDecoderJob();
+
+ // Prevent job re-creation attempts in OnDemuxerConfigsAvailable()
reconfig_audio_decoder_ = false;
reconfig_video_decoder_ = false;
+
+ // Prevent player restart, including job re-creation attempts.
playing_ = false;
- pending_event_ = NO_EVENT_PENDING;
+
decoder_starvation_callback_.Cancel();
surface_ = gfx::ScopedJavaSurface();
- ReleaseMediaResourcesFromManager();
+ manager()->ReleaseMediaResources(player_id());
+ if (process_pending_events) {
+ DVLOG(1) << __FUNCTION__ << " : Resuming seek or config change processing";
+ ProcessPendingEvents();
+ }
}
void MediaSourcePlayer::SetVolume(double volume) {
@@ -258,8 +318,6 @@ void MediaSourcePlayer::StartInternal() {
return;
}
- audio_finished_ = false;
- video_finished_ = false;
SetPendingEvent(PREFETCH_REQUEST_EVENT_PENDING);
ProcessPendingEvents();
}
@@ -288,19 +346,15 @@ void MediaSourcePlayer::OnDemuxerConfigsAvailable(
height_ = configs.video_size.height();
is_video_encrypted_ = configs.is_video_encrypted;
- OnMediaMetadataChanged(duration_, width_, height_, true);
+ manager()->OnMediaMetadataChanged(
+ player_id(), duration_, width_, height_, true);
if (IsEventPending(CONFIG_CHANGE_EVENT_PENDING)) {
if (reconfig_audio_decoder_)
ConfigureAudioDecoderJob();
- // If there is a pending surface change, we can merge it with the config
- // change.
- if (reconfig_video_decoder_) {
- if (IsEventPending(SURFACE_CHANGE_EVENT_PENDING))
- ClearPendingEvent(SURFACE_CHANGE_EVENT_PENDING);
+ if (reconfig_video_decoder_)
ConfigureVideoDecoderJob();
- }
ClearPendingEvent(CONFIG_CHANGE_EVENT_PENDING);
@@ -313,10 +367,13 @@ void MediaSourcePlayer::OnDemuxerConfigsAvailable(
void MediaSourcePlayer::OnDemuxerDataAvailable(const DemuxerData& data) {
DVLOG(1) << __FUNCTION__ << "(" << data.type << ")";
DCHECK_LT(0u, data.access_units.size());
- if (data.type == DemuxerStream::AUDIO && audio_decoder_job_)
+ if (data.type == DemuxerStream::AUDIO && audio_decoder_job_) {
audio_decoder_job_->OnDataReceived(data);
- else if (data.type == DemuxerStream::VIDEO && video_decoder_job_)
- video_decoder_job_->OnDataReceived(data);
+ } else if (data.type == DemuxerStream::VIDEO) {
+ next_video_data_is_iframe_ = false;
+ if (video_decoder_job_)
+ video_decoder_job_->OnDataReceived(data);
+ }
}
void MediaSourcePlayer::OnDemuxerDurationChanged(base::TimeDelta duration) {
@@ -345,8 +402,8 @@ void MediaSourcePlayer::SetDrmBridge(MediaDrmBridge* drm_bridge) {
// TODO(qinmin): support DRM change after playback has started.
// http://crbug.com/253792.
if (GetCurrentTime() > base::TimeDelta()) {
- LOG(INFO) << "Setting DRM bridge after playback has started. "
- << "This is not well supported!";
+ VLOG(0) << "Setting DRM bridge after playback has started. "
+ << "This is not well supported!";
}
drm_bridge_ = drm_bridge;
@@ -361,15 +418,56 @@ void MediaSourcePlayer::SetDrmBridge(MediaDrmBridge* drm_bridge) {
StartInternal();
}
-void MediaSourcePlayer::OnDemuxerSeeked(unsigned seek_request_id) {
- DVLOG(1) << __FUNCTION__ << "(" << seek_request_id << ")";
- // Do nothing until the most recent seek request is processed.
- if (seek_request_id_ != seek_request_id)
- return;
+void MediaSourcePlayer::OnDemuxerSeekDone(
+ const base::TimeDelta& actual_browser_seek_time) {
+ DVLOG(1) << __FUNCTION__;
ClearPendingEvent(SEEK_EVENT_PENDING);
+ if (IsEventPending(PREFETCH_REQUEST_EVENT_PENDING))
+ ClearPendingEvent(PREFETCH_REQUEST_EVENT_PENDING);
+
+ next_video_data_is_iframe_ = true;
+
+ if (pending_seek_) {
+ DVLOG(1) << __FUNCTION__ << "processing pending seek";
+ DCHECK(doing_browser_seek_);
+ pending_seek_ = false;
+ SeekTo(pending_seek_time_);
+ return;
+ }
+
+ // It is possible that a browser seek to I-frame had to seek to a buffered
+ // I-frame later than the requested one due to data removal or GC. Update
+ // player clock to the actual seek target.
+ if (doing_browser_seek_) {
+ DCHECK(actual_browser_seek_time != kNoTimestamp());
+ // A browser seek must not jump into the past. Ideally, it seeks to the
+ // requested time, but it might jump into the future.
+ DCHECK(actual_browser_seek_time >= GetCurrentTime());
+ DVLOG(1) << __FUNCTION__ << " : setting clock to actual browser seek time: "
+ << actual_browser_seek_time.InSecondsF();
+ clock_.SetTime(actual_browser_seek_time, actual_browser_seek_time);
+ if (audio_timestamp_helper_)
+ audio_timestamp_helper_->SetBaseTimestamp(actual_browser_seek_time);
+ }
+
+ reached_audio_eos_ = false;
+ reached_video_eos_ = false;
+
+ base::TimeDelta current_time = GetCurrentTime();
+ // TODO(qinmin): Simplify the logic by using |start_presentation_timestamp_|
+ // to preroll media decoder jobs. Currently |start_presentation_timestamp_|
+ // is calculated from decoder output, while preroll relies on the access
+ // unit's timestamp. There are some differences between the two.
+ preroll_timestamp_ = current_time;
+ if (audio_decoder_job_)
+ audio_decoder_job_->BeginPrerolling(preroll_timestamp_);
+ if (video_decoder_job_)
+ video_decoder_job_->BeginPrerolling(preroll_timestamp_);
+
+ if (!doing_browser_seek_)
+ manager()->OnSeekComplete(player_id(), current_time);
- OnSeekComplete();
ProcessPendingEvents();
}
@@ -384,7 +482,7 @@ void MediaSourcePlayer::UpdateTimestamps(
}
clock_.SetMaxTime(new_max_time);
- OnTimeUpdated();
+ manager()->OnTimeUpdate(player_id(), GetCurrentTime());
}
void MediaSourcePlayer::ProcessPendingEvents() {
@@ -406,11 +504,9 @@ void MediaSourcePlayer::ProcessPendingEvents() {
}
if (IsEventPending(SEEK_EVENT_PENDING)) {
- int seek_request_id = ++seek_request_id_;
- DVLOG(1) << __FUNCTION__ << " : Handling SEEK_EVENT: " << seek_request_id;
+ DVLOG(1) << __FUNCTION__ << " : Handling SEEK_EVENT";
ClearDecodingData();
- demuxer_->RequestDemuxerSeek(
- demuxer_client_id_, GetCurrentTime(), seek_request_id);
+ demuxer_->RequestDemuxerSeek(GetCurrentTime(), doing_browser_seek_);
return;
}
@@ -418,32 +514,47 @@ void MediaSourcePlayer::ProcessPendingEvents() {
if (IsEventPending(CONFIG_CHANGE_EVENT_PENDING)) {
DVLOG(1) << __FUNCTION__ << " : Handling CONFIG_CHANGE_EVENT.";
DCHECK(reconfig_audio_decoder_ || reconfig_video_decoder_);
- demuxer_->RequestDemuxerConfigs(demuxer_client_id_);
+ demuxer_->RequestDemuxerConfigs();
return;
}
if (IsEventPending(SURFACE_CHANGE_EVENT_PENDING)) {
DVLOG(1) << __FUNCTION__ << " : Handling SURFACE_CHANGE_EVENT.";
- video_decoder_job_.reset();
+ // Setting a new surface will require a new MediaCodec to be created.
+ ResetVideoDecoderJob();
ConfigureVideoDecoderJob();
- ClearPendingEvent(SURFACE_CHANGE_EVENT_PENDING);
+
+ // Return early if we can't successfully configure a new video decoder job
+ // yet, except continue processing other pending events if |surface_| is
+ // empty.
+ if (HasVideo() && !video_decoder_job_ && !surface_.IsEmpty())
+ return;
}
if (IsEventPending(PREFETCH_REQUEST_EVENT_PENDING)) {
DVLOG(1) << __FUNCTION__ << " : Handling PREFETCH_REQUEST_EVENT.";
- int count = (audio_decoder_job_ ? 1 : 0) + (video_decoder_job_ ? 1 : 0);
+ DCHECK(audio_decoder_job_ || AudioFinished());
+ DCHECK(video_decoder_job_ || VideoFinished());
+
+ int count = (AudioFinished() ? 0 : 1) + (VideoFinished() ? 0 : 1);
+
+ // It is possible that all streams have finished decode, yet starvation
+ // occurred during the last stream's EOS decode. In this case, prefetch is a
+ // no-op.
+ ClearPendingEvent(PREFETCH_REQUEST_EVENT_PENDING);
+ if (count == 0)
+ return;
+ SetPendingEvent(PREFETCH_DONE_EVENT_PENDING);
base::Closure barrier = BarrierClosure(count, base::Bind(
&MediaSourcePlayer::OnPrefetchDone, weak_this_.GetWeakPtr()));
- if (audio_decoder_job_)
+ if (!AudioFinished())
audio_decoder_job_->Prefetch(barrier);
- if (video_decoder_job_)
+ if (!VideoFinished())
video_decoder_job_->Prefetch(barrier);
- SetPendingEvent(PREFETCH_DONE_EVENT_PENDING);
- ClearPendingEvent(PREFETCH_REQUEST_EVENT_PENDING);
return;
}
@@ -459,7 +570,25 @@ void MediaSourcePlayer::MediaDecoderCallback(
bool is_audio, MediaCodecStatus status,
const base::TimeDelta& presentation_timestamp, size_t audio_output_bytes) {
DVLOG(1) << __FUNCTION__ << ": " << is_audio << ", " << status;
- DCHECK(!is_waiting_for_key_);
+
+ // TODO(xhwang): Drop IntToString() when http://crbug.com/303899 is fixed.
+ if (is_audio) {
+ TRACE_EVENT_ASYNC_END1("media",
+ "MediaSourcePlayer::DecodeMoreAudio",
+ audio_decoder_job_.get(),
+ "MediaCodecStatus",
+ base::IntToString(status));
+ } else {
+ TRACE_EVENT_ASYNC_END1("media",
+ "MediaSourcePlayer::DecodeMoreVideo",
+ video_decoder_job_.get(),
+ "MediaCodecStatus",
+ base::IntToString(status));
+ }
+
+ // Let tests hook the completion of this decode cycle.
+ if (!decode_callback_for_testing_.is_null())
+ base::ResetAndReturn(&decode_callback_for_testing_).Run();
bool is_clock_manager = is_audio || !HasAudio();
@@ -467,23 +596,37 @@ void MediaSourcePlayer::MediaDecoderCallback(
decoder_starvation_callback_.Cancel();
if (status == MEDIA_CODEC_ERROR) {
+ DVLOG(1) << __FUNCTION__ << " : decode error";
Release();
- OnMediaError(MEDIA_ERROR_DECODE);
+ manager()->OnError(player_id(), MEDIA_ERROR_DECODE);
return;
}
- if (pending_event_ != NO_EVENT_PENDING) {
+ DCHECK(!IsEventPending(PREFETCH_DONE_EVENT_PENDING));
+
+ // Let |SEEK_EVENT_PENDING| (the highest priority event outside of
+ // |PREFETCH_DONE_EVENT_PENDING|) preempt output EOS detection here. Process
+ // any other pending events only after handling EOS detection.
+ if (IsEventPending(SEEK_EVENT_PENDING)) {
ProcessPendingEvents();
return;
}
- if (status == MEDIA_CODEC_OUTPUT_END_OF_STREAM) {
+ if (status == MEDIA_CODEC_OUTPUT_END_OF_STREAM)
PlaybackCompleted(is_audio);
+
+ if (pending_event_ != NO_EVENT_PENDING) {
+ ProcessPendingEvents();
return;
}
- if (status == MEDIA_CODEC_OK && is_clock_manager)
+ if (status == MEDIA_CODEC_OUTPUT_END_OF_STREAM)
+ return;
+
+ if (status == MEDIA_CODEC_OK && is_clock_manager &&
+ presentation_timestamp != kNoTimestamp()) {
UpdateTimestamps(presentation_timestamp, audio_output_bytes);
+ }
if (!playing_) {
if (is_clock_manager)
@@ -502,8 +645,15 @@ void MediaSourcePlayer::MediaDecoderCallback(
if (status == MEDIA_CODEC_STOPPED)
return;
- if (status == MEDIA_CODEC_OK && is_clock_manager)
- StartStarvationCallback(presentation_timestamp);
+ if (is_clock_manager) {
+ // If we have a valid timestamp, start the starvation callback. Otherwise,
+ // reset the |start_time_ticks_| so that the next frame will not suffer
+ // from the decoding delay caused by the current frame.
+ if (presentation_timestamp != kNoTimestamp())
+ StartStarvationCallback(presentation_timestamp);
+ else
+ start_time_ticks_ = base::TimeTicks::Now();
+ }
if (is_audio) {
DecodeMoreAudio();
@@ -516,17 +666,29 @@ void MediaSourcePlayer::MediaDecoderCallback(
void MediaSourcePlayer::DecodeMoreAudio() {
DVLOG(1) << __FUNCTION__;
DCHECK(!audio_decoder_job_->is_decoding());
+ DCHECK(!AudioFinished());
if (audio_decoder_job_->Decode(
start_time_ticks_, start_presentation_timestamp_, base::Bind(
&MediaSourcePlayer::MediaDecoderCallback,
weak_this_.GetWeakPtr(), true))) {
+ TRACE_EVENT_ASYNC_BEGIN0("media", "MediaSourcePlayer::DecodeMoreAudio",
+ audio_decoder_job_.get());
return;
}
// Failed to start the next decode.
// Wait for demuxer ready message.
+ DCHECK(!reconfig_audio_decoder_);
reconfig_audio_decoder_ = true;
+
+ // Config change may have just been detected on the other stream. If so,
+ // don't send a duplicate demuxer config request.
+ if (IsEventPending(CONFIG_CHANGE_EVENT_PENDING)) {
+ DCHECK(reconfig_video_decoder_);
+ return;
+ }
+
SetPendingEvent(CONFIG_CHANGE_EVENT_PENDING);
ProcessPendingEvents();
}
@@ -534,17 +696,34 @@ void MediaSourcePlayer::DecodeMoreAudio() {
void MediaSourcePlayer::DecodeMoreVideo() {
DVLOG(1) << __FUNCTION__;
DCHECK(!video_decoder_job_->is_decoding());
+ DCHECK(!VideoFinished());
if (video_decoder_job_->Decode(
start_time_ticks_, start_presentation_timestamp_, base::Bind(
&MediaSourcePlayer::MediaDecoderCallback,
weak_this_.GetWeakPtr(), false))) {
+ TRACE_EVENT_ASYNC_BEGIN0("media", "MediaSourcePlayer::DecodeMoreVideo",
+ video_decoder_job_.get());
return;
}
// Failed to start the next decode.
// Wait for demuxer ready message.
+
+ // After this detection of video config change, next video data received
+ // will begin with I-frame.
+ next_video_data_is_iframe_ = true;
+
+ DCHECK(!reconfig_video_decoder_);
reconfig_video_decoder_ = true;
+
+ // Config change may have just been detected on the other stream. If so,
+ // don't send a duplicate demuxer config request.
+ if (IsEventPending(CONFIG_CHANGE_EVENT_PENDING)) {
+ DCHECK(reconfig_audio_decoder_);
+ return;
+ }
+
SetPendingEvent(CONFIG_CHANGE_EVENT_PENDING);
ProcessPendingEvents();
}
@@ -552,15 +731,15 @@ void MediaSourcePlayer::DecodeMoreVideo() {
void MediaSourcePlayer::PlaybackCompleted(bool is_audio) {
DVLOG(1) << __FUNCTION__ << "(" << is_audio << ")";
if (is_audio)
- audio_finished_ = true;
+ reached_audio_eos_ = true;
else
- video_finished_ = true;
+ reached_video_eos_ = true;
- if ((!HasAudio() || audio_finished_) && (!HasVideo() || video_finished_)) {
+ if (AudioFinished() && VideoFinished()) {
playing_ = false;
clock_.Pause();
start_time_ticks_ = base::TimeTicks();
- OnPlaybackComplete();
+ manager()->OnPlaybackComplete(player_id());
}
}
@@ -581,6 +760,14 @@ bool MediaSourcePlayer::HasAudio() {
return kUnknownAudioCodec != audio_codec_;
}
+bool MediaSourcePlayer::AudioFinished() {
+ return reached_audio_eos_ || !HasAudio();
+}
+
+bool MediaSourcePlayer::VideoFinished() {
+ return reached_video_eos_ || !HasVideo();
+}
+
void MediaSourcePlayer::ConfigureAudioDecoderJob() {
if (!HasAudio()) {
audio_decoder_job_.reset();
@@ -597,38 +784,71 @@ void MediaSourcePlayer::ConfigureAudioDecoderJob() {
DCHECK(!audio_decoder_job_ || !audio_decoder_job_->is_decoding());
+ DVLOG(1) << __FUNCTION__ << " : creating new audio decoder job";
+
audio_decoder_job_.reset(AudioDecoderJob::Create(
audio_codec_, sampling_rate_, num_channels_, &audio_extra_data_[0],
audio_extra_data_.size(), media_crypto.obj(),
base::Bind(&DemuxerAndroid::RequestDemuxerData,
- base::Unretained(demuxer_), demuxer_client_id_,
- DemuxerStream::AUDIO)));
+ base::Unretained(demuxer_.get()), DemuxerStream::AUDIO)));
if (audio_decoder_job_) {
SetVolumeInternal();
+ audio_decoder_job_->BeginPrerolling(preroll_timestamp_);
reconfig_audio_decoder_ = false;
}
}
+void MediaSourcePlayer::ResetVideoDecoderJob() {
+ video_decoder_job_.reset();
+
+ // Any eventual video decoder job re-creation will use the current |surface_|.
+ if (IsEventPending(SURFACE_CHANGE_EVENT_PENDING))
+ ClearPendingEvent(SURFACE_CHANGE_EVENT_PENDING);
+}
+
void MediaSourcePlayer::ConfigureVideoDecoderJob() {
if (!HasVideo() || surface_.IsEmpty()) {
- video_decoder_job_.reset();
+ ResetVideoDecoderJob();
return;
}
- // Create video decoder job only if config changes.
- if (video_decoder_job_ && !reconfig_video_decoder_)
+ // Create video decoder job only if config changes or we don't have a job.
+ if (video_decoder_job_ && !reconfig_video_decoder_) {
+ DCHECK(!IsEventPending(SURFACE_CHANGE_EVENT_PENDING));
return;
+ }
+
+ DCHECK(!video_decoder_job_ || !video_decoder_job_->is_decoding());
+
+ if (reconfig_video_decoder_) {
+ // No hack browser seek should be required. I-Frame must be next.
+ DCHECK(next_video_data_is_iframe_) << "Received video data between "
+ << "detecting video config change and reconfiguring video decoder";
+ }
+
+ // If uncertain that video I-frame data is next and there is no seek already
+ // in process, request browser demuxer seek so the new decoder will decode
+ // an I-frame first. Otherwise, the new MediaCodec might crash. See b/8950387.
+ // Eventual OnDemuxerSeekDone() will trigger ProcessPendingEvents() and
+ // continue from here.
+ // TODO(wolenetz): Instead of doing hack browser seek, replay cached data
+ // since last keyframe. See http://crbug.com/304234.
+ if (!next_video_data_is_iframe_ && !IsEventPending(SEEK_EVENT_PENDING)) {
+ BrowserSeekToCurrentTime();
+ return;
+ }
+
+ // Release the old VideoDecoderJob first so the surface can get released.
+ // Android does not allow 2 MediaCodec instances use the same surface.
+ ResetVideoDecoderJob();
base::android::ScopedJavaLocalRef<jobject> media_crypto = GetMediaCrypto();
if (is_video_encrypted_ && media_crypto.is_null())
return;
- DCHECK(!video_decoder_job_ || !video_decoder_job_->is_decoding());
+ DVLOG(1) << __FUNCTION__ << " : creating new video decoder job";
- // Release the old VideoDecoderJob first so the surface can get released.
- // Android does not allow 2 MediaCodec instances use the same surface.
- video_decoder_job_.reset();
// Create the new VideoDecoderJob.
bool is_secure = IsProtectedSurfaceRequired();
video_decoder_job_.reset(
@@ -638,16 +858,19 @@ void MediaSourcePlayer::ConfigureVideoDecoderJob() {
surface_.j_surface().obj(),
media_crypto.obj(),
base::Bind(&DemuxerAndroid::RequestDemuxerData,
- base::Unretained(demuxer_),
- demuxer_client_id_,
+ base::Unretained(demuxer_.get()),
DemuxerStream::VIDEO)));
- if (video_decoder_job_)
- reconfig_video_decoder_ = false;
+ if (!video_decoder_job_)
+ return;
+
+ video_decoder_job_->BeginPrerolling(preroll_timestamp_);
+ reconfig_video_decoder_ = false;
// Inform the fullscreen view the player is ready.
// TODO(qinmin): refactor MediaPlayerBridge so that we have a better way
// to inform ContentVideoView.
- OnMediaMetadataChanged(duration_, width_, height_, true);
+ manager()->OnMediaMetadataChanged(
+ player_id(), duration_, width_, height_, true);
}
void MediaSourcePlayer::OnDecoderStarved() {
@@ -702,7 +925,17 @@ void MediaSourcePlayer::OnPrefetchDone() {
DVLOG(1) << __FUNCTION__;
DCHECK(!audio_decoder_job_ || !audio_decoder_job_->is_decoding());
DCHECK(!video_decoder_job_ || !video_decoder_job_->is_decoding());
- DCHECK(IsEventPending(PREFETCH_DONE_EVENT_PENDING));
+
+ // A previously posted OnPrefetchDone() could race against a Release(). If
+ // Release() won the race, we should no longer have decoder jobs.
+ // TODO(qinmin/wolenetz): Maintain channel state to not double-request data
+ // or drop data received across Release()+Start(). See http://crbug.com/306314
+ // and http://crbug.com/304234.
+ if (!IsEventPending(PREFETCH_DONE_EVENT_PENDING)) {
+ DVLOG(1) << __FUNCTION__ << " : aborting";
+ DCHECK(!audio_decoder_job_ && !video_decoder_job_);
+ return;
+ }
ClearPendingEvent(PREFETCH_DONE_EVENT_PENDING);
@@ -716,9 +949,10 @@ void MediaSourcePlayer::OnPrefetchDone() {
if (!clock_.IsPlaying())
clock_.Play();
- if (audio_decoder_job_)
+ if (!AudioFinished())
DecodeMoreAudio();
- if (video_decoder_job_)
+
+ if (!VideoFinished())
DecodeMoreVideo();
}