Render text tracks
[WebKit-https.git] / Source / WebCore / html / HTMLMediaElement.h
1 /*
2  * Copyright (C) 2007, 2008, 2009, 2010, 2011 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 COMPUTER, 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 COMPUTER, 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 #ifndef HTMLMediaElement_h
27 #define HTMLMediaElement_h
28
29 #if ENABLE(VIDEO)
30
31 #include "HTMLElement.h"
32 #include "ActiveDOMObject.h"
33 #include "MediaCanStartListener.h"
34 #include "MediaControllerInterface.h"
35 #include "MediaPlayer.h"
36
37 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
38 #include "MediaPlayerProxy.h"
39 #endif
40
41 #if ENABLE(VIDEO_TRACK)
42 #include "PODIntervalTree.h"
43 #include "TextTrack.h"
44 #include "TextTrackCue.h"
45 #endif
46
47 namespace WebCore {
48
49 #if ENABLE(WEB_AUDIO)
50 class AudioSourceProvider;
51 class MediaElementAudioSourceNode;
52 #endif
53 class Event;
54 class HTMLSourceElement;
55 class HTMLTrackElement;
56 class MediaController;
57 class MediaControls;
58 class MediaError;
59 class KURL;
60 class TextTrackList;
61 class TimeRanges;
62 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
63 class Widget;
64 #endif
65 #if PLATFORM(MAC)
66 class DisplaySleepDisabler;
67 #endif
68
69 #if ENABLE(VIDEO_TRACK)
70 typedef PODIntervalTree<double, TextTrackCue*> CueIntervalTree;
71 typedef Vector<CueIntervalTree::IntervalType> CueList;
72 #endif
73
74 // FIXME: The inheritance from MediaPlayerClient here should be private inheritance.
75 // But it can't be until the Chromium WebMediaPlayerClientImpl class is fixed so it
76 // no longer depends on typecasting a MediaPlayerClient to an HTMLMediaElement.
77
78 class HTMLMediaElement : public HTMLElement, public MediaPlayerClient, private MediaCanStartListener, public ActiveDOMObject, public MediaControllerInterface
79 #if ENABLE(VIDEO_TRACK)
80     , private TextTrackClient
81 #endif
82 {
83 public:
84     MediaPlayer* player() const { return m_player.get(); }
85     
86     virtual bool isVideo() const = 0;
87     virtual bool hasVideo() const { return false; }
88     virtual bool hasAudio() const;
89
90     void rewind(float timeDelta);
91     void returnToRealtime();
92
93     // Eventually overloaded in HTMLVideoElement
94     virtual bool supportsFullscreen() const { return false; };
95
96     virtual bool supportsSave() const;
97     virtual bool supportsScanning() const;
98     
99     PlatformMedia platformMedia() const;
100 #if USE(ACCELERATED_COMPOSITING)
101     PlatformLayer* platformLayer() const;
102 #endif
103
104     enum LoadType {
105         MediaResource = 1 << 0,
106         TextTrackResource = 1 << 1
107     };
108     void scheduleLoad(LoadType);
109     
110     MediaPlayer::MovieLoadType movieLoadType() const;
111     
112     bool inActiveDocument() const { return m_inActiveDocument; }
113     
114 // DOM API
115 // error state
116     PassRefPtr<MediaError> error() const;
117
118 // network state
119     void setSrc(const String&);
120     const KURL& currentSrc() const { return m_currentSrc; }
121
122     enum NetworkState { NETWORK_EMPTY, NETWORK_IDLE, NETWORK_LOADING, NETWORK_NO_SOURCE };
123     NetworkState networkState() const;
124
125     String preload() const;    
126     void setPreload(const String&);
127
128     PassRefPtr<TimeRanges> buffered() const;
129     void load(ExceptionCode&);
130     String canPlayType(const String& mimeType) const;
131
132 // ready state
133     ReadyState readyState() const;
134     bool seeking() const;
135
136 // playback state
137     float currentTime() const;
138     void setCurrentTime(float, ExceptionCode&);
139     double initialTime() const;
140     float startTime() const;
141     float duration() const;
142     bool paused() const;
143     float defaultPlaybackRate() const;
144     void setDefaultPlaybackRate(float);
145     float playbackRate() const;
146     void setPlaybackRate(float);
147     void updatePlaybackRate();
148     bool webkitPreservesPitch() const;
149     void setWebkitPreservesPitch(bool);
150     PassRefPtr<TimeRanges> played();
151     PassRefPtr<TimeRanges> seekable() const;
152     bool ended() const;
153     bool autoplay() const;    
154     void setAutoplay(bool b);
155     bool loop() const;    
156     void setLoop(bool b);
157     void play();
158     void pause();
159
160 // captions
161     bool webkitHasClosedCaptions() const;
162     bool webkitClosedCaptionsVisible() const;
163     void setWebkitClosedCaptionsVisible(bool);
164
165 #if ENABLE(MEDIA_STATISTICS)
166 // Statistics
167     unsigned webkitAudioDecodedByteCount() const;
168     unsigned webkitVideoDecodedByteCount() const;
169 #endif
170
171 #if ENABLE(MEDIA_SOURCE)
172 //  Media Source.
173     const KURL& webkitMediaSourceURL() const { return m_mediaSourceURL; }
174     void webkitSourceAppend(PassRefPtr<Uint8Array> data, ExceptionCode&);
175     enum EndOfStreamStatus { EOS_NO_ERROR, EOS_NETWORK_ERR, EOS_DECODE_ERR };
176     void webkitSourceEndOfStream(unsigned short, ExceptionCode&);
177     enum SourceState { SOURCE_CLOSED, SOURCE_OPEN, SOURCE_ENDED };
178     SourceState webkitSourceState() const;
179     void setSourceState(SourceState);
180 #endif 
181
182 // controls
183     bool controls() const;
184     void setControls(bool);
185     float volume() const;
186     void setVolume(float, ExceptionCode&);
187     bool muted() const;
188     void setMuted(bool);
189
190     void togglePlayState();
191     void beginScrubbing();
192     void endScrubbing();
193     
194     bool canPlay() const;
195
196     float percentLoaded() const;
197
198 #if ENABLE(VIDEO_TRACK)
199     PassRefPtr<TextTrack> addTrack(const String& kind, const String& label, const String& language, ExceptionCode&);
200     PassRefPtr<TextTrack> addTrack(const String& kind, const String& label, ExceptionCode& ec) { return addTrack(kind, label, emptyString(), ec); }
201     PassRefPtr<TextTrack> addTrack(const String& kind, ExceptionCode& ec) { return addTrack(kind, emptyString(), emptyString(), ec); }
202
203     TextTrackList* textTracks();
204     CueList currentlyActiveCues() const { return m_currentlyActiveCues; }
205
206     virtual void trackWasAdded(HTMLTrackElement*);
207     virtual void trackWillBeRemoved(HTMLTrackElement*);
208     
209     void configureTextTrack(HTMLTrackElement*);
210     void configureTextTracks();
211     bool textTracksAreReady() const;
212     void configureTextTrackDisplay();
213
214     // TextTrackClient
215     virtual void textTrackReadyStateChanged(TextTrack*);
216     virtual void textTrackKindChanged(TextTrack*);
217     virtual void textTrackModeChanged(TextTrack*);
218     virtual void textTrackAddCues(TextTrack*, const TextTrackCueList*);
219     virtual void textTrackRemoveCues(TextTrack*, const TextTrackCueList*);
220     virtual void textTrackAddCue(TextTrack*, PassRefPtr<TextTrackCue>);
221     virtual void textTrackRemoveCue(TextTrack*, PassRefPtr<TextTrackCue>);
222 #endif
223
224 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
225     void allocateMediaPlayerIfNecessary();
226     void setNeedWidgetUpdate(bool needWidgetUpdate) { m_needWidgetUpdate = needWidgetUpdate; }
227     void deliverNotification(MediaPlayerProxyNotificationType notification);
228     void setMediaPlayerProxy(WebMediaPlayerProxy* proxy);
229     void getPluginProxyParams(KURL& url, Vector<String>& names, Vector<String>& values);
230     void createMediaPlayerProxy();
231     void updateWidget(PluginCreationOption);
232 #endif
233
234     bool hasSingleSecurityOrigin() const { return !m_player || m_player->hasSingleSecurityOrigin(); }
235     
236     bool isFullscreen() const;
237     void enterFullscreen();
238     void exitFullscreen();
239
240     bool hasClosedCaptions() const;
241     bool closedCaptionsVisible() const;
242     void setClosedCaptionsVisible(bool);
243
244     MediaControls* mediaControls();
245
246     void sourceWillBeRemoved(HTMLSourceElement*);
247     void sourceWasAdded(HTMLSourceElement*);
248
249     void privateBrowsingStateDidChange();
250
251     // Media cache management.
252     static void getSitesInMediaCache(Vector<String>&);
253     static void clearMediaCache();
254     static void clearMediaCacheForSite(const String&);
255
256     bool isPlaying() const { return m_playing; }
257
258     virtual bool hasPendingActivity() const;
259
260 #if ENABLE(WEB_AUDIO)
261     MediaElementAudioSourceNode* audioSourceNode() { return m_audioSourceNode; }
262     void setAudioSourceNode(MediaElementAudioSourceNode*);
263
264     AudioSourceProvider* audioSourceProvider();
265 #endif
266
267     enum InvalidURLAction { DoNothing, Complain };
268     bool isSafeToLoadURL(const KURL&, InvalidURLAction);
269
270     const String& mediaGroup() const;
271     void setMediaGroup(const String&);
272
273     MediaController* controller() const;
274     void setController(PassRefPtr<MediaController>);
275
276 protected:
277     HTMLMediaElement(const QualifiedName&, Document*, bool);
278     virtual ~HTMLMediaElement();
279
280     virtual void parseMappedAttribute(Attribute*);
281     virtual void finishParsingChildren();
282     virtual bool isURLAttribute(Attribute*) const;
283     virtual void attach();
284
285     virtual void willMoveToNewOwnerDocument();
286     virtual void didMoveToNewOwnerDocument();
287
288     enum DisplayMode { Unknown, None, Poster, PosterWaitingForVideo, Video };
289     DisplayMode displayMode() const { return m_displayMode; }
290     virtual void setDisplayMode(DisplayMode mode) { m_displayMode = mode; }
291     
292     virtual bool isMediaElement() const { return true; }
293
294     // Restrictions to change default behaviors.
295     enum BehaviorRestrictionFlags {
296         NoRestrictions = 0,
297         RequireUserGestureForLoadRestriction = 1 << 0,
298         RequireUserGestureForRateChangeRestriction = 1 << 1,
299         RequireUserGestureForFullscreenRestriction = 1 << 2,
300         RequirePageConsentToLoadMediaRestriction = 1 << 3,
301     };
302     typedef unsigned BehaviorRestrictions;
303     
304     bool userGestureRequiredForLoad() const { return m_restrictions & RequireUserGestureForLoadRestriction; }
305     bool userGestureRequiredForRateChange() const { return m_restrictions & RequireUserGestureForRateChangeRestriction; }
306     bool userGestureRequiredForFullscreen() const { return m_restrictions & RequireUserGestureForFullscreenRestriction; }
307     bool pageConsentRequiredForLoad() const { return m_restrictions & RequirePageConsentToLoadMediaRestriction; }
308     
309     void addBehaviorRestriction(BehaviorRestrictions restriction) { m_restrictions |= restriction; }
310     void removeBehaviorRestriction(BehaviorRestrictions restriction) { m_restrictions &= ~restriction; }
311     
312 private:
313     void createMediaPlayer();
314
315     virtual bool supportsFocus() const;
316     virtual void attributeChanged(Attribute*, bool preserveDecls);
317     virtual bool rendererIsNeeded(const NodeRenderingContext&);
318     virtual RenderObject* createRenderer(RenderArena*, RenderStyle*);
319     virtual void insertedIntoDocument();
320     virtual void removedFromDocument();
321     virtual void didRecalcStyle(StyleChange);
322     
323     virtual void defaultEventHandler(Event*);
324
325     virtual void didBecomeFullscreenElement();
326     virtual void willStopBeingFullscreenElement();
327
328     // ActiveDOMObject functions.
329     virtual bool canSuspend() const;
330     virtual void suspend(ReasonForSuspension);
331     virtual void resume();
332     virtual void stop();
333     
334     virtual void mediaVolumeDidChange();
335
336     virtual void updateDisplayState() { }
337     
338     void setReadyState(MediaPlayer::ReadyState);
339     void setNetworkState(MediaPlayer::NetworkState);
340
341     virtual Document* mediaPlayerOwningDocument();
342     virtual void mediaPlayerNetworkStateChanged(MediaPlayer*);
343     virtual void mediaPlayerReadyStateChanged(MediaPlayer*);
344     virtual void mediaPlayerTimeChanged(MediaPlayer*);
345     virtual void mediaPlayerVolumeChanged(MediaPlayer*);
346     virtual void mediaPlayerMuteChanged(MediaPlayer*);
347     virtual void mediaPlayerDurationChanged(MediaPlayer*);
348     virtual void mediaPlayerRateChanged(MediaPlayer*);
349     virtual void mediaPlayerPlaybackStateChanged(MediaPlayer*);
350     virtual void mediaPlayerSawUnsupportedTracks(MediaPlayer*);
351     virtual void mediaPlayerRepaint(MediaPlayer*);
352     virtual void mediaPlayerSizeChanged(MediaPlayer*);
353 #if USE(ACCELERATED_COMPOSITING)
354     virtual bool mediaPlayerRenderingCanBeAccelerated(MediaPlayer*);
355     virtual void mediaPlayerRenderingModeChanged(MediaPlayer*);
356 #endif
357     virtual void mediaPlayerEngineUpdated(MediaPlayer*);
358     
359     virtual void mediaPlayerFirstVideoFrameAvailable(MediaPlayer*);
360     virtual void mediaPlayerCharacteristicChanged(MediaPlayer*);
361
362 #if ENABLE(MEDIA_SOURCE)
363     virtual void mediaPlayerSourceOpened();
364     virtual String mediaPlayerSourceURL() const;
365 #endif
366
367     void loadTimerFired(Timer<HTMLMediaElement>*);
368     void asyncEventTimerFired(Timer<HTMLMediaElement>*);
369     void progressEventTimerFired(Timer<HTMLMediaElement>*);
370     void playbackProgressTimerFired(Timer<HTMLMediaElement>*);
371     void startPlaybackProgressTimer();
372     void startProgressEventTimer();
373     void stopPeriodicTimers();
374
375     void seek(float time, ExceptionCode&);
376     void finishSeek();
377     void checkIfSeekNeeded();
378     void addPlayedRange(float start, float end);
379     
380     void scheduleTimeupdateEvent(bool periodicEvent);
381     void scheduleEvent(const AtomicString& eventName);
382     
383     // loading
384     void selectMediaResource();
385     void loadResource(const KURL&, ContentType&);
386     void scheduleNextSourceChild();
387     void loadNextSourceChild();
388     void userCancelledLoad();
389     bool havePotentialSourceChild();
390     void noneSupported();
391     void mediaEngineError(PassRefPtr<MediaError> err);
392     void cancelPendingEventsAndCallbacks();
393     void waitForSourceChange();
394     void prepareToPlay();
395
396     KURL selectNextSourceChild(ContentType*, InvalidURLAction);
397     void mediaLoadingFailed(MediaPlayer::NetworkState);
398
399 #if ENABLE(VIDEO_TRACK)
400     void updateActiveTextTrackCues(float);
401     bool userIsInterestedInThisLanguage(const String&) const;
402     bool userIsInterestedInThisTrack(HTMLTrackElement*) const;
403     HTMLTrackElement* showingTrackWithSameKind(HTMLTrackElement*) const;
404 #endif
405
406     // These "internal" functions do not check user gesture restrictions.
407     void loadInternal();
408     void playInternal();
409     void pauseInternal();
410
411     void prepareForLoad();
412     void allowVideoRendering();
413
414     bool processingMediaPlayerCallback() const { return m_processingMediaPlayerCallback > 0; }
415     void beginProcessingMediaPlayerCallback() { ++m_processingMediaPlayerCallback; }
416     void endProcessingMediaPlayerCallback() { ASSERT(m_processingMediaPlayerCallback); --m_processingMediaPlayerCallback; }
417
418     void updateVolume();
419     void updatePlayState();
420     bool potentiallyPlaying() const;
421     bool endedPlayback() const;
422     bool stoppedDueToErrors() const;
423     bool pausedForUserInteraction() const;
424     bool couldPlayIfEnoughData() const;
425
426     float minTimeSeekable() const;
427     float maxTimeSeekable() const;
428
429     // Pauses playback without changing any states or generating events
430     void setPausedInternal(bool);
431
432     void setPlaybackRateInternal(float);
433
434     virtual void mediaCanStart();
435
436     void setShouldDelayLoadEvent(bool);
437
438     void invalidateCachedTime();
439     void refreshCachedTime() const;
440
441     bool hasMediaControls();
442     bool createMediaControls();
443     void configureMediaControls();
444
445     virtual void* preDispatchEventHandler(Event*);
446
447 #if ENABLE(MICRODATA)
448     virtual String itemValueText() const;
449     virtual void setItemValueText(const String&, ExceptionCode&);
450 #endif
451
452     void updateMediaController();
453     bool isBlocked() const;
454     bool isBlockedOnMediaController() const;
455     bool hasCurrentSrc() const { return !m_currentSrc.isEmpty(); }
456     bool isLiveStream() const { return movieLoadType() == MediaPlayer::LiveStream; }
457     bool isAutoplaying() const { return m_autoplaying; }
458
459     Timer<HTMLMediaElement> m_loadTimer;
460     Timer<HTMLMediaElement> m_asyncEventTimer;
461     Timer<HTMLMediaElement> m_progressEventTimer;
462     Timer<HTMLMediaElement> m_playbackProgressTimer;
463     Vector<RefPtr<Event> > m_pendingEvents;
464     RefPtr<TimeRanges> m_playedTimeRanges;
465
466     float m_playbackRate;
467     float m_defaultPlaybackRate;
468     bool m_webkitPreservesPitch;
469     NetworkState m_networkState;
470     ReadyState m_readyState;
471     ReadyState m_readyStateMaximum;
472     KURL m_currentSrc;
473
474     RefPtr<MediaError> m_error;
475
476     float m_volume;
477     float m_lastSeekTime;
478     
479     unsigned m_previousProgress;
480     double m_previousProgressTime;
481
482     // The last time a timeupdate event was sent (wall clock).
483     double m_lastTimeUpdateEventWallTime;
484
485     // The last time a timeupdate event was sent in movie time.
486     float m_lastTimeUpdateEventMovieTime;
487     
488     // Loading state.
489     enum LoadState { WaitingForSource, LoadingFromSrcAttr, LoadingFromSourceElement };
490     LoadState m_loadState;
491     HTMLSourceElement* m_currentSourceNode;
492     Node* m_nextChildNodeToConsider;
493     Node* sourceChildEndOfListValue() { return static_cast<Node*>(this); }
494
495     OwnPtr<MediaPlayer> m_player;
496 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
497     RefPtr<Widget> m_proxyWidget;
498 #endif
499
500     BehaviorRestrictions m_restrictions;
501     
502     MediaPlayer::Preload m_preload;
503
504     DisplayMode m_displayMode;
505
506     // Counter incremented while processing a callback from the media player, so we can avoid
507     // calling the media engine recursively.
508     int m_processingMediaPlayerCallback;
509
510 #if ENABLE(MEDIA_SOURCE)
511     KURL m_mediaSourceURL;
512     SourceState m_sourceState;
513 #endif
514
515     mutable float m_cachedTime;
516     mutable double m_cachedTimeWallClockUpdateTime;
517     mutable double m_minimumWallClockTimeToCacheMediaTime;
518     
519     typedef unsigned PendingLoadFlags;
520     PendingLoadFlags m_pendingLoadFlags;
521
522     bool m_playing : 1;
523     bool m_isWaitingUntilMediaCanStart : 1;
524     bool m_shouldDelayLoadEvent : 1;
525     bool m_haveFiredLoadedData : 1;
526     bool m_inActiveDocument : 1;
527     bool m_autoplaying : 1;
528     bool m_muted : 1;
529     bool m_paused : 1;
530     bool m_seeking : 1;
531
532     // data has not been loaded since sending a "stalled" event
533     bool m_sentStalledEvent : 1;
534
535     // time has not changed since sending an "ended" event
536     bool m_sentEndEvent : 1;
537
538     bool m_pausedInternal : 1;
539
540     // Not all media engines provide enough information about a file to be able to
541     // support progress events so setting m_sendProgressEvents disables them 
542     bool m_sendProgressEvents : 1;
543
544     bool m_isFullscreen : 1;
545     bool m_closedCaptionsVisible : 1;
546
547 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
548     bool m_needWidgetUpdate : 1;
549 #endif
550
551     bool m_dispatchingCanPlayEvent : 1;
552     bool m_loadInitiatedByUserGesture : 1;
553     bool m_completelyLoaded : 1;
554     bool m_havePreparedToPlay : 1;
555     bool m_parsingInProgress : 1;
556
557 #if ENABLE(VIDEO_TRACK)
558     bool m_tracksAreReady : 1;
559     bool m_haveVisibleTextTrack : 1;
560
561     RefPtr<TextTrackList> m_textTracks;
562     Vector<RefPtr<TextTrack> > m_textTracksWhenResourceSelectionBegan;
563     CueIntervalTree m_cueTree;
564     CueList m_currentlyActiveCues;
565 #endif
566
567 #if ENABLE(WEB_AUDIO)
568     // This is a weak reference, since m_audioSourceNode holds a reference to us.
569     // The value is set just after the MediaElementAudioSourceNode is created.
570     // The value is cleared in MediaElementAudioSourceNode::~MediaElementAudioSourceNode().
571     MediaElementAudioSourceNode* m_audioSourceNode;
572 #endif
573
574     String m_mediaGroup;
575     friend class MediaController;
576     RefPtr<MediaController> m_mediaController;
577
578 #if PLATFORM(MAC)
579     OwnPtr<DisplaySleepDisabler> m_sleepDisabler;
580 #endif
581 };
582
583 #if ENABLE(VIDEO_TRACK)
584 #ifndef NDEBUG
585 // Template specializations required by PodIntervalTree in debug mode.
586 template <>
587 struct ValueToString<double> {
588     static String string(const double value)
589     {
590         return String::number(value);
591     }
592 };
593
594 template <>
595 struct ValueToString<TextTrackCue*> {
596     static String string(TextTrackCue* const& cue)
597     {
598         return String::format("%p id=%s interval=%f-->%f cue=%s)", cue, cue->id().utf8().data(), cue->startTime(), cue->endTime(), cue->getCueAsSource().utf8().data());
599     }
600 };
601 #endif
602 #endif
603
604 } //namespace
605
606 #endif
607 #endif