diff options
Diffstat (limited to 'dom/media/utils/TelemetryProbesReporter.cpp')
-rw-r--r-- | dom/media/utils/TelemetryProbesReporter.cpp | 708 |
1 files changed, 708 insertions, 0 deletions
diff --git a/dom/media/utils/TelemetryProbesReporter.cpp b/dom/media/utils/TelemetryProbesReporter.cpp new file mode 100644 index 0000000000..a2854812ba --- /dev/null +++ b/dom/media/utils/TelemetryProbesReporter.cpp @@ -0,0 +1,708 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TelemetryProbesReporter.h" + +#include <cmath> + +#include "FrameStatistics.h" +#include "VideoUtils.h" +#include "mozilla/EMEUtils.h" +#include "mozilla/Logging.h" +#include "mozilla/Telemetry.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/glean/GleanMetrics.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +LazyLogModule gTelemetryProbesReporterLog("TelemetryProbesReporter"); +#define LOG(msg, ...) \ + MOZ_LOG(gTelemetryProbesReporterLog, LogLevel::Debug, \ + ("TelemetryProbesReporter=%p, " msg, this, ##__VA_ARGS__)) + +static const char* ToVisibilityStr( + TelemetryProbesReporter::Visibility aVisibility) { + switch (aVisibility) { + case TelemetryProbesReporter::Visibility::eVisible: + return "visible"; + case TelemetryProbesReporter::Visibility::eInvisible: + return "invisible"; + case TelemetryProbesReporter::Visibility::eInitial: + return "initial"; + default: + MOZ_ASSERT_UNREACHABLE("invalid visibility"); + return "unknown"; + } +} +static const char* ToAudibilityStr( + TelemetryProbesReporter::AudibleState aAudibleState) { + switch (aAudibleState) { + case TelemetryProbesReporter::AudibleState::eAudible: + return "audible"; + case TelemetryProbesReporter::AudibleState::eNotAudible: + return "inaudible"; + default: + MOZ_ASSERT_UNREACHABLE("invalid audibility"); + return "unknown"; + } +} + +static const char* ToMutedStr(bool aMuted) { + return aMuted ? "muted" : "unmuted"; +} + +MediaContent TelemetryProbesReporter::MediaInfoToMediaContent( + const MediaInfo& aInfo) { + MediaContent content = MediaContent::MEDIA_HAS_NOTHING; + if (aInfo.HasAudio()) { + content |= MediaContent::MEDIA_HAS_AUDIO; + } + if (aInfo.HasVideo()) { + content |= MediaContent::MEDIA_HAS_VIDEO; + if (aInfo.mVideo.GetAsVideoInfo()->mColorDepth > gfx::ColorDepth::COLOR_8) { + content |= MediaContent::MEDIA_HAS_COLOR_DEPTH_ABOVE_8; + } + } + return content; +} + +TelemetryProbesReporter::TelemetryProbesReporter( + TelemetryProbesReporterOwner* aOwner) + : mOwner(aOwner) { + MOZ_ASSERT(mOwner); +} + +void TelemetryProbesReporter::OnPlay(Visibility aVisibility, + MediaContent aMediaContent, + bool aIsMuted) { + LOG("Start time accumulation for total play time"); + + AssertOnMainThreadAndNotShutdown(); + MOZ_ASSERT_IF(mMediaContent & MediaContent::MEDIA_HAS_VIDEO, + !mTotalVideoPlayTime.IsStarted()); + MOZ_ASSERT_IF(mMediaContent & MediaContent::MEDIA_HAS_AUDIO, + !mTotalAudioPlayTime.IsStarted()); + + if (aMediaContent & MediaContent::MEDIA_HAS_VIDEO) { + mTotalVideoPlayTime.Start(); + + MOZ_ASSERT_IF(mMediaContent & MediaContent::MEDIA_HAS_COLOR_DEPTH_ABOVE_8, + !mTotalVideoHDRPlayTime.IsStarted()); + if (aMediaContent & MediaContent::MEDIA_HAS_COLOR_DEPTH_ABOVE_8) { + mTotalVideoHDRPlayTime.Start(); + } + } + if (aMediaContent & MediaContent::MEDIA_HAS_AUDIO) { + mTotalAudioPlayTime.Start(); + } + + OnMediaContentChanged(aMediaContent); + OnVisibilityChanged(aVisibility); + OnMutedChanged(aIsMuted); + + mOwner->DispatchAsyncTestingEvent(u"moztotalplaytimestarted"_ns); + + mIsPlaying = true; +} + +void TelemetryProbesReporter::OnPause(Visibility aVisibility) { + if (!mIsPlaying) { + // Not started + LOG("TelemetryProbesReporter::OnPause: not started, early return"); + return; + } + + LOG("Pause time accumulation for total play time"); + + AssertOnMainThreadAndNotShutdown(); + MOZ_ASSERT_IF(mMediaContent & MediaContent::MEDIA_HAS_VIDEO, + mTotalVideoPlayTime.IsStarted()); + MOZ_ASSERT_IF(mMediaContent & MediaContent::MEDIA_HAS_AUDIO, + mTotalAudioPlayTime.IsStarted()); + + if (mMediaContent & MediaContent::MEDIA_HAS_VIDEO) { + MOZ_ASSERT_IF(mMediaContent & MediaContent::MEDIA_HAS_COLOR_DEPTH_ABOVE_8, + mTotalVideoHDRPlayTime.IsStarted()); + + LOG("Pause video time accumulation for total play time"); + if (mInvisibleVideoPlayTime.IsStarted()) { + LOG("Pause invisible video time accumulation for total play time"); + PauseInvisibleVideoTimeAccumulator(); + } + mTotalVideoPlayTime.Pause(); + mTotalVideoHDRPlayTime.Pause(); + } + if (mMediaContent & MediaContent::MEDIA_HAS_AUDIO) { + LOG("Pause audio time accumulation for total play time"); + if (mInaudibleAudioPlayTime.IsStarted()) { + LOG("Pause audible audio time accumulation for total play time"); + PauseInaudibleAudioTimeAccumulator(); + } + if (mMutedAudioPlayTime.IsStarted()) { + LOG("Pause muted audio time accumulation for total play time"); + PauseMutedAudioTimeAccumulator(); + } + mTotalAudioPlayTime.Pause(); + } + + mOwner->DispatchAsyncTestingEvent(u"moztotalplaytimepaused"_ns); + ReportTelemetry(); + + mIsPlaying = false; +} + +void TelemetryProbesReporter::OnVisibilityChanged(Visibility aVisibility) { + AssertOnMainThreadAndNotShutdown(); + LOG("Corresponding media element visibility change=%s -> %s", + ToVisibilityStr(mMediaElementVisibility), ToVisibilityStr(aVisibility)); + if (aVisibility == Visibility::eInvisible) { + StartInvisibleVideoTimeAccumulator(); + } else { + if (aVisibility != Visibility::eInitial) { + PauseInvisibleVideoTimeAccumulator(); + } else { + LOG("Visibility was initial, not pausing."); + } + } + mMediaElementVisibility = aVisibility; +} + +void TelemetryProbesReporter::OnAudibleChanged(AudibleState aAudibleState) { + AssertOnMainThreadAndNotShutdown(); + LOG("Audibility changed, now %s", ToAudibilityStr(aAudibleState)); + if (aAudibleState == AudibleState::eNotAudible) { + if (!mInaudibleAudioPlayTime.IsStarted()) { + StartInaudibleAudioTimeAccumulator(); + } + } else { + // This happens when starting playback, no need to pause, because it hasn't + // been started yet. + if (mInaudibleAudioPlayTime.IsStarted()) { + PauseInaudibleAudioTimeAccumulator(); + } + } +} + +void TelemetryProbesReporter::OnMutedChanged(bool aMuted) { + // There are multiple ways to mute an element: + // - volume = 0 + // - muted = true + // - set the enabled property of the playing AudioTrack to false + // Muted -> Muted "transisition" can therefore happen, and we can't add + // asserts here. + AssertOnMainThreadAndNotShutdown(); + if (!(mMediaContent & MediaContent::MEDIA_HAS_AUDIO)) { + return; + } + LOG("Muted changed, was %s now %s", ToMutedStr(mIsMuted), ToMutedStr(aMuted)); + if (aMuted) { + if (!mMutedAudioPlayTime.IsStarted()) { + StartMutedAudioTimeAccumulator(); + } + } else { + // This happens when starting playback, no need to pause, because it hasn't + // been started yet. + if (mMutedAudioPlayTime.IsStarted()) { + PauseMutedAudioTimeAccumulator(); + } + } + mIsMuted = aMuted; +} + +void TelemetryProbesReporter::OnMediaContentChanged(MediaContent aContent) { + AssertOnMainThreadAndNotShutdown(); + if (aContent == mMediaContent) { + return; + } + if (mMediaContent & MediaContent::MEDIA_HAS_VIDEO && + !(aContent & MediaContent::MEDIA_HAS_VIDEO)) { + LOG("Video track removed from media."); + if (mInvisibleVideoPlayTime.IsStarted()) { + PauseInvisibleVideoTimeAccumulator(); + } + if (mTotalVideoPlayTime.IsStarted()) { + mTotalVideoPlayTime.Pause(); + mTotalVideoHDRPlayTime.Pause(); + } + } + if (mMediaContent & MediaContent::MEDIA_HAS_AUDIO && + !(aContent & MediaContent::MEDIA_HAS_AUDIO)) { + LOG("Audio track removed from media."); + if (mTotalAudioPlayTime.IsStarted()) { + mTotalAudioPlayTime.Pause(); + } + if (mInaudibleAudioPlayTime.IsStarted()) { + mInaudibleAudioPlayTime.Pause(); + } + if (mMutedAudioPlayTime.IsStarted()) { + mMutedAudioPlayTime.Pause(); + } + } + if (!(mMediaContent & MediaContent::MEDIA_HAS_VIDEO) && + aContent & MediaContent::MEDIA_HAS_VIDEO) { + LOG("Video track added to media."); + if (mIsPlaying) { + mTotalVideoPlayTime.Start(); + if (mMediaElementVisibility == Visibility::eInvisible) { + StartInvisibleVideoTimeAccumulator(); + } + } + } + if (!(mMediaContent & MediaContent::MEDIA_HAS_COLOR_DEPTH_ABOVE_8) && + aContent & MediaContent::MEDIA_HAS_COLOR_DEPTH_ABOVE_8) { + if (mIsPlaying) { + mTotalVideoHDRPlayTime.Start(); + } + } + if (!(mMediaContent & MediaContent::MEDIA_HAS_AUDIO) && + aContent & MediaContent::MEDIA_HAS_AUDIO) { + LOG("Audio track added to media."); + if (mIsPlaying) { + mTotalAudioPlayTime.Start(); + if (mIsMuted) { + StartMutedAudioTimeAccumulator(); + } + } + } + + mMediaContent = aContent; +} + +void TelemetryProbesReporter::OnDecodeSuspended() { + AssertOnMainThreadAndNotShutdown(); + // Suspended time should only be counted after starting accumulating invisible + // time. + if (!mInvisibleVideoPlayTime.IsStarted()) { + return; + } + LOG("Start time accumulation for video decoding suspension"); + mVideoDecodeSuspendedTime.Start(); + mOwner->DispatchAsyncTestingEvent(u"mozvideodecodesuspendedstarted"_ns); +} + +void TelemetryProbesReporter::OnDecodeResumed() { + AssertOnMainThreadAndNotShutdown(); + if (!mVideoDecodeSuspendedTime.IsStarted()) { + return; + } + LOG("Pause time accumulation for video decoding suspension"); + mVideoDecodeSuspendedTime.Pause(); + mOwner->DispatchAsyncTestingEvent(u"mozvideodecodesuspendedpaused"_ns); +} + +void TelemetryProbesReporter::OnShutdown() { + AssertOnMainThreadAndNotShutdown(); + LOG("Shutdown"); + OnPause(Visibility::eInvisible); + mOwner = nullptr; +} + +void TelemetryProbesReporter::StartInvisibleVideoTimeAccumulator() { + AssertOnMainThreadAndNotShutdown(); + if (!mTotalVideoPlayTime.IsStarted() || mInvisibleVideoPlayTime.IsStarted() || + !HasOwnerHadValidVideo()) { + return; + } + LOG("Start time accumulation for invisible video"); + mInvisibleVideoPlayTime.Start(); + mOwner->DispatchAsyncTestingEvent(u"mozinvisibleplaytimestarted"_ns); +} + +void TelemetryProbesReporter::PauseInvisibleVideoTimeAccumulator() { + AssertOnMainThreadAndNotShutdown(); + if (!mInvisibleVideoPlayTime.IsStarted()) { + return; + } + OnDecodeResumed(); + LOG("Pause time accumulation for invisible video"); + mInvisibleVideoPlayTime.Pause(); + mOwner->DispatchAsyncTestingEvent(u"mozinvisibleplaytimepaused"_ns); +} + +void TelemetryProbesReporter::StartInaudibleAudioTimeAccumulator() { + AssertOnMainThreadAndNotShutdown(); + MOZ_ASSERT(!mInaudibleAudioPlayTime.IsStarted()); + mInaudibleAudioPlayTime.Start(); + mOwner->DispatchAsyncTestingEvent(u"mozinaudibleaudioplaytimestarted"_ns); +} + +void TelemetryProbesReporter::PauseInaudibleAudioTimeAccumulator() { + AssertOnMainThreadAndNotShutdown(); + MOZ_ASSERT(mInaudibleAudioPlayTime.IsStarted()); + mInaudibleAudioPlayTime.Pause(); + mOwner->DispatchAsyncTestingEvent(u"mozinaudibleaudioplaytimepaused"_ns); +} + +void TelemetryProbesReporter::StartMutedAudioTimeAccumulator() { + AssertOnMainThreadAndNotShutdown(); + MOZ_ASSERT(!mMutedAudioPlayTime.IsStarted()); + mMutedAudioPlayTime.Start(); + mOwner->DispatchAsyncTestingEvent(u"mozmutedaudioplaytimestarted"_ns); +} + +void TelemetryProbesReporter::PauseMutedAudioTimeAccumulator() { + AssertOnMainThreadAndNotShutdown(); + MOZ_ASSERT(mMutedAudioPlayTime.IsStarted()); + mMutedAudioPlayTime.Pause(); + mOwner->DispatchAsyncTestingEvent(u"mozmutedeaudioplaytimepaused"_ns); +} + +bool TelemetryProbesReporter::HasOwnerHadValidVideo() const { + // Checking both image and display dimensions helps address cases such as + // suspending, where we use a null decoder. In that case a null decoder + // produces 0x0 video frames, which might cause layout to resize the display + // size, but the image dimensions would be still non-null. + const VideoInfo info = mOwner->GetMediaInfo().mVideo; + return (info.mDisplay.height > 0 && info.mDisplay.width > 0) || + (info.mImage.height > 0 && info.mImage.width > 0); +} + +bool TelemetryProbesReporter::HasOwnerHadValidMedia() const { + return mMediaContent != MediaContent::MEDIA_HAS_NOTHING; +} + +void TelemetryProbesReporter::AssertOnMainThreadAndNotShutdown() const { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mOwner, "Already shutdown?"); +} + +void TelemetryProbesReporter::ReportTelemetry() { + AssertOnMainThreadAndNotShutdown(); + // ReportResultForAudio needs to be called first, because it can use the video + // play time, that is reset in ReportResultForVideo. + ReportResultForAudio(); + ReportResultForVideo(); + mOwner->DispatchAsyncTestingEvent(u"mozreportedtelemetry"_ns); +} + +void TelemetryProbesReporter::ReportResultForVideo() { + // We don't want to know the result for video without valid video frames. + if (!HasOwnerHadValidVideo()) { + return; + } + + const double totalVideoPlayTimeS = mTotalVideoPlayTime.GetAndClearTotal(); + const double invisiblePlayTimeS = mInvisibleVideoPlayTime.GetAndClearTotal(); + const double videoDecodeSuspendTimeS = + mVideoDecodeSuspendedTime.GetAndClearTotal(); + const double totalVideoHDRPlayTimeS = + mTotalVideoHDRPlayTime.GetAndClearTotal(); + + // No need to report result for video that didn't start playing. + if (totalVideoPlayTimeS == 0.0) { + return; + } + MOZ_ASSERT(totalVideoPlayTimeS >= invisiblePlayTimeS); + + LOG("VIDEO_PLAY_TIME_S = %f", totalVideoPlayTimeS); + Telemetry::Accumulate(Telemetry::VIDEO_PLAY_TIME_MS, + SECONDS_TO_MS(totalVideoPlayTimeS)); + + LOG("VIDEO_HIDDEN_PLAY_TIME_S = %f", invisiblePlayTimeS); + Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_MS, + SECONDS_TO_MS(invisiblePlayTimeS)); + + // We only want to accumulate non-zero samples for HDR playback. + // This is different from the other timings tracked here, but + // we don't need 0-length play times to do our calculations. + if (totalVideoHDRPlayTimeS > 0.0) { + LOG("VIDEO_HDR_PLAY_TIME_S = %f", totalVideoHDRPlayTimeS); + Telemetry::Accumulate(Telemetry::VIDEO_HDR_PLAY_TIME_MS, + SECONDS_TO_MS(totalVideoHDRPlayTimeS)); + } + + if (mOwner->IsEncrypted()) { + LOG("VIDEO_ENCRYPTED_PLAY_TIME_S = %f", totalVideoPlayTimeS); + Telemetry::Accumulate(Telemetry::VIDEO_ENCRYPTED_PLAY_TIME_MS, + SECONDS_TO_MS(totalVideoPlayTimeS)); + } + + // Report result for video using CDM + auto keySystem = mOwner->GetKeySystem(); + if (keySystem) { + if (IsClearkeyKeySystem(*keySystem)) { + LOG("VIDEO_CLEARKEY_PLAY_TIME_S = %f", totalVideoPlayTimeS); + Telemetry::Accumulate(Telemetry::VIDEO_CLEARKEY_PLAY_TIME_MS, + SECONDS_TO_MS(totalVideoPlayTimeS)); + + } else if (IsWidevineKeySystem(*keySystem)) { + LOG("VIDEO_WIDEVINE_PLAY_TIME_S = %f", totalVideoPlayTimeS); + Telemetry::Accumulate(Telemetry::VIDEO_WIDEVINE_PLAY_TIME_MS, + SECONDS_TO_MS(totalVideoPlayTimeS)); + } + } + + // Keyed by audio+video or video alone, and by a resolution range. + const MediaInfo& info = mOwner->GetMediaInfo(); + nsCString key; + DetermineResolutionForTelemetry(info, key); + + auto visiblePlayTimeS = totalVideoPlayTimeS - invisiblePlayTimeS; + LOG("VIDEO_VISIBLE_PLAY_TIME = %f, keys: '%s' and 'All'", visiblePlayTimeS, + key.get()); + Telemetry::Accumulate(Telemetry::VIDEO_VISIBLE_PLAY_TIME_MS, key, + SECONDS_TO_MS(visiblePlayTimeS)); + // Also accumulate result in an "All" key. + Telemetry::Accumulate(Telemetry::VIDEO_VISIBLE_PLAY_TIME_MS, "All"_ns, + SECONDS_TO_MS(visiblePlayTimeS)); + + const uint32_t hiddenPercentage = + lround(invisiblePlayTimeS / totalVideoPlayTimeS * 100.0); + Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE, key, + hiddenPercentage); + // Also accumulate all percentages in an "All" key. + Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE, "All"_ns, + hiddenPercentage); + LOG("VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE = %u, keys: '%s' and 'All'", + hiddenPercentage, key.get()); + + const uint32_t videoDecodeSuspendPercentage = + lround(videoDecodeSuspendTimeS / totalVideoPlayTimeS * 100.0); + Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE, + key, videoDecodeSuspendPercentage); + Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE, + "All"_ns, videoDecodeSuspendPercentage); + LOG("VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE = %u, keys: '%s' and 'All'", + videoDecodeSuspendPercentage, key.get()); + + ReportResultForVideoFrameStatistics(totalVideoPlayTimeS, key); +#ifdef MOZ_WMF_CDM + if (mOwner->IsUsingWMFCDM()) { + ReportResultForMFCDMPlaybackIfNeeded(totalVideoPlayTimeS, key); + } +#endif +} + +#ifdef MOZ_WMF_CDM +void TelemetryProbesReporter::ReportResultForMFCDMPlaybackIfNeeded( + double aTotalPlayTimeS, const nsCString& aResolution) { + const auto keySystem = mOwner->GetKeySystem(); + if (!keySystem) { + NS_WARNING("Can not find key system to report telemetry for MFCDM!!"); + return; + } + glean::mfcdm::EmePlaybackExtra extraData; + extraData.keySystem = Some(NS_ConvertUTF16toUTF8(*keySystem)); + extraData.videoCodec = Some(mOwner->GetMediaInfo().mVideo.mMimeType); + extraData.resolution = Some(aResolution); + extraData.playedTime = Some(aTotalPlayTimeS); + + Maybe<uint64_t> renderedFrames; + Maybe<uint64_t> droppedFrames; + if (auto* stats = mOwner->GetFrameStatistics()) { + renderedFrames = Some(stats->GetPresentedFrames()); + droppedFrames = Some(stats->GetDroppedFrames()); + extraData.renderedFrames = Some(*renderedFrames); + extraData.droppedFrames = Some(*droppedFrames); + } + if (MOZ_LOG_TEST(gTelemetryProbesReporterLog, LogLevel::Debug)) { + nsPrintfCString logMessage{ + "MFCDM EME_Playback event, keySystem=%s, videoCodec=%s, resolution=%s, " + "playedTime=%s", + NS_ConvertUTF16toUTF8(*keySystem).get(), + mOwner->GetMediaInfo().mVideo.mMimeType.get(), aResolution.get(), + std::to_string(aTotalPlayTimeS).c_str()}; + if (renderedFrames) { + logMessage.Append( + nsPrintfCString{", renderedFrames=%" PRIu64, *renderedFrames}); + } + if (droppedFrames) { + logMessage.Append( + nsPrintfCString{", droppedFrames=%" PRIu64, *droppedFrames}); + } + LOG("%s", logMessage.get()); + } + glean::mfcdm::eme_playback.Record(Some(extraData)); +} +#endif + +void TelemetryProbesReporter::ReportResultForAudio() { + // Don't record telemetry for a media that didn't have a valid audio or video + // to play, or hasn't played. + if (!HasOwnerHadValidMedia() || (mTotalAudioPlayTime.PeekTotal() == 0.0 && + mTotalVideoPlayTime.PeekTotal() == 0.0)) { + return; + } + + nsCString key; + nsCString avKey; + const double totalAudioPlayTimeS = mTotalAudioPlayTime.GetAndClearTotal(); + const double inaudiblePlayTimeS = mInaudibleAudioPlayTime.GetAndClearTotal(); + const double mutedPlayTimeS = mMutedAudioPlayTime.GetAndClearTotal(); + const double audiblePlayTimeS = totalAudioPlayTimeS - inaudiblePlayTimeS; + const double unmutedPlayTimeS = totalAudioPlayTimeS - mutedPlayTimeS; + const uint32_t audiblePercentage = + lround(audiblePlayTimeS / totalAudioPlayTimeS * 100.0); + const uint32_t unmutedPercentage = + lround(unmutedPlayTimeS / totalAudioPlayTimeS * 100.0); + const double totalVideoPlayTimeS = mTotalVideoPlayTime.PeekTotal(); + + // Key semantics: + // - AV: Audible audio + video + // - IV: Inaudible audio + video + // - MV: Muted audio + video + // - A: Audible audio-only + // - I: Inaudible audio-only + // - M: Muted audio-only + // - V: Video-only + if (mMediaContent & MediaContent::MEDIA_HAS_AUDIO) { + if (audiblePercentage == 0) { + // Media element had an audio track, but it was inaudible throughout + key.AppendASCII("I"); + } else if (unmutedPercentage == 0) { + // Media element had an audio track, but it was muted throughout + key.AppendASCII("M"); + } else { + // Media element had an audible audio track + key.AppendASCII("A"); + } + avKey.AppendASCII("A"); + } + if (mMediaContent & MediaContent::MEDIA_HAS_VIDEO) { + key.AppendASCII("V"); + avKey.AppendASCII("V"); + } + + LOG("Key: %s", key.get()); + + if (mMediaContent & MediaContent::MEDIA_HAS_AUDIO) { + LOG("Audio:\ntotal: %lf\naudible: %lf\ninaudible: %lf\nmuted: " + "%lf\npercentage audible: " + "%u\npercentage unmuted: %u\n", + totalAudioPlayTimeS, audiblePlayTimeS, inaudiblePlayTimeS, + mutedPlayTimeS, audiblePercentage, unmutedPercentage); + Telemetry::Accumulate(Telemetry::MEDIA_PLAY_TIME_MS, key, + SECONDS_TO_MS(totalAudioPlayTimeS)); + Telemetry::Accumulate(Telemetry::MUTED_PLAY_TIME_PERCENT, avKey, + 100 - unmutedPercentage); + Telemetry::Accumulate(Telemetry::AUDIBLE_PLAY_TIME_PERCENT, avKey, + audiblePercentage); + } else { + MOZ_ASSERT(mMediaContent & MediaContent::MEDIA_HAS_VIDEO); + Telemetry::Accumulate(Telemetry::MEDIA_PLAY_TIME_MS, key, + SECONDS_TO_MS(totalVideoPlayTimeS)); + } +} + +void TelemetryProbesReporter::ReportResultForVideoFrameStatistics( + double aTotalPlayTimeS, const nsCString& key) { + FrameStatistics* stats = mOwner->GetFrameStatistics(); + if (!stats) { + return; + } + + FrameStatisticsData data = stats->GetFrameStatisticsData(); + if (data.mInterKeyframeCount != 0) { + const uint32_t average_ms = uint32_t( + std::min<uint64_t>(lround(double(data.mInterKeyframeSum_us) / + double(data.mInterKeyframeCount) / 1000.0), + UINT32_MAX)); + Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS, key, + average_ms); + Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS, "All"_ns, + average_ms); + LOG("VIDEO_INTER_KEYFRAME_AVERAGE_MS = %u, keys: '%s' and 'All'", + average_ms, key.get()); + + const uint32_t max_ms = uint32_t(std::min<uint64_t>( + (data.mInterKeyFrameMax_us + 500) / 1000, UINT32_MAX)); + Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS, key, max_ms); + Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS, "All"_ns, + max_ms); + LOG("VIDEO_INTER_KEYFRAME_MAX_MS = %u, keys: '%s' and 'All'", max_ms, + key.get()); + } else { + // Here, we have played *some* of the video, but didn't get more than 1 + // keyframe. Report '0' if we have played for longer than the video- + // decode-suspend delay (showing recovery would be difficult). + const uint32_t suspendDelay_ms = + StaticPrefs::media_suspend_background_video_delay_ms(); + if (uint32_t(aTotalPlayTimeS * 1000.0) > suspendDelay_ms) { + Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS, key, 0); + Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS, "All"_ns, + 0); + LOG("VIDEO_INTER_KEYFRAME_MAX_MS = 0 (only 1 keyframe), keys: '%s' and " + "'All'", + key.get()); + } + } + + const uint64_t parsedFrames = stats->GetParsedFrames(); + if (parsedFrames) { + const uint64_t droppedFrames = stats->GetDroppedFrames(); + MOZ_ASSERT(droppedFrames <= parsedFrames); + // Dropped frames <= total frames, so 'percentage' cannot be higher than + // 100 and therefore can fit in a uint32_t (that Telemetry takes). + const uint32_t percentage = 100 * droppedFrames / parsedFrames; + LOG("DROPPED_FRAMES_IN_VIDEO_PLAYBACK = %u", percentage); + Telemetry::Accumulate(Telemetry::VIDEO_DROPPED_FRAMES_PROPORTION, + percentage); + const uint32_t proportion = 10000 * droppedFrames / parsedFrames; + Telemetry::Accumulate( + Telemetry::VIDEO_DROPPED_FRAMES_PROPORTION_EXPONENTIAL, proportion); + + { + const uint64_t droppedFrames = stats->GetDroppedDecodedFrames(); + const uint32_t proportion = 10000 * droppedFrames / parsedFrames; + Telemetry::Accumulate( + Telemetry::VIDEO_DROPPED_DECODED_FRAMES_PROPORTION_EXPONENTIAL, + proportion); + } + { + const uint64_t droppedFrames = stats->GetDroppedSinkFrames(); + const uint32_t proportion = 10000 * droppedFrames / parsedFrames; + Telemetry::Accumulate( + Telemetry::VIDEO_DROPPED_SINK_FRAMES_PROPORTION_EXPONENTIAL, + proportion); + } + { + const uint64_t droppedFrames = stats->GetDroppedCompositorFrames(); + const uint32_t proportion = 10000 * droppedFrames / parsedFrames; + Telemetry::Accumulate( + Telemetry::VIDEO_DROPPED_COMPOSITOR_FRAMES_PROPORTION_EXPONENTIAL, + proportion); + } + } +} + +double TelemetryProbesReporter::GetTotalVideoPlayTimeInSeconds() const { + return mTotalVideoPlayTime.PeekTotal(); +} + +double TelemetryProbesReporter::GetTotalVideoHDRPlayTimeInSeconds() const { + return mTotalVideoHDRPlayTime.PeekTotal(); +} + +double TelemetryProbesReporter::GetVisibleVideoPlayTimeInSeconds() const { + return GetTotalVideoPlayTimeInSeconds() - + GetInvisibleVideoPlayTimeInSeconds(); +} + +double TelemetryProbesReporter::GetInvisibleVideoPlayTimeInSeconds() const { + return mInvisibleVideoPlayTime.PeekTotal(); +} + +double TelemetryProbesReporter::GetVideoDecodeSuspendedTimeInSeconds() const { + return mVideoDecodeSuspendedTime.PeekTotal(); +} + +double TelemetryProbesReporter::GetTotalAudioPlayTimeInSeconds() const { + return mTotalAudioPlayTime.PeekTotal(); +} + +double TelemetryProbesReporter::GetInaudiblePlayTimeInSeconds() const { + return mInaudibleAudioPlayTime.PeekTotal(); +} + +double TelemetryProbesReporter::GetMutedPlayTimeInSeconds() const { + return mMutedAudioPlayTime.PeekTotal(); +} + +double TelemetryProbesReporter::GetAudiblePlayTimeInSeconds() const { + return GetTotalAudioPlayTimeInSeconds() - GetInaudiblePlayTimeInSeconds(); +} + +#undef LOG +} // namespace mozilla |