From 4aa2bec702043dd0f225c045e9cf213a278e65d4 Mon Sep 17 00:00:00 2001 From: "zhishun.zhou" Date: Tue, 12 Mar 2024 15:39:02 +0800 Subject: [PATCH] [M120 Migration][MM] Handle live stream duration and currenttime 1. Because player_get_duration return a 0 duration value for live streams, special handling procedures are needed: For HLS stream, use player_get_adaptive_streaming_info; For Dash stream, use player_get_dash_info. 2. For live stream cases, in PlaybackCompleteCb, emit a ended event to media element to mark live playback completed. Patch from: https://review.tizen.org/gerrit/#/c/293843/ Change-Id: Ib307ddf121b974785cf1dc9923d2f741cd715b1f Signed-off-by: xiaofang Signed-off-by: zhishun.zhou --- media/base/pipeline.h | 1 + media/base/pipeline_impl.cc | 12 +++ media/base/pipeline_impl.h | 1 + media/base/renderer_client.h | 2 + media/mojo/clients/mojo_renderer.cc | 5 + media/mojo/clients/mojo_renderer.h | 1 + media/mojo/mojom/renderer.mojom | 3 + media/mojo/services/mojo_renderer_service.cc | 5 + media/mojo/services/mojo_renderer_service.h | 1 + .../blink/public/platform/web_media_player.h | 1 + .../public/platform/web_media_player_client.h | 1 + .../renderer/core/html/media/html_media_element.cc | 11 +++ .../renderer/core/html/media/html_media_element.h | 4 + .../platform/media/web_media_player_impl.cc | 9 ++ .../platform/media/web_media_player_impl.h | 1 + .../content/browser/media/tizen_renderer_impl.cc | 8 ++ .../content/browser/media/tizen_renderer_impl.h | 1 + .../media/filters/media_player_bridge_capi.cc | 7 +- .../media/filters/media_player_bridge_capi.h | 1 + .../media/filters/media_player_bridge_capi_tv.cc | 109 +++++++++++++++++++-- .../media/filters/media_player_bridge_capi_tv.h | 8 +- .../media/filters/media_player_tizen_client.h | 1 + 22 files changed, 185 insertions(+), 8 deletions(-) diff --git a/media/base/pipeline.h b/media/base/pipeline.h index aba0a73..62f5dae1 100644 --- a/media/base/pipeline.h +++ b/media/base/pipeline.h @@ -94,6 +94,7 @@ class MEDIA_EXPORT Pipeline { virtual void OnSeekableTimeChange(base::TimeDelta min_time, base::TimeDelta max_time, bool is_live) = 0; + virtual void OnLivePlaybackComplete() = 0; #endif }; diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc index 747ff15..e707a0d 100644 --- a/media/base/pipeline_impl.cc +++ b/media/base/pipeline_impl.cc @@ -199,6 +199,7 @@ class PipelineImpl::RendererWrapper final : public DemuxerHost, void OnSeekableTimeChange(base::TimeDelta min_time, base::TimeDelta max_time, bool is_live) final; + void OnLivePlaybackComplete() final; #endif // Common handlers for notifications from renderers and demuxer. @@ -1141,6 +1142,12 @@ void PipelineImpl::RendererWrapper::OnSeekableTimeChange( FROM_HERE, base::BindOnce(&PipelineImpl::OnSeekableTimeChange, weak_pipeline_, min_time, max_time, is_live)); } + +void PipelineImpl::RendererWrapper::OnLivePlaybackComplete() { + main_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&PipelineImpl::OnLivePlaybackComplete, weak_pipeline_)); +} #endif void PipelineImpl::RendererWrapper::OnPipelineError(PipelineStatus error) { @@ -2043,6 +2050,11 @@ void PipelineImpl::OnSeekableTimeChange(base::TimeDelta min_time, DVLOG(3) << __func__; client_->OnSeekableTimeChange(min_time, max_time, is_live); } + +void PipelineImpl::OnLivePlaybackComplete() { + LOG(INFO) << "(" << static_cast(this) << ") " << __func__; + client_->OnLivePlaybackComplete(); +} #endif void PipelineImpl::OnSeekDone(bool is_suspended) { diff --git a/media/base/pipeline_impl.h b/media/base/pipeline_impl.h index 93a56b6..d49ba75 100644 --- a/media/base/pipeline_impl.h +++ b/media/base/pipeline_impl.h @@ -196,6 +196,7 @@ class MEDIA_EXPORT PipelineImpl : public Pipeline { void OnSeekableTimeChange(base::TimeDelta min_time, base::TimeDelta max_time, bool is_live); + void OnLivePlaybackComplete(); #endif // Task completion callbacks from RendererWrapper. diff --git a/media/base/renderer_client.h b/media/base/renderer_client.h index 4d6be27..f53f7e6 100644 --- a/media/base/renderer_client.h +++ b/media/base/renderer_client.h @@ -75,6 +75,8 @@ class MEDIA_EXPORT RendererClient { virtual void OnRequestSuspend(bool resource_conflicted) {} virtual void OnRequestSeek(base::TimeDelta time) {} + + virtual void OnLivePlaybackComplete() {} #endif }; diff --git a/media/mojo/clients/mojo_renderer.cc b/media/mojo/clients/mojo_renderer.cc index 7fec3cc..da89779 100644 --- a/media/mojo/clients/mojo_renderer.cc +++ b/media/mojo/clients/mojo_renderer.cc @@ -471,6 +471,11 @@ void MojoRenderer::OnSeekCompleted() { std::move(seek_cb_).Run(); } + +void MojoRenderer::OnLivePlaybackComplete() { + DVLOG(1) << __func__; + client_->OnLivePlaybackComplete(); +} #endif void MojoRenderer::OnCdmAttached(bool success) { diff --git a/media/mojo/clients/mojo_renderer.h b/media/mojo/clients/mojo_renderer.h index ba357a9..bb3ca96 100644 --- a/media/mojo/clients/mojo_renderer.h +++ b/media/mojo/clients/mojo_renderer.h @@ -102,6 +102,7 @@ class MojoRenderer : public Renderer, public mojom::RendererClient { void OnSeekableTimeChange(base::TimeDelta min_time, base::TimeDelta max_time, bool is_live) override; + void OnLivePlaybackComplete() override; #endif // Binds |remote_renderer_| to the mojo message pipe. Can be called multiple diff --git a/media/mojo/mojom/renderer.mojom b/media/mojo/mojom/renderer.mojom index 204cb3b0b..e1342e6 100644 --- a/media/mojo/mojom/renderer.mojom +++ b/media/mojo/mojom/renderer.mojom @@ -129,4 +129,7 @@ interface RendererClient { OnSeekableTimeChange(mojo_base.mojom.TimeDelta min_time, mojo_base.mojom.TimeDelta max_time, bool is_live); + + [EnableIf=tizen_multimedia] + OnLivePlaybackComplete(); }; diff --git a/media/mojo/services/mojo_renderer_service.cc b/media/mojo/services/mojo_renderer_service.cc index dc315f6..9cf82ee 100644 --- a/media/mojo/services/mojo_renderer_service.cc +++ b/media/mojo/services/mojo_renderer_service.cc @@ -277,6 +277,11 @@ void MojoRendererService::OnSeekableTimeChange(base::TimeDelta min_time, << "is_live " << is_live; client_->OnSeekableTimeChange(min_time, max_time, is_live); } + +void MojoRendererService::OnLivePlaybackComplete() { + DVLOG(2) << __func__; + client_->OnLivePlaybackComplete(); +} #endif void MojoRendererService::OnVideoFrameRateChange(absl::optional fps) { diff --git a/media/mojo/services/mojo_renderer_service.h b/media/mojo/services/mojo_renderer_service.h index 5b6e16b..4088111 100644 --- a/media/mojo/services/mojo_renderer_service.h +++ b/media/mojo/services/mojo_renderer_service.h @@ -115,6 +115,7 @@ class MEDIA_MOJO_EXPORT MojoRendererService final : public mojom::Renderer, void OnSeekableTimeChange(base::TimeDelta min_time, base::TimeDelta max_time, bool is_live) final; + void OnLivePlaybackComplete() final; #endif // Called when the MediaResourceShim is ready to go (has a config, diff --git a/third_party/blink/public/platform/web_media_player.h b/third_party/blink/public/platform/web_media_player.h index 7bc042b..645766f 100644 --- a/third_party/blink/public/platform/web_media_player.h +++ b/third_party/blink/public/platform/web_media_player.h @@ -185,6 +185,7 @@ class WebMediaPlayer { virtual void Suspend() {} virtual void Resume() {} virtual bool SuspendedByPlayer() { return false; } + virtual void OnLivePlaybackComplete() {} #endif // Called when the backing media element and the page it is attached to is diff --git a/third_party/blink/public/platform/web_media_player_client.h b/third_party/blink/public/platform/web_media_player_client.h index 3a24a57..21830e6 100644 --- a/third_party/blink/public/platform/web_media_player_client.h +++ b/third_party/blink/public/platform/web_media_player_client.h @@ -225,6 +225,7 @@ class BLINK_PLATFORM_EXPORT WebMediaPlayerClient { #if defined(TIZEN_MULTIMEDIA) virtual void SuspendPlayer() {} + virtual void OnLivePlaybackComplete() = 0; #endif #if BUILDFLAG(IS_TIZEN_TV) diff --git a/third_party/blink/renderer/core/html/media/html_media_element.cc b/third_party/blink/renderer/core/html/media/html_media_element.cc index 3c081e3..d4eb2eb 100644 --- a/third_party/blink/renderer/core/html/media/html_media_element.cc +++ b/third_party/blink/renderer/core/html/media/html_media_element.cc @@ -503,6 +503,9 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tag_name, is_deactivate_(false), is_translated_url_(false), #endif +#if defined(TIZEN_MULTIMEDIA) + live_playback_complete_(false), +#endif audio_tracks_(MakeGarbageCollected(*this)), video_tracks_(MakeGarbageCollected(*this)), audio_source_node_(nullptr), @@ -3701,6 +3704,10 @@ void HTMLMediaElement::TimeChanged() { } } UpdatePlayState(); + +#if defined(TIZEN_MULTIMEDIA) + live_playback_complete_ = false; +#endif } void HTMLMediaElement::DurationChanged() { @@ -4915,6 +4922,10 @@ void HTMLMediaElement::SuspendPlayer() { SetShouldDelayLoadEvent(false); SetNetworkState(kNetworkIdle); } + +void HTMLMediaElement::OnLivePlaybackComplete() { + live_playback_complete_ = true; +} #endif media::mojom::blink::MediaPlayerHost& diff --git a/third_party/blink/renderer/core/html/media/html_media_element.h b/third_party/blink/renderer/core/html/media/html_media_element.h index 45db0e5..da0d0ab 100644 --- a/third_party/blink/renderer/core/html/media/html_media_element.h +++ b/third_party/blink/renderer/core/html/media/html_media_element.h @@ -389,6 +389,10 @@ class CORE_EXPORT HTMLMediaElement void SetParentalRatingResult(bool is_pass) override; #endif +#if defined(TIZEN_MULTIMEDIA) + void OnLivePlaybackComplete() override; +#endif + bool HasMediaSource() const { return media_source_attachment_.get(); } // Return true if element is paused and won't resume automatically if it diff --git a/third_party/blink/renderer/platform/media/web_media_player_impl.cc b/third_party/blink/renderer/platform/media/web_media_player_impl.cc index 78775f6..e568676 100644 --- a/third_party/blink/renderer/platform/media/web_media_player_impl.cc +++ b/third_party/blink/renderer/platform/media/web_media_player_impl.cc @@ -2584,6 +2584,15 @@ void WebMediaPlayerImpl::OnSeekableTimeChange(base::TimeDelta min_time, max_seekable_time_ = max_time; } +void WebMediaPlayerImpl::OnLivePlaybackComplete() { + LOG(INFO) << __func__; + if (!client_) { + LOG(ERROR) << __func__ << ", client is null"; + return; + } + client_->OnLivePlaybackComplete(); +} + void WebMediaPlayerImpl::OnRequestSuspend(bool resource_conflicted) { if (pipeline_controller_->IsSuspended()) { LOG(INFO) << __func__ << " Already suspended."; diff --git a/third_party/blink/renderer/platform/media/web_media_player_impl.h b/third_party/blink/renderer/platform/media/web_media_player_impl.h index a7041f9..3340d55 100644 --- a/third_party/blink/renderer/platform/media/web_media_player_impl.h +++ b/third_party/blink/renderer/platform/media/web_media_player_impl.h @@ -424,6 +424,7 @@ class PLATFORM_EXPORT WebMediaPlayerImpl void OnSeekableTimeChange(base::TimeDelta min_time, base::TimeDelta max_time, bool is_live) override; + void OnLivePlaybackComplete() override; // Called if a player in the browser process is suspended. void OnRequestSuspend(bool resource_conflicted) override; diff --git a/tizen_src/chromium_impl/content/browser/media/tizen_renderer_impl.cc b/tizen_src/chromium_impl/content/browser/media/tizen_renderer_impl.cc index 7c4cbbe..5d58dd6 100644 --- a/tizen_src/chromium_impl/content/browser/media/tizen_renderer_impl.cc +++ b/tizen_src/chromium_impl/content/browser/media/tizen_renderer_impl.cc @@ -528,6 +528,14 @@ void TizenRendererImpl::NotifyPlaybackState(int state, media_resource_acquired, translated_url, drm_info); } + +void TizenRendererImpl::OnLivePlaybackComplete() { + if (!client_) { + LOG(ERROR) << "client is not exist"; + return; + } + client_->OnLivePlaybackComplete(); +} #endif void TizenRendererImpl::OnSeekableTimeChange(base::TimeDelta min_time, diff --git a/tizen_src/chromium_impl/content/browser/media/tizen_renderer_impl.h b/tizen_src/chromium_impl/content/browser/media/tizen_renderer_impl.h index 2fac96d..5e50009 100644 --- a/tizen_src/chromium_impl/content/browser/media/tizen_renderer_impl.h +++ b/tizen_src/chromium_impl/content/browser/media/tizen_renderer_impl.h @@ -124,6 +124,7 @@ class CONTENT_EXPORT TizenRendererImpl bool* media_resource_acquired = NULL, std::string* translated_url = NULL, std::string* drm_info = NULL) override; + void OnLivePlaybackComplete() override; #endif #if defined(TIZEN_TBM_SUPPORT) diff --git a/tizen_src/chromium_impl/media/filters/media_player_bridge_capi.cc b/tizen_src/chromium_impl/media/filters/media_player_bridge_capi.cc index 837352d..fe43bc2 100644 --- a/tizen_src/chromium_impl/media/filters/media_player_bridge_capi.cc +++ b/tizen_src/chromium_impl/media/filters/media_player_bridge_capi.cc @@ -626,7 +626,7 @@ base::TimeDelta MediaPlayerBridgeCapi::GetCurrentTime() { if (is_end_reached_) { if (playback_rate_ < 0) return base::TimeDelta(); - if (duration_.InSecondsF()) + if (!is_live_stream_ && duration_.InSecondsF()) return duration_; } @@ -654,6 +654,11 @@ void MediaPlayerBridgeCapi::StopCurrentTimeUpdateTimer() { } void MediaPlayerBridgeCapi::OnBufferingUpdateTimerFired() { + /* player_get_streaming_download_progress is not apply for live stream + here no need triggered*/ + if (is_live_stream_) + return; + int start = 0, current = 0; if (player_get_streaming_download_progress(player_, &start, ¤t) == PLAYER_ERROR_NONE) { diff --git a/tizen_src/chromium_impl/media/filters/media_player_bridge_capi.h b/tizen_src/chromium_impl/media/filters/media_player_bridge_capi.h index d1d0bf1..4f408b5 100644 --- a/tizen_src/chromium_impl/media/filters/media_player_bridge_capi.h +++ b/tizen_src/chromium_impl/media/filters/media_player_bridge_capi.h @@ -147,6 +147,7 @@ class MEDIA_EXPORT MediaPlayerBridgeCapi : public MediaPlayerTizen { virtual void UpdateDuration(); GURL url_; + bool is_live_stream_{false}; int player_id_ = 0; int delayed_player_state_; player_h player_ = nullptr; diff --git a/tizen_src/chromium_impl/media/filters/media_player_bridge_capi_tv.cc b/tizen_src/chromium_impl/media/filters/media_player_bridge_capi_tv.cc index 94c898b..b18eaa3 100644 --- a/tizen_src/chromium_impl/media/filters/media_player_bridge_capi_tv.cc +++ b/tizen_src/chromium_impl/media/filters/media_player_bridge_capi_tv.cc @@ -8,6 +8,8 @@ #include "media/base/efl/media_player_util_efl.h" #include "tizen_src/chromium_impl/media/filters/media_player_tizen_client.h" +#include + namespace { const int kSeekableTimeUpdateInterval = 500; } @@ -151,8 +153,11 @@ void MediaPlayerBridgeCapiTV::Pause(bool is_media_related_action) { void MediaPlayerBridgeCapiTV::PlaybackCompleteUpdate() { MediaPlayerBridgeCapi::PlaybackCompleteUpdate(); - if (GetMediaPlayerClient()) + if (GetMediaPlayerClient()) { GetMediaPlayerClient()->NotifyPlaybackState(kPlaybackFinish, player_id_); + if (is_live_stream_) + GetMediaPlayerClient()->OnLivePlaybackComplete(); + } } // namespace media void MediaPlayerBridgeCapiTV::PlayerPrepared() { @@ -191,7 +196,7 @@ void MediaPlayerBridgeCapiTV::OnSeekableTimeUpdateTimerFired() { void MediaPlayerBridgeCapiTV::GetAdaptiveStreamingInfo() { int ret = player_get_adaptive_streaming_info(player_, &is_live_stream_, PLAYER_ADAPTIVE_INFO_IS_LIVE); - if (ret != PLAYER_ERROR_NONE || is_live_stream_) { + if (ret == PLAYER_ERROR_NONE && is_live_stream_) { LOG(INFO) << "(" << static_cast(this) << ") " << __func__ << " A live stream."; } @@ -235,7 +240,7 @@ bool MediaPlayerBridgeCapiTV::GetLiveStreamingDuration(int64_t* min, }; char* live_ptr = live_duration; const auto err = player_get_adaptive_streaming_info( - player_, live_duration, PLAYER_ADAPTIVE_INFO_LIVE_DURATION); + player_, &live_ptr, PLAYER_ADAPTIVE_INFO_LIVE_DURATION); if (err != PLAYER_ERROR_NONE) { LOG(ERROR) << "(" << static_cast(this) << ") " << __func__ << " |player_get_adaptive_streaming_info| failed " << err; @@ -284,6 +289,67 @@ bool MediaPlayerBridgeCapiTV::GetLiveStreamingDuration(int64_t* min, return true; } +void MediaPlayerBridgeCapiTV::ParseDashInfo() { + std::string dash_info = GetDashInfo(); + Json::CharReaderBuilder builder; + Json::CharReader* reader(builder.newCharReader()); + Json::Value value; + + if (reader->parse(dash_info.c_str(), dash_info.c_str() + dash_info.length(), + &value, nullptr)) { + mrs_url_ = value["mrsUrl"].asString(); + std::string periodId = value["periodId"].asString(); + if (periodId != "") + content_id_ = clean_url_ + "#period=" + periodId; + } + LOG(INFO) << "mrsUrl: " << mrs_url_ << ",contentId: " << content_id_; +} + +std::string MediaPlayerBridgeCapiTV::GetDashInfo() { + char* dash_info = nullptr; + // dash_info format: + // {"type":0,"minBufferTime":4000,"mrsUrl":"","periodId":"first"} + const int ret = player_get_dash_info(player_, &dash_info); + if (ret != PLAYER_ERROR_NONE) { + if (dash_info) + free(dash_info); + LOG(ERROR) << "Fail to call player_get_dash_info,ret:" << ret; + return ""; + } + + if (!dash_info) { + LOG(ERROR) << "empty dash_info."; + return ""; + } + + const std::string info(dash_info); + free(dash_info); + LOG(INFO) << "dash info str: " << info; + + return info; +} + +bool MediaPlayerBridgeCapiTV::GetDashLiveDuration(int64_t* duration) { + DCHECK(duration); + std::string dash_info = GetDashInfo(); + Json::CharReaderBuilder builder; + Json::CharReader* reader(builder.newCharReader()); + Json::Value value; + int64_t out_duration = 0; + + if (reader->parse(dash_info.c_str(), dash_info.c_str() + dash_info.length(), + &value, nullptr)) { + out_duration = value["mediaPresentationDuration"].asInt(); + } + + // According to dash spec, if duration == -1, set max time as duration. + if (out_duration == -1) + out_duration = base::TimeDelta::Max().InMilliseconds(); + *duration = out_duration; + + return true; +} + void MediaPlayerBridgeCapiTV::AppendUrlHighBitRate(const std::string& url) { // don't use url.append("|STARTBITRATE=HIGHEST") to get hbbtv url // "|" will be replaced with "%7C" @@ -365,7 +431,38 @@ void MediaPlayerBridgeCapiTV::UpdateDuration() { LOG(INFO) << "HBBTV preload not finished, no need update duration. "; return; } - MediaPlayerBridgeCapi::UpdateDuration(); + if (is_live_stream_) { + int64_t duration = 0; + if (stream_type_ == HLS_STREAM) { + int64_t min = 0; + if (!GetLiveStreamingDuration(&min, &duration)) { + LOG(ERROR) << "Fail to get duration for hls live stream."; + return; + } + } else if (stream_type_ == DASH_STREAM) { + if (!GetDashLiveDuration(&duration)) { + if (duration_ != base::TimeDelta::Max()) { + duration_ = base::TimeDelta::Max(); + OnDurationChange(player_id_, duration_.InSecondsF()); + } + return; + } + } else { + LOG(ERROR) << "Unknown live stream type : " << stream_type_; + } + + if (duration == 0) { + LOG(WARNING) << "live stream type : convert duration to max"; + duration_ = base::TimeDelta::Max(); + OnDurationChange(player_id_, duration_.InSecondsF()); + } else if (duration_.InSecondsF() != + ConvertMilliSecondsToSeconds(duration)) { + duration_ = base::Milliseconds(duration); + OnDurationChange(player_id_, duration_.InSecondsF()); + } + } else { // non-live stream sequence + MediaPlayerBridgeCapi::UpdateDuration(); + } } void MediaPlayerBridgeCapiTV::UpdateMediaType() { @@ -424,8 +521,8 @@ void MediaPlayerBridgeCapiTV::PlayerPreloaded() { is_preloaded_ = true; is_live_stream_ = CheckLiveStreaming(); - // if (stream_type_ == DASH_STREAM) - // ParseDashInfo(); + if (stream_type_ == DASH_STREAM) + ParseDashInfo(); if (is_live_stream_ && stream_type_ != OTHER_STREAM) { // UpdateSeekableTime(); // StartSeekableTimeUpdateTimer(); diff --git a/tizen_src/chromium_impl/media/filters/media_player_bridge_capi_tv.h b/tizen_src/chromium_impl/media/filters/media_player_bridge_capi_tv.h index 228deca..702a098 100644 --- a/tizen_src/chromium_impl/media/filters/media_player_bridge_capi_tv.h +++ b/tizen_src/chromium_impl/media/filters/media_player_bridge_capi_tv.h @@ -67,12 +67,18 @@ class MEDIA_EXPORT MediaPlayerBridgeCapiTV : public MediaPlayerBridgeCapi { void OnSeekableTimeUpdateTimerFired(); void GetAdaptiveStreamingInfo(); void UpdateSeekableTime(); + void ParseDashInfo(); bool GetLiveStreamingDuration(int64_t* min, int64_t* max); + bool GetDashLiveDuration(int64_t* duration); + std::string GetDashInfo(); StreamType stream_type_{OTHER_STREAM}; std::string hbbtv_url_{""}; // url_ + HIGHBITRATE(if mpd) std::string mime_type_ = ""; - bool is_live_stream_ = false; + std::string clean_url_{""}; // Original url without suffix t/period + std::string mrs_url_{""}; + std::string content_id_{""}; // clean_url + period,only report to APP + bool parental_rating_pass_{false}; base::TimeDelta min_seekable_time_{base::TimeDelta()}; diff --git a/tizen_src/chromium_impl/media/filters/media_player_tizen_client.h b/tizen_src/chromium_impl/media/filters/media_player_tizen_client.h index 95078d0..9b21291 100644 --- a/tizen_src/chromium_impl/media/filters/media_player_tizen_client.h +++ b/tizen_src/chromium_impl/media/filters/media_player_tizen_client.h @@ -63,6 +63,7 @@ class MEDIA_EXPORT MediaPlayerTizenClient { bool* media_resource_acquired = NULL, std::string* translated_url = NULL, std::string* drm_info = NULL) = 0; + virtual void OnLivePlaybackComplete() = 0; virtual content::WebContentsDelegate* GetWebContentsDelegate() const = 0; #endif }; -- 2.7.4