blob: 36719a92b3c2e61560b36c152940fe4507b05c5e [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/filter/filter_based_player_worker_handler.h"
#include "starboard/audio_sink.h"
#include "starboard/log.h"
#include "starboard/memory.h"
#include "starboard/shared/starboard/drm/drm_system_internal.h"
#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
#include "starboard/shared/starboard/player/filter/player_components.h"
#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
#include "starboard/shared/starboard/player/input_buffer_internal.h"
#include "starboard/time.h"
namespace starboard {
namespace shared {
namespace starboard {
namespace player {
namespace filter {
namespace {
typedef MediaTimeProviderImpl::MonotonicSystemTimeProvider
MonotonicSystemTimeProvider;
// TODO: Make this configurable inside SbPlayerCreate().
const SbTimeMonotonic kUpdateInterval = 5 * kSbTimeMillisecond;
class MonotonicSystemTimeProviderImpl : public MonotonicSystemTimeProvider {
SbTimeMonotonic GetMonotonicNow() const override {
return SbTimeGetMonotonicNow();
}
};
} // namespace
FilterBasedPlayerWorkerHandler::FilterBasedPlayerWorkerHandler(
SbMediaVideoCodec video_codec,
SbMediaAudioCodec audio_codec,
SbDrmSystem drm_system,
const SbMediaAudioHeader* audio_header,
SbPlayerOutputMode output_mode,
SbDecodeTargetGraphicsContextProvider* provider)
: video_codec_(video_codec),
audio_codec_(audio_codec),
drm_system_(drm_system),
output_mode_(output_mode),
decode_target_graphics_context_provider_(provider) {
if (audio_codec != kSbMediaAudioCodecNone) {
audio_header_ = *audio_header;
#if SB_API_VERSION >= 6
if (audio_header_.audio_specific_config_size > 0) {
audio_specific_config_.reset(
new int8_t[audio_header_.audio_specific_config_size]);
SbMemoryCopy(audio_specific_config_.get(),
audio_header->audio_specific_config,
audio_header->audio_specific_config_size);
audio_header_.audio_specific_config = audio_specific_config_.get();
}
#endif // SB_API_VERSION >= 6
}
update_job_ = std::bind(&FilterBasedPlayerWorkerHandler::Update, this);
bounds_ = PlayerWorker::Bounds();
}
bool FilterBasedPlayerWorkerHandler::IsPunchoutMode() const {
return (output_mode_ == kSbPlayerOutputModePunchOut);
}
bool FilterBasedPlayerWorkerHandler::Init(
PlayerWorker* player_worker,
JobQueue* job_queue,
SbPlayer player,
UpdateMediaTimeCB update_media_time_cb,
GetPlayerStateCB get_player_state_cb,
UpdatePlayerStateCB update_player_state_cb
#if SB_HAS(PLAYER_ERROR_MESSAGE)
,
UpdatePlayerErrorCB update_player_error_cb
#endif // SB_HAS(PLAYER_ERROR_MESSAGE)
) {
// This function should only be called once.
SB_DCHECK(player_worker_ == NULL);
// All parameters have to be valid.
SB_DCHECK(player_worker);
SB_DCHECK(job_queue);
SB_DCHECK(job_queue->BelongsToCurrentThread());
SB_DCHECK(SbPlayerIsValid(player));
SB_DCHECK(update_media_time_cb);
SB_DCHECK(get_player_state_cb);
SB_DCHECK(update_player_state_cb);
player_worker_ = player_worker;
job_queue_ = job_queue;
player_ = player;
update_media_time_cb_ = update_media_time_cb;
get_player_state_cb_ = get_player_state_cb;
update_player_state_cb_ = update_player_state_cb;
#if SB_HAS(PLAYER_ERROR_MESSAGE)
update_player_error_cb_ = update_player_error_cb;
#endif // SB_HAS(PLAYER_ERROR_MESSAGE)
scoped_ptr<PlayerComponents> player_components = PlayerComponents::Create();
SB_DCHECK(player_components);
if (audio_codec_ != kSbMediaAudioCodecNone) {
// TODO: This is not ideal as we should really handle the creation failure
// of audio sink inside the audio renderer to give the renderer a chance to
// resample the decoded audio.
const int audio_channels = audio_header_.number_of_channels;
if (audio_channels > SbAudioSinkGetMaxChannels()) {
return false;
}
PlayerComponents::AudioParameters audio_parameters = {
audio_codec_, audio_header_, drm_system_, job_queue_};
audio_renderer_ = player_components->CreateAudioRenderer(audio_parameters);
SB_DCHECK(audio_renderer_);
if (audio_renderer_) {
audio_renderer_->SetPlaybackRate(playback_rate_);
audio_renderer_->SetVolume(volume_);
audio_renderer_->Initialize(
std::bind(&FilterBasedPlayerWorkerHandler::OnError, this));
}
} else {
media_time_provider_impl_.reset(
new MediaTimeProviderImpl(scoped_ptr<MonotonicSystemTimeProvider>(
new MonotonicSystemTimeProviderImpl)));
media_time_provider_impl_->SetPlaybackRate(playback_rate_);
}
PlayerComponents::VideoParameters video_parameters = {
player_, video_codec_, drm_system_,
job_queue_, output_mode_, decode_target_graphics_context_provider_};
::starboard::ScopedLock lock(video_renderer_existence_mutex_);
auto media_time_provider = GetMediaTimeProvider();
SB_DCHECK(media_time_provider);
video_renderer_ = player_components->CreateVideoRenderer(video_parameters,
media_time_provider);
SB_DCHECK(video_renderer_);
if (video_renderer_) {
video_renderer_->Initialize(
std::bind(&FilterBasedPlayerWorkerHandler::OnError, this));
}
update_job_token_ = job_queue_->Schedule(update_job_, kUpdateInterval);
return true;
}
bool FilterBasedPlayerWorkerHandler::Seek(SbTime seek_to_time, int ticket) {
SB_UNREFERENCED_PARAMETER(ticket);
SB_DCHECK(job_queue_->BelongsToCurrentThread());
if (!GetMediaTimeProvider() || !video_renderer_) {
return false;
}
if (seek_to_time < 0) {
SB_DLOG(ERROR) << "Try to seek to negative timestamp " << seek_to_time;
seek_to_time = 0;
}
GetMediaTimeProvider()->Pause();
video_renderer_->Seek(seek_to_time);
GetMediaTimeProvider()->Seek(seek_to_time);
return true;
}
bool FilterBasedPlayerWorkerHandler::WriteSample(
const scoped_refptr<InputBuffer>& input_buffer,
bool* written) {
SB_DCHECK(input_buffer);
SB_DCHECK(job_queue_->BelongsToCurrentThread());
SB_DCHECK(written != NULL);
if (input_buffer->sample_type() == kSbMediaTypeAudio) {
if (!audio_renderer_) {
return false;
}
*written = true;
if (audio_renderer_->IsEndOfStreamWritten()) {
SB_LOG(WARNING) << "Try to write audio sample after EOS is reached";
} else {
if (!audio_renderer_->CanAcceptMoreData()) {
*written = false;
return true;
}
if (input_buffer->drm_info()) {
if (!SbDrmSystemIsValid(drm_system_)) {
return false;
}
SbDrmSystemPrivate::DecryptStatus decrypt_status =
drm_system_->Decrypt(input_buffer);
if (decrypt_status == SbDrmSystemPrivate::kRetry) {
*written = false;
return true;
}
if (decrypt_status == SbDrmSystemPrivate::kFailure) {
*written = false;
return false;
}
}
audio_renderer_->WriteSample(input_buffer);
}
} else {
SB_DCHECK(input_buffer->sample_type() == kSbMediaTypeVideo);
if (!video_renderer_) {
return false;
}
*written = true;
if (video_renderer_->IsEndOfStreamWritten()) {
SB_LOG(WARNING) << "Try to write video sample after EOS is reached";
} else {
if (!video_renderer_->CanAcceptMoreData()) {
*written = false;
return true;
}
if (input_buffer->drm_info()) {
if (!SbDrmSystemIsValid(drm_system_)) {
return false;
}
SbDrmSystemPrivate::DecryptStatus decrypt_status =
drm_system_->Decrypt(input_buffer);
if (decrypt_status == SbDrmSystemPrivate::kRetry) {
*written = false;
return true;
}
if (decrypt_status == SbDrmSystemPrivate::kFailure) {
*written = false;
return false;
}
}
if (media_time_provider_impl_) {
media_time_provider_impl_->UpdateVideoDuration(
input_buffer->timestamp());
}
video_renderer_->WriteSample(input_buffer);
}
}
return true;
}
bool FilterBasedPlayerWorkerHandler::WriteEndOfStream(SbMediaType sample_type) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
if (sample_type == kSbMediaTypeAudio) {
if (!audio_renderer_) {
return false;
}
if (audio_renderer_->IsEndOfStreamWritten()) {
SB_LOG(WARNING) << "Try to write audio EOS after EOS is enqueued";
} else {
SB_LOG(INFO) << "Audio EOS enqueued";
audio_renderer_->WriteEndOfStream();
}
} else {
if (!video_renderer_) {
return false;
}
if (video_renderer_->IsEndOfStreamWritten()) {
SB_LOG(WARNING) << "Try to write video EOS after EOS is enqueued";
} else {
SB_LOG(INFO) << "Video EOS enqueued";
if (media_time_provider_impl_) {
media_time_provider_impl_->VideoEndOfStreamReached();
}
video_renderer_->WriteEndOfStream();
}
}
return true;
}
bool FilterBasedPlayerWorkerHandler::SetPause(bool pause) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
if (!GetMediaTimeProvider()) {
return false;
}
paused_ = pause;
if (pause) {
GetMediaTimeProvider()->Pause();
SB_DLOG(INFO) << "Playback paused.";
} else {
GetMediaTimeProvider()->Play();
SB_DLOG(INFO) << "Playback started.";
}
return true;
}
bool FilterBasedPlayerWorkerHandler::SetPlaybackRate(double playback_rate) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
playback_rate_ = playback_rate;
if (!GetMediaTimeProvider()) {
return false;
}
GetMediaTimeProvider()->SetPlaybackRate(playback_rate_);
return true;
}
void FilterBasedPlayerWorkerHandler::SetVolume(double volume) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
volume_ = volume;
if (audio_renderer_) {
audio_renderer_->SetVolume(volume_);
}
}
bool FilterBasedPlayerWorkerHandler::SetBounds(
const PlayerWorker::Bounds& bounds) {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
if (SbMemoryCompare(&bounds_, &bounds, sizeof(bounds_)) != 0) {
bounds_ = bounds;
if (video_renderer_) {
// TODO: Force a frame update
video_renderer_->SetBounds(bounds.z_index, bounds.x, bounds.y,
bounds.width, bounds.height);
}
}
return true;
}
void FilterBasedPlayerWorkerHandler::OnError() {
if (!job_queue_->BelongsToCurrentThread()) {
job_queue_->Schedule(
std::bind(&FilterBasedPlayerWorkerHandler::OnError, this));
return;
}
#if SB_HAS(PLAYER_ERROR_MESSAGE)
if (update_player_error_cb_) {
(*player_worker_.*
update_player_error_cb_)("FilterBasedPlayerWorkerHandler error.");
}
#else // SB_HAS(PLAYER_ERROR_MESSAGE)
(*player_worker_.*update_player_state_cb_)(kSbPlayerStateError);
#endif // SB_HAS(PLAYER_ERROR_MESSAGE)
}
// TODO: This should be driven by callbacks instead polling.
void FilterBasedPlayerWorkerHandler::Update() {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
if (!GetMediaTimeProvider() || !video_renderer_) {
return;
}
if ((*player_worker_.*get_player_state_cb_)() == kSbPlayerStatePrerolling) {
bool audio_seek_in_progress =
audio_renderer_ && audio_renderer_->IsSeekingInProgress();
if (!audio_seek_in_progress && !video_renderer_->IsSeekingInProgress()) {
(*player_worker_.*update_player_state_cb_)(kSbPlayerStatePresenting);
if (!paused_) {
GetMediaTimeProvider()->Play();
}
}
}
if ((*player_worker_.*get_player_state_cb_)() == kSbPlayerStatePresenting) {
bool is_audio_playing;
bool is_audio_eos_played;
GetMediaTimeProvider()->GetCurrentMediaTime(&is_audio_playing,
&is_audio_eos_played);
if (is_audio_eos_played && video_renderer_->IsEndOfStreamPlayed()) {
(*player_worker_.*update_player_state_cb_)(kSbPlayerStateEndOfStream);
}
player_worker_->UpdateDroppedVideoFrames(
video_renderer_->GetDroppedFrames());
bool is_playing;
bool is_eos_played;
(*player_worker_.*
update_media_time_cb_)(GetMediaTimeProvider()->GetCurrentMediaTime(
&is_playing, &is_eos_played));
}
update_job_token_ = job_queue_->Schedule(update_job_, kUpdateInterval);
}
void FilterBasedPlayerWorkerHandler::Stop() {
SB_DCHECK(job_queue_->BelongsToCurrentThread());
job_queue_->RemoveJobByToken(update_job_token_);
scoped_ptr<VideoRenderer> video_renderer;
{
// Set |video_renderer_| to null with the lock, but we actually destroy
// it outside of the lock. This is because the VideoRenderer destructor
// may post a task to destroy the SbDecodeTarget to the same thread that
// might call GetCurrentDecodeTarget(), which would try to take this lock.
::starboard::ScopedLock lock(video_renderer_existence_mutex_);
video_renderer = video_renderer_.Pass();
}
video_renderer.reset();
audio_renderer_.reset();
media_time_provider_impl_.reset();
}
SbDecodeTarget FilterBasedPlayerWorkerHandler::GetCurrentDecodeTarget() {
::starboard::ScopedLock lock(video_renderer_existence_mutex_);
return video_renderer_ ? video_renderer_->GetCurrentDecodeTarget()
: kSbDecodeTargetInvalid;
}
MediaTimeProvider* FilterBasedPlayerWorkerHandler::GetMediaTimeProvider()
const {
if (audio_renderer_) {
return audio_renderer_.get();
}
return media_time_provider_impl_.get();
}
} // namespace filter
} // namespace player
} // namespace starboard
} // namespace shared
} // namespace starboard