Skip to content

Commit

Permalink
Support live streams (ie big timestamps), replace float with double (y…
Browse files Browse the repository at this point in the history
…outube#1910)

b/309222250
Live video streams with epoch timestamps was not possible to play. Huge
timestamps values was truncated because use of data type float with
limited resolution.

Change-Id: I7bed973b56e58bea5cd4740ce1778f32dfe617e2

**BACKGROUND**
It is problem to play some live video streams in Cobalt with a
javascript player. It turned out to be related to timestamp values.
Normally at play of a video the timestamp starts at zero and grow. For
live streams they don't. As such a video is continuously created. Then
the timestamp can be the current clock, i.e. UTC time. In seconds these
values will be quite big and in microseconds huge. Cobalt cannot handle
these huge values and therefor cannot play such video streams.

**DEVELOPMENT**
I started to investigate the call path for the Seek function. The Seek
value that comes to a starboard integration is not the same as higher up
in function call path. I found out that the function
ConvertSecondsToTimestamp will disturb the value. It uses a float type
for micro seconds. The float type is only 24 bits. I rewrite it.
In javascript "mediaElement.currentTime = 0.0;" will create a call to
currentTimeAttributeSetter in v8c_html_media_element.cc (file created
during build) and that calls set_current_time in the HTMLMediaElement
class.

Call flow for seek:
In javascript assign currentTime a value.
currentTimeAttributeSetter // v8c_html_media_element.cc at build time
HTMLMediaElement::set_current_time()
HTMLMediaElement::Seek()
WebMediaPlayerImpl::Seek()
WebMediaPlayerImpl::OnPipelineSeek()
WebMediaPlayerImpl::Seek()
SbPlayerPipeline::Seek()
SbPlayerBridge::PrepareForSeek()
SbPlayerBridge::Seek()
SbPlayerPrivate::Seek()
PlayerWorker::Seek()
PlayerWorker::DoSeek()
PlayerWorkerHandler::Seek() // third_party/starboard integration

I discover that timestamp is saved in different data types. In some
places as seconds in a float. The floating-point data type "float"
doesn't have enough resolution to handle timestamps. For example date of
today will be approx. 1,695,316,864. A float mantissa is only 24 bits.
In javascript all numbers are 64 bit float (52 bits mantissa, 11 bits
exponent, one sign bit), max 9007199254740991 and min -9007199254740991.
Let look at a today time in microseconds: 1,695,392,081,110,344 and
compare with 9,007,199,254,740,991. It will fit. In C/C++ data type
"double" is 64 bit floating-point.

Fortunately, I didn't have to change the glue file between javascript
and C++ (v8c_html_media_element.cc). It is already for double. One
problem was that the called function in cobalt had a float argument.

I ended up with changing float to double in a lot of places.

While convert from floating-point value to an integer value (e.g. double
to int64_t). The value needs to be rounded, not truncated. This is done
in function ConvertSecondsToTimestamp. It might be nicer to use the
TimeDelta::FromDouble function. However this function will truncate the
value and it is a unittest for it. I don't see the use of such function.
Well, I didn't change it. It would have effects a lot.

**TEST**
I have built (debug/devel/gold linux-x64x11) and run unittest (devel).
Got 3 failed unittest, nplb about ipv6. I assume this is not caused by
me. Executed Cobalt (devel and debug) in Ubuntu before and after change.
With a html page running a javascript DASH player. Unfortunately, I have
not found any open test streams on internet with huge timestamps. I have
used streams from two different sources (with huge timestamps) to
validate this change.

**RESULT**
Now it is possible to play live streams that have timestamps as current
time expressed in epoch.
  • Loading branch information
MichaelSweden authored Nov 22, 2023
1 parent 85e1b18 commit ee2f99f
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 89 deletions.
72 changes: 35 additions & 37 deletions cobalt/dom/html_media_element.cc
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ HTMLMediaElement::HTMLMediaElement(Document* document, base::Token tag_name)
ready_state_(WebMediaPlayer::kReadyStateHaveNothing),
ready_state_maximum_(WebMediaPlayer::kReadyStateHaveNothing),
volume_(1.0f),
last_seek_time_(0),
last_seek_time_(0.0),
previous_progress_time_(std::numeric_limits<double>::max()),
duration_(std::numeric_limits<double>::quiet_NaN()),
playing_(false),
Expand All @@ -149,7 +149,7 @@ HTMLMediaElement::HTMLMediaElement(Document* document, base::Token tag_name)
resume_frozen_flag_(false),
seeking_(false),
controls_(false),
last_time_update_event_movie_time_(std::numeric_limits<float>::max()),
last_time_update_event_movie_time_(std::numeric_limits<double>::max()),
processing_media_player_callback_(0),
media_source_url_(std::string(kMediaSourceUrlProtocol) + ':' +
base::GenerateGUID()),
Expand Down Expand Up @@ -222,7 +222,7 @@ scoped_refptr<TimeRanges> HTMLMediaElement::buffered() const {
}

player_->UpdateBufferedTimeRanges(
[&](float start, float end) { buffered->Add(start, end); });
[&](double start, double end) { buffered->Add(start, end); });
return buffered;
}

Expand Down Expand Up @@ -344,27 +344,26 @@ bool HTMLMediaElement::seeking() const {
return seeking_;
}

float HTMLMediaElement::current_time(
double HTMLMediaElement::current_time(
script::ExceptionState* exception_state) const {
if (!player_) {
LOG(INFO) << 0 << " (because player is NULL)";
return 0;
return 0.0;
}

if (seeking_) {
MLOG() << last_seek_time_ << " (seeking)";
return last_seek_time_;
}

float time = player_->GetCurrentTime();
const double time = player_->GetCurrentTime();
MLOG() << time << " (from player)";
return time;
}

void HTMLMediaElement::set_current_time(
float time, script::ExceptionState* exception_state) {
double time, script::ExceptionState* exception_state) {
// 4.8.9.9 Seeking

// 1 - If the media element's readyState is
// WebMediaPlayer::kReadyStateHaveNothing, then raise an INVALID_STATE_ERR
// exception.
Expand All @@ -377,10 +376,9 @@ void HTMLMediaElement::set_current_time(
Seek(time);
}

float HTMLMediaElement::duration() const {
double HTMLMediaElement::duration() const {
MLOG() << duration_;
// TODO: Turn duration into double.
return static_cast<float>(duration_);
return duration_;
}

base::Time HTMLMediaElement::GetStartDate() const {
Expand Down Expand Up @@ -433,7 +431,7 @@ void HTMLMediaElement::set_playback_rate(float rate) {
const scoped_refptr<TimeRanges>& HTMLMediaElement::played() {
MLOG();
if (playing_) {
float time = current_time(NULL);
const double time = current_time(NULL);
if (time > last_seek_time_) {
AddPlayedRange(last_seek_time_, time);
}
Expand All @@ -447,8 +445,8 @@ const scoped_refptr<TimeRanges>& HTMLMediaElement::played() {
}

scoped_refptr<TimeRanges> HTMLMediaElement::seekable() const {
if (player_ && player_->GetMaxTimeSeekable() != 0) {
double max_time_seekable = player_->GetMaxTimeSeekable();
if (player_ && player_->GetMaxTimeSeekable() != 0.0) {
const double max_time_seekable = player_->GetMaxTimeSeekable();
MLOG() << "(0, " << max_time_seekable << ")";
return new TimeRanges(0, max_time_seekable);
}
Expand Down Expand Up @@ -632,7 +630,7 @@ void HTMLMediaElement::DurationChanged(double duration, bool request_seek) {
ScheduleOwnEvent(base::Tokens::durationchange());

if (request_seek) {
Seek(static_cast<float>(duration));
Seek(duration);
}
}

Expand Down Expand Up @@ -775,7 +773,7 @@ void HTMLMediaElement::PrepareForLoad() {

// 2 - Asynchronously await a stable state.
played_time_ranges_ = new TimeRanges;
last_seek_time_ = 0;
last_seek_time_ = 0.0;

ConfigureMediaControls();
}
Expand Down Expand Up @@ -1019,8 +1017,8 @@ void HTMLMediaElement::OnProgressEventTimer() {
return;
}

double time = base::Time::Now().ToDoubleT();
double time_delta = time - previous_progress_time_;
const double time = base::Time::Now().ToDoubleT();
const double time_delta = time - previous_progress_time_;

if (player_->DidLoadingProgress()) {
ScheduleOwnEvent(base::Tokens::progress());
Expand Down Expand Up @@ -1082,7 +1080,7 @@ void HTMLMediaElement::StopPeriodicTimers() {
void HTMLMediaElement::ScheduleTimeupdateEvent(bool periodic_event) {
// Some media engines make multiple "time changed" callbacks at the same time,
// but we only want one event at a given time so filter here
float movie_time = current_time(NULL);
const double movie_time = current_time(NULL);
if (movie_time != last_time_update_event_movie_time_) {
if (!periodic_event && playback_progress_timer_.IsRunning()) {
playback_progress_timer_.Reset();
Expand Down Expand Up @@ -1268,7 +1266,7 @@ void HTMLMediaElement::ChangeNetworkStateFromLoadingToIdle() {
network_state_ = kNetworkIdle;
}

void HTMLMediaElement::Seek(float time) {
void HTMLMediaElement::Seek(double time) {
LOG(INFO) << "Seek to " << time << ".";
// 4.8.9.9 Seeking - continued from set_current_time().

Expand All @@ -1279,7 +1277,7 @@ void HTMLMediaElement::Seek(float time) {

// Get the current time before setting seeking_, last_seek_time_ is returned
// once it is set.
float now = current_time(NULL);
const double now = current_time(NULL);

// 2 - If the element's seeking IDL attribute is true, then another instance
// of this algorithm is already running. Abort that other instance of the
Expand All @@ -1297,7 +1295,7 @@ void HTMLMediaElement::Seek(float time) {

// 6 - If the new playback position is less than the earliest possible
// position, let it be that position instead.
time = std::max(time, 0.f);
time = std::max(time, 0.0);

// 7 - If the (possibly now changed) new playback position is not in one of
// the ranges given in the seekable attribute, then let it be the position in
Expand Down Expand Up @@ -1329,7 +1327,7 @@ void HTMLMediaElement::Seek(float time) {
seeking_ = false;
return;
}
time = static_cast<float>(seekable_ranges->Nearest(time));
time = seekable_ranges->Nearest(time);

if (playing_) {
if (last_seek_time_ < now) {
Expand Down Expand Up @@ -1361,7 +1359,7 @@ void HTMLMediaElement::FinishSeek() {
ScheduleOwnEvent(base::Tokens::seeked());
}

void HTMLMediaElement::AddPlayedRange(float start, float end) {
void HTMLMediaElement::AddPlayedRange(double start, double end) {
if (!played_time_ranges_) {
played_time_ranges_ = new TimeRanges;
}
Expand Down Expand Up @@ -1411,7 +1409,7 @@ void HTMLMediaElement::UpdatePlayState() {

playback_progress_timer_.Stop();
playing_ = false;
float time = current_time(NULL);
const double time = current_time(NULL);
if (time > last_seek_time_) {
AddPlayedRange(last_seek_time_, time);
}
Expand All @@ -1431,7 +1429,7 @@ bool HTMLMediaElement::PotentiallyPlaying() const {
}

bool HTMLMediaElement::EndedPlayback() const {
float dur = duration();
const double dur = duration();
if (!player_ || std::isnan(dur)) {
return false;
}
Expand All @@ -1448,15 +1446,15 @@ bool HTMLMediaElement::EndedPlayback() const {
// direction of playback is forwards, Either the media element does not have a
// loop attribute specified, or the media element has a current media
// controller.
float now = current_time(NULL);
if (playback_rate_ > 0) {
return dur > 0 && now >= dur && !loop();
const double now = current_time(NULL);
if (playback_rate_ > 0.f) {
return dur > 0.0 && now >= dur && !loop();
}

// or the current playback position is the earliest possible position and the
// direction of playback is backwards
if (playback_rate_ < 0) {
return now <= 0;
if (playback_rate_ < 0.f) {
return now <= 0.0;
}

return false;
Expand Down Expand Up @@ -1557,14 +1555,14 @@ void HTMLMediaElement::TimeChanged(bool eos_played) {
// already posted one at the current movie time.
ScheduleTimeupdateEvent(false);

float now = current_time(NULL);
float dur = duration();
const double now = current_time(NULL);
const double dur = duration();

// When the current playback position reaches the end of the media resource
// when the direction of playback is forwards, then the user agent must follow
// these steps:
eos_played |=
!std::isnan(dur) && (0.0f != dur) && now >= dur && playback_rate_ > 0;
!std::isnan(dur) && (0.0 != dur) && now >= dur && playback_rate_ > 0.f;
if (eos_played) {
LOG(INFO) << "End of stream is played.";
// If the media element has a loop attribute specified and does not have a
Expand All @@ -1573,7 +1571,7 @@ void HTMLMediaElement::TimeChanged(bool eos_played) {
sent_end_event_ = false;
// then seek to the earliest possible position of the media resource and
// abort these steps.
Seek(0);
Seek(0.0);
} else {
// If the media element does not have a current media controller, and the
// media element has still ended playback, and the direction of playback
Expand Down Expand Up @@ -1603,15 +1601,15 @@ void HTMLMediaElement::DurationChanged() {

ScheduleOwnEvent(base::Tokens::durationchange());

double now = current_time(NULL);
const double now = current_time(NULL);
// Reset and update |duration_|.
duration_ = std::numeric_limits<double>::quiet_NaN();
if (player_ && ready_state_ >= WebMediaPlayer::kReadyStateHaveMetadata) {
duration_ = player_->GetDuration();
}

if (now > duration_) {
Seek(static_cast<float>(duration_));
Seek(duration_);
}

EndProcessingMediaPlayerCallback();
Expand Down
12 changes: 6 additions & 6 deletions cobalt/dom/html_media_element.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ class HTMLMediaElement : public HTMLElement,
bool seeking() const;

// Playback state
float current_time(script::ExceptionState* exception_state) const;
void set_current_time(float time, script::ExceptionState* exception_state);
float duration() const;
double current_time(script::ExceptionState* exception_state) const;
void set_current_time(double time, script::ExceptionState* exception_state);
double duration() const;
base::Time GetStartDate() const;
bool paused() const;
bool resume_frozen_flag() const;
Expand Down Expand Up @@ -210,10 +210,10 @@ class HTMLMediaElement : public HTMLElement,
void ChangeNetworkStateFromLoadingToIdle();

// Playback
void Seek(float time);
void Seek(double time);
void FinishSeek();

void AddPlayedRange(float start, float end);
void AddPlayedRange(double start, double end);

void UpdateVolume();
void UpdatePlayState();
Expand Down Expand Up @@ -269,7 +269,7 @@ class HTMLMediaElement : public HTMLElement,
WebMediaPlayer::ReadyState ready_state_maximum_;

float volume_;
float last_seek_time_;
double last_seek_time_;
double previous_progress_time_;

double duration_;
Expand Down
12 changes: 6 additions & 6 deletions cobalt/media/player/web_media_player.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class WebMediaPlayer {
// Return true if the punch through box should be rendered. Return false if
// no punch through box should be rendered.
typedef base::Callback<bool(int x, int y, int width, int height)> SetBoundsCB;
typedef std::function<void(float start, float end)> AddRangeCB;
typedef std::function<void(double start, double end)> AddRangeCB;

enum NetworkState {
kNetworkStateEmpty,
Expand Down Expand Up @@ -109,12 +109,12 @@ class WebMediaPlayer {
// Playback controls.
virtual void Play() = 0;
virtual void Pause() = 0;
virtual void Seek(float seconds) = 0;
virtual void Seek(double seconds) = 0;
virtual void SetRate(float rate) = 0;
virtual void SetVolume(float volume) = 0;
virtual void SetVisible(bool visible) = 0;
virtual void UpdateBufferedTimeRanges(const AddRangeCB& add_range_cb) = 0;
virtual float GetMaxTimeSeekable() const = 0;
virtual double GetMaxTimeSeekable() const = 0;

// Suspend/Resume
virtual void Suspend() = 0;
Expand All @@ -136,11 +136,11 @@ class WebMediaPlayer {
// Getters of playback state.
virtual bool IsPaused() const = 0;
virtual bool IsSeeking() const = 0;
virtual float GetDuration() const = 0;
virtual double GetDuration() const = 0;
#if SB_HAS(PLAYER_WITH_URL)
virtual base::Time GetStartDate() const = 0;
#endif // SB_HAS(PLAYER_WITH_URL)
virtual float GetCurrentTime() const = 0;
virtual double GetCurrentTime() const = 0;
virtual float GetPlaybackRate() const = 0;

// Get rate of loading the resource.
Expand All @@ -152,7 +152,7 @@ class WebMediaPlayer {

virtual bool DidLoadingProgress() const = 0;

virtual float MediaTimeForTimeValue(float timeValue) const = 0;
virtual double MediaTimeForTimeValue(double timeValue) const = 0;

virtual PlayerStatistics GetStatistics() const = 0;

Expand Down
Loading

0 comments on commit ee2f99f

Please sign in to comment.