1 // Copyright 2013 The Chromium Authors. 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/webmediaplayer_impl.h"
12 #include "base/bind.h"
13 #include "base/callback.h"
14 #include "base/command_line.h"
15 #include "base/debug/crash_logging.h"
16 #include "base/debug/trace_event.h"
17 #include "base/message_loop/message_loop_proxy.h"
18 #include "base/metrics/histogram.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/synchronization/waitable_event.h"
21 #include "cc/layers/video_layer.h"
22 #include "content/public/common/content_switches.h"
23 #include "content/renderer/media/buffered_data_source.h"
24 #include "content/renderer/media/crypto/key_systems.h"
25 #include "content/renderer/media/texttrack_impl.h"
26 #include "content/renderer/media/webaudiosourceprovider_impl.h"
27 #include "content/renderer/media/webinbandtexttrack_impl.h"
28 #include "content/renderer/media/webmediaplayer_delegate.h"
29 #include "content/renderer/media/webmediaplayer_params.h"
30 #include "content/renderer/media/webmediaplayer_util.h"
31 #include "content/renderer/media/webmediasource_impl.h"
32 #include "content/renderer/pepper/pepper_webplugin_impl.h"
33 #include "gpu/GLES2/gl2extchromium.h"
34 #include "media/audio/null_audio_sink.h"
35 #include "media/base/bind_to_loop.h"
36 #include "media/base/filter_collection.h"
37 #include "media/base/limits.h"
38 #include "media/base/media_log.h"
39 #include "media/base/media_switches.h"
40 #include "media/base/pipeline.h"
41 #include "media/base/video_frame.h"
42 #include "media/filters/audio_renderer_impl.h"
43 #include "media/filters/chunk_demuxer.h"
44 #include "media/filters/ffmpeg_audio_decoder.h"
45 #include "media/filters/ffmpeg_demuxer.h"
46 #include "media/filters/ffmpeg_video_decoder.h"
47 #include "media/filters/gpu_video_accelerator_factories.h"
48 #include "media/filters/gpu_video_decoder.h"
49 #include "media/filters/opus_audio_decoder.h"
50 #include "media/filters/video_renderer_base.h"
51 #include "media/filters/vpx_video_decoder.h"
52 #include "third_party/WebKit/public/platform/WebMediaSource.h"
53 #include "third_party/WebKit/public/platform/WebRect.h"
54 #include "third_party/WebKit/public/platform/WebSize.h"
55 #include "third_party/WebKit/public/platform/WebString.h"
56 #include "third_party/WebKit/public/platform/WebURL.h"
57 #include "third_party/WebKit/public/web/WebDocument.h"
58 #include "third_party/WebKit/public/web/WebRuntimeFeatures.h"
59 #include "third_party/WebKit/public/web/WebView.h"
60 #include "v8/include/v8.h"
61 #include "webkit/renderer/compositor_bindings/web_layer_impl.h"
63 using WebKit::WebCanvas;
64 using WebKit::WebMediaPlayer;
65 using WebKit::WebRect;
66 using WebKit::WebSize;
67 using WebKit::WebString;
68 using media::PipelineStatus;
72 // Amount of extra memory used by each player instance reported to V8.
73 // It is not exact number -- first, it differs on different platforms,
74 // and second, it is very hard to calculate. Instead, use some arbitrary
75 // value that will cause garbage collection from time to time. We don't want
76 // it to happen on every allocation, but don't want 5k players to sit in memory
77 // either. Looks that chosen constant achieves both goals, at least for audio
78 // objects. (Do not worry about video objects yet, JS programs do not create
79 // thousands of them...)
80 const int kPlayerExtraMemory = 1024 * 1024;
82 // Limits the range of playback rate.
84 // TODO(kylep): Revisit these.
86 // Vista has substantially lower performance than XP or Windows7. If you speed
87 // up a video too much, it can't keep up, and rendering stops updating except on
88 // the time bar. For really high speeds, audio becomes a bottleneck and we just
89 // use up the data we have, which may not achieve the speed requested, but will
92 // A very slow speed, ie 0.00000001x, causes the machine to lock up. (It seems
93 // like a busy loop). It gets unresponsive, although its not completely dead.
95 // Also our timers are not very accurate (especially for ogg), which becomes
96 // evident at low speeds and on Vista. Since other speeds are risky and outside
97 // the norms, we think 1/16x to 16x is a safe and useful range for now.
98 const double kMinRate = 0.0625;
99 const double kMaxRate = 16.0;
101 // Prefix for histograms related to Encrypted Media Extensions.
102 const char* kMediaEme = "Media.EME.";
108 #define COMPILE_ASSERT_MATCHING_ENUM(name) \
109 COMPILE_ASSERT(static_cast<int>(WebMediaPlayer::CORSMode ## name) == \
110 static_cast<int>(BufferedResourceLoader::k ## name), \
112 COMPILE_ASSERT_MATCHING_ENUM(Unspecified);
113 COMPILE_ASSERT_MATCHING_ENUM(Anonymous);
114 COMPILE_ASSERT_MATCHING_ENUM(UseCredentials);
115 #undef COMPILE_ASSERT_MATCHING_ENUM
117 #define BIND_TO_RENDER_LOOP(function) \
118 media::BindToLoop(main_loop_, base::Bind(function, AsWeakPtr()))
120 #define BIND_TO_RENDER_LOOP_1(function, arg1) \
121 media::BindToLoop(main_loop_, base::Bind(function, AsWeakPtr(), arg1))
123 #define BIND_TO_RENDER_LOOP_2(function, arg1, arg2) \
124 media::BindToLoop(main_loop_, base::Bind(function, AsWeakPtr(), arg1, arg2))
126 static void LogMediaSourceError(const scoped_refptr<media::MediaLog>& media_log,
127 const std::string& error) {
128 media_log->AddEvent(media_log->CreateMediaSourceErrorEvent(error));
131 WebMediaPlayerImpl::WebMediaPlayerImpl(
132 WebKit::WebFrame* frame,
133 WebKit::WebMediaPlayerClient* client,
134 base::WeakPtr<WebMediaPlayerDelegate> delegate,
135 const WebMediaPlayerParams& params)
137 network_state_(WebMediaPlayer::NetworkStateEmpty),
138 ready_state_(WebMediaPlayer::ReadyStateHaveNothing),
139 main_loop_(base::MessageLoopProxy::current()),
140 media_loop_(params.message_loop_proxy()),
143 playback_rate_(0.0f),
144 pending_seek_(false),
145 pending_seek_seconds_(0.0f),
148 defer_load_cb_(params.defer_load_cb()),
149 media_log_(params.media_log()),
150 accelerated_compositing_reported_(false),
151 incremented_externally_allocated_memory_(false),
152 gpu_factories_(params.gpu_factories()),
153 is_local_source_(false),
154 supports_save_(true),
156 chunk_demuxer_(NULL),
157 current_frame_painted_(false),
158 frames_dropped_before_paint_(0),
159 pending_repaint_(false),
160 pending_size_change_(false),
161 video_frame_provider_client_(NULL),
162 text_track_index_(0) {
163 media_log_->AddEvent(
164 media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_CREATED));
166 pipeline_.reset(new media::Pipeline(media_loop_, media_log_.get()));
168 // |gpu_factories_| requires that its entry points be called on its
169 // |GetMessageLoop()|. Since |pipeline_| will own decoders created from the
170 // factories, require that their message loops are identical.
171 DCHECK(!gpu_factories_ || (gpu_factories_->GetMessageLoop() == media_loop_));
173 // Let V8 know we started new thread if we did not do it yet.
174 // Made separate task to avoid deletion of player currently being created.
175 // Also, delaying GC until after player starts gets rid of starting lag --
176 // collection happens in parallel with playing.
178 // TODO(enal): remove when we get rid of per-audio-stream thread.
179 main_loop_->PostTask(
181 base::Bind(&WebMediaPlayerImpl::IncrementExternallyAllocatedMemory,
184 // Also we want to be notified of |main_loop_| destruction.
185 base::MessageLoop::current()->AddDestructionObserver(this);
187 if (WebKit::WebRuntimeFeatures::isPrefixedEncryptedMediaEnabled()) {
188 decryptor_.reset(new ProxyDecryptor(
189 #if defined(ENABLE_PEPPER_CDMS)
193 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnKeyAdded),
194 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnKeyError),
195 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnKeyMessage)));
198 // Use the null sink if no sink was provided.
199 audio_source_provider_ = new WebAudioSourceProviderImpl(
200 params.audio_renderer_sink().get()
201 ? params.audio_renderer_sink()
202 : new media::NullAudioSink(media_loop_));
205 WebMediaPlayerImpl::~WebMediaPlayerImpl() {
206 SetVideoFrameProviderClient(NULL);
207 GetClient()->setWebLayer(NULL);
209 DCHECK(main_loop_->BelongsToCurrentThread());
210 media_log_->AddEvent(
211 media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_DESTROYED));
214 delegate_->PlayerGone(this);
218 // Remove destruction observer if we're being destroyed but the main thread is
220 if (base::MessageLoop::current())
221 base::MessageLoop::current()->RemoveDestructionObserver(this);
226 // Helper enum for reporting scheme histograms.
227 enum URLSchemeForHistogram {
233 kChromeExtensionURLScheme,
234 kJavascriptURLScheme,
239 kMaxURLScheme = kFileSystemScheme // Must be equal to highest enum value.
242 URLSchemeForHistogram URLScheme(const GURL& url) {
243 if (!url.has_scheme()) return kMissingURLScheme;
244 if (url.SchemeIs("http")) return kHttpURLScheme;
245 if (url.SchemeIs("https")) return kHttpsURLScheme;
246 if (url.SchemeIs("ftp")) return kFtpURLScheme;
247 if (url.SchemeIs("chrome-extension")) return kChromeExtensionURLScheme;
248 if (url.SchemeIs("javascript")) return kJavascriptURLScheme;
249 if (url.SchemeIs("file")) return kFileURLScheme;
250 if (url.SchemeIs("blob")) return kBlobURLScheme;
251 if (url.SchemeIs("data")) return kDataURLScheme;
252 if (url.SchemeIs("filesystem")) return kFileSystemScheme;
253 return kUnknownURLScheme;
256 } // anonymous namespace
258 void WebMediaPlayerImpl::load(LoadType load_type, const WebKit::WebURL& url,
259 CORSMode cors_mode) {
260 if (!defer_load_cb_.is_null()) {
261 defer_load_cb_.Run(base::Bind(
262 &WebMediaPlayerImpl::DoLoad, AsWeakPtr(), load_type, url, cors_mode));
265 DoLoad(load_type, url, cors_mode);
268 void WebMediaPlayerImpl::DoLoad(LoadType load_type,
269 const WebKit::WebURL& url,
270 CORSMode cors_mode) {
271 DCHECK(main_loop_->BelongsToCurrentThread());
274 UMA_HISTOGRAM_ENUMERATION("Media.URLScheme", URLScheme(gurl), kMaxURLScheme);
276 // Set subresource URL for crash reporting.
277 base::debug::SetCrashKeyValue("subresource_url", gurl.spec());
279 load_type_ = load_type;
281 // Handle any volume/preload changes that occurred before load().
282 setVolume(GetClient()->volume());
283 setPreload(GetClient()->preload());
285 SetNetworkState(WebMediaPlayer::NetworkStateLoading);
286 SetReadyState(WebMediaPlayer::ReadyStateHaveNothing);
287 media_log_->AddEvent(media_log_->CreateLoadEvent(url.spec()));
289 // Media source pipelines can start immediately.
290 if (load_type == LoadTypeMediaSource) {
291 supports_save_ = false;
296 // Otherwise it's a regular request which requires resolving the URL first.
297 data_source_.reset(new BufferedDataSource(
301 base::Bind(&WebMediaPlayerImpl::NotifyDownloading, AsWeakPtr())));
302 data_source_->Initialize(
303 url, static_cast<BufferedResourceLoader::CORSMode>(cors_mode),
305 &WebMediaPlayerImpl::DataSourceInitialized,
308 is_local_source_ = !gurl.SchemeIsHTTPOrHTTPS();
311 void WebMediaPlayerImpl::play() {
312 DCHECK(main_loop_->BelongsToCurrentThread());
315 pipeline_->SetPlaybackRate(playback_rate_);
317 data_source_->MediaIsPlaying();
319 media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PLAY));
322 delegate_->DidPlay(this);
325 void WebMediaPlayerImpl::pause() {
326 DCHECK(main_loop_->BelongsToCurrentThread());
329 pipeline_->SetPlaybackRate(0.0f);
331 data_source_->MediaIsPaused();
332 paused_time_ = pipeline_->GetMediaTime();
334 media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PAUSE));
337 delegate_->DidPause(this);
340 bool WebMediaPlayerImpl::supportsFullscreen() const {
341 DCHECK(main_loop_->BelongsToCurrentThread());
345 bool WebMediaPlayerImpl::supportsSave() const {
346 DCHECK(main_loop_->BelongsToCurrentThread());
347 return supports_save_;
350 void WebMediaPlayerImpl::seek(double seconds) {
351 DCHECK(main_loop_->BelongsToCurrentThread());
353 base::TimeDelta seek_time = ConvertSecondsToTimestamp(seconds);
355 if (starting_ || seeking_) {
356 pending_seek_ = true;
357 pending_seek_seconds_ = seconds;
359 chunk_demuxer_->CancelPendingSeek(seek_time);
363 media_log_->AddEvent(media_log_->CreateSeekEvent(seconds));
365 // Update our paused time.
367 paused_time_ = seek_time;
372 chunk_demuxer_->StartWaitingForSeek(seek_time);
374 // Kick off the asynchronous seek!
377 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineSeek));
380 void WebMediaPlayerImpl::setRate(double rate) {
381 DCHECK(main_loop_->BelongsToCurrentThread());
383 // TODO(kylep): Remove when support for negatives is added. Also, modify the
384 // following checks so rewind uses reasonable values also.
388 // Limit rates to reasonable values by clamping.
392 else if (rate > kMaxRate)
396 playback_rate_ = rate;
398 pipeline_->SetPlaybackRate(rate);
400 data_source_->MediaPlaybackRateChanged(rate);
404 void WebMediaPlayerImpl::setVolume(double volume) {
405 DCHECK(main_loop_->BelongsToCurrentThread());
407 pipeline_->SetVolume(volume);
410 #define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, chromium_name) \
411 COMPILE_ASSERT(static_cast<int>(WebMediaPlayer::webkit_name) == \
412 static_cast<int>(content::chromium_name), \
414 COMPILE_ASSERT_MATCHING_ENUM(PreloadNone, NONE);
415 COMPILE_ASSERT_MATCHING_ENUM(PreloadMetaData, METADATA);
416 COMPILE_ASSERT_MATCHING_ENUM(PreloadAuto, AUTO);
417 #undef COMPILE_ASSERT_MATCHING_ENUM
419 void WebMediaPlayerImpl::setPreload(WebMediaPlayer::Preload preload) {
420 DCHECK(main_loop_->BelongsToCurrentThread());
423 data_source_->SetPreload(static_cast<content::Preload>(preload));
426 bool WebMediaPlayerImpl::hasVideo() const {
427 DCHECK(main_loop_->BelongsToCurrentThread());
429 return pipeline_->HasVideo();
432 bool WebMediaPlayerImpl::hasAudio() const {
433 DCHECK(main_loop_->BelongsToCurrentThread());
435 return pipeline_->HasAudio();
438 WebKit::WebSize WebMediaPlayerImpl::naturalSize() const {
439 DCHECK(main_loop_->BelongsToCurrentThread());
442 pipeline_->GetNaturalVideoSize(&size);
443 return WebKit::WebSize(size);
446 bool WebMediaPlayerImpl::paused() const {
447 DCHECK(main_loop_->BelongsToCurrentThread());
449 return pipeline_->GetPlaybackRate() == 0.0f;
452 bool WebMediaPlayerImpl::seeking() const {
453 DCHECK(main_loop_->BelongsToCurrentThread());
455 if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing)
461 double WebMediaPlayerImpl::duration() const {
462 DCHECK(main_loop_->BelongsToCurrentThread());
464 if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing)
465 return std::numeric_limits<double>::quiet_NaN();
467 return GetPipelineDuration();
470 double WebMediaPlayerImpl::currentTime() const {
471 DCHECK(main_loop_->BelongsToCurrentThread());
472 return (paused_ ? paused_time_ : pipeline_->GetMediaTime()).InSecondsF();
475 WebMediaPlayer::NetworkState WebMediaPlayerImpl::networkState() const {
476 DCHECK(main_loop_->BelongsToCurrentThread());
477 return network_state_;
480 WebMediaPlayer::ReadyState WebMediaPlayerImpl::readyState() const {
481 DCHECK(main_loop_->BelongsToCurrentThread());
485 const WebKit::WebTimeRanges& WebMediaPlayerImpl::buffered() {
486 DCHECK(main_loop_->BelongsToCurrentThread());
487 WebKit::WebTimeRanges web_ranges(
488 ConvertToWebTimeRanges(pipeline_->GetBufferedTimeRanges()));
489 buffered_.swap(web_ranges);
493 double WebMediaPlayerImpl::maxTimeSeekable() const {
494 DCHECK(main_loop_->BelongsToCurrentThread());
496 // If we haven't even gotten to ReadyStateHaveMetadata yet then just
497 // return 0 so that the seekable range is empty.
498 if (ready_state_ < WebMediaPlayer::ReadyStateHaveMetadata)
501 // We don't support seeking in streaming media.
502 if (data_source_ && data_source_->IsStreaming())
507 bool WebMediaPlayerImpl::didLoadingProgress() const {
508 DCHECK(main_loop_->BelongsToCurrentThread());
509 return pipeline_->DidLoadingProgress();
512 void WebMediaPlayerImpl::paint(WebCanvas* canvas,
514 unsigned char alpha) {
515 DCHECK(main_loop_->BelongsToCurrentThread());
517 if (!accelerated_compositing_reported_) {
518 accelerated_compositing_reported_ = true;
519 // Normally paint() is only called in non-accelerated rendering, but there
520 // are exceptions such as webgl where compositing is used in the WebView but
521 // video frames are still rendered to a canvas.
522 UMA_HISTOGRAM_BOOLEAN(
523 "Media.AcceleratedCompositingActive",
524 frame_->view()->isAcceleratedCompositingActive());
527 // Avoid locking and potentially blocking the video rendering thread while
528 // painting in software.
529 scoped_refptr<media::VideoFrame> video_frame;
531 base::AutoLock auto_lock(lock_);
532 DoneWaitingForPaint(true);
533 video_frame = current_frame_;
535 TRACE_EVENT0("media", "WebMediaPlayerImpl:paint");
536 gfx::Rect gfx_rect(rect);
537 skcanvas_video_renderer_.Paint(video_frame.get(), canvas, gfx_rect, alpha);
540 bool WebMediaPlayerImpl::hasSingleSecurityOrigin() const {
542 return data_source_->HasSingleOrigin();
546 bool WebMediaPlayerImpl::didPassCORSAccessCheck() const {
548 return data_source_->DidPassCORSAccessCheck();
552 double WebMediaPlayerImpl::mediaTimeForTimeValue(double timeValue) const {
553 return ConvertSecondsToTimestamp(timeValue).InSecondsF();
556 unsigned WebMediaPlayerImpl::decodedFrameCount() const {
557 DCHECK(main_loop_->BelongsToCurrentThread());
559 media::PipelineStatistics stats = pipeline_->GetStatistics();
560 return stats.video_frames_decoded;
563 unsigned WebMediaPlayerImpl::droppedFrameCount() const {
564 DCHECK(main_loop_->BelongsToCurrentThread());
566 media::PipelineStatistics stats = pipeline_->GetStatistics();
568 base::AutoLock auto_lock(lock_);
569 unsigned frames_dropped =
570 stats.video_frames_dropped + frames_dropped_before_paint_;
571 DCHECK_LE(frames_dropped, stats.video_frames_decoded);
572 return frames_dropped;
575 unsigned WebMediaPlayerImpl::audioDecodedByteCount() const {
576 DCHECK(main_loop_->BelongsToCurrentThread());
578 media::PipelineStatistics stats = pipeline_->GetStatistics();
579 return stats.audio_bytes_decoded;
582 unsigned WebMediaPlayerImpl::videoDecodedByteCount() const {
583 DCHECK(main_loop_->BelongsToCurrentThread());
585 media::PipelineStatistics stats = pipeline_->GetStatistics();
586 return stats.video_bytes_decoded;
589 void WebMediaPlayerImpl::SetVideoFrameProviderClient(
590 cc::VideoFrameProvider::Client* client) {
591 // This is called from both the main renderer thread and the compositor
592 // thread (when the main thread is blocked).
593 if (video_frame_provider_client_)
594 video_frame_provider_client_->StopUsingProvider();
595 video_frame_provider_client_ = client;
598 scoped_refptr<media::VideoFrame> WebMediaPlayerImpl::GetCurrentFrame() {
599 base::AutoLock auto_lock(lock_);
600 DoneWaitingForPaint(true);
601 TRACE_EVENT_ASYNC_BEGIN0(
602 "media", "WebMediaPlayerImpl:compositing", this);
603 return current_frame_;
606 void WebMediaPlayerImpl::PutCurrentFrame(
607 const scoped_refptr<media::VideoFrame>& frame) {
608 if (!accelerated_compositing_reported_) {
609 accelerated_compositing_reported_ = true;
610 DCHECK(frame_->view()->isAcceleratedCompositingActive());
611 UMA_HISTOGRAM_BOOLEAN("Media.AcceleratedCompositingActive", true);
613 TRACE_EVENT_ASYNC_END0("media", "WebMediaPlayerImpl:compositing", this);
616 bool WebMediaPlayerImpl::copyVideoTextureToPlatformTexture(
617 WebKit::WebGraphicsContext3D* web_graphics_context,
618 unsigned int texture,
620 unsigned int internal_format,
622 bool premultiply_alpha,
624 scoped_refptr<media::VideoFrame> video_frame;
626 base::AutoLock auto_lock(lock_);
627 video_frame = current_frame_;
630 TRACE_EVENT0("media", "WebMediaPlayerImpl:copyVideoTextureToPlatformTexture");
634 if (video_frame->format() != media::VideoFrame::NATIVE_TEXTURE)
636 if (video_frame->texture_target() != GL_TEXTURE_2D)
639 // Since this method changes which texture is bound to the TEXTURE_2D target,
640 // ideally it would restore the currently-bound texture before returning.
641 // The cost of getIntegerv is sufficiently high, however, that we want to
642 // avoid it in user builds. As a result assume (below) that |texture| is
643 // bound when this method is called, and only verify this fact when
645 if (DCHECK_IS_ON()) {
646 GLint bound_texture = 0;
647 web_graphics_context->getIntegerv(GL_TEXTURE_BINDING_2D, &bound_texture);
648 DCHECK_EQ(static_cast<GLuint>(bound_texture), texture);
651 scoped_refptr<media::VideoFrame::MailboxHolder> mailbox_holder =
652 video_frame->texture_mailbox();
654 uint32 source_texture = web_graphics_context->createTexture();
656 web_graphics_context->waitSyncPoint(mailbox_holder->sync_point());
657 web_graphics_context->bindTexture(GL_TEXTURE_2D, source_texture);
658 web_graphics_context->consumeTextureCHROMIUM(GL_TEXTURE_2D,
659 mailbox_holder->mailbox().name);
661 // The video is stored in a unmultiplied format, so premultiply
663 web_graphics_context->pixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_CHROMIUM,
665 // Application itself needs to take care of setting the right flip_y
666 // value down to get the expected result.
667 // flip_y==true means to reverse the video orientation while
668 // flip_y==false means to keep the intrinsic orientation.
669 web_graphics_context->pixelStorei(GL_UNPACK_FLIP_Y_CHROMIUM, flip_y);
670 web_graphics_context->copyTextureCHROMIUM(GL_TEXTURE_2D,
676 web_graphics_context->pixelStorei(GL_UNPACK_FLIP_Y_CHROMIUM, false);
677 web_graphics_context->pixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_CHROMIUM,
680 // Restore the state for TEXTURE_2D binding point as mentioned above.
681 web_graphics_context->bindTexture(GL_TEXTURE_2D, texture);
683 web_graphics_context->deleteTexture(source_texture);
685 // The flush() operation is not necessary here. It is kept since the
686 // performance will be better when it is added than not.
687 web_graphics_context->flush();
691 // Helper functions to report media EME related stats to UMA. They follow the
692 // convention of more commonly used macros UMA_HISTOGRAM_ENUMERATION and
693 // UMA_HISTOGRAM_COUNTS. The reason that we cannot use those macros directly is
694 // that UMA_* macros require the names to be constant throughout the process'
696 static void EmeUMAHistogramEnumeration(const WebKit::WebString& key_system,
697 const std::string& method,
699 int boundary_value) {
700 base::LinearHistogram::FactoryGet(
701 kMediaEme + KeySystemNameForUMA(key_system) + "." + method,
702 1, boundary_value, boundary_value + 1,
703 base::Histogram::kUmaTargetedHistogramFlag)->Add(sample);
706 static void EmeUMAHistogramCounts(const WebKit::WebString& key_system,
707 const std::string& method,
709 // Use the same parameters as UMA_HISTOGRAM_COUNTS.
710 base::Histogram::FactoryGet(
711 kMediaEme + KeySystemNameForUMA(key_system) + "." + method,
712 1, 1000000, 50, base::Histogram::kUmaTargetedHistogramFlag)->Add(sample);
715 // Helper enum for reporting generateKeyRequest/addKey histograms.
716 enum MediaKeyException {
719 kKeySystemNotSupported,
721 kMaxMediaKeyException
724 static MediaKeyException MediaKeyExceptionForUMA(
725 WebMediaPlayer::MediaKeyException e) {
727 case WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported:
728 return kKeySystemNotSupported;
729 case WebMediaPlayer::MediaKeyExceptionInvalidPlayerState:
730 return kInvalidPlayerState;
731 case WebMediaPlayer::MediaKeyExceptionNoError:
734 return kUnknownResultId;
738 // Helper for converting |key_system| name and exception |e| to a pair of enum
739 // values from above, for reporting to UMA.
740 static void ReportMediaKeyExceptionToUMA(
741 const std::string& method,
742 const WebString& key_system,
743 WebMediaPlayer::MediaKeyException e) {
744 MediaKeyException result_id = MediaKeyExceptionForUMA(e);
745 DCHECK_NE(result_id, kUnknownResultId) << e;
746 EmeUMAHistogramEnumeration(
747 key_system, method, result_id, kMaxMediaKeyException);
750 WebMediaPlayer::MediaKeyException
751 WebMediaPlayerImpl::generateKeyRequest(const WebString& key_system,
752 const unsigned char* init_data,
753 unsigned init_data_length) {
754 WebMediaPlayer::MediaKeyException e =
755 GenerateKeyRequestInternal(key_system, init_data, init_data_length);
756 ReportMediaKeyExceptionToUMA("generateKeyRequest", key_system, e);
760 WebMediaPlayer::MediaKeyException
761 WebMediaPlayerImpl::GenerateKeyRequestInternal(
762 const WebString& key_system,
763 const unsigned char* init_data,
764 unsigned init_data_length) {
765 DVLOG(1) << "generateKeyRequest: " << key_system.utf8().data() << ": "
766 << std::string(reinterpret_cast<const char*>(init_data),
767 static_cast<size_t>(init_data_length));
769 if (!IsConcreteSupportedKeySystem(key_system))
770 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
772 // We do not support run-time switching between key systems for now.
773 if (current_key_system_.isEmpty()) {
774 if (!decryptor_->InitializeCDM(key_system.utf8(), frame_->document().url()))
775 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
776 current_key_system_ = key_system;
778 else if (key_system != current_key_system_) {
779 return WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;
782 // TODO(xhwang): We assume all streams are from the same container (thus have
783 // the same "type") for now. In the future, the "type" should be passed down
784 // from the application.
785 if (!decryptor_->GenerateKeyRequest(init_data_type_,
786 init_data, init_data_length)) {
787 current_key_system_.reset();
788 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
791 return WebMediaPlayer::MediaKeyExceptionNoError;
794 WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::addKey(
795 const WebString& key_system,
796 const unsigned char* key,
798 const unsigned char* init_data,
799 unsigned init_data_length,
800 const WebString& session_id) {
801 WebMediaPlayer::MediaKeyException e = AddKeyInternal(
802 key_system, key, key_length, init_data, init_data_length, session_id);
803 ReportMediaKeyExceptionToUMA("addKey", key_system, e);
807 WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::AddKeyInternal(
808 const WebString& key_system,
809 const unsigned char* key,
811 const unsigned char* init_data,
812 unsigned init_data_length,
813 const WebString& session_id) {
815 DCHECK_GT(key_length, 0u);
816 DVLOG(1) << "addKey: " << key_system.utf8().data() << ": "
817 << std::string(reinterpret_cast<const char*>(key),
818 static_cast<size_t>(key_length)) << ", "
819 << std::string(reinterpret_cast<const char*>(init_data),
820 static_cast<size_t>(init_data_length))
821 << " [" << session_id.utf8().data() << "]";
824 if (!IsConcreteSupportedKeySystem(key_system))
825 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
827 if (current_key_system_.isEmpty() || key_system != current_key_system_)
828 return WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;
830 decryptor_->AddKey(key, key_length,
831 init_data, init_data_length, session_id.utf8());
832 return WebMediaPlayer::MediaKeyExceptionNoError;
835 WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::cancelKeyRequest(
836 const WebString& key_system,
837 const WebString& session_id) {
838 WebMediaPlayer::MediaKeyException e =
839 CancelKeyRequestInternal(key_system, session_id);
840 ReportMediaKeyExceptionToUMA("cancelKeyRequest", key_system, e);
844 WebMediaPlayer::MediaKeyException
845 WebMediaPlayerImpl::CancelKeyRequestInternal(
846 const WebString& key_system,
847 const WebString& session_id) {
848 if (!IsConcreteSupportedKeySystem(key_system))
849 return WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported;
851 if (current_key_system_.isEmpty() || key_system != current_key_system_)
852 return WebMediaPlayer::MediaKeyExceptionInvalidPlayerState;
854 decryptor_->CancelKeyRequest(session_id.utf8());
855 return WebMediaPlayer::MediaKeyExceptionNoError;
858 void WebMediaPlayerImpl::WillDestroyCurrentMessageLoop() {
862 void WebMediaPlayerImpl::Repaint() {
863 DCHECK(main_loop_->BelongsToCurrentThread());
864 TRACE_EVENT0("media", "WebMediaPlayerImpl:repaint");
866 bool size_changed = false;
868 base::AutoLock auto_lock(lock_);
869 std::swap(pending_size_change_, size_changed);
870 if (pending_repaint_) {
871 TRACE_EVENT_ASYNC_END0(
872 "media", "WebMediaPlayerImpl:repaintPending", this);
873 pending_repaint_ = false;
878 TRACE_EVENT0("media", "WebMediaPlayerImpl:clientSizeChanged");
879 GetClient()->sizeChanged();
882 TRACE_EVENT0("media", "WebMediaPlayerImpl:clientRepaint");
883 GetClient()->repaint();
886 void WebMediaPlayerImpl::OnPipelineSeek(PipelineStatus status) {
887 DCHECK(main_loop_->BelongsToCurrentThread());
891 pending_seek_ = false;
892 seek(pending_seek_seconds_);
896 if (status != media::PIPELINE_OK) {
897 OnPipelineError(status);
901 // Update our paused time.
903 paused_time_ = pipeline_->GetMediaTime();
905 GetClient()->timeChanged();
908 void WebMediaPlayerImpl::OnPipelineEnded() {
909 DCHECK(main_loop_->BelongsToCurrentThread());
910 GetClient()->timeChanged();
913 void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) {
914 DCHECK(main_loop_->BelongsToCurrentThread());
915 DCHECK_NE(error, media::PIPELINE_OK);
917 if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing) {
918 // Any error that occurs before reaching ReadyStateHaveMetadata should
919 // be considered a format error.
920 SetNetworkState(WebMediaPlayer::NetworkStateFormatError);
925 SetNetworkState(PipelineErrorToNetworkState(error));
927 if (error == media::PIPELINE_ERROR_DECRYPT)
928 EmeUMAHistogramCounts(current_key_system_, "DecryptError", 1);
930 // Repaint to trigger UI update.
934 void WebMediaPlayerImpl::OnPipelineBufferingState(
935 media::Pipeline::BufferingState buffering_state) {
936 DVLOG(1) << "OnPipelineBufferingState(" << buffering_state << ")";
938 switch (buffering_state) {
939 case media::Pipeline::kHaveMetadata:
940 SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata);
942 if (hasVideo() && GetClient()->needsWebLayerForVideo()) {
943 DCHECK(!video_weblayer_);
944 video_weblayer_.reset(
945 new webkit::WebLayerImpl(cc::VideoLayer::Create(this)));
946 GetClient()->setWebLayer(video_weblayer_.get());
949 case media::Pipeline::kPrerollCompleted:
950 // Only transition to ReadyStateHaveEnoughData if we don't have
951 // any pending seeks because the transition can cause Blink to
952 // report that the most recent seek has completed.
954 SetReadyState(WebMediaPlayer::ReadyStateHaveEnoughData);
958 // Repaint to trigger UI update.
962 void WebMediaPlayerImpl::OnDemuxerOpened() {
963 DCHECK(main_loop_->BelongsToCurrentThread());
964 GetClient()->mediaSourceOpened(new WebMediaSourceImpl(
965 chunk_demuxer_, base::Bind(&LogMediaSourceError, media_log_)));
968 void WebMediaPlayerImpl::OnKeyAdded(const std::string& session_id) {
969 DCHECK(main_loop_->BelongsToCurrentThread());
970 EmeUMAHistogramCounts(current_key_system_, "KeyAdded", 1);
971 GetClient()->keyAdded(current_key_system_,
972 WebString::fromUTF8(session_id));
975 void WebMediaPlayerImpl::OnNeedKey(const std::string& type,
976 const std::vector<uint8>& init_data) {
977 DCHECK(main_loop_->BelongsToCurrentThread());
979 // Do not fire NeedKey event if encrypted media is not enabled.
983 UMA_HISTOGRAM_COUNTS(kMediaEme + std::string("NeedKey"), 1);
985 DCHECK(init_data_type_.empty() || type.empty() || type == init_data_type_);
986 if (init_data_type_.empty())
987 init_data_type_ = type;
989 const uint8* init_data_ptr = init_data.empty() ? NULL : &init_data[0];
990 GetClient()->keyNeeded(WebString(),
996 scoped_ptr<media::TextTrack>
997 WebMediaPlayerImpl::OnTextTrack(media::TextKind kind,
998 const std::string& label,
999 const std::string& language) {
1000 typedef WebInbandTextTrackImpl::Kind webkind_t;
1001 const webkind_t webkind = static_cast<webkind_t>(kind);
1002 const WebKit::WebString weblabel = WebKit::WebString::fromUTF8(label);
1003 const WebKit::WebString weblanguage = WebKit::WebString::fromUTF8(language);
1005 WebInbandTextTrackImpl* const text_track =
1006 new WebInbandTextTrackImpl(webkind, weblabel, weblanguage,
1007 text_track_index_++);
1009 return scoped_ptr<media::TextTrack>(new TextTrackImpl(GetClient(),
1013 void WebMediaPlayerImpl::OnKeyError(const std::string& session_id,
1014 media::MediaKeys::KeyError error_code,
1016 DCHECK(main_loop_->BelongsToCurrentThread());
1018 EmeUMAHistogramEnumeration(current_key_system_, "KeyError",
1019 error_code, media::MediaKeys::kMaxKeyError);
1021 GetClient()->keyError(
1022 current_key_system_,
1023 WebString::fromUTF8(session_id),
1024 static_cast<WebKit::WebMediaPlayerClient::MediaKeyErrorCode>(error_code),
1028 void WebMediaPlayerImpl::OnKeyMessage(const std::string& session_id,
1029 const std::vector<uint8>& message,
1030 const std::string& default_url) {
1031 DCHECK(main_loop_->BelongsToCurrentThread());
1033 const GURL default_url_gurl(default_url);
1034 DLOG_IF(WARNING, !default_url.empty() && !default_url_gurl.is_valid())
1035 << "Invalid URL in default_url: " << default_url;
1037 GetClient()->keyMessage(current_key_system_,
1038 WebString::fromUTF8(session_id),
1039 message.empty() ? NULL : &message[0],
1044 void WebMediaPlayerImpl::SetOpaque(bool opaque) {
1045 DCHECK(main_loop_->BelongsToCurrentThread());
1047 GetClient()->setOpaque(opaque);
1050 void WebMediaPlayerImpl::DataSourceInitialized(const GURL& gurl, bool success) {
1051 DCHECK(main_loop_->BelongsToCurrentThread());
1054 SetNetworkState(WebMediaPlayer::NetworkStateFormatError);
1062 void WebMediaPlayerImpl::NotifyDownloading(bool is_downloading) {
1063 if (!is_downloading && network_state_ == WebMediaPlayer::NetworkStateLoading)
1064 SetNetworkState(WebMediaPlayer::NetworkStateIdle);
1065 else if (is_downloading && network_state_ == WebMediaPlayer::NetworkStateIdle)
1066 SetNetworkState(WebMediaPlayer::NetworkStateLoading);
1067 media_log_->AddEvent(
1068 media_log_->CreateBooleanEvent(
1069 media::MediaLogEvent::NETWORK_ACTIVITY_SET,
1070 "is_downloading_data", is_downloading));
1073 void WebMediaPlayerImpl::StartPipeline() {
1074 const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
1075 bool increase_preroll_on_underflow = true;
1077 // Keep track if this is a MSE or non-MSE playback.
1078 UMA_HISTOGRAM_BOOLEAN("Media.MSE.Playback",
1079 (load_type_ == LoadTypeMediaSource));
1081 // Figure out which demuxer to use.
1082 if (load_type_ != LoadTypeMediaSource) {
1083 DCHECK(!chunk_demuxer_);
1084 DCHECK(data_source_);
1086 demuxer_.reset(new media::FFmpegDemuxer(
1087 media_loop_, data_source_.get(),
1088 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnNeedKey),
1091 DCHECK(!chunk_demuxer_);
1092 DCHECK(!data_source_);
1094 media::AddTextTrackCB add_text_track_cb;
1096 if (cmd_line->HasSwitch(switches::kEnableInbandTextTracks)) {
1098 base::Bind(&WebMediaPlayerImpl::OnTextTrack, base::Unretained(this));
1101 chunk_demuxer_ = new media::ChunkDemuxer(
1102 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDemuxerOpened),
1103 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnNeedKey),
1105 base::Bind(&LogMediaSourceError, media_log_));
1106 demuxer_.reset(chunk_demuxer_);
1108 #if !defined(OS_CHROMEOS)
1109 // Disable GpuVideoDecoder creation on platforms other than CrOS until
1110 // they support codec config changes.
1111 // TODO(acolwell): Remove this once http://crbug.com/151045 is fixed.
1112 gpu_factories_ = NULL;
1115 // Disable preroll increases on underflow since the web application has no
1116 // way to detect that this is happening and runs the risk of triggering
1117 // unwanted garbage collection if it is to aggressive about appending data.
1118 // TODO(acolwell): Remove this once http://crbug.com/144683 is fixed.
1119 increase_preroll_on_underflow = false;
1122 scoped_ptr<media::FilterCollection> filter_collection(
1123 new media::FilterCollection());
1124 filter_collection->SetDemuxer(demuxer_.get());
1126 // Figure out if EME is enabled.
1127 media::SetDecryptorReadyCB set_decryptor_ready_cb;
1129 set_decryptor_ready_cb = base::Bind(&ProxyDecryptor::SetDecryptorReadyCB,
1130 base::Unretained(decryptor_.get()));
1133 // Create our audio decoders and renderer.
1134 ScopedVector<media::AudioDecoder> audio_decoders;
1135 audio_decoders.push_back(new media::FFmpegAudioDecoder(media_loop_));
1136 if (cmd_line->HasSwitch(switches::kEnableOpusPlayback)) {
1137 audio_decoders.push_back(new media::OpusAudioDecoder(media_loop_));
1140 scoped_ptr<media::AudioRenderer> audio_renderer(
1141 new media::AudioRendererImpl(media_loop_,
1142 audio_source_provider_.get(),
1143 audio_decoders.Pass(),
1144 set_decryptor_ready_cb,
1145 increase_preroll_on_underflow));
1146 filter_collection->SetAudioRenderer(audio_renderer.Pass());
1148 // Create our video decoders and renderer.
1149 ScopedVector<media::VideoDecoder> video_decoders;
1151 if (gpu_factories_.get()) {
1152 video_decoders.push_back(
1153 new media::GpuVideoDecoder(gpu_factories_, media_log_));
1156 // TODO(phajdan.jr): Remove ifdefs when libvpx with vp9 support is released
1157 // (http://crbug.com/174287) .
1158 #if !defined(MEDIA_DISABLE_LIBVPX)
1159 video_decoders.push_back(new media::VpxVideoDecoder(media_loop_));
1160 #endif // !defined(MEDIA_DISABLE_LIBVPX)
1162 video_decoders.push_back(new media::FFmpegVideoDecoder(media_loop_));
1164 scoped_ptr<media::VideoRenderer> video_renderer(
1165 new media::VideoRendererBase(
1167 video_decoders.Pass(),
1168 set_decryptor_ready_cb,
1169 base::Bind(&WebMediaPlayerImpl::FrameReady, base::Unretained(this)),
1170 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::SetOpaque),
1172 filter_collection->SetVideoRenderer(video_renderer.Pass());
1174 // ... and we're ready to go!
1177 filter_collection.Pass(),
1178 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineEnded),
1179 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineError),
1180 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineSeek),
1181 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineBufferingState),
1182 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDurationChange));
1185 void WebMediaPlayerImpl::SetNetworkState(WebMediaPlayer::NetworkState state) {
1186 DCHECK(main_loop_->BelongsToCurrentThread());
1187 DVLOG(1) << "SetNetworkState: " << state;
1188 network_state_ = state;
1189 // Always notify to ensure client has the latest value.
1190 GetClient()->networkStateChanged();
1193 void WebMediaPlayerImpl::SetReadyState(WebMediaPlayer::ReadyState state) {
1194 DCHECK(main_loop_->BelongsToCurrentThread());
1195 DVLOG(1) << "SetReadyState: " << state;
1197 if (state == WebMediaPlayer::ReadyStateHaveEnoughData &&
1199 network_state_ == WebMediaPlayer::NetworkStateLoading)
1200 SetNetworkState(WebMediaPlayer::NetworkStateLoaded);
1202 ready_state_ = state;
1203 // Always notify to ensure client has the latest value.
1204 GetClient()->readyStateChanged();
1207 void WebMediaPlayerImpl::Destroy() {
1208 DCHECK(main_loop_->BelongsToCurrentThread());
1210 // Abort any pending IO so stopping the pipeline doesn't get blocked.
1212 data_source_->Abort();
1213 if (chunk_demuxer_) {
1214 chunk_demuxer_->Shutdown();
1215 chunk_demuxer_ = NULL;
1218 if (gpu_factories_.get()) {
1219 gpu_factories_->Abort();
1220 gpu_factories_ = NULL;
1223 // Make sure to kill the pipeline so there's no more media threads running.
1224 // Note: stopping the pipeline might block for a long time.
1225 base::WaitableEvent waiter(false, false);
1226 pipeline_->Stop(base::Bind(
1227 &base::WaitableEvent::Signal, base::Unretained(&waiter)));
1230 // Let V8 know we are not using extra resources anymore.
1231 if (incremented_externally_allocated_memory_) {
1232 v8::V8::AdjustAmountOfExternalAllocatedMemory(-kPlayerExtraMemory);
1233 incremented_externally_allocated_memory_ = false;
1236 // Release any final references now that everything has stopped.
1239 data_source_.reset();
1242 WebKit::WebMediaPlayerClient* WebMediaPlayerImpl::GetClient() {
1243 DCHECK(main_loop_->BelongsToCurrentThread());
1248 WebKit::WebAudioSourceProvider* WebMediaPlayerImpl::audioSourceProvider() {
1249 return audio_source_provider_.get();
1252 void WebMediaPlayerImpl::IncrementExternallyAllocatedMemory() {
1253 DCHECK(main_loop_->BelongsToCurrentThread());
1254 incremented_externally_allocated_memory_ = true;
1255 v8::V8::AdjustAmountOfExternalAllocatedMemory(kPlayerExtraMemory);
1258 double WebMediaPlayerImpl::GetPipelineDuration() const {
1259 base::TimeDelta duration = pipeline_->GetMediaDuration();
1261 // Return positive infinity if the resource is unbounded.
1262 // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#dom-media-duration
1263 if (duration == media::kInfiniteDuration())
1264 return std::numeric_limits<double>::infinity();
1266 return duration.InSecondsF();
1269 void WebMediaPlayerImpl::OnDurationChange() {
1270 if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing)
1273 GetClient()->durationChanged();
1276 void WebMediaPlayerImpl::FrameReady(
1277 const scoped_refptr<media::VideoFrame>& frame) {
1278 base::AutoLock auto_lock(lock_);
1280 if (current_frame_ &&
1281 current_frame_->natural_size() != frame->natural_size() &&
1282 !pending_size_change_) {
1283 pending_size_change_ = true;
1286 DoneWaitingForPaint(false);
1288 current_frame_ = frame;
1289 current_frame_painted_ = false;
1290 TRACE_EVENT_FLOW_BEGIN0("media", "WebMediaPlayerImpl:waitingForPaint", this);
1292 if (pending_repaint_)
1295 TRACE_EVENT_ASYNC_BEGIN0("media", "WebMediaPlayerImpl:repaintPending", this);
1296 pending_repaint_ = true;
1297 main_loop_->PostTask(FROM_HERE, base::Bind(
1298 &WebMediaPlayerImpl::Repaint, AsWeakPtr()));
1301 void WebMediaPlayerImpl::DoneWaitingForPaint(bool painting_frame) {
1302 lock_.AssertAcquired();
1303 if (!current_frame_ || current_frame_painted_)
1306 TRACE_EVENT_FLOW_END0("media", "WebMediaPlayerImpl:waitingForPaint", this);
1308 if (painting_frame) {
1309 current_frame_painted_ = true;
1313 // The frame wasn't painted, but we aren't waiting for a Repaint() call so
1314 // assume that the frame wasn't painted because the video wasn't visible.
1315 if (!pending_repaint_)
1318 // The |current_frame_| wasn't painted, it is being replaced, and we haven't
1319 // even gotten the chance to request a repaint for it yet. Mark it as dropped.
1320 TRACE_EVENT0("media", "WebMediaPlayerImpl:frameDropped");
1321 DVLOG(1) << "Frame dropped before being painted: "
1322 << current_frame_->GetTimestamp().InSecondsF();
1323 if (frames_dropped_before_paint_ < kuint32max)
1324 frames_dropped_before_paint_++;
1327 } // namespace content