blob: 716bb8465a5983186d3d405fc2b113ad38f0d36f [file] [log] [blame]
// Copyright 2016 The Cobalt Authors. 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 <algorithm>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/compiler_specific.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/threading/platform_thread.h"
#include "cobalt/base/wrap_main.h"
#include "cobalt/media/sandbox/format_guesstimator.h"
#include "cobalt/media/sandbox/media_sandbox.h"
#include "cobalt/media/sandbox/web_media_player_helper.h"
#include "cobalt/render_tree/image.h"
#if !defined(COBALT_MEDIA_SOURCE_2016)
#include "media/base/video_frame.h"
#endif // !defined(COBALT_MEDIA_SOURCE_2016)
#include "starboard/event.h"
#include "starboard/file.h"
#include "starboard/system.h"
namespace cobalt {
namespace media {
namespace sandbox {
namespace {
#if !defined(COBALT_MEDIA_SOURCE_2016)
using ::media::ChunkDemuxer;
using ::media::PIPELINE_OK;
using ::media::Ranges;
using ::media::VideoFrame;
using ::media::WebMediaPlayer;
#endif // !defined(COBALT_MEDIA_SOURCE_2016)
using base::TimeDelta;
using render_tree::Image;
using starboard::ScopedFile;
void PrintUsage(const char* executable_path_name) {
std::string executable_file_name =
FilePath(executable_path_name).BaseName().value();
const char kExampleAdaptiveAudioPathName[] =
"cobalt/demos/media-element-demo/dash-audio.mp4";
const char kExampleAdaptiveVideoPathName[] =
"cobalt/demos/media-element-demo/dash-video-1080p.mp4";
const char kExampleProgressiveUrl[] =
"https://storage.googleapis.com/yt-cobalt-media-element-demo/"
"progressive.mp4";
LOG(ERROR) << "\n\n\n" // Extra empty lines to separate from other messages
<< "Usage: " << executable_file_name
<< " [OPTIONS] <adaptive audio file path>\n"
<< " or: " << executable_file_name
<< " [OPTIONS] <adaptive video file path>\n"
<< " or: " << executable_file_name
<< " [OPTIONS] <adaptive audio file path> "
<< " <adaptive video file path>\n"
<< " or: " << executable_file_name
<< " [OPTIONS] <progressive video path or url>\n"
<< "Play adaptive audio/video or progressive video\n\n"
<< "For example:\n"
<< executable_file_name << " " << kExampleAdaptiveAudioPathName
<< "\n"
<< executable_file_name << " " << kExampleAdaptiveVideoPathName
<< "\n"
<< executable_file_name << " " << kExampleAdaptiveAudioPathName
<< " " << kExampleAdaptiveVideoPathName << "\n"
<< executable_file_name << " " << kExampleProgressiveUrl << "\n\n";
}
#if defined(COBALT_MEDIA_SOURCE_2016)
std::string MakeCodecParameter(const std::string& string) { return string; }
void OnInitSegmentReceived(scoped_ptr<MediaTracks> tracks) {
UNREFERENCED_PARAMETER(tracks);
}
#else // defined(COBALT_MEDIA_SOURCE_2016)
std::vector<std::string> MakeCodecParameter(const std::string& string) {
std::vector<std::string> result;
result.push_back(string);
return result;
}
#endif // defined(COBALT_MEDIA_SOURCE_2016)
class InitCobaltHelper {
public:
InitCobaltHelper(int argc, char* argv[]) {
cobalt::InitCobalt(argc, argv, NULL);
}
private:
base::AtExitManager at_exit_manager_;
};
class Application {
public:
Application(int argc, char* argv[])
: init_cobalt_helper_(argc, argv),
media_sandbox_(
argc, argv,
FilePath(FILE_PATH_LITERAL("media_source_sandbox_trace.json"))) {
if (argc > 1) {
FormatGuesstimator guesstimator1(argv[argc - 1]);
FormatGuesstimator guesstimator2(argv[argc - 2]);
if (!guesstimator1.is_valid()) {
SB_LOG(ERROR) << "Invalid path or url: " << argv[argc - 1];
// Fall off to PrintUsage() and terminate.
} else if (guesstimator1.is_progressive()) {
InitializeProgressivePlayback(guesstimator1);
return;
} else if (!guesstimator2.is_adaptive()) {
InitializeAdaptivePlayback(guesstimator1);
return;
} else if (guesstimator1.is_audio() && guesstimator2.is_audio()) {
SB_LOG(ERROR) << "Failed to play because both " << argv[argc - 1]
<< " and " << argv[argc - 2]
<< " are audio streams, check usage for more details.";
// Fall off to PrintUsage() and terminate.
} else if (!guesstimator1.is_audio() && !guesstimator2.is_audio()) {
SB_LOG(ERROR) << "Failed to play because both " << argv[argc - 1]
<< " and " << argv[argc - 2]
<< " are video streams, check usage for more details.";
// Fall off to PrintUsage() and terminate.
} else if (guesstimator1.is_audio()) {
InitializeAdaptivePlayback(guesstimator1, guesstimator2);
return;
} else {
InitializeAdaptivePlayback(guesstimator2, guesstimator1);
return;
}
}
PrintUsage(argv[0]);
SbSystemRequestStop(0);
}
private:
void InitializeAdaptivePlayback(const FormatGuesstimator& guesstimator) {
is_adaptive_playback_ = true;
scoped_ptr<ScopedFile>& file =
guesstimator.is_audio() ? audio_file_ : video_file_;
file.reset(new ScopedFile(guesstimator.adaptive_path().c_str(),
kSbFileOpenOnly | kSbFileRead));
if (!file->IsValid()) {
LOG(ERROR) << "Failed to open file: " << guesstimator.adaptive_path();
SbSystemRequestStop(0);
return;
}
player_helper_.reset(
new WebMediaPlayerHelper(media_sandbox_.GetMediaModule(),
base::Bind(&Application::OnChunkDemuxerOpened,
base::Unretained(this))));
// |chunk_demuxer_| will be set inside OnChunkDemuxerOpened()
// asynchronously during initialization of |player_helper_|. Wait until
// it is set before proceed.
while (!chunk_demuxer_) {
MessageLoop::current()->RunUntilIdle();
}
LOG(INFO) << "Playing " << guesstimator.adaptive_path();
std::string id = guesstimator.is_audio() ? kAudioId : kVideoId;
auto codecs = MakeCodecParameter(guesstimator.codecs());
auto status = chunk_demuxer_->AddId(id, guesstimator.mime(), codecs);
CHECK_EQ(status, ChunkDemuxer::kOk);
#if defined(COBALT_MEDIA_SOURCE_2016)
chunk_demuxer_->SetTracksWatcher(id, base::Bind(OnInitSegmentReceived));
#endif // defined(COBALT_MEDIA_SOURCE_2016)
player_ = player_helper_->player();
media_sandbox_.RegisterFrameCB(
base::Bind(&Application::FrameCB, base::Unretained(this)));
timer_event_id_ =
SbEventSchedule(Application::OnTimer, this, kSbTimeSecond / 10);
}
void InitializeAdaptivePlayback(
const FormatGuesstimator& audio_guesstimator,
const FormatGuesstimator& video_guesstimator) {
is_adaptive_playback_ = true;
audio_file_.reset(new ScopedFile(audio_guesstimator.adaptive_path().c_str(),
kSbFileOpenOnly | kSbFileRead));
video_file_.reset(new ScopedFile(video_guesstimator.adaptive_path().c_str(),
kSbFileOpenOnly | kSbFileRead));
if (!audio_file_->IsValid()) {
LOG(ERROR) << "Failed to open audio file: "
<< audio_guesstimator.adaptive_path();
SbSystemRequestStop(0);
return;
}
if (!video_file_->IsValid()) {
LOG(ERROR) << "Failed to open video file: "
<< video_guesstimator.adaptive_path();
SbSystemRequestStop(0);
return;
}
player_helper_.reset(
new WebMediaPlayerHelper(media_sandbox_.GetMediaModule(),
base::Bind(&Application::OnChunkDemuxerOpened,
base::Unretained(this))));
// |chunk_demuxer_| will be set inside OnChunkDemuxerOpened()
// asynchronously during initialization of |player_helper_|. Wait until
// it is set before proceed.
while (!chunk_demuxer_) {
MessageLoop::current()->RunUntilIdle();
}
LOG(INFO) << "Playing " << audio_guesstimator.adaptive_path() << " and "
<< video_guesstimator.adaptive_path();
auto codecs = MakeCodecParameter(audio_guesstimator.codecs());
auto status =
chunk_demuxer_->AddId(kAudioId, audio_guesstimator.mime(), codecs);
CHECK_EQ(status, ChunkDemuxer::kOk);
codecs = MakeCodecParameter(video_guesstimator.codecs());
status = chunk_demuxer_->AddId(kVideoId, video_guesstimator.mime(), codecs);
CHECK_EQ(status, ChunkDemuxer::kOk);
#if defined(COBALT_MEDIA_SOURCE_2016)
chunk_demuxer_->SetTracksWatcher(kAudioId,
base::Bind(OnInitSegmentReceived));
chunk_demuxer_->SetTracksWatcher(kVideoId,
base::Bind(OnInitSegmentReceived));
#endif // defined(COBALT_MEDIA_SOURCE_2016)
player_ = player_helper_->player();
media_sandbox_.RegisterFrameCB(
base::Bind(&Application::FrameCB, base::Unretained(this)));
timer_event_id_ =
SbEventSchedule(Application::OnTimer, this, kSbTimeSecond / 10);
}
void InitializeProgressivePlayback(const FormatGuesstimator& guesstimator) {
LOG(INFO) << "Playing " << guesstimator.progressive_url();
is_adaptive_playback_ = false;
player_helper_.reset(new WebMediaPlayerHelper(
media_sandbox_.GetMediaModule(), media_sandbox_.GetFetcherFactory(),
guesstimator.progressive_url()));
player_ = player_helper_->player();
media_sandbox_.RegisterFrameCB(
base::Bind(&Application::FrameCB, base::Unretained(this)));
timer_event_id_ =
SbEventSchedule(Application::OnTimer, this, kSbTimeSecond / 10);
}
static void OnTimer(void* context) {
Application* application = static_cast<Application*>(context);
DCHECK(application);
application->Tick();
}
void Tick() {
if (player_helper_->IsPlaybackFinished()) {
media_sandbox_.RegisterFrameCB(MediaSandbox::FrameCB());
LOG(INFO) << "Playback finished.";
SbEventCancel(timer_event_id_);
SbSystemRequestStop(0);
return;
}
if (is_adaptive_playback_ && !eos_appended_) {
if (audio_file_) {
AppendData(kAudioId, audio_file_.get(), &audio_offset_);
}
if (video_file_) {
AppendData(kVideoId, video_file_.get(), &video_offset_);
}
bool audio_eos = !audio_file_ || audio_offset_ == audio_file_->GetSize();
bool video_eos = !video_file_ || video_offset_ == video_file_->GetSize();
if (audio_eos && video_eos) {
#if defined(COBALT_MEDIA_SOURCE_2016)
chunk_demuxer_->MarkEndOfStream(PIPELINE_OK);
#else // defined(COBALT_MEDIA_SOURCE_2016)
chunk_demuxer_->EndOfStream(PIPELINE_OK);
#endif // defined(COBALT_MEDIA_SOURCE_2016)
eos_appended_ = true;
}
}
MessageLoop::current()->RunUntilIdle();
timer_event_id_ =
SbEventSchedule(Application::OnTimer, this, kSbTimeSecond / 10);
}
void OnChunkDemuxerOpened(ChunkDemuxer* chunk_demuxer) {
CHECK(chunk_demuxer);
CHECK(!chunk_demuxer_);
chunk_demuxer_ = chunk_demuxer;
}
void AppendData(const std::string& id, ScopedFile* file, int64* offset) {
const float kLowWaterMarkInSeconds = 5.f;
const int64 kMaxBytesToAppend = 1024 * 1024;
char buffer[kMaxBytesToAppend];
while (*offset < file->GetSize()) {
Ranges<TimeDelta> ranges = chunk_demuxer_->GetBufferedRanges(id);
float end_of_buffer =
ranges.size() == 0 ? 0.f : ranges.end(ranges.size() - 1).InSecondsF();
if (end_of_buffer - player_->GetCurrentTime() > kLowWaterMarkInSeconds) {
break;
}
int64 bytes_to_append =
std::min(kMaxBytesToAppend, file->GetSize() - *offset);
file->Read(buffer, bytes_to_append);
#if defined(COBALT_MEDIA_SOURCE_2016)
base::TimeDelta timestamp_offset;
chunk_demuxer_->AppendData(id, reinterpret_cast<uint8*>(buffer),
bytes_to_append, base::TimeDelta(),
media::kInfiniteDuration, &timestamp_offset);
#else // defined(COBALT_MEDIA_SOURCE_2016)
chunk_demuxer_->AppendData(id, reinterpret_cast<uint8*>(buffer),
bytes_to_append);
#endif // defined(COBALT_MEDIA_SOURCE_2016)
*offset += bytes_to_append;
}
}
scoped_refptr<Image> FrameCB(const base::TimeDelta& time) {
UNREFERENCED_PARAMETER(time);
#if SB_HAS(GRAPHICS)
SbDecodeTarget decode_target = player_helper_->GetCurrentDecodeTarget();
if (SbDecodeTargetIsValid(decode_target)) {
return media_sandbox_.resource_provider()->CreateImageFromSbDecodeTarget(
decode_target);
}
#if !defined(COBALT_MEDIA_SOURCE_2016)
scoped_refptr<VideoFrame> frame = player_helper_->GetCurrentFrame();
return frame ? reinterpret_cast<Image*>(frame->texture_id()) : NULL;
#else // !defined(COBALT_MEDIA_SOURCE_2016)
return NULL;
#endif // !defined(COBALT_MEDIA_SOURCE_2016)
#else // SB_HAS(GRAPHICS)
return NULL;
#endif // SB_HAS(GRAPHICS)
}
const std::string kAudioId = "audio";
const std::string kVideoId = "video";
bool is_adaptive_playback_;
InitCobaltHelper init_cobalt_helper_;
MediaSandbox media_sandbox_;
scoped_ptr<WebMediaPlayerHelper> player_helper_;
ChunkDemuxer* chunk_demuxer_ = NULL;
WebMediaPlayer* player_ = NULL;
scoped_ptr<ScopedFile> audio_file_;
scoped_ptr<ScopedFile> video_file_;
int64 audio_offset_ = 0;
int64 video_offset_ = 0;
bool eos_appended_ = false;
SbEventId timer_event_id_ = kSbEventIdInvalid;
};
} // namespace
} // namespace sandbox
} // namespace media
} // namespace cobalt
void SbEventHandle(const SbEvent* event) {
using cobalt::media::sandbox::Application;
static Application* s_application;
switch (event->type) {
case kSbEventTypeStart: {
SbEventStartData* data = static_cast<SbEventStartData*>(event->data);
DCHECK(!s_application);
s_application =
new Application(data->argument_count, data->argument_values);
break;
}
case kSbEventTypeStop: {
delete s_application;
s_application = NULL;
break;
}
default: { break; }
}
}