TelemetryProbesReporter.cpp (27155B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "TelemetryProbesReporter.h" 6 7 #include <cmath> 8 9 #include "FrameStatistics.h" 10 #include "MediaCodecsSupport.h" 11 #include "VideoUtils.h" 12 #include "mozilla/EMEUtils.h" 13 #include "mozilla/Logging.h" 14 #include "mozilla/StaticPrefs_media.h" 15 #include "mozilla/glean/DomMediaEmeMetrics.h" 16 #include "mozilla/glean/DomMediaMetrics.h" 17 #include "mozilla/glean/DomMediaPlatformsWmfMetrics.h" 18 #include "nsThreadUtils.h" 19 20 namespace mozilla { 21 22 LazyLogModule gTelemetryProbesReporterLog("TelemetryProbesReporter"); 23 #define LOG(msg, ...) \ 24 MOZ_LOG(gTelemetryProbesReporterLog, LogLevel::Debug, \ 25 ("TelemetryProbesReporter=%p, " msg, this, ##__VA_ARGS__)) 26 27 static const char* ToMutedStr(bool aMuted) { 28 return aMuted ? "muted" : "unmuted"; 29 } 30 31 MediaContent TelemetryProbesReporter::MediaInfoToMediaContent( 32 const MediaInfo& aInfo) { 33 MediaContent content = MediaContent::MEDIA_HAS_NOTHING; 34 if (aInfo.HasAudio()) { 35 content |= MediaContent::MEDIA_HAS_AUDIO; 36 } 37 if (aInfo.HasVideo()) { 38 content |= MediaContent::MEDIA_HAS_VIDEO; 39 if (aInfo.mVideo.GetAsVideoInfo()->mColorDepth > gfx::ColorDepth::COLOR_8) { 40 content |= MediaContent::MEDIA_HAS_COLOR_DEPTH_ABOVE_8; 41 } 42 } 43 return content; 44 } 45 46 TelemetryProbesReporter::TelemetryProbesReporter( 47 TelemetryProbesReporterOwner* aOwner) 48 : mOwner(aOwner) { 49 MOZ_ASSERT(mOwner); 50 } 51 52 void TelemetryProbesReporter::OnPlay(Visibility aVisibility, 53 MediaContent aMediaContent, 54 bool aIsMuted) { 55 LOG("Start time accumulation for total play time"); 56 57 AssertOnMainThreadAndNotShutdown(); 58 MOZ_ASSERT_IF(mMediaContent & MediaContent::MEDIA_HAS_VIDEO, 59 !mTotalVideoPlayTime.IsStarted()); 60 MOZ_ASSERT_IF(mMediaContent & MediaContent::MEDIA_HAS_AUDIO, 61 !mTotalAudioPlayTime.IsStarted()); 62 63 if (aMediaContent & MediaContent::MEDIA_HAS_VIDEO) { 64 mTotalVideoPlayTime.Start(); 65 66 MOZ_ASSERT_IF(mMediaContent & MediaContent::MEDIA_HAS_COLOR_DEPTH_ABOVE_8, 67 !mTotalVideoHDRPlayTime.IsStarted()); 68 if (aMediaContent & MediaContent::MEDIA_HAS_COLOR_DEPTH_ABOVE_8) { 69 mTotalVideoHDRPlayTime.Start(); 70 } 71 } 72 if (aMediaContent & MediaContent::MEDIA_HAS_AUDIO) { 73 mTotalAudioPlayTime.Start(); 74 } 75 76 OnMediaContentChanged(aMediaContent); 77 OnVisibilityChanged(aVisibility); 78 OnMutedChanged(aIsMuted); 79 80 mOwner->DispatchAsyncTestingEvent(u"moztotalplaytimestarted"_ns); 81 82 mIsPlaying = true; 83 } 84 85 void TelemetryProbesReporter::OnPause(Visibility aVisibility) { 86 if (!mIsPlaying) { 87 // Not started 88 LOG("TelemetryProbesReporter::OnPause: not started, early return"); 89 return; 90 } 91 92 LOG("Pause time accumulation for total play time"); 93 94 AssertOnMainThreadAndNotShutdown(); 95 MOZ_ASSERT_IF(mMediaContent & MediaContent::MEDIA_HAS_VIDEO, 96 mTotalVideoPlayTime.IsStarted()); 97 MOZ_ASSERT_IF(mMediaContent & MediaContent::MEDIA_HAS_AUDIO, 98 mTotalAudioPlayTime.IsStarted()); 99 100 if (mMediaContent & MediaContent::MEDIA_HAS_VIDEO) { 101 MOZ_ASSERT_IF(mMediaContent & MediaContent::MEDIA_HAS_COLOR_DEPTH_ABOVE_8, 102 mTotalVideoHDRPlayTime.IsStarted()); 103 104 LOG("Pause video time accumulation for total play time"); 105 if (mInvisibleVideoPlayTime.IsStarted()) { 106 LOG("Pause invisible video time accumulation for total play time"); 107 PauseInvisibleVideoTimeAccumulator(); 108 } 109 mTotalVideoPlayTime.Pause(); 110 mTotalVideoHDRPlayTime.Pause(); 111 } 112 if (mMediaContent & MediaContent::MEDIA_HAS_AUDIO) { 113 LOG("Pause audio time accumulation for total play time"); 114 if (mInaudibleAudioPlayTime.IsStarted()) { 115 LOG("Pause audible audio time accumulation for total play time"); 116 PauseInaudibleAudioTimeAccumulator(); 117 } 118 if (mMutedAudioPlayTime.IsStarted()) { 119 LOG("Pause muted audio time accumulation for total play time"); 120 PauseMutedAudioTimeAccumulator(); 121 } 122 mTotalAudioPlayTime.Pause(); 123 } 124 125 mOwner->DispatchAsyncTestingEvent(u"moztotalplaytimepaused"_ns); 126 ReportTelemetry(); 127 128 mIsPlaying = false; 129 } 130 131 void TelemetryProbesReporter::OnVisibilityChanged(Visibility aVisibility) { 132 AssertOnMainThreadAndNotShutdown(); 133 LOG("Corresponding media element visibility change=%s -> %s", 134 EnumValueToString(mMediaElementVisibility), 135 EnumValueToString(aVisibility)); 136 if (aVisibility == Visibility::eInvisible) { 137 StartInvisibleVideoTimeAccumulator(); 138 } else { 139 if (aVisibility != Visibility::eInitial) { 140 PauseInvisibleVideoTimeAccumulator(); 141 } else { 142 LOG("Visibility was initial, not pausing."); 143 } 144 } 145 mMediaElementVisibility = aVisibility; 146 } 147 148 void TelemetryProbesReporter::OnAudibleChanged(AudibleState aAudibleState) { 149 AssertOnMainThreadAndNotShutdown(); 150 LOG("Audibility changed, now %s", 151 dom::AudioChannelService::EnumValueToString(aAudibleState)); 152 if (aAudibleState == AudibleState::eNotAudible) { 153 if (!mInaudibleAudioPlayTime.IsStarted()) { 154 StartInaudibleAudioTimeAccumulator(); 155 } 156 } else { 157 // This happens when starting playback, no need to pause, because it hasn't 158 // been started yet. 159 if (mInaudibleAudioPlayTime.IsStarted()) { 160 PauseInaudibleAudioTimeAccumulator(); 161 } 162 } 163 } 164 165 void TelemetryProbesReporter::OnMutedChanged(bool aMuted) { 166 // There are multiple ways to mute an element: 167 // - volume = 0 168 // - muted = true 169 // - set the enabled property of the playing AudioTrack to false 170 // Muted -> Muted "transisition" can therefore happen, and we can't add 171 // asserts here. 172 AssertOnMainThreadAndNotShutdown(); 173 if (!(mMediaContent & MediaContent::MEDIA_HAS_AUDIO)) { 174 return; 175 } 176 LOG("Muted changed, was %s now %s", ToMutedStr(mIsMuted), ToMutedStr(aMuted)); 177 if (aMuted) { 178 if (!mMutedAudioPlayTime.IsStarted()) { 179 StartMutedAudioTimeAccumulator(); 180 } 181 } else { 182 // This happens when starting playback, no need to pause, because it hasn't 183 // been started yet. 184 if (mMutedAudioPlayTime.IsStarted()) { 185 PauseMutedAudioTimeAccumulator(); 186 } 187 } 188 mIsMuted = aMuted; 189 } 190 191 void TelemetryProbesReporter::OnMediaContentChanged(MediaContent aContent) { 192 AssertOnMainThreadAndNotShutdown(); 193 if (aContent == mMediaContent) { 194 return; 195 } 196 if (mMediaContent & MediaContent::MEDIA_HAS_VIDEO && 197 !(aContent & MediaContent::MEDIA_HAS_VIDEO)) { 198 LOG("Video track removed from media."); 199 if (mInvisibleVideoPlayTime.IsStarted()) { 200 PauseInvisibleVideoTimeAccumulator(); 201 } 202 if (mTotalVideoPlayTime.IsStarted()) { 203 mTotalVideoPlayTime.Pause(); 204 mTotalVideoHDRPlayTime.Pause(); 205 } 206 } 207 if (mMediaContent & MediaContent::MEDIA_HAS_AUDIO && 208 !(aContent & MediaContent::MEDIA_HAS_AUDIO)) { 209 LOG("Audio track removed from media."); 210 if (mTotalAudioPlayTime.IsStarted()) { 211 mTotalAudioPlayTime.Pause(); 212 } 213 if (mInaudibleAudioPlayTime.IsStarted()) { 214 mInaudibleAudioPlayTime.Pause(); 215 } 216 if (mMutedAudioPlayTime.IsStarted()) { 217 mMutedAudioPlayTime.Pause(); 218 } 219 } 220 if (!(mMediaContent & MediaContent::MEDIA_HAS_VIDEO) && 221 aContent & MediaContent::MEDIA_HAS_VIDEO) { 222 LOG("Video track added to media."); 223 if (mIsPlaying) { 224 mTotalVideoPlayTime.Start(); 225 if (mMediaElementVisibility == Visibility::eInvisible) { 226 StartInvisibleVideoTimeAccumulator(); 227 } 228 } 229 } 230 if (!(mMediaContent & MediaContent::MEDIA_HAS_COLOR_DEPTH_ABOVE_8) && 231 aContent & MediaContent::MEDIA_HAS_COLOR_DEPTH_ABOVE_8) { 232 if (mIsPlaying) { 233 mTotalVideoHDRPlayTime.Start(); 234 } 235 } 236 if (!(mMediaContent & MediaContent::MEDIA_HAS_AUDIO) && 237 aContent & MediaContent::MEDIA_HAS_AUDIO) { 238 LOG("Audio track added to media."); 239 if (mIsPlaying) { 240 mTotalAudioPlayTime.Start(); 241 if (mIsMuted) { 242 StartMutedAudioTimeAccumulator(); 243 } 244 } 245 } 246 247 mMediaContent = aContent; 248 } 249 250 void TelemetryProbesReporter::OnFirstFrameLoaded( 251 const double aLoadedFirstFrameTime, const double aLoadedMetadataTime, 252 const double aTotalWaitingDataTime, const double aTotalBufferingTime, 253 const FirstFrameLoadedFlagSet aFlags, const MediaInfo& aInfo, 254 const nsCString& aVideoDecoderName) { 255 MOZ_ASSERT(aInfo.HasVideo()); 256 nsCString resolution; 257 DetermineResolutionForTelemetry(aInfo, resolution); 258 259 const bool isMSE = aFlags.contains(FirstFrameLoadedFlag::IsMSE); 260 const bool isExternalEngineStateMachine = 261 aFlags.contains(FirstFrameLoadedFlag::IsExternalEngineStateMachine); 262 263 glean::media_playback::FirstFrameLoadedExtra extraData; 264 extraData.firstFrameLoadedTime = Some(aLoadedFirstFrameTime); 265 extraData.metadataLoadedTime = Some(aLoadedMetadataTime); 266 extraData.totalWaitingDataTime = Some(aTotalWaitingDataTime); 267 extraData.bufferingTime = Some(aTotalBufferingTime); 268 if (!isMSE && !isExternalEngineStateMachine) { 269 extraData.playbackType = Some("Non-MSE playback"_ns); 270 } else if (isMSE && !isExternalEngineStateMachine) { 271 extraData.playbackType = !mOwner->IsEncrypted() ? Some("MSE playback"_ns) 272 : Some("EME playback"_ns); 273 } else if (!isMSE && isExternalEngineStateMachine) { 274 extraData.playbackType = Some("Non-MSE media-engine playback"_ns); 275 } else if (isMSE && isExternalEngineStateMachine) { 276 extraData.playbackType = !mOwner->IsEncrypted() 277 ? Some("MSE media-engine playback"_ns) 278 : Some("EME media-engine playback"_ns); 279 } else { 280 extraData.playbackType = Some("ERROR TYPE"_ns); 281 MOZ_ASSERT(false, "Unexpected playback type!"); 282 } 283 extraData.videoCodec = Some(aInfo.mVideo.mMimeType); 284 extraData.resolution = Some(resolution); 285 if (const auto keySystem = mOwner->GetKeySystem()) { 286 extraData.keySystem = Some(NS_ConvertUTF16toUTF8(*keySystem)); 287 } 288 extraData.isHardwareDecoding = 289 Some(aFlags.contains(FirstFrameLoadedFlag::IsHardwareDecoding)); 290 291 #ifdef MOZ_WIDGET_ANDROID 292 if (aFlags.contains(FirstFrameLoadedFlag::IsHLS)) { 293 extraData.hlsDecoder = Some(true); 294 } 295 #endif 296 297 extraData.decoderName = Some(aVideoDecoderName); 298 extraData.isHdr = Some(static_cast<bool>( 299 mMediaContent & MediaContent::MEDIA_HAS_COLOR_DEPTH_ABOVE_8)); 300 301 if (MOZ_LOG_TEST(gTelemetryProbesReporterLog, LogLevel::Debug)) { 302 nsPrintfCString logMessage{ 303 "Media_Playabck First_Frame_Loaded event, time(ms)=[" 304 "full:%f, loading-meta:%f, waiting-data:%f, buffering:%f], " 305 "playback-type=%s, " 306 "videoCodec=%s, resolution=%s, hardwareAccelerated=%d, decoderName=%s, " 307 "hdr=%d", 308 aLoadedFirstFrameTime, 309 aLoadedMetadataTime, 310 aTotalWaitingDataTime, 311 aTotalBufferingTime, 312 extraData.playbackType->get(), 313 extraData.videoCodec->get(), 314 extraData.resolution->get(), 315 aFlags.contains(FirstFrameLoadedFlag::IsHardwareDecoding), 316 aVideoDecoderName.get(), 317 *extraData.isHdr}; 318 if (const auto keySystem = mOwner->GetKeySystem()) { 319 logMessage.AppendPrintf(", keySystem=%s", 320 NS_ConvertUTF16toUTF8(*keySystem).get()); 321 } 322 LOG("%s", logMessage.get()); 323 } 324 glean::media_playback::first_frame_loaded.Record(Some(extraData)); 325 mOwner->DispatchAsyncTestingEvent(u"mozfirstframeloadedprobe"_ns); 326 } 327 328 void TelemetryProbesReporter::OnShutdown() { 329 AssertOnMainThreadAndNotShutdown(); 330 LOG("Shutdown"); 331 OnPause(Visibility::eInvisible); 332 mOwner = nullptr; 333 } 334 335 void TelemetryProbesReporter::StartInvisibleVideoTimeAccumulator() { 336 AssertOnMainThreadAndNotShutdown(); 337 if (!mTotalVideoPlayTime.IsStarted() || mInvisibleVideoPlayTime.IsStarted() || 338 !HasOwnerHadValidVideo()) { 339 return; 340 } 341 LOG("Start time accumulation for invisible video"); 342 mInvisibleVideoPlayTime.Start(); 343 mOwner->DispatchAsyncTestingEvent(u"mozinvisibleplaytimestarted"_ns); 344 } 345 346 void TelemetryProbesReporter::PauseInvisibleVideoTimeAccumulator() { 347 AssertOnMainThreadAndNotShutdown(); 348 if (!mInvisibleVideoPlayTime.IsStarted()) { 349 return; 350 } 351 LOG("Pause time accumulation for invisible video"); 352 mInvisibleVideoPlayTime.Pause(); 353 mOwner->DispatchAsyncTestingEvent(u"mozinvisibleplaytimepaused"_ns); 354 } 355 356 void TelemetryProbesReporter::StartInaudibleAudioTimeAccumulator() { 357 AssertOnMainThreadAndNotShutdown(); 358 MOZ_ASSERT(!mInaudibleAudioPlayTime.IsStarted()); 359 mInaudibleAudioPlayTime.Start(); 360 mOwner->DispatchAsyncTestingEvent(u"mozinaudibleaudioplaytimestarted"_ns); 361 } 362 363 void TelemetryProbesReporter::PauseInaudibleAudioTimeAccumulator() { 364 AssertOnMainThreadAndNotShutdown(); 365 MOZ_ASSERT(mInaudibleAudioPlayTime.IsStarted()); 366 mInaudibleAudioPlayTime.Pause(); 367 mOwner->DispatchAsyncTestingEvent(u"mozinaudibleaudioplaytimepaused"_ns); 368 } 369 370 void TelemetryProbesReporter::StartMutedAudioTimeAccumulator() { 371 AssertOnMainThreadAndNotShutdown(); 372 MOZ_ASSERT(!mMutedAudioPlayTime.IsStarted()); 373 mMutedAudioPlayTime.Start(); 374 mOwner->DispatchAsyncTestingEvent(u"mozmutedaudioplaytimestarted"_ns); 375 } 376 377 void TelemetryProbesReporter::PauseMutedAudioTimeAccumulator() { 378 AssertOnMainThreadAndNotShutdown(); 379 MOZ_ASSERT(mMutedAudioPlayTime.IsStarted()); 380 mMutedAudioPlayTime.Pause(); 381 mOwner->DispatchAsyncTestingEvent(u"mozmutedeaudioplaytimepaused"_ns); 382 } 383 384 bool TelemetryProbesReporter::HasOwnerHadValidVideo() const { 385 // Checking both image and display dimensions helps address cases such as 386 // suspending, where we use a null decoder. In that case a null decoder 387 // produces 0x0 video frames, which might cause layout to resize the display 388 // size, but the image dimensions would be still non-null. 389 const VideoInfo info = mOwner->GetMediaInfo().mVideo; 390 return (info.mDisplay.height > 0 && info.mDisplay.width > 0) || 391 (info.mImage.height > 0 && info.mImage.width > 0); 392 } 393 394 bool TelemetryProbesReporter::HasOwnerHadValidMedia() const { 395 return mMediaContent != MediaContent::MEDIA_HAS_NOTHING; 396 } 397 398 void TelemetryProbesReporter::AssertOnMainThreadAndNotShutdown() const { 399 MOZ_ASSERT(NS_IsMainThread()); 400 MOZ_ASSERT(mOwner, "Already shutdown?"); 401 } 402 403 void TelemetryProbesReporter::ReportTelemetry() { 404 AssertOnMainThreadAndNotShutdown(); 405 // ReportResultForAudio needs to be called first, because it can use the video 406 // play time, that is reset in ReportResultForVideo. 407 ReportResultForAudio(); 408 ReportResultForVideo(); 409 mOwner->DispatchAsyncTestingEvent(u"mozreportedtelemetry"_ns); 410 } 411 412 void TelemetryProbesReporter::ReportResultForVideo() { 413 // We don't want to know the result for video without valid video frames. 414 if (!HasOwnerHadValidVideo()) { 415 return; 416 } 417 418 const double totalVideoPlayTimeS = mTotalVideoPlayTime.GetAndClearTotal(); 419 const double invisiblePlayTimeS = mInvisibleVideoPlayTime.GetAndClearTotal(); 420 const double totalVideoHDRPlayTimeS = 421 mTotalVideoHDRPlayTime.GetAndClearTotal(); 422 423 // No need to report result for video that didn't start playing. 424 if (totalVideoPlayTimeS == 0.0) { 425 return; 426 } 427 MOZ_ASSERT(totalVideoPlayTimeS >= invisiblePlayTimeS); 428 429 LOG("VIDEO_PLAY_TIME_S = %f", totalVideoPlayTimeS); 430 glean::media::video_play_time.AccumulateRawDuration( 431 TimeDuration::FromSeconds(totalVideoPlayTimeS)); 432 433 LOG("VIDEO_HIDDEN_PLAY_TIME_S = %f", invisiblePlayTimeS); 434 glean::media::video_hidden_play_time.AccumulateRawDuration( 435 TimeDuration::FromSeconds(invisiblePlayTimeS)); 436 437 // We only want to accumulate non-zero samples for HDR playback. 438 // This is different from the other timings tracked here, but 439 // we don't need 0-length play times to do our calculations. 440 if (totalVideoHDRPlayTimeS > 0.0) { 441 LOG("VIDEO_HDR_PLAY_TIME_S = %f", totalVideoHDRPlayTimeS); 442 glean::media::video_hdr_play_time.AccumulateRawDuration( 443 TimeDuration::FromSeconds(totalVideoHDRPlayTimeS)); 444 } 445 446 if (mOwner->IsEncrypted()) { 447 LOG("VIDEO_ENCRYPTED_PLAY_TIME_S = %f", totalVideoPlayTimeS); 448 glean::media::video_encrypted_play_time.AccumulateRawDuration( 449 TimeDuration::FromSeconds(totalVideoPlayTimeS)); 450 } 451 452 // TODO: deprecate the old probes. 453 // Report result for video using CDM 454 auto keySystem = mOwner->GetKeySystem(); 455 if (keySystem) { 456 if (IsClearkeyKeySystem(*keySystem)) { 457 LOG("VIDEO_CLEARKEY_PLAY_TIME_S = %f", totalVideoPlayTimeS); 458 glean::media::video_clearkey_play_time.AccumulateRawDuration( 459 TimeDuration::FromSeconds(totalVideoPlayTimeS)); 460 461 } else if (IsWidevineKeySystem(*keySystem)) { 462 LOG("VIDEO_WIDEVINE_PLAY_TIME_S = %f", totalVideoPlayTimeS); 463 glean::media::video_widevine_play_time.AccumulateRawDuration( 464 TimeDuration::FromSeconds(totalVideoPlayTimeS)); 465 } 466 } 467 468 // Keyed by audio+video or video alone, and by a resolution range. 469 const MediaInfo& info = mOwner->GetMediaInfo(); 470 nsCString key; 471 DetermineResolutionForTelemetry(info, key); 472 473 auto visiblePlayTimeS = totalVideoPlayTimeS - invisiblePlayTimeS; 474 LOG("VIDEO_VISIBLE_PLAY_TIME = %f, keys: '%s' and 'All'", visiblePlayTimeS, 475 key.get()); 476 glean::media::video_visible_play_time.Get(key).AccumulateRawDuration( 477 TimeDuration::FromSeconds(visiblePlayTimeS)); 478 // Also accumulate result in an "All" key. 479 glean::media::video_visible_play_time.Get("All"_ns).AccumulateRawDuration( 480 TimeDuration::FromSeconds(visiblePlayTimeS)); 481 482 const uint32_t hiddenPercentage = 483 lround(invisiblePlayTimeS / totalVideoPlayTimeS * 100.0); 484 glean::media::video_hidden_play_time_percentage.Get(key) 485 .AccumulateSingleSample(hiddenPercentage); 486 // Also accumulate all percentages in an "All" key. 487 glean::media::video_hidden_play_time_percentage.Get("All"_ns) 488 .AccumulateSingleSample(hiddenPercentage); 489 LOG("VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE = %u, keys: '%s' and 'All'", 490 hiddenPercentage, key.get()); 491 492 ReportResultForVideoFrameStatistics(totalVideoPlayTimeS, key); 493 #ifdef MOZ_WMF_CDM 494 if (mOwner->IsUsingWMFCDM()) { 495 ReportResultForMFCDMPlaybackIfNeeded(totalVideoPlayTimeS, key); 496 } 497 #endif 498 if (keySystem) { 499 ReportPlaytimeForKeySystem(*keySystem, totalVideoPlayTimeS, 500 info.mVideo.mMimeType, key); 501 } 502 } 503 504 #ifdef MOZ_WMF_CDM 505 void TelemetryProbesReporter::ReportResultForMFCDMPlaybackIfNeeded( 506 double aTotalPlayTimeS, const nsCString& aResolution) { 507 const auto keySystem = mOwner->GetKeySystem(); 508 if (!keySystem) { 509 NS_WARNING("Can not find key system to report telemetry for MFCDM!!"); 510 return; 511 } 512 glean::mfcdm::EmePlaybackExtra extraData; 513 extraData.keySystem = Some(NS_ConvertUTF16toUTF8(*keySystem)); 514 extraData.videoCodec = Some(mOwner->GetMediaInfo().mVideo.mMimeType); 515 extraData.resolution = Some(aResolution); 516 extraData.playedTime = Some(aTotalPlayTimeS); 517 518 Maybe<uint64_t> renderedFrames; 519 Maybe<uint64_t> droppedFrames; 520 if (auto* stats = mOwner->GetFrameStatistics()) { 521 renderedFrames = Some(stats->GetPresentedFrames()); 522 droppedFrames = Some(stats->GetDroppedFrames()); 523 extraData.renderedFrames = Some(*renderedFrames); 524 extraData.droppedFrames = Some(*droppedFrames); 525 } 526 if (MOZ_LOG_TEST(gTelemetryProbesReporterLog, LogLevel::Debug)) { 527 nsPrintfCString logMessage{ 528 "MFCDM EME_Playback event, keySystem=%s, videoCodec=%s, resolution=%s, " 529 "playedTime=%lf", 530 NS_ConvertUTF16toUTF8(*keySystem).get(), 531 mOwner->GetMediaInfo().mVideo.mMimeType.get(), aResolution.get(), 532 aTotalPlayTimeS}; 533 if (renderedFrames) { 534 logMessage.AppendPrintf(", renderedFrames=%" PRIu64, *renderedFrames); 535 } 536 if (droppedFrames) { 537 logMessage.AppendPrintf(", droppedFrames=%" PRIu64, *droppedFrames); 538 } 539 LOG("%s", logMessage.get()); 540 } 541 glean::mfcdm::eme_playback.Record(Some(extraData)); 542 } 543 #endif 544 545 void TelemetryProbesReporter::ReportPlaytimeForKeySystem( 546 const nsAString& aKeySystem, const double aTotalPlayTimeS, 547 const nsCString& aCodec, const nsCString& aResolution) { 548 glean::mediadrm::EmePlaybackExtra extra = { 549 .keySystem = Some(NS_ConvertUTF16toUTF8(aKeySystem)), 550 .playedTime = Some(aTotalPlayTimeS), 551 .resolution = Some(aResolution), 552 .videoCodec = Some(aCodec)}; 553 glean::mediadrm::eme_playback.Record(Some(extra)); 554 } 555 556 void TelemetryProbesReporter::ReportResultForAudio() { 557 // Don't record telemetry for a media that didn't have a valid audio or video 558 // to play, or hasn't played. 559 if (!HasOwnerHadValidMedia() || (mTotalAudioPlayTime.PeekTotal() == 0.0 && 560 mTotalVideoPlayTime.PeekTotal() == 0.0)) { 561 return; 562 } 563 564 nsCString key; 565 nsCString avKey; 566 const double totalAudioPlayTimeS = mTotalAudioPlayTime.GetAndClearTotal(); 567 const double inaudiblePlayTimeS = mInaudibleAudioPlayTime.GetAndClearTotal(); 568 const double mutedPlayTimeS = mMutedAudioPlayTime.GetAndClearTotal(); 569 const double audiblePlayTimeS = totalAudioPlayTimeS - inaudiblePlayTimeS; 570 const double unmutedPlayTimeS = totalAudioPlayTimeS - mutedPlayTimeS; 571 const uint32_t audiblePercentage = 572 lround(audiblePlayTimeS / totalAudioPlayTimeS * 100.0); 573 const uint32_t unmutedPercentage = 574 lround(unmutedPlayTimeS / totalAudioPlayTimeS * 100.0); 575 const double totalVideoPlayTimeS = mTotalVideoPlayTime.PeekTotal(); 576 577 // Key semantics: 578 // - AV: Audible audio + video 579 // - IV: Inaudible audio + video 580 // - MV: Muted audio + video 581 // - A: Audible audio-only 582 // - I: Inaudible audio-only 583 // - M: Muted audio-only 584 // - V: Video-only 585 if (mMediaContent & MediaContent::MEDIA_HAS_AUDIO) { 586 if (audiblePercentage == 0) { 587 // Media element had an audio track, but it was inaudible throughout 588 key.AppendASCII("I"); 589 } else if (unmutedPercentage == 0) { 590 // Media element had an audio track, but it was muted throughout 591 key.AppendASCII("M"); 592 } else { 593 // Media element had an audible audio track 594 key.AppendASCII("A"); 595 } 596 avKey.AppendASCII("A"); 597 } 598 if (mMediaContent & MediaContent::MEDIA_HAS_VIDEO) { 599 key.AppendASCII("V"); 600 avKey.AppendASCII("V"); 601 } 602 603 LOG("Key: %s", key.get()); 604 605 if (mMediaContent & MediaContent::MEDIA_HAS_AUDIO) { 606 LOG("Audio:\ntotal: %lf\naudible: %lf\ninaudible: %lf\nmuted: " 607 "%lf\npercentage audible: " 608 "%u\npercentage unmuted: %u\n", 609 totalAudioPlayTimeS, audiblePlayTimeS, inaudiblePlayTimeS, 610 mutedPlayTimeS, audiblePercentage, unmutedPercentage); 611 glean::media::media_play_time.Get(key).AccumulateRawDuration( 612 TimeDuration::FromSeconds(totalAudioPlayTimeS)); 613 glean::media::muted_play_time_percent.Get(avKey).AccumulateSingleSample( 614 100 - unmutedPercentage); 615 glean::media::audible_play_time_percent.Get(avKey).AccumulateSingleSample( 616 audiblePercentage); 617 } else { 618 MOZ_ASSERT(mMediaContent & MediaContent::MEDIA_HAS_VIDEO); 619 glean::media::media_play_time.Get(key).AccumulateRawDuration( 620 TimeDuration::FromSeconds(totalVideoPlayTimeS)); 621 } 622 } 623 624 void TelemetryProbesReporter::ReportResultForVideoFrameStatistics( 625 double aTotalPlayTimeS, const nsCString& key) { 626 FrameStatistics* stats = mOwner->GetFrameStatistics(); 627 if (!stats) { 628 return; 629 } 630 631 const uint64_t parsedFrames = stats->GetParsedFrames(); 632 if (parsedFrames) { 633 const uint64_t droppedFrames = stats->GetDroppedFrames(); 634 MOZ_ASSERT(droppedFrames <= parsedFrames); 635 // Dropped frames <= total frames, so 'percentage' cannot be higher than 636 // 100 and therefore can fit in a uint32_t (that Telemetry takes). 637 const uint32_t percentage = 100 * droppedFrames / parsedFrames; 638 LOG("DROPPED_FRAMES_IN_VIDEO_PLAYBACK = %u", percentage); 639 glean::media::video_dropped_frames_proportion.AccumulateSingleSample( 640 percentage); 641 const uint32_t proportion = 10000 * droppedFrames / parsedFrames; 642 glean::media::video_dropped_frames_proportion_exponential 643 .AccumulateSingleSample(proportion); 644 645 { 646 const uint64_t droppedFrames = stats->GetDroppedDecodedFrames(); 647 const uint32_t proportion = 10000 * droppedFrames / parsedFrames; 648 glean::media::video_dropped_decoded_frames_proportion_exponential 649 .AccumulateSingleSample(proportion); 650 } 651 { 652 const uint64_t droppedFrames = stats->GetDroppedSinkFrames(); 653 const uint32_t proportion = 10000 * droppedFrames / parsedFrames; 654 glean::media::video_dropped_sink_frames_proportion_exponential 655 .AccumulateSingleSample(proportion); 656 } 657 { 658 const uint64_t droppedFrames = stats->GetDroppedCompositorFrames(); 659 const uint32_t proportion = 10000 * droppedFrames / parsedFrames; 660 glean::media::video_dropped_compositor_frames_proportion_exponential 661 .AccumulateSingleSample(proportion); 662 } 663 } 664 } 665 666 double TelemetryProbesReporter::GetTotalVideoPlayTimeInSeconds() const { 667 return mTotalVideoPlayTime.PeekTotal(); 668 } 669 670 double TelemetryProbesReporter::GetTotalVideoHDRPlayTimeInSeconds() const { 671 return mTotalVideoHDRPlayTime.PeekTotal(); 672 } 673 674 double TelemetryProbesReporter::GetVisibleVideoPlayTimeInSeconds() const { 675 return GetTotalVideoPlayTimeInSeconds() - 676 GetInvisibleVideoPlayTimeInSeconds(); 677 } 678 679 double TelemetryProbesReporter::GetInvisibleVideoPlayTimeInSeconds() const { 680 return mInvisibleVideoPlayTime.PeekTotal(); 681 } 682 683 double TelemetryProbesReporter::GetTotalAudioPlayTimeInSeconds() const { 684 return mTotalAudioPlayTime.PeekTotal(); 685 } 686 687 double TelemetryProbesReporter::GetInaudiblePlayTimeInSeconds() const { 688 return mInaudibleAudioPlayTime.PeekTotal(); 689 } 690 691 double TelemetryProbesReporter::GetMutedPlayTimeInSeconds() const { 692 return mMutedAudioPlayTime.PeekTotal(); 693 } 694 695 double TelemetryProbesReporter::GetAudiblePlayTimeInSeconds() const { 696 return GetTotalAudioPlayTimeInSeconds() - GetInaudiblePlayTimeInSeconds(); 697 } 698 699 /* static */ 700 void TelemetryProbesReporter::ReportDeviceMediaCodecSupported( 701 const media::MediaCodecsSupported& aSupported) { 702 static bool sReported = false; 703 if (sReported) { 704 return; 705 } 706 MOZ_ASSERT(ContainHardwareCodecsSupported(aSupported)); 707 sReported = true; 708 709 glean::media_playback::device_hardware_decoder_support.Get("h264"_ns).Set( 710 aSupported.contains( 711 mozilla::media::MediaCodecsSupport::H264HardwareDecode)); 712 glean::media_playback::device_hardware_decoder_support.Get("vp8"_ns).Set( 713 aSupported.contains( 714 mozilla::media::MediaCodecsSupport::VP8HardwareDecode)); 715 glean::media_playback::device_hardware_decoder_support.Get("vp9"_ns).Set( 716 aSupported.contains( 717 mozilla::media::MediaCodecsSupport::VP9HardwareDecode)); 718 glean::media_playback::device_hardware_decoder_support.Get("av1"_ns).Set( 719 aSupported.contains( 720 mozilla::media::MediaCodecsSupport::AV1HardwareDecode)); 721 glean::media_playback::device_hardware_decoder_support.Get("hevc"_ns).Set( 722 aSupported.contains( 723 mozilla::media::MediaCodecsSupport::HEVCHardwareDecode)); 724 } 725 726 #undef LOG 727 } // namespace mozilla