blob: 0be4ba6ff6d69c7fbaea09c61bfe152a16b44f6b [file] [log] [blame]
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/starboard/player/player_worker.h"
#include <string>
#include "starboard/common/reset_and_return.h"
#include "starboard/condition_variable.h"
#include "starboard/memory.h"
#include "starboard/mutex.h"
namespace starboard {
namespace shared {
namespace starboard {
namespace player {
namespace {
using std::placeholders::_1;
using std::placeholders::_2;
// 8 ms is enough to ensure that DoWritePendingSamples() is called twice for
// every frame in HFR.
// TODO: Reduce this as there should be enough frames caches in the renderers.
// Also this should be configurable for platforms with very limited video
// backlogs.
const SbTimeMonotonic kWritePendingSampleDelay = 8 * kSbTimeMillisecond;
struct ThreadParam {
explicit ThreadParam(PlayerWorker* player_worker)
: condition_variable(mutex), player_worker(player_worker) {}
Mutex mutex;
ConditionVariable condition_variable;
PlayerWorker* player_worker;
};
} // namespace
PlayerWorker::PlayerWorker(SbMediaAudioCodec audio_codec,
SbMediaVideoCodec video_codec,
scoped_ptr<Handler> handler,
UpdateMediaInfoCB update_media_info_cb,
SbPlayerDecoderStatusFunc decoder_status_func,
SbPlayerStatusFunc player_status_func,
#if SB_HAS(PLAYER_ERROR_MESSAGE)
SbPlayerErrorFunc player_error_func,
#endif // SB_HAS(PLAYER_ERROR_MESSAGE)
SbPlayer player,
void* context)
: thread_(kSbThreadInvalid),
audio_codec_(audio_codec),
video_codec_(video_codec),
handler_(handler.Pass()),
update_media_info_cb_(update_media_info_cb),
decoder_status_func_(decoder_status_func),
player_status_func_(player_status_func),
#if SB_HAS(PLAYER_ERROR_MESSAGE)
player_error_func_(player_error_func),
#endif // SB_HAS(PLAYER_ERROR_MESSAGE)
player_(player),
context_(context),
ticket_(SB_PLAYER_INITIAL_TICKET),
player_state_(kSbPlayerStateInitialized) {
SB_DCHECK(handler_ != NULL);
SB_DCHECK(update_media_info_cb_);
ThreadParam thread_param(this);
thread_ = SbThreadCreate(0, kSbThreadPriorityHigh, kSbThreadNoAffinity, true,
"player_worker", &PlayerWorker::ThreadEntryPoint,
&thread_param);
if (!SbThreadIsValid(thread_)) {
SB_DLOG(ERROR) << "Failed to create thread in PlayerWorker constructor.";
return;
}
ScopedLock scoped_lock(thread_param.mutex);
while (!job_queue_) {
thread_param.condition_variable.Wait();
}
SB_DCHECK(job_queue_);
}
PlayerWorker::~PlayerWorker() {
job_queue_->Schedule(std::bind(&PlayerWorker::DoStop, this));
SbThreadJoin(thread_, NULL);
thread_ = kSbThreadInvalid;
// Now the whole pipeline has been torn down and no callback will be called.
// The caller can ensure that upon the return of SbPlayerDestroy() all side
// effects are gone.
}
void PlayerWorker::UpdateMediaInfo(SbTime time, int dropped_video_frames) {
update_media_info_cb_(time, dropped_video_frames, ticket_);
}
void PlayerWorker::UpdatePlayerState(SbPlayerState player_state) {
#if SB_HAS(PLAYER_ERROR_MESSAGE)
SB_DCHECK(!error_occurred_) << "Player state should not update after error.";
if (error_occurred_) {
return;
}
#else // SB_HAS(PLAYER_ERROR_MESSAGE)
SB_DCHECK(error_occurred_ == (player_state == kSbPlayerStateError))
<< "Player state error if and only if error occurred.";
if (error_occurred_ && (player_state != kSbPlayerStateError)) {
return;
}
#endif // SB_HAS(PLAYER_ERROR_MESSAGE)
player_state_ = player_state;
if (!player_status_func_) {
return;
}
player_status_func_(player_, context_, player_state_, ticket_);
}
void PlayerWorker::UpdatePlayerError(const std::string& message) {
SB_DLOG(WARNING) << "encountered player error: " << message;
error_occurred_ = true;
#if SB_HAS(PLAYER_ERROR_MESSAGE)
if (!player_error_func_) {
return;
}
player_error_func_(player_, context_, kSbPlayerErrorDecode, message.c_str());
#else // SB_HAS(PLAYER_ERROR_MESSAGE)
UpdatePlayerState(kSbPlayerStateError);
#endif // SB_HAS(PLAYER_ERROR_MESSAGE)
}
// static
void* PlayerWorker::ThreadEntryPoint(void* context) {
ThreadParam* param = static_cast<ThreadParam*>(context);
SB_DCHECK(param != NULL);
PlayerWorker* player_worker = param->player_worker;
{
ScopedLock scoped_lock(param->mutex);
player_worker->job_queue_.reset(new JobQueue);
param->condition_variable.Signal();
}
player_worker->RunLoop();
return NULL;
}
void PlayerWorker::RunLoop() {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
DoInit();
job_queue_->RunUntilStopped();
job_queue_.reset();
}
void PlayerWorker::DoInit() {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
Handler::UpdatePlayerErrorCB update_player_error_cb;
#if SB_HAS(PLAYER_ERROR_MESSAGE)
update_player_error_cb =
std::bind(&PlayerWorker::UpdatePlayerError, this, _1);
#endif // SB_HAS(PLAYER_ERROR_MESSAGE)
if (handler_->Init(player_,
std::bind(&PlayerWorker::UpdateMediaInfo, this, _1, _2),
std::bind(&PlayerWorker::player_state, this),
std::bind(&PlayerWorker::UpdatePlayerState, this, _1),
update_player_error_cb)) {
UpdatePlayerState(kSbPlayerStateInitialized);
} else {
UpdatePlayerError("Failed to initialize PlayerWorker.");
}
}
void PlayerWorker::DoSeek(SbTime seek_to_time, int ticket) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
SB_DCHECK(player_state_ != kSbPlayerStateDestroyed);
SB_DCHECK(!error_occurred_);
SB_DCHECK(ticket_ != ticket);
SB_DLOG(INFO) << "Try to seek to timestamp " << seek_to_time / kSbTimeSecond;
if (write_pending_sample_job_token_.is_valid()) {
job_queue_->RemoveJobByToken(write_pending_sample_job_token_);
write_pending_sample_job_token_.ResetToInvalid();
}
pending_audio_buffer_ = NULL;
pending_video_buffer_ = NULL;
if (!handler_->Seek(seek_to_time, ticket)) {
UpdatePlayerError("Failed seek.");
return;
}
ticket_ = ticket;
UpdatePlayerState(kSbPlayerStatePrerolling);
if (audio_codec_ != kSbMediaAudioCodecNone) {
UpdateDecoderState(kSbMediaTypeAudio, kSbPlayerDecoderStateNeedsData);
}
if (video_codec_ != kSbMediaVideoCodecNone) {
UpdateDecoderState(kSbMediaTypeVideo, kSbPlayerDecoderStateNeedsData);
}
}
void PlayerWorker::DoWriteSample(
const scoped_refptr<InputBuffer>& input_buffer) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
SB_DCHECK(input_buffer);
if (player_state_ == kSbPlayerStateInitialized ||
player_state_ == kSbPlayerStateEndOfStream ||
player_state_ == kSbPlayerStateDestroyed) {
SB_LOG(ERROR) << "Tried to write sample when |player_state_| is "
<< player_state_;
return;
}
if (error_occurred_) {
SB_LOG(ERROR) << "Tried to write sample after error occurred.";
return;
}
if (input_buffer->sample_type() == kSbMediaTypeAudio) {
SB_DCHECK(audio_codec_ != kSbMediaAudioCodecNone);
SB_DCHECK(!pending_audio_buffer_);
} else {
SB_DCHECK(video_codec_ != kSbMediaVideoCodecNone);
SB_DCHECK(!pending_video_buffer_);
}
bool written;
bool result = handler_->WriteSample(input_buffer, &written);
if (!result) {
UpdatePlayerError("Failed to write sample.");
return;
}
if (written) {
UpdateDecoderState(input_buffer->sample_type(),
kSbPlayerDecoderStateNeedsData);
} else {
if (input_buffer->sample_type() == kSbMediaTypeAudio) {
pending_audio_buffer_ = input_buffer;
} else {
pending_video_buffer_ = input_buffer;
}
if (!write_pending_sample_job_token_.is_valid()) {
write_pending_sample_job_token_ = job_queue_->Schedule(
std::bind(&PlayerWorker::DoWritePendingSamples, this),
kWritePendingSampleDelay);
}
}
}
void PlayerWorker::DoWritePendingSamples() {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
SB_DCHECK(write_pending_sample_job_token_.is_valid());
write_pending_sample_job_token_.ResetToInvalid();
if (pending_audio_buffer_) {
SB_DCHECK(audio_codec_ != kSbMediaAudioCodecNone);
DoWriteSample(common::ResetAndReturn(&pending_audio_buffer_));
}
if (pending_video_buffer_) {
SB_DCHECK(video_codec_ != kSbMediaVideoCodecNone);
DoWriteSample(common::ResetAndReturn(&pending_video_buffer_));
}
}
void PlayerWorker::DoWriteEndOfStream(SbMediaType sample_type) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
SB_DCHECK(player_state_ != kSbPlayerStateDestroyed);
if (player_state_ == kSbPlayerStateInitialized ||
player_state_ == kSbPlayerStateEndOfStream) {
SB_LOG(ERROR) << "Tried to write EOS when |player_state_| is "
<< player_state_;
return;
}
if (error_occurred_) {
SB_LOG(ERROR) << "Tried to write EOS after error occurred.";
return;
}
if (sample_type == kSbMediaTypeAudio) {
SB_DCHECK(audio_codec_ != kSbMediaAudioCodecNone);
SB_DCHECK(!pending_audio_buffer_);
} else {
SB_DCHECK(video_codec_ != kSbMediaVideoCodecNone);
SB_DCHECK(!pending_video_buffer_);
}
if (!handler_->WriteEndOfStream(sample_type)) {
UpdatePlayerError("Failed to write end of stream.");
}
}
void PlayerWorker::DoSetBounds(Bounds bounds) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
if (!handler_->SetBounds(bounds)) {
UpdatePlayerError("Failed to set bounds");
}
}
void PlayerWorker::DoSetPause(bool pause) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
if (!handler_->SetPause(pause)) {
UpdatePlayerError("Failed to set pause.");
}
}
void PlayerWorker::DoSetPlaybackRate(double playback_rate) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
if (!handler_->SetPlaybackRate(playback_rate)) {
UpdatePlayerError("Failed to set playback rate.");
}
}
void PlayerWorker::DoSetVolume(double volume) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
handler_->SetVolume(volume);
}
void PlayerWorker::DoStop() {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
handler_->Stop();
handler_.reset();
if (!error_occurred_) {
UpdatePlayerState(kSbPlayerStateDestroyed);
}
job_queue_->StopSoon();
}
void PlayerWorker::UpdateDecoderState(SbMediaType type,
SbPlayerDecoderState state) {
SB_DCHECK(type == kSbMediaTypeAudio || type == kSbMediaTypeVideo);
if (!decoder_status_func_) {
return;
}
decoder_status_func_(player_, context_, type, state, ticket_);
}
} // namespace player
} // namespace starboard
} // namespace shared
} // namespace starboard