1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "DecoderTraits.h"
8 #include "MediaContainerType.h"
9 #include "nsMimeTypes.h"
10 #include "mozilla/Preferences.h"
11 
12 #include "OggDecoder.h"
13 #include "OggDemuxer.h"
14 
15 #include "WebMDecoder.h"
16 #include "WebMDemuxer.h"
17 
18 #ifdef MOZ_ANDROID_HLS_SUPPORT
19 #  include "HLSDecoder.h"
20 #endif
21 #ifdef MOZ_FMP4
22 #  include "MP4Decoder.h"
23 #  include "MP4Demuxer.h"
24 #endif
25 #include "MediaFormatReader.h"
26 
27 #include "MP3Decoder.h"
28 #include "MP3Demuxer.h"
29 
30 #include "WaveDecoder.h"
31 #include "WaveDemuxer.h"
32 
33 #include "ADTSDecoder.h"
34 #include "ADTSDemuxer.h"
35 
36 #include "FlacDecoder.h"
37 #include "FlacDemuxer.h"
38 
39 #include "nsPluginHost.h"
40 
41 namespace mozilla {
42 
43 /* static */
IsHttpLiveStreamingType(const MediaContainerType & aType)44 bool DecoderTraits::IsHttpLiveStreamingType(const MediaContainerType& aType) {
45   const auto& mimeType = aType.Type();
46   return  // For m3u8.
47           // https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-10
48       mimeType == MEDIAMIMETYPE("application/vnd.apple.mpegurl") ||
49       // Some sites serve these as the informal m3u type.
50       mimeType == MEDIAMIMETYPE("application/x-mpegurl") ||
51       mimeType == MEDIAMIMETYPE("audio/mpegurl") ||
52       mimeType == MEDIAMIMETYPE("audio/x-mpegurl");
53 }
54 
55 /* static */
IsMatroskaType(const MediaContainerType & aType)56 bool DecoderTraits::IsMatroskaType(const MediaContainerType& aType) {
57   const auto& mimeType = aType.Type();
58   // https://matroska.org/technical/specs/notes.html
59   return mimeType == MEDIAMIMETYPE("audio/x-matroska") ||
60          mimeType == MEDIAMIMETYPE("video/x-matroska");
61 }
62 
63 /* static */
IsMP4SupportedType(const MediaContainerType & aType,DecoderDoctorDiagnostics * aDiagnostics)64 bool DecoderTraits::IsMP4SupportedType(const MediaContainerType& aType,
65                                        DecoderDoctorDiagnostics* aDiagnostics) {
66 #ifdef MOZ_FMP4
67   return MP4Decoder::IsSupportedType(aType, aDiagnostics);
68 #else
69   return false;
70 #endif
71 }
72 
CanHandleCodecsType(const MediaContainerType & aType,DecoderDoctorDiagnostics * aDiagnostics)73 static CanPlayStatus CanHandleCodecsType(
74     const MediaContainerType& aType, DecoderDoctorDiagnostics* aDiagnostics) {
75   // We should have been given a codecs string, though it may be empty.
76   MOZ_ASSERT(aType.ExtendedType().HaveCodecs());
77 
78   // Container type with the MIME type, no codecs.
79   const MediaContainerType mimeType(aType.Type());
80 
81   if (OggDecoder::IsSupportedType(mimeType)) {
82     if (OggDecoder::IsSupportedType(aType)) {
83       return CANPLAY_YES;
84     }
85     // We can only reach this position if a particular codec was requested,
86     // ogg is supported and working: the codec must be invalid.
87     return CANPLAY_NO;
88   }
89   if (WaveDecoder::IsSupportedType(MediaContainerType(mimeType))) {
90     if (WaveDecoder::IsSupportedType(aType)) {
91       return CANPLAY_YES;
92     }
93     // We can only reach this position if a particular codec was requested, wave
94     // is supported and working: the codec must be invalid or not supported.
95     return CANPLAY_NO;
96   }
97   if (WebMDecoder::IsSupportedType(mimeType)) {
98     if (WebMDecoder::IsSupportedType(aType)) {
99       return CANPLAY_YES;
100     }
101     // We can only reach this position if a particular codec was requested,
102     // webm is supported and working: the codec must be invalid.
103     return CANPLAY_NO;
104   }
105 #ifdef MOZ_FMP4
106   if (MP4Decoder::IsSupportedType(mimeType,
107                                   /* DecoderDoctorDiagnostics* */ nullptr)) {
108     if (MP4Decoder::IsSupportedType(aType, aDiagnostics)) {
109       return CANPLAY_YES;
110     }
111     // We can only reach this position if a particular codec was requested,
112     // fmp4 is supported and working: the codec must be invalid.
113     return CANPLAY_NO;
114   }
115 #endif
116   if (MP3Decoder::IsSupportedType(mimeType)) {
117     if (MP3Decoder::IsSupportedType(aType)) {
118       return CANPLAY_YES;
119     }
120     // We can only reach this position if a particular codec was requested,
121     // mp3 is supported and working: the codec must be invalid.
122     return CANPLAY_NO;
123   }
124   if (ADTSDecoder::IsSupportedType(mimeType)) {
125     if (ADTSDecoder::IsSupportedType(aType)) {
126       return CANPLAY_YES;
127     }
128     // We can only reach this position if a particular codec was requested,
129     // adts is supported and working: the codec must be invalid.
130     return CANPLAY_NO;
131   }
132   if (FlacDecoder::IsSupportedType(mimeType)) {
133     if (FlacDecoder::IsSupportedType(aType)) {
134       return CANPLAY_YES;
135     }
136     // We can only reach this position if a particular codec was requested,
137     // flac is supported and working: the codec must be invalid.
138     return CANPLAY_NO;
139   }
140 
141   return CANPLAY_MAYBE;
142 }
143 
CanHandleMediaType(const MediaContainerType & aType,DecoderDoctorDiagnostics * aDiagnostics)144 static CanPlayStatus CanHandleMediaType(
145     const MediaContainerType& aType, DecoderDoctorDiagnostics* aDiagnostics) {
146   if (DecoderTraits::IsHttpLiveStreamingType(aType)) {
147     Telemetry::Accumulate(Telemetry::MEDIA_HLS_CANPLAY_REQUESTED, true);
148   }
149 #ifdef MOZ_ANDROID_HLS_SUPPORT
150   if (HLSDecoder::IsSupportedType(aType)) {
151     Telemetry::Accumulate(Telemetry::MEDIA_HLS_CANPLAY_SUPPORTED, true);
152     return CANPLAY_MAYBE;
153   }
154 #endif
155 
156   if (DecoderTraits::IsMatroskaType(aType)) {
157     Telemetry::Accumulate(Telemetry::MEDIA_MKV_CANPLAY_REQUESTED, true);
158   }
159 
160   if (aType.ExtendedType().HaveCodecs()) {
161     CanPlayStatus result = CanHandleCodecsType(aType, aDiagnostics);
162     if (result == CANPLAY_NO || result == CANPLAY_YES) {
163       return result;
164     }
165   }
166 
167   // Container type with just the MIME type/subtype, no codecs.
168   const MediaContainerType mimeType(aType.Type());
169 
170   if (OggDecoder::IsSupportedType(mimeType)) {
171     return CANPLAY_MAYBE;
172   }
173   if (WaveDecoder::IsSupportedType(mimeType)) {
174     return CANPLAY_MAYBE;
175   }
176 #ifdef MOZ_FMP4
177   if (MP4Decoder::IsSupportedType(mimeType, aDiagnostics)) {
178     return CANPLAY_MAYBE;
179   }
180 #endif
181   if (WebMDecoder::IsSupportedType(mimeType)) {
182     return CANPLAY_MAYBE;
183   }
184   if (MP3Decoder::IsSupportedType(mimeType)) {
185     return CANPLAY_MAYBE;
186   }
187   if (ADTSDecoder::IsSupportedType(mimeType)) {
188     return CANPLAY_MAYBE;
189   }
190   if (FlacDecoder::IsSupportedType(mimeType)) {
191     return CANPLAY_MAYBE;
192   }
193   return CANPLAY_NO;
194 }
195 
196 /* static */
CanHandleContainerType(const MediaContainerType & aContainerType,DecoderDoctorDiagnostics * aDiagnostics)197 CanPlayStatus DecoderTraits::CanHandleContainerType(
198     const MediaContainerType& aContainerType,
199     DecoderDoctorDiagnostics* aDiagnostics) {
200   return CanHandleMediaType(aContainerType, aDiagnostics);
201 }
202 
203 /* static */
ShouldHandleMediaType(const char * aMIMEType,DecoderDoctorDiagnostics * aDiagnostics)204 bool DecoderTraits::ShouldHandleMediaType(
205     const char* aMIMEType, DecoderDoctorDiagnostics* aDiagnostics) {
206   Maybe<MediaContainerType> containerType = MakeMediaContainerType(aMIMEType);
207   if (!containerType) {
208     return false;
209   }
210 
211   if (WaveDecoder::IsSupportedType(*containerType)) {
212     // We should not return true for Wave types, since there are some
213     // Wave codecs actually in use in the wild that we don't support, and
214     // we should allow those to be handled by plugins or helper apps.
215     // Furthermore people can play Wave files on most platforms by other
216     // means.
217     return false;
218   }
219 
220   // If an external plugin which can handle quicktime video is available
221   // (and not disabled), prefer it over native playback as there several
222   // codecs found in the wild that we do not handle.
223   if (containerType->Type() == MEDIAMIMETYPE("video/quicktime")) {
224     RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
225     if (pluginHost &&
226         pluginHost->HavePluginForType(containerType->Type().AsString())) {
227       return false;
228     }
229   }
230 
231   return CanHandleMediaType(*containerType, aDiagnostics) != CANPLAY_NO;
232 }
233 
234 /* static */
CreateDemuxer(const MediaContainerType & aType,MediaResource * aResource)235 already_AddRefed<MediaDataDemuxer> DecoderTraits::CreateDemuxer(
236     const MediaContainerType& aType, MediaResource* aResource) {
237   MOZ_ASSERT(NS_IsMainThread());
238   RefPtr<MediaDataDemuxer> demuxer;
239 
240 #ifdef MOZ_FMP4
241   if (MP4Decoder::IsSupportedType(aType,
242                                   /* DecoderDoctorDiagnostics* */ nullptr)) {
243     demuxer = new MP4Demuxer(aResource);
244   } else
245 #endif
246       if (MP3Decoder::IsSupportedType(aType)) {
247     demuxer = new MP3Demuxer(aResource);
248   } else if (ADTSDecoder::IsSupportedType(aType)) {
249     demuxer = new ADTSDemuxer(aResource);
250   } else if (WaveDecoder::IsSupportedType(aType)) {
251     demuxer = new WAVDemuxer(aResource);
252   } else if (FlacDecoder::IsSupportedType(aType)) {
253     demuxer = new FlacDemuxer(aResource);
254   } else if (OggDecoder::IsSupportedType(aType)) {
255     demuxer = new OggDemuxer(aResource);
256   } else if (WebMDecoder::IsSupportedType(aType)) {
257     demuxer = new WebMDemuxer(aResource);
258   }
259 
260   return demuxer.forget();
261 }
262 
263 /* static */
CreateReader(const MediaContainerType & aType,MediaFormatReaderInit & aInit)264 MediaFormatReader* DecoderTraits::CreateReader(const MediaContainerType& aType,
265                                                MediaFormatReaderInit& aInit) {
266   MOZ_ASSERT(NS_IsMainThread());
267 
268   RefPtr<MediaDataDemuxer> demuxer = CreateDemuxer(aType, aInit.mResource);
269   if (!demuxer) {
270     return nullptr;
271   }
272 
273   MediaFormatReader* decoderReader = new MediaFormatReader(aInit, demuxer);
274 
275   if (OggDecoder::IsSupportedType(aType)) {
276     static_cast<OggDemuxer*>(demuxer.get())
277         ->SetChainingEvents(&decoderReader->TimedMetadataProducer(),
278                             &decoderReader->MediaNotSeekableProducer());
279   }
280 
281   return decoderReader;
282 }
283 
284 /* static */
IsSupportedInVideoDocument(const nsACString & aType)285 bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType) {
286   // Forbid playing media in video documents if the user has opted
287   // not to, using either the legacy WMF specific pref, or the newer
288   // catch-all pref.
289   if (!Preferences::GetBool("media.wmf.play-stand-alone", true) ||
290       !Preferences::GetBool("media.play-stand-alone", true)) {
291     return false;
292   }
293 
294   Maybe<MediaContainerType> type = MakeMediaContainerType(aType);
295   if (!type) {
296     return false;
297   }
298 
299   return OggDecoder::IsSupportedType(*type) ||
300          WebMDecoder::IsSupportedType(*type) ||
301 #ifdef MOZ_FMP4
302          MP4Decoder::IsSupportedType(*type,
303                                      /* DecoderDoctorDiagnostics* */ nullptr) ||
304 #endif
305          MP3Decoder::IsSupportedType(*type) ||
306          ADTSDecoder::IsSupportedType(*type) ||
307          FlacDecoder::IsSupportedType(*type) ||
308 #ifdef MOZ_ANDROID_HLS_SUPPORT
309          HLSDecoder::IsSupportedType(*type) ||
310 #endif
311          false;
312 }
313 
314 /* static */
GetTracksInfo(const MediaContainerType & aType)315 nsTArray<UniquePtr<TrackInfo>> DecoderTraits::GetTracksInfo(
316     const MediaContainerType& aType) {
317   // Container type with just the MIME type/subtype, no codecs.
318   const MediaContainerType mimeType(aType.Type());
319 
320   if (OggDecoder::IsSupportedType(mimeType)) {
321     return OggDecoder::GetTracksInfo(aType);
322   }
323   if (WaveDecoder::IsSupportedType(mimeType)) {
324     return WaveDecoder::GetTracksInfo(aType);
325   }
326 #ifdef MOZ_FMP4
327   if (MP4Decoder::IsSupportedType(mimeType, nullptr)) {
328     return MP4Decoder::GetTracksInfo(aType);
329   }
330 #endif
331   if (WebMDecoder::IsSupportedType(mimeType)) {
332     return WebMDecoder::GetTracksInfo(aType);
333   }
334   if (MP3Decoder::IsSupportedType(mimeType)) {
335     return MP3Decoder::GetTracksInfo(aType);
336   }
337   if (ADTSDecoder::IsSupportedType(mimeType)) {
338     return ADTSDecoder::GetTracksInfo(aType);
339   }
340   if (FlacDecoder::IsSupportedType(mimeType)) {
341     return FlacDecoder::GetTracksInfo(aType);
342   }
343   return nsTArray<UniquePtr<TrackInfo>>();
344 }
345 
346 }  // namespace mozilla
347