tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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