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