1 // Copyright (c) 2020 Samsung Electronics. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/renderer/media/tizen/elementary_media_stream_source/control_thread/source_impl.h"
12 #include "absl/types/optional.h"
13 #include "content/renderer/media/tizen/elementary_media_stream_source/any_thread/common_types.h"
14 #include "content/renderer/media/tizen/elementary_media_stream_source/any_thread/source_dispatcher.h"
15 #include "content/renderer/media/tizen/elementary_media_stream_source/any_thread/source_dispatcher_client.h"
16 #include "content/renderer/media/tizen/elementary_media_stream_source/control_thread/demuxer.h"
17 #include "content/renderer/media/tizen/elementary_media_stream_source/control_thread/source_client.h"
18 #include "content/renderer/media/tizen/elementary_media_stream_source/worker_thread/source_dispatcher_client.h"
19 #include "media/base/audio_decoder_config.h"
20 #include "media/base/timestamp_constants.h"
21 #include "media/base/video_decoder_config.h"
22 #include "services/elementary_media_stream_source/public/cpp/logger.h"
26 namespace elementary_media_stream_source {
28 namespace control_thread {
30 SourceImpl::SourceImpl(any_thread::SourceParams params)
31 : control_client_(nullptr),
32 dispatcher_(std::move(params.dispatcher)),
33 is_open_delayed_(false),
36 pipeline_mode_(params.pipeline_mode),
37 playback_position_(0),
38 player_state_(PlayerState::kClosed),
39 seek_video_keyframe_timestamp_(media::kNoTimestamp),
40 state_(std::move(params.state)),
41 input_session_id_(kInitialSessionId),
42 output_session_id_(kInitialSessionId) {
43 EMSS_DEBUG() << "Constructing object";
46 SourceImpl::~SourceImpl() {
47 EMSS_DEBUG() << "Destructing object";
50 //////////////////// DemuxerClient ////////////////////
52 void SourceImpl::OnClosedCaptions(const std::vector<uint8_t>& closed_captions) {
56 control_client_->OnClosedCaptions(closed_captions);
59 void SourceImpl::OnPlayerError(BackendError error, const std::string& message) {
60 EMSS_DEBUG() << error << ", message = " << message;
62 const auto new_session_id =
63 IsNextInputSessionIdRequired()
64 ? absl::make_optional(GenerateNextInputSessionId())
67 for (const auto& client_weak_ptr : clients_) {
68 auto client = client_weak_ptr.lock();
73 client->OnPlayerError(new_session_id);
76 OnBufferedRangesChanged();
78 SetPlayerState(PlayerState::kClosed);
81 control_client_->OnPlayerError(error, message);
84 void SourceImpl::OnResumeComplete() {
90 void SourceImpl::RegisterDemuxer(std::shared_ptr<Demuxer> demuxer) {
96 void SourceImpl::Seek(const base::TimeDelta& seek_time) {
97 EMSS_DEBUG() << "seek time: " << seek_time;
99 EMSS_LOG_ASSERT(pipeline_mode_.latency_mode == LatencyMode::kNormal)
100 << "Cannot seek in low latency mode";
102 seek_video_keyframe_timestamp_ = media::kNoTimestamp;
104 SetTracksState(TrackState::kOpen, ChangeReason::kOpen);
106 if (player_state_ != PlayerState::kOpenedPending)
109 OnBufferedRangesChanged();
110 SetPlayerState(PlayerState::kOpened);
113 void SourceImpl::SetIsSeeking(bool is_seeking) {
114 EMSS_DEBUG() << "is_seeking: " << (is_seeking ? "true" : "false");
116 is_seeking_ = is_seeking;
119 void SourceImpl::StartWaitingForSeek(const base::TimeDelta& seek_time) {
120 EMSS_DEBUG() << "seek time: " << seek_time;
122 EMSS_LOG_ASSERT(pipeline_mode_.latency_mode == LatencyMode::kNormal)
123 << "Cannot StartWaitingForSeek in low latency mode";
125 SetPlayerState(PlayerState::kOpenedPending);
127 const auto new_session_id =
128 IsNextInputSessionIdRequired()
129 ? absl::make_optional(GenerateNextInputSessionId())
132 for (const auto& client_weak_ptr : clients_) {
133 auto client = client_weak_ptr.lock();
138 client->RequestSeek(seek_time, new_session_id);
142 void SourceImpl::Stop() {
145 is_open_delayed_ = false;
147 SetPlayerState(PlayerState::kDetached);
148 SetTracksState(TrackState::kClosed, ChangeReason::kCloseSourceDetached);
152 for (const auto& client_weak_ptr : clients_) {
153 auto client = client_weak_ptr.lock();
161 if (auto demuxer = demuxer_.lock())
162 demuxer->OnPipelineClosed();
165 control_client_->Stop();
168 //////////////////// Source ////////////////////
170 void SourceImpl::CloseTracksIfAllReachedEOS() {
173 if (std::any_of(clients_.begin(), clients_.end(),
174 [](const std::weak_ptr<SourceClient>& weak_ptr) {
175 if (auto client = weak_ptr.lock())
176 return !client->IsFullyClosable();
183 SetTracksState(TrackState::kClosed, ChangeReason::kCloseTrackEnded);
186 void SourceImpl::OnBufferedRangesChanged() const {
189 if (pipeline_mode_.latency_mode != LatencyMode::kNormal) {
190 // In LL ranges never change and are always (0, +inf) set during setup.
195 if (auto dispatcher = dispatcher_.lock()) {
196 dispatcher->DispatchTask(
197 &worker_thread::SourceDispatcherClient::BufferedRangesChanged);
201 void SourceImpl::OnSeekTrackCompleted(
203 base::TimeDelta first_keyframe_timestamp) {
204 EMSS_DEBUG() << "<" << type
205 << "> completed seek to: " << first_keyframe_timestamp;
208 case TrackType::kAudio:
210 case TrackType::kVideo:
211 seek_video_keyframe_timestamp_ = first_keyframe_timestamp;
214 EMSS_LOG_ASSERT(false) << "Unsupported DemuxerStream type = " << type;
218 if (std::any_of(clients_.begin(), clients_.end(),
219 [](const std::weak_ptr<SourceClient>& weak_ptr) {
220 if (auto client = weak_ptr.lock())
221 return client->IsSeeking();
226 OnBufferedRangesChanged();
228 // Use video keyframe timestamp for seeking if available.
229 // If not, use audio keyframe.
230 auto keyframe_timestamp =
231 seek_video_keyframe_timestamp_ != media::kNoTimestamp
232 ? seek_video_keyframe_timestamp_
233 : first_keyframe_timestamp;
235 OnPlaybackPositionChanged(keyframe_timestamp.InSecondsF(),
238 if (auto demuxer = demuxer_.lock())
239 demuxer->OnDemuxerSeekDone(keyframe_timestamp, output_session_id_);
242 void SourceImpl::OnTrackEnded() {
245 if (player_state_ != PlayerState::kOpened) {
250 if (std::all_of(clients_.begin(), clients_.end(),
251 [](const std::weak_ptr<SourceClient>& weak_ptr) {
252 if (auto client = weak_ptr.lock())
253 return client->IsEnded() && !client->IsOpen();
256 SetPlayerState(PlayerState::kEnded);
260 //////////////////// SourceDispatcherClient ////////////////////
262 void SourceImpl::RegisterClient(std::shared_ptr<SourceClient> client) {
263 EMSS_DEBUG() << "<" << client->Type() << ">";
265 clients_[+client->Type()] = client;
266 client->RegisterSource(std::static_pointer_cast<Source>(shared_from_this()));
269 ////////// blink::WebElementaryMediaStreamSourceControl //////////
271 media::Ranges<base::TimeDelta> SourceImpl::GetBufferedRanges() const {
272 return state_->GetLockedState()->buffered_ranges;
275 void SourceImpl::OnFlushRequested() {
278 for (const auto& client_weak_ptr : clients_) {
279 auto client = client_weak_ptr.lock();
284 client->OnFlushRequested();
287 OnBufferedRangesChanged();
290 void SourceImpl::OnPause() {
293 if (pipeline_mode_.latency_mode == LatencyMode::kNormal) {
298 is_open_delayed_ = false;
300 if (player_state_ != PlayerState::kOpened) {
305 SetPlayerState(PlayerState::kOpenedPending);
306 SetTracksState(TrackState::kClosed, ChangeReason::kCloseSourceClosed);
309 void SourceImpl::OnPlay() {
312 if (pipeline_mode_.latency_mode == LatencyMode::kNormal) {
317 if (player_state_ == PlayerState::kDetached ||
318 player_state_ == PlayerState::kClosed) {
319 EMSS_DEBUG() << "Parent media element is already playing, delaying "
320 << PlayerState::kOpened << " state until EMSS is set up";
321 is_open_delayed_ = true;
325 if (player_state_ != PlayerState::kOpenedPending) {
330 SetTracksState(TrackState::kOpen, ChangeReason::kOpen);
331 SetPlayerState(PlayerState::kOpened);
334 void SourceImpl::OnResume() {
339 if (auto demuxer = demuxer_.lock())
340 demuxer->OnPipelineResuming();
343 void SourceImpl::OnSuspend() {
346 if (auto demuxer = demuxer_.lock())
347 demuxer->OnPipelineSuspended();
349 if (player_state_ != PlayerState::kOpened) {
354 // Note on handling multitasking: explicit reaction to Suspend is required in
355 // order to properly close source & tracks with kCloseSourceSuspended reason.
356 // On the other hand, Resume doesn't need to be handled, because:
357 // - in normal latency, player would be opened by a seek after App is resumed,
358 // - in low latency, player would be opened by the unpausing HTMLMediaElement.
360 SetPlayerState(PlayerState::kOpenedPending);
361 SetTracksState(TrackState::kClosed, ChangeReason::kCloseSourceSuspended);
364 void SourceImpl::UpdatePlaybackPosition(double playback_position,
365 uint32_t session_id) {
366 EMSS_VERBOSE() << "Reported playback position: " << playback_position;
369 EMSS_VERBOSE() << "Resuming. Ignoring playback_position update";
374 EMSS_VERBOSE() << "Processing seek. Ignoring playback_position update";
378 if (session_id != output_session_id_) {
379 EMSS_VERBOSE() << "Dropping time update = " << playback_position
380 << "s because session id changed, update id = " << session_id
381 << ", current = " << output_session_id_;
385 if (playback_position_ != playback_position)
386 OnPlaybackPositionChanged(playback_position, session_id);
389 void SourceImpl::RegisterClient(
390 WebElementaryMediaStreamSourceControlClient* control_client) {
393 control_client_ = control_client;
396 void SourceImpl::SetDuration(double duration) {
400 EMSS_LOG(ERROR) << "Cannot set negative duration.";
404 if (pipeline_mode_.latency_mode != LatencyMode::kNormal) {
405 EMSS_LOG_ASSERT(duration == std::numeric_limits<double>::infinity())
406 << "Cannot set duration in low latency mode to other value "
410 if (auto demuxer = demuxer_.lock()) {
411 demuxer->SetDuration(
412 base::Milliseconds(base::Seconds(duration).InMillisecondsF()));
415 state_->GetLockedState()->duration_ = duration;
418 CallbackResult SourceImpl::SetSourceClosed() {
421 SetPlayerState(PlayerState::kClosed);
423 SetTracksState(TrackState::kClosed, ChangeReason::kCloseSourceClosed);
427 if (auto demuxer = demuxer_.lock())
428 demuxer->OnPipelineClosed();
433 CallbackResult SourceImpl::SetSourceOpened() {
436 NotifyDemuxerInitialized();
438 SetPlayerState(PlayerState::kOpenedPending);
440 auto result = CallbackResult::kSuccess;
442 const auto is_open_delayed = is_open_delayed_;
443 if (is_open_delayed) {
444 EMSS_DEBUG() << "Immediately entering delayed " << PlayerState::kOpened
446 is_open_delayed_ = false;
449 // In LowLatency track will open later when pipeline starts playing, so
450 // continue with OnOpenedDone immediately, unless `is_open_delayed` is set
451 // which means pipeline *is* playing already (i.e. HTMLMediaElement wants to
453 if (pipeline_mode_.latency_mode == LatencyMode::kNormal || is_open_delayed) {
454 // * In NormalLatency packets can be appended as soon as track is
455 // configured, so open tracks before continuing with OnOpenedDone.
456 // * It's possible pipeline is already in playing state, so just open EMSS
457 // in LowLatency if `is_open_delayed`.
458 result = SetTracksState(TrackState::kOpen, ChangeReason::kOpen);
461 if (result != CallbackResult::kSuccess) {
462 SetPlayerState(PlayerState::kClosed);
463 } else if (pipeline_mode_.latency_mode == LatencyMode::kNormal ||
465 // In low latency source will open upon Play (if `is_open_delayed` play
466 // was called already).
467 SetPlayerState(PlayerState::kOpened);
473 //////////////////// private ////////////////////
475 uint32_t SourceImpl::GenerateNextInputSessionId() {
477 EMSS_DEBUG() << "New input session id = " << input_session_id_;
478 return input_session_id_;
481 bool SourceImpl::IsNextInputSessionIdRequired() const {
484 // If tracks are not fully closed, we do require a new session id. Otherwise
485 // current session id is new enough (it was set when tracks were closing and
486 // it should not be changed again).
487 return std::all_of(clients_.begin(), clients_.end(),
488 [](const std::weak_ptr<SourceClient>& weak_ptr) {
489 if (auto client = weak_ptr.lock())
490 return !client->IsFullyClosed();
495 void SourceImpl::NotifyDemuxerInitialized() {
498 auto audio_config = ([&]() -> MaybeAudioConfig {
499 if (auto client = clients_[+TrackType::kAudio].lock()) {
500 return client->GetAudioConfig();
505 auto video_config = ([&]() -> MaybeVideoConfig {
506 if (auto client = clients_[+TrackType::kVideo].lock()) {
507 return client->GetVideoConfig();
512 if (auto dispatcher = dispatcher_.lock()) {
514 &any_thread::SourceDispatcherClient::OnInitialConfigReady,
515 std::move(audio_config), std::move(video_config));
518 if (auto demuxer = demuxer_.lock()) {
519 if (pipeline_mode_.latency_mode != LatencyMode::kNormal) {
520 SetDuration(std::numeric_limits<double>::infinity());
521 // In normal latency ranges inform backend player about currently buffered
522 // timestamp range that was buffered by the backend and is playable. This
523 // concept doesn't apply in live streaming (i.e. low latency mode): source
524 // is considered to have infinite duration and infinite amount of
525 // _unbuffered_ packets available at any time. Using actual track ranges
526 // (where most recent pts is close to playback position) confuses
527 // buffering mechanism, causing HTMLMediaElement ready state to jump back
528 // and forth between have metadata and have enough data states, which may
529 // result in unexpected behavior (e.g. not resolving Play() promise, media
530 // element ready state being incorrectly reported, etc).
531 media::Ranges<base::TimeDelta> live_source_ranges;
532 live_source_ranges.Add(base::TimeDelta::FromSeconds(0),
533 base::TimeDelta::Max());
534 state_->GetLockedState()->buffered_ranges = live_source_ranges;
535 demuxer->OnBufferedTimeRangesChanged(std::move(live_source_ranges));
538 demuxer->NotifyDemuxerInitialized();
542 void SourceImpl::OnPlaybackPositionChanged(double new_playback_position,
543 uint32_t session_id) {
544 EMSS_VERBOSE() << "New playback position: " << new_playback_position
545 << ", session_id: " << session_id;
546 playback_position_ = new_playback_position;
549 control_client_->OnPlaybackPositionChanged(playback_position_, session_id);
552 void SourceImpl::SetPlayerState(PlayerState state) {
553 EMSS_DEBUG() << state;
555 if (!control_client_) {
556 EMSS_LOG(WARNING) << "Control client not registered";
560 if (player_state_ == state) {
561 EMSS_LOG(INFO) << "Requested to re-enter state: " << state << ". Ignoring.";
565 auto previous_state = player_state_;
566 player_state_ = state;
569 case PlayerState::kClosed:
570 // Can be entered from any state.
571 control_client_->SetPlayerClosed();
573 case PlayerState::kDetached:
574 // Can be entered from any state.
575 control_client_->SetPlayerDetached();
577 case PlayerState::kOpened:
578 EMSS_LOG_ASSERT(previous_state == PlayerState::kOpenedPending)
579 << "Requested illegal state transition from " << previous_state
581 control_client_->SetPlayerOpened();
583 case PlayerState::kOpenedPending:
584 // Can be entered from any state.
585 control_client_->SetPlayerOpenedPending();
587 case PlayerState::kEnded:
588 EMSS_LOG_ASSERT(previous_state == PlayerState::kOpened)
589 << "Requested illegal state transition from " << previous_state
591 control_client_->SetPlayerEnded();
596 CallbackResult SourceImpl::SetTracksState(TrackState track_state,
597 ChangeReason reason) {
598 EMSS_DEBUG() << track_state;
600 if (track_state == TrackState::kOpen) {
601 // Opening tracks always precede opening the source itself. We're opening
602 // a new session for output, so sync session id now.
603 SyncOutputSessionId();
606 CallbackResult result = CallbackResult::kSuccess;
607 const absl::optional<uint32_t> new_session_id =
608 track_state == TrackState::kClosed && IsNextInputSessionIdRequired()
609 ? absl::make_optional(GenerateNextInputSessionId())
612 for (const auto& client_weak_ptr : clients_) {
613 auto client = client_weak_ptr.lock();
618 result = client->ChangeTrackState(track_state, reason, new_session_id);
620 if (result != CallbackResult::kSuccess)
627 void SourceImpl::SyncOutputSessionId() {
628 if (output_session_id_ == input_session_id_) {
633 output_session_id_ = input_session_id_;
634 EMSS_DEBUG() << "New output session id = " << output_session_id_;
636 control_client_->SetOutputSessionId(output_session_id_);
638 if (auto demuxer = demuxer_.lock())
639 demuxer->OnSessionIdChange(output_session_id_);
642 } // namespace control_thread
644 } // namespace elementary_media_stream_source
646 } // namespace content