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
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "VideoUtils.h"
6 
7 #include <functional>
8 #include <stdint.h>
9 
10 #include "CubebUtils.h"
11 #include "ImageContainer.h"
12 #include "MediaContainerType.h"
13 #include "MediaResource.h"
14 #include "TimeUnits.h"
15 #include "VorbisUtils.h"
16 #include "mozilla/Base64.h"
17 #include "mozilla/dom/ContentChild.h"
18 #include "mozilla/SchedulerGroup.h"
19 #include "mozilla/SharedThreadPool.h"
20 #include "mozilla/StaticPrefs_accessibility.h"
21 #include "mozilla/StaticPrefs_media.h"
22 #include "mozilla/TaskCategory.h"
23 #include "mozilla/TaskQueue.h"
24 #include "mozilla/Telemetry.h"
25 #include "nsCharSeparatedTokenizer.h"
26 #include "nsContentTypeParser.h"
27 #include "nsIConsoleService.h"
28 #include "nsINetworkLinkService.h"
29 #include "nsIRandomGenerator.h"
30 #include "nsMathUtils.h"
31 #include "nsNetCID.h"
32 #include "nsServiceManagerUtils.h"
33 #include "nsThreadUtils.h"
34 #include "AudioStream.h"
35 
36 namespace mozilla {
37 
38 using layers::PlanarYCbCrImage;
39 using media::TimeUnit;
40 
SaferMultDiv(int64_t aValue,uint64_t aMul,uint64_t aDiv)41 CheckedInt64 SaferMultDiv(int64_t aValue, uint64_t aMul, uint64_t aDiv) {
42   if (aMul > INT64_MAX || aDiv > INT64_MAX) {
43     return CheckedInt64(INT64_MAX) + 1;  // Return an invalid checked int.
44   }
45   int64_t mul = aMul;
46   int64_t div = aDiv;
47   int64_t major = aValue / div;
48   int64_t remainder = aValue % div;
49   return CheckedInt64(remainder) * mul / div + CheckedInt64(major) * mul;
50 }
51 
52 // Converts from number of audio frames to microseconds, given the specified
53 // audio rate.
FramesToUsecs(int64_t aFrames,uint32_t aRate)54 CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate) {
55   return SaferMultDiv(aFrames, USECS_PER_S, aRate);
56 }
57 
FramesToTimeUnit(int64_t aFrames,uint32_t aRate)58 TimeUnit FramesToTimeUnit(int64_t aFrames, uint32_t aRate) {
59   if (MOZ_UNLIKELY(!aRate)) {
60     return TimeUnit::Invalid();
61   }
62   int64_t major = aFrames / aRate;
63   int64_t remainder = aFrames % aRate;
64   return TimeUnit::FromMicroseconds(major) * USECS_PER_S +
65          (TimeUnit::FromMicroseconds(remainder) * USECS_PER_S) / aRate;
66 }
67 
68 // Converts from microseconds to number of audio frames, given the specified
69 // audio rate.
UsecsToFrames(int64_t aUsecs,uint32_t aRate)70 CheckedInt64 UsecsToFrames(int64_t aUsecs, uint32_t aRate) {
71   return SaferMultDiv(aUsecs, aRate, USECS_PER_S);
72 }
73 
74 // Format TimeUnit as number of frames at given rate.
TimeUnitToFrames(const TimeUnit & aTime,uint32_t aRate)75 CheckedInt64 TimeUnitToFrames(const TimeUnit& aTime, uint32_t aRate) {
76   return aTime.IsValid() ? UsecsToFrames(aTime.ToMicroseconds(), aRate)
77                          : CheckedInt64(INT64_MAX) + 1;
78 }
79 
SecondsToUsecs(double aSeconds,int64_t & aOutUsecs)80 nsresult SecondsToUsecs(double aSeconds, int64_t& aOutUsecs) {
81   if (aSeconds * double(USECS_PER_S) > double(INT64_MAX)) {
82     return NS_ERROR_FAILURE;
83   }
84   aOutUsecs = int64_t(aSeconds * double(USECS_PER_S));
85   return NS_OK;
86 }
87 
ConditionDimension(float aValue)88 static int32_t ConditionDimension(float aValue) {
89   // This will exclude NaNs and too-big values.
90   if (aValue > 1.0 && aValue <= float(INT32_MAX)) {
91     return int32_t(NS_round(aValue));
92   }
93   return 0;
94 }
95 
ScaleDisplayByAspectRatio(gfx::IntSize & aDisplay,float aAspectRatio)96 void ScaleDisplayByAspectRatio(gfx::IntSize& aDisplay, float aAspectRatio) {
97   if (aAspectRatio > 1.0) {
98     // Increase the intrinsic width
99     aDisplay.width = ConditionDimension(aAspectRatio * aDisplay.width);
100   } else {
101     // Increase the intrinsic height
102     aDisplay.height = ConditionDimension(aDisplay.height / aAspectRatio);
103   }
104 }
105 
BytesToTime(int64_t offset,int64_t length,int64_t durationUs)106 static int64_t BytesToTime(int64_t offset, int64_t length, int64_t durationUs) {
107   NS_ASSERTION(length > 0, "Must have positive length");
108   double r = double(offset) / double(length);
109   if (r > 1.0) {
110     r = 1.0;
111   }
112   return int64_t(double(durationUs) * r);
113 }
114 
GetEstimatedBufferedTimeRanges(mozilla::MediaResource * aStream,int64_t aDurationUsecs)115 media::TimeIntervals GetEstimatedBufferedTimeRanges(
116     mozilla::MediaResource* aStream, int64_t aDurationUsecs) {
117   media::TimeIntervals buffered;
118   // Nothing to cache if the media takes 0us to play.
119   if (aDurationUsecs <= 0 || !aStream) {
120     return buffered;
121   }
122 
123   // Special case completely cached files.  This also handles local files.
124   if (aStream->IsDataCachedToEndOfResource(0)) {
125     buffered += media::TimeInterval(TimeUnit::Zero(),
126                                     TimeUnit::FromMicroseconds(aDurationUsecs));
127     return buffered;
128   }
129 
130   int64_t totalBytes = aStream->GetLength();
131 
132   // If we can't determine the total size, pretend that we have nothing
133   // buffered. This will put us in a state of eternally-low-on-undecoded-data
134   // which is not great, but about the best we can do.
135   if (totalBytes <= 0) {
136     return buffered;
137   }
138 
139   int64_t startOffset = aStream->GetNextCachedData(0);
140   while (startOffset >= 0) {
141     int64_t endOffset = aStream->GetCachedDataEnd(startOffset);
142     // Bytes [startOffset..endOffset] are cached.
143     NS_ASSERTION(startOffset >= 0, "Integer underflow in GetBuffered");
144     NS_ASSERTION(endOffset >= 0, "Integer underflow in GetBuffered");
145 
146     int64_t startUs = BytesToTime(startOffset, totalBytes, aDurationUsecs);
147     int64_t endUs = BytesToTime(endOffset, totalBytes, aDurationUsecs);
148     if (startUs != endUs) {
149       buffered += media::TimeInterval(TimeUnit::FromMicroseconds(startUs),
150                                       TimeUnit::FromMicroseconds(endUs));
151     }
152     startOffset = aStream->GetNextCachedData(endOffset);
153   }
154   return buffered;
155 }
156 
DownmixStereoToMono(mozilla::AudioDataValue * aBuffer,uint32_t aFrames)157 void DownmixStereoToMono(mozilla::AudioDataValue* aBuffer, uint32_t aFrames) {
158   MOZ_ASSERT(aBuffer);
159   const int channels = 2;
160   for (uint32_t fIdx = 0; fIdx < aFrames; ++fIdx) {
161 #ifdef MOZ_SAMPLE_TYPE_FLOAT32
162     float sample = 0.0;
163 #else
164     int sample = 0;
165 #endif
166     // The sample of the buffer would be interleaved.
167     sample = (aBuffer[fIdx * channels] + aBuffer[fIdx * channels + 1]) * 0.5f;
168     aBuffer[fIdx * channels] = aBuffer[fIdx * channels + 1] = sample;
169   }
170 }
171 
DecideAudioPlaybackChannels(const AudioInfo & info)172 uint32_t DecideAudioPlaybackChannels(const AudioInfo& info) {
173   if (StaticPrefs::accessibility_monoaudio_enable()) {
174     return 1;
175   }
176 
177   if (StaticPrefs::media_forcestereo_enabled()) {
178     return 2;
179   }
180 
181   return info.mChannels;
182 }
183 
DecideAudioPlaybackSampleRate(const AudioInfo & aInfo)184 uint32_t DecideAudioPlaybackSampleRate(const AudioInfo& aInfo) {
185   bool resampling = StaticPrefs::media_resampling_enabled();
186 
187   uint32_t rate = 0;
188 
189   if (resampling) {
190     rate = 48000;
191   } else if (aInfo.mRate >= 44100) {
192     // The original rate is of good quality and we want to minimize unecessary
193     // resampling, so we let cubeb decide how to resample (if needed).
194     rate = aInfo.mRate;
195   } else {
196     // We will resample all data to match cubeb's preferred sampling rate.
197     rate = AudioStream::GetPreferredRate();
198   }
199   MOZ_DIAGNOSTIC_ASSERT(rate, "output rate can't be 0.");
200 
201   return rate;
202 }
203 
IsDefaultPlaybackDeviceMono()204 bool IsDefaultPlaybackDeviceMono() {
205   return CubebUtils::MaxNumberOfChannels() == 1;
206 }
207 
IsVideoContentType(const nsCString & aContentType)208 bool IsVideoContentType(const nsCString& aContentType) {
209   constexpr auto video = "video"_ns;
210   return FindInReadable(video, aContentType);
211 }
212 
IsValidVideoRegion(const gfx::IntSize & aFrame,const gfx::IntRect & aPicture,const gfx::IntSize & aDisplay)213 bool IsValidVideoRegion(const gfx::IntSize& aFrame,
214                         const gfx::IntRect& aPicture,
215                         const gfx::IntSize& aDisplay) {
216   return aFrame.width > 0 && aFrame.width <= PlanarYCbCrImage::MAX_DIMENSION &&
217          aFrame.height > 0 &&
218          aFrame.height <= PlanarYCbCrImage::MAX_DIMENSION &&
219          aFrame.width * aFrame.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
220          aPicture.width > 0 &&
221          aPicture.width <= PlanarYCbCrImage::MAX_DIMENSION &&
222          aPicture.x < PlanarYCbCrImage::MAX_DIMENSION &&
223          aPicture.x + aPicture.width < PlanarYCbCrImage::MAX_DIMENSION &&
224          aPicture.height > 0 &&
225          aPicture.height <= PlanarYCbCrImage::MAX_DIMENSION &&
226          aPicture.y < PlanarYCbCrImage::MAX_DIMENSION &&
227          aPicture.y + aPicture.height < PlanarYCbCrImage::MAX_DIMENSION &&
228          aPicture.width * aPicture.height <=
229              MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
230          aDisplay.width > 0 &&
231          aDisplay.width <= PlanarYCbCrImage::MAX_DIMENSION &&
232          aDisplay.height > 0 &&
233          aDisplay.height <= PlanarYCbCrImage::MAX_DIMENSION &&
234          aDisplay.width * aDisplay.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT;
235 }
236 
GetMediaThreadPool(MediaThreadType aType)237 already_AddRefed<SharedThreadPool> GetMediaThreadPool(MediaThreadType aType) {
238   const char* name;
239   uint32_t threads = 4;
240   switch (aType) {
241     case MediaThreadType::PLATFORM_DECODER:
242       name = "MediaPDecoder";
243       break;
244     case MediaThreadType::WEBRTC_CALL_THREAD:
245       name = "WebrtcCallThread";
246       threads = 1;
247       break;
248     case MediaThreadType::WEBRTC_WORKER:
249       name = "WebrtcWorker";
250       break;
251     case MediaThreadType::MDSM:
252       name = "MediaDecoderStateMachine";
253       threads = 1;
254       break;
255     case MediaThreadType::PLATFORM_ENCODER:
256       name = "MediaPEncoder";
257       break;
258     default:
259       MOZ_FALLTHROUGH_ASSERT("Unexpected MediaThreadType");
260     case MediaThreadType::SUPERVISOR:
261       name = "MediaSupervisor";
262       break;
263   }
264 
265   RefPtr<SharedThreadPool> pool =
266       SharedThreadPool::Get(nsDependentCString(name), threads);
267 
268   // Ensure a larger stack for platform decoder threads
269   if (aType == MediaThreadType::PLATFORM_DECODER) {
270     const uint32_t minStackSize = 512 * 1024;
271     uint32_t stackSize;
272     MOZ_ALWAYS_SUCCEEDS(pool->GetThreadStackSize(&stackSize));
273     if (stackSize < minStackSize) {
274       MOZ_ALWAYS_SUCCEEDS(pool->SetThreadStackSize(minStackSize));
275     }
276   }
277 
278   return pool.forget();
279 }
280 
ExtractVPXCodecDetails(const nsAString & aCodec,uint8_t & aProfile,uint8_t & aLevel,uint8_t & aBitDepth)281 bool ExtractVPXCodecDetails(const nsAString& aCodec, uint8_t& aProfile,
282                             uint8_t& aLevel, uint8_t& aBitDepth) {
283   uint8_t dummyChromaSubsampling = 1;
284   VideoColorSpace dummyColorspace;
285   return ExtractVPXCodecDetails(aCodec, aProfile, aLevel, aBitDepth,
286                                 dummyChromaSubsampling, dummyColorspace);
287 }
288 
ExtractVPXCodecDetails(const nsAString & aCodec,uint8_t & aProfile,uint8_t & aLevel,uint8_t & aBitDepth,uint8_t & aChromaSubsampling,VideoColorSpace & aColorSpace)289 bool ExtractVPXCodecDetails(const nsAString& aCodec, uint8_t& aProfile,
290                             uint8_t& aLevel, uint8_t& aBitDepth,
291                             uint8_t& aChromaSubsampling,
292                             VideoColorSpace& aColorSpace) {
293   // Assign default value.
294   aChromaSubsampling = 1;
295   auto splitter = aCodec.Split(u'.');
296   auto fieldsItr = splitter.begin();
297   auto fourCC = *fieldsItr;
298 
299   if (!fourCC.EqualsLiteral("vp09") && !fourCC.EqualsLiteral("vp08")) {
300     // Invalid 4CC
301     return false;
302   }
303   ++fieldsItr;
304   uint8_t* fields[] = {&aProfile,
305                        &aLevel,
306                        &aBitDepth,
307                        &aChromaSubsampling,
308                        &aColorSpace.mPrimaryId,
309                        &aColorSpace.mTransferId,
310                        &aColorSpace.mMatrixId,
311                        &aColorSpace.mRangeId};
312   int fieldsCount = 0;
313   nsresult rv;
314   for (; fieldsItr != splitter.end(); ++fieldsItr, ++fieldsCount) {
315     if (fieldsCount > 7) {
316       // No more than 8 fields are expected.
317       return false;
318     }
319     *(fields[fieldsCount]) =
320         static_cast<uint8_t>((*fieldsItr).ToInteger(&rv, 10));
321     // We got invalid field value, parsing error.
322     NS_ENSURE_SUCCESS(rv, false);
323   }
324   // Mandatory Fields
325   // <sample entry 4CC>.<profile>.<level>.<bitDepth>.
326   // Optional Fields
327   // <chromaSubsampling>.<colourPrimaries>.<transferCharacteristics>.
328   // <matrixCoefficients>.<videoFullRangeFlag>
329   // First three fields are mandatory(we have parsed 4CC).
330   if (fieldsCount < 3) {
331     // Invalid number of fields.
332     return false;
333   }
334   // Start to validate the parsing value.
335 
336   // profile should be 0,1,2 or 3.
337   // See https://www.webmproject.org/vp9/profiles/
338   // We don't support more than profile 2
339   if (aProfile > 2) {
340     // Invalid profile.
341     return false;
342   }
343 
344   // level, See https://www.webmproject.org/vp9/mp4/#semantics_1
345   switch (aLevel) {
346     case 10:
347     case 11:
348     case 20:
349     case 21:
350     case 30:
351     case 31:
352     case 40:
353     case 41:
354     case 50:
355     case 51:
356     case 52:
357     case 60:
358     case 61:
359     case 62:
360       break;
361     default:
362       // Invalid level.
363       return false;
364   }
365 
366   if (aBitDepth != 8 && aBitDepth != 10 && aBitDepth != 12) {
367     // Invalid bitDepth:
368     return false;
369   }
370 
371   if (fieldsCount == 3) {
372     // No more options.
373     return true;
374   }
375 
376   // chromaSubsampling should be 0,1,2,3...4~7 are reserved.
377   if (aChromaSubsampling > 3) {
378     return false;
379   }
380 
381   if (fieldsCount == 4) {
382     // No more options.
383     return true;
384   }
385 
386   // It is an integer that is defined by the "Colour primaries"
387   // section of ISO/IEC 23001-8:2016 Table 2.
388   // We treat reserved value as false case.
389   const auto& primaryId = aColorSpace.mPrimaryId;
390   if (primaryId == 0 || primaryId == 3 || primaryId > 22) {
391     // reserved value.
392     return false;
393   }
394   if (primaryId > 12 && primaryId < 22) {
395     // 13~21 are reserved values.
396     return false;
397   }
398 
399   if (fieldsCount == 5) {
400     // No more options.
401     return true;
402   }
403 
404   // It is an integer that is defined by the
405   // "Transfer characteristics" section of ISO/IEC 23001-8:2016 Table 3.
406   // We treat reserved value as false case.
407   const auto& transferId = aColorSpace.mTransferId;
408   if (transferId == 0 || transferId == 3 || transferId > 18) {
409     // reserved value.
410     return false;
411   }
412 
413   if (fieldsCount == 6) {
414     // No more options.
415     return true;
416   }
417 
418   // It is an integer that is defined by the
419   // "Matrix coefficients" section of ISO/IEC 23001-8:2016 Table 4.
420   // We treat reserved value as false case.
421   const auto& matrixId = aColorSpace.mMatrixId;
422   if (matrixId == 3 || matrixId > 11) {
423     return false;
424   }
425 
426   // If matrixCoefficients is 0 (RGB), then chroma subsampling MUST be 3
427   // (4:4:4).
428   if (matrixId == 0 && aChromaSubsampling != 3) {
429     return false;
430   }
431 
432   if (fieldsCount == 7) {
433     // No more options.
434     return true;
435   }
436 
437   // videoFullRangeFlag indicates the black level and range of the luma and
438   // chroma signals. 0 = legal range (e.g. 16-235 for 8 bit sample depth);
439   // 1 = full range (e.g. 0-255 for 8-bit sample depth).
440   const auto& rangeId = aColorSpace.mRangeId;
441   return rangeId <= 1;
442 }
443 
ExtractH264CodecDetails(const nsAString & aCodec,uint8_t & aProfile,uint8_t & aConstraint,uint8_t & aLevel)444 bool ExtractH264CodecDetails(const nsAString& aCodec, uint8_t& aProfile,
445                              uint8_t& aConstraint, uint8_t& aLevel) {
446   // H.264 codecs parameters have a type defined as avcN.PPCCLL, where
447   // N = avc type. avc3 is avcc with SPS & PPS implicit (within stream)
448   // PP = profile_idc, CC = constraint_set flags, LL = level_idc.
449   // We ignore the constraint_set flags, as it's not clear from any
450   // documentation what constraints the platform decoders support.
451   // See
452   // http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
453   // for more details.
454   if (aCodec.Length() != strlen("avc1.PPCCLL")) {
455     return false;
456   }
457 
458   // Verify the codec starts with "avc1." or "avc3.".
459   const nsAString& sample = Substring(aCodec, 0, 5);
460   if (!sample.EqualsASCII("avc1.") && !sample.EqualsASCII("avc3.")) {
461     return false;
462   }
463 
464   // Extract the profile_idc, constraint_flags and level_idc.
465   nsresult rv = NS_OK;
466   aProfile = Substring(aCodec, 5, 2).ToInteger(&rv, 16);
467   NS_ENSURE_SUCCESS(rv, false);
468 
469   // Constraint flags are stored on the 6 most significant bits, first two bits
470   // are reserved_zero_2bits.
471   aConstraint = Substring(aCodec, 7, 2).ToInteger(&rv, 16);
472   NS_ENSURE_SUCCESS(rv, false);
473 
474   aLevel = Substring(aCodec, 9, 2).ToInteger(&rv, 16);
475   NS_ENSURE_SUCCESS(rv, false);
476 
477   if (aLevel == 9) {
478     aLevel = H264_LEVEL_1_b;
479   } else if (aLevel <= 5) {
480     aLevel *= 10;
481   }
482 
483   return true;
484 }
485 
GenerateRandomName(nsCString & aOutSalt,uint32_t aLength)486 nsresult GenerateRandomName(nsCString& aOutSalt, uint32_t aLength) {
487   nsresult rv;
488   nsCOMPtr<nsIRandomGenerator> rg =
489       do_GetService("@mozilla.org/security/random-generator;1", &rv);
490   if (NS_FAILED(rv)) {
491     return rv;
492   }
493 
494   // For each three bytes of random data we will get four bytes of ASCII.
495   const uint32_t requiredBytesLength =
496       static_cast<uint32_t>((aLength + 3) / 4 * 3);
497 
498   uint8_t* buffer;
499   rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
500   if (NS_FAILED(rv)) {
501     return rv;
502   }
503 
504   nsCString temp;
505   nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer),
506                                    requiredBytesLength);
507   rv = Base64Encode(randomData, temp);
508   free(buffer);
509   buffer = nullptr;
510   if (NS_FAILED(rv)) {
511     return rv;
512   }
513 
514   aOutSalt = std::move(temp);
515   return NS_OK;
516 }
517 
GenerateRandomPathName(nsCString & aOutSalt,uint32_t aLength)518 nsresult GenerateRandomPathName(nsCString& aOutSalt, uint32_t aLength) {
519   nsresult rv = GenerateRandomName(aOutSalt, aLength);
520   if (NS_FAILED(rv)) {
521     return rv;
522   }
523 
524   // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
525   // to replace illegal characters -- notably '/'
526   aOutSalt.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_');
527   return NS_OK;
528 }
529 
CreateMediaDecodeTaskQueue(const char * aName)530 already_AddRefed<TaskQueue> CreateMediaDecodeTaskQueue(const char* aName) {
531   RefPtr<TaskQueue> queue = new TaskQueue(
532       GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), aName);
533   return queue.forget();
534 }
535 
Cancel()536 void SimpleTimer::Cancel() {
537   if (mTimer) {
538 #ifdef DEBUG
539     nsCOMPtr<nsIEventTarget> target;
540     mTimer->GetTarget(getter_AddRefs(target));
541     bool onCurrent;
542     nsresult rv = target->IsOnCurrentThread(&onCurrent);
543     MOZ_ASSERT(NS_SUCCEEDED(rv) && onCurrent);
544 #endif
545     mTimer->Cancel();
546     mTimer = nullptr;
547   }
548   mTask = nullptr;
549 }
550 
551 NS_IMETHODIMP
Notify(nsITimer * timer)552 SimpleTimer::Notify(nsITimer* timer) {
553   RefPtr<SimpleTimer> deathGrip(this);
554   if (mTask) {
555     mTask->Run();
556     mTask = nullptr;
557   }
558   return NS_OK;
559 }
560 
561 NS_IMETHODIMP
GetName(nsACString & aName)562 SimpleTimer::GetName(nsACString& aName) {
563   aName.AssignLiteral("SimpleTimer");
564   return NS_OK;
565 }
566 
Init(nsIRunnable * aTask,uint32_t aTimeoutMs,nsIEventTarget * aTarget)567 nsresult SimpleTimer::Init(nsIRunnable* aTask, uint32_t aTimeoutMs,
568                            nsIEventTarget* aTarget) {
569   nsresult rv;
570 
571   // Get target thread first, so we don't have to cancel the timer if it fails.
572   nsCOMPtr<nsIEventTarget> target;
573   if (aTarget) {
574     target = aTarget;
575   } else {
576     target = GetMainThreadEventTarget();
577     if (!target) {
578       return NS_ERROR_NOT_AVAILABLE;
579     }
580   }
581 
582   rv = NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, aTimeoutMs,
583                                nsITimer::TYPE_ONE_SHOT, target);
584   if (NS_FAILED(rv)) {
585     return rv;
586   }
587 
588   mTask = aTask;
589   return NS_OK;
590 }
591 
NS_IMPL_ISUPPORTS(SimpleTimer,nsITimerCallback,nsINamed)592 NS_IMPL_ISUPPORTS(SimpleTimer, nsITimerCallback, nsINamed)
593 
594 already_AddRefed<SimpleTimer> SimpleTimer::Create(nsIRunnable* aTask,
595                                                   uint32_t aTimeoutMs,
596                                                   nsIEventTarget* aTarget) {
597   RefPtr<SimpleTimer> t(new SimpleTimer());
598   if (NS_FAILED(t->Init(aTask, aTimeoutMs, aTarget))) {
599     return nullptr;
600   }
601   return t.forget();
602 }
603 
LogToBrowserConsole(const nsAString & aMsg)604 void LogToBrowserConsole(const nsAString& aMsg) {
605   if (!NS_IsMainThread()) {
606     nsString msg(aMsg);
607     nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
608         "LogToBrowserConsole", [msg]() { LogToBrowserConsole(msg); });
609     SchedulerGroup::Dispatch(TaskCategory::Other, task.forget());
610     return;
611   }
612   nsCOMPtr<nsIConsoleService> console(
613       do_GetService("@mozilla.org/consoleservice;1"));
614   if (!console) {
615     NS_WARNING("Failed to log message to console.");
616     return;
617   }
618   nsAutoString msg(aMsg);
619   console->LogStringMessage(msg.get());
620 }
621 
ParseCodecsString(const nsAString & aCodecs,nsTArray<nsString> & aOutCodecs)622 bool ParseCodecsString(const nsAString& aCodecs,
623                        nsTArray<nsString>& aOutCodecs) {
624   aOutCodecs.Clear();
625   bool expectMoreTokens = false;
626   nsCharSeparatedTokenizer tokenizer(aCodecs, ',');
627   while (tokenizer.hasMoreTokens()) {
628     const nsAString& token = tokenizer.nextToken();
629     expectMoreTokens = tokenizer.separatorAfterCurrentToken();
630     aOutCodecs.AppendElement(token);
631   }
632   if (expectMoreTokens) {
633     // Last codec name was empty
634     return false;
635   }
636   return true;
637 }
638 
ParseMIMETypeString(const nsAString & aMIMEType,nsString & aOutContainerType,nsTArray<nsString> & aOutCodecs)639 bool ParseMIMETypeString(const nsAString& aMIMEType,
640                          nsString& aOutContainerType,
641                          nsTArray<nsString>& aOutCodecs) {
642   nsContentTypeParser parser(aMIMEType);
643   nsresult rv = parser.GetType(aOutContainerType);
644   if (NS_FAILED(rv)) {
645     return false;
646   }
647 
648   nsString codecsStr;
649   parser.GetParameter("codecs", codecsStr);
650   return ParseCodecsString(codecsStr, aOutCodecs);
651 }
652 
653 template <int N>
StartsWith(const nsACString & string,const char (& prefix)[N])654 static bool StartsWith(const nsACString& string, const char (&prefix)[N]) {
655   if (N - 1 > string.Length()) {
656     return false;
657   }
658   return memcmp(string.Data(), prefix, N - 1) == 0;
659 }
660 
IsH264CodecString(const nsAString & aCodec)661 bool IsH264CodecString(const nsAString& aCodec) {
662   uint8_t profile = 0;
663   uint8_t constraint = 0;
664   uint8_t level = 0;
665   return ExtractH264CodecDetails(aCodec, profile, constraint, level);
666 }
667 
IsAACCodecString(const nsAString & aCodec)668 bool IsAACCodecString(const nsAString& aCodec) {
669   return aCodec.EqualsLiteral("mp4a.40.2") ||  // MPEG4 AAC-LC
670          aCodec.EqualsLiteral(
671              "mp4a.40.02") ||  // MPEG4 AAC-LC(for compatibility)
672          aCodec.EqualsLiteral("mp4a.40.5") ||  // MPEG4 HE-AAC
673          aCodec.EqualsLiteral(
674              "mp4a.40.05") ||                 // MPEG4 HE-AAC(for compatibility)
675          aCodec.EqualsLiteral("mp4a.67") ||   // MPEG2 AAC-LC
676          aCodec.EqualsLiteral("mp4a.40.29");  // MPEG4 HE-AACv2
677 }
678 
IsVP8CodecString(const nsAString & aCodec)679 bool IsVP8CodecString(const nsAString& aCodec) {
680   uint8_t profile = 0;
681   uint8_t level = 0;
682   uint8_t bitDepth = 0;
683   return aCodec.EqualsLiteral("vp8") || aCodec.EqualsLiteral("vp8.0") ||
684          (StartsWith(NS_ConvertUTF16toUTF8(aCodec), "vp08") &&
685           ExtractVPXCodecDetails(aCodec, profile, level, bitDepth));
686 }
687 
IsVP9CodecString(const nsAString & aCodec)688 bool IsVP9CodecString(const nsAString& aCodec) {
689   uint8_t profile = 0;
690   uint8_t level = 0;
691   uint8_t bitDepth = 0;
692   return aCodec.EqualsLiteral("vp9") || aCodec.EqualsLiteral("vp9.0") ||
693          (StartsWith(NS_ConvertUTF16toUTF8(aCodec), "vp09") &&
694           ExtractVPXCodecDetails(aCodec, profile, level, bitDepth));
695 }
696 
IsAV1CodecString(const nsAString & aCodec)697 bool IsAV1CodecString(const nsAString& aCodec) {
698   return aCodec.EqualsLiteral("av1") ||
699          StartsWith(NS_ConvertUTF16toUTF8(aCodec), "av01");
700 }
701 
CreateTrackInfoWithMIMEType(const nsACString & aCodecMIMEType)702 UniquePtr<TrackInfo> CreateTrackInfoWithMIMEType(
703     const nsACString& aCodecMIMEType) {
704   UniquePtr<TrackInfo> trackInfo;
705   if (StartsWith(aCodecMIMEType, "audio/")) {
706     trackInfo.reset(new AudioInfo());
707     trackInfo->mMimeType = aCodecMIMEType;
708   } else if (StartsWith(aCodecMIMEType, "video/")) {
709     trackInfo.reset(new VideoInfo());
710     trackInfo->mMimeType = aCodecMIMEType;
711   }
712   return trackInfo;
713 }
714 
CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(const nsACString & aCodecMIMEType,const MediaContainerType & aContainerType)715 UniquePtr<TrackInfo> CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
716     const nsACString& aCodecMIMEType,
717     const MediaContainerType& aContainerType) {
718   UniquePtr<TrackInfo> trackInfo = CreateTrackInfoWithMIMEType(aCodecMIMEType);
719   if (trackInfo) {
720     VideoInfo* videoInfo = trackInfo->GetAsVideoInfo();
721     if (videoInfo) {
722       Maybe<int32_t> maybeWidth = aContainerType.ExtendedType().GetWidth();
723       if (maybeWidth && *maybeWidth > 0) {
724         videoInfo->mImage.width = *maybeWidth;
725         videoInfo->mDisplay.width = *maybeWidth;
726       }
727       Maybe<int32_t> maybeHeight = aContainerType.ExtendedType().GetHeight();
728       if (maybeHeight && *maybeHeight > 0) {
729         videoInfo->mImage.height = *maybeHeight;
730         videoInfo->mDisplay.height = *maybeHeight;
731       }
732     } else if (trackInfo->GetAsAudioInfo()) {
733       AudioInfo* audioInfo = trackInfo->GetAsAudioInfo();
734       Maybe<int32_t> maybeChannels =
735           aContainerType.ExtendedType().GetChannels();
736       if (maybeChannels && *maybeChannels > 0) {
737         audioInfo->mChannels = *maybeChannels;
738       }
739       Maybe<int32_t> maybeSamplerate =
740           aContainerType.ExtendedType().GetSamplerate();
741       if (maybeSamplerate && *maybeSamplerate > 0) {
742         audioInfo->mRate = *maybeSamplerate;
743       }
744     }
745   }
746   return trackInfo;
747 }
748 
OnCellularConnection()749 bool OnCellularConnection() {
750   uint32_t linkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
751   if (XRE_IsContentProcess()) {
752     mozilla::dom::ContentChild* cpc =
753         mozilla::dom::ContentChild::GetSingleton();
754     if (!cpc) {
755       NS_WARNING("Can't get ContentChild singleton in content process!");
756       return false;
757     }
758     linkType = cpc->NetworkLinkType();
759   } else {
760     nsresult rv;
761     nsCOMPtr<nsINetworkLinkService> nls =
762         do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
763     if (NS_FAILED(rv)) {
764       NS_WARNING("Can't get nsINetworkLinkService.");
765       return false;
766     }
767 
768     rv = nls->GetLinkType(&linkType);
769     if (NS_FAILED(rv)) {
770       NS_WARNING("Can't get network link type.");
771       return false;
772     }
773   }
774 
775   switch (linkType) {
776     case nsINetworkLinkService::LINK_TYPE_UNKNOWN:
777     case nsINetworkLinkService::LINK_TYPE_ETHERNET:
778     case nsINetworkLinkService::LINK_TYPE_USB:
779     case nsINetworkLinkService::LINK_TYPE_WIFI:
780       return false;
781     case nsINetworkLinkService::LINK_TYPE_WIMAX:
782     case nsINetworkLinkService::LINK_TYPE_MOBILE:
783       return true;
784   }
785 
786   return false;
787 }
788 
789 }  // end namespace mozilla
790