Replace WTF::move with WTFMove
[WebKit-https.git] / Source / WebCore / platform / graphics / MediaPlayer.cpp
1 /*
2  * Copyright (C) 2007-2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27
28 #if ENABLE(VIDEO)
29 #include "MediaPlayer.h"
30
31 #include "ContentType.h"
32 #include "Document.h"
33 #include "IntRect.h"
34 #include "Logging.h"
35 #include "MIMETypeRegistry.h"
36 #include "MediaPlayerPrivate.h"
37 #include "PlatformTimeRanges.h"
38 #include "Settings.h"
39 #include <wtf/NeverDestroyed.h>
40 #include <wtf/text/CString.h>
41
42 #if ENABLE(VIDEO_TRACK)
43 #include "InbandTextTrackPrivate.h"
44 #endif
45
46 #if ENABLE(MEDIA_SOURCE)
47 #include "MediaSourcePrivateClient.h"
48 #endif
49
50 #if ENABLE(MEDIA_STREAM)
51 #include "MediaStreamPrivate.h"
52 #endif
53
54 #if USE(GSTREAMER)
55 #include "MediaPlayerPrivateGStreamer.h"
56 #define PlatformMediaEngineClassName MediaPlayerPrivateGStreamer
57 #endif
58
59 #if USE(MEDIA_FOUNDATION)
60 #include "MediaPlayerPrivateMediaFoundation.h"
61 #define PlatformMediaEngineClassName MediaPlayerPrivateMediaFoundation
62 #endif
63
64 #if PLATFORM(COCOA)
65 #include "MediaPlayerPrivateQTKit.h"
66
67 #if USE(AVFOUNDATION)
68 #include "MediaPlayerPrivateAVFoundationObjC.h"
69 #endif
70
71 #if ENABLE(MEDIA_SOURCE) && USE(AVFOUNDATION)
72 #include "MediaPlayerPrivateMediaSourceAVFObjC.h"
73 #endif
74
75 #if ENABLE(MEDIA_STREAM) && USE(AVFOUNDATION)
76 #include "MediaPlayerPrivateMediaStreamAVFObjC.h"
77 #endif
78
79 #endif // PLATFORM(COCOA)
80
81 #if PLATFORM(WIN) && USE(AVFOUNDATION) && !USE(GSTREAMER)
82 #include "MediaPlayerPrivateAVFoundationCF.h"
83 #endif
84
85 namespace WebCore {
86
87 const PlatformMedia NoPlatformMedia = { PlatformMedia::None, {0} };
88
89 // a null player to make MediaPlayer logic simpler
90
91 class NullMediaPlayerPrivate : public MediaPlayerPrivateInterface {
92 public:
93     explicit NullMediaPlayerPrivate(MediaPlayer*) { }
94
95     void load(const String&) override { }
96 #if ENABLE(MEDIA_SOURCE)
97     void load(const String&, MediaSourcePrivateClient*) override { }
98 #endif
99 #if ENABLE(MEDIA_STREAM)
100     void load(MediaStreamPrivate&) override { }
101 #endif
102     void cancelLoad() override { }
103
104     void prepareToPlay() override { }
105     void play() override { }
106     void pause() override { }
107
108     PlatformMedia platformMedia() const override { return NoPlatformMedia; }
109     PlatformLayer* platformLayer() const override { return 0; }
110
111     FloatSize naturalSize() const override { return FloatSize(); }
112
113     bool hasVideo() const override { return false; }
114     bool hasAudio() const override { return false; }
115
116     void setVisible(bool) override { }
117
118     double durationDouble() const override { return 0; }
119
120     double currentTimeDouble() const override { return 0; }
121     void seekDouble(double) override { }
122     bool seeking() const override { return false; }
123
124     void setRateDouble(double) override { }
125     void setPreservesPitch(bool) override { }
126     bool paused() const override { return false; }
127
128     void setVolumeDouble(double) override { }
129
130     bool supportsMuting() const override { return false; }
131     void setMuted(bool) override { }
132
133     bool hasClosedCaptions() const override { return false; }
134     void setClosedCaptionsVisible(bool) override { };
135
136     MediaPlayer::NetworkState networkState() const override { return MediaPlayer::Empty; }
137     MediaPlayer::ReadyState readyState() const override { return MediaPlayer::HaveNothing; }
138
139     float maxTimeSeekable() const override { return 0; }
140     double minTimeSeekable() const override { return 0; }
141     std::unique_ptr<PlatformTimeRanges> buffered() const override { return std::make_unique<PlatformTimeRanges>(); }
142
143     unsigned long long totalBytes() const override { return 0; }
144     bool didLoadingProgress() const override { return false; }
145
146     void setSize(const IntSize&) override { }
147
148     void paint(GraphicsContext&, const FloatRect&) override { }
149
150     bool canLoadPoster() const override { return false; }
151     void setPoster(const String&) override { }
152
153     bool hasSingleSecurityOrigin() const override { return true; }
154
155 #if ENABLE(ENCRYPTED_MEDIA)
156     MediaPlayer::MediaKeyException generateKeyRequest(const String&, const unsigned char*, unsigned) override { return MediaPlayer::InvalidPlayerState; }
157     MediaPlayer::MediaKeyException addKey(const String&, const unsigned char*, unsigned, const unsigned char*, unsigned, const String&) override { return MediaPlayer::InvalidPlayerState; }
158     MediaPlayer::MediaKeyException cancelKeyRequest(const String&, const String&) override { return MediaPlayer::InvalidPlayerState; }
159 #endif
160 };
161
162 // engine support
163
164 struct MediaPlayerFactory {
165     CreateMediaEnginePlayer constructor;
166     MediaEngineSupportedTypes getSupportedTypes;
167     MediaEngineSupportsType supportsTypeAndCodecs;
168     MediaEngineGetSitesInMediaCache getSitesInMediaCache;
169     MediaEngineClearMediaCache clearMediaCache;
170     MediaEngineClearMediaCacheForSite clearMediaCacheForSite;
171     MediaEngineSupportsKeySystem supportsKeySystem;
172 };
173
174 static void addMediaEngine(CreateMediaEnginePlayer, MediaEngineSupportedTypes, MediaEngineSupportsType, MediaEngineGetSitesInMediaCache, MediaEngineClearMediaCache, MediaEngineClearMediaCacheForSite, MediaEngineSupportsKeySystem);
175
176 static bool haveMediaEnginesVector;
177
178 static Vector<MediaPlayerFactory>& mutableInstalledMediaEnginesVector()
179 {
180     static NeverDestroyed<Vector<MediaPlayerFactory>> installedEngines;
181     return installedEngines;
182 }
183
184 static void buildMediaEnginesVector()
185 {
186 #if USE(AVFOUNDATION)
187     if (Settings::isAVFoundationEnabled()) {
188
189 #if PLATFORM(COCOA)
190         MediaPlayerPrivateAVFoundationObjC::registerMediaEngine(addMediaEngine);
191 #endif
192
193 #if ENABLE(MEDIA_SOURCE)
194         MediaPlayerPrivateMediaSourceAVFObjC::registerMediaEngine(addMediaEngine);
195 #endif
196
197 #if ENABLE(MEDIA_STREAM)
198         MediaPlayerPrivateMediaStreamAVFObjC::registerMediaEngine(addMediaEngine);
199 #endif
200
201 #if PLATFORM(WIN)
202         MediaPlayerPrivateAVFoundationCF::registerMediaEngine(addMediaEngine);
203 #endif
204     }
205 #endif // USE(AVFOUNDATION)
206
207 #if PLATFORM(MAC)
208     if (Settings::isQTKitEnabled())
209         MediaPlayerPrivateQTKit::registerMediaEngine(addMediaEngine);
210 #endif
211
212 #if defined(PlatformMediaEngineClassName)
213     PlatformMediaEngineClassName::registerMediaEngine(addMediaEngine);
214 #endif
215
216     haveMediaEnginesVector = true;
217 }
218
219 static const Vector<MediaPlayerFactory>& installedMediaEngines()
220 {
221     if (!haveMediaEnginesVector)
222         buildMediaEnginesVector();
223     return mutableInstalledMediaEnginesVector();
224 }
225
226 static void addMediaEngine(CreateMediaEnginePlayer constructor, MediaEngineSupportedTypes getSupportedTypes, MediaEngineSupportsType supportsType,
227     MediaEngineGetSitesInMediaCache getSitesInMediaCache, MediaEngineClearMediaCache clearMediaCache, MediaEngineClearMediaCacheForSite clearMediaCacheForSite, MediaEngineSupportsKeySystem supportsKeySystem)
228 {
229     ASSERT(constructor);
230     ASSERT(getSupportedTypes);
231     ASSERT(supportsType);
232
233     mutableInstalledMediaEnginesVector().append(MediaPlayerFactory { constructor, getSupportedTypes, supportsType, getSitesInMediaCache, clearMediaCache, clearMediaCacheForSite, supportsKeySystem });
234 }
235
236 static const AtomicString& applicationOctetStream()
237 {
238     DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, applicationOctetStream, ("application/octet-stream", AtomicString::ConstructFromLiteral));
239     return applicationOctetStream;
240 }
241
242 static const AtomicString& textPlain()
243 {
244     DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, textPlain, ("text/plain", AtomicString::ConstructFromLiteral));
245     return textPlain;
246 }
247
248 static const AtomicString& codecs()
249 {
250     DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, codecs, ("codecs", AtomicString::ConstructFromLiteral));
251     return codecs;
252 }
253
254 static const MediaPlayerFactory* bestMediaEngineForSupportParameters(const MediaEngineSupportParameters& parameters, const MediaPlayerFactory* current = nullptr)
255 {
256     if (parameters.type.isEmpty() && !parameters.isMediaSource && !parameters.isMediaStream)
257         return nullptr;
258
259     // 4.8.10.3 MIME types - In the absence of a specification to the contrary, the MIME type "application/octet-stream"
260     // when used with parameters, e.g. "application/octet-stream;codecs=theora", is a type that the user agent knows 
261     // it cannot render.
262     if (parameters.type == applicationOctetStream()) {
263         if (!parameters.codecs.isEmpty())
264             return nullptr;
265     }
266
267     const MediaPlayerFactory* foundEngine = nullptr;
268     MediaPlayer::SupportsType supported = MediaPlayer::IsNotSupported;
269     for (auto& engine : installedMediaEngines()) {
270         if (current) {
271             if (current == &engine)
272                 current = nullptr;
273             continue;
274         }
275         MediaPlayer::SupportsType engineSupport = engine.supportsTypeAndCodecs(parameters);
276         if (engineSupport > supported) {
277             supported = engineSupport;
278             foundEngine = &engine;
279         }
280     }
281
282     return foundEngine;
283 }
284
285 static const MediaPlayerFactory* nextMediaEngine(const MediaPlayerFactory* current)
286 {
287     auto& engines = installedMediaEngines();
288     if (engines.isEmpty())
289         return nullptr;
290
291     if (!current) 
292         return &engines.first();
293
294     size_t currentIndex = current - &engines.first();
295     if (currentIndex + 1 >= engines.size())
296         return nullptr;
297
298     return &engines[currentIndex + 1];
299 }
300
301 // media player
302
303 MediaPlayer::MediaPlayer(MediaPlayerClient& client)
304     : m_client(client)
305     , m_reloadTimer(*this, &MediaPlayer::reloadTimerFired)
306     , m_private(std::make_unique<NullMediaPlayerPrivate>(this))
307     , m_currentMediaEngine(0)
308     , m_preload(Auto)
309     , m_visible(false)
310     , m_volume(1.0f)
311     , m_muted(false)
312     , m_preservesPitch(true)
313     , m_privateBrowsing(false)
314     , m_shouldPrepareToRender(false)
315     , m_contentMIMETypeWasInferredFromExtension(false)
316 {
317 }
318
319 MediaPlayer::~MediaPlayer()
320 {
321 }
322
323 bool MediaPlayer::load(const URL& url, const ContentType& contentType, const String& keySystem)
324 {
325     ASSERT(!m_reloadTimer.isActive());
326
327     m_contentMIMEType = contentType.type().lower();
328     m_contentTypeCodecs = contentType.parameter(codecs());
329     m_url = url;
330     m_keySystem = keySystem.lower();
331     m_contentMIMETypeWasInferredFromExtension = false;
332
333 #if ENABLE(MEDIA_SOURCE)
334     m_mediaSource = nullptr;
335 #endif
336 #if ENABLE(MEDIA_STREAM)
337     m_mediaStream = nullptr;
338 #endif
339
340     // If the MIME type is missing or is not meaningful, try to figure it out from the URL.
341     if (m_contentMIMEType.isEmpty() || m_contentMIMEType == applicationOctetStream() || m_contentMIMEType == textPlain()) {
342         if (m_url.protocolIsData())
343             m_contentMIMEType = mimeTypeFromDataURL(m_url.string());
344         else {
345             String lastPathComponent = url.lastPathComponent();
346             size_t pos = lastPathComponent.reverseFind('.');
347             if (pos != notFound) {
348                 String extension = lastPathComponent.substring(pos + 1);
349                 String mediaType = MIMETypeRegistry::getMediaMIMETypeForExtension(extension);
350                 if (!mediaType.isEmpty()) {
351                     m_contentMIMEType = mediaType;
352                     m_contentMIMETypeWasInferredFromExtension = true;
353                 }
354             }
355         }
356     }
357
358     loadWithNextMediaEngine(0);
359     return m_currentMediaEngine;
360 }
361
362 #if ENABLE(MEDIA_SOURCE)
363 bool MediaPlayer::load(const URL& url, const ContentType& contentType, MediaSourcePrivateClient* mediaSource)
364 {
365     ASSERT(!m_reloadTimer.isActive());
366     ASSERT(mediaSource);
367
368     m_mediaSource = mediaSource;
369     m_contentMIMEType = contentType.type().lower();
370     m_contentTypeCodecs = contentType.parameter(codecs());
371     m_url = url;
372     m_keySystem = "";
373     m_contentMIMETypeWasInferredFromExtension = false;
374     loadWithNextMediaEngine(0);
375     return m_currentMediaEngine;
376 }
377 #endif
378
379 #if ENABLE(MEDIA_STREAM)
380 bool MediaPlayer::load(MediaStreamPrivate* mediaStream)
381 {
382     ASSERT(!m_reloadTimer.isActive());
383     ASSERT(mediaStream);
384
385     m_mediaStream = mediaStream;
386     m_keySystem = "";
387     m_contentMIMEType = "";
388     m_contentMIMETypeWasInferredFromExtension = false;
389     loadWithNextMediaEngine(0);
390     return m_currentMediaEngine;
391 }
392 #endif
393
394 const MediaPlayerFactory* MediaPlayer::nextBestMediaEngine(const MediaPlayerFactory* current) const
395 {
396     MediaEngineSupportParameters parameters;
397     parameters.type = m_contentMIMEType;
398     parameters.codecs = m_contentTypeCodecs;
399     parameters.url = m_url;
400 #if ENABLE(ENCRYPTED_MEDIA)
401     parameters.keySystem = m_keySystem;
402 #endif
403 #if ENABLE(MEDIA_SOURCE)
404     parameters.isMediaSource = !!m_mediaSource;
405 #endif
406 #if ENABLE(MEDIA_STREAM)
407     parameters.isMediaStream = !!m_mediaStream;
408 #endif
409
410     return bestMediaEngineForSupportParameters(parameters, current);
411 }
412
413 void MediaPlayer::loadWithNextMediaEngine(const MediaPlayerFactory* current)
414 {
415 #if ENABLE(MEDIA_SOURCE) 
416 #define MEDIASOURCE m_mediaSource
417 #else
418 #define MEDIASOURCE 0
419 #endif
420
421 #if ENABLE(MEDIA_STREAM)
422 #define MEDIASTREAM m_mediaStream
423 #else
424 #define MEDIASTREAM 0
425 #endif
426
427     const MediaPlayerFactory* engine = nullptr;
428
429     if (!m_contentMIMEType.isEmpty() || MEDIASTREAM || MEDIASOURCE)
430         engine = nextBestMediaEngine(current);
431
432     // If no MIME type is specified or the type was inferred from the file extension, just use the next engine.
433     if (!engine && (m_contentMIMEType.isEmpty() || m_contentMIMETypeWasInferredFromExtension))
434         engine = nextMediaEngine(current);
435
436     // Don't delete and recreate the player unless it comes from a different engine.
437     if (!engine) {
438         LOG(Media, "MediaPlayer::loadWithNextMediaEngine - no media engine found for type \"%s\"", m_contentMIMEType.utf8().data());
439         m_currentMediaEngine = engine;
440         m_private = nullptr;
441     } else if (m_currentMediaEngine != engine) {
442         m_currentMediaEngine = engine;
443         m_private = engine->constructor(this);
444         m_client.mediaPlayerEngineUpdated(this);
445         m_private->setPrivateBrowsingMode(m_privateBrowsing);
446         m_private->setPreload(m_preload);
447         m_private->setPreservesPitch(preservesPitch());
448         if (m_shouldPrepareToRender)
449             m_private->prepareForRendering();
450     }
451
452     if (m_private) {
453 #if ENABLE(MEDIA_SOURCE)
454         if (m_mediaSource)
455             m_private->load(m_url.string(), m_mediaSource.get());
456         else
457 #endif
458 #if ENABLE(MEDIA_STREAM)
459         if (m_mediaStream)
460             m_private->load(*m_mediaStream);
461         else
462 #endif
463         m_private->load(m_url.string());
464     } else {
465         m_private = std::make_unique<NullMediaPlayerPrivate>(this);
466         m_client.mediaPlayerEngineUpdated(this);
467         m_client.mediaPlayerResourceNotSupported(this);
468     }
469 }
470
471 bool MediaPlayer::hasAvailableVideoFrame() const
472 {
473     return m_private->hasAvailableVideoFrame();
474 }
475
476 void MediaPlayer::prepareForRendering()
477 {
478     m_shouldPrepareToRender = true;
479     m_private->prepareForRendering();
480 }
481
482 bool MediaPlayer::canLoadPoster() const
483 {
484     return m_private->canLoadPoster();
485 }
486
487 void MediaPlayer::setPoster(const String& url)
488 {
489     m_private->setPoster(url);
490 }    
491
492 void MediaPlayer::cancelLoad()
493 {
494     m_private->cancelLoad();
495 }    
496
497 void MediaPlayer::prepareToPlay()
498 {
499     m_private->prepareToPlay();
500 }
501
502 void MediaPlayer::play()
503 {
504     m_private->play();
505 }
506
507 void MediaPlayer::pause()
508 {
509     m_private->pause();
510 }
511
512 void MediaPlayer::setShouldBufferData(bool shouldBuffer)
513 {
514     m_private->setShouldBufferData(shouldBuffer);
515 }
516
517 #if ENABLE(ENCRYPTED_MEDIA)
518 MediaPlayer::MediaKeyException MediaPlayer::generateKeyRequest(const String& keySystem, const unsigned char* initData, unsigned initDataLength)
519 {
520     return m_private->generateKeyRequest(keySystem.lower(), initData, initDataLength);
521 }
522
523 MediaPlayer::MediaKeyException MediaPlayer::addKey(const String& keySystem, const unsigned char* key, unsigned keyLength, const unsigned char* initData, unsigned initDataLength, const String& sessionId)
524 {
525     return m_private->addKey(keySystem.lower(), key, keyLength, initData, initDataLength, sessionId);
526 }
527
528 MediaPlayer::MediaKeyException MediaPlayer::cancelKeyRequest(const String& keySystem, const String& sessionId)
529 {
530     return m_private->cancelKeyRequest(keySystem.lower(), sessionId);
531 }
532 #endif
533
534 #if ENABLE(ENCRYPTED_MEDIA_V2)
535 std::unique_ptr<CDMSession> MediaPlayer::createSession(const String& keySystem, CDMSessionClient* client)
536 {
537     return m_private->createSession(keySystem, client);
538 }
539
540 void MediaPlayer::setCDMSession(CDMSession* session)
541 {
542     m_private->setCDMSession(session);
543 }
544
545 void MediaPlayer::keyAdded()
546 {
547     m_private->keyAdded();
548 }
549 #endif
550     
551 MediaTime MediaPlayer::duration() const
552 {
553     return m_private->durationMediaTime();
554 }
555
556 MediaTime MediaPlayer::startTime() const
557 {
558     return m_private->startTime();
559 }
560
561 MediaTime MediaPlayer::initialTime() const
562 {
563     return m_private->initialTime();
564 }
565
566 MediaTime MediaPlayer::currentTime() const
567 {
568     return m_private->currentMediaTime();
569 }
570
571 MediaTime MediaPlayer::getStartDate() const
572 {
573     return m_private->getStartDate();
574 }
575
576 void MediaPlayer::seekWithTolerance(const MediaTime& time, const MediaTime& negativeTolerance, const MediaTime& positiveTolerance)
577 {
578     m_private->seekWithTolerance(time, negativeTolerance, positiveTolerance);
579 }
580
581 void MediaPlayer::seek(const MediaTime& time)
582 {
583     m_private->seek(time);
584 }
585
586 bool MediaPlayer::paused() const
587 {
588     return m_private->paused();
589 }
590
591 bool MediaPlayer::seeking() const
592 {
593     return m_private->seeking();
594 }
595
596 bool MediaPlayer::supportsFullscreen() const
597 {
598     return m_private->supportsFullscreen();
599 }
600
601 bool MediaPlayer::canSaveMediaData() const
602 {
603     return m_private->canSaveMediaData();
604 }
605
606 bool MediaPlayer::supportsScanning() const
607 {
608     return m_private->supportsScanning();
609 }
610
611 bool MediaPlayer::requiresImmediateCompositing() const
612 {
613     return m_private->requiresImmediateCompositing();
614 }
615
616 FloatSize MediaPlayer::naturalSize()
617 {
618     return m_private->naturalSize();
619 }
620
621 bool MediaPlayer::hasVideo() const
622 {
623     return m_private->hasVideo();
624 }
625
626 bool MediaPlayer::hasAudio() const
627 {
628     return m_private->hasAudio();
629 }
630
631 bool MediaPlayer::inMediaDocument() const
632 {
633     return m_visible && m_client.mediaPlayerIsInMediaDocument();
634 }
635
636 PlatformMedia MediaPlayer::platformMedia() const
637 {
638     return m_private->platformMedia();
639 }
640
641 PlatformLayer* MediaPlayer::platformLayer() const
642 {
643     return m_private->platformLayer();
644 }
645     
646 #if PLATFORM(IOS)
647 void MediaPlayer::setVideoFullscreenLayer(PlatformLayer* layer)
648 {
649     m_private->setVideoFullscreenLayer(layer);
650 }
651
652 void MediaPlayer::setVideoFullscreenFrame(FloatRect frame)
653 {
654     m_private->setVideoFullscreenFrame(frame);
655 }
656
657 void MediaPlayer::setVideoFullscreenGravity(MediaPlayer::VideoGravity gravity)
658 {
659     m_private->setVideoFullscreenGravity(gravity);
660 }
661
662 void MediaPlayer::setVideoFullscreenMode(MediaPlayer::VideoFullscreenMode mode)
663 {
664     m_private->setVideoFullscreenMode(mode);
665 }
666
667 MediaPlayer::VideoFullscreenMode MediaPlayer::fullscreenMode() const
668 {
669     return m_client.mediaPlayerFullscreenMode();
670 }
671
672 NSArray* MediaPlayer::timedMetadata() const
673 {
674     return m_private->timedMetadata();
675 }
676
677 String MediaPlayer::accessLog() const
678 {
679     return m_private->accessLog();
680 }
681
682 String MediaPlayer::errorLog() const
683 {
684     return m_private->errorLog();
685 }
686 #endif
687
688 MediaPlayer::NetworkState MediaPlayer::networkState()
689 {
690     return m_private->networkState();
691 }
692
693 MediaPlayer::ReadyState MediaPlayer::readyState()
694 {
695     return m_private->readyState();
696 }
697
698 double MediaPlayer::volume() const
699 {
700     return m_volume;
701 }
702
703 void MediaPlayer::setVolume(double volume)
704 {
705     m_volume = volume;
706
707     if (m_private->supportsMuting() || !m_muted)
708         m_private->setVolumeDouble(volume);
709 }
710
711 bool MediaPlayer::muted() const
712 {
713     return m_muted;
714 }
715
716 void MediaPlayer::setMuted(bool muted)
717 {
718     m_muted = muted;
719
720     if (m_private->supportsMuting())
721         m_private->setMuted(muted);
722     else
723         m_private->setVolume(muted ? 0 : m_volume);
724 }
725
726 bool MediaPlayer::hasClosedCaptions() const
727 {
728     return m_private->hasClosedCaptions();
729 }
730
731 void MediaPlayer::setClosedCaptionsVisible(bool closedCaptionsVisible)
732 {
733     m_private->setClosedCaptionsVisible(closedCaptionsVisible);
734 }
735
736 double MediaPlayer::rate() const
737 {
738     return m_private->rate();
739 }
740
741 void MediaPlayer::setRate(double rate)
742 {
743     m_private->setRateDouble(rate);
744 }
745
746 double MediaPlayer::requestedRate() const
747 {
748     return m_client.mediaPlayerRequestedPlaybackRate();
749 }
750
751 bool MediaPlayer::preservesPitch() const
752 {
753     return m_preservesPitch;
754 }
755
756 void MediaPlayer::setPreservesPitch(bool preservesPitch)
757 {
758     m_preservesPitch = preservesPitch;
759     m_private->setPreservesPitch(preservesPitch);
760 }
761
762 std::unique_ptr<PlatformTimeRanges> MediaPlayer::buffered()
763 {
764     return m_private->buffered();
765 }
766
767 std::unique_ptr<PlatformTimeRanges> MediaPlayer::seekable()
768 {
769     return m_private->seekable();
770 }
771
772 MediaTime MediaPlayer::maxTimeSeekable()
773 {
774     return m_private->maxMediaTimeSeekable();
775 }
776
777 MediaTime MediaPlayer::minTimeSeekable()
778 {
779     return m_private->minMediaTimeSeekable();
780 }
781
782 bool MediaPlayer::didLoadingProgress()
783 {
784     return m_private->didLoadingProgress();
785 }
786
787 void MediaPlayer::setSize(const IntSize& size)
788
789     m_size = size;
790     m_private->setSize(size);
791 }
792
793 bool MediaPlayer::visible() const
794 {
795     return m_visible;
796 }
797
798 void MediaPlayer::setVisible(bool b)
799 {
800     m_visible = b;
801     m_private->setVisible(b);
802 }
803
804 MediaPlayer::Preload MediaPlayer::preload() const
805 {
806     return m_preload;
807 }
808
809 void MediaPlayer::setPreload(MediaPlayer::Preload preload)
810 {
811     m_preload = preload;
812     m_private->setPreload(preload);
813 }
814
815 void MediaPlayer::paint(GraphicsContext& p, const FloatRect& r)
816 {
817     m_private->paint(p, r);
818 }
819
820 void MediaPlayer::paintCurrentFrameInContext(GraphicsContext& p, const FloatRect& r)
821 {
822     m_private->paintCurrentFrameInContext(p, r);
823 }
824
825 bool MediaPlayer::copyVideoTextureToPlatformTexture(GraphicsContext3D* context, Platform3DObject texture, GC3Denum target, GC3Dint level, GC3Denum internalFormat, GC3Denum format, GC3Denum type, bool premultiplyAlpha, bool flipY)
826 {
827     return m_private->copyVideoTextureToPlatformTexture(context, texture, target, level, internalFormat, format, type, premultiplyAlpha, flipY);
828 }
829
830 PassNativeImagePtr MediaPlayer::nativeImageForCurrentTime()
831 {
832     return m_private->nativeImageForCurrentTime();
833 }
834
835 MediaPlayer::SupportsType MediaPlayer::supportsType(const MediaEngineSupportParameters& parameters, const MediaPlayerSupportsTypeClient* client)
836 {
837     // 4.8.10.3 MIME types - The canPlayType(type) method must return the empty string if type is a type that the 
838     // user agent knows it cannot render or is the type "application/octet-stream"
839     if (parameters.type == applicationOctetStream())
840         return IsNotSupported;
841
842     const MediaPlayerFactory* engine = bestMediaEngineForSupportParameters(parameters);
843     if (!engine)
844         return IsNotSupported;
845
846 #if PLATFORM(COCOA)
847     // YouTube will ask if the HTMLMediaElement canPlayType video/webm, then
848     // video/x-flv, then finally video/mp4, and will then load a URL of the first type
849     // in that list which returns "probably". When Perian is installed,
850     // MediaPlayerPrivateQTKit claims to support both video/webm and video/x-flv, but
851     // due to a bug in Perian, loading media in these formats will sometimes fail on
852     // slow connections. <https://bugs.webkit.org/show_bug.cgi?id=86409>
853     if (client && client->mediaPlayerNeedsSiteSpecificHacks()) {
854         String host = client->mediaPlayerDocumentHost();
855         if ((host.endsWith(".youtube.com", false) || equalIgnoringCase("youtube.com", host))
856             && (parameters.type.startsWith("video/webm", false) || parameters.type.startsWith("video/x-flv", false)))
857             return IsNotSupported;
858     }
859 #else
860     UNUSED_PARAM(client);
861 #endif
862
863     return engine->supportsTypeAndCodecs(parameters);
864 }
865
866 void MediaPlayer::getSupportedTypes(HashSet<String>& types)
867 {
868     for (auto& engine : installedMediaEngines()) {
869         HashSet<String> engineTypes;
870         engine.getSupportedTypes(engineTypes);
871         types.add(engineTypes.begin(), engineTypes.end());
872     }
873
874
875 bool MediaPlayer::isAvailable()
876 {
877     return !installedMediaEngines().isEmpty();
878
879
880 #if USE(NATIVE_FULLSCREEN_VIDEO)
881 void MediaPlayer::enterFullscreen()
882 {
883     m_private->enterFullscreen();
884 }
885
886 void MediaPlayer::exitFullscreen()
887 {
888     m_private->exitFullscreen();
889 }
890 #endif
891
892 #if ENABLE(WIRELESS_PLAYBACK_TARGET)
893 bool MediaPlayer::isCurrentPlaybackTargetWireless() const
894 {
895     return m_private->isCurrentPlaybackTargetWireless();
896 }
897
898 String MediaPlayer::wirelessPlaybackTargetName() const
899 {
900     return m_private->wirelessPlaybackTargetName();
901 }
902
903 MediaPlayer::WirelessPlaybackTargetType MediaPlayer::wirelessPlaybackTargetType() const
904 {
905     return m_private->wirelessPlaybackTargetType();
906 }
907
908 bool MediaPlayer::wirelessVideoPlaybackDisabled() const
909 {
910     return m_private->wirelessVideoPlaybackDisabled();
911 }
912
913 void MediaPlayer::setWirelessVideoPlaybackDisabled(bool disabled)
914 {
915     m_private->setWirelessVideoPlaybackDisabled(disabled);
916 }
917
918 void MediaPlayer::currentPlaybackTargetIsWirelessChanged()
919 {
920     m_client.mediaPlayerCurrentPlaybackTargetIsWirelessChanged(this);
921 }
922
923 bool MediaPlayer::canPlayToWirelessPlaybackTarget() const
924 {
925     return m_private->canPlayToWirelessPlaybackTarget();
926 }
927
928 void MediaPlayer::setWirelessPlaybackTarget(Ref<MediaPlaybackTarget>&& device)
929 {
930     m_private->setWirelessPlaybackTarget(WTFMove(device));
931 }
932
933 void MediaPlayer::setShouldPlayToPlaybackTarget(bool shouldPlay)
934 {
935     m_private->setShouldPlayToPlaybackTarget(shouldPlay);
936 }
937 #endif
938
939 double MediaPlayer::maxFastForwardRate() const
940 {
941     return m_private->maxFastForwardRate();
942 }
943
944 double MediaPlayer::minFastReverseRate() const
945 {
946     return m_private->minFastReverseRate();
947 }
948
949 #if USE(NATIVE_FULLSCREEN_VIDEO)
950 bool MediaPlayer::canEnterFullscreen() const
951 {
952     return m_private->canEnterFullscreen();
953 }
954 #endif
955
956 void MediaPlayer::acceleratedRenderingStateChanged()
957 {
958     m_private->acceleratedRenderingStateChanged();
959 }
960
961 bool MediaPlayer::supportsAcceleratedRendering() const
962 {
963     return m_private->supportsAcceleratedRendering();
964 }
965
966 bool MediaPlayer::shouldMaintainAspectRatio() const
967 {
968     return m_private->shouldMaintainAspectRatio();
969 }
970
971 void MediaPlayer::setShouldMaintainAspectRatio(bool maintainAspectRatio)
972 {
973     m_private->setShouldMaintainAspectRatio(maintainAspectRatio);
974 }
975
976 bool MediaPlayer::hasSingleSecurityOrigin() const
977 {
978     return m_private->hasSingleSecurityOrigin();
979 }
980
981 bool MediaPlayer::didPassCORSAccessCheck() const
982 {
983     return m_private->didPassCORSAccessCheck();
984 }
985
986 MediaPlayer::MovieLoadType MediaPlayer::movieLoadType() const
987 {
988     return m_private->movieLoadType();
989 }
990
991 MediaTime MediaPlayer::mediaTimeForTimeValue(const MediaTime& timeValue) const
992 {
993     return m_private->mediaTimeForTimeValue(timeValue);
994 }
995
996 double MediaPlayer::maximumDurationToCacheMediaTime() const
997 {
998     return m_private->maximumDurationToCacheMediaTime();
999 }
1000
1001 unsigned MediaPlayer::decodedFrameCount() const
1002 {
1003     return m_private->decodedFrameCount();
1004 }
1005
1006 unsigned MediaPlayer::droppedFrameCount() const
1007 {
1008     return m_private->droppedFrameCount();
1009 }
1010
1011 unsigned MediaPlayer::audioDecodedByteCount() const
1012 {
1013     return m_private->audioDecodedByteCount();
1014 }
1015
1016 unsigned MediaPlayer::videoDecodedByteCount() const
1017 {
1018     return m_private->videoDecodedByteCount();
1019 }
1020
1021 void MediaPlayer::reloadTimerFired()
1022 {
1023     m_private->cancelLoad();
1024     loadWithNextMediaEngine(m_currentMediaEngine);
1025 }
1026
1027 void MediaPlayer::getSitesInMediaCache(Vector<String>& sites)
1028 {
1029     for (auto& engine : installedMediaEngines()) {
1030         if (!engine.getSitesInMediaCache)
1031             continue;
1032         Vector<String> engineSites;
1033         engine.getSitesInMediaCache(engineSites);
1034         sites.appendVector(engineSites);
1035     }
1036 }
1037
1038 void MediaPlayer::clearMediaCache()
1039 {
1040     for (auto& engine : installedMediaEngines()) {
1041         if (engine.clearMediaCache)
1042             engine.clearMediaCache();
1043     }
1044 }
1045
1046 void MediaPlayer::clearMediaCacheForSite(const String& site)
1047 {
1048     for (auto& engine : installedMediaEngines()) {
1049         if (engine.clearMediaCacheForSite)
1050             engine.clearMediaCacheForSite(site);
1051     }
1052 }
1053
1054 bool MediaPlayer::supportsKeySystem(const String& keySystem, const String& mimeType)
1055 {
1056     for (auto& engine : installedMediaEngines()) {
1057         if (engine.supportsKeySystem && engine.supportsKeySystem(keySystem, mimeType))
1058             return true;
1059     }
1060     return false;
1061 }
1062
1063 void MediaPlayer::setPrivateBrowsingMode(bool privateBrowsingMode)
1064 {
1065     m_privateBrowsing = privateBrowsingMode;
1066     m_private->setPrivateBrowsingMode(m_privateBrowsing);
1067 }
1068
1069 // Client callbacks.
1070 void MediaPlayer::networkStateChanged()
1071 {
1072     // If more than one media engine is installed and this one failed before finding metadata,
1073     // let the next engine try.
1074     if (m_private->networkState() >= FormatError && m_private->readyState() < HaveMetadata) {
1075         m_client.mediaPlayerEngineFailedToLoad();
1076         if (installedMediaEngines().size() > 1 && (m_contentMIMEType.isEmpty() || nextBestMediaEngine(m_currentMediaEngine))) {
1077             m_reloadTimer.startOneShot(0);
1078             return;
1079         }
1080     }
1081     m_client.mediaPlayerNetworkStateChanged(this);
1082 }
1083
1084 void MediaPlayer::readyStateChanged()
1085 {
1086     m_client.mediaPlayerReadyStateChanged(this);
1087 }
1088
1089 void MediaPlayer::volumeChanged(double newVolume)
1090 {
1091 #if PLATFORM(IOS)
1092     UNUSED_PARAM(newVolume);
1093     m_volume = m_private->volume();
1094 #else
1095     m_volume = newVolume;
1096 #endif
1097     m_client.mediaPlayerVolumeChanged(this);
1098 }
1099
1100 void MediaPlayer::muteChanged(bool newMuted)
1101 {
1102     m_muted = newMuted;
1103     m_client.mediaPlayerMuteChanged(this);
1104 }
1105
1106 void MediaPlayer::timeChanged()
1107 {
1108     m_client.mediaPlayerTimeChanged(this);
1109 }
1110
1111 void MediaPlayer::sizeChanged()
1112 {
1113     m_client.mediaPlayerSizeChanged(this);
1114 }
1115
1116 void MediaPlayer::repaint()
1117 {
1118     m_client.mediaPlayerRepaint(this);
1119 }
1120
1121 void MediaPlayer::durationChanged()
1122 {
1123     m_client.mediaPlayerDurationChanged(this);
1124 }
1125
1126 void MediaPlayer::rateChanged()
1127 {
1128     m_client.mediaPlayerRateChanged(this);
1129 }
1130
1131 void MediaPlayer::playbackStateChanged()
1132 {
1133     m_client.mediaPlayerPlaybackStateChanged(this);
1134 }
1135
1136 void MediaPlayer::firstVideoFrameAvailable()
1137 {
1138     m_client.mediaPlayerFirstVideoFrameAvailable(this);
1139 }
1140
1141 void MediaPlayer::characteristicChanged()
1142 {
1143     m_client.mediaPlayerCharacteristicChanged(this);
1144 }
1145
1146 #if ENABLE(WEB_AUDIO)
1147 AudioSourceProvider* MediaPlayer::audioSourceProvider()
1148 {
1149     return m_private->audioSourceProvider();
1150 }
1151 #endif // WEB_AUDIO
1152
1153 #if ENABLE(ENCRYPTED_MEDIA)
1154 void MediaPlayer::keyAdded(const String& keySystem, const String& sessionId)
1155 {
1156     m_client.mediaPlayerKeyAdded(this, keySystem, sessionId);
1157 }
1158
1159 void MediaPlayer::keyError(const String& keySystem, const String& sessionId, MediaPlayerClient::MediaKeyErrorCode errorCode, unsigned short systemCode)
1160 {
1161     m_client.mediaPlayerKeyError(this, keySystem, sessionId, errorCode, systemCode);
1162 }
1163
1164 void MediaPlayer::keyMessage(const String& keySystem, const String& sessionId, const unsigned char* message, unsigned messageLength, const URL& defaultURL)
1165 {
1166     m_client.mediaPlayerKeyMessage(this, keySystem, sessionId, message, messageLength, defaultURL);
1167 }
1168
1169 bool MediaPlayer::keyNeeded(const String& keySystem, const String& sessionId, const unsigned char* initData, unsigned initDataLength)
1170 {
1171     return m_client.mediaPlayerKeyNeeded(this, keySystem, sessionId, initData, initDataLength);
1172 }
1173 #endif
1174
1175 #if ENABLE(ENCRYPTED_MEDIA_V2)
1176 RefPtr<ArrayBuffer> MediaPlayer::cachedKeyForKeyId(const String& keyId) const
1177 {
1178     return m_client.mediaPlayerCachedKeyForKeyId(keyId);
1179 }
1180
1181 bool MediaPlayer::keyNeeded(Uint8Array* initData)
1182 {
1183     return m_client.mediaPlayerKeyNeeded(this, initData);
1184 }
1185
1186 String MediaPlayer::mediaKeysStorageDirectory() const
1187 {
1188     return m_client.mediaPlayerMediaKeysStorageDirectory();
1189 }
1190 #endif
1191
1192 String MediaPlayer::referrer() const
1193 {
1194     return m_client.mediaPlayerReferrer();
1195 }
1196
1197 String MediaPlayer::userAgent() const
1198 {
1199     return m_client.mediaPlayerUserAgent();
1200 }
1201
1202 String MediaPlayer::engineDescription() const
1203 {
1204     if (!m_private)
1205         return String();
1206
1207     return m_private->engineDescription();
1208 }
1209
1210 long MediaPlayer::platformErrorCode() const
1211 {
1212     if (!m_private)
1213         return 0;
1214
1215     return m_private->platformErrorCode();
1216 }
1217
1218 #if PLATFORM(WIN) && USE(AVFOUNDATION)
1219 GraphicsDeviceAdapter* MediaPlayer::graphicsDeviceAdapter() const
1220 {
1221     return m_client.mediaPlayerGraphicsDeviceAdapter(this);
1222 }
1223 #endif
1224
1225 CachedResourceLoader* MediaPlayer::cachedResourceLoader()
1226 {
1227     return m_client.mediaPlayerCachedResourceLoader();
1228 }
1229
1230 PassRefPtr<PlatformMediaResourceLoader> MediaPlayer::createResourceLoader(std::unique_ptr<PlatformMediaResourceLoaderClient> client)
1231 {
1232     return m_client.mediaPlayerCreateResourceLoader(WTFMove(client));
1233 }
1234
1235 #if ENABLE(VIDEO_TRACK)
1236 void MediaPlayer::addAudioTrack(PassRefPtr<AudioTrackPrivate> track)
1237 {
1238     m_client.mediaPlayerDidAddAudioTrack(track);
1239 }
1240
1241 void MediaPlayer::removeAudioTrack(PassRefPtr<AudioTrackPrivate> track)
1242 {
1243     m_client.mediaPlayerDidRemoveAudioTrack(track);
1244 }
1245
1246 void MediaPlayer::addTextTrack(PassRefPtr<InbandTextTrackPrivate> track)
1247 {
1248     m_client.mediaPlayerDidAddTextTrack(track);
1249 }
1250
1251 void MediaPlayer::removeTextTrack(PassRefPtr<InbandTextTrackPrivate> track)
1252 {
1253     m_client.mediaPlayerDidRemoveTextTrack(track);
1254 }
1255
1256 void MediaPlayer::addVideoTrack(PassRefPtr<VideoTrackPrivate> track)
1257 {
1258     m_client.mediaPlayerDidAddVideoTrack(track);
1259 }
1260
1261 void MediaPlayer::removeVideoTrack(PassRefPtr<VideoTrackPrivate> track)
1262 {
1263     m_client.mediaPlayerDidRemoveVideoTrack(track);
1264 }
1265
1266 bool MediaPlayer::requiresTextTrackRepresentation() const
1267 {
1268     return m_private->requiresTextTrackRepresentation();
1269 }
1270
1271 void MediaPlayer::setTextTrackRepresentation(TextTrackRepresentation* representation)
1272 {
1273     m_private->setTextTrackRepresentation(representation);
1274 }
1275
1276 void MediaPlayer::syncTextTrackBounds()
1277 {
1278     m_private->syncTextTrackBounds();
1279 }
1280
1281 void MediaPlayer::tracksChanged()
1282 {
1283     m_private->tracksChanged();
1284 }
1285
1286 #if ENABLE(AVF_CAPTIONS)
1287 void MediaPlayer::notifyTrackModeChanged()
1288 {
1289     if (m_private)
1290         m_private->notifyTrackModeChanged();
1291 }
1292
1293 Vector<RefPtr<PlatformTextTrack>> MediaPlayer::outOfBandTrackSources()
1294 {
1295     return m_client.outOfBandTrackSources();
1296 }
1297 #endif
1298
1299 #endif // ENABLE(VIDEO_TRACK)
1300
1301 void MediaPlayer::resetMediaEngines()
1302 {
1303     mutableInstalledMediaEnginesVector().clear();
1304     haveMediaEnginesVector = false;
1305 }
1306
1307 #if USE(GSTREAMER)
1308 void MediaPlayer::simulateAudioInterruption()
1309 {
1310     if (!m_private)
1311         return;
1312
1313     m_private->simulateAudioInterruption();
1314 }
1315 #endif
1316
1317 String MediaPlayer::languageOfPrimaryAudioTrack() const
1318 {
1319     if (!m_private)
1320         return emptyString();
1321     
1322     return m_private->languageOfPrimaryAudioTrack();
1323 }
1324
1325 size_t MediaPlayer::extraMemoryCost() const
1326 {
1327     if (!m_private)
1328         return 0;
1329
1330     return m_private->extraMemoryCost();
1331 }
1332
1333 unsigned long long MediaPlayer::fileSize() const
1334 {
1335     if (!m_private)
1336         return 0;
1337     
1338     return m_private->fileSize();
1339 }
1340
1341 bool MediaPlayer::ended() const
1342 {
1343     return m_private->ended();
1344 }
1345
1346 #if ENABLE(MEDIA_SOURCE)
1347 unsigned long MediaPlayer::totalVideoFrames()
1348 {
1349     if (!m_private)
1350         return 0;
1351
1352     return m_private->totalVideoFrames();
1353 }
1354
1355 unsigned long MediaPlayer::droppedVideoFrames()
1356 {
1357     if (!m_private)
1358         return 0;
1359
1360     return m_private->droppedVideoFrames();
1361 }
1362
1363 unsigned long MediaPlayer::corruptedVideoFrames()
1364 {
1365     if (!m_private)
1366         return 0;
1367
1368     return m_private->corruptedVideoFrames();
1369 }
1370
1371 MediaTime MediaPlayer::totalFrameDelay()
1372 {
1373     if (!m_private)
1374         return MediaTime::zeroTime();
1375
1376     return m_private->totalFrameDelay();
1377 }
1378 #endif
1379
1380 bool MediaPlayer::shouldWaitForResponseToAuthenticationChallenge(const AuthenticationChallenge& challenge)
1381 {
1382     return m_client.mediaPlayerShouldWaitForResponseToAuthenticationChallenge(challenge);
1383 }
1384
1385 void MediaPlayer::handlePlaybackCommand(PlatformMediaSession::RemoteControlCommandType command)
1386 {
1387     m_client.mediaPlayerHandlePlaybackCommand(command);
1388 }
1389
1390 String MediaPlayer::sourceApplicationIdentifier() const
1391 {
1392     return m_client.mediaPlayerSourceApplicationIdentifier();
1393 }
1394
1395 Vector<String> MediaPlayer::preferredAudioCharacteristics() const
1396 {
1397     return m_client.mediaPlayerPreferredAudioCharacteristics();
1398 }
1399
1400 void MediaPlayerFactorySupport::callRegisterMediaEngine(MediaEngineRegister registerMediaEngine)
1401 {
1402     registerMediaEngine(addMediaEngine);
1403 }
1404
1405 bool MediaPlayer::doesHaveAttribute(const AtomicString& attribute, AtomicString* value) const
1406 {
1407     return m_client.doesHaveAttribute(attribute, value);
1408 }
1409
1410 #if PLATFORM(IOS)
1411 String MediaPlayer::mediaPlayerNetworkInterfaceName() const
1412 {
1413     return m_client.mediaPlayerNetworkInterfaceName();
1414 }
1415
1416 bool MediaPlayer::getRawCookies(const URL& url, Vector<Cookie>& cookies) const
1417 {
1418     return m_client.mediaPlayerGetRawCookies(url, cookies);
1419 }
1420 #endif
1421
1422 }
1423
1424 #endif